UNPKG

mdast

Version:

Markdown processor powered by plugins

313 lines (257 loc) 7.46 kB
/** * @author Titus Wormer * @copyright 2015 Titus Wormer * @license MIT * @module mdast:cli:configuration * @version 2.2.2 * @fileoverview Find mdast rc files. */ 'use strict'; /* eslint-env node */ /* * Dependencies. */ var fs = require('fs'); var path = require('path'); var debug = require('debug')('mdast:cli:configuration'); var home = require('user-home'); var findUp = require('vfile-find-up'); var extend = require('extend.js'); var defaults = require('../defaults'); /* * Constants. */ var RC_NAME = '.mdastrc'; var PLUGIN_KEY = 'plugins'; var PACKAGE_NAME = 'package'; var PACKAGE_EXTENSION = 'json'; var PACKAGE_FIELD = 'mdastConfig'; var PERSONAL_CONFIGURATION = home ? path.join(home, RC_NAME) : null; /* * Methods. */ var read = fs.readFileSync; var exists = fs.existsSync; var has = Object.prototype.hasOwnProperty; var concat = Array.prototype.concat; /* * Set-up. */ var base = { 'settings': {} }; extend(base.settings, defaults.parse); extend(base.settings, defaults.stringify); /** * Merge two configurations, `configuration` into * `target`. * * @example * var target = {}; * merge(target, current); * * @param {Object} target - Configuration to merge into. * @param {Object} configuration - Configuration to merge from. * @return {Object} - `target`. */ function merge(target, configuration) { var key; var value; var index; var length; var result; for (key in configuration) { if (has.call(configuration, key)) { value = configuration[key]; result = target[key]; if (key === PLUGIN_KEY) { if (!result) { result = {}; target[key] = result; } if ('length' in value) { index = -1; length = value.length; while (++index < length) { if (!(value[index] in result)) { result[value[index]] = null; } } } else { target[key] = merge(result || {}, value); } } else if (typeof value === 'object' && value !== null) { if ('length' in value) { target[key] = concat.apply(value); } else { target[key] = merge(result || {}, value); } } else if (value !== undefined) { target[key] = value; } } } return target; } /** * Parse a JSON configuration object from a file. * * @example * var rawConfig = load('package.json'); * * @throws {Error} - Throws when `filePath` is not found. * @param {string} filePath - File location. * @return {Object} - Parsed JSON. */ function load(filePath) { var configuration = {}; if (filePath) { try { configuration = JSON.parse(read(filePath, 'utf8')) || {}; } catch (exception) { exception.message = 'Cannot read configuration file: ' + filePath + '\n' + exception.message; throw exception; } } return configuration; } /** * Get personal configuration object from `~`. * * @example * var config = getUserConfiguration(); * * @return {Object} - Parsed JSON. */ function getUserConfiguration() { var configuration = {}; if (PERSONAL_CONFIGURATION && exists(PERSONAL_CONFIGURATION)) { configuration = load(PERSONAL_CONFIGURATION); } return configuration; } /** * Get a local configuration object, by walking from * `directory` upwards and mergin all configurations. * If no configuration was found by walking upwards, the * current user's config (at `~`) is used. * * @example * var configuration = new Configuration(); * var config = getLocalConfiguration(configuration, '~/bar'); * * @param {Configuration} context - Configuration object to use. * @param {string} directory - Location to search. * @param {Function} callback - Invoked with `files`. */ function getLocalConfiguration(context, directory, callback) { findUp.all([RC_NAME, PACKAGE_NAME], directory, function (err, files) { var configuration = {}; var index = files && files.length; var file; var local; var found; while (index--) { file = files[index]; local = load(file.filePath()); if ( file.filename === PACKAGE_NAME && file.extension === PACKAGE_EXTENSION ) { local = local[PACKAGE_FIELD] || {}; } found = true; debug('Using ' + file.filePath()); merge(configuration, local); } if (!found) { debug('Using personal configuration'); merge(configuration, getUserConfiguration()); } callback(err, configuration); }); } /** * Configuration. * * @example * var configuration = new Configuration(); * * @constructor * @class Configuration * @param {Object} options - Options to be passed in. */ function Configuration(options) { var self = this; var settings = options || {}; var file = settings.file; var cliConfiguration = {}; self.cache = {}; self.cwd = settings.cwd || process.cwd(); self.settings = settings.settings || {}; self.plugins = settings.plugins || {}; self.output = settings.output; self.detectRC = settings.detectRC; if (file) { debug('Using command line configuration `' + file + '`'); cliConfiguration = load(path.resolve(self.cwd, file)); } self.cliConfiguration = cliConfiguration; } /** * Defaults. * * @type {Object} - Default settings. */ Configuration.prototype.base = base; /** * Build a configuration object. * * @example * new Configuration().getConfiguration('~/foo', console.log); * * @param {string} filePath - File location. */ Configuration.prototype.getConfiguration = function (filePath, callback) { var self = this; var directory = filePath ? path.dirname(filePath) : self.cwd; var configuration = self.cache[directory]; debug('Constructing configuration for `' + (filePath || self.cwd) + '`'); /** * Handle (possible) local config result. * * @param {Error?} [err] - Loading error. * @param {Object?} [localConfiguration] - Configuration. */ function handleLocalConfiguration(err, localConfiguration) { if (localConfiguration) { merge(configuration, localConfiguration); } merge(configuration, self.cliConfiguration); merge(configuration, { 'settings': self.settings, 'plugins': self.plugins, 'output': self.output }); self.cache[directory] = configuration; callback(err, configuration); } if (configuration) { debug('Using configuration from cache'); callback(null, configuration); } else { configuration = {}; merge(configuration, self.base); if (!self.detectRC) { debug('Ignoring .rc files'); handleLocalConfiguration(); } else { getLocalConfiguration(self, directory, handleLocalConfiguration); } } }; /* * Expose. */ module.exports = Configuration;