Skip to content

Commit 7e5dc7a

Browse files
committed
feat(config): ngConfig
1 parent 33bf94d commit 7e5dc7a

File tree

8 files changed

+686
-98
lines changed

8 files changed

+686
-98
lines changed

addon/ng2/commands/get.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,30 @@
11
import * as chalk from 'chalk';
22
import * as Command from 'ember-cli/lib/models/command';
3-
import {CliConfig} from '../models/config';
3+
import {Config} from '../models/config';
44

55

66
const GetCommand = Command.extend({
77
name: 'get',
8-
description: 'Set a value in the configuration.',
9-
works: 'outsideProject',
8+
description: 'Get a value from the configuration.',
9+
works: 'everywhere',
1010

1111
availableOptions: [],
1212

1313
run: function (commandOptions, rawArgs): Promise<void> {
1414
return new Promise(resolve => {
15-
const value = new CliConfig().get(rawArgs[0]);
16-
if (value === null) {
15+
const config = new Config();
16+
config.validatePath(rawArgs[0]);
17+
const val = config.get(config.config, rawArgs[0]);
18+
19+
if (val === null) {
1720
console.error(chalk.red('Value cannot be found.'));
18-
} else if (typeof value == 'object') {
19-
console.log(JSON.stringify(value));
2021
} else {
21-
console.log(value);
22+
console.log(val);
2223
}
24+
2325
resolve();
2426
});
2527
}
2628
});
2729

2830
module.exports = GetCommand;
29-
module.exports.overrideCore = true;

addon/ng2/commands/set.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,30 @@
11
import * as Command from 'ember-cli/lib/models/command';
2-
import {CliConfig} from '../models/config';
2+
import {Config} from '../models/config';
33

44

55
const SetCommand = Command.extend({
66
name: 'set',
77
description: 'Set a value in the configuration.',
8-
works: 'outsideProject',
8+
works: 'everywhere',
99

1010
availableOptions: [
1111
{ name: 'global', type: Boolean, default: false, aliases: ['g'] },
1212
],
1313

1414
run: function (commandOptions, rawArgs): Promise<void> {
1515
return new Promise(resolve => {
16-
const config = new CliConfig();
17-
config.set(rawArgs[0], rawArgs[1], commandOptions.force);
16+
let config = new Config();
17+
18+
for (let arg of rawArgs) {
19+
let [key, value] = arg.split('=');
20+
config.validatePath(key);
21+
config.set(key, value);
22+
}
23+
1824
config.save();
1925
resolve();
2026
});
2127
}
2228
});
2329

2430
module.exports = SetCommand;
25-
module.exports.overrideCore = true;

addon/ng2/models/config.ts

Lines changed: 141 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,125 +1,186 @@
11
import * as fs from 'fs';
22
import * as path from 'path';
3-
3+
import * as Proxy from 'harmony-proxy';
4+
import * as Reflect from 'harmony-reflect';
5+
import * as ObjectAssign from '../utilities/object-assign';
46

57
export const CLI_CONFIG_FILE_NAME = 'angular-cli.json';
68

9+
export const ArrayMethods: Array<string> = ['pop', 'push', 'reverse', 'shift', 'sort', 'unshift'];
710

8-
export interface CliConfigJson {
9-
routes?: { [name: string]: any },
10-
packages?: { [name: string]: any }
11-
}
11+
export const handler = {
12+
get: function(target: any, key: any, receiver: any) {
13+
if (key === 'toJSON') {
14+
return () => target;
15+
}
1216

17+
if (key === 'length') {
18+
return;
19+
}
1320

14-
function _findUp(name: string, from: string) {
15-
let currentDir = from;
16-
while (currentDir && currentDir != '/') {
17-
const p = path.join(currentDir, name);
18-
if (fs.existsSync(p)) {
19-
return p;
21+
if (key === 'inspect') {
22+
return target;
2023
}
2124

22-
currentDir = path.resolve(currentDir, '..');
23-
}
25+
if (target.type === 'array') {
26+
const arr: Array<any> = [];
27+
arr[key].call(target.enum);
28+
}
2429

25-
return null;
26-
}
30+
if (ArrayMethods.indexOf(key) === -1) {
31+
if (!(key in target)) {
32+
target[key] = new Proxy({ type: 'object' }, handler);
33+
}
34+
return Reflect.get(target, key, receiver);
35+
} else {
36+
return Reflect.get([], key, receiver);
37+
}
38+
},
39+
set: function(target: any, key: any, value: any) {
40+
if (key === 'length') {
41+
return;
42+
}
2743

44+
if (!isNaN(key) && parseInt(key, 10) === 0) {
45+
if (!target.enum) {
46+
target.type = 'array';
47+
target.enum = [];
48+
}
2849

29-
export class CliConfig {
30-
constructor(private _config: CliConfigJson = CliConfig.fromProject()) {}
50+
if (value) {
51+
target.enum.push(value);
52+
}
53+
} else {
54+
if (target[key] && target[key].type) {
55+
let type: string = target[key].type;
56+
let assigningType: string;
57+
58+
if (!value && value === null) {
59+
assigningType = 'null';
60+
} else if (value && Array.isArray(value) && typeof value !== 'string') {
61+
assigningType = 'array';
62+
} else {
63+
assigningType = typeof value;
64+
}
3165

32-
save(path: string = CliConfig.configFilePath()) {
33-
if (!path) {
34-
throw new Error('Could not find config path.');
35-
}
66+
if (type !== assigningType) {
67+
throw new Error(`Cannot assign value of type '${assigningType}' to an property with type '${type}'.`);
68+
}
69+
}
3670

37-
fs.writeFileSync(path, JSON.stringify(this._config, null, 2), { encoding: 'utf-8' });
71+
if (!value && value === null) {
72+
target[key] = { type: 'null', value: value };
73+
} else if (value && Array.isArray(value) && typeof value !== 'string') {
74+
target[key] = { type: 'array', enum: value };
75+
} else {
76+
if (typeof value === 'object' && Object.getOwnPropertyNames(value).length === 0) {
77+
target[key] = { type: typeof value };
78+
} else {
79+
target[key] = { type: typeof value, value: value };
80+
}
81+
}
82+
}
3883
}
84+
};
85+
86+
export interface ConfigJson {
87+
routes?: { [name: string]: any },
88+
packages?: { [name: string]: any }
89+
}
3990

40-
set(jsonPath: string, value: any, force: boolean = false): boolean {
41-
let { parent, name, remaining } = this._findParent(jsonPath);
42-
while (force && remaining) {
43-
if (remaining.indexOf('.') != -1) {
44-
// Create an empty map.
45-
// TODO: create the object / array based on the Schema of the configuration.
46-
parent[name] = {};
91+
export class Config {
92+
path: ConfigJson;
93+
config: Proxy;
94+
95+
constructor(path?: string) {
96+
if (path) {
97+
try {
98+
fs.accessSync(path);
99+
this.path = path;
100+
this.config = new Proxy({}, handler);
101+
} catch (e) {
102+
throw new Error(`${path} not found.`);
47103
}
104+
} else {
105+
this.path = this._configFilePath();
106+
this.config = new Proxy(this._fromProject(), handler);
107+
}
108+
}
48109

110+
public save(): void {
111+
try {
112+
let config = ObjectAssign({}, JSON.parse(fs.readFileSync(this.path, 'utf8')), this.config.toJSON());
113+
fs.writeFileSync(this.path, JSON.stringify(config, null, 2), 'utf8');
114+
} catch (e) {
115+
throw new Error(`Error while saving config.`);
49116
}
117+
}
50118

51-
parent[name] = value;
52-
return true;
119+
public set(path: string, value: any): void {
120+
const levels = path.split('.');
121+
let current = this.config;
122+
let i = 0;
123+
while (i < levels.length - 1) {
124+
delete current[levels[i]];
125+
current = current[levels[i]];
126+
i += 1;
127+
}
128+
129+
current[levels[levels.length - 1]] = value;
53130
}
54131

55-
get(jsonPath: string): any {
56-
let { parent, name, remaining } = this._findParent(jsonPath);
57-
if (remaining || !(name in parent)) {
58-
return null;
132+
public get(obj: any, path: string): any {
133+
const levels = path.split('.');
134+
let current = obj;
135+
let i = 0;
136+
while (i < levels.length) {
137+
if (current[levels[i]]) {
138+
current = current[levels[i]];
139+
i += 1;
140+
} else {
141+
return null;
142+
}
143+
}
144+
145+
if (current.type === 'array') {
146+
return current.enum;
147+
} else if (current.type === 'object') {
148+
return current;
59149
} else {
60-
return parent[name];
150+
return current.value;
61151
}
62152
}
63153

64-
private _validatePath(jsonPath: string) {
154+
public validatePath(jsonPath: string) {
65155
if (!jsonPath.match(/^(?:[-_\w\d]+(?:\[\d+\])*\.)*(?:[-_\w\d]+(?:\[\d+\])*)$/)) {
66156
throw `Invalid JSON path: "${jsonPath}"`;
67157
}
68158
}
69159

70-
private _findParent(jsonPath: string): { parent: any, name: string | number, remaining?: string } {
71-
this._validatePath(jsonPath);
72-
73-
let parent: any = null;
74-
let current: any = this._config;
75-
76-
const splitPath = jsonPath.split('.');
77-
let name: string | number = '';
78-
79-
while (splitPath.length > 0) {
80-
const m = splitPath.shift().match(/^(.*?)(?:\[(\d+)\])*$/);
81-
82-
name = m[1];
83-
const index: string = m[2];
84-
parent = current;
85-
current = current[name];
86-
87-
if (current === null || current === undefined) {
88-
return {
89-
parent,
90-
name,
91-
remaining: (!isNaN(index) ? `[${index}]` : '') + splitPath.join('.')
92-
};
160+
private _findUp(name: string, from: string): string {
161+
let currentDir = from;
162+
while (currentDir && currentDir != '/') {
163+
const p = path.join(currentDir, name);
164+
if (fs.existsSync(p)) {
165+
return p;
93166
}
94167

95-
if (!isNaN(index)) {
96-
name = index;
97-
parent = current;
98-
current = current[index];
99-
100-
if (current === null || current === undefined) {
101-
return {
102-
parent,
103-
name,
104-
remaining: splitPath.join('.')
105-
};
106-
}
107-
}
168+
currentDir = path.resolve(currentDir, '..');
108169
}
109170

110-
return { parent, name };
171+
return null;
111172
}
112173

113-
static configFilePath(projectPath?: string): string {
174+
private _configFilePath(projectPath?: string): string {
114175
// Find the configuration, either where specified, in the angular-cli project
115176
// (if it's in node_modules) or from the current process.
116-
return (projectPath && _findUp(CLI_CONFIG_FILE_NAME, projectPath))
117-
|| _findUp(CLI_CONFIG_FILE_NAME, __dirname)
118-
|| _findUp(CLI_CONFIG_FILE_NAME, process.cwd());
177+
return (projectPath && this._findUp(CLI_CONFIG_FILE_NAME, projectPath))
178+
|| this._findUp(CLI_CONFIG_FILE_NAME, __dirname)
179+
|| this._findUp(CLI_CONFIG_FILE_NAME, process.cwd());
119180
}
120181

121-
static fromProject(): CliConfigJson {
122-
const configPath = this.configFilePath();
182+
private _fromProject(): ConfigJson {
183+
const configPath = this._configFilePath();
123184
return configPath ? require(configPath) : {};
124185
}
125186
}

addon/ng2/utilities/object-assign.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/* eslint-disable no-unused-vars */
2+
'use strict';
3+
4+
var hasOwnProperty = Object.prototype.hasOwnProperty;
5+
var propIsEnumerable = Object.prototype.propertyIsEnumerable;
6+
7+
function toObject(val) {
8+
if (val === null || val === undefined) {
9+
throw new TypeError('Object.assign cannot be called with null or undefined');
10+
}
11+
12+
return Object(val);
13+
}
14+
15+
module.exports = Object.assign || function (target, source) {
16+
var from;
17+
var to = toObject(target);
18+
var symbols;
19+
20+
for (var s = 1; s < arguments.length; s++) {
21+
from = Object(arguments[s]);
22+
23+
for (var key in from) {
24+
if (hasOwnProperty.call(from, key)) {
25+
to[key] = from[key];
26+
}
27+
}
28+
29+
if (Object.getOwnPropertySymbols) {
30+
symbols = Object.getOwnPropertySymbols(from);
31+
for (var i = 0; i < symbols.length; i++) {
32+
if (propIsEnumerable.call(from, symbols[i])) {
33+
to[symbols[i]] = from[symbols[i]];
34+
}
35+
}
36+
}
37+
}
38+
39+
return to;
40+
};

bin/ng

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/usr/bin/env node
1+
#!node --harmony-proxies
22
'use strict';
33

44
// Provide a title to the process in `ps`

0 commit comments

Comments
 (0)