livingcss
Version:
Generate a style guide using comment driven content creation
223 lines (191 loc) • 6.21 kB
JavaScript
/*jshint -W083 */
var fs = require('fs');
var glob = require('glob');
var svgRegex = /url\("data:image\/svg\+xml;.*?"\)/gi;
var whiteSpaceRegex = /\s/g;
var utils = {};
/**
* Read a file and return a promise.
* @param {string} file - Path to the file
* @returns Promise
*/
utils._readFileWithPromise = function readFileWithPromise(file) {
return new Promise((resolve, reject) => {
fs.readFile(file, 'utf8', function(err, data) {
if (err) {
reject(err);
}
resolve(data);
});
});
}
/**
* Read a glob string and return a promise.
* @param {string} pattern - Glob file pattern
* @returns Promise
*/
function globWithPromsie(pattern) {
return new Promise((resolve, reject) => {
glob(pattern, function(err, files) {
if (err) {
reject(err);
}
if (files.length === 0) {
console.warn('pattern "' + pattern + '" does not match any file');
}
resolve(files);
});
});
}
/**
* Get an id by hyphen separating the name.
*
* @param {string} str - String to hyphenate.
* @returns {string}
*/
utils.getId = function getId(str) {
return encodeURIComponent(str.toLowerCase().replace(whiteSpaceRegex, '-'));
};
/**
* Normalize a name.
*
* @param {string} name - Name to normalize.
* @returns string
*/
utils.normalizeName = function normalizeName(name) {
return name.toLowerCase();
};
/**
* Generate the sort order of pages and sections.
*
* @param {object} context - Context to pass to handlebars.
* @param {string[]} [context.pageOrder=[]] - Order that the pages should be sorted by.
* @param {object[]} context.pages - List of pages.
* @param {object[]} sortOrder - Array of objects of page name to array of section names.
*/
utils.generateSortOrder = function generateSortOrder(context, sortOrder) {
context.pageOrder = context.pageOrder || [];
// sortOrder can be:
// sort just pages: array of page names
// sort just sections: object of page names to array of section names
// sort both: array of objects of page names to array of section names
// sort some: array of objects and strings
if (!Array.isArray(sortOrder)) {
sortOrder = [sortOrder];
}
sortOrder.forEach(function(value, index) {
// string page name
if (typeof value === 'string') {
context.pageOrder.push(utils.normalizeName(value));
return;
}
if (Array.isArray(value)) {
value.forEach(function(name) {
context.pageOrder.push(utils.normalizeName(name));
});
return;
}
// object of string page name to array of section names
for (var prop in value) {
if (!value.hasOwnProperty(prop)) {
continue;
}
context.pageOrder.push(utils.normalizeName(prop));
if (context.pages[prop]) {
context.pages[prop].sectionOrder = context.pages[prop].sectionOrder || [];
// sections
value[prop].forEach(function(val) {
context.pages[prop].sectionOrder.push(utils.normalizeName(val));
});
}
}
});
};
/**
* Sort a list of categories (pages, sections) by a given array.
*
* @param {object[]} categories - List of categories.
* @param {object} sortOrder - List of category names in the order they should be sorted. Any category not listed will be added to the end in the order encountered.
*/
utils.sortCategoryBy = function sortCategoryBy(categories, sortOrder) {
categories.sort(function(a, b) {
var aIndex = sortOrder.indexOf(utils.normalizeName(a.name));
var bIndex = sortOrder.indexOf(utils.normalizeName(b.name));
// default categories not in the section order to the bottom of the stack
if (aIndex === -1) {
return 1;
}
else if (bIndex === -1) {
return -1;
}
return aIndex - bIndex;
});
};
/**
* Asynchronously read a list of glob pattern files and pass the list of files to
* be read. Maintains file order.
*
* @param {string[]} patterns - List of glob pattern files.
* @param {function} callback - Callback to execute for each read file.
* @returns {Promise}
*/
utils.readFileGlobs = async function readFileGlobs(patterns, callback) {
patterns = (!Array.isArray(patterns) ? [patterns] : patterns);
let globPromises = await patterns.map(async (pattern) => {
return globWithPromsie(pattern);
});
return Promise.all(globPromises)
.then(async (fileList) => {
// fileList is an array of arrays of files
// e.g. [ [fileOne, fileTwo], [fileThree, fileFour], ...]
let readFilePromises = await fileList.map(async (files) => {
return await utils.readFiles(files)
});
return Promise.all(readFilePromises);
})
.then(readFileList => {
// readFileList is an array of arrays of file data
// e.g. [ [ ['contents fileOne', fileOne], ['contents fileTwo', fileTow] ] ]
readFileList.forEach(readFiles => {
readFiles.forEach(fileData => {
callback.apply(null, fileData);
});
});
});
};
/**
* Asynchronously read a list of files and call the callback function for each of
* them. Maintains file order.
*
* @param {string[]} patterns - List of glob pattern files.
* @param {function} callback - Callback to execute for each read file.
* @returns {Promise}
*/
utils.readFiles = async function readFiles(files, callback) {
if (!files || (Array.isArray(files) && !files.length)) return Promise.resolve();
files = (!Array.isArray(files) ? [files] : files);
let promises = await Promise.all(files.map(async (file) => {
let data = await utils._readFileWithPromise(file);
return [data, file];
}));
return Promise.all(promises).then(files => {
if (callback) {
files.forEach(function(data) {
callback.apply(null, data);
});
}
return files;
})
}
/**
* Workaround for polymer issue: https://github.com/Polymer/polymer/issues/1276.
*
* @param {string} stylesheetData - Actual stylesheet text
* @returns {string} Corrected stylesheet text
*/
utils.fixSVGIssue = function fixSVGIssue(stylesheetData) {
return stylesheetData.replace(svgRegex, function replacer(match) {
return match.replace(/'/g, '%27');
});
};
module.exports = utils;