Détail du package

customize

bootprint9kMIT4.0.4

A simple framework to create customizable engines

readme

customize

NPM version

A simple framework to create customizable engines

Customize is an abstraction of bootprint's the merging-behaviour. It allows you to create your own projects and engines (other than Less and Handlebars) and create overridable configurations for those.

At its core, it uses lodash#mergeWith to merge configurations. It uses a customizer-function that supports promises and custom overrider functions attached to the object.

Engines

Used by

Installation

npm install customize

Usage

The following example should demonstrate the usage of Customize and the files io-helper. Consider the following file tree


├─┬ dir1/
│ ├── a.md
│ └── b.md
├─┬ dir2/
│ └── a.md
├── engine-concat-files.js
├── example-buildConfig.js
├── example1.js
└── example2.js

Creating an engine

The first thing we need, is an engine. For now, we create an engine that just concatenates the contents of all files in a directory. We put this engine into the file engine-concat-files.js

const files = require('customize/helpers-io').files

module.exports = {
  // Optional input schema for engine-configurations
  // If this is present, the JSON will be validated before being passed into "preprocessConfig"
  schema: {
    description: 'Path to a directory containing files',
    type: 'string'
  },

  // Initial configuration when registering the engine.
  defaultConfig: null,

  // Files/Dirs to-be-watched with the default configuration
  defaultWatched: [],

  // This function is called for any `.merge` input.
  // It converts the input into its mergable form
  preprocessConfig: function(config) {
    return files(config)
  },

  // This function is called to determine the files and directories
  // to watch in developmentMode
  watched: function(config) {
    return [
      // The config itself is the directory-path
      config
    ]
  },

  // Runs the engine with a resolved configuration.
  // The config contains no Promises anymore.
  // The function returns an object
  //
  // {
  //    "filename.txt": "file-contents"
  // }
  //
  run: function(config) {
    let result = ''
    Object.keys(config).forEach(filename => {
      result += config[filename].contents + '\n'
    })
    return {
      // Return a file called "concat.txt"
      'concat.txt': result
    }
  }
}
  • The engine provides an empty default configuration. This configuration is used as long as no .merge and .load function is called.
  • The preprocessor of the engine assumes that the input configuration for this engine a path to a directory. It then uses the files io-helper to convert this path into an object of lazy promises.
  • The run-function concatenates the contents of the files. It returns an object

      { "filename.txt": "contents", ... }
    

    output file. The module customize-write-files can be used to write such files to disk in a node environment. In order to this to work, the contents must either be a string, a buffer or a readable stream. Strings will be stored in utf-8 encoding.

Loading a configuration

In order to see, how the preprocessor and the files-helper works, we can display the configuration after a merge:

const customize = require('customize')

// Load files from one directory and merge with second
customize()
  .registerEngine('files', require('./engine-concat-files'))
  .merge({
    files: 'dir1'
  })
  .buildConfig()
  .then(result => console.log(result.files))

The example creates a new Customize-instances, registers our engine under the name files and provides the path to a directory as configuration for the files engine (i.e. as property files within the configuration object). It then uses the .buildConfig() function convert all nested promises to a single promise for the whole config. This example prints the following result.

{ 'a.md': { contents: 'First file (from dir1)', path: 'dir1/a.md' },
  'b.md': { contents: 'Second file (from dir1)', path: 'dir1/b.md' } }

We can see that the files-call of the preprocessor converted the directory path into an object containing a one property for each file in the directory.

Running the engine

So far, we have loaded and displayed the preprocessed configuration. Now replace the .buildConfig()-call by .run()

const customize = require('customize')

// Load files from one directory
customize()
  .registerEngine('files', require('./engine-concat-files'))
  .merge({
    files: 'dir1'
  })
  .run()
  .then(result => console.log(result.files))

The engines run()-method will now be executed with the resolved configuration, which yields the following output:

{ 'concat.txt': 'First file (from dir1)\nSecond file (from dir1)\n' }

Merging another configuration

We now have a working customizable configuration. The only thing we have not tried yet is to customize it. We are going to assume that someone, maybe Bob, wants to reuse the configuration for my own purposes, because he really likes it, and it really does exactly what he was looking for. Almost... Except, that the contents of the first file (a.md) needs to be replace by something else. In reality this might be a Handlebars partial to include different contents, or an additional Less-file that changes some styles to follow Bob' company's style-guide.

