Path: blob/master/platform/web/js/jsdoc2rst/publish.js
10279 views
/* eslint-disable strict */12'use strict';34const fs = require('fs');56class JSDoclet {7constructor(doc) {8this.doc = doc;9this.description = doc['description'] || '';10this.name = doc['name'] || 'unknown';11this.longname = doc['longname'] || '';12this.types = [];13if (doc['type'] && doc['type']['names']) {14this.types = doc['type']['names'].slice();15}16this.type = this.types.length > 0 ? this.types.join('\\|') : '*';17this.variable = doc['variable'] || false;18this.kind = doc['kind'] || '';19this.memberof = doc['memberof'] || null;20this.scope = doc['scope'] || '';21this.members = [];22this.optional = doc['optional'] || false;23this.defaultvalue = doc['defaultvalue'];24this.summary = doc['summary'] || null;25this.classdesc = doc['classdesc'] || null;2627// Parameters (functions)28this.params = [];29this.returns = doc['returns'] ? doc['returns'][0]['type']['names'][0] : 'void';30this.returns_desc = doc['returns'] ? doc['returns'][0]['description'] : null;3132this.params = (doc['params'] || []).slice().map((p) => new JSDoclet(p));3334// Custom tags35this.tags = doc['tags'] || [];36this.header = this.tags.filter((t) => t['title'] === 'header').map((t) => t['text']).pop() || null;37}3839add_member(obj) {40this.members.push(obj);41}4243is_static() {44return this.scope === 'static';45}4647is_instance() {48return this.scope === 'instance';49}5051is_object() {52return this.kind === 'Object' || (this.kind === 'typedef' && this.type === 'Object');53}5455is_class() {56return this.kind === 'class';57}5859is_function() {60return this.kind === 'function' || (this.kind === 'typedef' && this.type === 'function');61}6263is_module() {64return this.kind === 'module';65}66}6768function format_table(f, data, depth = 0) {69if (!data.length) {70return;71}7273const column_sizes = new Array(data[0].length).fill(0);7475data.forEach((row) => {76row.forEach((e, idx) => {77column_sizes[idx] = Math.max(e.length, column_sizes[idx]);78});79});8081const indent = ' '.repeat(depth);82let sep = indent;83column_sizes.forEach((size) => {84sep += '+';85sep += '-'.repeat(size + 2);86});87sep += '+\n';88f.write(sep);8990data.forEach((row) => {91let row_text = `${indent}|`;92row.forEach((entry, idx) => {93row_text += ` ${entry.padEnd(column_sizes[idx])} |`;94});95row_text += '\n';96f.write(row_text);97f.write(sep);98});99100f.write('\n');101}102103function make_header(header, sep) {104return `${header}\n${sep.repeat(header.length)}\n\n`;105}106107function indent_multiline(text, depth) {108const indent = ' '.repeat(depth);109return text.split('\n').map((l) => (l === '' ? l : indent + l)).join('\n');110}111112function make_rst_signature(obj, types = false, style = false) {113let out = '';114const fmt = style ? '*' : '';115obj.params.forEach((arg, idx) => {116if (idx > 0) {117if (arg.optional) {118out += ` ${fmt}[`;119}120out += ', ';121} else {122out += ' ';123if (arg.optional) {124out += `${fmt}[ `;125}126}127if (types) {128out += `${arg.type} `;129}130const variable = arg.variable ? '...' : '';131const defval = arg.defaultvalue !== undefined ? `=${arg.defaultvalue}` : '';132out += `${variable}${arg.name}${defval}`;133if (arg.optional) {134out += ` ]${fmt}`;135}136});137out += ' ';138return out;139}140141function make_rst_param(f, obj, depth = 0) {142const indent = ' '.repeat(depth * 3);143f.write(indent);144f.write(`:param ${obj.type} ${obj.name}:\n`);145f.write(indent_multiline(obj.description, (depth + 1) * 3));146f.write('\n\n');147}148149function make_rst_attribute(f, obj, depth = 0, brief = false) {150const indent = ' '.repeat(depth * 3);151f.write(indent);152f.write(`.. js:attribute:: ${obj.name}\n\n`);153154if (brief) {155if (obj.summary) {156f.write(indent_multiline(obj.summary, (depth + 1) * 3));157}158f.write('\n\n');159return;160}161162f.write(indent_multiline(obj.description, (depth + 1) * 3));163f.write('\n\n');164165f.write(indent);166f.write(` :type: ${obj.type}\n\n`);167168if (obj.defaultvalue !== undefined) {169let defval = obj.defaultvalue;170if (defval === '') {171defval = '""';172}173f.write(indent);174f.write(` :value: \`\`${defval}\`\`\n\n`);175}176}177178function make_rst_function(f, obj, depth = 0) {179let prefix = '';180if (obj.is_instance()) {181prefix = 'prototype.';182}183184const indent = ' '.repeat(depth * 3);185const sig = make_rst_signature(obj);186f.write(indent);187f.write(`.. js:function:: ${prefix}${obj.name}(${sig})\n`);188f.write('\n');189190f.write(indent_multiline(obj.description, (depth + 1) * 3));191f.write('\n\n');192193obj.params.forEach((param) => {194make_rst_param(f, param, depth + 1);195});196197if (obj.returns !== 'void') {198f.write(indent);199f.write(' :return:\n');200f.write(indent_multiline(obj.returns_desc, (depth + 2) * 3));201f.write('\n\n');202f.write(indent);203f.write(` :rtype: ${obj.returns}\n\n`);204}205}206207function make_rst_object(f, obj) {208let brief = false;209// Our custom header flag.210if (obj.header !== null) {211f.write(make_header(obj.header, '-'));212f.write(`${obj.description}\n\n`);213brief = true;214}215216// Format members table and descriptions217const data = [['type', 'name']].concat(obj.members.map((m) => [m.type, `:js:attr:\`${m.name}\``]));218219f.write(make_header('Properties', '^'));220format_table(f, data, 0);221222make_rst_attribute(f, obj, 0, brief);223224if (!obj.members.length) {225return;226}227228f.write(' **Property Descriptions**\n\n');229230// Properties first231obj.members.filter((m) => !m.is_function()).forEach((m) => {232make_rst_attribute(f, m, 1);233});234235// Callbacks last236obj.members.filter((m) => m.is_function()).forEach((m) => {237make_rst_function(f, m, 1);238});239}240241function make_rst_class(f, obj) {242const header = obj.header ? obj.header : obj.name;243f.write(make_header(header, '-'));244245if (obj.classdesc) {246f.write(`${obj.classdesc}\n\n`);247}248249const funcs = obj.members.filter((m) => m.is_function());250function make_data(m) {251const base = m.is_static() ? obj.name : `${obj.name}.prototype`;252const params = make_rst_signature(m, true, true);253const sig = `:js:attr:\`${m.name} <${base}.${m.name}>\` **(**${params}**)**`;254return [m.returns, sig];255}256const sfuncs = funcs.filter((m) => m.is_static());257const ifuncs = funcs.filter((m) => !m.is_static());258259f.write(make_header('Static Methods', '^'));260format_table(f, sfuncs.map((m) => make_data(m)));261262f.write(make_header('Instance Methods', '^'));263format_table(f, ifuncs.map((m) => make_data(m)));264265const sig = make_rst_signature(obj);266f.write(`.. js:class:: ${obj.name}(${sig})\n\n`);267f.write(indent_multiline(obj.description, 3));268f.write('\n\n');269270obj.params.forEach((p) => {271make_rst_param(f, p, 1);272});273274f.write(' **Static Methods**\n\n');275sfuncs.forEach((m) => {276make_rst_function(f, m, 1);277});278279f.write(' **Instance Methods**\n\n');280ifuncs.forEach((m) => {281make_rst_function(f, m, 1);282});283}284285function make_rst_module(f, obj) {286const header = obj.header !== null ? obj.header : obj.name;287f.write(make_header(header, '='));288f.write(obj.description);289f.write('\n\n');290}291292function write_base_object(f, obj) {293if (obj.is_object()) {294make_rst_object(f, obj);295} else if (obj.is_function()) {296make_rst_function(f, obj);297} else if (obj.is_class()) {298make_rst_class(f, obj);299} else if (obj.is_module()) {300make_rst_module(f, obj);301}302}303304function generate(f, docs) {305const globs = [];306const SYMBOLS = {};307docs.filter((d) => !d.ignore && d.kind !== 'package').forEach((d) => {308SYMBOLS[d.name] = d;309if (d.memberof) {310const up = SYMBOLS[d.memberof];311if (up === undefined) {312console.log(d); // eslint-disable-line no-console313console.log(`Undefined symbol! ${d.memberof}`); // eslint-disable-line no-console314throw new Error('Undefined symbol!');315}316SYMBOLS[d.memberof].add_member(d);317} else {318globs.push(d);319}320});321322f.write('.. _doc_html5_shell_classref:\n\n');323globs.forEach((obj) => write_base_object(f, obj));324}325326/**327* Generate documentation output.328*329* @param {TAFFY} data - A TaffyDB collection representing330* all the symbols documented in your code.331* @param {object} opts - An object with options information.332*/333exports.publish = function (data, opts) {334const docs = data().get().filter((doc) => !doc.undocumented && !doc.ignore).map((doc) => new JSDoclet(doc));335const dest = opts.destination;336if (dest === 'dry-run') {337process.stdout.write('Dry run... ');338generate({339write: function () { /* noop */ },340}, docs);341process.stdout.write('Okay!\n');342return;343}344if (dest !== '' && !dest.endsWith('.rst')) {345throw new Error('Destination file must be either a ".rst" file, or an empty string (for printing to stdout)');346}347if (dest !== '') {348const f = fs.createWriteStream(dest);349generate(f, docs);350} else {351generate(process.stdout, docs);352}353};354355356