123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390 |
- const fs = require('fs');
- const path = require('path');
- const util = require('util');
- class DocsWriter {
- /**
- * Utility class used to write the HTML files used on the documentation.
- *
- * Initializes the writer to the specified output file,
- * creating the parent directories when used if required.
- */
- constructor(filename, typeToPath) {
- this.filename = filename;
- this._parent = path.join(this.filename, '..');
- this.handle = null;
- this.title = '';
- // Should be set before calling adding items to the menu
- this.menuSeparatorTag = null;
- // Utility functions
- this.typeToPath = t => this._rel(typeToPath(t));
- // Control signals
- this.menuBegan = false;
- this.tableColumns = 0;
- this.tableColumnsLeft = null;
- this.writeCopyScript = false;
- this._script = '';
- }
- /**
- * Get the relative path for the given path from the current
- * file by working around https://bugs.python.org/issue20012.
- */
- _rel(path_) {
- return path
- .relative(this._parent, path_)
- .replace(new RegExp(`\\${path.sep}`, 'g'), '/');
- }
- /**
- * Writes the head part for the generated document,
- * with the given title and CSS
- */
- // High level writing
- writeHead(title, cssPath, defaultCss) {
- this.title = title;
- this.write(
- `<!DOCTYPE html>
- <html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>${title}</title>
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <link id="style" href="${this._rel(
- cssPath
- )}/docs.dark.css" rel="stylesheet">
- <script>
- document.getElementById("style").href = "${this._rel(cssPath)}/docs."
- + (localStorage.getItem("theme") || "${defaultCss}")
- + ".css";
- </script>
- <link href="https://fonts.googleapis.com/css?family=Nunito|Source+Code+Pro"
- rel="stylesheet">
- </head>
- <body>
- <div id="main_div">`
- );
- }
- /**
- * Sets the menu separator.
- * Must be called before adding entries to the menu
- */
- setMenuSeparator(img) {
- if (img) {
- this.menuSeparatorTag = `<img src="${this._rel(img)}" alt="/" />`;
- } else {
- this.menuSeparatorTag = null;
- }
- }
- /**
- * Adds a menu entry, will create it if it doesn't exist yet
- */
- addMenu(name, link) {
- if (this.menuBegan) {
- if (this.menuSeparatorTag) {
- this.write(this.menuSeparatorTag);
- }
- } else {
- // First time, create the menu tag
- this.write('<ul class="horizontal">');
- this.menuBegan = true;
- }
- this.write('<li>');
- if (link) {
- this.write(`<a href="${this._rel(link)}">`);
- }
- // Write the real menu entry text
- this.write(name);
- if (link) {
- this.write('</a>');
- }
- this.write('</li>');
- }
- /**
- * Ends an opened menu
- */
- endMenu() {
- if (!this.menuBegan) {
- throw new Error('No menu had been started in the first place.');
- }
- this.write('</ul>');
- }
- /**
- * Writes a title header in the document body,
- * with an optional depth level
- */
- writeTitle(title, level, id) {
- level = level || 1;
- if (id) {
- this.write(`<h${level} id="${id}">${title}</h${level}>`);
- } else {
- this.write(`<h${level}>${title}</h${level}>`);
- }
- }
- /**
- * Writes the code for the given 'tlobject' properly
- * formatted with hyperlinks
- */
- writeCode(tlobject) {
- this.write(
- `<pre>---${tlobject.isFunction ? 'functions' : 'types'}---\n`
- );
- // Write the function or type and its ID
- if (tlobject.namespace) {
- this.write(tlobject.namespace);
- this.write('.');
- }
- this.write(
- `${tlobject.name}#${tlobject.id.toString(16).padStart(8, '0')}`
- );
- // Write all the arguments (or do nothing if there's none)
- for (const arg of tlobject.args) {
- this.write(' ');
- const addLink = !arg.genericDefinition && !arg.isGeneric;
- // "Opening" modifiers
- if (arg.genericDefinition) {
- this.write('{');
- }
- // Argument name
- this.write(arg.name);
- this.write(':');
- // "Opening" modifiers
- if (arg.isFlag) {
- this.write(`flags.${arg.flagIndex}?`);
- }
- if (arg.isGeneric) {
- this.write('!');
- }
- if (arg.isVector) {
- this.write(
- `<a href="${this.typeToPath('vector')}">Vector</a><`
- );
- }
- // Argument type
- if (arg.type) {
- if (addLink) {
- this.write(`<a href="${this.typeToPath(arg.type)}">`);
- }
- this.write(arg.type);
- if (addLink) {
- this.write('</a>');
- }
- } else {
- this.write('#');
- }
- // "Closing" modifiers
- if (arg.isVector) {
- this.write('>');
- }
- if (arg.genericDefinition) {
- this.write('}');
- }
- }
- // Now write the resulting type (result from a function/type)
- this.write(' = ');
- const [genericName] = tlobject.args
- .filter(arg => arg.genericDefinition)
- .map(arg => arg.name);
- if (tlobject.result === genericName) {
- // Generic results cannot have any link
- this.write(tlobject.result);
- } else {
- if (/^vector</i.test(tlobject.result)) {
- // Notice that we don't simply make up the "Vector" part,
- // because some requests (as of now, only FutureSalts),
- // use a lower type name for it (see #81)
- let [vector, inner] = tlobject.result.split('<');
- inner = inner.replace(/>+$/, '');
- this.write(
- `<a href="${this.typeToPath(vector)}">${vector}</a><`
- );
- this.write(
- `<a href="${this.typeToPath(inner)}">${inner}</a>>`
- );
- } else {
- this.write(
- `<a href="${this.typeToPath(tlobject.result)}">${
- tlobject.result
- }</a>`
- );
- }
- }
- this.write('</pre>');
- }
- /**
- * Begins a table with the given 'column_count', required to automatically
- * create the right amount of columns when adding items to the rows
- */
- beginTable(columnCount) {
- this.tableColumns = columnCount;
- this.tableColumnsLeft = 0;
- this.write('<table>');
- }
- /**
- * This will create a new row, or add text to the next column
- * of the previously created, incomplete row, closing it if complete
- */
- addRow(text, link, bold, align) {
- if (!this.tableColumnsLeft) {
- // Starting a new row
- this.write('<tr>');
- this.tableColumnsLeft = this.tableColumns;
- }
- this.write('<td');
- if (align) {
- this.write(` style="text-align: ${align}"`);
- }
- this.write('>');
- if (bold) {
- this.write('<b>');
- }
- if (link) {
- this.write(`<a href="${this._rel(link)}">`);
- }
- // Finally write the real table data, the given text
- this.write(text);
- if (link) {
- this.write('</a>');
- }
- if (bold) {
- this.write('</b>');
- }
- this.write('</td>');
- this.tableColumnsLeft -= 1;
- if (!this.tableColumnsLeft) {
- this.write('</tr>');
- }
- }
- endTable() {
- if (this.tableColumnsLeft) {
- this.write('</tr>');
- }
- this.write('</table>');
- }
- /**
- * Writes a paragraph of text
- */
- writeText(text) {
- this.write(`<p>${text}</p>`);
- }
- /**
- * Writes a button with 'text' which can be used
- * to copy 'textToCopy' to clipboard when it's clicked.
- */
- writeCopyButton(text, textToCopy) {
- this.writeCopyScript = true;
- this.write(
- `<button onclick="cp('${textToCopy.replace(
- /'/g,
- "\\'"
- )}');">${text}</button>`
- );
- }
- addScript(src, path) {
- if (path) {
- this._script += `<script src="${this._rel(path)}"></script>`;
- } else if (src) {
- this._script += `<script>${src}</script>`;
- }
- }
- /***
- * Ends the whole document. This should be called the last
- */
- endBody() {
- if (this.writeCopyScript) {
- this.write(
- '<textarea id="c" class="invisible"></textarea>' +
- '<script>' +
- 'function cp(t){' +
- 'var c=document.getElementById("c");' +
- 'c.value=t;' +
- 'c.select();' +
- 'try{document.execCommand("copy")}' +
- 'catch(e){}}' +
- '</script>'
- );
- }
- this.write(`</div>${this._script}</body></html>`);
- }
- /**
- * Wrapper around handle.write
- */
- // "Low" level writing
- write(s, ...args) {
- if (args.length) {
- fs.appendFileSync(this.handle, util.format(s, ...args));
- } else {
- fs.appendFileSync(this.handle, s);
- }
- }
- open() {
- // Sanity check
- const parent = path.join(this.filename, '..');
- fs.mkdirSync(parent, { recursive: true });
- this.handle = fs.openSync(this.filename, 'w');
- return this;
- }
- close() {
- fs.closeSync(this.handle);
- }
- }
- module.exports = {
- DocsWriter,
- };
|