We can do this, by merging another configuration, but let's have a look at the directory tree before doing this:


├─┬ dir1/
│ ├── a.md
│ └── b.md
├─┬ dir2/
│ └── a.md
├── engine-concat-files.js
├── example-buildConfig.js
├── example1.js
└── example2.js

You can see that the second directory contains a file a.md. We will use this file to replace the file of the first directory.

const customize = require('customize')

// Load files from one directory and merge with second
customize()
  .registerEngine('files', require('./engine-concat-files'))
  .merge({
    files: 'dir1'
  })
  .merge({
    files: 'dir2'
  })
  .run()
  .then(result => console.log(result.files))

There is an additional call to .merge in this code. Its input is also passed to the engine's preprocessor, so now we get two objects containing files and their contents and those are merged by the .mergeWith-function of the lodash library, so that in the above example, the property a.md is replace by the value in the second configuration. So the output of this example is

{ 'concat.txt': 'First file (from dir2)\nSecond file (from dir1)\n' }

Advanced usage

This is the essence of customize. Actually, things are a bit more complicated. A custom overrider ensures (in this order)

  • that nested objects can provide there own overrider function in a _customize_custom_overrider-property,
  • that array-values are concatenated rather than replaced
  • and that promises are correctly merged.

Finally, the .files()-helper does not return the file contents directly. It returns a promise for the file contents. This promise is lazy and only evaluated when the .then()-method is called. And it uses the Customize.leaf() method to attach custom overrider, so that a file-promise replaces its predecessor without .then() being called. This means that files, whose contents is overridden by other files, are not opened for reading.

Application of the principles

Currently, there is only the thought package uses customize, but bootprint uses the same principle.

In thought the .thought/partials directory is included to allow the user to override default Handlebars-partials with custom verison.

In bootprint the user can create packages with Handlebars-partials and Less-definitions, which include and override partials and definitions from other packages.

Troubleshooting

Customize uses the debug module for debug logging. You can use the following channels to enable debugging:

  • DEBUG=customize:versions logs versions of loaded modules (like it was the default in version 1.x)
  • DEBUG=customize:state logs the resolved state after a merge
  • DEBUG=customize:base logs errors and status changes

API-reference

This package will always support the latest version of NodeJS and as well as the current LTS version. In the future, it will not be considered a breaking change to drop support of a pre-LTS version of NodeJS.

The exported module is a function that creates a new empty Customize-instance.

customize

Create a new Customize object with an empty configuration

customize.debugState

For coverage testing: Expose the debugState object so it can be enabled an disabled in testcases

Kind: static property of customize

customize.debug

For coverage testing: Expose the debug object so it can be enabled an disabled in testcases

Kind: static property of customize

customize.Customize : customize

Exposes the constructor of the customize object

Kind: static property of customize

customize.overrider : customOverrider

