Skip to content

Commit

Permalink
Merge pull request #131 from namics/feature/#130-twig-tpl-engine
Browse files Browse the repository at this point in the history
Feature/#130 twig tpl engine
  • Loading branch information
janwidmer authored May 3, 2018
2 parents d994840 + 773b9d8 commit bbf4895
Show file tree
Hide file tree
Showing 39 changed files with 1,156 additions and 198 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ Thumbs.db
_.*
.project
.idea
/*.iml
.vscode
.cache
.settings
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ On creating a new project, you will be guided through some configuration options

* Desired Name `--name=` (default: current directory name)
* Desired CSS preprocessor `--pre=` (`less` or `scss`; default: `scss`)
* Desired view file extension `--viewExt=` (`html`, `hbs` or `mustache`; default: `hbs`)
* Desired template engine `--templateEngine=` (`hbs` or `twig`; default: `hbs`)
* Using client side templates `--clientTpl` (default: false)
* Including example code `--exampleCode` (default: false)
* Installing [`nitro-exporter`](https://www.npmjs.com/package/nitro-exporter) `--exporter` (default: false)
Expand All @@ -77,9 +77,9 @@ The choosen options will be stored for the next project generation.
It's possible to pass in these options through the command line:

```
npx -p yo -p generator-nitro@latest -- yo nitro --name=myproject --pre=less --viewExt=hbs --clientTpl
npx -p yo -p generator-nitro@latest -- yo nitro --name=myproject --pre=less --templateEngine=hbs --clientTpl
# or
yo nitro --name=myproject --pre=less --viewExt=hbs --clientTpl
yo nitro --name=myproject --pre=less --templateEngine=hbs --clientTpl
```

You may bypass the questions with `--skip-questions`. This will use the defaults for not specified options
Expand Down
95 changes: 69 additions & 26 deletions generators/app/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ module.exports = class extends Generator {
pre: this.options.pre,
js: this.options.js,
viewExt: this.options.viewExt,
templateEngine: this.options.templateEngine,
clientTpl: this.options.clientTpl,
exampleCode: this.options.exampleCode,
exporter: this.options.exporter,
Expand Down Expand Up @@ -52,11 +53,18 @@ module.exports = class extends Generator {
defaults: this._passedInOptions.js || this._jsOptions[0],
});

this._viewExtOptions = ['html', 'hbs', 'mustache'];
this._templateEngineOptions = ['hbs', 'twig'];
this.option('templateEngine', {
desc: `your desired template engine [${this._templateEngineOptions.join('|')}]`,
type: String,
defaults: this._passedInOptions.templateEngine || this._templateEngineOptions[0],
});

this._viewExtOptions = ['hbs', 'twig'];
this.option('viewExt', {
desc: `your desired view file extension [${this._viewExtOptions.join('|')}]`,
type: String,
defaults: this._passedInOptions.viewExt || this._viewExtOptions[1],
defaults: this._passedInOptions.viewExt || this._viewExtOptions[0],
});

this.option('clientTpl', {
Expand Down Expand Up @@ -129,6 +137,7 @@ module.exports = class extends Generator {
this.options.pre = config.preprocessor || this.options.pre;
this.options.js = config.jscompiler || this.options.js;
this.options.viewExt = config.viewExtension || this.options.viewExt;
this.options.templateEngine = config.templateEngine || this.options.templateEngine;
this.options.clientTpl = typeof config.clientTemplates === 'boolean' ? config.clientTemplates : this.options.clientTpl;
this.options.exampleCode = typeof config.exampleCode === 'boolean' ? config.exampleCode : this.options.exampleCode;
this.options.exporter = typeof config.exporter === 'boolean' ? config.exporter : this.options.exporter;
Expand Down Expand Up @@ -163,14 +172,24 @@ module.exports = class extends Generator {
when: () => !this._skipQuestions && !this._passedInOptions.js,
},*/
{
name: 'templateEngine',
type: 'list',
message: 'What\'s your desired template engine?',
choices: this._templateEngineOptions,
default: this.options.templateEngine,
store: true,
when: () => !this._skipQuestions && !this._passedInOptions.templateEngine,
},
// viewExt is automatically derived from templateEngine
/* {
name: 'viewExt',
type: 'list',
message: 'What\'s your desired view file extension?',
choices: this._viewExtOptions,
default: this.options.viewExt,
store: true,
when: () => !this._skipQuestions && !this._passedInOptions.viewExt,
},
when: () => !this._skipQuestions || !this._passedInOptions.viewExt,
},*/
{
name: 'clientTpl',
type: 'confirm',
Expand Down Expand Up @@ -207,15 +226,15 @@ module.exports = class extends Generator {
this.options.name = answers.name || this.options.name;
this.options.pre = answers.pre || this.options.pre;
this.options.js = answers.js || this.options.js;
this.options.viewExt = answers.viewExt || this.options.viewExt;
this.options.templateEngine = answers.templateEngine || this.options.templateEngine;
this.options.viewExt = this.options.templateEngine;
this.options.clientTpl = answers.clientTpl !== undefined ? answers.clientTpl : this.options.clientTpl;
this.options.exampleCode = answers.exampleCode !== undefined ? answers.exampleCode : this.options.exampleCode;
this.options.exporter = answers.exporter !== undefined ? answers.exporter : this.options.exporter;

this.config.set('name', this.options.name);
this.config.set('preprocessor', this.options.pre);
this.config.set('jscompiler', this.options.js);
this.config.set('viewExtension', this.options.viewExt);
this.config.set('templateEngine', this.options.templateEngine);
this.config.set('clientTemplates', this.options.clientTpl);
this.config.set('exampleCode', this.options.exampleCode);
this.config.set('exporter', this.options.exporter);
Expand Down Expand Up @@ -285,23 +304,31 @@ module.exports = class extends Generator {
const tplFiles = [
// files to process with copyTpl
'app/core/config.js',
'app/tests/jasmine/templating/engineSpec.js',
'app/tests/jasmine/templating/patternSpec.js',
'config/default.js',
'config/default/assets.js',
'gulp/compile-css.js',
'gulp/compile-css-proto.js',
'gulp/compile-js.js',
'gulp/compile-templates.js',
'gulp/utils.js',
'gulp/watch-assets.js',
'project/.githooks/pre-commit',
'project/docs/nitro.md',
'src/patterns/molecules/example/example.html',
'project/docs/client-templates.md',
'src/patterns/molecules/example/example.hbs',
'src/patterns/molecules/example/example.twig',
'src/patterns/molecules/example/schema.json',
'src/proto/js/prototype.js',
'src/views/index.html',
'src/views/_partials/head.html',
'src/views/_partials/foot.html',
'src/views/index.hbs',
'src/views/index.twig',
'src/views/_partials/head.hbs',
'src/views/_partials/head.twig',
'src/views/_partials/foot.hbs',
'src/views/_partials/foot.twig',
'tests/backstop/backstop.config.js',
'server.js',
'gulpfile.js',
'package.json',
];
Expand All @@ -312,7 +339,7 @@ module.exports = class extends Generator {
'frontend-defaults.zip',
];
const ignoresOnUpdate = [
// files to ignore ono updating projects
// files to ignore on updating projects
'config/local.js',
];
const typeScriptFiles = [
Expand All @@ -333,16 +360,17 @@ module.exports = class extends Generator {
'gulp/compile-templates.js',
];
const viewFiles = [
// files that might change file extension
'src/views/404.html',
'src/views/index.html',
'src/views/_layouts/default.html',
'src/views/_partials/foot.html',
'src/views/_partials/head.html',
'src/patterns/atoms/icon/icon.html',
'src/patterns/molecules/example/example.html',
'project/blueprints/pattern/pattern.html',
// all view files exists in different templateEngine variants
'src/views/404',
'src/views/index',
'src/views/_layouts/default',
'src/views/_partials/foot',
'src/views/_partials/head',
'src/patterns/atoms/icon/icon',
'src/patterns/molecules/example/example',
'project/blueprints/pattern/pattern',
];
const enginePath = 'app/templating/';
const examplePaths = [
// paths only for this.options.exampleCode===true
'src/patterns/atoms/icon/',
Expand Down Expand Up @@ -399,6 +427,14 @@ module.exports = class extends Generator {
}
}

// check if the file is within the app/templating/ path
if (file.indexOf(enginePath) !== -1) {
if (file.indexOf(`${enginePath}${this.options.templateEngine}`) === -1) {
// only matching engine files
return;
}
}

// Example only Files
if (!this.options.exampleCode) {
if (
Expand All @@ -424,6 +460,7 @@ module.exports = class extends Generator {
}

const ext = path.extname(file).substring(1);
const fileWithoutExt = file.substring(0, (file.length - ext.length - 1));

// exclude unnecessary preprocessor files
if (_.indexOf(this._preOptions, ext) !== -1 && this.options.pre !== ext) {
Expand All @@ -437,11 +474,17 @@ module.exports = class extends Generator {
const sourcePath = this.templatePath(file);
let destinationPath = this.destinationPath(file);

// adjust destination template file extension for view files
if (_.indexOf(this._viewExtOptions, ext) !== -1 && _.indexOf(viewFiles, file) !== -1) {
const targetExt = `.${this.options.viewExt !== 0 ? this.options.viewExt : this._viewExtOptions[0]}`;

destinationPath = destinationPath.replace(path.extname(destinationPath), targetExt);
// check if it's a view file
if (_.indexOf(viewFiles, fileWithoutExt) !== -1) {
if (ext !== this.options.templateEngine) {
// return view files with ext not matching the current templateEngine
return;
}
if (this.options.viewExt !== this.options.templateEngine) {
// sanity check for update case of old generated app's having viewExt other than hbs
const targetExt = `.${this.options.viewExt !== 0 ? this.options.viewExt : this._viewExtOptions[0]}`;
destinationPath = destinationPath.replace(path.extname(destinationPath), targetExt);
}
}

if (_.indexOf(tplFiles, file) !== -1) {
Expand Down
3 changes: 2 additions & 1 deletion generators/app/templates/app/templating/hbs/helpers/t.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
*
* {{t 'test.example.string'}}
* {{t 'test.example.sprintf' 'alphabet' 'a' 'l' 'p'}}
* {{t 'test.example.interpolation' word='alphabet' one='a'}}
* {{t 'test.example.interpolation' name='developer'}}
* {{t 'test.example.interpolation' data}}
*
* It should be also possible to use other translation features from i18next (http://i18next.com/translate/)
*/
Expand Down
52 changes: 52 additions & 0 deletions generators/app/templates/app/templating/twig/engine.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* This is a wrapper for the Twig.js engine.
*/

'use strict';

const fs = require('fs');
const path = require('path');
const Twig = require('twig');
const config = require('config');

const files = {};
const coreHelpersDir = `${config.get('nitro.basePath')}app/templating/twig/helpers/`;
const projectHelpersDir = `${config.get('nitro.basePath')}project/helpers/`;
const coreFiles = fs.readdirSync(coreHelpersDir);
const projectFiles = fs.readdirSync(projectHelpersDir);

coreFiles.map((file) => {
if (path.extname(file) === '.js') {
files[path.basename(file, '.js')] = coreHelpersDir + file;
}
});

projectFiles.map((file) => {
if (path.extname(file) === '.js') {
files[path.basename(file, '.js')] = projectHelpersDir + file;
}
});

Object.keys(files).forEach((key) => {
const helperTagFactory = require(files[key]);

// expose helper as custom tag
Twig.extend(function(Twig) {
Twig.exports.extendTag(helperTagFactory(Twig));
});
});

Twig.renderWithLayout = function(path, options, fn) {
const layoutPath = options.settings.views + '/' + options.layout + '.' + options.settings['view engine'];

function layoutRendered(error, layout) {
function bodyRendered(error, body) {
layout = layout.replace('<!-- Replace With Body -->', body);
return fn(null, layout);
}
return Twig.__express(path, options, bodyRendered);
}
return Twig.__express(layoutPath, options, layoutRendered);
};

module.exports = Twig;
78 changes: 78 additions & 0 deletions generators/app/templates/app/templating/twig/helpers/partial.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
'use strict';

/**
* twig helper: {% partial Partial Name %}
*
* Usage
* {% partial 'head' %}
*
*/

const fs = require('fs');
const path = require('path');
const config = require('config');
const twigUtils = require('../utils');

module.exports = function (Twig) {
return {
type: 'partial',
regex: /^partial\s+('\S*')$/,
next: [],
open: true,
compile: function(token) {

token.name = Twig.expression.compile.apply(this, [{
type: Twig.expression.type.expression,
value: token.match[1].trim()
}]).stack;

delete token.match;
return token;
},
parse: function(token, context, chain) {
try {
const partial = Twig.expression.parse.apply(this, [token.name, context]);
let innerContext = Twig.ChildContext(context);
let template;
let templateFile = `${partial}.${config.get('nitro.viewFileExtension')}`;

const templateFilePath = path.join(
config.get('nitro.basePath'),
config.get('nitro.viewPartialsDirectory'),
templateFile
);

// TODO CHECK WHAT THIS IF SHOULD DO
if (partial instanceof Twig.Template) {
template = name;
} else if (fs.existsSync(templateFilePath)) {
// Import file
template = Twig.Templates.loadRemote(templateFilePath, {
method: 'fs',
base: '',
async: false,
options: this.options,
id: templateFilePath
});
} else {
return {
chain: chain,
output: twigUtils.logAndRenderError(
new Error(`Partial ${templateFilePath} not found.`)
)
};
}

return {
chain: chain,
output: template.render(innerContext)
};
} catch (e) {
return {
chain: chain,
output: twigUtils.logAndRenderError(e)
};
}
}
};
};
Loading

0 comments on commit bbf4895

Please sign in to comment.