Add support for templates
This commit is contained in:
parent
fb85e42dee
commit
cae260b9ce
@ -24,6 +24,10 @@ export abstract class Directive extends AppClass {
|
||||
return ''
|
||||
}
|
||||
|
||||
public getOptions(): OptionDefinition[] {
|
||||
return []
|
||||
}
|
||||
|
||||
public abstract handle(argv: string[]): void | Promise<void>
|
||||
|
||||
private _setOptionValues(optionValues: any) {
|
||||
@ -111,10 +115,6 @@ export abstract class Directive extends AppClass {
|
||||
}
|
||||
}
|
||||
|
||||
public getOptions(): OptionDefinition[] {
|
||||
return []
|
||||
}
|
||||
|
||||
public getResolvedOptions(): CLIOption<any>[] {
|
||||
return this.getOptions().map(option => {
|
||||
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 {Singleton, Instantiable} from "@extollo/di"
|
||||
import {Unit, Logging} from "@extollo/lib"
|
||||
import {Singleton, Instantiable, Inject} from "@extollo/di"
|
||||
import {Collection} from "@extollo/util"
|
||||
import {CommandLineApplication} from "./CommandLineApplication"
|
||||
import {Directive} from "../Directive";
|
||||
import {Directive} from "../Directive"
|
||||
import {Template} from "../Template"
|
||||
import {directive_template} from "../templates/directive"
|
||||
|
||||
@Singleton()
|
||||
export class CommandLine extends Unit {
|
||||
@Inject()
|
||||
protected readonly logging!: Logging
|
||||
|
||||
protected directives: Collection<Instantiable<Directive>> = new Collection<Instantiable<Directive>>()
|
||||
protected templates: Collection<Template> = new Collection<Template>()
|
||||
|
||||
constructor() { super() }
|
||||
|
||||
async up() {
|
||||
this.registerTemplate(directive_template)
|
||||
}
|
||||
|
||||
public isCLI() {
|
||||
return this.app().hasUnit(CommandLineApplication)
|
||||
}
|
||||
@ -36,4 +46,25 @@ export class CommandLine extends Unit {
|
||||
public getDirectives() {
|
||||
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 {Directive} from "../Directive";
|
||||
import {ShellDirective} from "../directive/ShellDirective";
|
||||
import {TemplateDirective} from "../directive/TemplateDirective";
|
||||
|
||||
@Singleton()
|
||||
export class CommandLineApplication extends Unit {
|
||||
@ -23,6 +24,7 @@ export class CommandLineApplication extends Unit {
|
||||
public async up() {
|
||||
this.cli.registerDirective(UsageDirective)
|
||||
this.cli.registerDirective(ShellDirective)
|
||||
this.cli.registerDirective(TemplateDirective)
|
||||
|
||||
const argv = process.argv.slice(2)
|
||||
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