diff --git a/lib/helpers.js b/lib/helpers.js
index 815003c..ad4cbce 100644
--- a/lib/helpers.js
+++ b/lib/helpers.js
@@ -16,6 +16,9 @@ function isObject(o) {
function isString(s) {
return typeof s === 'string'
}
+function isUndefined(u) {
+ return typeof u === 'undefined'
+}
/**
* Recursively remove a directory
@@ -52,6 +55,7 @@ const helpers = {
isNumber,
isString,
isObject,
+ isUndefined,
rm
}
diff --git a/lib/index.js b/lib/index.js
index bee6c5a..71d945d 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -5,7 +5,7 @@ const matter = require('gray-matter')
const Mode = require('stat-mode')
const path = require('path')
let readdir = require('recursive-readdir')
-const { rm, isString, isBoolean, isObject, isNumber } = require('./helpers')
+const { rm, isString, isBoolean, isObject, isNumber, isUndefined } = require('./helpers')
const thunkify = require('thunkify')
const unyield = require('unyield')
const utf8 = require('is-utf8')
@@ -13,9 +13,69 @@ const Ware = require('ware')
const match = require('micromatch')
/**
- * Thunks.
+ * @typedef {Object.} Files
+ */
+
+/**
+ * Metalsmith file. Defines `mode`, `stats` and `contents` properties by default, but may be altered by plugins
+ *
+ * @typedef File
+ * @property {Buffer} contents - A NodeJS [buffer](https://nodejs.org/api/buffer.html)
+ * @property {import('fs').Stats} stats - A NodeJS [fs.Stats object](https://nodejs.org/api/fs.html#fs_class_fs_stats)
+ * @property {String} mode - Octal permission mode, see https://en.wikipedia.org/wiki/File-system_permissions#Numeric_notation
+ */
+
+/**
+ * A callback to run when the Metalsmith build is done
+ *
+ * @callback BuildCallback
+ * @param {?Error} error
+ * @param {Object.} files
+ * @this {Metalsmith}
+ *
+ * @example
+ * function onBuildEnd(error, files) {
+ * if (error) throw error
+ * console.log('Build success')
+ * }
+ */
+
+/**
+ * A callback to indicate that a plugin's work is done
+ *
+ * @callback DoneCallback
+ * @param {?Error} [error]
+ *
+ * @example
+ * function plugin(files, metalsmith, done) {
+ * // ..do stuff
+ * done()
+ * }
+ */
+
+/**
+ * Metalsmith plugin
+ *
+ * @callback Plugin
+ * @param {Object.} files
+ * @param {Metalsmith} metalsmith
+ * @param {DoneCallback} done
+ *
+ * @example
+ * function drafts(files, metalsmith) {
+ * Object.keys(files).forEach(path => {
+ * if (files[path].draft) {
+ * delete files[path]
+ * }
+ * })
+ * }
+ *
+ * metalsmith.use(drafts)
*/
+/**
+ * Thunks.
+ */
readdir = thunkify(readdir)
/**
@@ -27,7 +87,11 @@ module.exports = Metalsmith
/**
* Initialize a new `Metalsmith` builder with a working `directory`.
*
+ * @callback Metalsmith
* @param {String} directory
+ * @property {Plugin[]} plugins
+ * @property {String[]} ignores
+ * @return {Metalsmith}
*/
function Metalsmith(directory) {
@@ -46,9 +110,13 @@ function Metalsmith(directory) {
/**
* Add a `plugin` function to the stack.
- *
- * @param {Function or Array} plugin
+ * @param {Plugin} plugin
* @return {Metalsmith}
+ *
+ * @example
+ * metalsmith
+ * .use(drafts()) // use the drafts plugin
+ * .use(markdown()) // use the markdown plugin
*/
Metalsmith.prototype.use = function (plugin) {
@@ -59,8 +127,13 @@ Metalsmith.prototype.use = function (plugin) {
/**
* Get or set the working `directory`.
*
- * @param {Object} directory
- * @return {Object or Metalsmith}
+ * @param {Object} [directory]
+ * @return {String|Metalsmith}
+ *
+ * @example
+ * new Metalsmith('.') // set the path of the working directory through the constructor
+ * metalsmith.directory() // returns '.'
+ * metalsmith.directory('./other/path') // set the path of the working directory
*/
Metalsmith.prototype.directory = function (directory) {
@@ -71,14 +144,18 @@ Metalsmith.prototype.directory = function (directory) {
}
/**
- * Get or set the global `metadata` to pass to templates.
+ * Get or set the global `metadata`.
+ *
+ * @param {Object} [metadata]
+ * @return {Object|Metalsmith}
*
- * @param {Object} metadata
- * @return {Object or Metalsmith}
+ * @example
+ * metalsmith.metadata({ sitename: 'My blog' }); // set metadata
+ * metalsmith.metadata() // returns { sitename: 'My blog' }
*/
Metalsmith.prototype.metadata = function (metadata) {
- if (!arguments.length) return this._metadata
+ if (isUndefined(metadata)) return this._metadata
assert(isObject(metadata), 'You must pass a metadata object.')
this._metadata = clone(metadata)
return this
@@ -87,12 +164,16 @@ Metalsmith.prototype.metadata = function (metadata) {
/**
* Get or set the source directory.
*
- * @param {String} path
- * @return {String or Metalsmith}
+ * @param {String} [path]
+ * @return {String|Metalsmith}
+ *
+ * @example
+ * metalsmith.source('./src'); // set source directory
+ * metalsmith.source() // returns './src'
*/
Metalsmith.prototype.source = function (path) {
- if (!arguments.length) return this.path(this._source)
+ if (isUndefined(path)) return this.path(this._source)
assert(isString(path), 'You must pass a source path string.')
this._source = path
return this
@@ -101,8 +182,12 @@ Metalsmith.prototype.source = function (path) {
/**
* Get or set the destination directory.
*
- * @param {String} path
- * @return {String or Metalsmith}
+ * @param {String} [path]
+ * @return {String|Metalsmith}
+ *
+ * @example
+ * metalsmith.destination('build'); // set destination
+ * metalsmith.destination() // returns 'build'
*/
Metalsmith.prototype.destination = function (path) {
@@ -115,12 +200,16 @@ Metalsmith.prototype.destination = function (path) {
/**
* Get or set the maximum number of files to open at once.
*
- * @param {Number} max
- * @return {Number or Metalsmith}
+ * @param {Number} [max]
+ * @returns {Number|Metalsmith}
+ *
+ * @example
+ * metalsmith.concurrency(20) // set concurrency to max 20
+ * metalsmith.concurrency() // returns 20
*/
Metalsmith.prototype.concurrency = function (max) {
- if (!arguments.length) return this._concurrency
+ if (isUndefined(max)) return this._concurrency
assert(isNumber(max), 'You must pass a number for concurrency.')
this._concurrency = max
return this
@@ -129,11 +218,15 @@ Metalsmith.prototype.concurrency = function (max) {
/**
* Get or set whether the destination directory will be removed before writing.
*
- * @param {Boolean} clean
- * @return {Boolean or Metalsmith}
+ * @param {Boolean} [clean]
+ * @return {Boolean|Metalsmith}
+ *
+ * @example
+ * metalsmith.clean(true) // clean the destination directory
+ * metalsmith.clean() // returns true
*/
Metalsmith.prototype.clean = function (clean) {
- if (!arguments.length) return this._clean
+ if (isUndefined(clean)) return this._clean
assert(isBoolean(clean), 'You must pass a boolean.')
this._clean = clean
return this
@@ -142,12 +235,16 @@ Metalsmith.prototype.clean = function (clean) {
/**
* Optionally turn off frontmatter parsing.
*
- * @param {Boolean|Object} frontmatter
- * @return {Boolean or Metalsmith}
+ * @param {Boolean} [frontmatter]
+ * @return {Boolean|Metalsmith}
+ *
+ * @example
+ * metalsmith.frontmatter(false) // turn off front-matter parsing
+ * metalsmith.frontmatter() // returns false
*/
Metalsmith.prototype.frontmatter = function (frontmatter) {
- if (!arguments.length) return this._frontmatter
+ if (isUndefined(frontmatter)) return this._frontmatter
assert(
isBoolean(frontmatter) || isObject(frontmatter),
'You must pass a boolean or a gray-matter options object: https://github.com/jonschlinkert/gray-matter/tree/4.0.2#options'
@@ -155,44 +252,52 @@ Metalsmith.prototype.frontmatter = function (frontmatter) {
this._frontmatter = frontmatter
return this
}
+
/**
- * Add a file or files to the list of ignores.
+ * Get or set the list of filepaths or glob patterns to ignore
*
- * @param {String or Strings} The names of files or directories to ignore.
- * @return {Metalsmith}
+ * @method Metalsmith#ignore
+ * @param {String|String[]} [files] - The names or glob patterns of files or directories to ignore.
+ * @return {Metalsmith|String[]}
+ *
+ * @example
+ * metalsmith.ignore()
+ * metalsmith.ignore('layouts') // ignore the layouts directory
+ * metalsmith.ignore(['.*', 'data.json']) // ignore dot files & a data file
*/
Metalsmith.prototype.ignore = function (files) {
- if (!arguments.length) return this.ignores.slice()
+ if (isUndefined(files)) return this.ignores.slice()
this.ignores = this.ignores.concat(files)
return this
}
/**
- * Resolve `paths` relative to the root directory.
+ * Resolve `paths` relative to the metalsmith `directory`.
*
- * @param {String} paths...
+ * @param {...string} paths
* @return {String}
+ *
+ * @example
+ * metalsmith.path('./path','to/file.ext')
*/
-Metalsmith.prototype.path = function () {
- const paths = [].slice.call(arguments)
- paths.unshift(this.directory())
- return path.resolve.apply(path, paths)
+Metalsmith.prototype.path = function (...paths) {
+ return path.resolve.apply(path, [this.directory(), ...paths])
}
/**
* Match filepaths in the source directory by [glob](https://en.wikipedia.org/wiki/Glob_(programming)) pattern.
- * Use the third arg `input` to match against a subset of strings/ filepaths
+ * If `input` is not specified, patterns are matched against `Object.keys(files)`
*
* @param {String|String[]} patterns - one or more glob patterns
- * @param {import('micromatch').Options} options - [micromatch options](https://github.com/micromatch/micromatch#options)
- * @param {String[]} [input=Object.keys(files)] array of strings to match against
+ * @param {import('micromatch').Options} options - [micromatch options](https://github.com/micromatch/micromatch#options), except `format`
+ * @param {String[]} [input] array of strings to match against
* @returns {String[]} An array of matching file paths
*/
Metalsmith.prototype.match = function (patterns, options, input) {
input = input || Object.keys(this._files)
- if (!this._files) return []
+ if (!(input && input.length)) return []
options = Object.assign({ dot: true }, options || {}, {
// required to convert forward to backslashes on Windows and match the file keys properly
format: path.normalize
@@ -203,8 +308,16 @@ Metalsmith.prototype.match = function (patterns, options, input) {
/**
* Build with the current settings to the destination directory.
*
- * @param {Function} callback
- * @return {Promise}
+ * @param {BuildCallback} [callback]
+ * @return {Promise}
+ * @fulfills {Files}
+ * @rejects {Error}
+ *
+ * @example
+ * metalsmith.build(function(error, files) {
+ * if (error) throw error
+ * console.log('Build success!')
+ * })
*/
Metalsmith.prototype.build = function (callback) {
@@ -248,7 +361,16 @@ Metalsmith.prototype.build = function (callback) {
/**
* Process files through plugins without writing out files.
*
- * @return {Object}
+ * @method Metalsmith#process
+ * @param {BuildCallback} [callback]
+ * @return {Object.}
+ *
+ * @example
+ * metalsmith.process(err => {
+ * if (err) throw err
+ * console.log('Success')
+ * console.log(this.metadata())
+ * })
*/
Metalsmith.prototype.process = unyield(function* () {
@@ -260,8 +382,10 @@ Metalsmith.prototype.process = unyield(function* () {
/**
* Run a set of `files` through the plugins stack.
*
- * @param {Object} files
- * @param {Array} plugins
+ * @method Metalsmith#run
+ * @package
+ * @param {Object.} files
+ * @param {Plugin[]} plugins
* @return {Object}
*/
@@ -276,7 +400,9 @@ Metalsmith.prototype.run = unyield(function* (files, plugins) {
* Read a dictionary of files from a `dir`, parsing frontmatter. If no directory
* is provided, it will default to the source directory.
*
- * @param {String} dir (optional)
+ * @method Metalsmith#read
+ * @package
+ * @param {String} [dir]
* @return {Object}
*/
@@ -312,8 +438,10 @@ Metalsmith.prototype.read = unyield(function* (dir) {
* Read a `file` by path. If the path is not absolute, it will be resolved
* relative to the source directory.
*
+ * @method Metalsmith#readFile
+ * @package
* @param {String} file
- * @return {Object}
+ * @returns {File}
*/
Metalsmith.prototype.readFile = unyield(function* (file) {
@@ -361,8 +489,10 @@ Metalsmith.prototype.readFile = unyield(function* (file) {
* Write a dictionary of `files` to a destination `dir`. If no directory is
* provided, it will default to the destination directory.
*
- * @param {Object} files
- * @param {String} dir (optional)
+ * @method Metalsmith#write
+ * @package
+ * @param {Object.} files
+ * @param {String} [dir]
*/
Metalsmith.prototype.write = unyield(function* (files, dir) {
@@ -389,8 +519,10 @@ Metalsmith.prototype.write = unyield(function* (files, dir) {
* Write a `file` by path with `data`. If the path is not absolute, it will be
* resolved relative to the destination directory.
*
+ * @method Metalsmith#writeFile
+ * @package
* @param {String} file
- * @param {Object} data
+ * @param {File} data
*/
Metalsmith.prototype.writeFile = unyield(function* (file, data) {
diff --git a/test/index.js b/test/index.js
index 316f81c..56652b0 100644
--- a/test/index.js
+++ b/test/index.js
@@ -285,8 +285,6 @@ describe('Metalsmith', function () {
})
})
- // this test is included because micromatch has an obscure option called "windows",
- // which is set to true by default on Windows. Maintains compat with how Metalsmith 2.x functions
it('should not transform backslashes to forward slashes in the returned matches', function (done) {
const m = Metalsmith(fixture('match'))
m.use(function windowsPaths(files) {