Skip to content

Commit 6f45e18

Browse files
committed
generateTemplateFilesCommandLine
1 parent 020b0d5 commit 6f45e18

File tree

10 files changed

+156
-77
lines changed

10 files changed

+156
-77
lines changed

examples/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"---------- Normal Example -------------------------------------------": "",
77
"generate": "node ./tools/generate.js",
88
"---------- Command Line Example -------------------------------------": "",
9+
"commandLineSimple": "node ./tools/commandLine.js angular-ngrx-store __name__=some-name __model__=some-other-name",
910
"commandline": "node ./tools/commandLine.js angular-ngrx-store __name__=some-name __model__=some-other-name --outputpath=./src/here --overwrite"
1011
},
1112
"devDependencies": {

examples/tools/commandLine.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,6 @@ const {generateTemplateFilesCommandLine} = require('../../dist/generate-template
33
// Note: In your file it will be like this:
44
// const {generateTemplateFilesCommandLine} = require('generate-template-files');
55

6-
// node ./tools/generate.js angular-ngrx-store __name__=some-name __model__=some-other-name --outputpath=asdf/asdf/ --overwrite
6+
// node ./tools/commandLine.js angular-ngrx-store __name__=some-name __model__=some-other-name --outputpath=./src/here --overwrite
77

88
generateTemplateFilesCommandLine(items);

package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@
5252
],
5353
"dependencies": {
5454
"enquirer": "2.3.5",
55-
"lodash.get": "4.4.2",
5655
"path-exists": "4.0.0",
5756
"recursive-copy": "2.0.10",
5857
"replace-string": "3.0.0",
@@ -69,7 +68,6 @@
6968
"@babel/preset-typescript": "7.9.0",
7069
"@babel/runtime": "7.9.6",
7170
"@types/jest": "25.2.1",
72-
"@types/lodash.get": "4.4.6",
7371
"@types/path-exists": "4.0.2",
7472
"@types/replace-string": "3.0.0",
7573
"@types/through2": "2.0.34",

src/GenerateTemplateFiles.ts

Lines changed: 23 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,16 @@ import through from 'through2';
55
import replaceString from 'replace-string';
66
import StringUtility from './utilities/StringUtility';
77
import CaseConverterEnum from './constants/CaseConverterEnum';
8-
import get from 'lodash.get';
98
import IConfigItem from './models/IConfigItem';
109
import IReplacer from './models/IReplacer';
1110
import IResults from './models/IResults';
1211
import IDefaultCaseConverter from './models/IDefaultCaseConverter';
13-
import CheckUtility from './utilities/CheckUtility';
12+
import {
13+
throwErrorIfNoConfigItems,
14+
throwErrorIfOptionNameIsNotFound,
15+
throwErrorIfNoStringOrDynamicReplacers,
16+
throwErrorIfStringReplacersDoNotMatch,
17+
} from './utilities/CheckUtility';
1418
import IReplacerSlotQuestion from './models/IReplacerSlotQuestion';
1519
import yargs from 'yargs';
1620

@@ -21,6 +25,9 @@ export default class GenerateTemplateFiles {
2125
* Main method to create your template files. Accepts an array of `IConfigItem` items.
2226
*/
2327
public async generate(options: IConfigItem[]): Promise<void> {
28+
throwErrorIfNoConfigItems(options);
29+
throwErrorIfNoStringOrDynamicReplacers(options);
30+
2431
const selectedConfigItem: IConfigItem = await this._getSelectedItem(options);
2532
const answeredReplacers: IReplacer[] = await this._getReplacerSlotValues(selectedConfigItem);
2633

@@ -31,48 +38,38 @@ export default class GenerateTemplateFiles {
3138
* Main method to create your template files with the command line.
3239
*/
3340
public async commandLine(options: IConfigItem[]): Promise<void> {
34-
const commandLineArgs: string[] = yargs.argv._;
35-
const templateName: string = commandLineArgs.shift() ?? '';
36-
const slots: string[] = commandLineArgs;
41+
this._isCommandLine = true;
42+
43+
throwErrorIfNoConfigItems(options);
44+
throwErrorIfNoStringOrDynamicReplacers(options);
3745

46+
const [templateName = '', ...replacers] = yargs.argv._;
3847
const selectedConfigItem: IConfigItem | undefined = options.find((configItem: IConfigItem) => {
3948
return (
4049
StringUtility.toCase(configItem.option, CaseConverterEnum.KebabCase) ===
4150
StringUtility.toCase(templateName, CaseConverterEnum.KebabCase)
4251
);
4352
});
4453

45-
if (!selectedConfigItem) {
46-
CheckUtility.check(Boolean(selectedConfigItem), `There was no IConfigItem found for ${templateName}`);
54+
throwErrorIfOptionNameIsNotFound(selectedConfigItem, StringUtility.toCase(templateName, CaseConverterEnum.KebabCase));
4755

48-
return;
49-
}
50-
51-
const replacers: IReplacer[] = slots.map((str: string) => {
56+
const commandLineStringReplacers: IReplacer[] = replacers.map((str: string) => {
5257
const [slot, slotValue] = str.split('=');
5358

54-
const isValidReplacer: boolean = Boolean(slot) && str.includes('=');
55-
CheckUtility.check(isValidReplacer, `${str} is not valid as a IReplacer`);
56-
5759
return {
5860
slot,
5961
slotValue,
6062
};
6163
});
62-
const dynamicReplacers: IReplacer[] = selectedConfigItem.dynamicReplacers || [];
6364

64-
await this._outputFiles(selectedConfigItem, [...replacers, ...dynamicReplacers]);
65-
}
66-
67-
private async _outputFiles(selectedConfigItem: IConfigItem, replacers: IReplacer[]): Promise<void> {
68-
const hasStringOrDynamicReplacers: boolean = replacers.length > 0;
65+
throwErrorIfStringReplacersDoNotMatch(selectedConfigItem, commandLineStringReplacers);
6966

70-
if (!hasStringOrDynamicReplacers) {
71-
CheckUtility.check(hasStringOrDynamicReplacers, 'You need at least one IReplacer');
67+
const dynamicReplacers: IReplacer[] = selectedConfigItem?.dynamicReplacers || [];
7268

73-
return;
74-
}
69+
await this._outputFiles(selectedConfigItem!, [...commandLineStringReplacers, ...dynamicReplacers]);
70+
}
7571

72+
private async _outputFiles(selectedConfigItem: IConfigItem, replacers: IReplacer[]): Promise<void> {
7673
const {contentCase, outputPathCase} = this._getDefaultCaseConverters(selectedConfigItem);
7774
const contentReplacers: IReplacer[] = this._getReplacers(replacers, contentCase);
7875
const outputPathReplacers: IReplacer[] = this._getReplacers(replacers, outputPathCase);
@@ -127,12 +124,8 @@ export default class GenerateTemplateFiles {
127124
/**
128125
*/
129126
private _getDefaultCaseConverters(selectedConfigItem: IConfigItem): IDefaultCaseConverter {
130-
const defaultContentCase: CaseConverterEnum = get(selectedConfigItem, 'defaultCase', CaseConverterEnum.None) as CaseConverterEnum;
131-
const defaultOutputPath: CaseConverterEnum = get(
132-
selectedConfigItem,
133-
'output.pathAndFileNameDefaultCase',
134-
defaultContentCase
135-
) as CaseConverterEnum;
127+
const defaultContentCase: CaseConverterEnum = selectedConfigItem?.defaultCase ?? CaseConverterEnum.None;
128+
const defaultOutputPath: CaseConverterEnum = selectedConfigItem.output?.pathAndFileNameDefaultCase ?? defaultContentCase;
136129

137130
return {
138131
contentCase: defaultContentCase,

src/models/IConfigItem.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export default interface IConfigItem {
3838
* defaultCase: '(pascalCase)',
3939
* ```
4040
*/
41-
defaultCase: CaseConverterEnum | string;
41+
defaultCase: CaseConverterEnum;
4242
/**
4343
* ```
4444
* entry: {
@@ -71,7 +71,7 @@ export default interface IConfigItem {
7171
*/
7272
output: {
7373
path: string;
74-
pathAndFileNameDefaultCase: CaseConverterEnum | string;
74+
pathAndFileNameDefaultCase?: CaseConverterEnum;
7575
overwrite?: boolean; // determines if existing files with the same name be over written.
7676
};
7777
/**

src/utilities/CheckUtility.test.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
1-
import CheckUtility from './CheckUtility';
1+
import {errorIfFalse} from './CheckUtility';
22

3-
describe('CheckUtility.check', () => {
3+
describe('errorIfTrue', () => {
44
const errorString = 'Some error thrown for testing purposes';
55

66
test('should throw an error if condition is false', () => {
7-
expect(() => CheckUtility.check(false, errorString)).toThrowError(errorString);
7+
expect(errorIfFalse(false, errorString)).toEqual(new Error(errorString));
88
});
99

1010
test('should throw an error if input is not a boolean', () => {
11-
expect(() => CheckUtility.check('true' as any, errorString)).toThrowError();
11+
expect(errorIfFalse('true' as any, errorString)).toEqual(
12+
new Error("errorIfTrue()'s first argument must be a boolean but argument was of type string")
13+
);
1214
});
1315

1416
test('should not throw an error if condition evaluates to a boolean true', () => {
15-
expect(() => CheckUtility.check(true, errorString)).not.toThrowError();
17+
expect(errorIfFalse(true, errorString)).toEqual(undefined);
1618
});
1719
});

src/utilities/CheckUtility.ts

Lines changed: 69 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,74 @@
1+
import IConfigItem from '../models/IConfigItem';
2+
import IReplacer from '../models/IReplacer';
3+
import StringUtility from './StringUtility';
4+
import IReplacerSlotQuestion from '../models/IReplacerSlotQuestion';
5+
6+
export const isBooleanType = (value: any) => {
7+
return typeof value === 'boolean';
8+
};
9+
110
/**
2-
* @ignore
11+
* Helper function for throwing errors if a given expression evaluates to false.
12+
* This function is strict and will throw an error the the type of the first
13+
* argument is not "boolean".
314
*/
4-
export default class CheckUtility {
5-
/**
6-
* Helper function for throwing errors if a given expression evaluates to false.
7-
* This function is strict and will throw an error the the type of the first
8-
* argument is not "boolean".
9-
*/
10-
public static check(predicate: boolean, errorMessage: string): void {
11-
if (CheckUtility._isNotTypeBoolean(predicate) === true) {
12-
throw CheckUtility.check(false, `CheckUtility.check()'s first argument must be a boolean but argument was of type ${typeof predicate}`);
13-
} else if (predicate === true) {
14-
return;
15-
}
16-
17-
throw new Error(errorMessage);
15+
export const errorIfFalse = (isError: boolean, errorMessage: string): Error | void => {
16+
if (!isBooleanType(isError)) {
17+
return errorIfFalse(false, `errorIfTrue()'s first argument must be a boolean but argument was of type ${typeof isError}`);
18+
}
19+
20+
if (!isError) {
21+
return new Error(errorMessage);
22+
}
23+
};
24+
25+
export const throwErrorIfNoConfigItems = (options: IConfigItem[]) => {
26+
const hasAtLeastOneItem = Boolean(options?.length);
27+
28+
if (!hasAtLeastOneItem) {
29+
throw new Error('There was no IConfigItem items found.');
30+
}
31+
};
32+
33+
export const throwErrorIfOptionNameIsNotFound = (item: IConfigItem | undefined, templateName: string) => {
34+
if (!item) {
35+
throw new Error(`No IConfigItem found for ${templateName}`);
1836
}
37+
};
38+
39+
export const throwErrorIfStringReplacersDoNotMatch = (item: IConfigItem | undefined, commandLineStringReplacers: IReplacer[]) => {
40+
const configItemStringReplacers: (string | IReplacerSlotQuestion)[] = item?.stringReplacers ?? [];
41+
42+
if (commandLineStringReplacers.length !== configItemStringReplacers.length) {
43+
throw new Error('IConfigItem stringReplacers do not match the command line arguments.');
44+
}
45+
46+
const configItemStringReplacersKeys = configItemStringReplacers
47+
.map((replacer: string | IReplacerSlotQuestion) => {
48+
return StringUtility.isString(replacer) ? replacer : replacer.slot;
49+
})
50+
.sort()
51+
.join(', ');
52+
53+
const commandLineStringReplacersKeys = commandLineStringReplacers
54+
.map((replacer: IReplacer) => replacer.slot)
55+
.sort()
56+
.join(', ');
57+
58+
if (configItemStringReplacersKeys !== commandLineStringReplacersKeys) {
59+
throw new Error(
60+
` ${configItemStringReplacersKeys} does not match ${commandLineStringReplacersKeys}. IConfigItem stringReplacers do not match the command line arguments.`
61+
);
62+
}
63+
};
64+
65+
export const throwErrorIfNoStringOrDynamicReplacers = (options: IConfigItem[]) => {
66+
const hasStringOrDynamicReplacers =
67+
options.every((item: IConfigItem) => {
68+
return Boolean(item?.stringReplacers?.length) || Boolean(item?.dynamicReplacers?.length);
69+
}) && options.length > 0;
1970

20-
private static _isNotTypeBoolean(value: any): boolean {
21-
return typeof value !== 'boolean';
71+
if (!hasStringOrDynamicReplacers) {
72+
throw new Error('IConfigItem needs to have a stringReplacers or dynamicReplacers.');
2273
}
23-
}
74+
};
Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,54 @@
1+
import GenerateTemplateFiles from '../GenerateTemplateFiles';
2+
import {IConfigItem} from '../index';
3+
import CaseConverterEnum from '../constants/CaseConverterEnum';
4+
import yargs from 'yargs';
5+
16
describe('GenerateTemplateFiles - Command Line', () => {
2-
//
7+
test('should throw an error if no IConfigItem items', () => {
8+
const items: IConfigItem[] = [];
9+
const gtf = new GenerateTemplateFiles();
10+
11+
expect(() => gtf.commandLine(items)).rejects.toThrowError('There was no IConfigItem items found.');
12+
});
13+
14+
test('should throw an error if IConfigItem is not found for option name', () => {
15+
const notFoundOptionName = 'other-template';
16+
17+
yargs([notFoundOptionName]);
18+
19+
const items: IConfigItem[] = [
20+
{
21+
option: 'some-template',
22+
defaultCase: CaseConverterEnum.PascalCase,
23+
stringReplacers: ['__name__'],
24+
entry: {
25+
folderPath: 'path',
26+
},
27+
output: {
28+
path: 'path',
29+
},
30+
},
31+
];
32+
const gtf = new GenerateTemplateFiles();
33+
34+
expect(() => gtf.commandLine(items)).rejects.toThrowError(`No IConfigItem found for ${notFoundOptionName}`);
35+
});
36+
37+
test('should throw an error if no stringReplacers or dynamicReplacers', () => {
38+
const items: IConfigItem[] = [
39+
{
40+
option: 'some-template',
41+
defaultCase: CaseConverterEnum.PascalCase,
42+
entry: {
43+
folderPath: 'path',
44+
},
45+
output: {
46+
path: 'path',
47+
},
48+
},
49+
];
50+
const gtf = new GenerateTemplateFiles();
51+
52+
expect(() => gtf.commandLine(items)).rejects.toThrowError('IConfigItem needs to have a stringReplacers or dynamicReplacers.');
53+
});
354
});

tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
3333

3434
/* Additional Checks */
35-
"noUnusedLocals": true /* Report errors on unused locals. */,
35+
"noUnusedLocals": false /* Report errors on unused locals. */,
3636
"noUnusedParameters": false /* Report errors on unused parameters. */,
3737
"noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
3838
"noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */,

yarn.lock

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1289,18 +1289,6 @@
12891289
jest-diff "^25.2.1"
12901290
pretty-format "^25.2.1"
12911291

1292-
"@types/lodash.get@4.4.6":
1293-
version "4.4.6"
1294-
resolved "https://registry.yarnpkg.com/@types/lodash.get/-/lodash.get-4.4.6.tgz#0c7ac56243dae0f9f09ab6f75b29471e2e777240"
1295-
integrity sha512-E6zzjR3GtNig8UJG/yodBeJeIOtgPkMgsLjDU3CbgCAPC++vJ0eCMnJhVpRZb/ENqEFlov1+3K9TKtY4UdWKtQ==
1296-
dependencies:
1297-
"@types/lodash" "*"
1298-
1299-
"@types/lodash@*":
1300-
version "4.14.149"
1301-
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.149.tgz#1342d63d948c6062838fbf961012f74d4e638440"
1302-
integrity sha512-ijGqzZt/b7BfzcK9vTrS6MFljQRPn5BFWOx8oE0GYxribu6uV+aA9zZuXI1zc/etK9E8nrgdoF2+LgUw7+9tJQ==
1303-
13041292
"@types/minimatch@^3.0.3":
13051293
version "3.0.3"
13061294
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
@@ -3506,11 +3494,6 @@ locate-path@^5.0.0:
35063494
dependencies:
35073495
p-locate "^4.1.0"
35083496

3509-
lodash.get@4.4.2:
3510-
version "4.4.2"
3511-
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
3512-
integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
3513-
35143497
lodash.memoize@4.x:
35153498
version "4.1.2"
35163499
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"

0 commit comments

Comments
 (0)