diff --git a/bin/kss-node b/bin/kss-node index fc0e8e0c..4bd36b5b 100755 --- a/bin/kss-node +++ b/bin/kss-node @@ -4,7 +4,7 @@ 'use strict'; -var cli = require('../lib/cli'); +const cli = require('../lib/cli'); cli({ stdin: process.stdin, diff --git a/generator/handlebars/helpers.js b/generator/handlebars/helpers.js index f04b2fa4..51d3defd 100644 --- a/generator/handlebars/helpers.js +++ b/generator/handlebars/helpers.js @@ -7,12 +7,7 @@ module.exports.register = function(handlebars, config) { * Outputs the current section's or modifier's markup. */ handlebars.registerHelper('markup', function() { - var options = arguments[arguments.length - 1], - partials = options.data.root.partials, - section, - template, - partial, - data; + let options = arguments[arguments.length - 1]; if (!this) { return options.inverse(''); @@ -20,7 +15,7 @@ module.exports.register = function(handlebars, config) { // Assume the current context is the section we want unless one is passed as // the first parameter of this helper. - section = (arguments.length > 1) ? arguments[0] : this; + let section = (arguments.length > 1) ? arguments[0] : this; // Verify we found a JSON representation of a KssSection object. if (!section.reference) { @@ -28,10 +23,10 @@ module.exports.register = function(handlebars, config) { } // Load the information about this section's markup partial. - partial = partials[section.reference]; + let partial = options.data.root.partials[section.reference]; // Copy the partial.data so we can modify it for this markup instance. - data = JSON.parse(JSON.stringify(partial.data)); + let data = JSON.parse(JSON.stringify(partial.data)); // Display the modifier_class hash (if given), or the modifier's className, // or the placeholder text if this section has modifiers. @@ -52,7 +47,7 @@ module.exports.register = function(handlebars, config) { /* eslint-enable camelcase */ // Compile the section's markup partial into a template. - template = handlebars.compile('{{> "' + partial.name + '"}}'); + let template = handlebars.compile('{{> "' + partial.name + '"}}'); // We don't wrap the rendered template in "new handlebars.SafeString()" since // we want the ability to display it as a code sample with {{ }} and as // rendered HTML with {{{ }}}. @@ -68,7 +63,7 @@ module.exports.register = function(handlebars, config) { handlebars.registerHelper('consoleLog', function() { if (arguments.length > 1) { // 'options' is automatically passed as the last argument, so skip it. - for (var i = 0; i < arguments.length - 1; i++) { + for (let i = 0; i < arguments.length - 1; i++) { console.log(arguments[i]); } } else { diff --git a/generator/handlebars/kss_handlebars_generator.js b/generator/handlebars/kss_handlebars_generator.js index f994ce03..325be327 100644 --- a/generator/handlebars/kss_handlebars_generator.js +++ b/generator/handlebars/kss_handlebars_generator.js @@ -7,13 +7,12 @@ * The `kss/generator/handlebars` module loads the kssHandlebarsGenerator * object, a `{@link KssGenerator}` object using Handlebars templating. * ``` - * var kssHandlebarsGenerator = require('kss/generator/handlebars'); + * const kssHandlebarsGenerator = require('kss/generator/handlebars'); * ``` * @module kss/generator/handlebars */ -var KssGenerator = require('../kss_generator.js'), - KssSection = require('../../lib/kss_section.js'), +const KssGenerator = require('../kss_generator.js'), fs = require('fs'), glob = require('glob'), marked = require('marked'), @@ -23,7 +22,7 @@ var KssGenerator = require('../kss_generator.js'), // Pass a string to KssGenerator() to tell the system which API version is // implemented by kssHandlebarsGenerator. -var kssHandlebarsGenerator = new KssGenerator('2.1', { +let kssHandlebarsGenerator = new KssGenerator('2.1', { 'helpers': { group: 'Style guide:', string: true, @@ -66,8 +65,6 @@ var kssHandlebarsGenerator = new KssGenerator('2.1', { * @returns {*} The callback's return value. */ kssHandlebarsGenerator.init = function(config, cb) { - var i, j, helper; - cb = cb || /* istanbul ignore next */ function() {}; // Save the configuration parameters. @@ -116,14 +113,14 @@ kssHandlebarsGenerator.init = function(config, cb) { // Load Handlebars helpers. if (this.config.helpers.length > 0) { - for (i = 0; i < this.config.helpers.length; i++) { + for (let i = 0; i < this.config.helpers.length; i++) { if (fs.existsSync(this.config.helpers[i])) { // Load custom Handlebars helpers. - var helperFiles = fs.readdirSync(this.config.helpers[i]); + let helperFiles = fs.readdirSync(this.config.helpers[i]); - for (j = 0; j < helperFiles.length; j++) { + for (let j = 0; j < helperFiles.length; j++) { if (path.extname(helperFiles[j]) === '.js') { - helper = require(this.config.helpers[i] + '/' + helperFiles[j]); + let helper = require(this.config.helpers[i] + '/' + helperFiles[j]); if (typeof helper.register === 'function') { helper.register(this.Handlebars, this.config); } @@ -150,27 +147,19 @@ kssHandlebarsGenerator.generate = function(styleGuide, cb) { this.styleGuide = styleGuide; this.partials = {}; - var sections = this.styleGuide.sections(), - sectionCount = sections.length, - sectionRoots = [], - rootCount, - currentRoot, - childSections = [], - partial, - files = [], - newSection, - i, - key; + let sections = this.styleGuide.sections(), + sectionRoots = []; cb = cb || /* istanbul ignore next */ function() {}; if (this.config.verbose && this.styleGuide.meta.files) { - this.log(this.styleGuide.meta.files.map(function(file) { + this.log(this.styleGuide.meta.files.map(file => { return ' - ' + file; }).join('\n')); } // Return an error if no KSS sections are found in the source files. + let sectionCount = sections.length; if (sectionCount === 0) { return cb(Error('No KSS documentation discovered in source files.')); } @@ -179,10 +168,10 @@ kssHandlebarsGenerator.generate = function(styleGuide, cb) { this.log('...Determining section markup:'); } - for (i = 0; i < sectionCount; i += 1) { + for (let i = 0; i < sectionCount; i += 1) { // Register all the markup blocks as Handlebars partials. if (sections[i].markup()) { - partial = { + let partial = { name: sections[i].reference(), reference: sections[i].reference(), file: '', @@ -193,8 +182,8 @@ kssHandlebarsGenerator.generate = function(styleGuide, cb) { if (partial.markup.match(/^[^\n]+\.(html|hbs)$/)) { partial.file = partial.markup; partial.name = path.basename(partial.file, path.extname(partial.file)); - files = []; - for (key in this.config.source) { + let files = []; + for (let key in this.config.source) { if (!files.length) { files = glob.sync(this.config.source[key] + '/**/' + partial.file); } @@ -238,7 +227,7 @@ kssHandlebarsGenerator.generate = function(styleGuide, cb) { // Accumulate an array of section references for all sections at the root of // the style guide. - currentRoot = sections[i].reference().split(/(?:\.|\ \-\ )/)[0]; + let currentRoot = sections[i].reference().split(/(?:\.|\ \-\ )/)[0]; if (sectionRoots.indexOf(currentRoot) === -1) { sectionRoots.push(currentRoot); } @@ -246,24 +235,24 @@ kssHandlebarsGenerator.generate = function(styleGuide, cb) { // If a root element doesn't have an actual section, build one for it. // @TODO: Move this "fixing" into KssStyleGuide. - rootCount = sectionRoots.length; - key = false; - for (i = 0; i < rootCount; i += 1) { - currentRoot = this.styleGuide.sections(sectionRoots[i]); + let rootCount = sectionRoots.length; + let newSection = false; + for (let i = 0; i < rootCount; i += 1) { + let currentRoot = this.styleGuide.sections(sectionRoots[i]); if (currentRoot === false) { - key = sectionRoots[i]; - // @TODO: Add section via KssStyleGuide API. - newSection = new KssSection({ - header: key, - reference: key - }); - this.styleGuide.data.sections.push(newSection); - this.styleGuide.meta.referenceMap[newSection.reference()] = newSection; + // Add a section to the style guide. + newSection = true; + this.styleGuide + .autoInit(false) + .sections({ + header: sectionRoots[i], + reference: sectionRoots[i] + }); } } - // Re-sort the style guide if we added new sections. - if (key !== false) { - this.styleGuide.init(); + // Re-init the style guide if we added new sections. + if (newSection) { + this.styleGuide.autoInit(true); } if (this.config.verbose) { @@ -273,15 +262,14 @@ kssHandlebarsGenerator.generate = function(styleGuide, cb) { // Now, group all of the sections by their root // reference, and make a page for each. rootCount = sectionRoots.length; - for (i = 0; i < rootCount; i += 1) { - childSections = this.styleGuide.sections(sectionRoots[i] + '.*'); + for (let i = 0; i < rootCount; i += 1) { + let childSections = this.styleGuide.sections(sectionRoots[i] + '.*'); this.generatePage(sectionRoots[i], childSections); } // Generate the homepage. - childSections = []; - this.generatePage('styleGuide.homepage', childSections); + this.generatePage('styleGuide.homepage', []); cb(null); }; @@ -295,14 +283,10 @@ kssHandlebarsGenerator.generate = function(styleGuide, cb) { * variable. */ kssHandlebarsGenerator.createMenu = function(pageReference) { - var self = this, - menu, - toMenuItem; - // Helper function that converts a section to a menu item. - toMenuItem = function(section) { + const toMenuItem = function(section) { // @TODO: Add an option to "include" the specific properties returned. - var menuItem = section.toJSON(); + let menuItem = section.toJSON(); // Remove data we definitely won't need for the menu. delete menuItem.markup; @@ -319,23 +303,21 @@ kssHandlebarsGenerator.createMenu = function(pageReference) { }; // Retrieve all the root sections of the style guide. - menu = this.styleGuide.sections('x').map(function(rootSection) { - var menuItem = toMenuItem(rootSection); + return this.styleGuide.sections('x').map(rootSection => { + let menuItem = toMenuItem(rootSection); // Retrieve the child sections for each of the root sections. - menuItem.children = self.styleGuide.sections(rootSection.reference() + '.*').slice(1).map(toMenuItem); + menuItem.children = this.styleGuide.sections(rootSection.reference() + '.*').slice(1).map(toMenuItem); // Remove menu items that are deeper than the nav-depth config setting. - for (var i = 0; i < menuItem.children.length; i++) { - if (menuItem.children[i].depth > self.config['nav-depth']) { + for (let i = 0; i < menuItem.children.length; i++) { + if (menuItem.children[i].depth > this.config['nav-depth']) { delete menuItem.children[i]; } } return menuItem; }); - - return menu; }; /** @@ -346,12 +328,8 @@ kssHandlebarsGenerator.createMenu = function(pageReference) { * @param {Array} sections An array of KssSection objects. */ kssHandlebarsGenerator.generatePage = function(pageReference, sections) { - var filename = '', files, - homepageText = false, - rootSection, - styles = '', - scripts = '', - key; + let filename = '', + homepageText = false; if (pageReference === 'styleGuide.homepage') { filename = 'index.html'; @@ -359,10 +337,10 @@ kssHandlebarsGenerator.generatePage = function(pageReference, sections) { this.log(' - homepage'); } // Ensure homepageText is a non-false value. - for (key in this.config.source) { + for (let key in this.config.source) { if (!homepageText) { try { - files = glob.sync(this.config.source[key] + '/**/' + this.config.homepage); + let files = glob.sync(this.config.source[key] + '/**/' + this.config.homepage); if (files.length) { homepageText = ' ' + marked(fs.readFileSync(files[0], 'utf8')); } @@ -380,7 +358,7 @@ kssHandlebarsGenerator.generatePage = function(pageReference, sections) { } } } else { - rootSection = this.styleGuide.sections(pageReference); + let rootSection = this.styleGuide.sections(pageReference); filename = 'section-' + rootSection.referenceURI() + '.html'; if (this.config.verbose) { this.log( @@ -392,12 +370,14 @@ kssHandlebarsGenerator.generatePage = function(pageReference, sections) { } // Create the HTML to load the optional CSS and JS. - for (key in this.config.css) { + let styles = '', + scripts = ''; + for (let key in this.config.css) { if (this.config.css.hasOwnProperty(key)) { styles = styles + '\n'; } } - for (key in this.config.js) { + for (let key in this.config.js) { if (this.config.js.hasOwnProperty(key)) { scripts = scripts + '\n'; } @@ -406,9 +386,8 @@ kssHandlebarsGenerator.generatePage = function(pageReference, sections) { fs.writeFileSync(this.config.destination + '/' + filename, this.template({ pageReference: pageReference, - sections: sections.map(function(section) { - var context = section.toJSON(); - return context; + sections: sections.map(section => { + return section.toJSON(); }), menu: this.createMenu(pageReference), homepage: homepageText, diff --git a/generator/handlebars/template/template_config.js b/generator/handlebars/template/template_config.js index 8ba918bf..9ae6511e 100644 --- a/generator/handlebars/template/template_config.js +++ b/generator/handlebars/template/template_config.js @@ -8,9 +8,7 @@ // either be named index.js or have its name set in the "main" property of the // template's package.json. -var kssHandlebarsTemplate; - -module.exports = kssHandlebarsTemplate = {}; +const kssHandlebarsTemplate = {}; // Tell kss-node which generator this template uses. try { @@ -59,7 +57,7 @@ kssHandlebarsTemplate.generator.prepare = function(styleGuide, cb) { * @param {String} reference The reference to search for. */ this.Handlebars.registerHelper('section', function(reference, options) { - var section = options.data.root.styleGuide.sections(reference); + let section = options.data.root.styleGuide.sections(reference); return section ? options.fn(section.toJSON()) : options.inverse(''); }); @@ -74,21 +72,19 @@ kssHandlebarsTemplate.generator.prepare = function(styleGuide, cb) { * @param {Mixed} query The section query */ this.Handlebars.registerHelper('eachSection', function(query, options) { - var styleGuide = options.data.root.styleGuide, - buffer = '', - sections, - i, l; + let styleGuide = options.data.root.styleGuide; if (!query.match(/\bx\b|\*/g)) { query = query + '.*'; } - sections = styleGuide.sections(query); + let sections = styleGuide.sections(query); if (!sections) { return options.inverse(''); } - l = sections.length; - for (i = 0; i < l; i += 1) { + let l = sections.length; + let buffer = ''; + for (let i = 0; i < l; i += 1) { buffer += options.fn(sections[i].toJSON()); } @@ -98,3 +94,5 @@ kssHandlebarsTemplate.generator.prepare = function(styleGuide, cb) { return cb(null, styleGuide); }; + +module.exports = kssHandlebarsTemplate; diff --git a/generator/kss_example_generator.js b/generator/kss_example_generator.js index 6f4468e6..519cc462 100644 --- a/generator/kss_example_generator.js +++ b/generator/kss_example_generator.js @@ -4,14 +4,14 @@ * The `kss/generator/example` module loads the KssExampleGenerator * object, a `{@link KssGenerator}` object using no templating. * ``` - * var kssExampleGenerator = require('kss/generator/example'); + * const kssExampleGenerator = require('kss/generator/example'); * ``` * @module kss/generator/example */ // Import the KssGenerator object. We will use its API to scaffold our // generator. -var KssGenerator = require('kss/generator'), +const KssGenerator = require('kss/generator'), Kss = require('kss'), path = require('path'); @@ -21,7 +21,7 @@ var KssGenerator = require('kss/generator'), // additional functionality added by overriding the parent methods. // // See the docs for KssGenerator() for info about its parameters. -var kssExampleGenerator = new KssGenerator('2.1', { +const kssExampleGenerator = new KssGenerator('2.1', { 'example-option': { alias: 'u', string: true, diff --git a/generator/kss_generator.js b/generator/kss_generator.js index a9e3423a..3506668b 100644 --- a/generator/kss_generator.js +++ b/generator/kss_generator.js @@ -3,7 +3,7 @@ /** * The `kss/generator` module loads the {@link KssGenerator} class constructor. * ``` - * var KssGenerator = require('kss/generator'); + * const KssGenerator = require('kss/generator'); * ``` * @module kss/generator */ @@ -12,228 +12,230 @@ See kss_example_generator.js for how to implement a generator. ************************************************************** */ -var Kss = require('../lib/kss.js'), +const Kss = require('../lib/kss.js'), wrench = require('wrench'); -var KssGenerator; +class KssGenerator { + /** + * Create a KssGenerator object. + * + * This is the base object used by all kss-node generators. Implementations of + * KssGenerator MUST pass the version parameter. kss-node will use this to + * ensure that only compatible generators are used. + * + * ``` + * const KssGenerator = require('kss/generator'); + * const customGenerator = new KssGenerator('2.1'); + * ``` + * + * @constructor + * @alias KssGenerator + * @param {string} version The generator API version implemented. + * @param {object} options The Yargs-like options this generator has. + * See https://github.com/bcoe/yargs/blob/master/README.md#optionskey-opt + */ + constructor(version, options) { + if (!(this instanceof KssGenerator)) { + return new KssGenerator(); + } -/** - * Create a KssGenerator object. - * - * This is the base object used by all kss-node generators. Implementations of - * KssGenerator MUST pass the version parameter. kss-node will use this to - * ensure that only compatible generators are used. - * - * ``` - * var KssGenerator = require('kss/generator'); - * var customGenerator = new KssGenerator('2.1'); - * ``` - * - * @constructor - * @alias KssGenerator - * @param {string} version The generator API version implemented. - * @param {object} options The Yargs-like options this generator has. - * See https://github.com/bcoe/yargs/blob/master/README.md#optionskey-opt - */ -module.exports = KssGenerator = function(version, options) { - if (!(this instanceof KssGenerator)) { - return new KssGenerator(); - } + // Tell generators which generator API version is currently running. + this.API = '2.1'; - // Tell generators which generator API version is currently running. - this.API = '2.1'; + // Store the version of the generator API that the generator instance is + // expecting; we will verify this in checkGenerator(). + this.implementsAPI = typeof version === 'undefined' ? 'undefined' : version; - // Store the version of the generator API that the generator instance is - // expecting; we will verify this in checkGenerator(). - this.implementsAPI = typeof version === 'undefined' ? 'undefined' : version; + // Tell kss-node which Yargs-like options this generator has. + this.options = options || {}; - // Tell kss-node which Yargs-like options this generator has. - this.options = options || {}; + // The log function defaults to console.log. + this.setLogFunction(console.log); + } - // The log function defaults to console.log. - this.setLogFunction(console.log); -}; + /** + * Logs a message to be reported to the user. + * + * Since a generator can be used in places other than the console, using + * console.log() is inappropriate. The log() method should be used to pass + * messages to the KSS system so it can report them to the user. + * + * @param {string} message The message to log. + */ + log() { + this.logFunction.apply(null, arguments); + } -/** - * Logs a message to be reported to the user. - * - * Since a generator can be used in places other than the console, using - * console.log() is inappropriate. The log() method should be used to pass - * messages to the KSS system so it can report them to the user. - * - * @param {string} message The message to log. - */ -KssGenerator.prototype.log = function() { - this.logFunction.apply(null, arguments); -}; + /** + * The log() method logs a message for the user. This method allows the system + * to define the underlying function used by the log method to report the + * message to the user. The default log function is a wrapper around + * console.log(). + * + * @param {Function} logFunction Function to log a message to the user. + */ + setLogFunction(logFunction) { + this.logFunction = logFunction; + } -/** - * The log() method logs a message for the user. This method allows the system - * to define the underlying function used by the log method to report the - * message to the user. The default log function is a wrapper around - * console.log(). - * - * @param {Function} logFunction Function to log a message to the user. - */ -KssGenerator.prototype.setLogFunction = function(logFunction) { - this.logFunction = logFunction; -}; + /** + * Checks the generator configuration. + * + * An instance of KssGenerator MUST NOT override this method. A process + * controlling the generator should call this method to verify the + * specified generator has been configured correctly. + * + * @alias KssGenerator.prototype.checkGenerator + * @param {Function} cb Callback that will be given an Error as its first + * parameter, if one occurs. + * @returns {*} The callback's return value. + */ + checkGenerator(cb) { + let isCompatible = true, + version, + apiMajor, + apiMinor, + thisMajor, + thisMinor; + + if (!(this instanceof KssGenerator)) { + return cb(new Error('The loaded generator is not a KssGenerator object.')); + } -/** - * Checks the generator configuration. - * - * An instance of KssGenerator MUST NOT override this method. A process - * controlling the generator should call this method to verify the - * specified generator has been configured correctly. - * - * @alias KssGenerator.prototype.checkGenerator - * @param {Function} cb Callback that will be given an Error as its first - * parameter, if one occurs. - * @returns {*} The callback's return value. - */ -KssGenerator.prototype.checkGenerator = function(cb) { - var isCompatible = true, - version, - apiMajor, - apiMinor, - thisMajor, - thisMinor; - - if (!(this instanceof KssGenerator)) { - return cb(new Error('The loaded generator is not a KssGenerator object.')); - } + if (this.implementsAPI === 'undefined') { + isCompatible = false; + } else { + version = this.API.split('.'); + apiMajor = parseInt(version[0]); + apiMinor = parseInt(version[1]); - if (this.implementsAPI === 'undefined') { - isCompatible = false; - } else { - version = this.API.split('.'); - apiMajor = parseInt(version[0]); - apiMinor = parseInt(version[1]); + version = this.implementsAPI.split('.'); + thisMajor = parseInt(version[0]); + thisMinor = parseInt(version[1]); - version = this.implementsAPI.split('.'); - thisMajor = parseInt(version[0]); - thisMinor = parseInt(version[1]); + if (thisMajor !== apiMajor || thisMinor > apiMinor) { + isCompatible = false; + } + } - if (thisMajor !== apiMajor || thisMinor > apiMinor) { - isCompatible = false; + if (!isCompatible) { + return cb(new Error('kss-node expected the template\'s generator to implement KssGenerator API version ' + this.API + '; version "' + this.implementsAPI + '" is being used instead.')); } - } - if (!isCompatible) { - return cb(new Error('kss-node expected the template\'s generator to implement KssGenerator API version ' + this.API + '; version "' + this.implementsAPI + '" is being used instead.')); + return cb(null); } - return cb(null); -}; - -/** - * Clone a template's files. - * - * This method is fairly simple; it copies one directory to the specified - * location. An instance of KssGenerator does not need to override this method, - * but it can if it needs to do something more complicated. - * - * @alias KssGenerator.prototype.clone - * @param {string} templatePath Path to the template to clone. - * @param {string} destinationPath Path to the destination of the newly cloned - * template. - * @param {Function} cb Callback that will be given an Error as its first - * parameter, if one occurs. - * @returns {*} The callback's return value. - */ -KssGenerator.prototype.clone = function(templatePath, destinationPath, cb) { - return wrench.copyDirRecursive( - templatePath, - destinationPath, - { - forceDelete: false, - excludeHiddenUnix: true - }, - function(error) { - if (error) { - // istanbul ignore else - if (error.message === 'You are trying to delete a directory that already exists. Specify forceDelete in an options object to override this.') { - error = new Error('This folder already exists: ' + destinationPath); + /** + * Clone a template's files. + * + * This method is fairly simple; it copies one directory to the specified + * location. An instance of KssGenerator does not need to override this method, + * but it can if it needs to do something more complicated. + * + * @alias KssGenerator.prototype.clone + * @param {string} templatePath Path to the template to clone. + * @param {string} destinationPath Path to the destination of the newly cloned + * template. + * @param {Function} cb Callback that will be given an Error as its first + * parameter, if one occurs. + * @returns {*} The callback's return value. + */ + clone(templatePath, destinationPath, cb) { + return wrench.copyDirRecursive( + templatePath, + destinationPath, + { + forceDelete: false, + excludeHiddenUnix: true + }, + function(error) { + if (error) { + // istanbul ignore else + if (error.message === 'You are trying to delete a directory that already exists. Specify forceDelete in an options object to override this.') { + error = new Error('This folder already exists: ' + destinationPath); + } + return cb(error); } - return cb(error); + return cb(null); } - return cb(null); - } - ); -}; + ); + } -/** - * Initialize the style guide creation process. - * - * This method is given a configuration JSON object with the details of the - * requested style guide generation. The generator can use this information for - * any necessary tasks before the KSS parsing of the source files. - * - * @alias KssGenerator.prototype.init - * @param {Object} config Configuration object for the requested generation. - * @param {Function} cb Callback that will be given an Error as its first - * parameter, if one occurs. - * @returns {*} The callback's return value. - */ -KssGenerator.prototype.init = function(config, cb) { - // At the very least, generators MUST save the configuration parameters. - this.config = config; + /** + * Initialize the style guide creation process. + * + * This method is given a configuration JSON object with the details of the + * requested style guide generation. The generator can use this information for + * any necessary tasks before the KSS parsing of the source files. + * + * @alias KssGenerator.prototype.init + * @param {Object} config Configuration object for the requested generation. + * @param {Function} cb Callback that will be given an Error as its first + * parameter, if one occurs. + * @returns {*} The callback's return value. + */ + init(config, cb) { + // At the very least, generators MUST save the configuration parameters. + this.config = config; + + return cb(null); + } - return cb(null); -}; + /** + * Parse the source files for KSS comments and create a KssStyleGuide object. + * + * When finished, it passes the completed KssStyleGuide to the given callback. + * + * @alias KssGenerator.prototype.parse + * @param {Function} cb Callback that will be given an Error as its first + * parameter, if one occurs, and a fully-populated + * KssStyleGuide as its second parameter. + * @returns {*} The callback's return value. + */ + parse(cb) { + if (this.config.verbose) { + this.log('...Parsing your style guide:'); + } -/** - * Parse the source files for KSS comments and create a KssStyleGuide object. - * - * When finished, it passes the completed KssStyleGuide to the given callback. - * - * @alias KssGenerator.prototype.parse - * @param {Function} cb Callback that will be given an Error as its first - * parameter, if one occurs, and a fully-populated - * KssStyleGuide as its second parameter. - * @returns {*} The callback's return value. - */ -KssGenerator.prototype.parse = function(cb) { - if (this.config.verbose) { - this.log('...Parsing your style guide:'); + // The default parse() method looks at the paths to the source folders and + // uses KSS' traverse method to load, read and parse the source files. Other + // generators may want to use KSS' parse method if they have already loaded + // the source files through some other mechanism. + return Kss.traverse(this.config.source, { + header: true, + markdown: true, + markup: true, + mask: this.config.mask, + custom: this.config.custom + }, cb); } - // The default parse() method looks at the paths to the source folders and - // uses KSS' traverse method to load, read and parse the source files. Other - // generators may want to use KSS' parse method if they have already loaded - // the source files through some other mechanism. - return Kss.traverse(this.config.source, { - header: true, - markdown: true, - markup: true, - mask: this.config.mask, - custom: this.config.custom - }, cb); -}; + /** + * Allow the template to prepare itself or modify the KssStyleGuide object. + * + * @alias KssGenerator.prototype.prepare + * @param {KssStyleGuide} styleGuide The KSS style guide in object format. + * @param {Function} cb Callback that will be given an Error as its first + * parameter, if one occurs, and a fully-populated + * KssStyleGuide as its second parameter. + * @returns {*} The callback's return value. + */ + prepare(styleGuide, cb) { + return cb(null, styleGuide); + } -/** - * Allow the template to prepare itself or modify the KssStyleGuide object. - * - * @alias KssGenerator.prototype.prepare - * @param {KssStyleGuide} styleGuide The KSS style guide in object format. - * @param {Function} cb Callback that will be given an Error as its first - * parameter, if one occurs, and a fully-populated - * KssStyleGuide as its second parameter. - * @returns {*} The callback's return value. - */ -KssGenerator.prototype.prepare = function(styleGuide, cb) { - return cb(null, styleGuide); -}; + /** + * Generate the HTML files of the style guide given a KssStyleGuide object. + * + * @alias KssGenerator.prototype.generate + * @param {KssStyleGuide} styleGuide The KSS style guide in object format. + * @param {Function} cb Callback that will be given an Error as its first + * parameter, if one occurs. + * @returns {*} The callback's return value. + */ + generate(styleGuide, cb) { + return cb(null); + } +} -/** - * Generate the HTML files of the style guide given a KssStyleGuide object. - * - * @alias KssGenerator.prototype.generate - * @param {KssStyleGuide} styleGuide The KSS style guide in object format. - * @param {Function} cb Callback that will be given an Error as its first - * parameter, if one occurs. - * @returns {*} The callback's return value. - */ -KssGenerator.prototype.generate = function(styleGuide, cb) { - return cb(null); -}; +module.exports = KssGenerator; diff --git a/lib/cli.js b/lib/cli.js index b14cd19b..54cb4b12 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -1,26 +1,17 @@ 'use strict'; -// var Promise = require('bluebird'), -var fs = require('fs-extra'), +const fs = require('fs-extra'), KssConfig = require('./kss_config.js'), path = require('path'), version = require('../package.json').version, yargs = require('yargs'); -var cli; +const cli = function(opts, done) { + const kssConfig = new KssConfig(); -cli = function(opts, done) { - var generator, - kssConfig = new KssConfig(), - logError, - positionalParams, - stdout, - stderr, - argv; - - stdout = opts.stdout; - stderr = opts.stderr; - argv = opts.argv || []; + let stdout = opts.stdout, + stderr = opts.stderr, + argv = opts.argv || []; // Add options only needed by the CLI or yargs. kssConfig.addOptions({ @@ -69,7 +60,7 @@ cli = function(opts, done) { } else { // Check if there are unnamed parameters. - positionalParams = kssConfig.get('_'); + let positionalParams = kssConfig.get('_'); if (positionalParams.length > 0) { // Check if the destination is the second unnamed parameter. if (positionalParams.length > 1) { @@ -85,19 +76,19 @@ cli = function(opts, done) { // Based on the template location specified in the kssConfig, load the // requested template's generator. - generator = kssConfig.loadGenerator(); + const generator = kssConfig.loadGenerator(); // Set the logging function of the generator. generator.setLogFunction(function() { - var message = ''; - for (var i = 0; i < arguments.length; i++) { + let message = ''; + for (let i = 0; i < arguments.length; i++) { message += arguments[i]; } stdout.write(message + '\n'); }); // Set up an error handling function. - logError = function(error) { + const logError = function(error) { // Show the full error stack if the verbose flag is used. if (kssConfig.get('verbose')) { stderr.write(error + '\n'); diff --git a/lib/kss.js b/lib/kss.js index c3230d46..8f5338fd 100644 --- a/lib/kss.js +++ b/lib/kss.js @@ -28,32 +28,37 @@ * @module kss */ -var cli = require('./cli.js'); +const cli = require('./cli.js'); /** * Generates a style guide given the proper options. * - * @param {object} options A collection of configuration options. + * @param {object} [options] A collection of configuration options. * @param {Function} done Callback function * @returns {*} null */ -var kss = function(options, done) { - var stdout, stderr, argv, - flag, values, i; +const kss = function(options, done) { + if (typeof options === 'function') { + done = options; + options = {}; + } + options = options || {}; + options.pipes = options.pipes || {}; + done = done || function() {}; // Allow options to provide an alternative to the process' stdout/stderr. - stdout = (options.pipes && options.pipes.stdout) ? options.pipes.stdout : process.stdout; - stderr = (options.pipes && options.pipes.stderr) ? options.pipes.stderr : process.stderr; + let stdout = options.pipes.stdout ? options.pipes.stdout : process.stdout; + let stderr = options.pipes.stderr ? options.pipes.stderr : process.stderr; // Create an argv-like Array from the options. - argv = ['node', 'bin/kss-node']; - for (flag in options) { + let argv = ['node', 'bin/kss-node']; + for (let flag in options) { if (options.hasOwnProperty(flag)) { - values = options[flag]; + let values = options[flag]; if (!Array.isArray(values)) { values = [values]; } - for (i = 0; i < values.length; i++) { + for (let i = 0; i < values.length; i++) { if (values[i] === null || typeof values[i] === 'boolean' || typeof values[i] === 'undefined' diff --git a/lib/kss_config.js b/lib/kss_config.js index 822746f2..d90fd7a2 100644 --- a/lib/kss_config.js +++ b/lib/kss_config.js @@ -2,8 +2,7 @@ /** * The `kss/lib/kss_config` module is normally accessed via the - * [`KssConfig()`]{@link module:kss.KssConfig} constructor of the `kss` - * module: + * [`KssConfig()`]{@link module:kss.KssConfig} class of the `kss` module: * ``` * const KssConfig = require('kss').KssConfig; * ``` @@ -11,10 +10,9 @@ * @module kss/lib/kss_config */ -var path = require('path'); +const path = require('path'); -var KssConfig, - coreOptions, +let coreOptions, resolveArray; /* eslint-disable key-spacing */ @@ -91,235 +89,233 @@ coreOptions = { /* eslint-enable key-spacing */ /** - * A KssConfig object stores configuration settings needed when generating a KSS - * style guide. + * Applies path.resolve() to the given path or array of paths. * - * It also validates the settings against the known set of options for the - * configured template and its generator. And makes it easy to load the template - * and its generator. + * Helper function for KssConfig.normalize(). * - * @constructor - * @alias module:kss.KssConfig - * @param {Object} config An object of config settings to store. + * @private + * @param {string} [from] Optional path to resolve the "to" path + * @param {string|array} to Relative path(s) to resolve. + * @returns {string|array} The absolute path(s). */ -KssConfig = function(config) { - if (!(this instanceof KssConfig)) { - return new KssConfig(); +resolveArray = function(from, to) { + let paths = []; + // "from" is optional. + if (typeof to === 'undefined') { + to = from; + from = ''; } - this.options = coreOptions; - this.config = {}; - if (config) { - this.set(config); + if (to instanceof Array) { + to.forEach((value, index) => { + paths[index] = path.resolve(from, value); + }); + } else { + paths = path.resolve(from, to); } + return paths; }; -/** - * Stores the given config settings. - * - * @param {Object} config An object of config settings to store. - */ -KssConfig.prototype.set = function(config) { - var key; - for (key in config) { - // istanbul ignore else - if (config.hasOwnProperty(key)) { - this.config[key] = config[key]; +class KssConfig { + /** + * A KssConfig object stores configuration settings needed when generating a KSS + * style guide. + * + * It also validates the settings against the known set of options for the + * configured template and its generator. And makes it easy to load the template + * and its generator. + * + * @constructor + * @alias module:kss.KssConfig + * @param {Object} [config] An object of config settings to store. + */ + constructor(config) { + if (!(this instanceof KssConfig)) { + return new KssConfig(); + } + this.options = coreOptions; + this.config = {}; + if (config) { + this.set(config); } } -}; - -/** - * Returns the requested configuration setting or, if no key is specified, an - * object containing all settings. - * - * @param {string} key Optional name of config setting to return. - * @returns {string|Object} The specified setting or an object of all settings. - */ -KssConfig.prototype.get = function(key) { - return key ? this.config[key] : this.config; -}; -/** - * Adds additional configuration options to the core kss-node options. - * - * Since kss-node is extendable, generators and templates can provide their own - * options for configuration. - * - * @param {object} options An object of configuration options. - */ -KssConfig.prototype.addOptions = function(options) { - var key; - for (key in options) { - // istanbul ignore else - if (options.hasOwnProperty(key)) { - this.options[key] = options[key]; + /** + * Stores the given config settings. + * + * @param {Object} config An object of config settings to store. + */ + set(config) { + for (let key in config) { + // istanbul ignore else + if (config.hasOwnProperty(key)) { + this.config[key] = config[key]; + } } } -}; -/** - * Adds the configuration settings from the given JSON file. - * - * @param {string} filename The path to the JSON file. - */ -KssConfig.prototype.loadJSON = function(filename) { - filename = path.resolve(filename); - var config = require(filename); + /** + * Returns the requested configuration setting or, if no key is specified, an + * object containing all settings. + * + * @param {string} key Optional name of config setting to return. + * @returns {string|Object} The specified setting or an object of all settings. + */ + get(key) { + return key ? this.config[key] : this.config; + } - this.set({ - // Save the full path to the config file. - config: filename, - // Store the list of config file keys for later. - configFileKeys: Object.keys(config) - }); - this.set(config); -}; + /** + * Adds additional configuration options to the core kss-node options. + * + * Since kss-node is extendable, generators and templates can provide their own + * options for configuration. + * + * @param {object} options An object of configuration options. + */ + addOptions(options) { + for (let key in options) { + // istanbul ignore else + if (options.hasOwnProperty(key)) { + this.options[key] = options[key]; + } + } + } -/** - * Normalizes the configuration object so that it is easy to use inside KSS. - * - * The options specified in the KssConfig object will determine how its - * configuration will be normalized. - * - * If an option object has a: - * - `multiple` property: if set to `false`, the corresponding configuration - * will be normalized to a single value. Otherwise, it will be normalized to - * an array of values. - * - `path` property: if set to `true`, the corresponding configuration will be - * normalized to a path, relative to the current working directory or, if - * given, the location of the JSON config file. - */ -KssConfig.prototype.normalize = function() { - var key; + /** + * Adds the configuration settings from the given JSON file. + * + * @param {string} filename The path to the JSON file. + */ + loadJSON(filename) { + filename = path.resolve(filename); + let config = require(filename); - // If some of the options were specified in a JSON config file, determine - // which ones by loading the keys from the config file. - if (typeof this.config.configFileKeys === 'undefined') { - if (this.config.config) { + this.set({ // Save the full path to the config file. - this.config.config = path.resolve(this.config.config); + config: filename, // Store the list of config file keys for later. - this.config.configFileKeys = Object.keys(require(this.config.config)); - } else { - this.config.configFileKeys = []; - } - } - - // Allow --clone to be used without a path. We can't specify this default path - // in coreOptions or the clone flag would always be "on". - if (this.config['clone'] === '') { - this.config['clone'] = 'custom-template'; + configFileKeys: Object.keys(config) + }); + this.set(config); } - // Normalize all the configuration settings. - for (key in this.config) { - if (typeof this.options[key] !== 'undefined') { - // "multiple" defaults to true. - if (typeof this.options[key].multiple === 'undefined') { - this.options[key].multiple = true; - } - // "path" defaults to false. - if (typeof this.options[key].path === 'undefined') { - this.options[key].path = false; + /** + * Normalizes the configuration object so that it is easy to use inside KSS. + * + * The options specified in the KssConfig object will determine how its + * configuration will be normalized. + * + * If an option object has a: + * - `multiple` property: if set to `false`, the corresponding configuration + * will be normalized to a single value. Otherwise, it will be normalized to + * an array of values. + * - `path` property: if set to `true`, the corresponding configuration will be + * normalized to a path, relative to the current working directory or, if + * given, the location of the JSON config file. + */ + normalize() { + // If some of the options were specified in a JSON config file, determine + // which ones by loading the keys from the config file. + if (typeof this.config.configFileKeys === 'undefined') { + if (this.config.config) { + // Save the full path to the config file. + this.config.config = path.resolve(this.config.config); + // Store the list of config file keys for later. + this.config.configFileKeys = Object.keys(require(this.config.config)); + } else { + this.config.configFileKeys = []; } - // If an option is specified multiple times, yargs will convert it into an - // array, but leave it as a string otherwise. This makes accessing the - // values of options inconsistent, so make all other options an array. - if (this.options[key].multiple) { - if (!(this.config[key] instanceof Array)) { - if (typeof this.config[key] === 'undefined') { - this.config[key] = []; - } else { - this.config[key] = [this.config[key]]; - } + } + + // Allow --clone to be used without a path. We can't specify this default path + // in coreOptions or the clone flag would always be "on". + if (this.config['clone'] === '') { + this.config['clone'] = 'custom-template'; + } + + // Normalize all the configuration settings. + for (let key in this.config) { + if (typeof this.options[key] !== 'undefined') { + // "multiple" defaults to true. + if (typeof this.options[key].multiple === 'undefined') { + this.options[key].multiple = true; } - } else { - // For options marked as "multiple: false", use the last value - // specified, ignoring the others. - if (this.config[key] instanceof Array) { - this.config[key] = this.config[key].pop(); + // "path" defaults to false. + if (typeof this.options[key].path === 'undefined') { + this.options[key].path = false; } - } - // Resolve any paths relative to the config file or to the working - // directory. - if (this.options[key].path && (typeof this.config[key] === 'string' || this.config[key] instanceof Array)) { - if (this.config.configFileKeys.indexOf(key) > -1) { - this.config[key] = resolveArray(path.dirname(this.config.config), this.config[key]); + // If an option is specified multiple times, yargs will convert it into an + // array, but leave it as a string otherwise. This makes accessing the + // values of options inconsistent, so make all other options an array. + if (this.options[key].multiple) { + if (!(this.config[key] instanceof Array)) { + if (typeof this.config[key] === 'undefined') { + this.config[key] = []; + } else { + this.config[key] = [this.config[key]]; + } + } } else { - this.config[key] = resolveArray(this.config[key]); + // For options marked as "multiple: false", use the last value + // specified, ignoring the others. + if (this.config[key] instanceof Array) { + this.config[key] = this.config[key].pop(); + } + } + // Resolve any paths relative to the config file or to the working + // directory. + if (this.options[key].path && (typeof this.config[key] === 'string' || this.config[key] instanceof Array)) { + if (this.config.configFileKeys.indexOf(key) > -1) { + this.config[key] = resolveArray(path.dirname(this.config.config), this.config[key]); + } else { + this.config[key] = resolveArray(this.config[key]); + } } } } } -}; - -/** - * Loads a generator object based on the previously-set configuration. - * - * @returns {object} The generator object - */ -KssConfig.prototype.loadGenerator = function() { - var template, - generator, - self = this; - // Clean up paths and massage options to expected types. - this.normalize(); + /** + * Loads a generator object based on the previously-set configuration. + * + * @returns {object} The generator object + */ + loadGenerator() { + let template, + generator, + self = this; - // Load the template's generator. - try { - template = require(this.config.template); - generator = template.generator; - } catch (e) { - // Templates don't have to load their own generator. If the template fails - // to load a generator, we assume it wanted the default generator. - generator = require('../generator/handlebars'); - } + // Clean up paths and massage options to expected types. + this.normalize(); - // Confirm this is a compatible generator. - return generator.checkGenerator(function(error) { - if (error) { - return error; + // Load the template's generator. + try { + template = require(this.config.template); + generator = template.generator; + } catch (e) { + // Templates don't have to load their own generator. If the template fails + // to load a generator, we assume it wanted the default generator. + generator = require('../generator/handlebars'); } - // Load the generator's and the template's CLI options. - // istanbul ignore else - if (Object.keys(generator.options).length) { - self.addOptions(generator.options); - } - if (template && template.options && Object.keys(template.options).length) { - self.addOptions(template.options); - } + // Confirm this is a compatible generator. + return generator.checkGenerator(function(error) { + if (error) { + return error; + } - return generator; - }); -}; + // Load the generator's and the template's CLI options. + // istanbul ignore else + if (Object.keys(generator.options).length) { + self.addOptions(generator.options); + } + if (template && template.options && Object.keys(template.options).length) { + self.addOptions(template.options); + } -/** - * Applies path.resolve() to the given path or array of paths. - * - * Helper function for KssConfig.normalize(). - * - * @private - * @param {string} from Optional path to resolve the "to" path - * @param {string|array} to Relative path(s) to resolve. - * @returns {string|array} The absolute path(s). - */ -resolveArray = function(from, to) { - var paths = []; - // "from" is optional. - if (typeof to === 'undefined') { - to = from; - from = ''; - } - if (to instanceof Array) { - to.forEach(function(value, index) { - paths[index] = path.resolve(from, value); + return generator; }); - } else { - paths = path.resolve(from, to); } - return paths; -}; +} module.exports = KssConfig; diff --git a/lib/kss_modifier.js b/lib/kss_modifier.js index f4b935c8..f371f5df 100644 --- a/lib/kss_modifier.js +++ b/lib/kss_modifier.js @@ -2,8 +2,7 @@ /** * The `kss/lib/kss_modifier` module is normally accessed via the - * [`KssModifier()`]{@link module:kss.KssModifier} constructor of the `kss` - * module: + * [`KssModifier()`]{@link module:kss.KssModifier} class of the `kss` module: * ``` * const KssModifier = require('kss').KssModifier; * ``` @@ -11,182 +10,179 @@ * @module kss/lib/kss_modifier */ -var KssModifier; - -/** - * An instance of this class is returned on calling `KssSection.modifier`. - * Exposes convenience methods for interpreting data. - * - * @constructor - * @alias module:kss.KssModifier - * @param {Object} [data] A part of the data object passed on by `KssSection`. - */ -KssModifier = function(data) { - if (!(this instanceof KssModifier)) { - return new KssModifier(data); +class KssModifier { + + /** + * An instance of this class is returned on calling `KssSection.modifier`. + * Exposes convenience methods for interpreting data. + * + * @constructor + * @alias module:kss.KssModifier + * @param {Object} [data] A part of the data object passed on by `KssSection`. + */ + constructor(data) { + data = data || {}; + + this.meta = { + section: null + }; + + this.data = { + name: '', + description: '', + className: '' + }; + + // Loop through the given properties. + for (let name in data) { + // If the property is defined in this.data or this.meta, add it via our API. + if (data.hasOwnProperty(name) && (this.data.hasOwnProperty(name) || this.meta.hasOwnProperty(name))) { + this[name](data[name]); + } + } } - data = data || {}; - - this.meta = { - section: null - }; - - this.data = { - name: '', - description: '', - className: '' - }; - - // Loop through the given properties. - for (var name in data) { - // If the property is defined in this.data or this.meta, add it via our API. - if (data.hasOwnProperty(name) && (this.data.hasOwnProperty(name) || this.meta.hasOwnProperty(name))) { - this[name](data[name]); + /** + * Gets or sets the `KssSection` object this `KssModifier` is associated with. + * + * If the `section` value is provided, the `KssSection` for this modifier is + * set. Otherwise, the `KssSection` of the modifier is returned. + * + * @param {KssSection} [section] Optional. The `KssSection` that owns the + * `KssModifier`. + * @returns {KssSection|KssModifier} If section is given, the current + * `KssModifier` object is returned to allow chaining of methods. Otherwise, + * the `KssSection` object the modifier belongs to is returned. + */ + section(section) { + if (typeof section === 'undefined') { + return this.meta.section; } - } -}; -/** - * Gets or sets the `KssSection` object this `KssModifier` is associated with. - * - * If the `section` value is provided, the `KssSection` for this modifier is - * set. Otherwise, the `KssSection` of the modifier is returned. - * - * @param {KssSection} [section] Optional. The `KssSection` that owns the - * `KssModifier`. - * @returns {KssSection|KssModifier} If section is given, the current - * `KssModifier` object is returned to allow chaining of methods. Otherwise, - * the `KssSection` object the modifier belongs to is returned. - */ -KssModifier.prototype.section = function(section) { - if (typeof section === 'undefined') { - return this.meta.section; + this.meta.section = section; + // Allow chaining. + return this; } - this.meta.section = section; - // Allow chaining. - return this; -}; + /** + * Gets or sets the name of the `KssModifier`, e.g. `:hover`, `.primary`, etc. + * + * If the `name` value is provided, the name of this `KssModifier` is set. + * Otherwise, the name of the `KssModifier` is returned. + * + * @param {string} [name] Optional. The name of the `KssModifier`. + * @returns {string|KssModifier} If name is given, the current `KssModifier` + * object is returned to allow chaining of methods. Otherwise, the name of the + * `KssModifier` is returned. + */ + name(name) { + if (typeof name === 'undefined') { + return this.data.name; + } -/** - * Gets or sets the name of the `KssModifier`, e.g. `:hover`, `.primary`, etc. - * - * If the `name` value is provided, the name of this `KssModifier` is set. - * Otherwise, the name of the `KssModifier` is returned. - * - * @param {string} [name] Optional. The name of the `KssModifier`. - * @returns {string|KssModifier} If name is given, the current `KssModifier` - * object is returned to allow chaining of methods. Otherwise, the name of the - * `KssModifier` is returned. - */ -KssModifier.prototype.name = function(name) { - if (typeof name === 'undefined') { - return this.data.name; + this.data.name = name; + // Allow chaining. + return this; } - this.data.name = name; - // Allow chaining. - return this; -}; + /** + * Gets or sets the description of the `KssModifier`. + * + * If the `description` is provided, the description of this `KssModifier` is set. + * Otherwise, the description of the `KssModifier` is returned. + * + * @param {string} [description] Optional. The description of the `KssModifier`. + * @returns {string|KssModifier} If description is given, the current + * `KssModifier` object is returned to allow chaining of methods. Otherwise, + * the description of the `KssModifier` is returned. + */ + description(description) { + if (typeof description === 'undefined') { + return this.data.description; + } -/** - * Gets or sets the description of the `KssModifier`. - * - * If the `description` is provided, the description of this `KssModifier` is set. - * Otherwise, the description of the `KssModifier` is returned. - * - * @param {string} [description] Optional. The description of the `KssModifier`. - * @returns {string|KssModifier} If description is given, the current - * `KssModifier` object is returned to allow chaining of methods. Otherwise, - * the description of the `KssModifier` is returned. - */ -KssModifier.prototype.description = function(description) { - if (typeof description === 'undefined') { - return this.data.description; + this.data.description = description; + // Allow chaining. + return this; } - this.data.description = description; - // Allow chaining. - return this; -}; - -/** - * Gets or sets CSS class(es) suitable to insert into a markup sample to display - * the modifier's design. - * - * By default, the CSS classes the className() method returns are based on the - * modifier's name. If the modifier's name includes a pseudo-class, e.g. - * `:hover`, this method will replace the ":" with "pseudo-class-", which - * matches the selector expected by the kss.js script and its KssStateGenerator. - * - * ``` - * modifier.name('.primary:hover'); - * modifier.className(); // Returns "primary pseudo-class-hover" - * ``` - * - * To override, the default behavior the class(es) can also be set manually; if - * the `className` parameter is provided, the className of this `KssModifier` - * is set and will later be returned as-is instead of calculated based on the - * `name()`. - * - * @param {string} [className] Optional. The class(es) of the `KssModifier`. - * @returns {string|KssModifier} If the className parameter is given, the - * current `KssModifier` object is returned to allow chaining of methods. - * Otherwise, the class name(s) of the `KssModifier` are returned. - */ -KssModifier.prototype.className = function(className) { - if (typeof className === 'undefined') { - if (this.data.className) { - return this.data.className; - } else { - var name = this.name().replace(/:/g, '.pseudo-class-'); - - // If the name includes child selectors, we only want the first parent - // selector. Markup should not be multiple elements deep at this stage. - name = name.split(/\s/)[0]; - - // Split into space-separated classes. - name = name - .replace(/\./g, ' ') - .replace(/^\s*/g, ''); - - return name; + /** + * Gets or sets CSS class(es) suitable to insert into a markup sample to display + * the modifier's design. + * + * By default, the CSS classes the className() method returns are based on the + * modifier's name. If the modifier's name includes a pseudo-class, e.g. + * `:hover`, this method will replace the ":" with "pseudo-class-", which + * matches the selector expected by the kss.js script and its KssStateGenerator. + * + * ``` + * modifier.name('.primary:hover'); + * modifier.className(); // Returns "primary pseudo-class-hover" + * ``` + * + * To override, the default behavior the class(es) can also be set manually; if + * the `className` parameter is provided, the className of this `KssModifier` + * is set and will later be returned as-is instead of calculated based on the + * `name()`. + * + * @param {string} [className] Optional. The class(es) of the `KssModifier`. + * @returns {string|KssModifier} If the className parameter is given, the + * current `KssModifier` object is returned to allow chaining of methods. + * Otherwise, the class name(s) of the `KssModifier` are returned. + */ + className(className) { + if (typeof className === 'undefined') { + if (this.data.className) { + return this.data.className; + } else { + let name = this.name().replace(/:/g, '.pseudo-class-'); + + // If the name includes child selectors, we only want the first parent + // selector. Markup should not be multiple elements deep at this stage. + name = name.split(/\s/)[0]; + + // Split into space-separated classes. + name = name + .replace(/\./g, ' ') + .replace(/^\s*/g, ''); + + return name; + } } + + this.data.className = className; + // Allow chaining. + return this; } - this.data.className = className; - // Allow chaining. - return this; -}; + /** + * Returns the HTML markup used to render this modifier. + * + * The markup is retrieved from the KssModifier's section. See + * `KssSection.markup()` to see how to set the markup. + * + * @returns {string} The markup of the modifier. + */ + markup() { + if (!this.section()) { + return ''; + } -/** - * Returns the HTML markup used to render this modifier. - * - * The markup is retrieved from the KssModifier's section. See - * `KssSection.markup()` to see how to set the markup. - * - * @returns {string} The markup of the modifier. - */ -KssModifier.prototype.markup = function() { - if (!this.section()) { - return ''; + return (this.section().markup() || ''); } - return (this.section().markup() || ''); -}; - -/** - * Return the `KssModifier` as a JSON object. - * - * @returns {Object} A JSON object representation of the `KssModifier`. - */ -KssModifier.prototype.toJSON = function() { - return { - name: this.name(), - description: this.description(), - className: this.className() - }; -}; + /** + * Return the `KssModifier` as a JSON object. + * + * @returns {Object} A JSON object representation of the `KssModifier`. + */ + toJSON() { + return { + name: this.name(), + description: this.description(), + className: this.className() + }; + } +} module.exports = KssModifier; diff --git a/lib/kss_parameter.js b/lib/kss_parameter.js index f3266e27..d0e22b0d 100644 --- a/lib/kss_parameter.js +++ b/lib/kss_parameter.js @@ -2,8 +2,7 @@ /** * The `kss/lib/kss_parameter` module is normally accessed via the - * [`KssParameter()`]{@link module:kss.KssParameter} constructor of the `kss` - * module: + * [`KssParameter()`]{@link module:kss.KssParameter} class of the `kss` module: * ``` * const KssParameter = require('kss').KssParameter; * ``` @@ -11,115 +10,112 @@ * @module kss/lib/kss_parameter */ -var KssParameter; +class KssParameter { -/** - * An instance of this class is returned on calling `KssSection.parameter`. - * Exposes convenience methods for interpreting data. - * - * @constructor - * @alias module:kss.KssParameter - * @param {Object} [data] A part of the data object passed on by `KssSection`. - */ -KssParameter = function(data) { - if (!(this instanceof KssParameter)) { - return new KssParameter(data); - } + /** + * An instance of this class is returned on calling `KssSection.parameter`. + * Exposes convenience methods for interpreting data. + * + * @constructor + * @alias module:kss.KssParameter + * @param {Object} [data] A part of the data object passed on by `KssSection`. + */ + constructor(data) { + data = data || {}; - data = data || {}; + this.meta = { + section: null + }; - this.meta = { - section: null - }; + this.data = { + name: '', + description: '' + }; - this.data = { - name: '', - description: '' - }; - - // Loop through the given properties. - for (var name in data) { - // If the property is defined in this.data or this.meta, add it via our API. - if (data.hasOwnProperty(name) && (this.data.hasOwnProperty(name) || this.meta.hasOwnProperty(name))) { - this[name](data[name]); + // Loop through the given properties. + for (let name in data) { + // If the property is defined in this.data or this.meta, add it via our API. + if (data.hasOwnProperty(name) && (this.data.hasOwnProperty(name) || this.meta.hasOwnProperty(name))) { + this[name](data[name]); + } } } -}; -/** - * Gets or sets the `KssSection` object this `KssParameter` is associated with. - * - * If the `section` value is provided, the `KssSection` for this parameter is - * set. Otherwise, the `KssSection` of the parameter is returned. - * - * @param {KssSection} [section] Optional. The `KssSection` that owns the - * `KssParameter`. - * @returns {KssSection|KssParameter} If section is given, the current - * `KssParameter` object is returned to allow chaining of methods. Otherwise, - * the `KssSection` object the parameter belongs to is returned. - */ -KssParameter.prototype.section = function(section) { - if (typeof section === 'undefined') { - return this.meta.section; + /** + * Gets or sets the `KssSection` object this `KssParameter` is associated with. + * + * If the `section` value is provided, the `KssSection` for this parameter is + * set. Otherwise, the `KssSection` of the parameter is returned. + * + * @param {KssSection} [section] Optional. The `KssSection` that owns the + * `KssParameter`. + * @returns {KssSection|KssParameter} If section is given, the current + * `KssParameter` object is returned to allow chaining of methods. Otherwise, + * the `KssSection` object the parameter belongs to is returned. + */ + section(section) { + if (typeof section === 'undefined') { + return this.meta.section; + } + + this.meta.section = section; + // Allow chaining. + return this; } - this.meta.section = section; - // Allow chaining. - return this; -}; + /** + * Gets or sets the name of the `KssParameter`. + * + * If the `name` value is provided, the name of this `KssParameter` is set. + * Otherwise, the name of the `KssParameter` is returned. + * + * @param {string} [name] Optional. The name of the `KssParameter`. + * @returns {string|KssParameter} If name is given, the current `KssParameter` + * object is returned to allow chaining of methods. Otherwise, the name of the + * `KssParameter` is returned. + */ + name(name) { + if (typeof name === 'undefined') { + return this.data.name; + } -/** - * Gets or sets the name of the `KssParameter`. - * - * If the `name` value is provided, the name of this `KssParameter` is set. - * Otherwise, the name of the `KssParameter` is returned. - * - * @param {string} [name] Optional. The name of the `KssParameter`. - * @returns {string|KssParameter} If name is given, the current `KssParameter` - * object is returned to allow chaining of methods. Otherwise, the name of the - * `KssParameter` is returned. - */ -KssParameter.prototype.name = function(name) { - if (typeof name === 'undefined') { - return this.data.name; + this.data.name = name; + // Allow chaining. + return this; } - this.data.name = name; - // Allow chaining. - return this; -}; + /** + * Gets or sets the description of the `KssParameter`. + * + * If the `description` is provided, the description of this `KssParameter` is set. + * Otherwise, the description of the `KssParameter` is returned. + * + * @param {string} [description] Optional. The description of the `KssParameter`. + * @returns {string|KssParameter} If description is given, the current + * `KssParameter` object is returned to allow chaining of methods. Otherwise, + * the description of the `KssParameter` is returned. + */ + description(description) { + if (typeof description === 'undefined') { + return this.data.description; + } -/** - * Gets or sets the description of the `KssParameter`. - * - * If the `description` is provided, the description of this `KssParameter` is set. - * Otherwise, the description of the `KssParameter` is returned. - * - * @param {string} [description] Optional. The description of the `KssParameter`. - * @returns {string|KssParameter} If description is given, the current - * `KssParameter` object is returned to allow chaining of methods. Otherwise, - * the description of the `KssParameter` is returned. - */ -KssParameter.prototype.description = function(description) { - if (typeof description === 'undefined') { - return this.data.description; + this.data.description = description; + // Allow chaining. + return this; } - this.data.description = description; - // Allow chaining. - return this; -}; - -/** - * Return the `KssParameter` as a JSON object. - * - * @returns {Object} A JSON object representation of the `KssParameter`. - */ -KssParameter.prototype.toJSON = function() { - return { - name: this.name(), - description: this.description() - }; -}; + /** + * Return the `KssParameter` as a JSON object. + * + * @returns {Object} A JSON object representation of the `KssParameter`. + */ + toJSON() { + return { + name: this.name(), + description: this.description() + }; + } +} module.exports = KssParameter; diff --git a/lib/kss_section.js b/lib/kss_section.js index d8b9e893..62d1c379 100644 --- a/lib/kss_section.js +++ b/lib/kss_section.js @@ -1,12 +1,11 @@ 'use strict'; -var KssModifier = require('./kss_modifier'), +const KssModifier = require('./kss_modifier'), KssParameter = require('./kss_parameter'); /** * The `kss/lib/kss_section` module is normally accessed via the - * [`KssSection()`]{@link module:kss.KssSection} constructor of the `kss` - * module: + * [`KssSection()`]{@link module:kss.KssSection} class of the `kss` module: * ``` * const KssSection = require('kss').KssSection; * ``` @@ -14,507 +13,503 @@ var KssModifier = require('./kss_modifier'), * @module kss/lib/kss_section */ -var KssSection; - -/** - * Creates one section of a style guide. - * - * @constructor - * @alias module:kss.KssSection - * @param {Object} [data] The data to use to initialize the `KssSection` object. - */ -KssSection = function(data) { - if (!(this instanceof KssSection)) { - return new KssSection(data); - } +class KssSection { + + /** + * Creates one section of a style guide. + * + * @constructor + * @alias module:kss.KssSection + * @param {Object} [data] The data to use to initialize the `KssSection` object. + */ + constructor(data) { + if (!(this instanceof KssSection)) { + return new KssSection(data); + } - data = data || {}; - - this.meta = { - styleGuide: data.styleGuide || null, - raw: data.raw || '', - customPropertyNames: [], - depth: data.depth || 0 - }; - - this.data = { - header: '', - description: '', - deprecated: false, - experimental: false, - reference: '', - referenceNumber: '', - referenceURI: '', - weight: 0, - markup: '', - modifiers: [], - parameters: [] - }; - - // Loop through the given properties. - for (var name in data) { - // istanbul ignore else - if (data.hasOwnProperty(name)) { - // If the property is defined in this.data, add it via our API. - if (this.data.hasOwnProperty(name)) { - this[name](data[name]); - - // If the property isn't defined in meta or data, add a custom property. - } else if (!this.meta.hasOwnProperty(name)) { - this.custom(name, data[name]); + data = data || {}; + + this.meta = { + styleGuide: data.styleGuide || null, + raw: data.raw || '', + customPropertyNames: [], + depth: data.depth || 0 + }; + + this.data = { + header: '', + description: '', + deprecated: false, + experimental: false, + reference: '', + referenceNumber: '', + referenceURI: '', + weight: 0, + markup: '', + modifiers: [], + parameters: [] + }; + + // Loop through the given properties. + for (let name in data) { + // istanbul ignore else + if (data.hasOwnProperty(name)) { + // If the property is defined in this.data, add it via our API. + if (this.data.hasOwnProperty(name)) { + this[name](data[name]); + + // If the property isn't defined in meta or data, add a custom property. + } else if (!this.meta.hasOwnProperty(name)) { + this.custom(name, data[name]); + } } } } -}; -/** - * Return the `KssSection` as a JSON object. - * - * @returns {Object} A JSON object representation of the KssSection. - */ -KssSection.prototype.toJSON = function() { - var returnObject; - - /* eslint-disable key-spacing */ - returnObject = { - header: this.header(), - description: this.description(), - deprecated: this.deprecated(), - experimental: this.experimental(), - reference: this.reference(), - referenceNumber: this.referenceNumber(), - referenceURI: this.referenceURI(), - weight: this.weight(), - markup: this.markup(), - // Include meta as well. - depth: this.depth() - }; - /* eslint-enable key-spacing */ - - returnObject.modifiers = this.modifiers().map(function(modifier) { - return modifier.toJSON(); - }); - returnObject.parameters = this.parameters().map(function(parameter) { - return parameter.toJSON(); - }); - - // Add custom properties to the JSON object. - for (var i = 0; i < this.meta.customPropertyNames.length; i++) { - // istanbul ignore else - if (typeof this.custom(this.meta.customPropertyNames[i]) !== 'undefined') { - returnObject[this.meta.customPropertyNames[i]] = this.custom(this.meta.customPropertyNames[i]); - } - } + /** + * Return the `KssSection` as a JSON object. + * + * @returns {Object} A JSON object representation of the KssSection. + */ + toJSON() { + /* eslint-disable key-spacing */ + let returnObject = { + header: this.header(), + description: this.description(), + deprecated: this.deprecated(), + experimental: this.experimental(), + reference: this.reference(), + referenceNumber: this.referenceNumber(), + referenceURI: this.referenceURI(), + weight: this.weight(), + markup: this.markup(), + // Include meta as well. + depth: this.depth() + }; + /* eslint-enable key-spacing */ + + returnObject.modifiers = this.modifiers().map(modifier => { + return modifier.toJSON(); + }); + returnObject.parameters = this.parameters().map(parameter => { + return parameter.toJSON(); + }); - return returnObject; -}; + // Add custom properties to the JSON object. + for (let i = 0; i < this.meta.customPropertyNames.length; i++) { + // istanbul ignore else + if (typeof this.custom(this.meta.customPropertyNames[i]) !== 'undefined') { + returnObject[this.meta.customPropertyNames[i]] = this.custom(this.meta.customPropertyNames[i]); + } + } -/** - * Gets or sets the `KssStyleGuide` object this `KssSection` is associated with. - * - * If the `styleGuide` value is provided, the `KssStyleGuide` for this section - * is set. Otherwise, the `KssStyleGuide` of the section is returned. - * - * @param {KssStyleGuide} [styleGuide] Optional. The `KssStyleGuide` that owns the - * `KssSection`. - * @returns {KssStyleGuide|KssSection} If styleGuide is given, the current - * `KssSection` object is returned to allow chaining of methods. Otherwise, - * the `KssStyleGuide` object the section belongs to is returned. - */ -KssSection.prototype.styleGuide = function(styleGuide) { - if (typeof styleGuide === 'undefined') { - return this.meta.styleGuide; + return returnObject; } - this.meta.styleGuide = styleGuide; - // Tell the style guide about this section's custom property names. - this.meta.styleGuide.customPropertyNames(this.customPropertyNames()); - // Allow chaining. - return this; -}; + /** + * Gets or sets the `KssStyleGuide` object this `KssSection` is associated with. + * + * If the `styleGuide` value is provided, the `KssStyleGuide` for this section + * is set. Otherwise, the `KssStyleGuide` of the section is returned. + * + * @param {KssStyleGuide} [styleGuide] Optional. The `KssStyleGuide` that owns the + * `KssSection`. + * @returns {KssStyleGuide|KssSection} If styleGuide is given, the current + * `KssSection` object is returned to allow chaining of methods. Otherwise, + * the `KssStyleGuide` object the section belongs to is returned. + */ + styleGuide(styleGuide) { + if (typeof styleGuide === 'undefined') { + return this.meta.styleGuide; + } -/** - * Gets or sets the header of the section, i.e. the first line in the description. - * - * If the `header` value is provided, the `header` for this section is set. - * Otherwise, the `header` of the section is returned. - * - * @param {string} [header] Optional. The header of the section. - * @returns {KssSection|string} If `header` is given, the `KssSection` object is - * returned to allow chaining of methods. Otherwise, the header of the section - * is returned. - */ -KssSection.prototype.header = function(header) { - if (typeof header === 'undefined') { - return this.data.header; + this.meta.styleGuide = styleGuide; + // Tell the style guide about this section's custom property names. + this.meta.styleGuide.customPropertyNames(this.customPropertyNames()); + // Allow chaining. + return this; } - this.data.header = header; - // Allow chaining. - return this; -}; + /** + * Gets or sets the header of the section, i.e. the first line in the description. + * + * If the `header` value is provided, the `header` for this section is set. + * Otherwise, the `header` of the section is returned. + * + * @param {string} [header] Optional. The header of the section. + * @returns {KssSection|string} If `header` is given, the `KssSection` object is + * returned to allow chaining of methods. Otherwise, the header of the section + * is returned. + */ + header(header) { + if (typeof header === 'undefined') { + return this.data.header; + } -/** - * Gets or sets the description of the section. - * - * If the `description` value is provided, the `description` for this section is - * set. Otherwise, the `description` of the section is returned. - * - * @param {string} [description] Optional. The description of the section. - * @returns {KssSection|string} If `description` is given, the `KssSection` - * object is returned to allow chaining of methods. Otherwise, the description - * of the section is returned. - */ -KssSection.prototype.description = function(description) { - if (typeof description === 'undefined') { - return this.data.description; + this.data.header = header; + // Allow chaining. + return this; } - this.data.description = description; - // Allow chaining. - return this; -}; - -/** - * Gets the list of custom properties of the section. - * - * Note that this method will return the actual custom properties set for this - * section, and not all of the custom properties available for the entire style - * guide. Use KssStyleGuide.customPropertyNames() for that list. - * - * @returns {string[]} An array of the section's custom property names. - */ -KssSection.prototype.customPropertyNames = function() { - return this.meta.customPropertyNames; -}; - -/** - * Gets or sets a custom property of the section. - * - * If the `value` is provided, the requested custom property of the section is - * set. Otherwise, the section's custom property with the name specified in the - * `name` parameter is returned. - * - * @param {string} name The name of the section's custom property. - * @param {*} [value] Optional. The value of the section's custom property. - * @returns {KssSection|*} If `value` is given, the `KssSection` object is - * returned to allow chaining of methods. Otherwise, the section's custom - * property, `name`, is returned. - */ -KssSection.prototype.custom = function(name, value) { - if (typeof value === 'undefined') { - /* eslint-disable no-undefined */ - return this.meta.customPropertyNames.indexOf(name) === -1 ? undefined : this.data[name]; - } + /** + * Gets or sets the description of the section. + * + * If the `description` value is provided, the `description` for this section is + * set. Otherwise, the `description` of the section is returned. + * + * @param {string} [description] Optional. The description of the section. + * @returns {KssSection|string} If `description` is given, the `KssSection` + * object is returned to allow chaining of methods. Otherwise, the description + * of the section is returned. + */ + description(description) { + if (typeof description === 'undefined') { + return this.data.description; + } - if (this.styleGuide()) { - this.styleGuide().customPropertyNames(name); + this.data.description = description; + // Allow chaining. + return this; } - this.meta.customPropertyNames.push(name); - this.data[name] = value; - // Allow chaining. - return this; -}; -/** - * Gets or sets the deprecated flag for the section. - * - * If the `deprecated` value is provided, the `deprecated` flag for this section - * is set. Otherwise, the `deprecated` flag for the section is returned. - * - * @param {boolean} [deprecated] Optional. The deprecated flag for the section. - * @returns {KssSection|boolean} If `deprecated` is given, the `KssSection` - * object is returned to allow chaining of methods. Otherwise, the deprecated - * flag for the section is returned. - */ -KssSection.prototype.deprecated = function(deprecated) { - if (typeof deprecated === 'undefined') { - return this.data.deprecated; + /** + * Gets the list of custom properties of the section. + * + * Note that this method will return the actual custom properties set for this + * section, and not all of the custom properties available for the entire style + * guide. Use KssStyleGuide.customPropertyNames() for that list. + * + * @returns {string[]} An array of the section's custom property names. + */ + customPropertyNames() { + return this.meta.customPropertyNames; } - this.data.deprecated = deprecated ? true : false; - // Allow chaining. - return this; -}; + /** + * Gets or sets a custom property of the section. + * + * If the `value` is provided, the requested custom property of the section is + * set. Otherwise, the section's custom property with the name specified in the + * `name` parameter is returned. + * + * @param {string} name The name of the section's custom property. + * @param {*} [value] Optional. The value of the section's custom property. + * @returns {KssSection|*} If `value` is given, the `KssSection` object is + * returned to allow chaining of methods. Otherwise, the section's custom + * property, `name`, is returned. + */ + custom(name, value) { + if (typeof value === 'undefined') { + /* eslint-disable no-undefined */ + return this.meta.customPropertyNames.indexOf(name) === -1 ? undefined : this.data[name]; + } -/** - * Gets or sets the experimental flag for the section. - * - * If the `experimental` value is provided, the `experimental` flag for this - * section is set. Otherwise, the `deprecated` flag for the section is returned. - * - * @param {boolean} [experimental] Optional. The experimental flag for the - * section. - * @returns {KssSection|boolean} If `experimental` is given, the `KssSection` - * object is returned to allow chaining of methods. Otherwise, the - * experimental flag for the section is returned. - */ -KssSection.prototype.experimental = function(experimental) { - if (typeof experimental === 'undefined') { - return this.data.experimental; + if (this.styleGuide()) { + this.styleGuide().customPropertyNames(name); + } + this.meta.customPropertyNames.push(name); + this.data[name] = value; + // Allow chaining. + return this; } - this.data.experimental = experimental ? true : false; - // Allow chaining. - return this; -}; + /** + * Gets or sets the deprecated flag for the section. + * + * If the `deprecated` value is provided, the `deprecated` flag for this section + * is set. Otherwise, the `deprecated` flag for the section is returned. + * + * @param {boolean} [deprecated] Optional. The deprecated flag for the section. + * @returns {KssSection|boolean} If `deprecated` is given, the `KssSection` + * object is returned to allow chaining of methods. Otherwise, the deprecated + * flag for the section is returned. + */ + deprecated(deprecated) { + if (typeof deprecated === 'undefined') { + return this.data.deprecated; + } -/** - * Gets or sets the reference for the section. - * - * If the `reference` value is provided, the `reference` for this section is - * set. Otherwise, the `reference` for the section is returned. - * - * @param {string} [reference] Optional. The reference of the section. - * @returns {KssSection|string} If `reference` is given, the `KssSection` object - * is returned to allow chaining of methods. Otherwise, the reference for the - * section is returned. - */ -KssSection.prototype.reference = function(reference) { - if (typeof reference === 'undefined') { - return this.data.reference; + this.data.deprecated = !!deprecated; + // Allow chaining. + return this; } - // @TODO: Tell the KssStyleGuide about the update. - - // Normalize any " - " delimeters. - reference = reference.replace(/\s+\-\s+/g, ' - '); - // Remove trailing dot-zeros and periods. - reference = reference.replace(/\.$|(\.0){1,}$/g, ''); + /** + * Gets or sets the experimental flag for the section. + * + * If the `experimental` value is provided, the `experimental` flag for this + * section is set. Otherwise, the `deprecated` flag for the section is returned. + * + * @param {boolean} [experimental] Optional. The experimental flag for the + * section. + * @returns {KssSection|boolean} If `experimental` is given, the `KssSection` + * object is returned to allow chaining of methods. Otherwise, the + * experimental flag for the section is returned. + */ + experimental(experimental) { + if (typeof experimental === 'undefined') { + return this.data.experimental; + } - this.data.reference = reference; - // Allow chaining. - return this; -}; + this.data.experimental = !!experimental; + // Allow chaining. + return this; + } -/** - * Gets or sets a numeric reference number for the section. - * - * If the `referenceNumber` value is provided, the `referenceNumber` for this - * section is set. - * - * If no parameters are given, this method returns a numeric reference number; - * if the style guide's references are already numeric (e.g. 2, 2.1.3, 3.2), - * then this method returns the same value as reference() does. Otherwise, an - * auto-incremented reference number will be returned. - * - * @param {string} [referenceNumber] Optional. The auto-incremented reference - * number of the section. - * @returns {KssSection|string} If `referenceNumber` is given, the `KssSection` - * object is returned to allow chaining of methods. Otherwise, the reference - * number of the section is returned. - */ -KssSection.prototype.referenceNumber = function(referenceNumber) { - if (typeof referenceNumber === 'undefined') { - if (this.styleGuide() && this.styleGuide().hasNumericReferences()) { + /** + * Gets or sets the reference for the section. + * + * If the `reference` value is provided, the `reference` for this section is + * set. Otherwise, the `reference` for the section is returned. + * + * @param {string} [reference] Optional. The reference of the section. + * @returns {KssSection|string} If `reference` is given, the `KssSection` object + * is returned to allow chaining of methods. Otherwise, the reference for the + * section is returned. + */ + reference(reference) { + if (typeof reference === 'undefined') { return this.data.reference; - } else { - return this.data.referenceNumber; } - } - this.data.referenceNumber = referenceNumber; - // Allow chaining. - return this; -}; + // @TODO: Tell the KssStyleGuide about the update. -/** - * Gets or sets the reference of the section, encoded as a valid URI fragment. - * - * If the `referenceURI` value is provided, the `referenceURI` for this section - * is set. Otherwise, the `referenceURI` of the section is returned. - * - * @param {string} [referenceURI] Optional. The referenceURI of the section. - * @returns {KssSection|string} If `referenceURI` is given, the `KssSection` - * object is returned to allow chaining of methods. Otherwise, the - * referenceURI of the section is returned. - */ -KssSection.prototype.referenceURI = function(referenceURI) { - if (typeof referenceURI === 'undefined') { - if (!this.data.referenceURI) { - this.data.referenceURI = encodeURI( - this.reference() - .replace(/ \- /g, '-') - .replace(/[^\w-]+/g, '-') - .toLowerCase() - ); - } - return this.data.referenceURI; + // Normalize any " - " delimiters. + reference = reference.replace(/\s+\-\s+/g, ' - '); + // Remove trailing dot-zeros and periods. + reference = reference.replace(/\.$|(\.0){1,}$/g, ''); + + this.data.reference = reference; + // Allow chaining. + return this; } - this.data.referenceURI = referenceURI; - // Allow chaining. - return this; -}; + /** + * Gets or sets a numeric reference number for the section. + * + * If the `referenceNumber` value is provided, the `referenceNumber` for this + * section is set. + * + * If no parameters are given, this method returns a numeric reference number; + * if the style guide's references are already numeric (e.g. 2, 2.1.3, 3.2), + * then this method returns the same value as reference() does. Otherwise, an + * auto-incremented reference number will be returned. + * + * @param {string} [referenceNumber] Optional. The auto-incremented reference + * number of the section. + * @returns {KssSection|string} If `referenceNumber` is given, the `KssSection` + * object is returned to allow chaining of methods. Otherwise, the reference + * number of the section is returned. + */ + referenceNumber(referenceNumber) { + if (typeof referenceNumber === 'undefined') { + if (this.styleGuide() && this.styleGuide().hasNumericReferences()) { + return this.data.reference; + } else { + return this.data.referenceNumber; + } + } -/** - * Gets or sets the weight of the section. - * - * If the `weight` value is provided, the `weight` for this section is set. - * Otherwise, the `weight` of the section is returned. - * - * @param {number} [weight] Optional. The weight of the section as an integer. - * @returns {KssSection|number} If `weight` is given, the `KssSection` object - * is returned to allow chaining of methods. Otherwise, the weight of the - * section is returned. - */ -KssSection.prototype.weight = function(weight) { - if (typeof weight === 'undefined') { - return this.data.weight; + this.data.referenceNumber = referenceNumber; + // Allow chaining. + return this; } - // @TODO: The weight needs to bubble-up to the KssStyleGuide weightMap. - this.data.weight = weight; - // Allow chaining. - return this; -}; + /** + * Gets or sets the reference of the section, encoded as a valid URI fragment. + * + * If the `referenceURI` value is provided, the `referenceURI` for this section + * is set. Otherwise, the `referenceURI` of the section is returned. + * + * @param {string} [referenceURI] Optional. The referenceURI of the section. + * @returns {KssSection|string} If `referenceURI` is given, the `KssSection` + * object is returned to allow chaining of methods. Otherwise, the + * referenceURI of the section is returned. + */ + referenceURI(referenceURI) { + if (typeof referenceURI === 'undefined') { + if (!this.data.referenceURI) { + this.data.referenceURI = encodeURI( + this.reference() + .replace(/ \- /g, '-') + .replace(/[^\w-]+/g, '-') + .toLowerCase() + ); + } + return this.data.referenceURI; + } -/** - * Gets or sets the depth of the section. - * - * If the `depth` value is provided, the `depth` for this section is set. - * Otherwise, the `depth` of the section is returned. - * - * @param {number} [depth] Optional. The depth of the section as a positive - * integer. - * @returns {KssSection|number} If `depth` is given, the `KssSection` object is - * returned to allow chaining of methods. Otherwise, the depth of the section - * is returned. - */ -KssSection.prototype.depth = function(depth) { - if (typeof depth === 'undefined') { - return this.meta.depth; + this.data.referenceURI = referenceURI; + // Allow chaining. + return this; } - this.meta.depth = depth; - // Allow chaining. - return this; -}; + /** + * Gets or sets the weight of the section. + * + * If the `weight` value is provided, the `weight` for this section is set. + * Otherwise, the `weight` of the section is returned. + * + * @param {number} [weight] Optional. The weight of the section as an integer. + * @returns {KssSection|number} If `weight` is given, the `KssSection` object + * is returned to allow chaining of methods. Otherwise, the weight of the + * section is returned. + */ + weight(weight) { + if (typeof weight === 'undefined') { + return this.data.weight; + } -/** - * Gets or sets the markup of the section. - * - * If the `markup` value is provided, the `markup` for this section is set. - * Otherwise, the `markup` of the section is returned. - * - * @param {string} [markup] Optional. The markup of the section. - * @returns {KssSection|string|false} If `markup` is given, the `KssSection` object is - * returned to allow chaining of methods. Otherwise, the markup of the section - * is returned, or `false` if none. - */ -KssSection.prototype.markup = function(markup) { - if (typeof markup === 'undefined') { - return this.data.markup; + // @TODO: The weight needs to bubble-up to the KssStyleGuide weightMap. + this.data.weight = weight; + // Allow chaining. + return this; } - this.data.markup = markup; - // Allow chaining. - return this; -}; - -/** - * Gets or adds nested objects of the section. - * - * A common helper for `.modifiers()` and `.parameters()` methods. - * - * Different types of arguments for `properties` will yield different results: - * - `Object|Array`: If the value is an array of objects or an object, the - * `properties` are added to this section. - * - `undefined`: Pass nothing to return all of the section's properties in an - * array. - * - `integer`: Use a 0-based index to return the section's Nth property. - * - `string`: Use a string to return a specific modifier by name. - * - * @private - * @param {string} propertyName The name of property in `KssSection`. - * @param {Constructor} objectConstructor The constructor function for the type - * of object the property is. - * @param {*} [properties] Optional. The properties to set for the section. - * @returns {*} If `properties` is given, the `KssSection` object is returned to - * allow chaining of methods. Otherwise, the requested properties of the - * section are returned. - */ -KssSection.prototype._propertyHelper = function(propertyName, objectConstructor, properties) { - var self = this, - query, index; + /** + * Gets or sets the depth of the section. + * + * If the `depth` value is provided, the `depth` for this section is set. + * Otherwise, the `depth` of the section is returned. + * + * @param {number} [depth] Optional. The depth of the section as a positive + * integer. + * @returns {KssSection|number} If `depth` is given, the `KssSection` object is + * returned to allow chaining of methods. Otherwise, the depth of the section + * is returned. + */ + depth(depth) { + if (typeof depth === 'undefined') { + return this.meta.depth; + } - if (typeof properties === 'undefined') { - return this.data[propertyName]; + this.meta.depth = depth; + // Allow chaining. + return this; } - // If we are given an object, assign the properties. - if (typeof properties === 'object') { - if (!(properties instanceof Array)) { - properties = [properties]; + /** + * Gets or sets the markup of the section. + * + * If the `markup` value is provided, the `markup` for this section is set. + * Otherwise, the `markup` of the section is returned. + * + * @param {string} [markup] Optional. The markup of the section. + * @returns {KssSection|string|boolean} If `markup` is given, the `KssSection` object is + * returned to allow chaining of methods. Otherwise, the markup of the section + * is returned, or `false` if none. + */ + markup(markup) { + if (typeof markup === 'undefined') { + return this.data.markup; } - properties.forEach(function(property) { - var newProperty = (property instanceof objectConstructor) ? property : new objectConstructor(property); - newProperty.section(self); - self.data[propertyName].push(newProperty); - }); + + this.data.markup = markup; // Allow chaining. return this; } - // Otherwise, we should search for the requested property. - query = properties; - index = parseInt(query); - if (typeof query === 'number' || typeof query === 'string' && query === (index + '')) { - return (index < this.data[propertyName].length) ? this.data[propertyName][index] : false; - // If the query can be converted to an integer, search by index instead. - } else { - // Otherwise, search for the property by name. - for (var i = 0; i < this.data[propertyName].length; i++) { - if (this.data[propertyName][i].name() === query) { - return this.data[propertyName][i]; + /** + * Gets or adds nested objects of the section. + * + * A common helper for `.modifiers()` and `.parameters()` methods. + * + * Different types of arguments for `properties` will yield different results: + * - `Object|Array`: If the value is an array of objects or an object, the + * `properties` are added to this section. + * - `undefined`: Pass nothing to return all of the section's properties in an + * array. + * - `integer`: Use a 0-based index to return the section's Nth property. + * - `string`: Use a string to return a specific modifier by name. + * + * @private + * @param {string} propertyName The name of property in `KssSection`. + * @param {Constructor} objectConstructor The constructor function for the type + * of object the property is. + * @param {*} [properties] Optional. The properties to set for the section. + * @returns {*} If `properties` is given, the `KssSection` object is returned to + * allow chaining of methods. Otherwise, the requested properties of the + * section are returned. + */ + _propertyHelper(propertyName, objectConstructor, properties) { + if (typeof properties === 'undefined') { + return this.data[propertyName]; + } + + // If we are given an object, assign the properties. + if (typeof properties === 'object') { + if (!(properties instanceof Array)) { + properties = [properties]; } + properties.forEach(property => { + let newProperty = (property instanceof objectConstructor) ? property : new objectConstructor(property); + newProperty.section(this); + this.data[propertyName].push(newProperty); + }); + // Allow chaining. + return this; } - } - // No matching property found. - return false; -}; + // Otherwise, we should search for the requested property. + let query = properties, + index = parseInt(query); + if (typeof query === 'number' || typeof query === 'string' && query === (index + '')) { + return (index < this.data[propertyName].length) ? this.data[propertyName][index] : false; + // If the query can be converted to an integer, search by index instead. + } else { + // Otherwise, search for the property by name. + for (let i = 0; i < this.data[propertyName].length; i++) { + if (this.data[propertyName][i].name() === query) { + return this.data[propertyName][i]; + } + } + } -/** - * Gets or adds modifiers of the section. - * - * Different types of arguments will yield different results: - * - `modifiers(Object|Array)`: If the value is an array of objects or an - * object, the `modifiers` are added to this section. - * - `modifiers()`: Pass nothing to return all of the section's modifiers in an - * array. - * - `modifiers(Integer)`: Use a 0-based index to return the section's Nth - * modifier. - * - `modifiers(String)`: Use a string to return a specific modifier by name. - * - * @param {*} [modifiers] Optional. The modifiers of the section. - * @returns {KssSection|KssModifier|KssModifier[]|false} If `modifiers` is - * given, the `KssSection` object is returned to allow chaining of methods. - * Otherwise, the requested modifiers of the section are returned. - */ -KssSection.prototype.modifiers = function(modifiers) { - return this._propertyHelper('modifiers', KssModifier, modifiers); -}; + // No matching property found. + return false; + } -/** - * Gets or adds parameters if the section is a CSS preprocessor function/mixin. - * - * Different types of arguments will yield different results: - * - `parameters(Object|Array)`: If the value is an array of objects or an - * object, the `parameters` are added to this section. - * - `parameters()`: Pass nothing to return all of the section's parameters in - * an array. - * - `parameters(Integer)`: Use a 0-based index to return the section's Nth - * parameter. - * - `parameters(String)`: Use a string to return a specific parameter by name. - * - * @param {*} [parameters] Optional. The parameters of the section. - * @returns {KssSection|KssParameter|KssParameter[]|false} If `parameters` is - * given, the `KssSection` object is returned to allow chaining of methods. - * Otherwise, the requested parameters of the section are returned. - */ -KssSection.prototype.parameters = function(parameters) { - return this._propertyHelper('parameters', KssParameter, parameters); -}; + /** + * Gets or adds modifiers of the section. + * + * Different types of arguments will yield different results: + * - `modifiers(Object|Array)`: If the value is an array of objects or an + * object, the `modifiers` are added to this section. + * - `modifiers()`: Pass nothing to return all of the section's modifiers in an + * array. + * - `modifiers(Integer)`: Use a 0-based index to return the section's Nth + * modifier. + * - `modifiers(String)`: Use a string to return a specific modifier by name. + * + * @param {*} [modifiers] Optional. The modifiers of the section. + * @returns {KssSection|KssModifier|KssModifier[]|boolean} If `modifiers` is + * given, the `KssSection` object is returned to allow chaining of methods. + * Otherwise, the requested modifiers of the section are returned. + */ + modifiers(modifiers) { + return this._propertyHelper('modifiers', KssModifier, modifiers); + } + + /** + * Gets or adds parameters if the section is a CSS preprocessor function/mixin. + * + * Different types of arguments will yield different results: + * - `parameters(Object|Array)`: If the value is an array of objects or an + * object, the `parameters` are added to this section. + * - `parameters()`: Pass nothing to return all of the section's parameters in + * an array. + * - `parameters(Integer)`: Use a 0-based index to return the section's Nth + * parameter. + * - `parameters(String)`: Use a string to return a specific parameter by name. + * + * @param {*} [parameters] Optional. The parameters of the section. + * @returns {KssSection|KssParameter|KssParameter[]|boolean} If `parameters` is + * given, the `KssSection` object is returned to allow chaining of methods. + * Otherwise, the requested parameters of the section are returned. + */ + parameters(parameters) { + return this._propertyHelper('parameters', KssParameter, parameters); + } +} module.exports = KssSection; diff --git a/lib/kss_styleguide.js b/lib/kss_styleguide.js index b0db06d8..fa469a62 100644 --- a/lib/kss_styleguide.js +++ b/lib/kss_styleguide.js @@ -1,10 +1,10 @@ 'use strict'; -var KssSection = require('./kss_section'); +const KssSection = require('./kss_section'); /** * The `kss/lib/kss_styleguide` module is normally accessed via the - * [`KssStyleGuide()`]{@link module:kss.KssStyleGuide} constructor of the `kss` + * [`KssStyleGuide()`]{@link module:kss.KssStyleGuide} class of the `kss` * module: * ``` * const KssStyleGuide = require('kss').KssStyleGuide; @@ -13,406 +13,403 @@ var KssSection = require('./kss_section'); * @module kss/lib/kss_styleguide */ -var KssStyleGuide; - -/** - * An instance of this class is returned on finishing `kss.traverse`. It has a - * few convenience methods for interpreting data, the most important of which is - * sections() for searching for specific sections of the style guide. - * - * @constructor - * @alias module:kss.KssStyleGuide - * @param {Object} [data] The data object generated by traverse. - */ -KssStyleGuide = function(data) { - if (!(this instanceof KssStyleGuide)) { - return new KssStyleGuide(data); - } +class KssStyleGuide { + + /** + * An instance of this class is returned on finishing `kss.traverse`. It has a + * few convenience methods for interpreting data, the most important of which is + * sections() for searching for specific sections of the style guide. + * + * @constructor + * @alias module:kss.KssStyleGuide + * @param {Object} [data] The data object generated by traverse. + */ + constructor(data) { + if (!(this instanceof KssStyleGuide)) { + return new KssStyleGuide(data); + } - data = data || {}; - - this.meta = { - autoInit: false, - files: data.files || [], - hasNumericReferences: true, - needsDepth: false, - needsReferenceNumber: false, - needsSort: false, - referenceDelimiter: '.', - referenceMap: [], - weightMap: {} - }; - - this.data = { - customPropertyNames: [], - sections: [] - }; - - if (data.customPropertyNames) { - this.customPropertyNames(data.customPropertyNames); - } + data = data || {}; + + this.meta = { + autoInit: false, + files: data.files || [], + hasNumericReferences: true, + needsDepth: false, + needsReferenceNumber: false, + needsSort: false, + referenceDelimiter: '.', + referenceMap: [], + weightMap: {} + }; + + this.data = { + customPropertyNames: [], + sections: [] + }; + + if (data.customPropertyNames) { + this.customPropertyNames(data.customPropertyNames); + } - if (data.sections) { - // Note that auto-initialization is temporarily off since we don't want to - // init this new object until after these sections are added. - this.sections(data.sections); - } + if (data.sections) { + // Note that auto-initialization is temporarily off since we don't want to + // init this new object until after these sections are added. + this.sections(data.sections); + } - // Now that all sections are added, turn on auto-initialization. But allow a - // flag passed to the constructor to turn off auto-initialization. - if (data.autoInit !== false) { - this.meta.autoInit = true; - } + // Now that all sections are added, turn on auto-initialization. But allow a + // flag passed to the constructor to turn off auto-initialization. + if (data.autoInit !== false) { + this.meta.autoInit = true; + } - if (this.meta.autoInit) { - this.init(); + if (this.meta.autoInit) { + this.init(); + } } -}; - -/** - * Return the `KssStyleGuide` as a JSON object. - * - * @returns {Object} A JSON object representation of the KssStyleGuide. - */ -KssStyleGuide.prototype.toJSON = function() { - var returnObject; - returnObject = { - customPropertyNames: this.customPropertyNames(), - hasNumericReferences: this.meta.hasNumericReferences, - referenceDelimiter: this.meta.referenceDelimiter - }; - - returnObject.sections = this.sections().map(function(section) { - return section.toJSON(); - }); - - return returnObject; -}; - -/** - * Toggles the auto-initialization setting of this style guide. - * - * If a `false` value is provided, auto-initialization is disabled and users - * will be required to call `init()` manually after adding sections via - * `sections()`. If a `true' value is provided, auto-initialization will be - * enabled and the `init()` method will immediately be called. - * - * @param {boolean} autoInit The new setting for auto-initialization. - * @returns {KssStyleGuide} The `KssStyleGuide` object is returned to allow - * chaining of methods. - */ -KssStyleGuide.prototype.autoInit = function(autoInit) { - this.meta.autoInit = autoInit ? true : false; + /** + * Return the `KssStyleGuide` as a JSON object. + * + * @returns {Object} A JSON object representation of the KssStyleGuide. + */ + toJSON() { + let returnObject; + + returnObject = { + customPropertyNames: this.customPropertyNames(), + hasNumericReferences: this.meta.hasNumericReferences, + referenceDelimiter: this.meta.referenceDelimiter + }; + + returnObject.sections = this.sections().map(section => { + return section.toJSON(); + }); - if (this.meta.autoInit) { - this.init(); + return returnObject; } - // Allow chaining. - return this; -}; - -/** - * Sorts the style guides sections and (re-)initializes some section values. - * - * Some section data is dependent on the state of the KssStyleGuide. When - * sections are added with `sections()`, it determines what updates are needed. - * If needed, this method: - * - Calculates the depth of the reference for each section. e.g. Section 2.1.7 - * has a depth of 3. - * - Sorts all the sections by reference. - * - Calculates a reference number if the style guide uses - * word-based references. - * - * By default this method is called automatically whenever new sections are - * added to the style guide. This - * - * @returns {KssStyleGuide} Returns the `KssStyleGuide` object to allow chaining - * of methods. - */ -KssStyleGuide.prototype.init = function() { - var self = this, i, numSections, getWeight, incrementIndex, autoIncrement = [0], ref, previousRef = [], index; + /** + * Toggles the auto-initialization setting of this style guide. + * + * If a `false` value is provided, auto-initialization is disabled and users + * will be required to call `init()` manually after adding sections via + * `sections()`. If a `true' value is provided, auto-initialization will be + * enabled and the `init()` method will immediately be called. + * + * @param {boolean} autoInit The new setting for auto-initialization. + * @returns {KssStyleGuide} The `KssStyleGuide` object is returned to allow + * chaining of methods. + */ + autoInit(autoInit) { + this.meta.autoInit = !!autoInit; + + if (this.meta.autoInit) { + this.init(); + } - if (this.data.sections.length) { - numSections = this.data.sections.length; + // Allow chaining. + return this; + } - // The delimiter has changed, so recalculate the depth of each section's - // reference. - if (this.meta.needsDepth) { - for (i = 0; i < numSections; i++) { - this.data.sections[i].depth(this.data.sections[i].reference().split(this.meta.referenceDelimiter).length); + /** + * Sorts the style guides sections and (re-)initializes some section values. + * + * Some section data is dependent on the state of the KssStyleGuide. When + * sections are added with `sections()`, it determines what updates are needed. + * If needed, this method: + * - Calculates the depth of the reference for each section. e.g. Section 2.1.7 + * has a depth of 3. + * - Sorts all the sections by reference. + * - Calculates a reference number if the style guide uses + * word-based references. + * + * By default this method is called automatically whenever new sections are + * added to the style guide. This + * + * @returns {KssStyleGuide} Returns the `KssStyleGuide` object to allow chaining + * of methods. + */ + init() { + if (this.data.sections.length) { + let numSections = this.data.sections.length; + + // The delimiter has changed, so recalculate the depth of each section's + // reference. + if (this.meta.needsDepth) { + for (let i = 0; i < numSections; i++) { + this.data.sections[i].depth(this.data.sections[i].reference().split(this.meta.referenceDelimiter).length); + } + this.meta.needsDepth = false; } - this.meta.needsDepth = false; - } - // Sort all the sections. - if (this.meta.needsSort) { - // Sorting helper function that gets the weight of the given reference at - // the given depth. e.g. `getWeight('4.3.2.2', 2)` will return the weight - // for section 4.3. - getWeight = function(reference, depth) { - reference = reference.toLowerCase(); - reference = reference.split(self.meta.referenceDelimiter, depth).join(self.meta.referenceDelimiter); - - return self.meta.weightMap[reference] ? self.meta.weightMap[reference] : 0; - }; - - // Sort sections based on the references. - this.data.sections.sort(function(a, b) { - // Split the 2 references into chunks by their period or dash separators. - var refsA = a.reference().toLowerCase().split(self.meta.referenceDelimiter), - refsB = b.reference().toLowerCase().split(self.meta.referenceDelimiter), - weightA, weightB, - j, l = Math.max(refsA.length, refsB.length); - - // Compare each set of chunks until we know which reference should be listed first. - for (j = 0; j < l; j++) { - if (refsA[j] && refsB[j]) { - // If the 2 chunks are unequal, compare them. - if (refsA[j] !== refsB[j]) { - // If the chunks have different weights, sort by weight. - weightA = getWeight(a.reference(), j + 1); - weightB = getWeight(b.reference(), j + 1); - if (weightA !== weightB) { - return weightA - weightB; - } else if (refsA[j].match(/^\d+$/) && refsB[j].match(/^\d+$/)) { - // If both chunks are digits, use numeric sorting. - return refsA[j] - refsB[j]; - } else { - // Otherwise, use alphabetical string sorting. - return (refsA[j] > refsB[j]) ? 1 : -1; + // Sort all the sections. + if (this.meta.needsSort) { + let delimiter = this.meta.referenceDelimiter; + let self = this; + // Sorting helper function that gets the weight of the given reference at + // the given depth. e.g. `getWeight('4.3.2.2', 2)` will return the weight + // for section 4.3. + let getWeight = function(reference, depth) { + reference = reference.toLowerCase().split(delimiter, depth).join(delimiter); + + return self.meta.weightMap[reference] ? self.meta.weightMap[reference] : 0; + }; + + // Sort sections based on the references. + this.data.sections.sort((a, b) => { + // Split the 2 references into chunks by their period or dash separators. + let refsA = a.reference().toLowerCase().split(delimiter), + refsB = b.reference().toLowerCase().split(delimiter), + weightA, weightB, + maxRefLength = Math.max(refsA.length, refsB.length); + + // Compare each set of chunks until we know which reference should be listed first. + for (let i = 0; i < maxRefLength; i++) { + if (refsA[i] && refsB[i]) { + // If the 2 chunks are unequal, compare them. + if (refsA[i] !== refsB[i]) { + // If the chunks have different weights, sort by weight. + weightA = getWeight(a.reference(), i + 1); + weightB = getWeight(b.reference(), i + 1); + if (weightA !== weightB) { + return weightA - weightB; + } else if (refsA[i].match(/^\d+$/) && refsB[i].match(/^\d+$/)) { + // If both chunks are digits, use numeric sorting. + return refsA[i] - refsB[i]; + } else { + // Otherwise, use alphabetical string sorting. + return (refsA[i] > refsB[i]) ? 1 : -1; + } } + } else { + // If 1 of the chunks is empty, it goes first. + return refsA[i] ? 1 : -1; } - } else { - // If 1 of the chunks is empty, it goes first. - return refsA[j] ? 1 : -1; } - } - return 0; - }); - this.meta.needsSort = false; - } + return 0; + }); + this.meta.needsSort = false; + } - // Create an auto-incremented reference number if the references are not - // number-based references. - if (this.meta.needsReferenceNumber) { - for (i = 0; i < numSections; i++) { - ref = this.data.sections[i].reference(); - - // Compare the previous Ref to the new Ref. - ref = ref.split(this.meta.referenceDelimiter); - // If they are already equal, we don't need to increment the section number. - if (previousRef.join(this.meta.referenceDelimiter) !== ref.join(this.meta.referenceDelimiter)) { - incrementIndex = 0; - for (index = 0; index < previousRef.length; index += 1) { - // Find the index where the refs differ. - if (index >= ref.length || previousRef[index] !== ref[index]) { - break; + // Create an auto-incremented reference number if the references are not + // number-based references. + if (this.meta.needsReferenceNumber) { + let autoIncrement = [0], ref, previousRef = []; + for (let i = 0; i < numSections; i++) { + ref = this.data.sections[i].reference(); + + // Compare the previous Ref to the new Ref. + ref = ref.split(this.meta.referenceDelimiter); + // If they are already equal, we don't need to increment the section number. + if (previousRef.join(this.meta.referenceDelimiter) !== ref.join(this.meta.referenceDelimiter)) { + let incrementIndex = 0; + for (let index = 0; index < previousRef.length; index += 1) { + // Find the index where the refs differ. + if (index >= ref.length || previousRef[index] !== ref[index]) { + break; + } + incrementIndex = index + 1; + } + if (incrementIndex < autoIncrement.length) { + // Increment the part where the refs started to differ. + autoIncrement[incrementIndex]++; + // Trim off the extra parts of the autoincrement where the refs differed. + autoIncrement = autoIncrement.slice(0, incrementIndex + 1); + } + // Add parts to the autoincrement to ensure it is the same length as the new ref. + for (let index = autoIncrement.length; index < ref.length; index += 1) { + autoIncrement[index] = 1; } - incrementIndex = index + 1; - } - if (incrementIndex < autoIncrement.length) { - // Increment the part where the refs started to differ. - autoIncrement[incrementIndex]++; - // Trim off the extra parts of the autoincrement where the refs differed. - autoIncrement = autoIncrement.slice(0, incrementIndex + 1); - } - // Add parts to the autoincrement to ensure it is the same length as the new ref. - for (index = autoIncrement.length; index < ref.length; index += 1) { - autoIncrement[index] = 1; } + this.data.sections[i].referenceNumber(autoIncrement.join('.')); + previousRef = ref; } - this.data.sections[i].referenceNumber(autoIncrement.join('.')); - previousRef = ref; + this.meta.needsReferenceNumber = false; } - this.meta.needsReferenceNumber = false; } - } - - // Allow chaining. - return this; -}; -/** - * Gets or sets the list of custom properties of the style guide. - * - * If the `names` value is provided, the names are added to the style guide's - * list of custom properties. Otherwise, the style guide's list of custom - * properties is returned. - * - * @param {string|string[]} [names] Optional. The names of of the section. - * @returns {KssStyleGuide|string[]} If `names` is given, the `KssStyleGuide` - * object is returned to allow chaining of methods. Otherwise, the list of - * custom properties of the style guide is returned. - */ -KssStyleGuide.prototype.customPropertyNames = function(names) { - if (typeof names === 'undefined') { - return this.data.customPropertyNames; + // Allow chaining. + return this; } - if (!(names instanceof Array)) { - names = [names]; - } - for (var i = 0; i < names.length; i++) { - if (this.data.customPropertyNames.indexOf(names[i]) === -1) { - this.data.customPropertyNames.push(names[i]); + /** + * Gets or sets the list of custom properties of the style guide. + * + * If the `names` value is provided, the names are added to the style guide's + * list of custom properties. Otherwise, the style guide's list of custom + * properties is returned. + * + * @param {string|string[]} [names] Optional. The names of of the section. + * @returns {KssStyleGuide|string[]} If `names` is given, the `KssStyleGuide` + * object is returned to allow chaining of methods. Otherwise, the list of + * custom properties of the style guide is returned. + */ + customPropertyNames(names) { + if (typeof names === 'undefined') { + return this.data.customPropertyNames; } - } - // Allow chaining. - return this; -}; -/** - * Returns whether the style guide has numeric references or not. - * - * @returns {boolean} Whether the style guide has numeric references or not. - */ -KssStyleGuide.prototype.hasNumericReferences = function() { - return this.meta.hasNumericReferences; -}; + if (!(names instanceof Array)) { + names = [names]; + } + for (let i = 0; i < names.length; i++) { + if (this.data.customPropertyNames.indexOf(names[i]) === -1) { + this.data.customPropertyNames.push(names[i]); + } + } + // Allow chaining. + return this; + } -/** - * Gets or sets the sections of the style guide. - * - * If `sections` objects are provided, the sections are added to the style - * guide. Otherwise, a search is performed to return the desired sections. - * - * There's a few ways to use search with this method: - * - `sections()` returns all of the sections. - * - * Using strings: - * - `sections('2')` returns Section 2. - * - `sections('2.*')` returns Section 2 and all of its descendants. - * - `sections('2.x')` returns Section 2's children only. - * - `sections('2.x.x')` returns Section 2's children, and their children too. - * - * Or Regular Expressions: - * - `sections(/2\.[1-5]/)` returns Sections 2.1 through to 2.5. - * - * @param {Object|Object[]|string|Regexp} [sections] Optional. A section object - * or array of secction objects to add to the style guide. Or a string or - * Regexp object to match a KssSection's style guide reference. - * @returns {KssStyleGuide|KssSection|KssSection[]|false} If `sections` is - * given, the `KssStyleGuide` object is returned to allow chaining of methods. - * Otherwise, the exact KssSection requested, an array of KssSection objects - * matching the query, or false is returned. - */ -KssStyleGuide.prototype.sections = function(sections) { - var self = this, - query, - i, - delim, - matchedSections = [], - match; - - if (typeof sections === 'undefined') { - return this.data.sections; + /** + * Returns whether the style guide has numeric references or not. + * + * @returns {boolean} Whether the style guide has numeric references or not. + */ + hasNumericReferences() { + return this.meta.hasNumericReferences; } - // If we are given an object, assign the properties. - if (typeof sections === 'object' && !(sections instanceof RegExp)) { - if (!(sections instanceof Array)) { - sections = [sections]; + /** + * Gets or sets the sections of the style guide. + * + * If `sections` objects are provided, the sections are added to the style + * guide. Otherwise, a search is performed to return the desired sections. + * + * There's a few ways to use search with this method: + * - `sections()` returns all of the sections. + * + * Using strings: + * - `sections('2')` returns Section 2. + * - `sections('2.*')` returns Section 2 and all of its descendants. + * - `sections('2.x')` returns Section 2's children only. + * - `sections('2.x.x')` returns Section 2's children, and their children too. + * + * Or Regular Expressions: + * - `sections(/2\.[1-5]/)` returns Sections 2.1 through to 2.5. + * + * @param {Object|Object[]|string|RegExp} [sections] Optional. A section object + * or array of secction objects to add to the style guide. Or a string or + * Regexp object to match a KssSection's style guide reference. + * @returns {KssStyleGuide|KssSection|KssSection[]|boolean} If `sections` is + * given, the `KssStyleGuide` object is returned to allow chaining of methods. + * Otherwise, the exact KssSection requested, an array of KssSection objects + * matching the query, or false is returned. + */ + sections(sections) { + let query, + matchedSections = []; + + if (typeof sections === 'undefined') { + return this.data.sections; } - sections.forEach(function(section) { - var originalDelimiter = self.meta.referenceDelimiter; - if (!(section instanceof KssSection)) { - section = new KssSection(section); + // If we are given an object, assign the properties. + if (typeof sections === 'object' && !(sections instanceof RegExp)) { + if (!(sections instanceof Array)) { + sections = [sections]; } + sections.forEach(section => { + let originalDelimiter = this.meta.referenceDelimiter; - // Set the style guide for each section. - section.styleGuide(self); - - // Determine if the references are number-based or word-based. - self.meta.hasNumericReferences = self.meta.hasNumericReferences && /^[\.\d]+$/.test(section.reference()); - // Store the reference for quick searching later. - self.meta.referenceMap[section.reference()] = section; - // Store the section's weight. - self.meta.weightMap[section.reference().toLowerCase()] = section.weight(); - // Determine the separator used in references; e.g. 'a - b' or 'a.b'. - if (section.reference().indexOf(' - ') > -1) { - self.meta.referenceDelimiter = ' - '; - } + if (!(section instanceof KssSection)) { + section = new KssSection(section); + } - // Add the section to the style guide. - self.data.sections.push(section); + // Set the style guide for each section. + section.styleGuide(this); + + // Determine if the references are number-based or word-based. + this.meta.hasNumericReferences = this.meta.hasNumericReferences && /^[\.\d]+$/.test(section.reference()); + // Store the reference for quick searching later. + this.meta.referenceMap[section.reference()] = section; + // Store the section's weight. + this.meta.weightMap[section.reference().toLowerCase()] = section.weight(); + // Determine the separator used in references; e.g. 'a - b' or 'a.b'. + if (section.reference().indexOf(' - ') > -1) { + this.meta.referenceDelimiter = ' - '; + } - // If the delimiter changed, flag the depths as needing recalculation. - if (originalDelimiter !== self.meta.referenceDelimiter) { - self.meta.needsDepth = true; - } else { - // Set the depth of this section's reference. - section.depth(section.reference().split(self.meta.referenceDelimiter).length); - } + // Add the section to the style guide. + this.data.sections.push(section); - // Determine if all sections need their reference number recalculated. - if (!self.meta.hasNumericReferences) { - self.meta.needsReferenceNumber = true; - } + // If the delimiter changed, flag the depths as needing recalculation. + if (originalDelimiter !== this.meta.referenceDelimiter) { + this.meta.needsDepth = true; + } else { + // Set the depth of this section's reference. + section.depth(section.reference().split(this.meta.referenceDelimiter).length); + } - // A new section means all sections need to be sorted. - self.meta.needsSort = true; - }); + // Determine if all sections need their reference number recalculated. + if (!this.meta.hasNumericReferences) { + this.meta.needsReferenceNumber = true; + } - // Automatically re-initialize the style guide. - if (this.autoInit) { - this.init(); + // A new section means all sections need to be sorted. + this.meta.needsSort = true; + }); + + // Automatically re-initialize the style guide. + if (this.autoInit) { + this.init(); + } + // Allow chaining. + return this; } - // Allow chaining. - return this; - } - // Otherwise, we should search for the requested section. - query = sections; - - // Exact queries. - if (typeof query === 'string') { - // If the query is '*', 'x', or ends with '.*', ' - *', '.x', or ' - x', - // then it is not an exact query. - if (!(/(^[x\*]$|\s\-\s[x\*]$|\.[x\*]$)/.test(query))) { - if (this.meta.referenceMap[query]) { - return this.meta.referenceMap[query]; - } else { - return false; + // Otherwise, we should search for the requested section. + query = sections; + + // Exact queries. + if (typeof query === 'string') { + // If the query is '*', 'x', or ends with '.*', ' - *', '.x', or ' - x', + // then it is not an exact query. + if (!(/(^[x\*]$|\s\-\s[x\*]$|\.[x\*]$)/.test(query))) { + if (this.meta.referenceMap[query]) { + return this.meta.referenceMap[query]; + } else { + return false; + } } } - } - // Convert regex strings into proper JavaScript RegExp objects. - if (!(query instanceof RegExp)) { - delim = this.meta.referenceDelimiter === '.' ? '\\.' : '\\ \\-\\ '; - query = new RegExp( - query + // Convert regex strings into proper JavaScript RegExp objects. + if (!(query instanceof RegExp)) { + let delim = this.meta.referenceDelimiter === '.' ? '\\.' : '\\ \\-\\ '; + query = new RegExp( + query // Convert '*' to a simple .+ regex. - .replace(/^\*$/, '.+') - // Convert 'x' to a regex matching one level of reference. - .replace(/^x$/, '^.+?(?=($|' + delim + '))') - // Convert '.*' or ' - *' to a ([delim].+){0,1} regex. - .replace(/(\.|\s+\-\s+)\*$/g, '(' + delim + '.+){0,1}') - // Convert the first '.x' or ' - x' to a regex matching one sub-level - // of a reference. - .replace(/(\.|\s+\-\s+)x\b/, delim + '.+?(?=($|' + delim + '))') - // Convert any remaining '.x' or ' - x' to a regex matching zero or one - // sub-levels of a reference. - .replace(/(\.|\s+\-\s+)x\b/g, '(' + delim + '.+?(?=($|' + delim + '))){0,1}') - // Convert any remaining '-' into '\-' - .replace(/([^\\])\-/g, '$1\\-') - ); - } + .replace(/^\*$/, '.+') + // Convert 'x' to a regex matching one level of reference. + .replace(/^x$/, '^.+?(?=($|' + delim + '))') + // Convert '.*' or ' - *' to a ([delim].+){0,1} regex. + .replace(/(\.|\s+\-\s+)\*$/g, '(' + delim + '.+){0,1}') + // Convert the first '.x' or ' - x' to a regex matching one sub-level + // of a reference. + .replace(/(\.|\s+\-\s+)x\b/, delim + '.+?(?=($|' + delim + '))') + // Convert any remaining '.x' or ' - x' to a regex matching zero or one + // sub-levels of a reference. + .replace(/(\.|\s+\-\s+)x\b/g, '(' + delim + '.+?(?=($|' + delim + '))){0,1}') + // Convert any remaining '-' into '\-' + .replace(/([^\\])\-/g, '$1\\-') + ); + } - // General (regex) search - for (i = 0; i < this.data.sections.length; i += 1) { - match = this.data.sections[i].reference().match(query); - // The regex must match the full reference. - if (match && match[0] === this.data.sections[i].reference()) { - matchedSections.push(this.data.sections[i]); + // General (regex) search + for (let i = 0; i < this.data.sections.length; i += 1) { + let match = this.data.sections[i].reference().match(query); + // The regex must match the full reference. + if (match && match[0] === this.data.sections[i].reference()) { + matchedSections.push(this.data.sections[i]); + } } - } - return matchedSections; -}; + return matchedSections; + } +} module.exports = KssStyleGuide; diff --git a/test/test_kss_config.js b/test/test_kss_config.js index 45ee297d..fedb6535 100644 --- a/test/test_kss_config.js +++ b/test/test_kss_config.js @@ -33,14 +33,6 @@ describe('KssConfig object API', function() { done(); }); - it('should return a KssConfig object when called normally', function(done) { - /* eslint-disable new-cap */ - var kssConfig = kss.KssConfig(); - expect(kssConfig).to.be.an.instanceof(kss.KssConfig); - done(); - /* eslint-enable new-cap */ - }); - it('should set config when given an object', function(done) { var opts = require(pathToJSON); var kssConfig = new kss.KssConfig(opts); diff --git a/test/test_kss_modifier.js b/test/test_kss_modifier.js index e4665d13..2dc94dac 100644 --- a/test/test_kss_modifier.js +++ b/test/test_kss_modifier.js @@ -37,15 +37,6 @@ describe('KssModifier object API', function() { expect(obj.data).to.have.property('className'); done(); }); - - it('should return a KssModifier object when called normally', function(done) { - /* eslint-disable new-cap */ - var obj = kss.KssModifier({name: '.modifier'}); - expect(obj).to.be.an.instanceof(kss.KssModifier); - expect(obj.name()).to.equal('.modifier'); - done(); - /* eslint-enable new-cap */ - }); }); describe('.section()', function() { diff --git a/test/test_kss_parameter.js b/test/test_kss_parameter.js index 18b2c95f..0016448a 100644 --- a/test/test_kss_parameter.js +++ b/test/test_kss_parameter.js @@ -34,15 +34,6 @@ describe('KssParameter object API', function() { expect(obj.data).to.have.property('description'); done(); }); - - it('should return a KssParameter object when called normally', function(done) { - /* eslint-disable new-cap */ - var obj = kss.KssParameter({name: '$variable'}); - expect(obj).to.be.an.instanceof(kss.KssParameter); - expect(obj.name()).to.equal('$variable'); - done(); - /* eslint-enable new-cap */ - }); }); describe('.section()', function() { diff --git a/test/test_kss_section.js b/test/test_kss_section.js index 8fe41ef0..3bec9d59 100644 --- a/test/test_kss_section.js +++ b/test/test_kss_section.js @@ -59,15 +59,6 @@ describe('KssSection object API', function() { done(); }); - it('should return a KssSection object when called normally', function(done) { - /* eslint-disable new-cap */ - var obj = kss.KssSection({header: 'Section'}); - expect(obj).to.be.an.instanceof(kss.KssSection); - expect(obj.header()).to.equal('Section'); - done(); - /* eslint-enable new-cap */ - }); - it('should set itself as parent of modifier and parameter objects', function(done) { var section, modifier = new kss.KssModifier({name: 'modifier'}), diff --git a/test/test_kss_styleguide.js b/test/test_kss_styleguide.js index c441cfa4..11faacba 100644 --- a/test/test_kss_styleguide.js +++ b/test/test_kss_styleguide.js @@ -50,14 +50,6 @@ describe('KssStyleGuide object API', function() { expect(obj.data).to.have.property('sections'); done(); }); - - it('should return a KssStyleGuide object when called normally', function(done) { - /* eslint-disable new-cap */ - var obj = kss.KssStyleGuide(); - expect(obj).to.be.an.instanceof(kss.KssStyleGuide); - done(); - /* eslint-enable new-cap */ - }); }); describe('.toJSON()', function() {