json-to-scss
Version:
A small utility to convert js & json file(s) to scss/sass file(s).
286 lines (262 loc) • 13.1 kB
JavaScript
#!/usr/bin/env node
'use strict';
/**
* JSON-TO-SCSS Command Line Interface.
* @author Renaud Lapoële
*/
/**
* Module dependencies.
*/
const path = require('path');
const packageJson = require(path.join(__dirname, '../package.json'));
const args = require('yargs').argv;
const chalk = require('chalk');
const glob = require('glob');
const pathBasename = require('../lib/utils/path/basename');
const removePathExtension = require('../lib/utils/path/removeExtension');
const isString = require('../lib/utils/inspection/isString');
const jsJsonFilesToSassScssFiles = require('../lib/jsJsonFilesToSassScssFiles');
/**
* @function banner
* @param {string} name - the name of the package the banner is getting built for.
* @param {string} version - the version number of the package the banner is getting built for.
* @returns {string} this lib/package's name & version.
* @description Returns a string containing the name and the version of this lib/package.
*/
function banner(name, version) {
return `${chalk.bold(`${name || 'NO NAME'} v${version || '0.0.0'}`)}`;
}
/**
* @function usage
* @param {string} name - the name of the package usage instructions are generated for.
* @returns {string} the lib/package's usage text.
* @description Returns the usage description of this lib/package.
*/
function usage(name) {
return `
${chalk.bold('Usage')}: ${chalk.yellow(
name || 'NO NAME'
)} <source> [destination] [options]
${chalk.bold(
'source'
)}: the path to a javascript, json or group of files to be converted.
(required) - only '.js' and '.json' are processed.
${chalk.bold(
'destination'
)}: the full or partial destination of the converted files.
(optional) - when the destination is a directory path only, all generated
files are saved in it with a default '.scss' extension. If
a '.sass' extension is required instead, the --sass option must be included.
${chalk.bold('options')}:
--h (help) Show this message.
--p='prefix' (prefix) Prepend the converted sass/scss content with the prefix.
Prefix is usually used & set to be used as sass variable name.
Default '\${source-filename} :'.
--no-underscore (no leading _) Remove any leading '_' (underscore) characters from the
prefix when used as sass variable name.
--s='suffix' (suffix) Append the converted sass/scss content with the suffix.
Default: ';' (default not used if --sass)
--tt='tabText' (tab text) Text to be used to indent or tabulate sass map.
Default: ' ' (two space characters)
--tn=tabNumber (tab number) Number of tabulations.
Default: 1 (set to 0 if --sass)
--es='sq'||'dq' (empty string) Sass/scss representation for an empty string (single or double quote).
Default is '""': { "prop": "" } => $xyzfilename: ( prop: "" );
--sass (sass ext.) Use sass extension.
--mo (merge objects) Merge obtained sass strings into a single sass map/list.
Enabled only if destination contains a full file name (name + .ext)
--k='auto'|| (sass map keys) Sass/scss format for map keys.
'sq'||'dq' 'auto' (default): keys are formatted as per their converted type (number, ...)
'sq': all keys are single quoted.
'dq': all keys are doubled quoted.
--v='auto'|| (sass map val.) Sass/scss format for map values other than nested maps.
'sq'||'dq' 'auto' (default): values are formatted as per their converted type
'sq': all values are single quoted.
'dq': all values are doubled quoted.
Notes regarding 'sq' or 'dq' usage:
1- nested quote characters are automatically replaced by their counterpart.
{ "prop": 'Arial, "sans-serif"'} with 'dq' => ( prop: "Arial, 'sans-serif'" );
2- empty strings are formatted as per the given 'sq' or 'dq' option value regardless
of the --es option.
--fk (flatten keys) Flatten JSON/js object keys to produce series of sass/scss variables instead of a map.
Provided prefix and suffix, if any, are applied to each flatten key.
Key name elements (nested JSON object props) are dash separated (kebab-case).
In case of flatten key name conflict(s), the latest processed key value is used.
This option is not available in the js/JSON embed-able config.
--fkc='kebab'|| (flat. key case) Flattened key case.
'camel' 'kebab' (default): nested keys are dash separated. No letter case change.
'camel': top level keys are left as-is whereas nested keys are capitalized before
being concatenated. The nested key capitalization does not change the case of the
subsequent letters: 'hEllO' => 'HEllO'.
`;
}
/**
* @function hasArgs
* @param {object} args - command line arguments extracted via/from/with yargs.
* @returns {boolean} true if args has an "_" property and if this property has a length property different than 0.
* @description This is an internal small helper function to quickly assess if 'json-to-scss' is called without any params. Note that the code written here relies on the fact that we are using the 'yargs' package.
*/
function hasArgs(args) {
return '_' in args && args._.length;
}
/**
* @function extensionCorrector
* @param {string} defaultExtension - the destination file extension to be used by default.
* @param {string} requiredExtension - the destination file extension which must be used.
* @returns {Function} a function to be used as input for an Array.map(fn) function call.
* @description Internal helper function encapsulating the destination file extension transformations.
*/
function extensionCorrector(defaultExtension, requiredExtension) {
return filepath => {
function _correctFilepathExtension(extensionName) {
let _switch = {
'': () =>
`${filepath}${
'' !== requiredExtension ? requiredExtension : defaultExtension
}`,
'.scss': () =>
'' === requiredExtension || requiredExtension === extensionName
? filepath
: `${removePathExtension(filepath)}${requiredExtension}`,
'.sass': () =>
'' === requiredExtension || requiredExtension === extensionName
? filepath
: `${removePathExtension(filepath)}${requiredExtension}`,
default: () =>
`${removePathExtension(filepath)}${
'' !== requiredExtension ? requiredExtension : defaultExtension
}`
};
return (_switch[extensionName] || _switch['default'])();
}
return _correctFilepathExtension(path.extname(filepath).toLowerCase());
};
}
/**
* @function basenameExtractor
* @param {string} filepath - the file path from which we want to extract the basename.
* @returns {string} the file path basename.
* @description Internal helper & wrapper function extracting the file path's base name.
*/
function basenameExtractor(filepath) {
return pathBasename(filepath);
}
/**
* @function dirnameSetter
* @param {string} dirname - the directory name we want to use for our destination file paths.
* @returns {function} a function to be used as input for an Array.map(fn) function call.
* @description set the directory(ies) for the given destination file path.
*/
function dirnameSetter(dirname) {
return filepath => path.resolve(path.join(dirname, filepath));
}
/**
* @function normalizeArgs
* @param {object} args - command line program arguments (built by the yargs package) to be normalized.
* @returns {{source: {paths: *}, destination: {paths: (*|Array)}, options: {prefix: string | string, suffix: (*|string), emptyString: string, indentationText: (*|string), indentationSize: (*|number), noUnderscore: boolean, format: string}}}
* @description check & normalize the command line program arguments.
*/
function normalizeArgs(args) {
const _source = path.resolve(process.cwd(), `${args._[0]}`);
const _sourcePaths = glob.sync(_source);
const _defaultExtension = '.scss';
let _requiredExtension = 'sass' in args ? '.sass' : '';
const _destination =
args._.length > 1 ? path.resolve(process.cwd(), `${args._[1]}`) : '';
const _destinationExtname = path.extname(_destination).toLowerCase();
let _destinationPaths = [];
if ('' === _destination) {
_destinationPaths = _sourcePaths.map(
extensionCorrector(_defaultExtension, _requiredExtension)
);
} else {
if ('' !== _destinationExtname) {
if ('.sass' === _destinationExtname || '.scss' === _destinationExtname) {
_requiredExtension = _destinationExtname;
_destinationPaths = [_destination];
} else {
_destinationPaths = [removePathExtension(_destination)].map(
extensionCorrector(_defaultExtension, _requiredExtension)
);
}
} else {
_destinationPaths = _sourcePaths
.map(basenameExtractor)
.map(extensionCorrector(_defaultExtension, _requiredExtension))
.map(dirnameSetter(_destination));
}
}
const _mergeSourceFiles =
_sourcePaths.length > 1 && _sourcePaths.length !== _destinationPaths.length;
return {
source: {
paths: _sourcePaths
},
destination: {
paths: _destinationPaths
},
options: {
prefix: 'p' in args ? args.p : '',
suffix: 's' in args ? args.s : ';',
emptyString: 'es' in args && 'sq' === args.es ? "''" : '""',
indentationText: 'tt' in args ? args.tt : ' ',
indentationSize: 'tn' in args ? args.tn : 1,
noUnderscore: !('underscore' in args),
format:
'' === _requiredExtension ? _defaultExtension : _requiredExtension,
mergeSourceFiles: _mergeSourceFiles,
mergeSassObjects: _mergeSourceFiles && 'mo' in args,
keys: 'k' in args ? ['auto', 'sq', 'dq'].indexOf(args.k) > -1 ? args.k : 'auto' : 'auto',
values: 'v' in args ? ['auto', 'sq', 'dq'].indexOf(args.v) > -1 ? args.v : 'auto' : 'auto',
stringKeys: 'sk' in args && isString(args.sk) ? args.sk : 'family,font-family,fontfamily,font-stack,fontstack,font-face,fontface',
flattenKeys: 'fk' in args,
flattenedKeyCase: 'fkc' in args ? ['kebab', 'camel'].indexOf(args.fkc) > -1 ? args.fkc : 'kebab' : 'kebab'
}
};
}
/**
* The 'json-to-scss' main function in charge of parsing arguments and, if possible,
* executing the file conversion.
*/
function main() {
console.log(banner(packageJson.name, packageJson.version));
if (hasArgs(args)) {
if ('h' in args) {
console.log(usage(packageJson.name));
} else {
const _nargs = normalizeArgs(args);
if (_nargs.source.paths.length) {
jsJsonFilesToSassScssFiles(
_nargs.source.paths,
_nargs.destination.paths,
_nargs.options.prefix,
_nargs.options.suffix,
_nargs.options.format,
_nargs.options.indentationText,
_nargs.options.indentationSize,
_nargs.options.emptyString,
_nargs.options.noUnderscore,
_nargs.options.mergeSourceFiles,
_nargs.options.mergeSassObjects,
_nargs.options.keys,
_nargs.options.values,
_nargs.options.stringKeys,
_nargs.options.flattenKeys,
_nargs.options.flattenedKeyCase
);
} else {
console.log(
`Hmmm strange... ${chalk.red(
args._[0]
)} cannot be found. Could there be a small mistake in the source path?`
);
}
}
} else {
console.log(usage(packageJson.name));
}
}
/**
* Execute the main module function.
*/
main();