2019-10-07 14:41:38 +00:00
|
|
|
/*
|
|
|
|
|
* Copyright 2011 Twitter, Inc.
|
|
|
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
|
* you may not use this file except in compliance with the License.
|
|
|
|
|
* You may obtain a copy of the License at
|
|
|
|
|
*
|
|
|
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
*
|
|
|
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
|
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
|
* See the License for the specific language governing permissions and
|
|
|
|
|
* limitations under the License.
|
|
|
|
|
*/
|
|
|
|
|
|
2019-12-29 22:31:32 +00:00
|
|
|
import * as path from 'path';
|
|
|
|
|
import * as fs from 'fs';
|
2019-10-07 14:41:38 +00:00
|
|
|
|
2019-12-29 22:31:32 +00:00
|
|
|
import * as hogan from 'hogan.js';
|
|
|
|
|
import nopt from 'nopt';
|
|
|
|
|
import * as mkderp from 'mkdirp';
|
2019-10-07 14:41:38 +00:00
|
|
|
|
|
|
|
|
const options = nopt(
|
|
|
|
|
{
|
|
|
|
|
namespace: String,
|
|
|
|
|
outputdir: path,
|
|
|
|
|
variable: String,
|
|
|
|
|
wrapper: String,
|
|
|
|
|
version: true,
|
2019-12-29 22:31:32 +00:00
|
|
|
help: true,
|
2019-10-07 14:41:38 +00:00
|
|
|
},
|
|
|
|
|
{
|
2019-12-29 22:31:32 +00:00
|
|
|
n: ['--namespace'],
|
|
|
|
|
o: ['--outputdir'],
|
|
|
|
|
vn: ['--variable'],
|
|
|
|
|
w: ['--wrapper'],
|
|
|
|
|
h: ['--help'],
|
|
|
|
|
v: ['--version'],
|
|
|
|
|
},
|
2019-10-07 14:41:38 +00:00
|
|
|
);
|
|
|
|
|
|
2019-12-29 22:31:32 +00:00
|
|
|
const specials = ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\'];
|
|
|
|
|
const specialsRegExp = new RegExp('(\\' + specials.join('|\\') + ')', 'g');
|
2019-10-07 14:41:38 +00:00
|
|
|
function escape(text: string): string {
|
2019-12-29 22:31:32 +00:00
|
|
|
return text.replace(specialsRegExp, '\\$1');
|
2019-10-07 14:41:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function cyan(text: string): string {
|
2019-12-29 22:31:32 +00:00
|
|
|
return '\x1B[36m' + text + '\x1B[39m';
|
2019-10-07 14:41:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function extractFiles(files: string[]): string[] {
|
2019-10-12 21:45:49 +00:00
|
|
|
const usage = `${cyan(
|
2019-12-29 22:31:32 +00:00
|
|
|
'USAGE:',
|
2019-10-12 21:45:49 +00:00
|
|
|
)} hulk [--wrapper wrapper] [--outputdir outputdir] [--namespace namespace] [--variable variable] FILES
|
2019-10-07 14:41:38 +00:00
|
|
|
|
2019-12-29 22:31:32 +00:00
|
|
|
${cyan('OPTIONS:')} [-w, --wrapper] :: wraps the template (i.e. amd)
|
2019-10-07 14:41:38 +00:00
|
|
|
[-o, --outputdir] :: outputs the templates as individual files to a directory
|
|
|
|
|
|
|
|
|
|
[-n, --namespace] :: prepend string to template names
|
|
|
|
|
|
|
|
|
|
[-vn, --variable] :: variable name for non-amd wrapper
|
|
|
|
|
|
2019-12-29 22:31:32 +00:00
|
|
|
${cyan('EXAMPLE:')} hulk --wrapper amd ./templates/*.mustache
|
2019-10-07 14:41:38 +00:00
|
|
|
|
2019-12-29 22:31:32 +00:00
|
|
|
${cyan('NOTE:')} hulk supports the "*" wildcard and allows you to target specific extensions too
|
2019-10-07 14:41:38 +00:00
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
if (options.version) {
|
2019-12-29 22:31:32 +00:00
|
|
|
console.log(require('../package.json').version);
|
2019-10-07 14:41:38 +00:00
|
|
|
process.exit(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!files.length || options.help) {
|
|
|
|
|
console.log(usage);
|
|
|
|
|
process.exit(0);
|
|
|
|
|
}
|
|
|
|
|
|
2019-12-29 22:31:32 +00:00
|
|
|
return files
|
2019-10-07 14:41:38 +00:00
|
|
|
.map((fileGlob: string) => {
|
|
|
|
|
if (/\*/.test(fileGlob)) {
|
2019-12-29 22:31:32 +00:00
|
|
|
const [fileGlobPrefix, fileGlobSuffix] = fileGlob.split('*');
|
2019-10-07 14:41:38 +00:00
|
|
|
|
2019-12-29 22:31:32 +00:00
|
|
|
return fs.readdirSync(fileGlobPrefix || '.').reduce<string[]>((previousFiles, relativeFilePath) => {
|
2019-10-07 14:41:38 +00:00
|
|
|
const file = path.join(fileGlobPrefix, relativeFilePath);
|
|
|
|
|
if (new RegExp(`${escape(fileGlobSuffix)}$`).test(relativeFilePath) && fs.statSync(file).isFile()) {
|
|
|
|
|
previousFiles.push(file);
|
|
|
|
|
}
|
|
|
|
|
return previousFiles;
|
|
|
|
|
}, []);
|
|
|
|
|
} else if (fs.statSync(fileGlob).isFile()) {
|
|
|
|
|
return [fileGlob];
|
|
|
|
|
} else {
|
|
|
|
|
return [];
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.reduce((previous, current) => previous.concat(current), []);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Remove utf-8 byte order mark, http://en.wikipedia.org/wiki/Byte_order_mark
|
|
|
|
|
function removeByteOrderMark(text: string): string {
|
|
|
|
|
if (text.charCodeAt(0) === 0xfeff) {
|
|
|
|
|
return text.substring(1);
|
|
|
|
|
}
|
|
|
|
|
return text;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Wrap templates
|
|
|
|
|
function wrap(file: string, name: string, openedFile: string): string {
|
|
|
|
|
const hoganTemplateString = `new Hogan.Template(${hogan.compile(openedFile, { asString: true })})`;
|
|
|
|
|
|
2019-12-29 22:31:32 +00:00
|
|
|
const objectName = options.variable || 'templates';
|
2019-10-07 14:41:38 +00:00
|
|
|
const objectAccessor = `${objectName}["${name}"]`;
|
|
|
|
|
const objectStmt = `${objectAccessor} = ${hoganTemplateString};`;
|
|
|
|
|
|
|
|
|
|
switch (options.wrapper) {
|
2019-12-29 22:31:32 +00:00
|
|
|
case 'amd':
|
2019-10-07 14:41:38 +00:00
|
|
|
return `define(${
|
2019-12-29 22:31:32 +00:00
|
|
|
!options.outputdir ? `"${path.join(path.dirname(file), name)}", ` : ''
|
2019-10-07 14:41:38 +00:00
|
|
|
}["hogan.js"], function(Hogan) { return ${hoganTemplateString}; });`;
|
|
|
|
|
|
2019-12-29 22:31:32 +00:00
|
|
|
case 'node':
|
2019-10-07 14:41:38 +00:00
|
|
|
// If we have a template per file the export will expose the template directly
|
|
|
|
|
return options.outputdir ? `global.${objectStmt};\nmodule.exports = ${objectAccessor};` : `global.${objectStmt}`;
|
|
|
|
|
|
2019-12-29 22:31:32 +00:00
|
|
|
case 'ts':
|
2019-10-12 21:45:49 +00:00
|
|
|
return `// @ts-ignore\n${objectStmt}`;
|
2019-10-07 14:41:38 +00:00
|
|
|
default:
|
|
|
|
|
return objectStmt;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function prepareOutput(content: string): string {
|
2019-12-29 22:31:32 +00:00
|
|
|
const variableName = options.variable || 'templates';
|
2019-10-07 14:41:38 +00:00
|
|
|
switch (options.wrapper) {
|
2019-12-29 22:31:32 +00:00
|
|
|
case 'amd':
|
2019-10-07 14:41:38 +00:00
|
|
|
return content;
|
2019-12-29 22:31:32 +00:00
|
|
|
case 'node':
|
2019-10-12 21:45:49 +00:00
|
|
|
return `(function() {
|
|
|
|
|
if (!!!global.${variableName}) global.${variableName} = {};
|
|
|
|
|
var Hogan = require("hogan.js");
|
|
|
|
|
${content}
|
2019-12-29 22:31:32 +00:00
|
|
|
${!options.outputdir ? `module.exports = global.${variableName};\n` : ''})();`;
|
2019-10-12 21:45:49 +00:00
|
|
|
|
2019-12-29 22:31:32 +00:00
|
|
|
case 'ts':
|
2019-10-12 21:45:49 +00:00
|
|
|
return `import * as Hogan from "hogan.js";
|
|
|
|
|
type CompiledTemplates = { [name: string]: Hogan.Template };
|
|
|
|
|
export const ${variableName}: CompiledTemplates = {};
|
|
|
|
|
${content}`;
|
|
|
|
|
|
2019-10-07 14:41:38 +00:00
|
|
|
default:
|
2019-12-29 22:31:32 +00:00
|
|
|
return 'if (!!!' + variableName + ') var ' + variableName + ' = {};\n' + content;
|
2019-10-07 14:41:38 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write the directory
|
|
|
|
|
if (options.outputdir) {
|
|
|
|
|
mkderp.sync(options.outputdir);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Prepend namespace to template name
|
|
|
|
|
function namespace(name: string): string {
|
2019-12-29 22:31:32 +00:00
|
|
|
return (options.namespace || '') + name;
|
2019-10-07 14:41:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Write a template foreach file that matches template extension
|
|
|
|
|
const templates = extractFiles(options.argv.remain)
|
|
|
|
|
.map(file => {
|
2019-12-29 22:31:32 +00:00
|
|
|
const timmedFileContents = fs.readFileSync(file, 'utf8').trim();
|
2019-10-07 14:41:38 +00:00
|
|
|
|
|
|
|
|
if (!timmedFileContents) return;
|
|
|
|
|
|
2019-12-29 22:31:32 +00:00
|
|
|
const name = namespace(path.basename(file).replace(/\..*$/, ''));
|
2019-10-07 14:41:38 +00:00
|
|
|
const cleanFileContents = wrap(file, name, removeByteOrderMark(timmedFileContents));
|
|
|
|
|
|
|
|
|
|
if (!options.outputdir) return cleanFileContents;
|
|
|
|
|
|
2019-12-29 22:31:32 +00:00
|
|
|
const fileExtension = options.wrapper === 'ts' ? 'ts' : 'js';
|
2019-10-12 21:45:49 +00:00
|
|
|
|
|
|
|
|
return fs.writeFileSync(path.join(options.outputdir, `${name}.${fileExtension}`), prepareOutput(cleanFileContents));
|
2019-10-07 14:41:38 +00:00
|
|
|
})
|
2019-12-29 22:31:32 +00:00
|
|
|
.filter(templateContents => typeof templateContents !== 'undefined');
|
2019-10-07 14:41:38 +00:00
|
|
|
|
|
|
|
|
// Output templates
|
|
|
|
|
if (!templates.length || options.outputdir) process.exit(0);
|
|
|
|
|
|
2019-12-29 22:31:32 +00:00
|
|
|
console.log(prepareOutput(templates.join('\n')));
|