UNPKG

kss

Version:

The Node.js port of KSS: A methodology for documenting CSS and building style guides

612 lines (558 loc) 20.4 kB
'use strict'; 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} class of the `kss` module: * ``` * const KssSection = require('kss').KssSection; * ``` * @private * @module kss/lib/kss_section */ /** * A KssSection object represents a single section of a `KssStyleGuide`. * * A section of a style guide can be used for: * - a category of sub-sections, since sections are hierarchical * - a component in a design * - a mixin (or similar concept) of a CSS preprocessor * * This class is normally accessed via the [`kss`]{@link module:kss} module: * ``` * const KssSection = require('kss').KssSection; * ``` * * @alias module:kss.KssSection */ class KssSection { /** * Creates a KssSection object and stores the given data. * * If passed an object, it will add the properties to the section. * * @param {Object} [data] An object of data. */ constructor(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: '', colors: [], source: { filename: '', path: '', line: '' }, 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. */ 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(), colors: this.colors(), markup: this.markup(), source: this.source(), // 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(); }); // 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]); } } return returnObject; } /** * 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; } 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 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; } this.data.header = header; // Allow chaining. return this; } /** * 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; } 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. */ customPropertyNames() { 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. */ custom(name, value) { if (typeof value === 'undefined') { /* eslint-disable no-undefined */ return this.meta.customPropertyNames.indexOf(name) === -1 ? undefined : this.data[name]; } if (this.styleGuide()) { this.styleGuide().customPropertyNames(name); } 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. */ deprecated(deprecated) { if (typeof deprecated === 'undefined') { return this.data.deprecated; } this.data.deprecated = !!deprecated; // Allow chaining. return this; } /** * 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.experimental = !!experimental; // Allow chaining. return this; } /** * 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; } // @TODO: Tell the KssStyleGuide about the update. reference = reference.toString(); // 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; } /** * 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; } } this.data.referenceNumber = referenceNumber; // 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; } this.data.referenceURI = referenceURI; // 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; } // @TODO: The weight needs to bubble-up to the KssStyleGuide weightMap. this.data.weight = weight; // Allow chaining. return this; } /** * 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; } this.meta.depth = depth; // Allow chaining. return this; } /** * 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; } this.data.markup = markup; // Allow chaining. return this; } /** * Gets or sets the colors of the section. * * If the `colors` value is provided, the `colors` for this section is set. * Otherwise, the `colors` of the section is returned. * * @param {Array} [colors] Collection of color object. * @returns {KssSection|string|boolean} If `colors` is given, the `KssSection` object is * returned to allow chaining of methods. Otherwise, the colors of the section * is returned, or `false` if none. */ colors(colors) { if (typeof colors === 'undefined') { return this.data.colors; } this.data.colors = colors; // Allow chaining. return this; } /** * Gets or sets the file information of the file where the section was * originally found. * * If the `source` parameter is provided, the `source` for this section is * set. Otherwise, the `source` of the section is returned. * * The `source` object contains the following information: * - filename: The name of the file. * - path: The full path of the file. * - line: The line number where the KSS comment is found. * * @param {{filename, path, line}} [source] The source file information where * the section was originally found. * @returns {KssSection|{filename, path, line}} If `source` is given, the * `KssSection` object is returned to allow chaining of methods. Otherwise, * the source of the section is returned. */ source(source) { if (typeof source === 'undefined') { return this.data.source; } if (source.filename) { this.data.source.filename = source.filename; } if (source.path) { this.data.source.path = source.path; } if (source.line) { this.data.source.line = source.line; } // Allow chaining. return this; } /** * Gets the name of the file where the section was originally found. * * @returns {string} Returns the source file's path relative to the base path. */ sourceFileName() { return this.data.source.filename; } /** * Gets the line number where the section was found in the original source file. * * @returns {string} Returns the source file's line number. */ sourceLine() { return this.data.source.line; } /** * 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; } // 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]; } } } // No matching property found. return false; } /** * 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;