UNPKG

jsdoc-75lb

Version:

An API documentation generator for JavaScript.

194 lines (168 loc) 6.24 kB
/** @overview @author Rafa&#322; Wrzeszcz <rafal.wrzeszcz@wrzasq.pl> @license Apache License 2.0 - See file 'LICENSE.md' in this project. */ /** @module jsdoc/tutorial/resolver */ 'use strict'; var env = require('jsdoc/env'); var fs = require('jsdoc/fs'); var logger = require('jsdoc/util/logger'); var path = require('path'); var tutorial = require('jsdoc/tutorial'); var hasOwnProp = Object.prototype.hasOwnProperty; // TODO: make this an instance member of `RootTutorial`? var conf = {}; var finder = /^(.*)\.(x(?:ht)?ml|html?|md|markdown|json)$/i; /** checks if `conf` is the metadata for a single tutorial. * A tutorial's metadata has a property 'title' and/or a property 'children'. * @param {object} json - the object we want to test (typically from JSON.parse) * @returns {boolean} whether `json` could be the metadata for a tutorial. */ function isTutorialJSON(json) { // if conf.title exists or conf.children exists, it is metadata for a tutorial return (hasOwnProp.call(json, 'title') || hasOwnProp.call(json, 'children')); } /** * Root tutorial. * @type {module:jsdoc/tutorial.Root} */ exports.root = new tutorial.RootTutorial(); /** Helper function that adds tutorial configuration to the `conf` variable. * This helps when multiple tutorial configurations are specified in one object, * or when a tutorial's children are specified as tutorial configurations as * opposed to an array of tutorial names. * * Recurses as necessary to ensure all tutorials are added. * * @param {string} name - if `meta` is a configuration for a single tutorial, * this is that tutorial's name. * @param {object} meta - object that contains tutorial information. * Can either be for a single tutorial, or for multiple * (where each key in `meta` is the tutorial name and each * value is the information for a single tutorial). * Additionally, a tutorial's 'children' property may * either be an array of strings (names of the child tutorials), * OR an object giving the configuration for the child tutorials. */ function addTutorialConf(name, meta) { var i; var l; var names; if (isTutorialJSON(meta)) { // if the children are themselves tutorial defintions as opposed to an // array of strings, add each child. if (hasOwnProp.call(meta, 'children') && !Array.isArray(meta.children)) { names = Object.keys(meta.children); for (i = 0, l = names.length; i < l; ++i) { addTutorialConf(names[i], meta.children[names[i]]); } // replace with an array of names. meta.children = names; } // check if the tutorial has already been defined... if (hasOwnProp.call(conf, name)) { logger.warn('Metadata for the tutorial %s is defined more than once. Only the first definition will be used.', name ); } else { conf[name] = meta; } } else { // keys are tutorial names, values are `Tutorial` instances names = Object.keys(meta); for (i = 0, l = names.length; i < l; ++i) { addTutorialConf(names[i], meta[names[i]]); } } } /** * Add a tutorial. * @param {module:jsdoc/tutorial.Tutorial} current - Tutorial to add. */ exports.addTutorial = function(current) { if (exports.root.getByName(current.name)) { logger.warn('The tutorial %s is defined more than once. Only the first definition will be used.', current.name); } else { // by default, the root tutorial is the parent current.setParent(exports.root); exports.root._addTutorial(current); } }; /** * Load tutorials from the given path. * @param {string} filepath - Tutorials directory. */ exports.load = function(filepath) { var content; var current; var files = fs.ls(filepath, env.opts.recurse ? 10 : undefined); var name; var match; var type; // tutorials handling files.forEach(function(file) { match = file.match(finder); // any filetype that can apply to tutorials if (match) { name = path.basename(match[1]); content = fs.readFileSync(file, env.opts.encoding); switch (match[2].toLowerCase()) { // HTML type case 'xml': case 'xhtml': case 'html': case 'htm': type = tutorial.TYPES.HTML; break; // Markdown typs case 'md': case 'markdown': type = tutorial.TYPES.MARKDOWN; break; // configuration file case 'json': var meta = JSON.parse(content); addTutorialConf(name, meta); // don't add this as a tutorial return; // how can it be? check `finder' regexp default: // not a file we want to work with return; } current = new tutorial.Tutorial(name, content, type); exports.addTutorial(current); } }); }; /** Resolves hierarchical structure. */ exports.resolve = function() { var item; var current; Object.keys(conf).forEach(function(name) { current = exports.root.getByName(name); // TODO: should we complain about this? if (!current) { return; } item = conf[name]; // set title if (item.title) { current.title = item.title; } // add children if (item.children) { item.children.forEach(function(child) { var childTutorial = exports.root.getByName(child); if (!childTutorial) { logger.error('Missing child tutorial: %s', child); } else { childTutorial.setParent(current); } }); } }); };