Source: lib/kss.js

'use strict';

/**
 * The core kss API can be imported with:
 * ```
 * var kss = require('kss');
 * ```
 *
 * The main object is a function that will build a style guide given the correct
 * options.
 * ```
 * kss(options);
 * ```
 * The various constructors and methods can then be accessed with:
 * ```
 * const KssStyleGuide = require('kss').KssStyleGuide;
 * const KssSection    = require('kss').KssSection;
 * const KssModifier   = require('kss').KssModifier;
 * const KssParameter  = require('kss').KssParameter;
 *
 * const kss           = require('kss');
 * const traverse      = require('kss').traverse();
 * const parse         = require('kss').parse();
 * ```
 *
 * The usual style guide build process:
 * - The command-line tool uses `lib/cli` to gather the command line options,
 *   which passes the options to `kss()`.
 * - The `kss()` function takes an object of options and calls `traverse()`.
 * - The `traverse()` function reads all the `source` directories and calls
 *   `parse()`.
 * - The `parse()` function finds the KSS comments in the provided text, creates
 *   a JSON object containing all the parsed data and passes it the
 *   `new KssStyleGuide(data)` constructor to create a style guide object.
 * - The `kss()` function loads the specified builder, which is a collection of
 *   files and an optional module that provides a sub-class of `KssBuilderBase`.
 * - `kss()` passes its options to the builder the builder's `addOptions()`
 *   method.
 * - The builder's `prepare()` method is given the `KssStyleGuide` object and
 *   can do any pre-build tasks, like initializing its templating system (if it
 *   has one.)
 * - The builder's `build()` method is run and the style guide files are
 *   created in the specified destination.
 * @module kss
 */

const KssBuilderBase = require('../builder/base'),
  KssStyleGuide = require('./kss_style_guide.js'),
  path = require('path'),
  Promise = require('bluebird'),
  traverse = require('./traverse.js');

/**
 * Builds a style guide given the proper options.
 *
 * @param {object} [options] A collection of options.
 * @returns {Promise.<KssStyleGuide|null>} A `Promise` object resolving to a
 *   `KssStyleGuide` object, or to `null` if the clone option is used.
 */
const kss = function(options) {
  options = options || {};

  let builder;

  // Confirm this is a compatible builder.
  return KssBuilderBase.loadBuilder(options.builder ? KssBuilderBase.builderResolve(options.builder) : require('../builder/handlebars')).catch(error => {
    let catchLoadFailure;
    const supportedBuilders = [
      'builder/twig',
      'builder/nunjucks'
    ];
    // If the builder was specified and there was no existing
    // path relative to the working directory, we should try again while using
    // the path to kss-node's builder.
    // istanbul ignore if
    if (supportedBuilders.indexOf(options.builder) > -1) {
      options.builder = path.resolve(__dirname, '..', options.builder);
      catchLoadFailure = KssBuilderBase.loadBuilder(options.builder);
    } else {
      catchLoadFailure = Promise.reject(error);
    }

    return catchLoadFailure.catch(error => {
      // If we fail to create a builder, set up builder.logError() for the
      // next .catch().
      builder = {
        logError: (typeof options.logErrorFunction === 'function') ? options.logErrorFunction : console.error
      };
      throw error;
    });
  }).then(newBuilder => {
    builder = newBuilder;

    // Add the options to the builder.
    builder.addOptions(options);

    // If requested, clone a builder and exit.
    if (builder.getOptions('clone')) {
      builder.log('Creating a new style guide builder in ' + builder.getOptions('clone') + '...');

      return builder.clone(builder.getOptions('builder'), builder.getOptions('clone')).then(() => {
        builder.log('You can change it as you like, and use it to build your style guide using the "builder" option');
        return Promise.resolve();
      });
    }

    // If no source is specified, display helpful error and exit.
    if (!builder.getOptions('source').length) {
      return Promise.reject(new Error('No "source" option specified.'));
    }

    let parseTask;

    // Check the 'source' array for paths ending in .json and convert the first
    // one into a KssStyleGuide object.
    let jsonInput = builder.getOptions('source').find(filePath => {
      return (path.extname(filePath) === '.json');
    });

    if (jsonInput) {
      try {
        let styleGuide = require(jsonInput);
        parseTask = Promise.resolve(new KssStyleGuide(styleGuide));
      } catch (error) {
        parseTask = Promise.reject(new Error('Failed to open JSON file: ' + jsonInput));
      }
    }

    // Then traverse the source and parse the files found.
    if (!parseTask) {
      if (builder.getOptions('verbose')) {
        builder.log('...Parsing your style guide:');
      }

      parseTask = traverse(builder.getOptions('source'), {
        header: true,
        markdown: true,
        markup: true,
        mask: builder.getOptions('mask'),
        custom: builder.getOptions('custom')
      });
    }

    if (builder.getOptions('json')) {
      return parseTask.then(styleGuide => {
        return styleGuide.toJSON();
      });
    }

    return parseTask.then(styleGuide => {
      // Then allow the builder to prepare itself and the KssStyleGuide object.
      return builder.prepare(styleGuide);
    }).then(styleGuide => {
      // Then build the style guide.
      return builder.build(styleGuide);
    }).then(styleGuide => {
      if (builder.getOptions('verbose')) {
        builder.log('Style guide build completed successfully!');
      }
      return Promise.resolve(styleGuide);
    });
  }).catch(error => {
    builder.logError(error);
    throw error;
  });
};

module.exports = kss;
module.exports.KssStyleGuide = require('./kss_style_guide.js');
module.exports.KssSection = require('./kss_section.js');
module.exports.KssModifier = require('./kss_modifier.js');
module.exports.KssParameter = require('./kss_parameter.js');
module.exports.parse = require('./parse.js');
module.exports.traverse = traverse;