From 0e16813387223a39741c9a687a6b8ae820dd8a7f Mon Sep 17 00:00:00 2001 From: Nils Knappmeier Date: Fri, 9 Jun 2017 22:00:12 +0200 Subject: [PATCH] Added "docEngine" to generate JSON for documenting configurations --- .thought/config.js | 5 +- .thought/partials/api.md.hbs | 3 + .thought/partials/usage.md.hbs | 97 +-------- .thought/stable-console.js | 4 +- .thought/templates/docs/api.md.hbs | 5 + .thought/templates/docs/examples.md.hbs | 105 ++++++++++ README.md | 240 +--------------------- docs/api.md | 23 +++ docs/examples.md | 256 ++++++++++++++++++++++++ examples/example-merge-doc-engine.js | 11 + index.js | 2 +- lib/doc-engine.js | 56 ++++++ test/docEngine-spec.js | 221 ++++++++++++++++++++ test/fixtures/helpers2.js | 11 + test/fixtures/preprocessor1.js | 3 + 15 files changed, 714 insertions(+), 328 deletions(-) create mode 100644 .thought/partials/api.md.hbs create mode 100644 .thought/templates/docs/api.md.hbs create mode 100644 .thought/templates/docs/examples.md.hbs create mode 100644 docs/api.md create mode 100644 docs/examples.md create mode 100644 examples/example-merge-doc-engine.js create mode 100644 lib/doc-engine.js create mode 100644 test/docEngine-spec.js create mode 100644 test/fixtures/helpers2.js create mode 100644 test/fixtures/preprocessor1.js diff --git a/.thought/config.js b/.thought/config.js index 514b8b9..2ccf1a5 100644 --- a/.thought/config.js +++ b/.thought/config.js @@ -1,5 +1,8 @@ module.exports = { plugins: [ require('thought-plugin-jsdoc') - ] + ], + badges: { + greenkeeper: true + } } diff --git a/.thought/partials/api.md.hbs b/.thought/partials/api.md.hbs new file mode 100644 index 0000000..d2665e4 --- /dev/null +++ b/.thought/partials/api.md.hbs @@ -0,0 +1,3 @@ +## API-documentation + +see [docs/api.md](docs/api.md) \ No newline at end of file diff --git a/.thought/partials/usage.md.hbs b/.thought/partials/usage.md.hbs index 255fa26..f8c830e 100644 --- a/.thought/partials/usage.md.hbs +++ b/.thought/partials/usage.md.hbs @@ -1,8 +1,6 @@ ## Usage -The following examples demonstrate how to use this module. The following files are involved: - -{{{dirTree 'examples/'}}} +The following examples demonstrate how to use this module. ### Configuration @@ -41,95 +39,6 @@ The output of this example is: {{{exec 'node -r "../.thought/stable-console" example.js' cwd='examples/'}}} -### Customizing configurations - -We can use the mechanism of {{npm 'customize'}} to adapt the configuration. -In the following example, we replace the `footer.hbs`-partial by a different version. -We do this by specifying a new partial directory. Partials with the same name as in -the previous directory will overwrite the old one. - -{{{example 'examples/example-merge.js'}}} - -The new `footer.hbs` writes only the current temperature, instead of the weather description - -{{{include 'examples/partials2/footer.hbs'}}} - -The output of this example is - -{{{exec 'node -r "../.thought/stable-console" example-merge.js' cwd='examples/'}}} - -In a similar fashion, we could replace other parts of the configuration, like templates, helpers -and the pre-processor. If we would provide a new preprocessor, it could call the old one, -by calling `this.parent(args)` - -### Name of the current target-file - -In some cases, we need to know which file we are actually rendering at the moment. -If we are rendering the template `some/template.txt.hbs`, the file `some/template.txt` -will be written (at least if {{npm 'customize-write-files'}} is used. If we want to -create relative links from this file, we need this information within helpers. -The parameter `options.customize.targetFile` that is passed to each helper, contains this information. -The following configuration registers a helper that return the targetFile: - -{{example 'examples/example-targetFile.js'}} - -Each template includes the `\{{>footer}}`-partial, which calls the `\{{targetFile}}`-helper -to include the name of the current file. - -{{include 'examples/partials-targetFile/footer.hbs'}} - -The output of this configuration is - -{{{exec 'node -r "../.thought/stable-console" example-targetFile.js' cwd='examples/'}}} - -### Accessing engine and configuration helpers - -The configuration and the engine itself is passed as additional option into each helper call: - -``` -module.exports = { - function(value, options) { - console.log("handlebars", options.customize.engine) - console.log("customizeConfig", options.customize.config) - } -} -``` - -### Which partial generates what? (Method 1) - -When we want to overriding parts of the output, we are looking for the correct partial to do so. -For this purpose, the engine allows to specify a "wrapper function" for partials. This function -is called with the contents and the name of a partial and returns the new content. Programs like -`Thought` can optionally include the partial names into the output to show the user which partial -to override in order to modify a given part of the output. - - -{{{example 'examples/example-partial-names.js'}}} - -{{{exec 'node -r "../.thought/stable-console" example-partial-names.js' cwd='examples/'}}} - -### Which partial generates what? (Method 2) - -Another method for gathering information about the source of parts of the output are source-locators. -The engine incoorporates the library {{npm 'handlebars-source-locators'}} to integrate a kind of -"source-map for the poor" into the output. Source-locators are activated by setting the option -`addSourceLocators` to `true`: - -{{{example 'examples/example-source-locators.js'}}} - -The output contain tags that contain location-information of the succeeding text: - -* `text...` for text the originate from a template file -* `text...` for text the originate from a partial - -Example output: - -{{{exec 'node -r "../.thought/stable-console" example-source-locators.js' cwd='examples/'}}} - -### Asynchronous helpers - -The `customize-engine-handlebars` uses the {{npm 'promised-handlebars'}} package as wrapper around Handlebars. -It allows helpers to return promises instead of real values. - - +### More examples +see [docs/examples.md](docs/examples.md) for more examples \ No newline at end of file diff --git a/.thought/stable-console.js b/.thought/stable-console.js index c3d7743..791d68c 100644 --- a/.thought/stable-console.js +++ b/.thought/stable-console.js @@ -4,8 +4,6 @@ var stringify = require('json-stable-stringify') console.log = function () { consoleLog.apply(this, Array.prototype.map.call( - arguments, (arg) => JSON.parse(stringify(arg, { space: 2 })) + arguments, (arg) => stringify(arg, { space: 2 }) )) } - - diff --git a/.thought/templates/docs/api.md.hbs b/.thought/templates/docs/api.md.hbs new file mode 100644 index 0000000..0c2d690 --- /dev/null +++ b/.thought/templates/docs/api.md.hbs @@ -0,0 +1,5 @@ +{{#if package.main}} +# API reference + +{{{jsdoc package.main}}} +{{/if}} \ No newline at end of file diff --git a/.thought/templates/docs/examples.md.hbs b/.thought/templates/docs/examples.md.hbs new file mode 100644 index 0000000..1e82213 --- /dev/null +++ b/.thought/templates/docs/examples.md.hbs @@ -0,0 +1,105 @@ + +### Customizing configurations + +We can use the mechanism of {{npm 'customize'}} to adapt the configuration. +In the following example, we replace the `footer.hbs`-partial by a different version. +We do this by specifying a new partial directory. Partials with the same name as in +the previous directory will overwrite the old one. + +{{{example 'examples/example-merge.js'}}} + +The new `footer.hbs` writes only the current temperature, instead of the weather description + +{{{include 'examples/partials2/footer.hbs'}}} + +The output of this example is + +{{{exec 'node -r "../.thought/stable-console" example-merge.js' cwd='examples/'}}} + +In a similar fashion, we could replace other parts of the configuration, like templates, helpers +and the pre-processor. If we would provide a new preprocessor, it could call the old one, +by calling `this.parent(args)` + +### Name of the current target-file + +In some cases, we need to know which file we are actually rendering at the moment. +If we are rendering the template `some/template.txt.hbs`, the file `some/template.txt` +will be written (at least if {{npm 'customize-write-files'}} is used. If we want to +create relative links from this file, we need this information within helpers. +The parameter `options.customize.targetFile` that is passed to each helper, contains this information. +The following configuration registers a helper that return the targetFile: + +{{example 'examples/example-targetFile.js'}} + +Each template includes the `\{{>footer}}`-partial, which calls the `\{{targetFile}}`-helper +to include the name of the current file. + +{{include 'examples/partials-targetFile/footer.hbs'}} + +The output of this configuration is + +{{{exec 'node -r "../.thought/stable-console" example-targetFile.js' cwd='examples/'}}} + +### Accessing engine and configuration helpers + +The configuration and the engine itself is passed as additional option into each helper call: + +``` +module.exports = { + function(value, options) { + console.log("handlebars", options.customize.engine) + console.log("customizeConfig", options.customize.config) + } +} +``` + +### Which partial generates what? (Method 1) + +When we want to overriding parts of the output, we are looking for the correct partial to do so. +For this purpose, the engine allows to specify a "wrapper function" for partials. This function +is called with the contents and the name of a partial and returns the new content. Programs like +`Thought` can optionally include the partial names into the output to show the user which partial +to override in order to modify a given part of the output. + + +{{{example 'examples/example-partial-names.js'}}} + +{{{exec 'node -r "../.thought/stable-console" example-partial-names.js' cwd='examples/'}}} + +### Which partial generates what? (Method 2) + +Another method for gathering information about the source of parts of the output are source-locators. +The engine incoorporates the library {{npm 'handlebars-source-locators'}} to integrate a kind of +"source-map for the poor" into the output. Source-locators are activated by setting the option +`addSourceLocators` to `true`: + +{{{example 'examples/example-source-locators.js'}}} + +The output contain tags that contain location-information of the succeeding text: + +* `text...` for text the originate from a template file +* `text...` for text the originate from a partial + +Example output: + +{{{exec 'node -r "../.thought/stable-console" example-source-locators.js' cwd='examples/'}}} + +### Asynchronous helpers + +The `customize-engine-handlebars` uses the {{npm 'promised-handlebars'}} package as wrapper around Handlebars. +It allows helpers to return promises instead of real values. + + +## Documenting configurations + +It is not possible to generate docs directly with `{{packageJson.name}}`. But using the `docEngine`, +you can generate a JSON that you can render to HTML or Markdown (for example using Handlebars). + +{{{example 'examples/example-merge-doc-engine.js'}}} + +The `docEngine` performs different transformations on the input configuration. For example, +helpers are not loaded, but the path to the file is collected into an array. + +{{{exec 'node -r "../.thought/stable-console" example-merge-doc-engine.js' cwd='examples/'}}} + + diff --git a/README.md b/README.md index 3ec3c94..0c0af44 100644 --- a/README.md +++ b/README.md @@ -16,29 +16,7 @@ npm install customize-engine-handlebars ## Usage -The following examples demonstrate how to use this module. The following files are involved: - -

