Skip to content
This repository was archived by the owner on May 1, 2020. It is now read-only.

Commit 333c7d0

Browse files
committed
generators - wip
1 parent 47dfaf2 commit 333c7d0

File tree

11 files changed

+470
-9
lines changed

11 files changed

+470
-9
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@
8585
"@types/uglify-js": "^2.0.27",
8686
"@types/webpack": "^1.12.35",
8787
"@types/ws": "^0.0.38",
88+
"change-case": "^3.0.1",
8889
"conventional-changelog-cli": "1.2.0",
8990
"github": "0.2.4",
9091
"ionic-cz-conventional-changelog": "1.0.0",

src/generators.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Logger} from './logger/logger';
2+
import { generateContext } from './util/config';
3+
import * as Constants from './util/constants';
4+
import { BuildContext, GeneratorOption, GeneratorRequest } from './util/interfaces';
5+
6+
export function generateNonTab(request: GeneratorRequest) {
7+
const context = generateContext();
8+
return processNonTabRequest(context, request);
9+
}
10+
11+
function processNonTabRequest(context: BuildContext, request: GeneratorRequest) {
12+
13+
}
14+
15+
export function listOptions() {
16+
const list: GeneratorOption[] = [];
17+
list.push({type: Constants.COMPONENT, multiple: false});
18+
list.push({type: Constants.DIRECTIVE, multiple: false});
19+
list.push({type: Constants.PAGE, multiple: false});
20+
list.push({type: Constants.PIPE, multiple: false});
21+
list.push({type: Constants.PROVIDER, multiple: false});
22+
list.push({type: Constants.TABS, multiple: true});
23+
}
24+
25+
26+

src/generators/constants.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export const CLASSNAME_VARIABLE = '$CLASSNAME';
2+
export const FILENAME_VARIABLE = '$FILENAME';
3+
4+
export const KNOWN_FILE_EXTENSION = '.tmpl';
5+
6+
export const SPEC_FILE_EXTENSION = 'spec.ts';
7+
export const NG_MODULE_FILE_EXTENSION = 'module.ts';
8+

