mirror of
https://github.com/angular/angular-cli.git
synced 2025-05-16 10:33:43 +08:00
feat(@angular-devkit/schematics): add .template as an extension
New rules to deal with templates using a .template extension. Apply the template only to those files, then remove the .template suffix. Also added a new rename() rule that takes a matcher and a renamer. Nothing big there. Also added a new composeFileOperator() that compose operators one after the other.
This commit is contained in:
parent
9aadb8e6d1
commit
f3e389bd8c
@ -99,6 +99,7 @@ ts_library(
|
||||
),
|
||||
deps = [
|
||||
":schematics",
|
||||
":testing",
|
||||
"//packages/angular_devkit/core",
|
||||
"@rxjs",
|
||||
"@rxjs//operators",
|
||||
|
@ -185,3 +185,20 @@ export function forEach(operator: FileOperator): Rule {
|
||||
return tree;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export function composeFileOperators(operators: FileOperator[]): FileOperator {
|
||||
return (entry: FileEntry) => {
|
||||
let current: FileEntry | null = entry;
|
||||
for (const op of operators) {
|
||||
current = op(current);
|
||||
|
||||
if (current === null) {
|
||||
// Deleted, just return.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return current;
|
||||
};
|
||||
}
|
||||
|
25
packages/angular_devkit/schematics/src/rules/rename.ts
Normal file
25
packages/angular_devkit/schematics/src/rules/rename.ts
Normal file
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import { normalize } from '@angular-devkit/core';
|
||||
import { Rule } from '../engine/interface';
|
||||
import { FilePredicate } from '../tree/interface';
|
||||
import { forEach } from './base';
|
||||
|
||||
|
||||
export function rename(match: FilePredicate<boolean>, to: FilePredicate<string>): Rule {
|
||||
return forEach(entry => {
|
||||
if (match(entry.path, entry)) {
|
||||
return {
|
||||
content: entry.content,
|
||||
path: normalize(to(entry.path, entry)),
|
||||
};
|
||||
} else {
|
||||
return entry;
|
||||
}
|
||||
});
|
||||
}
|
61
packages/angular_devkit/schematics/src/rules/rename_spec.ts
Normal file
61
packages/angular_devkit/schematics/src/rules/rename_spec.ts
Normal file
@ -0,0 +1,61 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google Inc. All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
// tslint:disable:non-null-operator
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { SchematicContext } from '../engine/interface';
|
||||
import { HostTree } from '../tree/host-tree';
|
||||
import { callRule } from './call';
|
||||
import { rename } from './rename';
|
||||
|
||||
|
||||
const context: SchematicContext = null !;
|
||||
|
||||
|
||||
describe('rename', () => {
|
||||
it('works', done => {
|
||||
const tree = new HostTree();
|
||||
tree.create('a/b/file1', 'hello world');
|
||||
tree.create('a/b/file2', 'hello world');
|
||||
tree.create('a/c/file3', 'hello world');
|
||||
|
||||
let i = 0;
|
||||
|
||||
// Rename all files that contain 'b' to 'hello'.
|
||||
callRule(rename(x => !!x.match(/b/), () => 'hello' + (i++)), observableOf(tree), context)
|
||||
.toPromise()
|
||||
.then(result => {
|
||||
expect(result.exists('a/b/file1')).toBe(false);
|
||||
expect(result.exists('a/b/file2')).toBe(false);
|
||||
expect(result.exists('hello0')).toBe(true);
|
||||
expect(result.exists('hello1')).toBe(true);
|
||||
expect(result.exists('a/c/file3')).toBe(true);
|
||||
})
|
||||
.then(done, done.fail);
|
||||
});
|
||||
|
||||
it('works (2)', done => {
|
||||
const tree = new HostTree();
|
||||
tree.create('a/b/file1', 'hello world');
|
||||
tree.create('a/b/file2', 'hello world');
|
||||
tree.create('a/c/file3', 'hello world');
|
||||
|
||||
let i = 0;
|
||||
|
||||
// Rename all files that contain 'b' to 'hello'.
|
||||
callRule(rename(x => !!x.match(/b/), x => x + (i++)), observableOf(tree), context)
|
||||
.toPromise()
|
||||
.then(result => {
|
||||
expect(result.exists('a/b/file1')).toBe(false);
|
||||
expect(result.exists('a/b/file2')).toBe(false);
|
||||
expect(result.exists('a/b/file10')).toBe(true);
|
||||
expect(result.exists('a/b/file21')).toBe(true);
|
||||
expect(result.exists('a/c/file3')).toBe(true);
|
||||
})
|
||||
.then(done, done.fail);
|
||||
});
|
||||
});
|
@ -8,10 +8,14 @@
|
||||
import { BaseException, normalize, template as templateImpl } from '@angular-devkit/core';
|
||||
import { FileOperator, Rule } from '../engine/interface';
|
||||
import { FileEntry } from '../tree/interface';
|
||||
import { chain, forEach } from './base';
|
||||
import { chain, composeFileOperators, forEach, when } from './base';
|
||||
import { rename } from './rename';
|
||||
import { isBinary } from './utils/is-binary';
|
||||
|
||||
|
||||
export const TEMPLATE_FILENAME_RE = /\.template$/;
|
||||
|
||||
|
||||
export class OptionIsNotDefinedException extends BaseException {
|
||||
constructor(name: string) { super(`Option "${name}" is not defined.`); }
|
||||
}
|
||||
@ -144,6 +148,17 @@ export function pathTemplate<T extends PathTemplateData>(options: T): Rule {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Remove every `.template` suffix from file names.
|
||||
*/
|
||||
export function renameTemplateFiles(): Rule {
|
||||
return rename(
|
||||
path => !!path.match(TEMPLATE_FILENAME_RE),
|
||||
path => path.replace(TEMPLATE_FILENAME_RE, ''),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export function template<T>(options: T): Rule {
|
||||
return chain([
|
||||
contentTemplate(options),
|
||||
@ -153,3 +168,23 @@ export function template<T>(options: T): Rule {
|
||||
pathTemplate(options as {} as PathTemplateData),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
export function applyTemplates<T>(options: T): Rule {
|
||||
return forEach(
|
||||
when(
|
||||
path => path.endsWith('.template'),
|
||||
composeFileOperators([
|
||||
applyContentTemplate(options),
|
||||
// See above for this weird cast.
|
||||
applyPathTemplate(options as {} as PathTemplateData),
|
||||
entry => {
|
||||
return {
|
||||
content: entry.content,
|
||||
path: entry.path.replace(TEMPLATE_FILENAME_RE, ''),
|
||||
} as FileEntry;
|
||||
},
|
||||
]),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -5,14 +5,21 @@
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
// tslint:disable:no-implicit-dependencies
|
||||
import { normalize } from '@angular-devkit/core';
|
||||
import { FileEntry } from '../tree/interface';
|
||||
import { UnitTestTree } from '@angular-devkit/schematics/testing';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { SchematicContext } from '../engine/interface';
|
||||
import { HostTree } from '../tree/host-tree';
|
||||
import { FileEntry, MergeStrategy } from '../tree/interface';
|
||||
import { callRule } from './call';
|
||||
import {
|
||||
InvalidPipeException,
|
||||
OptionIsNotDefinedException,
|
||||
UnknownPipeException,
|
||||
applyContentTemplate,
|
||||
applyPathTemplate,
|
||||
applyTemplates,
|
||||
} from './template';
|
||||
|
||||
|
||||
@ -151,3 +158,37 @@ describe('contentTemplate', () => {
|
||||
expect(_applyContentTemplate('a<%= "\\n" + "b" %>b', {})).toBe('a\nbb');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('applyTemplateFiles', () => {
|
||||
it('works with template files exclusively', done => {
|
||||
const tree = new UnitTestTree(new HostTree());
|
||||
tree.create('a/b/file1', 'hello world');
|
||||
tree.create('a/b/file2', 'hello world');
|
||||
tree.create('a/b/file3.template', 'hello <%= 1 %> world');
|
||||
tree.create('a/b/file__a__.template', 'hello <%= 1 %> world');
|
||||
tree.create('a/b/file__norename__', 'hello <%= 1 %> world');
|
||||
tree.create('a/c/file4', 'hello world');
|
||||
|
||||
const context: SchematicContext = {
|
||||
strategy: MergeStrategy.Default,
|
||||
} as SchematicContext;
|
||||
|
||||
// Rename all files that contain 'b' to 'hello'.
|
||||
callRule(applyTemplates({ a: 'foo' }), observableOf(tree), context)
|
||||
.toPromise()
|
||||
.then(() => {
|
||||
expect([...tree.files].sort()).toEqual([
|
||||
'/a/b/file1',
|
||||
'/a/b/file2',
|
||||
'/a/b/file3',
|
||||
'/a/b/file__norename__',
|
||||
'/a/b/filefoo',
|
||||
'/a/c/file4',
|
||||
]);
|
||||
|
||||
expect(tree.readContent('/a/b/file3')).toBe('hello 1 world');
|
||||
})
|
||||
.then(done, done.fail);
|
||||
});
|
||||
});
|
||||
|
Loading…
x
Reference in New Issue
Block a user