Custom overrider-function (that is used as customizer in (lodash#merge)[https://lodash.com/docs#merge]

Kind: static property of customize

customize.withParent

Wrap a function so that if it overrides another function, that function will be available as this.parent

Kind: static property of customize
Read only: true
Api: public

Param
fn

customize.leaf ⇒ Promise

Create a promise that is regarded as leaf in the configuration tree. That means, that the overrider is not resolving this promise when overriding values. Promised object values will not be merged but replaced.

Kind: static property of customize
Access: public
Read only: true

Param Type Description
promiseOrValue * a promise or a valude that represents the leaf

customize~Customize

Kind: inner class of customize

new Customize()

This class does the actual work. When calling require('customize')() a new instance of this class is returned with an empty configuration, so new Customize(...) should never be called outside this module config and parentConfig are of the form

{ engine: { config: ..., watched: [ ... ] } }

customize.registerEngine(id, engine)

Register an engine

Kind: instance method of Customize
Access: public

Param Type Description
id string the identifier of the engine. This identifier is also used within the config as key within the configuration object to identify the sub-configuration stored for this engine.
engine object a customize engine that is registered
[engine.defaultConfig] object the default configuration of the engine
engine.preprocessConfig function a preprocessor to convert a merge-configuration to the internal format of the engine
engine.run function the execution function of the engine (the merged config is passed as parameter
engine.run function the execution function of the engine (the merged config is passed as parameter)
[engine.schema] object a JSON-schema to validate the merge-configurations against.

customize.configSchema()

Returns the JSON-schema that configuration objects must match for this configuration. The schema does not contain main description property

Kind: instance method of Customize

customize.merge(config) ⇒ Customize

Creates a new instance of Customize. The configuration values of the current Customize are used as default values and are overridden by the configuration provided as parameter.

Kind: instance method of Customize
Returns: Customize - the new Customize instance
Api: public

Param Type Description
config object configuration overriding the current configuration

customize.load(customizeModule) ⇒ Customize

Inherit configuration config from another module. a Customizer-module usually exports a function(Customize):Customize which in tern calls Customize.merge to create a new Customize instance. This function needs to be passed in here.

A new Customize will be returned that overrides the current configuration with the configuration of the module.

Kind: instance method of Customize
Returns: Customize - the Customize instance returned by the module
Access: public

Param Type Description
customizeModule function that receives a Customize as paramater and returns a Customize with changed configuration.

customize.buildConfig() ⇒ Promise.<object>

Return a promise for the merged configuration. This functions is only needed to inspect intermediate configuration results (i.e. for testing and documentation purposes)

Kind: instance method of Customize
Returns: Promise.<object> - a promise for the whole configuration
Access: public

customize.watched() ⇒ Promise.<object.<Array.<string>>>

Return a promise for the files needing to be watched in watch-mode, indexed by engine.

Kind: instance method of Customize
Returns: Promise.<object.<Array.<string>>> - a promise for the files to be watched.
Access: public

customize.run([options]) ⇒ Promise.<object>

Run each engine with its part of the config.

Kind: instance method of Customize
Returns: Promise.<object> - an object containing on property per registered engine (the key is the engine-id) containing the result of each engine
Access: public

Param Type Description
[options] object optional paramters
[options.onlyEngine] string the name of an engine if only a single engine should be executed

customize~customize() ⇒ Customize

Kind: inner method of customize
Api: public

IO/Helpers

Functions

readFiles(directoryPath, [options])Promise.<object.<string, Promise.<{path:string, contents:string}>>>

An overridable directory which resolves to the contents of all its files (recursively). Returns an undefined value if the directory path is undefined.

files(directoryPath, [options])Promise.<object.<string, Promise.<{path:string, contents:string}>>>

An overridable directory which resolves to the contents of all its files (recursively). Returns an undefined value if the directory path is undefined. The contents of each file is a UTF-8 encoded string.

readFiles(directoryPath, [options]) ⇒ Promise.<object.<string, Promise.<{path:string, contents:string}>>>

An overridable directory which resolves to the contents of all its files (recursively). Returns an undefined value if the directory path is undefined.

Kind: global function
Returns: Promise.<object.<string, Promise.<{path:string, contents:string}>>> - an object containing the relative file-path from the directoryPath as key and the file-path and the file-contents as value

Param Type Description
directoryPath string \ null \ undefined the path to the directory
[options] object
[options.glob] string an optional glob pattern for filtering files
[options.stream] boolean if set to true, the contents of a file will be a readable stream instead of the actual data.
[options.encoding] string the file is expected to be encoded. This means that the instead of a Buffer, a string is returned. If the 'stream' option is set, the stream's encoding will be set via readable.setEncoding(encoding)

files(directoryPath, [options]) ⇒ Promise.<object.<string, Promise.<{path:string, contents:string}>>>

Deprecated

An overridable directory which resolves to the contents of all its files (recursively). Returns an undefined value if the directory path is undefined. The contents of each file is a UTF-8 encoded string.

Kind: global function
Returns: Promise.<object.<string, Promise.<{path:string, contents:string}>>> - an object containing the relative file-path from the directoryPath as key and the file-path and the file-contents as value

Param Type Description
directoryPath string \ null \ undefined the path to the directory
[options] object
[options.glob] string an optional glob pattern for filtering files

License

customize is published under the MIT-license.

See LICENSE.md for details.

Release-Notes

For release notes, see CHANGELOG.md

Contributing guidelines

See CONTRIBUTING.md.

changelog

Change Log

All notable changes to this project will be documented in this file. See Conventional Commits for commit guidelines.

4.0.4 (2020-01-27)

Note: Version bump only for package customize

4.0.1 (2020-01-01)

Note: Version bump only for package customize

4.0.0 (2019-12-30)

Note: Version bump only for package customize

Change Log

This project adheres to Semantic Versioning.

Version 3.0.2 (Thu, 07 Nov 2019 20:13:48 GMT)

  • 1094e39 remove "11" from dependencies (why was it there?) - Nils Knappmeier

Version 3.0.1 (Thu, 07 Nov 2019 20:10:45 GMT)

  • d377070 Update dependencies, fix style with eslint - Nils Knappmeier
  • 0ea0c2c test: use --require=trace... is mocha.opts - Nils Knappmeier
  • 46f7fb3 chore: revert to mocha 5.x - Nils Knappmeier
  • b2a9297 chore: update package-lock.json - Nils Knappmeier
  • 714d265 chore: bump dependencies - Nils Knappmeier
  • d1eb6b1 chore: switch from istanbul to nyc - Nils Knappmeier
  • 5d4c639 chore: fix travis config for node lts - Nils Knappmeier

Version 3.0.0 (Thu, 14 Feb 2019 21:28:03 GMT)

  • 29b9dc9 chore: drop support for Node pre LTS - Nils Knappmeier
  • 629e43b chore: bump dependency-versions and node 10 - Nils Knappmeier
  • 488d9d8 chore(package): update mocha to version 4.0.0 - greenkeeper[bot]
  • ef35f88 Remove obsolete comment - Nils Knappmeier
  • 8b7298a Remove "preversion"-script as "thought" is now a devDependency - Nils Knappmeier
  • 67a0fae Remove "prethought"-script from package.json - Nils Knappmeier

Version 2.0.1 (Sat, 08 Apr 2017 21:27:05 GMT)

  • 0eea4eb Fix files-helpers for directories with subdirectories - Nils Knappmeier

Version 2.0.0 (Sat, 08 Apr 2017 20:58:34 GMT)

Breaking changes:

  • Support for node version below 6 has been dropped.

Other changes:

  • 09331ce Update documentation - Nils Knappmeier
  • 69cbd21 Remove obsolete precommit-hook (and husky) - Nils Knappmeier
  • 7f7e1d4 Fix tests, remove dependency on "m-io" in favour of "glob" - Nils Knappmeier
  • 94a8038 Remove dependency on "q" (now completely) - Nils Knappmeier
  • 631ec00 Remove obsolete "trace" from dev-dependencies - Nils Knappmeier
  • 2f91605 Customize now uses .mergeWith, not .merge - Nils Knappmeier
  • a5c5e13 docs(readme): add Greenkeeper badge - greenkeeper[bot]
  • 18c137a chore(package): update dependencies - greenkeeper[bot]

Version 2.0.0-alpha1 (Fri, 24 Mar 2017 15:36:34 GMT)

  • 56c95c0 BREAKING: Remove support for js-quantities in configuration schemas - Nils Knappmeier
  • 3042778 Remove lodash dependency - Nils Knappmeier

Version 1.1.0 (Thu, 22 Dec 2016 21:43:36 GMT)

  • 6761bff IO-Helper "readFiles" for returning the contents of files as stream or Buffer (#1) - Nils Knappmeier
  • 0144f70 Add missing dependency to "m-io" - Nils Knappmeier

Version 1.0.1 (Tue, 20 Dec 2016 00:29:30 GMT)

  • d99a2fe Update README - Nils Knappmeier

Version 1.0.0 (Tue, 20 Dec 2016 00:23:05 GMT)

  • 9d460b4 Use m-io/fs instead of custom "listTree"-implementation - Nils Knappmeier
  • 01af3e9 Use "trace-and-clarify-if-possible" when running tests - Nils Knappmeier
  • 4773b02 Use current version of deep-aplus instead of q-deep - Nils Knappmeier
  • 753fad6 Remove the dependency to q-io and use custom implementations of the used functions. - Nils Knappmeier
  • e987d49 Use q-io@2 to exclude wrongly implemented "Array.prototype.find"-method - Nils Knappmeier

Version 0.8.4 (Wed, 16 Mar 2016 21:49:32 GMT)

  • 92c2ab0 Remove unnecessary debug output - Nils Knappmeier

Version 0.8.3 (Tue, 15 Mar 2016 13:46:57 GMT)

  • 2b04495 Adjust travis-configuration - Nils Knappmeier

Version 0.8.2 (Tue, 15 Mar 2016 08:42:44 GMT)

  • 1a1f8ed Move package to bootprint-org on github, remove locked branch settings - Nils Knappmeier

Version 0.8.1 (Wed, 02 Mar 2016 18:49:17 GMT)

  • 8f7dcc3 Load the "trace"-module only for tests with node version >= 1 - Nils Knappmeier

Version 0.8.0 (Wed, 02 Mar 2016 16:40:54 GMT)

  • 5c87900 Added more unit tests to increase test-coverage - Nils Knappmeier
  • 6972f07 Remove method "watch" - Nils Knappmeier
  • 2580eb6 Better error messages, update jsdoc, less console-output - Nils Knappmeier
  • 0cdc00a customize.withParent() now handles an undefined-argument gracefully - Nils Knappmeier
  • d406762 Use thoughtful-release to enforce git-workflow - Nils Knappmeier
  • 2bef8b4 Typo in JsDoc-Comment - Nils Knappmeier

v0.7.0 - 2015-12-20

Add

  • Added a .configSchema() method that creates the JSON schema required for input configurations.

v0.6.1 - 2015-11-08

Fix

  • Exception-Handling for DEBUG=customize:state

v0.6.0 - 2015-10-22

Add

  • Include package.json of loaded modules in _metadata in configuration. (Can be access using the buildConfig()-method)

v0.5.0 - 2015-10-21

Change/Fix

  • The files-function of require('customize/helpers-io') now always returns paths in POSIX-style, that are separated by slashes and not backslashed (even on Windows) (see bootprint-swagger#42)

v0.4.4 - 2015-10-19

Fix

  • Fix files-property in package.json

v0.4.3 - 2015-10-19

Fix

  • Add files-property to package.json

v0.4.2 - 2015-10-18

Fix

  • Remove unused dependencies
  • Use mocha-chai as testing framework.

v0.4.1 - 2015-10-16

Fix

  • Only allow strings in list of watched files. Ignore undefined file-paths.

v0.4.0 - 2015-10-14

Add

  • The constructor of the Customize class can be accessed via require('customize').Customize
  • Schema-Validation of engine configurations

Change

  • The build()-method is now called buildConfig() to allow backward compatibility of the bootprint-package. The build()-method of bootprint has different semantics

v0.3.0 - 2015-10-05

Add

  • Necessary methods to add support for customize-watch

v0.2.3 - 2015-07-17

Change

  • If the input configuration of an engine is a Promise, it is resolved before passing it to the engine's preprocessor. Inner Promises are not resolved.

v0.2.2 - 2015-07-15

  • README-Updates in 0.2.1 and 0.2.2

v0.2.0 - 2015-07-14

Update

  • Promises created with files() no longer resolve to the file contents, but to an object { path: string, contents: string }
  • The overrider-function now resolves promises if either the original value or the overriding value is a promise (before, both needed to be promises). This allows merging of promises and values of the same type

Add

  • Engine preprocessors may return Promises or direct values. The result is coerced to a promise dynamically.

Devel

  • Internal renaming of RideOver (old name) to Customize.

v0.1.0 - 2015-06-29

Update

  • Engines are now expected to return an object { defaultConfig: object, preprocessConfig: function, run: function }
  • Expose I/O-helper functions as require('customize/helpers-io').
  • More rigid parameter validation in Customizer#registerEngines(id,engine)

v0.0.2 - 2015-06-28

Fix

  • Fix dependencies in package.json
  • Removed Handlebars-Engine (will be part of a separate package)

v0.0.1 - 2015-06-28

Initial version

  • Abstraction of the configuration-override functionality from bootprint. Using a promise based configuration