src/generators/util.spec.ts

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
import { basename, join } from 'path';
2+
import * as fs from 'fs';
3+
import * as Constants from '../util/constants';
4+
import * as helpers from '../util/helpers';
5+
import * as util from './util';
6+
import * as GeneratorConstants from './constants';
7+
8+
describe('util', () => {
9+
describe('hydrateRequest', () => {
10+
it('should take a request and return a hydrated request', () => {
11+
// arrange
12+
const componentsDir = '/Users/dan/project/src/components';
13+
const context = {
14+
componentsDir: componentsDir
15+
};
16+
const request = {
17+
type: Constants.COMPONENT,
18+
name: 'settings view',
19+
includeSpec: true,
20+
includeNgModule: true
21+
};
22+
23+
const templateDir = '/Users/dan/project/node_modules/ionic-angular/templates';
24+
spyOn(helpers, helpers.getPropertyValue.name).and.returnValue(templateDir);
25+
26+
// act
27+
const hydratedRequest = util.hydrateRequest(context, request);
28+
29+
// assert
30+
expect(hydratedRequest.type).toEqual(Constants.COMPONENT);
31+
expect(hydratedRequest.name).toEqual(request.name);
32+
expect(hydratedRequest.includeNgModule).toBeTruthy();
33+
expect(hydratedRequest.includeSpec).toBeTruthy();
34+
expect(hydratedRequest.className).toEqual('SettingsView');
35+
expect(hydratedRequest.fileName).toEqual('settings-view');
36+
expect(hydratedRequest.dirToRead).toEqual(join(templateDir, Constants.COMPONENT));
37+
expect(hydratedRequest.dirToWrite).toEqual(join(componentsDir, hydratedRequest.fileName));
38+
});
39+
});
40+
41+
describe('readTemplates', () => {
42+
it('should get a map of templates and their content back', () => {
43+
// arrange
44+
const templateDir = '/Users/dan/project/node_modules/ionic-angular/templates/component';
45+
const knownValues = ['html.tmpl', 'scss.tmpl', 'spec.ts.tmpl', 'ts.tmpl', 'module.tmpl'];
46+
const fileContent = 'SomeContent';
47+
spyOn(fs, 'readdirSync').and.returnValue(knownValues);
48+
spyOn(helpers, helpers.readFileAsync.name).and.returnValue(Promise.resolve(fileContent));
49+
50+
// act
51+
const promise = util.readTemplates(templateDir);
52+
53+
// assert
54+
return promise.then((map: Map<string, string>) => {
55+
expect(map.get(join(templateDir, knownValues[0]))).toEqual(fileContent);
56+
expect(map.get(join(templateDir, knownValues[1]))).toEqual(fileContent);
57+
expect(map.get(join(templateDir, knownValues[2]))).toEqual(fileContent);
58+
expect(map.get(join(templateDir, knownValues[3]))).toEqual(fileContent);
59+
expect(map.get(join(templateDir, knownValues[4]))).toEqual(fileContent);
60+
});
61+
});
62+
});
63+
64+
describe('filterOutTemplates', () => {
65+
it('should preserve all templates', () => {
66+
const map = new Map<string, string>();
67+
const templateDir = '/Users/dan/project/node_modules/ionic-angular/templates/component';
68+
const fileContent = 'SomeContent';
69+
const knownValues = ['html.tmpl', 'scss.tmpl', 'spec.ts.tmpl', 'ts.tmpl', 'module.tmpl'];
70+
map.set(join(templateDir, knownValues[0]), fileContent);
71+
map.set(join(templateDir, knownValues[1]), fileContent);
72+
map.set(join(templateDir, knownValues[2]), fileContent);
73+
map.set(join(templateDir, knownValues[3]), fileContent);
74+
map.set(join(templateDir, knownValues[4]), fileContent);
75+
76+
const newMap = util.filterOutTemplates({includeNgModule: true, includeSpec: true}, map);
77+
expect(newMap.size).toEqual(knownValues.length);
78+
});
79+
80+
it('should remove spec', () => {
81+
const map = new Map<string, string>();
82+
const templateDir = '/Users/dan/project/node_modules/ionic-angular/templates/component';
83+
const fileContent = 'SomeContent';
84+
const knownValues = ['html.tmpl', 'scss.tmpl', 'spec.ts.tmpl', 'ts.tmpl', 'module.tmpl'];
85+
map.set(join(templateDir, knownValues[0]), fileContent);
86+
map.set(join(templateDir, knownValues[1]), fileContent);
87+
map.set(join(templateDir, knownValues[2]), fileContent);
88+
map.set(join(templateDir, knownValues[3]), fileContent);
89+
map.set(join(templateDir, knownValues[4]), fileContent);
90+
91+
const newMap = util.filterOutTemplates({includeNgModule: true, includeSpec: false}, map);
92+
expect(newMap.size).toEqual(4);
93+
expect(newMap.get(join(templateDir, knownValues[0]))).toBeTruthy();
94+
expect(newMap.get(join(templateDir, knownValues[1]))).toBeTruthy();
95+
expect(newMap.get(join(templateDir, knownValues[2]))).toBeFalsy();
96+
expect(newMap.get(join(templateDir, knownValues[3]))).toBeTruthy();
97+
expect(newMap.get(join(templateDir, knownValues[4]))).toBeTruthy();
98+
});
99+
100+
it('should remove spec and module', () => {
101+
const map = new Map<string, string>();
102+
const templateDir = '/Users/dan/project/node_modules/ionic-angular/templates/component';
103+
const fileContent = 'SomeContent';
104+
const knownValues = ['html.tmpl', 'scss.tmpl', 'spec.ts.tmpl', 'ts.tmpl', 'module.ts.tmpl'];
105+
map.set(join(templateDir, knownValues[0]), fileContent);
106+
map.set(join(templateDir, knownValues[1]), fileContent);
107+
map.set(join(templateDir, knownValues[2]), fileContent);
108+
map.set(join(templateDir, knownValues[3]), fileContent);
109+
map.set(join(templateDir, knownValues[4]), fileContent);
110+
111+
const newMap = util.filterOutTemplates({includeNgModule: false, includeSpec: false}, map);
112+
expect(newMap.size).toEqual(3);
113+
expect(newMap.get(join(templateDir, knownValues[0]))).toBeTruthy();
114+
expect(newMap.get(join(templateDir, knownValues[1]))).toBeTruthy();
115+
expect(newMap.get(join(templateDir, knownValues[2]))).toBeFalsy();
116+
expect(newMap.get(join(templateDir, knownValues[3]))).toBeTruthy();
117+
expect(newMap.get(join(templateDir, knownValues[4]))).toBeFalsy();
118+
});
119+
});
120+
121+
describe('applyTemplates', () => {
122+
it('should replace the template content', () => {
123+
const fileOne = '/Users/dan/fileOne';
124+
125+
const fileOneContent = `
126+
<!--
127+
Generated template for the $CLASSNAME component.
128+
129+
See https://angular.io/docs/ts/latest/api/core/index/ComponentMetadata-class.html
130+
for more info on Angular 2 Components.
131+
-->
132+
133+
{{text}}
134+
135+
`;
136+
137+
const fileTwo = '/Users/dan/fileTwo';
138+
const fileTwoContent = `
139+
$FILENAME {
140+
141+
}
142+
`;
143+
144+
const fileThree = '/Users/dan/fileThree';
145+
const fileThreeContent = `
146+
describe('$CLASSNAME', () => {
147+
it('should do something', () => {
148+
expect(true).toEqual(true);
149+
});
150+
});
151+
`;
152+
153+
const fileFour = '/Users/dan/fileFour';
154+
const fileFourContent = `
155+
import { Component } from '@angular/core';
156+
157+
/*
158+
Generated class for the $CLASSNAME component.
159+
160+
See https://angular.io/docs/ts/latest/api/core/index/ComponentMetadata-class.html
161+
for more info on Angular 2 Components.
162+
*/
163+
@Component({
164+
selector: '$FILENAME',
165+
templateUrl: '$FILENAME.html'
166+
})
167+
export class $CLASSNAMEComponent {
168+
169+
text: string;
170+
171+
constructor() {
172+
console.log('Hello $CLASSNAME Component');
173+
this.text = 'Hello World';
174+
}
175+
176+
}
177+
178+
`;
179+
180+
const map = new Map<string, string>();
181+
map.set(fileOne, fileOneContent);
182+
map.set(fileTwo, fileTwoContent);
183+
map.set(fileThree, fileThreeContent);
184+
map.set(fileFour, fileFourContent);
185+
186+
const className = 'SettingsView';
187+
const fileName = 'settings-view';
188+
189+
const results = util.applyTemplates({ className: className, fileName: fileName}, map);
190+
const modifiedContentOne = results.get(fileOne);
191+
const modifiedContentTwo = results.get(fileTwo);
192+
const modifiedContentThree = results.get(fileThree);
193+
const modifiedContentFour = results.get(fileFour);
194+
expect(modifiedContentOne.indexOf(GeneratorConstants.CLASSNAME_VARIABLE)).toEqual(-1);
195+
expect(modifiedContentOne.indexOf(GeneratorConstants.FILENAME_VARIABLE)).toEqual(-1);
196+
expect(modifiedContentTwo.indexOf(GeneratorConstants.CLASSNAME_VARIABLE)).toEqual(-1);
197+
expect(modifiedContentTwo.indexOf(GeneratorConstants.FILENAME_VARIABLE)).toEqual(-1);
198+
expect(modifiedContentThree.indexOf(GeneratorConstants.CLASSNAME_VARIABLE)).toEqual(-1);
199+
expect(modifiedContentThree.indexOf(GeneratorConstants.FILENAME_VARIABLE)).toEqual(-1);
200+
expect(modifiedContentFour.indexOf(GeneratorConstants.CLASSNAME_VARIABLE)).toEqual(-1);
201+
expect(modifiedContentFour.indexOf(GeneratorConstants.FILENAME_VARIABLE)).toEqual(-1);
202+
});
203+
});
204+
205+
describe('writeGeneratedFiles', () => {
206+
it('should return the list of files generated', () => {
207+
const map = new Map<string, string>();
208+
const templateDir = '/Users/dan/project/node_modules/ionic-angular/templates/component';
209+
const fileContent = 'SomeContent';
210+
const knownValues = ['html.tmpl', 'scss.tmpl', 'spec.ts.tmpl', 'ts.tmpl', 'module.tmpl'];
211+
const fileName = 'settings-view';
212+
const dirToWrite = join('/Users/dan/project/src/components', fileName);
213+
map.set(join(templateDir, knownValues[0]), fileContent);
214+
map.set(join(templateDir, knownValues[1]), fileContent);
215+
map.set(join(templateDir, knownValues[2]), fileContent);
216+
map.set(join(templateDir, knownValues[3]), fileContent);
217+
map.set(join(templateDir, knownValues[4]), fileContent);
218+
219+
spyOn(helpers, helpers.writeFileAsync.name).and.returnValue(Promise.resolve());
220+
221+
const promise = util.writeGeneratedFiles({ dirToWrite: dirToWrite, fileName: fileName }, map);
222+
223+
return promise.then((filesCreated: string[]) => {
224+
const fileExtensions = knownValues.map(knownValue => basename(knownValue, GeneratorConstants.KNOWN_FILE_EXTENSION));
225+
expect(filesCreated[0]).toEqual(join(dirToWrite, `${fileName}.${fileExtensions[0]}`));
226+
expect(filesCreated[1]).toEqual(join(dirToWrite, `${fileName}.${fileExtensions[1]}`));
227+
expect(filesCreated[2]).toEqual(join(dirToWrite, `${fileName}.${fileExtensions[2]}`));
228+
expect(filesCreated[3]).toEqual(join(dirToWrite, `${fileName}.${fileExtensions[3]}`));
229+
expect(filesCreated[4]).toEqual(join(dirToWrite, `${fileName}.${fileExtensions[4]}`));
230+
});
231+
});
232+
});
233+
});

