From e8e6bd9b3a5e429cd5fc715ecb649103fa79c718 Mon Sep 17 00:00:00 2001 From: Yasuaki Uechi Date: Tue, 8 Oct 2019 12:14:34 +0900 Subject: [PATCH] feat: new template system BREAKING CHANGES: - Omit {{author_full}} in favor of {{contact}} --- README.md | 2 +- package.json | 3 +- src/index.ts | 25 ++++---- src/template.ts | 61 ++++++++++++++++--- templates/default/README.md | 4 +- templates/default/package.json | 4 +- templates/default/src/cli.js | 2 +- templates/default/templates/default/README.md | 6 +- templates/typescript/README.md | 4 +- templates/typescript/package.json | 4 +- templates/typescript/src/cli.ts | 2 +- .../typescript/templates/default/README.md | 6 +- yarn.lock | 2 +- 13 files changed, 85 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 104902b..e02ee7d 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Edit files inside `templates/default`. Text files will be passed through Mustach - `{{description}}` package description - `{{author}}` author name - `{{email}}` author email -- `{{author_full}}` author name formatted with `{{name}} <{{email}}>` if email given, otherwise `{{name}}` +- `{{contact}}` author name formatted with `{{name}} <{{email}}>` if email given, otherwise `{{name}}` - `{{license}}` package license (e.g. `MIT`) - `{{year}}` current year (e.g. `2020`) diff --git a/package.json b/package.json index 7fedf8a..eaf32af 100644 --- a/package.json +++ b/package.json @@ -11,17 +11,16 @@ }, "dependencies": { "@types/inquirer": "^6.5.0", - "@types/mustache": "^0.8.32", "@types/yargs-interactive": "^2.1.0", "chalk": "^2.4.2", "cross-spawn": "^7.0.0", "execa": "^2.0.5", "gitconfig": "^2.0.8", "globby": "^10.0.1", + "handlebars": "^4.4.2", "inquirer": "^7.0.0", "is-utf8": "^0.2.1", "license.js": "^3.1.2", - "mustache": "^3.1.0", "yargs-interactive": "^3.0.0" }, "devDependencies": { diff --git a/src/index.ts b/src/index.ts index 62bfd09..e9a7f25 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,23 +9,26 @@ import {makeLicenseSync, availableLicenses} from 'license.js'; import {copy, getAvailableTemplates} from './template'; -export interface Config { - packageDir: string; - templateDir: string; - view: View; -} - export interface View { + name: string; + description: string; author: string; email: string; - description: string; + contact: string; license: string; [key: string]: string | number | boolean | any[]; } +export interface Config { + packageDir: string; + templateDir: string; + view: View; +} + export interface Options { extra?: Option; caveat?: string; + after?: () => void; } async function getGitUser() { @@ -67,7 +70,7 @@ async function initGit(root: string) { await execa('git init', {shell: true, cwd: root}); } -function authorString(author: string, email?: string) { +function getContact(author: string, email?: string) { return `${author}${email ? ` <${email}>` : ''}`; } @@ -153,7 +156,7 @@ export async function create( const templateDir = path.resolve(templateRoot, args.template); const year = new Date().getFullYear(); - const author_full = authorString(args.author, args.email); + const contact = getContact(args.author, args.email); if (!fs.existsSync(templateDir)) { throw new Error('No template found'); @@ -173,7 +176,7 @@ export async function create( ...filterdArgs, name, year, - author_full, + contact, }; // copy files from template @@ -189,7 +192,7 @@ export async function create( year, project: name, description: args.description, - organization: authorString(args.author, args.email), + organization: getContact(args.author, args.email), }); const licenseText = license.header + license.text + license.warranty; fs.writeFileSync(path.resolve(packageDir, 'LICENSE'), licenseText); diff --git a/src/template.ts b/src/template.ts index 3872c52..9700d59 100644 --- a/src/template.ts +++ b/src/template.ts @@ -2,12 +2,59 @@ import fs from 'fs'; import path from 'path'; import globby from 'globby'; import isUtf8 from 'is-utf8'; -import Mustache from 'mustache'; +import Handlebars from 'handlebars'; import {Config} from '.'; +function trim(text: string) { + return text.replace(/[\r\n]/g, ''); +} +Handlebars.registerHelper('trim', trim); + +function upper(text: string) { + return trim(text).toUpperCase(); +} +Handlebars.registerHelper('upper', upper); + +function lower(text: string) { + return trim(text).toLowerCase(); +} +Handlebars.registerHelper('lower', lower); + +function capital(text: string, options?: any) { + const space = options && options.hash && options.hash.space; + return trim(text) + .split(/[-_\s]+/) + .map((s) => s.charAt(0).toUpperCase() + s.slice(1).toLowerCase()) + .join(space ? ' ' : ''); +} +Handlebars.registerHelper('capital', capital); + +function camel(text: string) { + return capital(text).replace(/^./, (s) => s.toLowerCase()); +} +Handlebars.registerHelper('camel', camel); + +function snake(text: string) { + return capital(text) + .replace(/(?<=([a-z](?=[A-Z])|[A-Za-z](?=[0-9])))(?=[A-Z0-9])/g, '_') + .toLowerCase(); +} +Handlebars.registerHelper('snake', snake); + +function kebab(text: string) { + return snake(text).replace(/_/g, '-'); +} +Handlebars.registerHelper('kebab', kebab); + +function format(text: Buffer | string, view: T) { + const template = Handlebars.compile(text.toString()); + return template(view); +} + function prepareDirectory(filePath: string) { try { - fs.mkdirSync(path.dirname(filePath), {recursive: true}); + const target = path.dirname(filePath); + fs.mkdirSync(target, {recursive: true}); } catch {} } @@ -19,16 +66,16 @@ export async function copy(args: Config) { const templateFiles = await globby(args.templateDir, {dot: true}); for (const sourcePath of templateFiles) { const relativePath = path.relative(args.templateDir, sourcePath); - const targetPath = path.resolve(args.packageDir, relativePath); - + const targetPath = format( + path.resolve(args.packageDir, relativePath), + args.view, + ); prepareDirectory(targetPath); let sourceData = fs.readFileSync(sourcePath); let targetData = sourceData; if (isUtf8(sourceData)) { - targetData = Buffer.from( - Mustache.render(sourceData.toString(), args.view), - ); + targetData = Buffer.from(format(sourceData, args.view)); } fs.writeFileSync(targetPath, targetData, 'utf-8'); } diff --git a/templates/default/README.md b/templates/default/README.md index f469a05..8c22ab8 100644 --- a/templates/default/README.md +++ b/templates/default/README.md @@ -1,9 +1,9 @@ -# {{name}} +# {{capital name space=true}} > {{description}} ## Usage ```bash -npx {{name}} +npx {{kebab name}} ``` diff --git a/templates/default/package.json b/templates/default/package.json index e9bf332..843313b 100644 --- a/templates/default/package.json +++ b/templates/default/package.json @@ -1,11 +1,11 @@ { - "name": "{{name}}", + "name": "{{kebab name}}", "description": "{{description}}", "version": "0.0.0", "dependencies": { "create-whatever": "^3.0.0" }, - "author": "{{author_full}}", + "author": "{{contact}}", "license": "{{license}}", "bin": "src/cli.js", "files": [ diff --git a/templates/default/src/cli.js b/templates/default/src/cli.js index 1ec09fb..ef3e471 100755 --- a/templates/default/src/cli.js +++ b/templates/default/src/cli.js @@ -8,4 +8,4 @@ const caveat = `This is a caveat! You can find this section in "src/cli.ts". `; -create('{{name}}', templateRoot, {caveat}); +create('{{kebab name}}', templateRoot, {caveat}); diff --git a/templates/default/templates/default/README.md b/templates/default/templates/default/README.md index 5afa765..c7e5473 100644 --- a/templates/default/templates/default/README.md +++ b/templates/default/templates/default/README.md @@ -1,5 +1,3 @@ -{{=<% %>=}} -# {{name}} +# \{{name}} -{{description}} -<%={{ }}=%> +\{{description}} diff --git a/templates/typescript/README.md b/templates/typescript/README.md index f469a05..8c22ab8 100644 --- a/templates/typescript/README.md +++ b/templates/typescript/README.md @@ -1,9 +1,9 @@ -# {{name}} +# {{capital name space=true}} > {{description}} ## Usage ```bash -npx {{name}} +npx {{kebab name}} ``` diff --git a/templates/typescript/package.json b/templates/typescript/package.json index 40269fc..9885b9d 100644 --- a/templates/typescript/package.json +++ b/templates/typescript/package.json @@ -1,5 +1,5 @@ { - "name": "{{name}}", + "name": "{{kebab name}}", "description": "{{description}}", "version": "0.0.0", "scripts": { @@ -13,7 +13,7 @@ "shx": "^0.3.2", "typescript": "^3.6.3" }, - "author": "{{author_full}}", + "author": "{{contact}}", "license": "{{license}}", "bin": "dist/cli.js", "files": [ diff --git a/templates/typescript/src/cli.ts b/templates/typescript/src/cli.ts index f7b0301..565b18d 100644 --- a/templates/typescript/src/cli.ts +++ b/templates/typescript/src/cli.ts @@ -8,4 +8,4 @@ const caveat = `This is a caveat! You can find this section in "src/cli.ts". `; -create('{{name}}', templateRoot, {caveat}); +create('{{kebab name}}', templateRoot, {caveat}); diff --git a/templates/typescript/templates/default/README.md b/templates/typescript/templates/default/README.md index 5afa765..c7e5473 100644 --- a/templates/typescript/templates/default/README.md +++ b/templates/typescript/templates/default/README.md @@ -1,5 +1,3 @@ -{{=<% %>=}} -# {{name}} +# \{{name}} -{{description}} -<%={{ }}=%> +\{{description}} diff --git a/yarn.lock b/yarn.lock index efee990..e797160 100644 --- a/yarn.lock +++ b/yarn.lock @@ -949,7 +949,7 @@ graceful-fs@^4.1.15, graceful-fs@^4.1.2: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.2.tgz#6f0952605d0140c1cfdb138ed005775b92d67b02" integrity sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q== -handlebars@^4.1.2: +handlebars@^4.1.2, handlebars@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.4.2.tgz#8810a9821a9d6d52cb2f57d326d6ce7c3dfe741d" integrity sha512-cIv17+GhL8pHHnRJzGu2wwcthL5sb8uDKBHvZ2Dtu5s1YNt0ljbzKbamnc+gr69y7bzwQiBdr5+hOpRd5pnOdg==