Skip to content

Commit

Permalink
feature: Hygen extensibility loading, and first integration - helpers…
Browse files Browse the repository at this point in the history
…. (also align flow types)
  • Loading branch information
jondot committed Mar 18, 2018
1 parent 9e86e83 commit 8ce79b5
Show file tree
Hide file tree
Showing 33 changed files with 505 additions and 105 deletions.
57 changes: 57 additions & 0 deletions content/docs/extensibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
---
title: Extensibility
doc: 4
section: 1
category: "tech"
type: "doc"
---

You can extend these properties of Hygen, using a special `.hygen.js` file:

* Helper functions in templates.
* Customizing logger, template location and shell executor.
* (_WIP_) Custom generator operations in addition to the built-in `add`, `inject`, `shell`

# .hygen.js

Hygen supports a bubbling-up mechanism for searching a `.hygen.js` file and loading it. Where ever you run `hygen` from, it will start searching for this file upwards, and stop at the first one it finds.

This means:

* You can set up a _single file_ per project.
* For some special sub-projects, set a different file for a different behavior.
* You can also set up one global file to use if you drop it in your user folder.

_Note:_ The current strategy when there are two or more `.hygen.js` files in the path upwards is to _take the first one_ and ignore the rest.

# Helpers

Here's a template that uses a function that doesn't exist in the helper accessor `h`. This function is plainly called `extended`, for lack of a better name.

```yaml{5}
---
to: given/hygen-js/new.md
---
this demonstrates hygen loaded up .hygen.js and extended helpers.
<%= h.extended('hello') %>
```

In order to add the function `extended` to the standard `hygen` helpers collection, we'll create a `.hygen.js` file at the root of our project:

```
src/
package.json
.hygen.js
```

This `.hygen.js` file will be the home of all of our extension points. For this example, we'll use the following content:

```javascript{3}
module.exports = {
helpers: {
extended: s => s.toUpperCase()
}
}
```

Note that beyond inlining a utility function like in this example, you can require and use any piece of code in your project here and export it for Hygen to use.
37 changes: 37 additions & 0 deletions lib/config-resolver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"use strict";

var _config = require("./config");

function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }

function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } function _next(value) { step("next", value); } function _throw(err) { step("throw", err); } _next(); }); }; }

const L = require('lodash');

const fs = require('fs-extra');

const path = require('path');

const resolver = new _config.ConfigResolver('.hygen.js', {
exists: fs.exists,
// $FlowFixMe
load: f => Promise.resolve(require(f))
});

module.exports =
/*#__PURE__*/
function () {
var _ref = _asyncToGenerator(function* (config) {
const cwd = config.cwd,
templates = config.templates;
const fileConfig = yield resolver.resolve(cwd);
const resolvedTemplates = L.find([process.env.HYGEN_TMPLS, path.join(cwd, '_templates')], _ => fs.existsSync(_)) || templates;
return _extends({}, config, {
templates: resolvedTemplates
}, fileConfig);
});

return function (_x) {
return _ref.apply(this, arguments);
};
}();
84 changes: 84 additions & 0 deletions lib/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"use strict";

Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ConfigResolver = exports.configLookup = void 0;

var _path = _interopRequireDefault(require("path"));

var _lodash = _interopRequireDefault(require("lodash"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } function _next(value) { step("next", value); } function _throw(err) { step("throw", err); } _next(); }); }; }

// inline fp methods due to perf
const reduce = (f, init) => arr => _lodash.default.reduce(arr, f, init);

const reversePathsToWalk = _lodash.default.flow(f => _path.default.resolve(f), f => f.split(_path.default.sep), reduce((acc, p) => p === '' ? [_path.default.sep] : [...acc, _path.default.join(_lodash.default.last(acc), p)], []), _lodash.default.reverse);

const configLookup = (file, folder) => _lodash.default.map(reversePathsToWalk(folder), p => _path.default.join(p, file));

exports.configLookup = configLookup;

class ConfigResolver {
constructor(configFile, io) {
Object.defineProperty(this, "configFile", {
configurable: true,
enumerable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "io", {
configurable: true,
enumerable: true,
writable: true,
value: void 0
});
this.configFile = configFile;
this.io = io;
}

resolve(from) {
var _this = this;

return _asyncToGenerator(function* () {
const configCandidates = configLookup(_this.configFile, from);
const _this$io = _this.io,
exists = _this$io.exists,
load = _this$io.load;
var _iteratorNormalCompletion = true;
var _didIteratorError = false;
var _iteratorError = undefined;

try {
for (var _iterator = configCandidates[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
const candidate = _step.value;

if (yield exists(candidate)) {
return yield load(candidate);
}
}
} catch (err) {
_didIteratorError = true;
_iteratorError = err;
} finally {
try {
if (!_iteratorNormalCompletion && _iterator.return != null) {
_iterator.return();
}
} finally {
if (_didIteratorError) {
throw _iteratorError;
}
}
}

return {};
})();
}

}

exports.ConfigResolver = ConfigResolver;
7 changes: 5 additions & 2 deletions lib/context.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }

const L = require('lodash');

const inflection = require('inflection'); // supports kebab-case to KebabCase
Expand All @@ -19,10 +21,11 @@ const localsDefaults = {

const capitalizedLocals = locals => L.mapValues(L.mapKeys(L.pick(locals, localsToCapitalize), (v, k) => helpers.capitalize(k)), v => helpers.capitalize(v));

const context = locals => {
const context = (locals, config) => {
const localsWithDefaults = Object.assign({}, localsDefaults, locals);
const configHelpers = config && config.helpers || {};
return Object.assign(localsWithDefaults, capitalizedLocals(localsWithDefaults), {
h: helpers
h: _extends({}, helpers, configHelpers)
});
};

Expand Down
2 changes: 1 addition & 1 deletion lib/engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ function () {

const render = require('./render');

yield execute((yield render(args)), args, config);
yield execute((yield render(args, config)), args, config);
});

return function engine(_x, _x2) {
Expand Down
2 changes: 0 additions & 2 deletions lib/execute.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } function _next(value) { step("next", value); } function _throw(err) { step("throw", err); } _next(); }); }; }