-├── config-module.js
-├── example-merge.js
-├── example-partial-names.js
-├── example-source-locators.js
-├── example-targetFile.js
-├── example.js
-├── hb-helpers.js
-├── hb-preprocessor.js
-├─┬ partials-targetFile/
-│ └── footer.hbs
-├─┬ partials/
-│ └── footer.hbs
-├─┬ partials2/
-│ └── footer.hbs
-└─┬ templates/
-  ├─┬ subdir/
-  │ └── text3.txt.hbs
-  ├── text1.txt.hbs
-  └── text2.txt.hbs
-
+The following examples demonstrate how to use this module. ### Configuration @@ -130,217 +108,21 @@ Github-Name: {{{github.name}}} The output of this example is: ``` -{ handlebars: - { 'subdir/text3.txt': '------\nGithub-Name: Nils Knappmeier', - 'text1.txt': 'I\'m nknapp\n\nI\'m living in Darmstadt.\n\n------\nGithub-Name: Nils Knappmeier', - 'text2.txt': 'I\'m nknapp\n\nI\'m living in DARMSTADT.\n\n------\nGithub-Name: Nils Knappmeier' } } -``` - -### Customizing configurations - -We can use the mechanism of [customize](https://npmjs.com/package/customize) to adapt the configuration. -In the following example, we replace the `footer.hbs`-partial by a different version. -We do this by specifying a new partial directory. Partials with the same name as in -the previous directory will overwrite the old one. - -```js -var customize = require('customize') -customize() - .registerEngine('handlebars', require('customize-engine-handlebars')) - .load(require('./config-module.js')) - .merge({ - handlebars: { - partials: 'partials2' - } - }) - .run() - .then(console.log) -``` - -The new `footer.hbs` writes only the current temperature, instead of the weather description - -```hbs ------- -Blog: {{{github.blog}}} - -``` - - -The output of this example is - -``` -{ handlebars: - { 'subdir/text3.txt': '------\nBlog: https://blog.knappi.org\n', - 'text1.txt': 'I\'m nknapp\n\nI\'m living in Darmstadt.\n\n------\nBlog: https://blog.knappi.org\n', - 'text2.txt': 'I\'m nknapp\n\nI\'m living in DARMSTADT.\n\n------\nBlog: https://blog.knappi.org\n' } } -``` - -In a similar fashion, we could replace other parts of the configuration, like templates, helpers -and the pre-processor. If we would provide a new preprocessor, it could call the old one, -by calling `this.parent(args)` - -### Name of the current target-file - -In some cases, we need to know which file we are actually rendering at the moment. -If we are rendering the template `some/template.txt.hbs`, the file `some/template.txt` -will be written (at least if [customize-write-files](https://npmjs.com/package/customize-write-files) is used. If we want to -create relative links from this file, we need this information within helpers. -The parameter `options.customize.targetFile` that is passed to each helper, contains this information. -The following configuration registers a helper that return the targetFile: - -```js -var customize = require('customize') -customize() - .registerEngine('handlebars', require('customize-engine-handlebars')) - .load(require('./config-module.js')) - .merge({ - handlebars: { - templates: 'templates', - partials: 'partials-targetFile', - helpers: { - // Helper that returns the targetFile - targetFile: function (options) { - return options.customize.targetFile - } - } - } - }) - .run() - .then(console.log) -``` - -Each template includes the `{{>footer}}`-partial, which calls the `{{targetFile}}`-helper -to include the name of the current file. - -```hbs ------- -File: {{targetFile}} -``` - - -The output of this configuration is - -``` -{ handlebars: - { 'subdir/text3.txt': '------\nFile: subdir/text3.txt', - 'text1.txt': 'I\'m nknapp\n\nI\'m living in Darmstadt.\n\n------\nFile: text1.txt', - 'text2.txt': 'I\'m nknapp\n\nI\'m living in DARMSTADT.\n\n------\nFile: text2.txt' } } -``` - -### Accessing engine and configuration helpers - -The configuration and the engine itself is passed as additional option into each helper call: - -``` -module.exports = { - function(value, options) { - console.log("handlebars", options.customize.engine) - console.log("customizeConfig", options.customize.config) - } +{ + "handlebars": { + "subdir/text3.txt": "------\nGithub-Name: Nils Knappmeier", + "text1.txt": "I'm nknapp\n\nI'm living in Darmstadt.\n\n------\nGithub-Name: Nils Knappmeier", + "text2.txt": "I'm nknapp\n\nI'm living in DARMSTADT.\n\n------\nGithub-Name: Nils Knappmeier" + } } ``` -### Which partial generates what? (Method 1) - -When we want to overriding parts of the output, we are looking for the correct partial to do so. -For this purpose, the engine allows to specify a "wrapper function" for partials. This function -is called with the contents and the name of a partial and returns the new content. Programs like -`Thought` can optionally include the partial names into the output to show the user which partial -to override in order to modify a given part of the output. - - -```js -var customize = require('customize') -customize() - .registerEngine('handlebars', require('customize-engine-handlebars')) - .load(require('./config-module.js')) - .merge({ - handlebars: { - partials: 'partials2', - partialWrapper: function (contents, name) { - return '[BEGIN ' + name + ']\n' + contents + '[END ' + name + ']' - } - } - }) - .run() - .then(console.log) -``` - -``` -{ handlebars: - { 'subdir/text3.txt': '[BEGIN footer]\n------\nBlog: https://blog.knappi.org\n[END footer]', - 'text1.txt': 'I\'m nknapp\n\nI\'m living in Darmstadt.\n\n[BEGIN footer]\n------\nBlog: https://blog.knappi.org\n[END footer]', - 'text2.txt': 'I\'m nknapp\n\nI\'m living in DARMSTADT.\n\n[BEGIN footer]\n------\nBlog: https://blog.knappi.org\n[END footer]' } } -``` - -### Which partial generates what? (Method 2) - -Another method for gathering information about the source of parts of the output are source-locators. -The engine incoorporates the library [handlebars-source-locators](https://npmjs.com/package/handlebars-source-locators) to integrate a kind of -"source-map for the poor" into the output. Source-locators are activated by setting the option -`addSourceLocators` to `true`: - -```js -var customize = require('customize') -customize() - .registerEngine('handlebars', require('customize-engine-handlebars')) - .load(require('./config-module.js')) - .merge({ - handlebars: { - partials: 'partials2', - addSourceLocators: true - } - }) - .run() - .then(console.log) -``` - -The output contain tags that contain location-information of the succeeding text: - -* `text...` for text the originate from a template file -* `text...` for text the originate from a partial - -Example output: - -``` -{ handlebars: - { 'subdir/text3.txt': '------\nBlog: https://blog.knappi.org\n', - 'text1.txt': 'I\'m nknapp\n\nI\'m living in Darmstadt.\n\n------\nBlog: https://blog.knappi.org\n', - 'text2.txt': 'I\'m nknapp\n\nI\'m living in DARMSTADT.\n\n------\nBlog: https://blog.knappi.org\n' } } -``` - -### Asynchronous helpers - -The `customize-engine-handlebars` uses the [promised-handlebars](https://npmjs.com/package/promised-handlebars) package as wrapper around Handlebars. -It allows helpers to return promises instead of real values. - - - - -# API reference - - - -## CustomizeHandlebarsConfig : object -The default configuration for the handlebars engine - -**Kind**: global typedef -**Api**: public -**Properties** - -| Name | Type | Description | -| --- | --- | --- | -| partials | string | path to a partials directory. Each `.hbs`-file in the directory (or in the tree) is registered as partial by its name (or relative path), without the `.hbs`-extension. | -| partialWrapper | function | a function that can modify partials just before they are registered with the Handlebars engine. It receives the partial contents as first parameter and the partial name as second parameter and must return the new content (or a promise for the content. The parameter was introduced mainly for debugging purposes (i.e. to surround each partial with a string containing the name of the partial). When this function is overridden, the parent function is available throught `this.parent`. | -| helpers | string \| object \| function | if this is an object it is assumed to be a list of helper functions, if this is function it is assumed to return an object of helper functions, if this is a string, it is assumed to be the path to a module returning either an object of a function as above. | -| templates | string | path to a directory containing templates. Handlebars is called with each `.hbs`-file as template. The result of the engine consists of an object with a property for each template and the Handlebars result for this template as value. | -| data | string \| object \| function | a javascript-object to use as input for handlebars. Same as with the `helpers`, it is also acceptable to specify the path to a module exporting the data and a function computing the data. | -| preprocessor | function \| string | a function that takes the input data as first parameter and transforms it into another object or the promise for an object. It the input data is a promise itself, is resolved before calling this function. If the preprocessor is overridden, the parent preprocessor is available with `this.parent(data)` | -| hbsOptions | object | options to pass to `Handlebars.compile`. | -| addSourceLocators | boolean | add [handlebars-source-locators](https://github.com/nknapp/handlebars-source-locators) to the output of each template | - +### More examples +see [docs/examples.md](docs/examples.md) for more examples +## API-documentation +see [docs/api.md](docs/api.md) # License diff --git a/docs/api.md b/docs/api.md new file mode 100644 index 0000000..8e0bb12 --- /dev/null +++ b/docs/api.md @@ -0,0 +1,23 @@ +# API reference + + + +## CustomizeHandlebarsConfig : object +The default configuration for the handlebars engine + +**Kind**: global typedef +**Api**: public +**Properties** + +| Name | Type | Description | +| --- | --- | --- | +| partials | string | path to a partials directory. Each `.hbs`-file in the directory (or in the tree) is registered as partial by its name (or relative path), without the `.hbs`-extension. | +| partialWrapper | function | a function that can modify partials just before they are registered with the Handlebars engine. It receives the partial contents as first parameter and the partial name as second parameter and must return the new content (or a promise for the content. The parameter was introduced mainly for debugging purposes (i.e. to surround each partial with a string containing the name of the partial). When this function is overridden, the parent function is available throught `this.parent`. | +| helpers | string \| object \| function | if this is an object it is assumed to be a list of helper functions, if this is function it is assumed to return an object of helper functions, if this is a string, it is assumed to be the path to a module returning either an object of a function as above. | +| templates | string | path to a directory containing templates. Handlebars is called with each `.hbs`-file as template. The result of the engine consists of an object with a property for each template and the Handlebars result for this template as value. | +| data | string \| object \| function | a javascript-object to use as input for handlebars. Same as with the `helpers`, it is also acceptable to specify the path to a module exporting the data and a function computing the data. | +| preprocessor | function \| string | a function that takes the input data as first parameter and transforms it into another object or the promise for an object. It the input data is a promise itself, is resolved before calling this function. If the preprocessor is overridden, the parent preprocessor is available with `this.parent(data)` | +| hbsOptions | object | options to pass to `Handlebars.compile`. | +| addSourceLocators | boolean | add [handlebars-source-locators](https://github.com/nknapp/handlebars-source-locators) to the output of each template | + + diff --git a/docs/examples.md b/docs/examples.md new file mode 100644 index 0000000..9c7c969 --- /dev/null +++ b/docs/examples.md @@ -0,0 +1,256 @@ + +### Customizing configurations + +We can use the mechanism of [customize](https://npmjs.com/package/customize) to adapt the configuration. +In the following example, we replace the `footer.hbs`-partial by a different version. +We do this by specifying a new partial directory. Partials with the same name as in +the previous directory will overwrite the old one. + +```js +var customize = require('customize') +customize() + .registerEngine('handlebars', require('customize-engine-handlebars')) + .load(require('./config-module.js')) + .merge({ + handlebars: { + partials: 'partials2' + } + }) + .run() + .then(console.log) +``` + +The new `footer.hbs` writes only the current temperature, instead of the weather description + +```hbs +------ +Blog: {{{github.blog}}} + +``` + + +The output of this example is + +``` +{ + "handlebars": { + "subdir/text3.txt": "------\nBlog: https://blog.knappi.org\n", + "text1.txt": "I'm nknapp\n\nI'm living in Darmstadt.\n\n------\nBlog: https://blog.knappi.org\n", + "text2.txt": "I'm nknapp\n\nI'm living in DARMSTADT.\n\n------\nBlog: https://blog.knappi.org\n" + } +} +``` + +In a similar fashion, we could replace other parts of the configuration, like templates, helpers +and the pre-processor. If we would provide a new preprocessor, it could call the old one, +by calling `this.parent(args)` + +### Name of the current target-file + +In some cases, we need to know which file we are actually rendering at the moment. +If we are rendering the template `some/template.txt.hbs`, the file `some/template.txt` +will be written (at least if [customize-write-files](https://npmjs.com/package/customize-write-files) is used. If we want to +create relative links from this file, we need this information within helpers. +The parameter `options.customize.targetFile` that is passed to each helper, contains this information. +The following configuration registers a helper that return the targetFile: + +```js +var customize = require('customize') +customize() + .registerEngine('handlebars', require('customize-engine-handlebars')) + .load(require('./config-module.js')) + .merge({ + handlebars: { + templates: 'templates', + partials: 'partials-targetFile', + helpers: { + // Helper that returns the targetFile + targetFile: function (options) { + return options.customize.targetFile + } + } + } + }) + .run() + .then(console.log) +``` + +Each template includes the `{{>footer}}`-partial, which calls the `{{targetFile}}`-helper +to include the name of the current file. + +```hbs +------ +File: {{targetFile}} +``` + + +The output of this configuration is + +``` +{ + "handlebars": { + "subdir/text3.txt": "------\nFile: subdir/text3.txt", + "text1.txt": "I'm nknapp\n\nI'm living in Darmstadt.\n\n------\nFile: text1.txt", + "text2.txt": "I'm nknapp\n\nI'm living in DARMSTADT.\n\n------\nFile: text2.txt" + } +} +``` + +### Accessing engine and configuration helpers + +The configuration and the engine itself is passed as additional option into each helper call: + +``` +module.exports = { + function(value, options) { + console.log("handlebars", options.customize.engine) + console.log("customizeConfig", options.customize.config) + } +} +``` + +### Which partial generates what? (Method 1) + +When we want to overriding parts of the output, we are looking for the correct partial to do so. +For this purpose, the engine allows to specify a "wrapper function" for partials. This function +is called with the contents and the name of a partial and returns the new content. Programs like +`Thought` can optionally include the partial names into the output to show the user which partial +to override in order to modify a given part of the output. + + +```js +var customize = require('customize') +customize() + .registerEngine('handlebars', require('customize-engine-handlebars')) + .load(require('./config-module.js')) + .merge({ + handlebars: { + partials: 'partials2', + partialWrapper: function (contents, name) { + return '[BEGIN ' + name + ']\n' + contents + '[END ' + name + ']' + } + } + }) + .run() + .then(console.log) +``` + +``` +{ + "handlebars": { + "subdir/text3.txt": "[BEGIN footer]\n------\nBlog: https://blog.knappi.org\n[END footer]", + "text1.txt": "I'm nknapp\n\nI'm living in Darmstadt.\n\n[BEGIN footer]\n------\nBlog: https://blog.knappi.org\n[END footer]", + "text2.txt": "I'm nknapp\n\nI'm living in DARMSTADT.\n\n[BEGIN footer]\n------\nBlog: https://blog.knappi.org\n[END footer]" + } +} +``` + +### Which partial generates what? (Method 2) + +Another method for gathering information about the source of parts of the output are source-locators. +The engine incoorporates the library [handlebars-source-locators](https://npmjs.com/package/handlebars-source-locators) to integrate a kind of +"source-map for the poor" into the output. Source-locators are activated by setting the option +`addSourceLocators` to `true`: + +```js +var customize = require('customize') +customize() + .registerEngine('handlebars', require('customize-engine-handlebars')) + .load(require('./config-module.js')) + .merge({ + handlebars: { + partials: 'partials2', + addSourceLocators: true + } + }) + .run() + .then(console.log) +``` + +The output contain tags that contain location-information of the succeeding text: + +* `text...` for text the originate from a template file +* `text...` for text the originate from a partial + +Example output: + +``` +{ + "handlebars": { + "subdir/text3.txt": "------\nBlog: https://blog.knappi.org\n", + "text1.txt": "I'm nknapp\n\nI'm living in Darmstadt.\n\n------\nBlog: https://blog.knappi.org\n", + "text2.txt": "I'm nknapp\n\nI'm living in DARMSTADT.\n\n------\nBlog: https://blog.knappi.org\n" + } +} +``` + +### Asynchronous helpers + +The `customize-engine-handlebars` uses the [promised-handlebars](https://npmjs.com/package/promised-handlebars) package as wrapper around Handlebars. +It allows helpers to return promises instead of real values. + + +## Documenting configurations + +It is not possible to generate docs directly with ``. But using the `docEngine`, +you can generate a JSON that you can render to HTML or Markdown (for example using Handlebars). + +```js +var customize = require('customize') +customize() + .registerEngine('handlebars', require('customize-engine-handlebars').docEngine) + .load(require('./config-module.js')) + .merge({ + handlebars: { + partials: 'partials2' + } + }) + .run() + .then(console.log) +``` + +The `docEngine` performs different transformations on the input configuration. For example, +helpers are not loaded, but the path to the file is collected into an array. + +``` +{ + "handlebars": { + "data": { + "city": "Darmstadt", + "name": "nknapp" + }, + "hbsOptions": { + }, + "helpers": [ + "hb-helpers.js" + ], + "partialWrapper": [ + ], + "partials": { + "footer.hbs": { + "contents": "------\nBlog: {{{github.blog}}}\n", + "path": "partials2/footer.hbs" + } + }, + "preprocessor": [ + "hb-preprocessor.js" + ], + "templates": { + "subdir/text3.txt.hbs": { + "contents": "{{>footer}}", + "path": "templates/subdir/text3.txt.hbs" + }, + "text1.txt.hbs": { + "contents": "I'm {{name}}\n\nI'm living in {{city}}.\n\n{{>footer}}", + "path": "templates/text1.txt.hbs" + }, + "text2.txt.hbs": { + "contents": "I'm {{name}}\n\nI'm living in {{shout city}}.\n\n{{>footer}}", + "path": "templates/text2.txt.hbs" + } + } + } +} +``` + + diff --git a/examples/example-merge-doc-engine.js b/examples/example-merge-doc-engine.js new file mode 100644 index 0000000..ff8a57e --- /dev/null +++ b/examples/example-merge-doc-engine.js @@ -0,0 +1,11 @@ +var customize = require('customize') +customize() + .registerEngine('handlebars', require('../').docEngine) + .load(require('./config-module.js')) + .merge({ + handlebars: { + partials: 'partials2' + } + }) + .run() + .then(console.log) diff --git a/index.js b/index.js index a510c79..f11f02b 100644 --- a/index.js +++ b/index.js @@ -71,7 +71,7 @@ var contents = function (partials) { */ module.exports = { schema: require('./schema.js'), - + docEngine: require('./lib/doc-engine'), defaultConfig: { partials: {}, partialWrapper: function (contents, name) { diff --git a/lib/doc-engine.js b/lib/doc-engine.js new file mode 100644 index 0000000..570c74f --- /dev/null +++ b/lib/doc-engine.js @@ -0,0 +1,56 @@ +const {files} = require('customize/helpers-io') + +/** + * This customize-engine takes the same configuration schema as + * `customize-engine-handlebars`, but it performs different + * merges. It's goal is to create a configuration that + * can be rendered as documentation with a Handlebars template. + * + * + * "partials" and "templates" use the `files`-helper of customize, "data" and "hbsOptions" are merged like objects. + * "partialWrapper","helpers" and "preprocessor" are collected in an array. + * + * For helpers and preprocessors that are directly defined in the configuration (not as path to a javascript-module), + * a `null` will be inserted into the list of loaded helper files. This allows the documentation-template to add a + * warning to the output. if no helpers are defined, no new entry (not even `null`) will be added. + */ +module.exports = { + defaultConfig: { + // Merged options + partials: {}, + templates: {}, + data: {}, + hbsOptions: {}, + // Collected options + partialWrapper: [], + helpers: [], + preprocessor: [] + }, + + preprocessConfig (config) { + return { + partials: files(config.partials), + partialWrapper: config.partialWrapper && [ config.partialWrapper.toString() ], + helpers: config.helpers && [onlyIfString(config.helpers)], + templates: files(config.templates), + data: config.data, + preprocessor: config.preprocessor && [onlyIfString(config.preprocessor)], + hbsOptions: config.hbsOptions, + addSourceLocators: config.addSourceLocators + } + }, + + run (config) { + return config + } +} + +/** + * Returns a string if the parameter is a string + * and null, if it is anything else + * @param {strnig|function} stringOrFunction the input parameter + * @return {string|null} a string or null + */ +function onlyIfString (stringOrFunction) { + return typeof stringOrFunction === 'string' ? stringOrFunction : null +} diff --git a/test/docEngine-spec.js b/test/docEngine-spec.js new file mode 100644 index 0000000..0cbae1b --- /dev/null +++ b/test/docEngine-spec.js @@ -0,0 +1,221 @@ +/*! + * customize-engine-handlebars + * + * Copyright (c) 2015 Nils Knappmeier. + * Released under the MIT license. + */ + +/* eslint-env mocha */ +'use strict' + +var customize = require('customize') +var _ = require('../lib/utils') +var chai = require('chai') +var chaiAsPromised = require('chai-as-promised') +chai.use(chaiAsPromised) + +var expect = chai.expect + +describe('the docEngine', function () { + var emptyEngine = null + var hb = null + before(function () { + emptyEngine = customize().registerEngine('handlebars', require('../').docEngine) + hb = emptyEngine + .merge({ + handlebars: { + partials: 'test/fixtures/testPartials1', + helpers: 'test/fixtures/helpers.js', + templates: 'test/fixtures/templates', + data: { + eins: 'one', + zwei: 'two', + drei: 'three', + vier: 'four' + }, + preprocessor: function (data) { + return _.mapValues(data, function (value) { + return '->' + value + '<-' + }) + } + } + }) + }) + + it('should load partials and templates and return one result per template', function () { + return expect(hb.run()).to.eventually.deep.equal({ + 'handlebars': { + 'data': { + 'drei': 'three', + 'eins': 'one', + 'vier': 'four', + 'zwei': 'two' + }, + 'hbsOptions': {}, + 'helpers': [ + 'test/fixtures/helpers.js' + ], + 'partialWrapper': [], + 'partials': { + 'eins.hbs': { + 'contents': 'testPartials1/eins {{{eins}}}', + 'path': 'test/fixtures/testPartials1/eins.hbs' + }, + 'zwei.hbs': { + 'contents': 'testPartials1/zwei {{{zwei}}}', + 'path': 'test/fixtures/testPartials1/zwei.hbs' + } + }, + 'preprocessor': [ + null + ], + 'templates': { + 'a.md.hbs': { + 'contents': 'a.md {{>eins}}', + 'path': 'test/fixtures/templates/a.md.hbs' + }, + 'b.md.hbs': { + 'contents': 'b.md {{>zwei}} {{{helper1 zwei}}}', + 'path': 'test/fixtures/templates/b.md.hbs' + } + } + } + }) + }) + + it('should collect multple helper-files and merge templates and partials', function () { + return expect(hb.merge({ + handlebars: { + partials: 'test/fixtures/testPartials2', + helpers: 'test/fixtures/helpers2.js', + templates: 'test/fixtures/templates-cleanInput', + data: { + eins: 'eins', + zwei: 'zwei' + }, + preprocessor: 'test/fixtures/preprocessor1.js' + } + + }).run()).to.eventually.deep.equal({ + 'handlebars': { + 'data': { + 'drei': 'three', + 'eins': 'eins', + 'vier': 'four', + 'zwei': 'zwei' + }, + 'hbsOptions': {}, + 'helpers': [ + 'test/fixtures/helpers.js', + 'test/fixtures/helpers2.js' + ], + 'partialWrapper': [], + 'partials': { + 'drei.hbs': { + 'contents': 'testPartials2/drei {{{drei}}}', + 'path': 'test/fixtures/testPartials2/drei.hbs' + }, + 'eins.hbs': { + 'contents': 'testPartials1/eins {{{eins}}}', + 'path': 'test/fixtures/testPartials1/eins.hbs' + }, + 'zwei.hbs': { + 'contents': 'testPartials2/zwei {{{zwei}}}', + 'path': 'test/fixtures/testPartials2/zwei.hbs' + } + }, + 'preprocessor': [ + null, + 'test/fixtures/preprocessor1.js' + ], + 'templates': { + 'a.md.hbs': { + 'contents': '{{#each .}}{{@key}}={{.}}{{/each}}', + 'path': 'test/fixtures/templates-cleanInput/a.md.hbs' + }, + 'b.md.hbs': { + 'contents': 'b.md {{>zwei}} {{{helper1 zwei}}}', + 'path': 'test/fixtures/templates/b.md.hbs' + } + } + } + }) + }) + + it('should use "null" as placeholder for helpers that are defined in a function', function () { + var hb2 = hb.merge({ + handlebars: { + helpers: function () { + return { + helper1: function (value) { + return `helper1[${value}]` + } + } + }, + data: function () { + return ['eins', 'zwei', 'drei', 'view'].reduce((result, key) => { + result[key] = key.split('').reverse().join('') + return result + }, {}) + } + } + }) + return expect(hb2.run().then(x => x.handlebars.helpers)).to.eventually.deep.equal([ + 'test/fixtures/helpers.js', + null + ]) + }) + + it('should not include helper- or preprocessor-modules that do not exist', function () { + return expect(emptyEngine.merge({ + handlebars: { + helpers: 'test/fixtures/non-existing-helper.js', + preprocessor: 'test/fixtures/non-existing-preprocessor.js' + } + }).run()).to.deep.equal({}) + }) + + it('should include the source-code of the partial wrapper', function () { + var hb2 = emptyEngine.merge({ + handlebars: { + partialWrapper: function (contents, name) { + return '[' + name + '] ' + contents + ' [/' + name + ']' + } + } + }) + + return expect(hb2.run().then(x => x.handlebars.partialWrapper)).to.eventually.deep.equal([ + `function (contents, name) {\n return '[' + name + '] ' + contents + ' [/' + name + ']'\n }` + ]) + }) + + it('should include multiple partial wrappers', function () { + var hb2 = emptyEngine.merge({ + handlebars: { + partialWrapper: function (contents, name) { + return '[' + name + '] ' + contents + ' [/' + name + ']' + } + } + }) + .merge({ + handlebars: { + partialWrapper: function (contents, name) { + return '(' + this.parent(contents, name) + ')' + } + } + }) + return expect(hb2.run().then(x => x.handlebars.partialWrapper)).to.eventually.deep.equal([ + `function (contents, name) {\n return '[' + name + '] ' + contents + ' [/' + name + ']'\n }`, + `function (contents, name) {\n return '(' + this.parent(contents, name) + ')'\n }` + ]) + }) + + it('should include the source-locators property', function () { + var hb2 = hb.merge({ + handlebars: { + addSourceLocators: true + } + }) + return expect(hb2.run().then(x => x.handlebars.addSourceLocators)).to.eventually.equal(true) + }) +}) diff --git a/test/fixtures/helpers2.js b/test/fixtures/helpers2.js new file mode 100644 index 0000000..025123d --- /dev/null +++ b/test/fixtures/helpers2.js @@ -0,0 +1,11 @@ +module.exports = { + helper1: function (abc) { + return 'helper1(' + abc + ')' + }, + thisOptionsHelper: function (abc, options) { + return this.name + abc + }, + targetFile: function (options) { + return options.customize.targetFile + } +} diff --git a/test/fixtures/preprocessor1.js b/test/fixtures/preprocessor1.js new file mode 100644 index 0000000..0ec774f --- /dev/null +++ b/test/fixtures/preprocessor1.js @@ -0,0 +1,3 @@ +module.exports = function preprocessor (input) { + return input +}