diff --git a/src/Directive.ts b/src/Directive.ts index d5822d3..f85e0f0 100644 --- a/src/Directive.ts +++ b/src/Directive.ts @@ -24,6 +24,10 @@ export abstract class Directive extends AppClass { return '' } + public getOptions(): OptionDefinition[] { + return [] + } + public abstract handle(argv: string[]): void | Promise private _setOptionValues(optionValues: any) { @@ -111,10 +115,6 @@ export abstract class Directive extends AppClass { } } - public getOptions(): OptionDefinition[] { - return [] - } - public getResolvedOptions(): CLIOption[] { return this.getOptions().map(option => { if ( typeof option === 'string' ) { diff --git a/src/Template.ts b/src/Template.ts new file mode 100644 index 0000000..df52e56 --- /dev/null +++ b/src/Template.ts @@ -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 +} diff --git a/src/directive/TemplateDirective.ts b/src/directive/TemplateDirective.ts new file mode 100644 index 0000000..1b82941 --- /dev/null +++ b/src/directive/TemplateDirective.ts @@ -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}`) + } +} diff --git a/src/service/CommandLine.ts b/src/service/CommandLine.ts index 1a9637c..13b1807 100644 --- a/src/service/CommandLine.ts +++ b/src/service/CommandLine.ts @@ -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> = new Collection>() + protected templates: Collection