yaml-include
Version:
Custom YAML tag definitions to support modular YAML documents with js-yaml.
193 lines (165 loc) • 4.93 kB
JavaScript
;
var fs = require('fs');
var p = require('path');
var yaml = require('js-yaml');
var yamlinc = require('../index');
var recursiveReadSync = require('recursive-readdir-sync');
var merge = require('lodash.merge');
var debug = require('debug')('include-yaml:dir');
/**
* options:
* whitelist
* blacklist
* allowEmpty (default: false)
* recursive (default: true)
* lowerKeys (default: false)
* variableIndicatorPrefix (default: ~)
* ignoreIndicatorPrefix (default: _)
*/
function constructIncludedDirectory(data) {
var defaults, opt, files, splitDirPath, tmp, included,
src, ext, filename, relfilename, key, work, basepath, fullpath;
basepath = yamlinc.getBasePath();
work = {};
opt = {};
defaults = {
whitelist: [],
blacklist: [],
allowEmpty: false,
recursive: true,
extensions: ['.yaml', '.yml'],
lowerKeys: false,
variableIndicator: '~',
variablePrefix: '{',
variableSuffix: '}',
ignoreIndicator: '_',
ignoreTopLevelDir: true,
excludeTopLevelDirSeparator: false,
pathSeparator: '/'
};
if (Array.isArray(data[0])) {
files = data[0];
} else {
fullpath = p.join(basepath, data[0]);
debug('reading %s', fullpath);
files = recursiveReadSync(fullpath);
files = files.map(function(filepath) {
return filepath.replace(basepath + p.sep, '');
});
debug('files ', files);
}
// sort the by length of filepath
files.sort(function(a, b) {
return a.length - b.length;
});
if (!data[1]) {
data[1] = {};
}
merge(opt, defaults, data[1]);
debug('resolved options %j', opt);
var keepFile = function(filepath) {
ext = p.extname(filepath);
if (opt.extensions.indexOf(ext) === -1) {
debug('skipping disallowed file extension %s: %s', ext, filepath);
return false;
}
filename = p.basename(filepath, ext);
// check whitelist for filepath and file name
if (opt.whitelist.indexOf(filepath) !== -1 ||
opt.whitelist.indexOf(filename) !== -1 ||
opt.whitelist.indexOf(filename + ext) !== -1) {
debug('whitelisting %s', filepath);
return true;
}
// if ANY part of the path has an ignorePrefix,
// skip it
if (filepath.indexOf(p.sep + opt.ignoreIndicator) !== -1) {
debug('ignoring %s', filepath);
return false;
}
// check blacklist for filepath and file name
if (opt.blacklist.indexOf(filepath) !== -1 ||
opt.blacklist.indexOf(filename) !== -1 ||
opt.blacklist.indexOf(filename + ext) !== -1) {
debug('blacklisting %s', filepath);
return false;
}
// guess we're keeping it!
debug('keepFile: keeping ' + filepath);
return true;
};
files.forEach(function(filepath) {
debug('looking at %s', filepath);
if (!keepFile(filepath)) {
return;
}
splitDirPath = filepath.split(p.sep);
splitDirPath.pop();
// we generally don't want the top dir.
if (opt.ignoreTopLevelDir) {
splitDirPath.shift();
}
if (opt.lowerKeys) {
filename = filename.toLowerCase();
}
// parameterize key
if (opt.variableIndicator) {
splitDirPath.forEach(function(element, i, a) {
if (element.charAt(0) === opt.variableIndicator) {
a[i] = opt.variablePrefix + element.substr(1) + opt.variableSuffix;
}
});
}
// get the source at last
included = {};
src = fs.readFileSync(p.join(basepath, filepath), 'utf8');
if (src.length > 0) {
yamlinc.YAML_VISITED_FILES.push(filepath);
included = yaml.load(src, {
schema: yamlinc.YAML_INCLUDE_SCHEMA,
filename: filepath
});
}
tmp = {};
if (opt.recursive) {
key = opt.excludeTopLevelDirSeparator ? '' : opt.pathSeparator;
key += splitDirPath.join(opt.pathSeparator);
if (opt.allowEmpty) {
tmp[key] = {};
tmp[key][filename] = included;
} else {
if (Object.getOwnPropertyNames(included).length > 0) {
if (key.length > 0) {
tmp[key] = {};
tmp[key][filename] = included;
} else {
// this implements the opt.excludeTopLevelDirSeparator option when at the top level
tmp[filename] = included;
}
}
}
} else {
if (opt.allowEmpty) {
tmp[filename] = included;
} else {
if (Object.getOwnPropertyNames(included).length > 0) {
tmp[filename] = included;
}
}
}
work = merge(work, tmp);
});
return work;
}
/**
* Since this is a sequence type, `data` must be an array
*/
function resolveIncludedDirectory(data) {
return Array.isArray(data) && data.length > 0 && data.length < 3;
}
module.exports = new yaml.Type('tag:yaml.org,2002:inc/dir', {
kind: 'sequence',
resolve: resolveIncludedDirectory,
construct: constructIncludedDirectory,
instanceOf: Object
});