src/generators/util.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import { basename, join } from 'path';
2+
import { readdirSync} from 'fs';
3+
4+
import { paramCase, pascalCase } from 'change-case';
5+
6+
import * as Constants from '../util/constants';
7+
import * as GeneratorConstants from './constants';
8+
import { getPropertyValue, readFileAsync, replaceAll, writeFileAsync } from '../util/helpers';
9+
import { BuildContext, GeneratorRequest, HydratedGeneratorRequest } from '../util/interfaces';
10+
11+
export function hydrateRequest(context: BuildContext, request: GeneratorRequest) {
12+
const hydrated = Object.assign({}, request) as HydratedGeneratorRequest;
13+
hydrated.className = pascalCase(request.name);
14+
hydrated.fileName = paramCase(request.name);
15+
16+
hydrated.dirToRead = join(getPropertyValue(Constants.ENV_VAR_IONIC_ANGULAR_TEMPLATE_DIR), request.type);
17+
18+
const baseDir = getDirToWriteToByType(context, request.type);
19+
hydrated.dirToWrite = join(baseDir, hydrated.fileName);
20+
21+
return hydrated;
22+
}
23+
24+
export function readTemplates(pathToRead: string): Promise<Map<string, string>> {
25+
const fileNames = readdirSync(pathToRead);
26+
const absolutePaths = fileNames.map(fileName => {
27+
return join(pathToRead, fileName);
28+
});
29+
const filePathToContent = new Map<string, string>();
30+
const promises = absolutePaths.map(absolutePath => {
31+
const promise = readFileAsync(absolutePath);
32+
promise.then((fileContent: string) => {
33+
filePathToContent.set(absolutePath, fileContent);
34+
});
35+
});
36+
return Promise.all(promises).then(() => {
37+
return filePathToContent;
38+
});
39+
}
40+
41+
export function filterOutTemplates(request: HydratedGeneratorRequest, templates: Map<string, string>) {
42+
const templatesToUseMap = new Map<string, string>();
43+
templates.forEach((fileContent: string, filePath: string) => {
44+
const newFileExtension = basename(filePath, GeneratorConstants.KNOWN_FILE_EXTENSION);
45+
const shouldSkip = (!request.includeNgModule && newFileExtension === GeneratorConstants.NG_MODULE_FILE_EXTENSION) || (!request.includeSpec && newFileExtension === GeneratorConstants.SPEC_FILE_EXTENSION);
46+
if (!shouldSkip) {
47+
templatesToUseMap.set(filePath, fileContent);
48+
}
49+
});
50+
return templatesToUseMap;
51+
}
52+
53+
export function applyTemplates(request: HydratedGeneratorRequest, templates: Map<string, string>) {
54+
const appliedTemplateMap = new Map<string, string>();
55+
templates.forEach((fileContent: string, filePath: string) => {
56+
const classnameRemovedContent = replaceAll(fileContent, GeneratorConstants.CLASSNAME_VARIABLE, request.className);
57+
const fileNameRemovedContent = replaceAll(classnameRemovedContent, GeneratorConstants.FILENAME_VARIABLE, request.fileName);
58+
appliedTemplateMap.set(filePath, fileNameRemovedContent);
59+
});
60+
return appliedTemplateMap;
61+
}
62+
63+
export function writeGeneratedFiles(request: HydratedGeneratorRequest, processedTemplates: Map<string, string>): Promise<string[]> {
64+
const promises: Promise<any>[] = [];
65+
const createdFileList: string[] = [];
66+
processedTemplates.forEach((fileContent: string, filePath: string) => {
67+
const newFileExtension = basename(filePath, GeneratorConstants.KNOWN_FILE_EXTENSION);
68+
const newFileName = `${request.fileName}.${newFileExtension}`;
69+
const fileToWrite = join(request.dirToWrite, newFileName);
70+
createdFileList.push(fileToWrite);
71+
promises.push(writeFileAsync(fileToWrite, fileContent));
72+
});
73+
return Promise.all(promises).then(() => {
74+
return createdFileList;
75+
});
76+
}
77+
78+
export function getDirToWriteToByType(context: BuildContext, type: string) {
79+
if (type === Constants.COMPONENT) {
80+
return context.componentsDir;
81+
} else if ( type === Constants.DIRECTIVE) {
82+
return context.directivesDir;
83+
} else if (type === Constants.PAGE) {
84+
return context.pagesDir;
85+
} else if ( type === Constants.PIPE) {
86+
return context.pipesDir;
87+
} else if (type === Constants.PROVIDER) {
88+
return context.providersDir;
89+
}
90+
throw new Error(`Unknown Generator Type: ${type}`);
91+
}

