leafdoc
Version:
A lightweight NaturalDocs-like LeafletJS-style documentation generator
1,013 lines (811 loc) • 33.4 kB
JavaScript
var leafdoc = (function (fs, path, Handlebars, MarkdownRenderer, xRegExp, unicodeRegExpIDStart, unicodeRegExpIDContinue, mec) {
'use strict';
fs = fs && Object.prototype.hasOwnProperty.call(fs, 'default') ? fs['default'] : fs;
path = path && Object.prototype.hasOwnProperty.call(path, 'default') ? path['default'] : path;
Handlebars = Handlebars && Object.prototype.hasOwnProperty.call(Handlebars, 'default') ? Handlebars['default'] : Handlebars;
MarkdownRenderer = MarkdownRenderer && Object.prototype.hasOwnProperty.call(MarkdownRenderer, 'default') ? MarkdownRenderer['default'] : MarkdownRenderer;
xRegExp = xRegExp && Object.prototype.hasOwnProperty.call(xRegExp, 'default') ? xRegExp['default'] : xRegExp;
unicodeRegExpIDStart = unicodeRegExpIDStart && Object.prototype.hasOwnProperty.call(unicodeRegExpIDStart, 'default') ? unicodeRegExpIDStart['default'] : unicodeRegExpIDStart;
unicodeRegExpIDContinue = unicodeRegExpIDContinue && Object.prototype.hasOwnProperty.call(unicodeRegExpIDContinue, 'default') ? unicodeRegExpIDContinue['default'] : unicodeRegExpIDContinue;
mec = mec && Object.prototype.hasOwnProperty.call(mec, 'default') ? mec['default'] : mec;
var templateDir = path.join(path.resolve(), 'templates', 'basic');
var templates = Object.create(null);
function setTemplateDir(newDir) {
templates = Object.create(null);
templateDir = newDir;
}
function getTemplate(templateName) {
if (!templates[templateName]) {
templates[templateName] = Handlebars.compile(fs.readFileSync(path.join(templateDir, (templateName + ".hbs"))).toString());
}
return templates[templateName];
}
var _AKAs = {};
function setAKAs(akas) {
// console.log('Template thing updating AKAs');
_AKAs = akas;
}
/// TODO: Allow setting markdown-it options (enable/disable tables, autolinks, abbrevs, etc)
var markdownRenderer;
function markdown(str) {
if (!markdownRenderer) {
markdownRenderer = MarkdownRenderer(/* markdown-it options */);
}
return markdownRenderer.render(str);
}
// Helper to replace AKAs in markdown links.
function replaceAKAs(str) {
str = str.trim();
// [...](#a) → [...](#b)
str = str.replace(/\[([^\[\]]*)\]\(#([^\(\)]*)\)/g, function (str, a, b) {
if (b in _AKAs) {
// console.log(' Replacing link AKA: ', `[${a}](#${[b]}) → [${a}](#${ _AKAs[b] })`);
return ("[" + a + "](#" + (_AKAs[b]) + ")");
} else {
// console.log(' Ignoring link AKA: ', `[${a}](#${[b]})`);
return str;
}
});
// `a` → [`a`](#b)
str = str.replace(/`([^`]*)`/g, function (str, a) {
if (a in _AKAs) {
// console.log(' Replacing code literal AKA: ', `\`${a}\` → [\`${a}\`](#${ _AKAs[a] })`);
return ("[`" + a + "`](#" + (_AKAs[a]) + ")");
} else {
// console.log(' Ignoring code literal AKA: ', '`' + a + '`');
return str;
}
});
// Remove links inside links (bug #63)
// [pre[`code`](trash)post](url) → [pre`code`post](url)
// regexp is [.*[`.*`](.*).*](.*) , but with each .* replaced with
// ([^`\[\]\(\)]*), i.e. a capture group of anything that is not ()[]` .
str = str.replace(/\[([^`\[\]\(\)]*)\[`([^`\[\]\(\)]*)`\]\([^`\[\]\(\)]*\)([^`\[\]\(\)]*)\]\(([^`\[\]\(\)]*)\)/g,
function (str, pre, code, post, url) {
//console.log(" Removing nested link: ", ` ${str} → [${pre}\`${code}\`${post}](${url})`);
return ("[" + pre + "`" + code + "`" + post + "](" + url + ")");
}
);
return str;
}
Handlebars.registerHelper('markdown', function markdownHelper(str) {
if (!str) { return ''; }
if (str instanceof Array) {
str = str.join('\n').trim();
// str = str.join(' ');
}
return markdown(replaceAKAs(str))
.trim()
.replace('<p>', '')
.replace('</p>', '');
});
Handlebars.registerHelper('rawmarkdown', function rawmarkdownHelper(str) {
if (!str) { return ''; }
if (str instanceof Array) {
str = str.join('\n');
}
return markdown(replaceAKAs(str));
});
// Automatically link to AKAs, mostly used on method/function/param/option data types.
Handlebars.registerHelper('type', function typeHelper(str) {
if (!str) { return ''; }
if (str in _AKAs) {
var id = _AKAs[str];
return ("<a href='#" + id + "'>" + str + "</a>");
} else {
// Should be a built-in type
return str;
}
});
// JSON stringify the stuff.
Handlebars.registerHelper('json', function jsonHelper(obj) {
return JSON.stringify(obj, undefined, 1);
});
// Comparison helper. Inspired from https://github.com/helpers/handlebars-helpers
Handlebars.registerHelper('equals', function equalityHelper(a, b) {
return a == b;
});
// One or more lines starting with whitespace and two or more forward slashes,
// or
// whitespace-slash-asterisk whatever asterisk-slash.
var commentBlock = xRegExp('^(?<multiline> ( (?!\\n) \\s*\\/{2,}\\s*.*\\n )+ ) \
| \
(\\s*\\/\\* (?<block> [\\s\\S]*?) \\*\\/) \
', 'gmnx');
var leafdocFile = xRegExp('^(?<block> [\\s\\S]+ )$', 'gmnx');
// Inside each line of a comment /* */ block, skips the leading spaces / asterisk (if any)
var leadingBlock = xRegExp('^ ( \\s* \\* \\s? )? (?<line> .* ) $', 'nx');
// Inside each line of a comment // block, skips the leading //
var leadingLine = xRegExp('^ \\s*/{0,4}\\s{0,1} (?<line> .* ) $', 'nx');
// Inside .leafdoc files, match any line without skipping anything
var anyLine = xRegExp('^ (?<line> .* ) $', 'nx');
// Parses a 🍂 directive, init'd at redoLeafDirective()
global.leafDirective = redoLeafDirective('🍂');
function getLeafDirective() {
return global.leafDirective;
}
// Re-builds the 🍂 directive based on a different leading character
function redoLeafDirective(char) {
global.leafDirective = new RegExp(("\\s*" + char + "(?<directive>\\S+)(\\s+(?<content>.+?))?(?:; |$)"), 'g');
return global.leafDirective;
}
// Parses an identifier, allowing only unicode ID_Start and ID_Continue characters
// An identifier allows dots in it, to allow for namespacing identifiers.
// TODO: An identifier shall allow an underscore or dollar at the beginning, as JS does.
var identifier = xRegExp.build('^(({{ID_Start}} | _ | \$) ( {{ID_Continue}} | \\. | : )*)$', {
ID_Start: unicodeRegExpIDStart, // eslint-disable-line camelcase
ID_Continue: unicodeRegExpIDContinue // eslint-disable-line camelcase
}, 'nx');
// Parses a function name, its return type, and its parameters
// Funny thing about functions is that not all printable characters are allowed. Thus,
// use unicode ID_Start and ID_Continue character sets via 'identifier' sub-regexp.
// eslint-disable-next-line no-useless-escape
var functionDefinition = xRegExp.build('^ (?<name> {{identifier}} ) (?<required> (\\?{0,1}) ) \\s* (?<params> \\( .* \\) ){0,1} \\s* ( \\: \\s* (?<type> .+? ) )? ( = \\s* (?<default> .+ ) \\s* ){0,1} \$', {
identifier: identifier
}, 'nx');
// var functionParam = xRegExp.build('^ \\s* (?<name> {{identifier}}) \\s* ( \\: \\s* (?<type> .+ ) \\s* ) $', {identifier: identifier}, 'nx');
// var functionParam = xRegExp.build('\\s* (?<name> ( {{identifier}} | … ) \\?{0,1} ) \\s* ( \\: \\s* (?<type> [^,]+ ) \\s* ) (, | \\)) ', {identifier: identifier}, 'gnx');
var functionParam = xRegExp.build('\\s* (?<name> ( {{identifier}} | … ) \\?{0,1} ) \\s* ( \\: \\s* (?<type> [^,]+ ) \\s* )? (, | \\)) ', {identifier: identifier}, 'gnx');
// Parses a miniclass name and its real class between parentheses.
var miniclassDefinition = xRegExp('^ (?<miniclass> .+ ) \\s* \\( (?<realclass> .+ ) \\) $', 'nx');
// Parses a UML-like relationship definition
var relationshipDefinition = xRegExp("^\n(?<type> \\S+ ) \\s*\n(?<namespace> [^,\\s]+ ) \\s*\n(, \\s* (?<cardinalityFrom> [^,\\s]*) )? \\s*\n(, \\s* (?<cardinalityTo> [^,\\s]*) )? \\s*\n(, \\s* (?<label> .+ )? )?\n\\s* $", 'nx');
// Trivial file/Str parser.
// Assumes that the whole string is leafdoc docstrings.
// This is the old 'isSource=false' behaviour.
function trivialParser(str) {
// Return an array with just one element, the whole string.
return [str];
}
// Generic parser, depends on multiline-extract-comments and returns a simplified
// version of the comments data structure
function multilangParser(str, filename) {
var mecBlocks = mec(str, {filename: filename || 'leafdoc_tmp.js'});
var blocks = Object.values(mecBlocks).map(function (mecBlock) { return mecBlock.content.trim(); }).filter(function (block) { return block && block !== ''; });
return blocks;
}
// 🍂class Leafdoc; Represents the Leafdoc parser
var Leafdoc = function Leafdoc(options) {
this._namespaces = {};
this._knownDocumentables = [
'example',
'constructor',
'destructor',
'factory',
'option',
'event',
'method',
'function',
'property'
];
this._documentableLabels = {
'example': 'Usage example',
'factory': 'Creation',
'constructor': 'Constructor',
'destructor': 'Destructor',
'option': 'Options',
'event': 'Events',
'method': 'Methods',
'function': 'Functions',
'property': 'Properties'
};
this._inheritableDocumentables = [
'method',
'function',
'event',
'option',
'property'
];
// Holds a list of miniclasses, with the miniclass as key and the real
// class as value.
// Maybe a better name would be "subnamespaces" or whatever.
this._miniclasses = {};
this._AKAs = {};
// 🍂section
// 🍂aka Leafdoc options
if (options) {
// 🍂option templateDir: String = 'templates/basic'
// Defines which subdirectory (relative to the directory the curent JS
// script is running) holds the handlebars template files for building up the HTML.
if (options.templateDir) {
setTemplateDir(options.templateDir);
}
// 🍂option showInheritancesWhenEmpty: Boolean = false
// When `true`, child classes/namespaces will display documentables from ancestors, even if the child class doesn't have any of such documentables.
// e.g. display inherited events even if the child doesn't define any new events.
this.showInheritancesWhenEmpty = options.showInheritancesWhenEmpty || false;
// 🍂option leadingCharacter: String = '🍂'
// Overrides the Leaf symbol as the leading character for documentation lines.
// See also [`setLeadingCharacter`](#leafdoc-setleadingcharacter).
if (options.leadingCharacter) {
this.setLeadingCharacter(options.leadingCharacter);
}
// 🍂option customDocumentables: Map = {}
// A key-value map. Each pair will be passed to [`registerDocumentable`](#leafdoc-registerdocumentable).
if (options.customDocumentables) {
for (var i in options.customDocumentables) {
this.registerDocumentable(i, options.customDocumentables[i]);
}
}
// 🍂option verbose: Boolean = false
// Set to `true` to display more information as files are being read.
if (options.verbose) {
this._verbose = options.verbose;
}
}
};
// 🍂method registerDocumentable (name: String, label?: String, inheritable?: Boolean): this
// Registers a new documentable type, beyond the preset ones (function,
// property, etc). New documentable should also not be an already used
// keyword (class, namespace, inherits, etc).
// When registering new documentables, make sure that there is an appropriate
// template file for it.
// Set `label` to the text for the sections in the generated docs.
// `inheritable` parameter determines documentable can be inherited via inherits keyword in a subclass.
Leafdoc.prototype.registerDocumentable = function registerDocumentable (name, label, inheritable) {
this._knownDocumentables.push(name, label);
if (label) {
this._documentableLabels[name] = label;
}
if (inheritable) {
this._inheritableDocumentables.push(name);
}
return this;
};
// 🍂method getTemplateEngine(): Handlebars
// Returns handlebars template engine used to render templates.
// You can use it for override helpers or register a new one.
Leafdoc.prototype.getTemplateEngine = function getTemplateEngine () {
return template.engine;
};
// 🍂method setLeadingCharacter(char: String): this
// In the rare case you don't want to use 🍂 as the leading character for
// leaf directives, run this function with the desired character, e.g.
// `setLeadingCharacter('@');`
// The new leading character will apply only to files/dirs/strings parsed from
// that moment on, so it's a good idea to call this before anything else.
Leafdoc.prototype.setLeadingCharacter = function setLeadingCharacter (char) {
// console.log('Setting leading character to', char);
redoLeafDirective(char);
};
// 🍂method addDir (dirname: String, extensions?: String[]): this
// Recursively scans a directory, and parses any files that match the
// given `extensions` (by default `.js` and `.leafdoc`, mind the dots).
// Files with a `.leafdoc` extension will be treated as leafdoc-only
// instead of source.
Leafdoc.prototype.addDir = function addDir (dirname, extensions) {
if (!extensions) {
extensions = ['.js', '.leafdoc'];
}
var filenames = fs.readdirSync(dirname);
for (var i in filenames) {
var filename = path.join(dirname, filenames[i]);
// Check if dir, recurse if so
var stats = fs.lstatSync(filename);
if (stats.isDirectory()) {
this.addDir(filename, extensions);
} else if (extensions.indexOf(path.extname(filename)) !== -1) {
if (this._verbose) {
console.log('Leafdoc processing file: ', filename);
}
this.addFile(filename);
}
}
return this;
};
// 🍂method addFile(filename: String): this
// Parses the given file using [`addBuffer`](#leafdoc-addbuffer).
Leafdoc.prototype.addFile = function addFile (filename) {
return this.addBuffer(fs.readFileSync(filename), filename);
};
// 🍂method addBuffer(buf: Buffer, filename?: String): this
// Parses the given buffer using [`addStr`](#leafdoc-addstr) underneath.
Leafdoc.prototype.addBuffer = function addBuffer (buf, filename) {
return this.addStr(buf.toString(), filename);
};
// 🍂method addStr(str: String, filename?: String): this
// Parses the given string for Leafdoc directives.
Leafdoc.prototype.addStr = function addStr (str, filename) {
var assign;
// Leaflet files use DOS line feeds, which screw up things.
str = str.replace(/\r\n?/g, '\n');
var ns = '__default';// namespace (or class)
var sec = '__default';// section
var dt = '';// Type of documentable
var dc = '';// Name of documentable
var alt = 0;// Will auto-increment for documentables with 🍂alternative
var altAppliesTo = null;// Ensures 'alt' resets when documentable changes
// Scope of the current line (parser state): ns, sec or dc.
// (namespace, section, documentable)
var scope = '';
var namespaces = this._namespaces;
var currentNamespace, currentSection, currentDocumentable;
// Temporal placeholder - section comments and AKAs are dangling until
// the documentable type is known
var sectionComments = [];
var sectionAKA = [];
var sectionIsUninheritable = false;
var parser = path.extname(filename) === '.leafdoc' ?
trivialParser :
multilangParser;
var parsedBlocks = parser(str, filename);
// 1: Fetch comment blocks (from the parser). For each block...
for (var i = 0, l = parsedBlocks.length; i < l; i++) {
var commentBlock = parsedBlocks[i];
var blockIsEmpty = true;
// Edge case: some comment blocks in markdown might choke up
if (!commentBlock) {
break;
}
// 2: Split into lines
var lines = commentBlock.split('\n');
// 3: Split lines into directives (separated by ";")
var directives = [];
for (var i$1 in lines) {
var line = lines[i$1];
var lineIsValid = false;
var parsedCharacters = 0;
var match = (void 0);
// In "param foo, bar", directive is "param" and content is "foo, bar"
while (match = getLeafDirective().exec(line)) {
if (match.groups.content) {
match.groups.content = match.groups.content.trim();
}
directives.push([
match.groups.directive,
match.groups.content
]);
// console.log('directive match: ', match);
blockIsEmpty = false;
lineIsValid = true;
parsedCharacters = match.index + match[0].length;
}
if (lineIsValid) {
var trailing = line.substr(parsedCharacters).trim();
if (trailing) {
directives.push(['comment', trailing]);
}
}
if (!lineIsValid && !blockIsEmpty) {
// implicit 🍂comment directive.
directives.push(['comment', line]);
}
}
for (var i$2 in directives) {
var directive = directives[i$2][0],
content = directives[i$2][1];
// 4: Parse 🍂 directives
if (directive === 'class' || directive === 'namespace') {
ns = content.trim();
sec = '__default';
scope = 'ns';
} else if (directive === 'miniclass') {
var split = miniclassDefinition.exec(content);
if (!split) {
console.error('Invalid miniclass definition: ', content);
console.log(split);
} else {
ns = split[1].trim();
var miniparent = split[2];
sec = '__default';
scope = 'ns';
this._miniclasses[ns] = miniparent;
}
} else if (directive === 'section') {
sec = content || '__default';
scope = 'sec';
} else if (this._knownDocumentables.indexOf(directive) !== -1) {
scope = 'dc';
dt = directive;
dc = '';// The name of the documentable will be set later
}
// console.log(scope, '-', directive, '-', content);
if (scope === 'ns') {
if (!namespaces.hasOwnProperty(ns)) {
// console.log('Defining class/namespace ', ns);
namespaces[ns] = {
name: ns,
aka: [],
comments: [],
supersections: {},
inherits: [],
relationships: [],
};
}
currentNamespace = namespaces[ns];
if (directive === 'aka') {
currentNamespace.aka.push(content);
}
if (directive === 'comment') {
currentNamespace.comments.push(content);
}
if (directive === 'inherits') {
currentNamespace.inherits.push(content);
}
if (directive === 'relationship') {
var split = relationshipDefinition.exec(content);
currentNamespace.relationships.push({
type: split[1],
namespace: split[2],
cardinalityFrom: split[3],
cardinalityTo: split[4],
label: split[5],
});
}
}
if (scope === 'sec') {
if (directive === 'comment') {
sectionComments.push(content);
}
if (directive === 'aka') {
sectionAKA.push(content);
}
if (directive === 'uninheritable') {
sectionIsUninheritable = true;
}
}
if (scope === 'dc') {
if (!currentNamespace) {
console.error('Error: No class/namespace set when parsing through:');
console.error(commentBlock);
}
if (!currentNamespace.supersections.hasOwnProperty(dt)) {
currentNamespace.supersections[dt] = {
name: dt,
aka: [],
comments: [],
sections: {}
};
}
if (!currentNamespace.supersections[dt].sections.hasOwnProperty(sec)) {
currentNamespace.supersections[dt].sections[sec] = {
name: sec,
aka: sectionAKA,
comments: sectionComments,
uninheritable: sectionIsUninheritable,
documentables: {},
type: dt
};
sectionAKA = [];
sectionComments = [];
sectionIsUninheritable = false;
}
currentSection = currentNamespace.supersections[dt].sections[sec];
// console.log(currentSection);
// console.log(directive);
if (this._knownDocumentables.indexOf(directive) !== -1) {
// Documentables might have more than their name as content.
// All documentables will follow the syntax for functions,
// with optional parameters, optional required flag, optional type, and optional default value.
// console.log(content, ', ', alt);
var name = (void 0), paramString = (void 0), params = {}, type = null, defaultValue = null, optional = false;
if (content) {
var split$1 = functionDefinition.exec(content);
if (!split$1) {
console.error(("Invalid " + directive + " definition: "), content);
} else {
optional = split$1[2] == '?';
(assign = split$1, name = assign[1], paramString = assign[3], type = assign[4], defaultValue = assign[5]);
// name = split[1];
// paramString = split[3];
// type = split[4];
// defaultValue = split[5];
if (paramString) {
var match$1 = (void 0);
while (match$1 = functionParam.exec(paramString)) {
params[ match$1[1] ] = {name: match$1[1], type: match$1[2]};
}
// console.log("\"" + paramString + "\"\n\t", params);
}
}
} else {
name = '__default';
}
// Handle alternatives - just modify the name if 'alt' and 'altAppliesTo' match
if (altAppliesTo === name) {
dc = name + "-alternative-" + alt;
} else {
dc = name;
alt = 0;
altAppliesTo = null;
}
if (!currentSection.documentables.hasOwnProperty(dc)) {
currentSection.documentables[dc] = {
name: name,
aka: [],
comments: [],
params: params,// Only for functions/methods/factories
type: type ? type.trim() : null,
optional: optional,
defaultValue: defaultValue || null// Only for options, properties
};
}
currentDocumentable = currentSection.documentables[dc];
} else if (directive === 'alternative') {
alt++;
// Alternative applies to current documentable name; if name
// doesn't match, alternative has no effect.
altAppliesTo = currentDocumentable.name;
} else if (directive === 'param') {
// Params are param name, type.
/// TODO: Think about default values, or param explanation.
var split = content.split(':');
var paramName = split[0].trim();
var paramType = split[1] ? split[1].trim() : '';
currentDocumentable.params[paramName] = {name: paramName, type: paramType};
} else if (directive === 'aka') {
currentDocumentable.aka.push(content);
} else if (directive === 'comment') {
// console.log('Doing stuff with a method comments: ', content);
currentDocumentable.comments.push(content);
}
}
}
}
// console.log(this._namespaces.Leafdoc.__default[0]);
// console.log(this._namespaces.Marker.__default[0]);
// console.log(this._namespaces);
// console.log(this._namespaces.Marker.supersections.method.sections.__default);
// console.log('namespaces after addStr', this._namespaces);
return this;
};
/*
* 🍂method outputStr: String
* Outputs the documentation to a string.
* Use only after all the needed files have been parsed.
*/
Leafdoc.prototype.outputStr = function outputStr () {
this._resolveAKAs();
var out = '';
for (var ns in this._namespaces) {
out += this._stringifyNamespace(this._namespaces[ns]);
}
// console.log('miniclasses: ', this._miniclasses);
return (getTemplate('html'))({body: out});
};
/*
* 🍂method outputJSON: String
* Outputs the internal documentation tree to a JSON blob, without any formatting.
* Use only after all the needed files have been parsed.
*/
Leafdoc.prototype.outputJSON = function outputJSON () {
this._resolveAKAs();
return JSON.stringify(this._namespaces, undefined, 1);
};
Leafdoc.prototype._stringifyNamespace = function _stringifyNamespace (namespace, isMini) {
if (!isMini && this._miniclasses.hasOwnProperty(namespace.name)) { return ''; }
var out = '';
var ancestors = this._flattenInheritances(namespace.name);
/// Ensure explicit order of the supersections (known types of documentable:
/// example, factory, options, events, methods, properties
for (var i in this._knownDocumentables) {
var s = this._knownDocumentables[i];
var supersectionHasSomething = namespace.supersections.hasOwnProperty(s);
if (s !== 'example' && this.showInheritancesWhenEmpty && !supersectionHasSomething) {
// console.log('checking for empty section with inherited stuff, ', namespace.name, s, ancestors);
for (var i in ancestors) {
var ancestor = ancestors[i];
// console.log(ancestor, this._namespaces[ancestor].supersections.hasOwnProperty(s));
if (this._namespaces[ancestor].supersections.hasOwnProperty(s)) {
for (var sec in this._namespaces[ancestor].supersections[s].sections) {
if (!this._namespaces[ancestor].supersections[s].sections[sec].uninheritable) {
supersectionHasSomething = true;
}
}
// console.log(this._namespaces[ancestor].supersections[s]);
if (supersectionHasSomething) {
namespace.supersections[s] = {
name: this._namespaces[ancestor].supersections[s].name,
sections: {}
};
}
}
}
}
if (supersectionHasSomething) {
out += this._stringifySupersection(namespace.supersections[s], ancestors, namespace.name, isMini);
}
}
if (!isMini) {
for (var i in this._miniclasses) {
if (this._miniclasses[i] === namespace.name) {
out += this._stringifyNamespace(this._namespaces[i], true);
// console.log('out is now', out);
}
}
}
// console.log(namespace);
return (getTemplate('namespace'))({
name: isMini ? undefined : namespace.name,
id: namespace.id,
comments: namespace.comments,
supersections: out,
inherits: namespace.inherits,
relationships: namespace.relationships
});
};
Leafdoc.prototype._stringifySupersection = function _stringifySupersection (supersection, ancestors, namespacename, isMini) {
var sections = '';
var inheritances = '';
// The "__default" section should show above any named sections
if ('__default' in supersection.sections) {
var oldSections = supersection.sections;
supersection.sections = {__default: oldSections.__default};
for (var s in oldSections) {
if (s !== '__default')
{ supersection.sections[s] = oldSections[s]; }
}
}
for (var s in supersection.sections) {
sections += this._stringifySection(supersection.sections[s], supersection.name, false);
}
var name = supersection.name;
var label = this._documentableLabels[name] || name;
// Calculate inherited documentables.
// In the order of the ancestors, check if each documentable has already been
// selected for output, skip it if so. Group rest into inherited sections.
if (this._inheritableDocumentables.indexOf(name) !== -1) {
if (ancestors.length) {
// inheritances += 'Inherits stuff from: ' + inheritances.join(',');
var inheritedSections = [];
// Build a list of the documentables which have been already outputted
var skip = [];
for (var s in supersection.sections) {
var section = supersection.sections[s];
for (var d in section.documentables) {
skip.push(d);
}
}
// console.log('Will skip: ', skip);
for (var i in ancestors) {
var parent = ancestors[i];
// console.log('Processing ancestor ', parent);
if (this._namespaces[parent].supersections.hasOwnProperty(name)) {
var parentSupersection = this._namespaces[parent].supersections[name];
for (var s in parentSupersection.sections) {
var parentSection = parentSupersection.sections[s];
if (parentSection && !parentSection.uninheritable) {
var inheritedSection = {
name: parentSection.name === '__default' ? label : parentSection.name,
parent: parent,
documentables: [],
id: parentSection.id
};
for (var d in parentSection.documentables) {
// console.log('Checking if should show inherited ', d);
if (skip.indexOf(d) === -1) {
skip.push(d);
inheritedSection.documentables.push(parentSection.documentables[d]);
}
}
// console.log(inheritedSection.documentables);
if (inheritedSection.documentables.length) {
inheritedSections.push(inheritedSection);
}
}
}
}
}
// Inherited sections have been calculated, template them away.
for (var i in inheritedSections) {
var inheritedSection = inheritedSections[i];
inheritances += (getTemplate('inherited'))({
name: inheritedSection.name,
ancestor: inheritedSection.parent,
inherited: this._stringifySection(inheritedSection, supersection.name, namespacename),
id: inheritedSection.id
});
}
}
}
return (getTemplate('supersection'))({
name: isMini ? namespacename : label,
id: supersection.id,
comments: supersection.comments,
sections: sections,
inheritances: inheritances
});
};
Leafdoc.prototype._stringifySection = function _stringifySection (section, documentableType, inheritingNamespace, supersectionLabel) {
var name = (section.name === '__default' || inheritingNamespace) ? '' : section.name;
// if (name) console.log('Named section:', section);
// console.log('Section:', section);
// If inheriting, recreate the documentable changing the ID.
var docs = section.documentables;
if (inheritingNamespace) {
docs = [];
for (var i in section.documentables) {
var oldDoc = section.documentables[i];
docs.push({
name: oldDoc.name,
comments: oldDoc.comments,
params: oldDoc.params,
type: oldDoc.type,
defaultValue: oldDoc.defaultValue,
id: this._normalizeName(inheritingNamespace, oldDoc.name),
});
}
}
// console.log(documentableType, section.name === '__default');
return (getTemplate('section'))({
name: name,
id: section.id,
comments: section.comments,
documentables: (getTemplate(documentableType))({
documentables: docs
}),
isSecondarySection: (section.name !== '__default' && documentableType !== 'example' && !inheritingNamespace),
isInherited: !!inheritingNamespace,
supersectionLabel: this._documentableLabels[documentableType] || documentableType
});
};
// Loop through all the documentables, create an _anchor property, and
// return a plain object containing a map of all valid link names to their anchors.
Leafdoc.prototype._resolveAKAs = function _resolveAKAs () {
for (var ns in this._namespaces) {
var namespace = this._namespaces[ns];
namespace.id = this._normalizeName(namespace.name);
this._assignAKAs(namespace.id, namespace.aka);
this._assignAKAs(namespace.id, [namespace.name]);
// console.log('Resolve namespace AKAs: ', namespace.id, namespace.name, namespace.aka);
for (var ss in namespace.supersections) {
// console.log(namespace.supersections[ss]);
var supersection = namespace.supersections[ss];
var documentableType = supersection.name;
supersection.id = this._normalizeName(namespace.name, supersection.name);
this._assignAKAs(supersection.id, [((supersection.id) + "s")]);
for (var s in supersection.sections) {
var section = supersection.sections[s];
section.id = this._normalizeName(namespace.name, section.name === '__default' ? documentableType : section.name);
this._assignAKAs(section.id, section.aka);
// console.log('Resolve section AKAs: ', section.id, section.name, section.aka);
for (var d in section.documentables) {
var doc = section.documentables[d];
if (doc.name !== '__default') {// Skip comments and examples
doc.id = this._normalizeName(namespace.name, doc.name);
this._assignAKAs(doc.id, doc.aka);
// console.log('Resolve doc AKAs: ', doc.id, doc.name, doc.aka);
}
}
}
}
}
setAKAs(this._AKAs);
// console.log(this._AKAs);
};
Leafdoc.prototype._normalizeName = function _normalizeName (namespace, name) {
var id = namespace + (name ? ("-" + name) : '');
id = id.trim().replace(/[\s\.]/g, '-');
return id.toLowerCase();
};
Leafdoc.prototype._assignAKAs = function _assignAKAs (id, akas) {
for (var i in akas) {
this._AKAs[akas[i].trim()] = id;
}
};
// Given a class/namespace, recurse through inherited classes to build
// up an ordered list of clases/namespaces this class inherits from.
Leafdoc.prototype._flattenInheritances = function _flattenInheritances (classname, inheritancesSoFar) {
if (!inheritancesSoFar) {
// console.log('Resolving inheritances for ', classname);
inheritancesSoFar = [];
}
if (this._namespaces.hasOwnProperty(classname)) {
for (var i in this._namespaces[classname].inherits) {
var parent = this._namespaces[classname].inherits[i];
if (inheritancesSoFar.indexOf(parent) === -1) {
inheritancesSoFar.push(parent);
inheritancesSoFar = this._flattenInheritances(parent, inheritancesSoFar);
}
}
} else {
console.warn('Warning: Ancestor class/namespace «', classname, '» not found!');
return [];
}
// console.log(classname, '→', inheritancesSoFar);
// console.log(this._namespaces[classname].inherits);
return inheritancesSoFar;
};
return Leafdoc;
}(fs, path, Handlebars, MarkdownRenderer, xRegExp, unicodeRegExpIDStart, unicodeRegExpIDContinue, mec));
//# sourceMappingURL=leafdoc.browser.js.map