/** * @license Apache-2.0 * * Copyright (c) 2019 The Stdlib Authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 'use strict'; // MODULES // var path = require( 'path' ); var logger = require( 'debug' ); var objectKeys = require( '@stdlib/utils/keys' ); var replace = require( '@stdlib/string/replace' ); var extname = require( '@stdlib/utils/extname' ); var hasOwnProp = require( '@stdlib/assert/has-own-property' ); var readDir = require( '@stdlib/fs/read-dir' ).sync; var readFile = require( '@stdlib/fs/read-file' ).sync; var writeFile = require( '@stdlib/fs/write-file' ).sync; var parse = require( '@stdlib/_tools/repl-txt/parse' ); var node2info = require( '@stdlib/_tools/repl-txt/abridge' ); // VARIABLES // var debug = logger( 'repl:help:build' ); // Source documentation directory: var SRC_DIR = path.resolve( __dirname, '..', 'docs', 'commands' ); // Output file directory: var OUTPUT_DIR = path.resolve( __dirname, '..', 'data' ); // FUNCTIONS // /** * Filters a list of files for documentation files. * * @private * @param {Array} list - file list * @returns {Array} filtered list */ function filterFiles( list ) { var out; var ext; var i; out = []; for ( i = 0; i < list.length; i++ ) { ext = extname( list[ i ] ); if ( ext === '.txt' ) { out.push( list[ i ] ); } } return out; } /** * Returns a section AST node having a specified title. * * @private * @param {ObjectArray} nodes - section AST nodes * @param {string} name - section name * @returns {(Object|null)} AST node or null */ function findSection( nodes, name ) { var i; for ( i = 0; i < nodes.length; i++ ) { if ( nodes[ i ].title === name ) { return nodes[ i ]; } } return null; } /** * Saves a JSON database. * * @private * @param {string} basename - base file name (excluding extension) * @param {Object} json - JSON database */ function save( basename, json ) { var fpath; var fopts; var keys; var csv; var tmp; var i; debug( 'Writing to JSON file.' ); fopts = { 'encoding': 'utf8' }; fpath = path.join( OUTPUT_DIR, basename+'.json' ); writeFile( fpath, JSON.stringify( json ), fopts ); debug( 'Writing to CSV file.' ); keys = objectKeys( json ); csv = ''; for ( i = 0; i < keys.length; i++ ) { tmp = replace( json[ keys[i] ], /\r?\n/g, '\\n' ); tmp = replace( tmp, '"', '\\"' ); csv += keys[ i ] + ',"' + tmp + '"\n'; // Note: ensures trailing newline } fpath = path.join( OUTPUT_DIR, basename+'.csv' ); writeFile( fpath, csv, fopts ); } /** * Main execution sequence. * * @private * @returns {void} */ function main() { var files; var ropts; var alias; var asts; var code; var len; var ast; var dbs; var tmp; var s; var i; var j; var k; debug( 'Reading directory: %s', SRC_DIR ); files = readDir( SRC_DIR ); if ( files instanceof Error ) { debug( 'Encountered an error when attempting to read directory. Error: %s', files.message ); console.error( 'Error: %s', files.message ); // eslint-disable-line no-console return; } debug( 'Filtering for documentation files.' ); files = filterFiles( files ); len = files.length; if ( len === 0 ) { debug( 'Unable to resolve documentation files.' ); return; } debug( 'Found %d documentation files.', len ); debug( 'Generating ASTs.' ); ropts = { 'encoding': 'utf8' }; asts = []; for ( i = 0; i < len; i++ ) { files[ i ] = readFile( path.join( SRC_DIR, files[ i ] ), ropts ); asts.push( parse( files[ i ] ) ); } debug( 'Finished generating ASTs.' ); debug( 'Generating databases.' ); dbs = { 'help': {}, 'info': {}, 'example': {} }; for ( i = 0; i < len; i++ ) { ast = asts[ i ]; alias = ast[ 0 ].signature.name; debug( 'Processing alias: %s', alias ); dbs.help[ alias ] = files[ i ]; debug( 'Number of documented interfaces: %d', ast.length ); debug( 'Processing interfaces.' ); for ( j = 0; j < ast.length; j++ ) { tmp = ast[ j ].signature.name; debug( 'Interface: %s', tmp ); if ( tmp.slice( 0, alias.length ) !== alias ) { debug( 'Interface is documented neither as the alias nor an associated method. Skipping...' ); continue; } // Help text: if ( tmp === alias ) { // Main alias should exclusively correspond to the full help text: debug( 'Interface corresponds to the main alias.' ); } else if ( hasOwnProp( dbs.help, tmp ) ) { // Concatenate associated signatures (e.g., same API, but multiple parameterizations based on argument types, etc): dbs.help[ tmp ] += ast[ j ].raw; } else { dbs.help[ tmp ] = '\n' + ast[ j ].raw; } // Info: if ( hasOwnProp( dbs.info, tmp ) ) { dbs.info[ tmp ] += node2info( ast[ j ] ); } else { dbs.info[ tmp ] = node2info( ast[ j ] ); } // Example: s = findSection( ast[ j ].sections, 'Examples' ); if ( s ) { code = ''; for ( k = 0; k < s.examples.length; k++ ) { code += s.examples[ k ].code + '\n'; } if ( hasOwnProp( dbs.example, tmp ) ) { // Concatenate associated examples (e.g., same API, but multiple parameterizations based on argument types, etc): dbs.example[ tmp ] += code; } else { dbs.example[ tmp ] = code; } } else { debug( 'Unable to resolve examples.' ); } debug( 'Finished processing interface.' ); } debug( 'Successfully processed alias: %s', alias ); } debug( 'Saving help text database.' ); save( 'help', dbs.help ); debug( 'Saving abbreviate help text database.' ); save( 'info', dbs.info ); debug( 'Saving example database.' ); save( 'example', dbs.example ); } // MAIN // main();