Code coverage report for master/lib/jsdoc/tutorial/resolver.js

Statements: 98.59% (70 / 71)      Branches: 93.75% (30 / 32)      Functions: 100% (8 / 8)      Lines: 98.59% (70 / 71)      Ignored: none     

All files » master/lib/jsdoc/tutorial/ » resolver.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194                      1 1 1 1 1   1     1 1             1   84             1                                     1 84 84 84   84     67 16 16 16     16     67 61   6       17 17 33                 1 103 7     96   96               1 18 18 18 18 18 18     18 150     150 134 134   134           50 50         49 49       35 35   35               99 99             1 11 11   11 53     53 7     46     46 46       46 34 45   45 1     44            
/**
    @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
        Eif (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);
                }
            });
        }
    });
};