Skip to content

Commit

Permalink
feat: initial implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
keithamus committed Jan 8, 2016
0 parents commit 0d4c74b
Show file tree
Hide file tree
Showing 8 changed files with 289 additions and 0 deletions.
9 changes: 9 additions & 0 deletions .editorconfig
@@ -0,0 +1,9 @@
root = true

[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
charset = utf-8
indent_style = space
indent_size = 2
9 changes: 9 additions & 0 deletions .gitignore
@@ -0,0 +1,9 @@
*~
Thumbs.db
node_modules/
*.log
*.log*
*.pid
*.seed
lib/
test/test*.html
15 changes: 15 additions & 0 deletions .travis.yml
@@ -0,0 +1,15 @@
sudo: false
language: node_js
cache:
directories:
- node_modules
node_js:
- '0.12'
- v4
- stable
after_success:
- 'travis-after-all && npm run semantic-release'
env:
global:
- secure: TuAEDPV6xmK6b0GF9oOTTdeLeA/2X3WDX3gfDAACnjKy3meADj0gIdZw1847jchLvKG5Hh+a3fpi8IA07yYqz/lqiNUbiuei18aScLd37Gq9U8ew0QoylS+c5j4KoiJLjzEHoz2t+yMUiDJbiYd69KHePfutFXvV2YbctL9PLWsJheDSpjqXdl2OTKTrfdvidOqy+U8H/1wuYozCJLw23NsLOafZPEGaQf5KCvdFUhGV5lbfVuB7JHhPvgDpKjw8+eHv6P6oUfiXbDybLlz0Taqp3LVb7B+9UzAzhIzPyNBSpmsT0tn7yfGeBTeRRXgN78S8kN0rtaSJFEuGJq0tr/d+1u3dUHLz4ny38kOx9Vj8vgbd3hvjJ5xdWr6un1cVTO7c5vmX1Rw3Ss6T3pMkdd66JNqktvF43EUMfGk6LoI0j7O/qjvELrxaPIwQfeDMkYukLrR7KW5endcY78NakIwPE9MYzEronn0Yt11vTyF3GBit+/hGUDS9zgxFBGsESDmF8S/ApsLn0jaRKUduV6nc2wnjd2Q0mejApcSnxFCQm8ii6scREifrHw2wS72uUae/cDVu2tKx/xZevcGIWEG0I/Hu+xczFIZ+iAXZOst2aKeaZw2HRku91SVX0VJICIgzQi9tvWAmVyeR5V0hpG0xSvg5t5tNN9bTJbXEoUw=
- secure: fInupYDLX9WLepc+D6MXBwOlZEff8uXof8OL63RU4jphBmD9q5NZi55bcHYT44kpYXHQ1YsdXazbeS/3iKitK0rz2fCprWNcLRKYXNVTTetpLoWIgrn4+nVNbeXC5KTLooAmnQns5kJ2FGDHwptSUCSIdJctFFzzTb4ju2MqTJotLa851TcNQtgh/HTkrpIb25ytPTZeomVJproEuzYC6aUz09bMI5pnhsn7gshb5AcrBiOEKByQfhF1sxoC5ZYXZ479ZagGTzqFOgC1FxzEWrMzwcd2uQrwIUYoLrI9xZg89tJkf//BG8U+C0McJTL4kHXIxxw8jTHgxkbrtRD+4c8NzaLkGkCkuSb7llpDMMjDaPrlehvruczsh58EoL29XESgFl3lomdUC+jOgDfNLCpUDYjz0eBUjqOSaPNdlvOXiHsLzolwT8IVTKSTF/7Y4HeZN0WKh0rNxY6L4z0+6Gs1KhctAIoYprGnq52enu76cVShVTdN6gbxYCXsY4yyymxy0sPZHViZm6i7e+IJVEkVnOOZQ7p5f6LIAhUz9UOijHrx8YqY+a87w8CqjIEFSkn2ii0bHoC9jQAXvCjsFj0KxMz0RNiBQwTjVvuaxNq+HzXTNNL3IX7OHYO03F4mcABBc6Du3U3tvbp3mRpw9pRJS9FWJdy+Xd44ld+FJFY=
74 changes: 74 additions & 0 deletions package.json
@@ -0,0 +1,74 @@
{
"name": "hbs-cli",
"version": "0.0.0-development",
"description": "A CLI tool for rendering Handlebars templates",
"homepage": "http://github.com/keithamus/hbs-cli/issues",
"bugs": "http://github.com/keithamus/hbs-cli/issues",
"license": "MIT",
"author": "Keith Cirkel (http://keithcirkel.co.uk)",
"files": [
"lib/*.js"
],
"main": "lib/index.js",
"bin": {
"hbs": "lib/index.js"
},
"repository": {
"type": "git",
"url": "git+ssh://git@github.com/keithamus/hbs-cli.git"
},
"scripts": {
"lint": "eslint src test --ignore-path .gitignore",
"prepublish": "babel src -d lib",
"pretest": "npm run lint",
"semantic-release": "semantic-release pre && npm publish && semantic-release post",
"test": "npm run test:run:1 && npm run test:verify:1",
"test:run:1": "node . -H handlebars-helper-br -D ./test/data.json -o test test/test-1.hbs",
"test:verify:1": "diff test/test-1.html test/verify-1.html",
"watch": "npm run prepublish -- -w"
},
"config": {
"ghooks": {
"commit-msg": "validate-commit-msg",
"pre-commit": "npm test"
}
},
"babel": {
"compact": false,
"ignore": "node_modules",
"loose": "all",
"optional": "runtime",
"retainLines": true,
"stage": 2
},
"eslintConfig": {
"extends": "strict",
"parser": "babel-eslint",
"rules": {
"no-console": 0,
"no-process-exit": 0
}
},
"dependencies": {
"babel-runtime": "^5.8.34",
"debug": "^2.2.0",
"fs-promise": "^0.3.1",
"glob-promise": "^1.0.4",
"handlebars": "^4.0.5",
"lodash.merge": "^3.3.2",
"minimist": "^1.2.0",
"resolve": "^1.1.6"
},
"devDependencies": {
"babel": "^5.8.34",
"babel-eslint": "^4.1.6",
"eslint": "^1.10.3",
"eslint-config-strict": "^7.0.4",
"eslint-plugin-filenames": "^0.2.0",
"ghooks": "^1.0.1",
"handlebars-helper-br": "^0.1.0",
"semantic-release": "^4.3.5",
"travis-after-all": "^1.4.4",
"validate-commit-msg": "^1.0.0"
}
}
164 changes: 164 additions & 0 deletions src/index.js
@@ -0,0 +1,164 @@
#!/usr/bin/env node
import { resolve as resolvePath, basename, extname } from 'path';
import Handlebars from 'handlebars';
import minimist from 'minimist';
import glob from 'glob-promise';
import packageJson from '../package.json';
import resolveNode from 'resolve';
import { readFile, writeFile } from 'fs-promise';
import merge from 'lodash.merge';
const debug = require('debug')('hbs');
function resolve(file, options) {
return new Promise((resolvePromise, reject) => resolveNode(file, options, (error, path) => {
if (error) {
reject(error);
} else {
resolvePromise(path);
}
}));
}
export async function resolveModuleOrGlob(path, cwd = process.cwd()) {
try {
debug(`Trying to require ${path} as a node_module`);
return [ await resolve(path, { basedir: cwd }) ];
} catch (error) {
debug(`${path} is glob or actual file, expanding...`);
return await glob(path, { cwd });
}
}

export async function expandGlobList(globs) {
if (typeof globs === 'string') {
globs = [ globs ];
}
if (Array.isArray(globs) === false) {
throw new Error(`expandGlobList expects Array or String, given ${typeof globs}`);
}
return (await Promise.all(
globs.map((path) => resolveModuleOrGlob(path))
)).reduce((total, current) => total.concat(current), []);
}

export function addHandlebarsHelpers(files) {
files.forEach((file) => {
debug(`Requiring ${file}`);
const handlebarsHelper = require(file); // eslint-disable-line global-require
if (handlebarsHelper && typeof handlebarsHelper.register === 'function') {
debug(`${file} has a register function, registering with handlebars`);
handlebarsHelper.register(Handlebars);
} else {
console.log(`WARNING: ${file} does not export a 'register' function, cannot import`);
}
});
}

export async function addHandlebarsPartials(files) {
await Promise.all(files.map(async function registerPartial(file) {
debug(`Registering partial ${file}`);
Handlebars.registerPartial(basename(file, extname(file)), await readFile(file, 'utf8'));
}));
}

export async function addObjectsToData(objects) {
if (typeof objects === 'string') {
objects = [ objects ];
}
if (Array.isArray(objects) === false) {
throw new Error(`addObjectsToData expects Array or String, given ${typeof objects}`);
}
const dataSets = [];
const files = await expandGlobList(objects.filter((object) => {
try {
debug(`Attempting to parse ${object} as JSON`);
dataSets.push(JSON.parse(object));
return false;
} catch (error) {
return true;
}
}));
const fileContents = await Promise.all(
files.map(async function registerPartial(file) {
debug(`Loading JSON file ${file}`);
return JSON.parse(await readFile(file, 'utf8'));
})
);
return merge({}, ...dataSets.concat(fileContents));
}

export async function renderHandlebarsTemplate(files, outputDirectory = process.cwd(), data = {}) {
await Promise.all(files.map(async function renderTemplate(file) {
debug(`Rendering template ${file} with data`, data);
const path = resolvePath(outputDirectory, `${basename(file, extname(file))}.html`);
await writeFile(path, Handlebars.compile(await readFile(file, 'utf8'))(data), 'utf8');
debug(`Wrote ${path}`);
console.log(`Wrote ${path} from ${file}`);
}));
}

if (require.main === module) {
const options = minimist(process.argv.slice(2), {
string: [
'output',
'partial',
'helper',
'data',
],
boolean: [
'version',
'help',
],
alias: {
'v': 'version',
'h': 'help',
'o': 'output',
'D': 'data',
'P': 'partial',
'H': 'helper',
},
});
debug('Parsed argv', options);
if (options.version) {
console.log(packageJson.version);
} else if (options.help || !options._ || !options._.length) {
console.log(`
Usage:
hbs --version
hbs --help
hbs [-P <partial>]... [-H <helper>]... [-D <data>]... [-o <directory>] [--] (<template...>)
-h, --help output usage information
-o, --output <directory> Directory to output rendered templates, defaults to cwd
-P, --partial <glob>... Register a partial (use as many of these as you want)
-H, --helper <glob>... Register a helper (use as many of these as you want)
-D, --data <glob|json>... Parse some data
-v, --version output the version number
Examples:
hbs --helper handlebars-layouts --partial ./templates/layout.hbs -- ./index.hbs
hbs --obj ./package.json ./homepage.hbs --output ./site/
`);
} else {
const setup = [];
let data = {};
if (options.helper) {
debug('Setting up helpers', options.helper);
setup.push(expandGlobList(options.helper).then(addHandlebarsHelpers));
}
if (options.partial) {
debug('Setting up partials', options.partial);
setup.push(expandGlobList(options.partial).then(addHandlebarsPartials));
}
if (options.data) {
debug('Setting up data', options.data);
setup.push(addObjectsToData(options.data).then((result) => data = result));
}
Promise.all(setup)
.then(() => expandGlobList(options._))
.then((files) => renderHandlebarsTemplate(files, options.output, data))
.catch((error) => {
console.error(error.stack || error);
process.exit(1);
});
}
}
4 changes: 4 additions & 0 deletions test/data.json
@@ -0,0 +1,4 @@
{
"title": "Some Title",
"body": "This is the body"
}
7 changes: 7 additions & 0 deletions test/test-1.hbs
@@ -0,0 +1,7 @@
<div class="entry">
<h1>{{title}}</h1>
<div class="body">
{{body}}
</div>
{{br 2}}
</div>
7 changes: 7 additions & 0 deletions test/verify-1.html
@@ -0,0 +1,7 @@
<div class="entry">
<h1>Some Title</h1>
<div class="body">
This is the body
</div>
<br><br>
</div>

0 comments on commit 0d4c74b

Please sign in to comment.