src/util/config.spec.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,14 @@ describe('config', () => {
7373
expect(context.wwwDir).toEqual(join(process.cwd(), Constants.WWW_DIR));
7474
expect(context.wwwIndex).toEqual('index.html');
7575
expect(context.buildDir).toEqual(join(process.cwd(), Constants.WWW_DIR, Constants.BUILD_DIR));
76+
expect(context.pagesDir).toEqual(join(context.srcDir, 'pages'));
77+
expect(context.componentsDir).toEqual(join(context.srcDir, 'components'));
78+
expect(context.directivesDir).toEqual(join(context.srcDir, 'components'));
79+
expect(context.pipesDir).toEqual(join(context.srcDir, 'pipes'));
80+
expect(context.providersDir).toEqual(join(context.srcDir, 'providers'));
7681
expect(context.nodeModulesDir).toEqual(join(process.cwd(), Constants.NODE_MODULES));
7782
expect(context.ionicAngularDir).toEqual(join(process.cwd(), Constants.NODE_MODULES, Constants.IONIC_ANGULAR));
83+
expect(fakeConfig[Constants.ENV_VAR_IONIC_ANGULAR_TEMPLATE_DIR]).toEqual(join(context.ionicAngularDir, 'templates'));
7884
expect(context.platform).toEqual(null);
7985
expect(context.target).toEqual(null);
8086
expect(fakeConfig[Constants.ENV_VAR_IONIC_ANGULAR_ENTRY_POINT]).toEqual(join(context.ionicAngularDir, 'index.js'));

0 commit comments

Comments
 (0)