123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355 |
- /* eslint-disable strict */
- 'use strict';
- const fs = require('fs');
- class JSDoclet {
- constructor(doc) {
- this.doc = doc;
- this.description = doc['description'] || '';
- this.name = doc['name'] || 'unknown';
- this.longname = doc['longname'] || '';
- this.types = [];
- if (doc['type'] && doc['type']['names']) {
- this.types = doc['type']['names'].slice();
- }
- this.type = this.types.length > 0 ? this.types.join('\\|') : '*';
- this.variable = doc['variable'] || false;
- this.kind = doc['kind'] || '';
- this.memberof = doc['memberof'] || null;
- this.scope = doc['scope'] || '';
- this.members = [];
- this.optional = doc['optional'] || false;
- this.defaultvalue = doc['defaultvalue'];
- this.summary = doc['summary'] || null;
- this.classdesc = doc['classdesc'] || null;
- // Parameters (functions)
- this.params = [];
- this.returns = doc['returns'] ? doc['returns'][0]['type']['names'][0] : 'void';
- this.returns_desc = doc['returns'] ? doc['returns'][0]['description'] : null;
- this.params = (doc['params'] || []).slice().map((p) => new JSDoclet(p));
- // Custom tags
- this.tags = doc['tags'] || [];
- this.header = this.tags.filter((t) => t['title'] === 'header').map((t) => t['text']).pop() || null;
- }
- add_member(obj) {
- this.members.push(obj);
- }
- is_static() {
- return this.scope === 'static';
- }
- is_instance() {
- return this.scope === 'instance';
- }
- is_object() {
- return this.kind === 'Object' || (this.kind === 'typedef' && this.type === 'Object');
- }
- is_class() {
- return this.kind === 'class';
- }
- is_function() {
- return this.kind === 'function' || (this.kind === 'typedef' && this.type === 'function');
- }
- is_module() {
- return this.kind === 'module';
- }
- }
- function format_table(f, data, depth = 0) {
- if (!data.length) {
- return;
- }
- const column_sizes = new Array(data[0].length).fill(0);
- data.forEach((row) => {
- row.forEach((e, idx) => {
- column_sizes[idx] = Math.max(e.length, column_sizes[idx]);
- });
- });
- const indent = ' '.repeat(depth);
- let sep = indent;
- column_sizes.forEach((size) => {
- sep += '+';
- sep += '-'.repeat(size + 2);
- });
- sep += '+\n';
- f.write(sep);
- data.forEach((row) => {
- let row_text = `${indent}|`;
- row.forEach((entry, idx) => {
- row_text += ` ${entry.padEnd(column_sizes[idx])} |`;
- });
- row_text += '\n';
- f.write(row_text);
- f.write(sep);
- });
- f.write('\n');
- }
- function make_header(header, sep) {
- return `${header}\n${sep.repeat(header.length)}\n\n`;
- }
- function indent_multiline(text, depth) {
- const indent = ' '.repeat(depth);
- return text.split('\n').map((l) => (l === '' ? l : indent + l)).join('\n');
- }
- function make_rst_signature(obj, types = false, style = false) {
- let out = '';
- const fmt = style ? '*' : '';
- obj.params.forEach((arg, idx) => {
- if (idx > 0) {
- if (arg.optional) {
- out += ` ${fmt}[`;
- }
- out += ', ';
- } else {
- out += ' ';
- if (arg.optional) {
- out += `${fmt}[ `;
- }
- }
- if (types) {
- out += `${arg.type} `;
- }
- const variable = arg.variable ? '...' : '';
- const defval = arg.defaultvalue !== undefined ? `=${arg.defaultvalue}` : '';
- out += `${variable}${arg.name}${defval}`;
- if (arg.optional) {
- out += ` ]${fmt}`;
- }
- });
- out += ' ';
- return out;
- }
- function make_rst_param(f, obj, depth = 0) {
- const indent = ' '.repeat(depth * 3);
- f.write(indent);
- f.write(`:param ${obj.type} ${obj.name}:\n`);
- f.write(indent_multiline(obj.description, (depth + 1) * 3));
- f.write('\n\n');
- }
- function make_rst_attribute(f, obj, depth = 0, brief = false) {
- const indent = ' '.repeat(depth * 3);
- f.write(indent);
- f.write(`.. js:attribute:: ${obj.name}\n\n`);
- if (brief) {
- if (obj.summary) {
- f.write(indent_multiline(obj.summary, (depth + 1) * 3));
- }
- f.write('\n\n');
- return;
- }
- f.write(indent_multiline(obj.description, (depth + 1) * 3));
- f.write('\n\n');
- f.write(indent);
- f.write(` :type: ${obj.type}\n\n`);
- if (obj.defaultvalue !== undefined) {
- let defval = obj.defaultvalue;
- if (defval === '') {
- defval = '""';
- }
- f.write(indent);
- f.write(` :value: \`\`${defval}\`\`\n\n`);
- }
- }
- function make_rst_function(f, obj, depth = 0) {
- let prefix = '';
- if (obj.is_instance()) {
- prefix = 'prototype.';
- }
- const indent = ' '.repeat(depth * 3);
- const sig = make_rst_signature(obj);
- f.write(indent);
- f.write(`.. js:function:: ${prefix}${obj.name}(${sig})\n`);
- f.write('\n');
- f.write(indent_multiline(obj.description, (depth + 1) * 3));
- f.write('\n\n');
- obj.params.forEach((param) => {
- make_rst_param(f, param, depth + 1);
- });
- if (obj.returns !== 'void') {
- f.write(indent);
- f.write(' :return:\n');
- f.write(indent_multiline(obj.returns_desc, (depth + 2) * 3));
- f.write('\n\n');
- f.write(indent);
- f.write(` :rtype: ${obj.returns}\n\n`);
- }
- }
- function make_rst_object(f, obj) {
- let brief = false;
- // Our custom header flag.
- if (obj.header !== null) {
- f.write(make_header(obj.header, '-'));
- f.write(`${obj.description}\n\n`);
- brief = true;
- }
- // Format members table and descriptions
- const data = [['type', 'name']].concat(obj.members.map((m) => [m.type, `:js:attr:\`${m.name}\``]));
- f.write(make_header('Properties', '^'));
- format_table(f, data, 0);
- make_rst_attribute(f, obj, 0, brief);
- if (!obj.members.length) {
- return;
- }
- f.write(' **Property Descriptions**\n\n');
- // Properties first
- obj.members.filter((m) => !m.is_function()).forEach((m) => {
- make_rst_attribute(f, m, 1);
- });
- // Callbacks last
- obj.members.filter((m) => m.is_function()).forEach((m) => {
- make_rst_function(f, m, 1);
- });
- }
- function make_rst_class(f, obj) {
- const header = obj.header ? obj.header : obj.name;
- f.write(make_header(header, '-'));
- if (obj.classdesc) {
- f.write(`${obj.classdesc}\n\n`);
- }
- const funcs = obj.members.filter((m) => m.is_function());
- function make_data(m) {
- const base = m.is_static() ? obj.name : `${obj.name}.prototype`;
- const params = make_rst_signature(m, true, true);
- const sig = `:js:attr:\`${m.name} <${base}.${m.name}>\` **(**${params}**)**`;
- return [m.returns, sig];
- }
- const sfuncs = funcs.filter((m) => m.is_static());
- const ifuncs = funcs.filter((m) => !m.is_static());
- f.write(make_header('Static Methods', '^'));
- format_table(f, sfuncs.map((m) => make_data(m)));
- f.write(make_header('Instance Methods', '^'));
- format_table(f, ifuncs.map((m) => make_data(m)));
- const sig = make_rst_signature(obj);
- f.write(`.. js:class:: ${obj.name}(${sig})\n\n`);
- f.write(indent_multiline(obj.description, 3));
- f.write('\n\n');
- obj.params.forEach((p) => {
- make_rst_param(f, p, 1);
- });
- f.write(' **Static Methods**\n\n');
- sfuncs.forEach((m) => {
- make_rst_function(f, m, 1);
- });
- f.write(' **Instance Methods**\n\n');
- ifuncs.forEach((m) => {
- make_rst_function(f, m, 1);
- });
- }
- function make_rst_module(f, obj) {
- const header = obj.header !== null ? obj.header : obj.name;
- f.write(make_header(header, '='));
- f.write(obj.description);
- f.write('\n\n');
- }
- function write_base_object(f, obj) {
- if (obj.is_object()) {
- make_rst_object(f, obj);
- } else if (obj.is_function()) {
- make_rst_function(f, obj);
- } else if (obj.is_class()) {
- make_rst_class(f, obj);
- } else if (obj.is_module()) {
- make_rst_module(f, obj);
- }
- }
- function generate(f, docs) {
- const globs = [];
- const SYMBOLS = {};
- docs.filter((d) => !d.ignore && d.kind !== 'package').forEach((d) => {
- SYMBOLS[d.name] = d;
- if (d.memberof) {
- const up = SYMBOLS[d.memberof];
- if (up === undefined) {
- console.log(d); // eslint-disable-line no-console
- console.log(`Undefined symbol! ${d.memberof}`); // eslint-disable-line no-console
- throw new Error('Undefined symbol!');
- }
- SYMBOLS[d.memberof].add_member(d);
- } else {
- globs.push(d);
- }
- });
- f.write('.. _doc_html5_shell_classref:\n\n');
- globs.forEach((obj) => write_base_object(f, obj));
- }
- /**
- * Generate documentation output.
- *
- * @param {TAFFY} data - A TaffyDB collection representing
- * all the symbols documented in your code.
- * @param {object} opts - An object with options information.
- */
- exports.publish = function (data, opts) {
- const docs = data().get().filter((doc) => !doc.undocumented && !doc.ignore).map((doc) => new JSDoclet(doc));
- const dest = opts.destination;
- if (dest === 'dry-run') {
- process.stdout.write('Dry run... ');
- generate({
- write: function () { /* noop */ },
- }, docs);
- process.stdout.write('Okay!\n');
- return;
- }
- if (dest !== '' && !dest.endsWith('.rst')) {
- throw new Error('Destination file must be either a ".rst" file, or an empty string (for printing to stdout)');
- }
- if (dest !== '') {
- const f = fs.createWriteStream(dest);
- generate(f, docs);
- } else {
- generate(process.stdout, docs);
- }
- };
|