diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..19fcf7b --- /dev/null +++ b/.eslintrc @@ -0,0 +1,243 @@ +{ + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module" + }, + "env": { + "node": true, + "es6" : true + }, + "rules": { + "no-cond-assign": 2, + "no-console": 0, + "no-constant-condition": 2, + "no-control-regex": 2, + "no-debugger": 2, + "no-dupe-args": 2, + "no-dupe-keys": 2, + "no-duplicate-case": 2, + "no-empty-character-class": 2, + "no-empty": 2, + "no-ex-assign": 2, + "no-extra-parens": 0, + "no-extra-semi": 2, + "no-func-assign": 2, + "no-inner-declarations": 1, + "no-invalid-regexp": 2, + "no-irregular-whitespace": 2, + "no-obj-calls": 2, + "no-prototype-builtins": 2, + "no-regex-spaces": 2, + "no-sparse-arrays": 2, + "no-template-curly-in-string": 2, + "no-unexpected-multiline": 2, + "no-unreachable": 2, + "no-unsafe-finally": 2, + "no-unsafe-negation": 2, + "use-isnan": 2, + "valid-jsdoc": 0, + "valid-typeof": 2, + "accessor-pairs": 2, + "array-callback-return": 2, + "block-scoped-var" : 2, + "class-methods-use-this" : 0, + "complexity" : 0, + "consistent-return" : 2, + "curly" : 0, + "default-case" : 2, + "dot-location" : [2, "property"], + "dot-notation" : 2, + "eqeqeq" : [2, "smart"], + "guard-for-in" : 2, + "no-alert" : 2, + "no-caller" : 2, + "no-case-declarations" : 2, + "no-div-regex" : 2, + "no-else-return" : 2, + "no-empty-function" : 2, + "no-empty-pattern" : 2, + "no-eq-null" : 0, + "no-eval" : 2, + "no-extend-native" : 2, + "no-extra-bind" : 2, + "no-extra-label": 2, + "no-fallthrough" : 2, + "no-floating-decimal" : 2, + "no-global-assign" : [2, {"exceptions" : ["Promise"]}], + "no-implicit-coercion" : 2, + "no-implicit-globals" : 2, + "no-implied-eval" : 2, + "no-invalid-this" : 2, + "no-iterator" : 2, + "no-labels" : [2, {"allowLoop" : true}], + "no-lone-blocks" : 2, + "no-loop-func" : 2, + "no-magic-numbers" : 0, + "no-multi-spaces" : 2, + "no-multi-str" : 2, + "no-new-func" : 2, + "no-new-wrappers" : 2, + "no-new" : 2, + "no-octal-escape" : 2, + "no-octal" : 2, + "no-param-reassign" : 2, + "no-proto" : 2, + "no-redeclare" : 2, + "no-return-assign" : 2, + "no-script-url" : 2, + "no-self-assign" : 2, + "no-self-compare" : 2, + "no-sequences" : 2, + "no-throw-literal" : 2, + "no-unmodified-loop-condition" : 2, + "no-unused-expressions" : 2, + "no-unused-labels" : 2, + "no-useless-call" : 2, + "no-useless-concat" : 2, + "no-useless-escape" : 2, + "no-void" : 2, + "no-warning-comments" : 1, + "no-with" : 2, + "radix" : 2, + "vars-on-top" : 2, + "wrap-iife" : 2, + "yoda" : 2, + "strict" : 2, + "init-declarations" : 0, + + "no-catch-shadow" : 2, + "no-delete-var": 2, + "no-label-var" : 2, + "no-restricted-globals" : 0, + "no-shadow-restricted-names" : 0, + "no-shadow" : 2, + "no-undef-init" : 2, + "no-undef" : 2, + "no-undefined" : 2, + "no-unused-vars" : 2, + "no-use-before-define" : 2, + + "callback-return" : 1, + "global-require" : 2, + "handle-callback-err" : 2, + "no-mixed-requires" : 0, + "no-new-require" : 2, + "no-path-concat" : 2, + "no-process-env" : 0, + "no-process-exit" : 1, + "no-restricted-modules" : 0, + "no-restricted-properties" : 0, + "no-sync" : 0, + + "array-bracket-spacing" : 2, + "block-spacing" : 2, + "brace-style" : 2, + "camelcase" : 2, + "comma-dangle" : 2, + "comma-spacing" : 2, + "comma-style" : 2, + "computed-property-spacing" : 2, + "consistent-this" : [2, "self"], + "eol-last" : 2, + "func-call-spacing" : 2, + "func-names" : 0, + "func-style" : 0, + "id-blacklist" : [2, "flavorTown"], + "id-length" : 0, + "id-match" : 0, + "indent" : [2, 2], + "jsx-quotes" : 2, + "key-spacing": 2, + "keyword-spacing" : 2, + "line-comment-position" : 0, + "linebreak-style" : 2, + "lines-around-comment" : 0, + "lines-around-directive" : 2, + "max-depth" : [2, 5], + "max-len" : [2, {"code" : 120, "tabWidth" : 2}], + "max-lines" : [2, {"max" : 600, "skipBlankLines" : true, "skipComments" : true}], + "max-nested-callbacks" : [2, 5], + "max-params" : 0, + "max-statements-per-line" : 0, + "max-statements" : 0, + "multiline-ternary" : 0, + "new-cap" : 0, + "new-parens" : 2, + "newline-after-var" : 0, + "newline-before-return" : 0, + "newline-per-chained-call" : 0, + "no-array-constructor" : 2, + "no-bitwise" : 1, + "no-continue" : 1, + "no-inline-comments" : 0, + "no-lonely-if" : 2, + "no-mixed-operators" : 2, + "no-mixed-spaces-and-tabs" : 2, + "no-multiple-empty-lines" : 0, + "no-negated-condition" : 0, + "no-nested-ternary" : 2, + "no-new-object" : 2, + "no-plusplus": 0, + "no-restricted-syntax" : 0, + "no-tabs" : 0, + "no-ternary" : 0, + "no-trailing-spaces" : 0, + "no-underscore-dangle" : 0, + "no-unneeded-ternary" : 2, + "no-whitespace-before-property" : 2, + "object-curly-newline" : 0, + "object-curly-spacing" : 2, + "object-property-newline" : 0, + "one-var-declaration-per-line" : 2, + "one-var" : 0, + "operator-assignment" : 0, + "operator-linebreak" : 2, + "padded-block" : 0, + "quote-props" : 0, + "quotes" : [2, "single", { "allowTemplateLiterals": true }], + "require-jsdoc" : 2, + "semi-spacing" : 2, + "semi" : 2, + "sort-keys" : 0, + "sort-vars" : 0, + "space-before-blocks" : 0, + "space-before-function-paren" : 0, + "space-in-parens" : 0, + "space-infix-ops" : 2, + "space-unary-ops" : 0, + "spaced-comment" : 0, + "unicode-bom" : 0, + "wrap-regex" : 2, + "arrow-body-style" : 0, + "arrow-parens" : 0, + "arrow-spacing" : 0, + "constructor-super" : 2, + "generator-star-spacing" : [2, {"before" : false, "after" : true}], + "no-class-assign" : 2, + "no-confusing-arrow" : 0, + "no-const-assign" : 2, + "no-dupe-class-members" : 2, + "no-duplicate-imports" : 2, + "no-new-symbol" : 2, + "no-restricted-imports" : 0, + "no-this-before-super" : 2, + "no-useless-computed-key" : 2, + "no-useless-constructor" : 2, + "no-useless-rename" : 2, + "no-var" : 2, + "object-shorthand" : 0, + "prefer-arrow-callback" : 0, + "prefer-const" : 2, + "prefer-numeric-literals" : 0, + "prefer-reflect" : 0, + "prefer-rest-params" : 2, + "prefer-spread" : 2, + "prefer-template" : 2, + "require-yield" : 2, + "rest-spread-spacing" : 0, + "sort-imports" : 0, + "symbol-description" : 2, + "template-curly-spacing" : 0, + "yield-star-spacing" : 2 + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 55f99ce..133afe8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules/ -docs/ \ No newline at end of file +docs/ +.vscode/ \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..b16d7f9 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,3 @@ +language: node_js +node_js: + - "6" \ No newline at end of file diff --git a/package.json b/package.json index 9709b19..b28bc17 100644 --- a/package.json +++ b/package.json @@ -10,17 +10,18 @@ "author": "robotmayo", "license": "GPL-3.0", "dependencies": { - "bluebird": "^3.4.6", - "body-parser": "^1.15.2", - "express": "^4.14.0", - "express-hbs": "^1.0.3", - "express-session": "^1.14.1", - "logbro": "^1.2.0", - "passport": "^0.3.2", - "passport-local": "^1.0.0" + "bluebird": "3.4.6", + "body-parser": "1.15.2", + "express": "4.14.0", + "express-hbs": "1.0.3", + "express-session": "1.14.1", + "logbro": "1.2.0", + "passport": "0.3.2", + "passport-local": "1.0.0" }, "devDependencies": { - "ava": "^0.16.0", - "documentation": "^4.0.0-beta10" + "ava": "0.16.0", + "documentation": "4.0.0-beta10", + "eslint": "^3.5.0" } } diff --git a/src/plugins/hook.js b/src/plugins/hook.js index c29c213..97da86a 100644 --- a/src/plugins/hook.js +++ b/src/plugins/hook.js @@ -1,10 +1,9 @@ -'use strict'; - const Promise = require('bluebird'); const log = require('logbro'); const HOOK_TYPES = require('./constants').HOOK_TYPES; const HOOK_TYPES_ARR = [HOOK_TYPES.FILTER, HOOK_TYPES.STATIC, HOOK_TYPES.ACTION]; +const DEFAULT_PRIORITY = 5; module.exports = function hookInit(Plugins) { /** @@ -37,31 +36,13 @@ module.exports = function hookInit(Plugins) { if (!opts.hook || typeof opts.hook !== 'string') throw new Error('Hook is required'); if (!opts.fn || typeof opts.fn !== 'function') throw new Error('fn is required'); const data = Object.assign({}, opts); - data.priority = typeof data.priority === 'number' ? data.priority : 5; + data.priority = typeof data.priority === 'number' ? data.priority : DEFAULT_PRIORITY; data.id = pluginID; if (!Plugins.hookMap.has(data.hook)) Plugins.hookMap.set(data.hook, []); Plugins.hookMap.get(data.hook).push(data); } Plugins.addHook = addHook; - /** - * - * @memberof Plugins - * @param {string} hook - * @param {object} context - * @returns {Promise} - */ - function fireHook(hook, context) { - const hookType = hook.split('::')[0]; - if (HOOK_TYPES.indexOf(hookType) === -1) return Promise.reject(new Error('Invalid hooktype')); - const hooks = Plugins.hookMap.get(hook); - if (!hooks) return Promise.resolve(context); - if (hookType === HOOK_TYPES.FILTER) return fireFilterHook(hooks, context); - else if (hookType === HOOK_TYPES.STATIC) return fireStaticHook(hooks, context); - else if (hookType === HOOK_TYPES.ACTION) return fireActionHook(hooks, context); - } - Plugins.fireHook = fireHook; - /** * @@ -71,7 +52,7 @@ module.exports = function hookInit(Plugins) { * @returns {Promise} */ function fireFilterHook(hooks, context) { - return Promise.reduce(hooks, function (accum, data) { + return Promise.reduce(hooks, function filterHookReduce(accum, data) { return data.fn(accum); }, context); } @@ -85,15 +66,15 @@ module.exports = function hookInit(Plugins) { * @returns {Promise} */ function fireStaticHook(hooks, context) { - return Promise.each(hooks, function (data) { + return Promise.each(hooks, function staticHookEach(data) { return data.fn(context) .timeout(5000) .catch(Promise.TimeoutError, function (err) { - log.error('Plugin failed to finish in time'); + log.error(`Plugin failed to finish in time ${err.stack}`); }) .catch(function (err) { - log.error('Plugin failure'); - }) + log.error(`Plugin failure ${err.stack}`); + }); }); } Plugins.fireStaticHook = fireStaticHook; @@ -109,7 +90,27 @@ module.exports = function hookInit(Plugins) { return Promise.all(hooks.map(d => d.fn(context))); } Plugins.fireActionHook = fireActionHook; + + /** + * + * @memberof Plugins + * @param {string} hook + * @param {object} context + * @returns {Promise} + */ + function fireHook(hook, context) { + const hookType = hook.split('::')[0]; + if (HOOK_TYPES_ARR.indexOf(hookType) === -1) return Promise.reject(new Error('Invalid hooktype')); + const hooks = Plugins.hookMap.get(hook); + if (!hooks) return Promise.resolve(context); + if (hookType === HOOK_TYPES.FILTER) return fireFilterHook(hooks, context); + else if (hookType === HOOK_TYPES.STATIC) return fireStaticHook(hooks, context); + else if (hookType === HOOK_TYPES.ACTION) return fireActionHook(hooks, context); + return Promise.reject(new Error('Invalid hooktype')); + } + Plugins.fireHook = fireHook; + return Plugins; -} +}; diff --git a/src/plugins/index.js b/src/plugins/index.js index f7efd1d..f881587 100644 --- a/src/plugins/index.js +++ b/src/plugins/index.js @@ -1,6 +1,5 @@ -'use strict'; /** * @namespace Plugins */ const Plugins = {}; -module.exports = require('./hook')(Plugins); \ No newline at end of file +module.exports = require('./hook')(Plugins); diff --git a/src/routes/admin.js b/src/routes/admin.js deleted file mode 100644 index 1687da7..0000000 --- a/src/routes/admin.js +++ /dev/null @@ -1,32 +0,0 @@ -'use strict'; -/** - * Express request object - * @external req - * @see {@link http://expressjs.com/en/api.html#req} - */ - -/** - * Express request object - * @external res - * @see {@link http://expressjs.com/en/api.html#res} - */ - - -const Admin = { - -}; -module.exports = Admin; - -/** - * - * - * @param {req} req - * @param {res} res - */ -function AdminIndex(req, res){ - -} - -Admin.init = function (App, router){ - -} \ No newline at end of file diff --git a/src/routes/index.js b/src/routes/index.js deleted file mode 100644 index 10b4ae3..0000000 --- a/src/routes/index.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = function(App, router){ - return router; -} \ No newline at end of file diff --git a/src/services/template.js b/src/services/template.js deleted file mode 100644 index fe6f1e7..0000000 --- a/src/services/template.js +++ /dev/null @@ -1,88 +0,0 @@ -'use strict'; -const exhbs = require('express-hbs'); -const log = require('logbro'); - -const fireHook = require('../plugins/hook'); - -const _render = exhbs.express4(); -const TemplateService = { - cache : new Map(), - templates : new Map() -}; - -module.exports = TemplateService; - -/** - * - * - * @typedef {object} TemplateRootObject - * @property {string} path Path to the template - * @property {number} sourceId Souce themes id - */ - - -/** - * Register a template - * @param {number} sourceThemeId Id of the source theme - * @param {string} name Name/key for the template eg: index -> index.hbs - * @param {string} path - * @throws {Error} - */ -function registerTemplate(sourceThemeId, name, path){ - if(sourceThemeId == null) throw new Error('SourceID cannont be null'); - if(name == null || name == '') throw new Error('name cannot be empty'); - if(path == null || path == '') throw new Error('path cannot be empty'); - let templatesRoot = TemplateService.templates.get(name); - if(!templatesRoot){ - templatesRoot = { - active : {}, - _default : {} - }; - TemplateService.templates.set(name, templatesRoot); - } - if(sourceThemeId === 0){ - // 0 is the default theme - templatesRoot._default = {path, sourceThemeId}; - } - templatesRoot.active = {path, sourceThemeId}; -} -TemplateService.registerTemplate = registerTemplate; - -/** - * - * - * @param {res} res - * @param {string} template - * @param {object} options - * @param {function(, html: string)} cb - */ -function render(res, template, options, cb){ - const opts = options || {}; - opts._locals = res.locals || {}; - opts.settings = {}; - log.info('Calling render') - const templateRootObject = getTemplate(template); - log.trace(templateRootObject); - _render(templateRootObject.path, opts, function(err, html){ - cb(err, html); - }); -} -TemplateService.render = render; - -function getTemplate(name){ - const t = TemplateService.templates.get(name); - if(!t) throw new Error('Unable to find template of ' + name); - return t.active ? t.active : t._default; -} - -function renderMiddleware(req, res, next){ - res.trostRender = function(template, opts, cb){ - render(res, template, opts, function(err, html){ - if(cb) return cb(err, html); - if(err) return req.next(err); - res.send(html); - }); - } - next(); -} -TemplateService.renderMiddleware = renderMiddleware; \ No newline at end of file diff --git a/src/services/template/index.js b/src/services/template/index.js new file mode 100644 index 0000000..020196c --- /dev/null +++ b/src/services/template/index.js @@ -0,0 +1,123 @@ +const exhbs = require('express-hbs'); +const log = require('logbro'); + +const themePath = require('path').join(__dirname, '../../theme'); + +/** + * + * + * @returns {object} + */ +function _defaults() { + return { + cache: new Map(), + templates: new Map(), + themePath, + hbsRender: exhbs.express4({ + layoutsDir: themePath, + partialsDir: themePath + }) + }; +} + +module.exports._defaults = _defaults; + +module.exports.create = function (creationOpts) { + const TemplateService = Object.assign(_defaults(), creationOpts); + + /** + * + * + * @typedef {object} TemplateRootObject + * @property {string} path Path to the template + * @property {number} sourceId Souce themes id + */ + + + /** + * Register a template + * @param {number} sourceThemeId Id of the source theme + * @param {string} name Name/key for the template eg: index -> index.hbs + * @param {string} path + * @throws {Error} + */ + function registerTemplate(sourceThemeId, name, path) { + if (sourceThemeId == null) throw new Error('SourceID cannont be null'); + if (name == null || name === '') throw new Error('name cannot be empty'); + if (path == null || path === '') throw new Error('path cannot be empty'); + let templatesRoot = TemplateService.templates.get(name); + if (!templatesRoot) { + templatesRoot = { + active: {}, + _default: {} + }; + TemplateService.templates.set(name, templatesRoot); + } + if (sourceThemeId === 0) { + // 0 is the default theme + templatesRoot._default = { + path, + sourceThemeId + }; + } + templatesRoot.active = { + path, + sourceThemeId + }; + } + TemplateService.registerTemplate = registerTemplate; + + + /** + * + * + * @param {string} name + * @returns {templateRootObject} + */ + function getTemplate(name) { + const t = TemplateService.templates.get(name); + if (!t) throw new Error(`Unable to find template of + ${name}`); + return t.active ? t.active : t._default; + } + + /** + * + * + * @param {res} res + * @param {string} template + * @param {object} options + * @param {function(, html: string)} cb + */ + function render(res, template, options, cb) { + const opts = options || {}; + opts._locals = res.locals || {}; + opts.settings = {}; + log.info('Calling render'); + const templateRootObject = getTemplate(template); + log.trace(templateRootObject); + TemplateService._render(templateRootObject.path, opts, function (err, html) { + cb(err, html); + }); + } + TemplateService.render = render; + + + /** + * + * + * @param {req} req + * @param {res} res + * @param {next} next + */ + function renderMiddleware(req, res, next) { + res.trostRender = function (template, opts, cb) { + return render(res, template, opts, function (err, html) { + if (cb) return cb(err, html); + if (err) return req.next(err); + return res.send(html); + }); + }; + next(); + } + TemplateService.renderMiddleware = renderMiddleware; +}; diff --git a/test/plugins/hook.test.js b/test/plugins/hook.test.js index 9aa28eb..eb32e4f 100644 --- a/test/plugins/hook.test.js +++ b/test/plugins/hook.test.js @@ -3,14 +3,6 @@ import CreateHook from '../../src/plugins/hook'; test('Plugins.addHook', function(t){ const Plugins = CreateHook({}); - function failIsSucc(p,succMessage, errMessage, msg){ - return p.then(function(){ - t.fail(errMessage); - }) - .catch(function(err){ - t.is(err.message, succMessage, msg); - }) - } t.throws(() => Plugins.addHook(null), 'PluginID required', 'addHook should not pass when pluginID is null'); t.throws( @@ -26,28 +18,28 @@ test('Plugins.addHook', function(t){ ); t.throws( - () => Plugins.addHook(0, {hook : 't'}), + () => Plugins.addHook(0, {hook: 't'}), 'fn is required', 'addHook should not pass when fn is missing', 'addHook opts.fn missing' ); t.throws( - () => Plugins.addHook(0, {hook : 22}), + () => Plugins.addHook(0, {hook: 22}), 'Hook is required', 'addHook should not pass when hook is not a string', 'addHook opts.hook is not string' ); t.throws( - () => Plugins.addHook(0, {hook : 't', fn : ''}), + () => Plugins.addHook(0, {hook: 't', fn: ''}), 'fn is required', 'addHook should not pass when fn is not a function', 'addHook opts.fn is not string' ); - Plugins.addHook(0, {hook : 'filter::test', fn : () => true}) + Plugins.addHook(0, {hook: 'filter::test', fn: () => true}); const h = Plugins.hookMap.get('filter::test'); t.truthy(h[0].fn(), 'Hook should get added to map'); -}); \ No newline at end of file +}); diff --git a/test/services/template/template.test.js b/test/services/template/template.test.js new file mode 100644 index 0000000..2820eec --- /dev/null +++ b/test/services/template/template.test.js @@ -0,0 +1,9 @@ +import test from 'ava'; +import TemplateService from '../../../src/services/template'; + +test('_defaults', function(t){ + // Lets make sure the defaults are what I think they are + // helps keep track of changes to the configurationg + // const defaults = TemplateService._defaults(); + t.pass(); +}); diff --git a/theme/friend/index.hbs b/theme/friend/index.hbs index bd11138..6e99af4 100644 --- a/theme/friend/index.hbs +++ b/theme/friend/index.hbs @@ -1,10 +1,2 @@ - - - - - Document - - -

HELLO

- - \ No newline at end of file +{{!< friend/layout}} +

Im in a layout

\ No newline at end of file diff --git a/theme/friend/layout.hbs b/theme/friend/layout.hbs new file mode 100644 index 0000000..564e77d --- /dev/null +++ b/theme/friend/layout.hbs @@ -0,0 +1,11 @@ + + + + + Document + + + {{> friend/partials/name}} + {{{body}}} + + \ No newline at end of file diff --git a/theme/friend/partials/name.hbs b/theme/friend/partials/name.hbs new file mode 100644 index 0000000..f683d7d --- /dev/null +++ b/theme/friend/partials/name.hbs @@ -0,0 +1 @@ +

Im name in friend!

\ No newline at end of file diff --git a/theme/override/index.hbs b/theme/override/index.hbs index 8cdda48..12a52c8 100644 --- a/theme/override/index.hbs +++ b/theme/override/index.hbs @@ -1,10 +1,2 @@ - - - - - Document - - -

Hey I am overridden!

- - \ No newline at end of file +{{!< override/nested}} +If im nested I should be big \ No newline at end of file diff --git a/theme/override/nested.hbs b/theme/override/nested.hbs new file mode 100644 index 0000000..8e84d59 --- /dev/null +++ b/theme/override/nested.hbs @@ -0,0 +1,3 @@ +{{!< friend/layout}} +

{{{body}}}

+{{> override/partials/name}} \ No newline at end of file diff --git a/theme/override/partials/name.hbs b/theme/override/partials/name.hbs new file mode 100644 index 0000000..d648528 --- /dev/null +++ b/theme/override/partials/name.hbs @@ -0,0 +1 @@ +

Im name in override!

\ No newline at end of file