Add support for templates
This commit is contained in:
parent
fb85e42dee
commit
cae260b9ce
@ -24,6 +24,10 @@ export abstract class Directive extends AppClass {
|
|||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getOptions(): OptionDefinition[] {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
public abstract handle(argv: string[]): void | Promise<void>
|
public abstract handle(argv: string[]): void | Promise<void>
|
||||||
|
|
||||||
private _setOptionValues(optionValues: any) {
|
private _setOptionValues(optionValues: any) {
|
||||||
@ -111,10 +115,6 @@ export abstract class Directive extends AppClass {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getOptions(): OptionDefinition[] {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
public getResolvedOptions(): CLIOption<any>[] {
|
public getResolvedOptions(): CLIOption<any>[] {
|
||||||
return this.getOptions().map(option => {
|
return this.getOptions().map(option => {
|
||||||
if ( typeof option === 'string' ) {
|
if ( typeof option === 'string' ) {
|
||||||
|
9
src/Template.ts
Normal file
9
src/Template.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import {UniversalPath} from '@extollo/util'
|
||||||
|
|
||||||
|
export interface Template {
|
||||||
|
name: string,
|
||||||
|
fileSuffix: string,
|
||||||
|
description: string,
|
||||||
|
baseAppPath: string[],
|
||||||
|
render: (name: string, fullCanonicalName: string, targetFilePath: UniversalPath) => string | Promise<string>
|
||||||
|
}
|
87
src/directive/TemplateDirective.ts
Normal file
87
src/directive/TemplateDirective.ts
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import {Directive, OptionDefinition} from "../Directive"
|
||||||
|
import {Injectable, Inject} from "@extollo/di"
|
||||||
|
import {ErrorWithContext} from "@extollo/util"
|
||||||
|
import {PositionalOption} from "./options/PositionalOption"
|
||||||
|
import {CommandLine} from "../service"
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TemplateDirective extends Directive {
|
||||||
|
@Inject()
|
||||||
|
protected readonly cli!: CommandLine
|
||||||
|
|
||||||
|
getKeywords(): string | string[] {
|
||||||
|
return ['new', 'make']
|
||||||
|
}
|
||||||
|
|
||||||
|
getDescription(): string {
|
||||||
|
return 'create a new file from a registered template'
|
||||||
|
}
|
||||||
|
|
||||||
|
getOptions(): OptionDefinition[] {
|
||||||
|
const registeredTemplates = this.cli.getTemplates()
|
||||||
|
const template = new PositionalOption('template_name', 'the template to base the new file on (e.g. model, controller)')
|
||||||
|
template.whitelist(...registeredTemplates.pluck('name'))
|
||||||
|
|
||||||
|
const destination = new PositionalOption('file_name', 'canonical name of the file to create (e.g. auth:Group, dash:Activity)')
|
||||||
|
|
||||||
|
return [template, destination]
|
||||||
|
}
|
||||||
|
|
||||||
|
getHelpText(): string {
|
||||||
|
const registeredTemplates = this.cli.getTemplates()
|
||||||
|
|
||||||
|
return [
|
||||||
|
'Modules in Extollo register templates that can be used to quickly create common file types.',
|
||||||
|
'',
|
||||||
|
'For example, you can create a new model from @extollo/orm using the "model" template:',
|
||||||
|
'',
|
||||||
|
'./ex new model auth:Group',
|
||||||
|
'',
|
||||||
|
'This would create a new Group model in the ./src/app/models/auth/Group.model.ts file.',
|
||||||
|
'',
|
||||||
|
'AVAILABLE TEMPLATES:',
|
||||||
|
'',
|
||||||
|
...(registeredTemplates.map(template => {
|
||||||
|
return ` - ${template.name}: ${template.description}`
|
||||||
|
}))
|
||||||
|
].join('\n')
|
||||||
|
}
|
||||||
|
|
||||||
|
async handle(argv: string[]) {
|
||||||
|
const templateName: string = this.option('template_name')
|
||||||
|
const destinationName: string = this.option('file_name')
|
||||||
|
|
||||||
|
if ( destinationName.includes('/') || destinationName.includes('\\') ) {
|
||||||
|
this.error(`The destination should be a canonical name, not a file path.`)
|
||||||
|
this.error(`Reference sub-directories using the : character instead.`)
|
||||||
|
this.error(`Did you mean ${destinationName.replace(/\/|\\/g, ':')}?`)
|
||||||
|
process.exitCode = 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const template = this.cli.getTemplate(templateName)
|
||||||
|
if ( !template ) {
|
||||||
|
throw new ErrorWithContext(`Unable to find template supposedly registered with name: ${templateName}`, {
|
||||||
|
templateName,
|
||||||
|
destinationName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = destinationName.split(':').reverse()[0]
|
||||||
|
const path = this.app().path('..', 'src', 'app', ...template.baseAppPath, ...(`${destinationName}${template.fileSuffix}`).split(':'))
|
||||||
|
|
||||||
|
if ( await path.exists() ) {
|
||||||
|
this.error(`File already exists: ${path}`)
|
||||||
|
process.exitCode = 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the parent direction exists
|
||||||
|
await path.concat('..').mkdir()
|
||||||
|
|
||||||
|
const contents = await template.render(name, destinationName, path.clone())
|
||||||
|
await path.write(contents)
|
||||||
|
|
||||||
|
this.success(`Created new ${template.name} in ${path}`)
|
||||||
|
}
|
||||||
|
}
|
@ -1,15 +1,25 @@
|
|||||||
import {Unit} from "@extollo/lib"
|
import {Unit, Logging} from "@extollo/lib"
|
||||||
import {Singleton, Instantiable} from "@extollo/di"
|
import {Singleton, Instantiable, Inject} from "@extollo/di"
|
||||||
import {Collection} from "@extollo/util"
|
import {Collection} from "@extollo/util"
|
||||||
import {CommandLineApplication} from "./CommandLineApplication"
|
import {CommandLineApplication} from "./CommandLineApplication"
|
||||||
import {Directive} from "../Directive";
|
import {Directive} from "../Directive"
|
||||||
|
import {Template} from "../Template"
|
||||||
|
import {directive_template} from "../templates/directive"
|
||||||
|
|
||||||
@Singleton()
|
@Singleton()
|
||||||
export class CommandLine extends Unit {
|
export class CommandLine extends Unit {
|
||||||
|
@Inject()
|
||||||
|
protected readonly logging!: Logging
|
||||||
|
|
||||||
protected directives: Collection<Instantiable<Directive>> = new Collection<Instantiable<Directive>>()
|
protected directives: Collection<Instantiable<Directive>> = new Collection<Instantiable<Directive>>()
|
||||||
|
protected templates: Collection<Template> = new Collection<Template>()
|
||||||
|
|
||||||
constructor() { super() }
|
constructor() { super() }
|
||||||
|
|
||||||
|
async up() {
|
||||||
|
this.registerTemplate(directive_template)
|
||||||
|
}
|
||||||
|
|
||||||
public isCLI() {
|
public isCLI() {
|
||||||
return this.app().hasUnit(CommandLineApplication)
|
return this.app().hasUnit(CommandLineApplication)
|
||||||
}
|
}
|
||||||
@ -36,4 +46,25 @@ export class CommandLine extends Unit {
|
|||||||
public getDirectives() {
|
public getDirectives() {
|
||||||
return this.directives.clone()
|
return this.directives.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public registerTemplate(template: Template) {
|
||||||
|
if ( !this.templates.firstWhere('name', '=', template.name) ) {
|
||||||
|
this.templates.push(template)
|
||||||
|
} else {
|
||||||
|
this.logging.warn(`Duplicate template will not be registered: ${template.name}`)
|
||||||
|
this.logging.debug(`Duplicate template registered at: ${(new Error()).stack}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasTemplate(name: string) {
|
||||||
|
return !!this.templates.firstWhere('name', '=', name)
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTemplate(name: string) {
|
||||||
|
return this.templates.firstWhere('name', '=', name)
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTemplates() {
|
||||||
|
return this.templates.clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ import {CommandLine} from "./CommandLine"
|
|||||||
import {UsageDirective} from "../directive/UsageDirective";
|
import {UsageDirective} from "../directive/UsageDirective";
|
||||||
import {Directive} from "../Directive";
|
import {Directive} from "../Directive";
|
||||||
import {ShellDirective} from "../directive/ShellDirective";
|
import {ShellDirective} from "../directive/ShellDirective";
|
||||||
|
import {TemplateDirective} from "../directive/TemplateDirective";
|
||||||
|
|
||||||
@Singleton()
|
@Singleton()
|
||||||
export class CommandLineApplication extends Unit {
|
export class CommandLineApplication extends Unit {
|
||||||
@ -23,6 +24,7 @@ export class CommandLineApplication extends Unit {
|
|||||||
public async up() {
|
public async up() {
|
||||||
this.cli.registerDirective(UsageDirective)
|
this.cli.registerDirective(UsageDirective)
|
||||||
this.cli.registerDirective(ShellDirective)
|
this.cli.registerDirective(ShellDirective)
|
||||||
|
this.cli.registerDirective(TemplateDirective)
|
||||||
|
|
||||||
const argv = process.argv.slice(2)
|
const argv = process.argv.slice(2)
|
||||||
const match = this.cli.getDirectives()
|
const match = this.cli.getDirectives()
|
||||||
|
40
src/templates/directive.ts
Normal file
40
src/templates/directive.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import {Template} from "../Template"
|
||||||
|
import {UniversalPath} from "@extollo/util"
|
||||||
|
|
||||||
|
const directive_template: Template = {
|
||||||
|
name: 'directive',
|
||||||
|
fileSuffix: '.directive.ts',
|
||||||
|
description: 'Create a new Directive class which adds functionality to the ./ex command.',
|
||||||
|
baseAppPath: ['directives'],
|
||||||
|
render(name: string, fullCanonicalName: string, targetFilePath: UniversalPath) {
|
||||||
|
return `import {Directive, OptionDefinition} from "@extollo/cli"
|
||||||
|
import {Injectable} from "@extollo/di"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ${name} Directive
|
||||||
|
* ---------------------------------------------------
|
||||||
|
* Put some description here.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class ${name}Directive extends Directive {
|
||||||
|
getKeywords(): string | string[] {
|
||||||
|
return ['${name.toLowerCase()}']
|
||||||
|
}
|
||||||
|
|
||||||
|
getDescription(): string {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
getOptions(): OptionDefinition[] {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
async handle(argv: string[]) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { directive_template }
|
10
src/tsconfig.json
Normal file
10
src/tsconfig.json
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
`{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "es5",
|
||||||
|
"sourceMap": true
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user