const L = require('lodash');

const resolve = require('./ops');

const execute =
Expand Down
7 changes: 3 additions & 4 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ function _asyncToGenerator(fn) { return function () { var self = this, args = ar

const engine = require('./engine');

const resolve = require('./templates-resolver');
const resolve = require('./config-resolver');

const _require = require('./help'),
printHelp = _require.printHelp;
Expand All @@ -11,7 +11,7 @@ const runner =
/*#__PURE__*/
function () {
var _ref = _asyncToGenerator(function* (argv, config) {
const resolvedConfig = resolve(config);
const resolvedConfig = yield resolve(config);
const templates = resolvedConfig.templates,
logger = resolvedConfig.logger;

Expand All @@ -26,8 +26,7 @@ function () {
logger.log('-------------------');
}

printHelp(templates, logger);
process.exit(1);
printHelp(templates, logger); // process.exit(1)
}
});

Expand Down
4 changes: 3 additions & 1 deletion lib/prompt.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ const L = require('lodash');
const hooksfiles = ['prompt.js', 'index.js'];

const prompt = (actionfolder, args) => {
const hooksfile = L.first(L.filter(L.map(hooksfiles, f => path.join(actionfolder, f)), f => fs.existsSync(f)));
console.log(actionfolder);
const hooksfile = L.first(L.filter(L.map(hooksfiles, f => path.resolve(path.join(actionfolder, f))), f => fs.existsSync(f)));

if (!hooksfile) {
return Promise.resolve({});
} // shortcircuit without inquirer
// $FlowFixMe


const hooksModule = require(hooksfile);
Expand Down
10 changes: 5 additions & 5 deletions lib/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,12 @@ const filter = f => arr => L.filter(arr, f);

const ignores = ['prompt.js', 'index.js'];

const renderTemplate = (tmpl, locals) => L.isString(tmpl) ? ejs.render(tmpl, context(locals)) : tmpl;
const renderTemplate = (tmpl, locals, config) => L.isString(tmpl) ? ejs.render(tmpl, context(locals, config)) : tmpl;

const render =
/*#__PURE__*/
function () {
var _ref = _asyncToGenerator(function* (args) {
var _ref = _asyncToGenerator(function* (args, config) {
return yield fs.readdir(args.actionfolder).then(map(_ => path.join(args.actionfolder, _))).then(filter(f => !L.find(ignores, ig => L.endsWith(f, ig)))).then(map(file => fs.lstat(file).then(stat => ({
file,
stat
Expand All @@ -48,12 +48,12 @@ function () {
body
}) => ({
file,
attributes: L.mapValues(attributes, _ => renderTemplate(_, args)),
body: renderTemplate(body, args)
attributes: L.mapValues(attributes, _ => renderTemplate(_, args, config)),
body: renderTemplate(body, args, config)
})));
});

return function render(_x) {
return function render(_x, _x2) {
return _ref.apply(this, arguments);
};
}();
Expand Down
38 changes: 30 additions & 8 deletions lib/templates-resolver.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,39 @@
"use strict";

var _config = require("./config");

function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }

function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } function _next(value) { step("next", value); } function _throw(err) { step("throw", err); } _next(); }); }; }

const L = require('lodash');

const fs = require('fs-extra');

const path = require('path');

module.exports = config => {
const cwd = config.cwd,
templates = config.templates;
const resolvedTemplates = L.find([// $FlowFixMe
process.env.HYGEN_TMPLS, path.join(cwd, '_templates')], _ => fs.existsSync(_)) || templates;
return _extends({}, config, {
templates: resolvedTemplates
const resolver = new _config.ConfigResolver('.hygen.js', {
exists: fs.exists,
load: f => Promise.resolve(require(f))
});

module.exports =
/*#__PURE__*/
function () {
var _ref = _asyncToGenerator(function* (config) {
const cwd = config.cwd,
templates = config.templates;
const start = new Date();
const fileConfig = yield resolver.resolve(cwd);
console.log('timing:', new Date() - start);
const resolvedTemplates = L.find([// $FlowFixMe
process.env.HYGEN_TMPLS, path.join(cwd, '_templates')], _ => fs.existsSync(_)) || templates;
return _extends({}, config, {
templates: resolvedTemplates
}, fileConfig);
});
};

return function (_x) {
return _ref.apply(this, arguments);
};
}();
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"watch": "node node_modules/.bin/jest --watch",
"flow:sync": "flow-typed install",
"hygen": "babel-node src/bin.js",
"hygen:timed": "babel-node src/bin.timed.js",
"docs:prepare": "cd hygen.io && yarn",
"docs:watch": "cd hygen.io && yarn gatsby develop",
"docs:build": "cd hygen.io && yarn build",
Expand All @@ -46,7 +47,7 @@
"babel-core": "^7.0.0-0",
"babel-jest": "^22.1.0",
"dir-compare": "^1.4.0",
"flow-bin": "^0.62.0",
"flow-bin": "0.68.0",
"flow-typed": "^2.2.3",
"gh-pages": "^1.1.0",
"gitbook-cli": "^2.3.2",
Expand Down
Loading

0 comments on commit 8ce79b5

Please sign in to comment.