Source: builder/base/nunjucks/kss_builder_base_nunjucks.js

'use strict';

/**
 * The `kss/builder/base/nunjucks` module loads the KssBuilderBaseNunjucks
 * class, a `{@link KssBuilderBase}` sub-class using no templating.
 * ```
 * const KssBuilderBaseNunjucks = require('kss/builder/base/nunjucks');
 * ```
 * @module kss/builder/base/nunjucks
 */

// Import the KssBuilderBase class. We will extend it to scaffold our builder.
// Note: Since you will be building a sub-class outside of the kss module, this
// next line will be: const KssBuilderBase = require('kss/builder/base');
const KssBuilderBase = require('../kss_builder_base.js');
const path = require('path');
const nunjucks = require('nunjucks');
const Promise = require('bluebird');

const fs = Promise.promisifyAll(require('fs-extra'));

// Define "KssBuilderBaseNunjucks" as the name of our nunjucks builder class.
//
// Our builder is a sub-class of the KssBuilderBase class with additional
// functionality added by overriding the parent methods.
class KssBuilderBaseNunjucks extends KssBuilderBase {
  /**
   * Create a KssBuilderBaseNunjucks object.
   *
   * ```
   * const KssBuilderBaseNunjucks = require('kss/builder/base/nunjucks');
   * const builder = new KssBuilderBaseNunjucks();
   * ```
   */
  constructor() {
    super();

    // Store the version of the builder API that the builder instance is
    // expecting; we will verify this in loadBuilder().
    this.API = '3.0';

    // Allow the use of other file extensions
    // @see: https://mozilla.github.io/nunjucks/templating.html#file-extensions
    this.addOptionDefinitions({
      extension: {
        multiple: false,
        describe: 'The preferred Nunjucks extension',
        group: 'Style guide:',
        default: 'njk'
      }
    });
  }

  /**
   * Allow the builder to preform pre-build tasks or modify the KssStyleGuide
   * object.
   *
   * The method can be set by any KssBuilderBase sub-class to do any custom tasks
   * after the KssStyleGuide object is created and before the HTML style guide
   * is built.
   *
   * @param {KssStyleGuide} styleGuide The KSS style guide in object format.
   * @returns {Promise.<KssStyleGuide>} A `Promise` object resolving to
   *   `styleGuide`.
   */
  prepare(styleGuide) {
    // First we let KssBuilderBase.prepare() clean-up the style guide object.
    return super.prepare(styleGuide).then((styleGuide) => {
      // Then we do our own prep work inside this Promise's .then() method.
      this.Nunjucks = nunjucks;
      this.Nunjucks.installJinjaCompat();
      this.NunjucksEnv = this.Nunjucks.configure({
        autoescape: false
      });

      const prepTasks = [];
      // Create a new destination directory.
      prepTasks.push(this.prepareDestination('kss-assets'));
      // Load modules that extend Nunjucks.
      prepTasks.push(this.prepareExtend(this.NunjucksEnv));

      return Promise.all(prepTasks).then(() => {
        return Promise.resolve(styleGuide);
      });
    });
  }

  /**
   * Build the HTML files of the style guide given a KssStyleGuide object.
   *
   * @param {KssStyleGuide} styleGuide The KSS style guide in object format.
   * @returns {Promise} A `Promise` object.
   */
  build(styleGuide) {
    const options = {};
    options.templateExtension = this.options.extension;
    options.emptyTemplate = '{# Cannot be an empty string. #}';
    this.templates = {};

    // Returns a promise to read/load a template provided by the builder.
    options.readBuilderTemplate = (name) => {
      const templateName = `${name}.${options.templateExtension}`;
      const templatePath = path.resolve(this.options.builder, templateName);

      return fs.readFileAsync(templatePath, 'utf8').then((content) => {
        return this.Nunjucks.compile(content, this.NunjucksEnv);
      });
    };

    // Returns a promise to read/load a template specified by a section.
    options.readSectionTemplate = (name, filepath) => {
      return fs.readFileAsync(filepath, 'utf8').then((contents) => {
        const compiled = this.Nunjucks.compile(contents, this.NunjucksEnv);
        this.templates[name] = compiled;

        return compiled;
      });
    };

    // Returns a promise to load an inline template from markup.
    options.loadInlineTemplate = (name, markup) => {
      const compiled = this.Nunjucks.compile(markup, this.NunjucksEnv);
      this.templates[name] = compiled;

      return Promise.resolve(compiled);
    };

    // Returns a promise to load the data context given a template file path.
    options.loadContext = (filepath) => {
      let context;
      // Load sample context for the template from the sample .json file.
      try {
        context = require(path.join(path.dirname(filepath), path.basename(filepath, path.extname(filepath)) + '.json'));
        // require() returns a cached object. We want an independent clone of
        // the object so we can make changes without affecting the original.
        context = JSON.parse(JSON.stringify(context));
      } catch (error) {
        context = {};
      }

      return Promise.resolve(context);
    };

    // Returns a promise to get a template by name.
    options.getTemplate = (name) => {
      return Promise.resolve(this.templates[name]);
    };

    // Returns a promise to get a template's markup by name.
    options.getTemplateMarkup = (name) => {
      return options.getTemplate(name).then((template) => {
        return template.tmplStr;
      });
    };

    // Renders a template and returns the markup.
    options.templateRender = (template, context) => {
      try {
        return this.Nunjucks.render(template, context);
      } catch (e) {
        // istanbul ignore next
        return '';
      }
    };

    // Converts a filename into a Handlebars partial name.
    options.filenameToTemplateRef = (filename) => {
      // Return the filename without the full path or the file extension.
      return path.basename(filename, path.extname(filename));
    };

    return this.buildGuide(styleGuide, options);
  }
}

// Export our "KssBuilderBaseNunjucks" class.
module.exports = KssBuilderBaseNunjucks;