#!/usr/bin/env node /** * @license Apache-2.0 * * Copyright (c) 2018 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. */ /* * Script to update the CLI help documentation. * * ## Notes * * - When invoking this script, be sure to set the `NODE_PATH` environment variable to include project package directory. Otherwise, if the `NODE_PATH` does not include the project package directory, imported modules may not be resolved. */ 'use strict'; // MODULES // var resolve = require( 'path' ).resolve; var logger = require( 'debug' ); var groupBy = require( '@stdlib/utils/group-by' ); var hasOwnProp = require( '@stdlib/assert/has-own-property' ); var objectKeys = require( '@stdlib/utils/keys' ); var rpad = require( '@stdlib/string/right-pad' ); var capitalize = require( '@stdlib/string/capitalize' ); var writeFile = require( '@stdlib/fs/write-file' ).sync; var CLI_COMMANDS = require( './../../bin/cli_commands.json' ); // WARNING: fragile as this may no longer be valid if either this script or the required file moves // VARIABLES // // Logger for debugging: var debug = logger( 'update-cli-help' ); // Minimal usage text template: var USAGE_TEXT = [ '', 'Usage: stdlib [options] [args] [-- ]', '', 'Options:', '', ' -h, --help Print this message.', ' -V, --version Print the version.', '', 'Commands:', '' ]; // Path to usage text: var USAGE_TEXT_OUT = resolve( __dirname, '..', '..', 'bin', 'usage.txt' ); // Number of character allowed for a usage text row: var USAGE_TEXT_WIDTH = 80; // chars // Number of characters allowed for usage text commands: var USAGE_TEXT_CMD_WIDTH = 34; // 2 + 30 + 2 = 34 chars; at char 35, begin command description // Indentation for usage text commands: var USAGE_TEXT_CMD_INDENT = ' '; // 2 chars // Number of characters allowed for usage text command names: var USAGE_TEXT_CMD_NAME_WIDTH = 30; // Gutter used to separate usage text commands from usage text command descriptions: var USAGE_TEXT_CMD_GUTTER = ' '; // 2 chars // FUNCTIONS // /** * Creates a CLI usage text. * * @private * @param {ObjectArray} cmds - commands * @returns {(string|Error)} usage text or an error */ function createUsageText( cmds ) { var groups; var keys; var rows; var txt; var key; var i; var j; txt = USAGE_TEXT.slice(); // Group the CLI commands: groups = groupBy( cmds, indicator ); // Begin with the "general" group: if ( hasOwnProp( groups, 'general' ) ) { rows = cmds2rows( groups.general.sort( commandNameComparator ) ); if ( rows instanceof Error ) { return rows; } for ( i = 0; i < rows.length; i++ ) { txt.push( rows[ i ] ); } txt.push( '' ); } // Extract a sorted list of group names: keys = objectKeys( groups ).sort(); // For each group other than "general", add a command section: for ( j = 0; j < keys.length; j++ ) { key = keys[ j ]; if ( key === 'general' ) { // We have already processed the "general" command section... continue; } txt.push( capitalize( key )+':' ); txt.push( '' ); rows = cmds2rows( groups[ key ].sort( commandNameComparator ) ); if ( rows instanceof Error ) { return rows; } for ( i = 0; i < rows.length; i++ ) { txt.push( rows[ i ] ); } txt.push( '' ); } // Add an extra trailing line: txt.push( '' ); // Return the usage text: return txt.join( '\n' ); } /** * Returns the value used to group CLI commands. * * @private * @param {Object} obj - collection element * @returns {string} indicator value */ function indicator( obj ) { return obj.group; } /** * Comparator function for sorting command names. * * @private * @param {Object} a - first value * @param {Object} b - second value * @returns {integer} value indicating sort order */ function commandNameComparator( a, b ) { // Note: we give special preference to `help [command]` to ensure it tops the list of command names... if ( a.command === 'help [command]' ) { return -1; } if ( b.command === 'help [command]' ) { return 1; } if ( a.command < b.command ) { return -1; } if ( a.command > b.command ) { return 1; } return 0; } /** * Processes a CLI command "group", returning a list of usage text rows. * * @private * @param {ObjectArray} cmds - list of commands * @returns {(StringArray|Error)} usage text rows */ function cmds2rows( cmds ) { var out; var row; var i; out = []; for ( i = 0; i < cmds.length; i++ ) { row = USAGE_TEXT_CMD_INDENT; row += rpad( cmds[ i ].command, USAGE_TEXT_CMD_NAME_WIDTH ); row += USAGE_TEXT_CMD_GUTTER; if ( row.length > USAGE_TEXT_CMD_WIDTH ) { debug( 'Command name is too long. Command: %s.', cmds[ i ].command ); return new Error( 'unexpected error. Command name is too long. Command: '+cmds[ i ].command+'.' ); } row += cmds[ i ].description; if ( row.length > USAGE_TEXT_WIDTH ) { debug( 'Command description is too long. Command: %s.', cmds[ i ].command ); return new Error( 'unexpected error. Command description is too long. Command: '+cmds[ i ].command+'.' ); } out.push( row ); } return out; } // MAIN // /** * Updates CLI help documentation. * * @private * @returns {void} */ function main() { var opts; var txt; var err; opts = { 'encoding': 'utf8' }; debug( 'Generating CLI usage text...' ); txt = createUsageText( CLI_COMMANDS ); if ( txt instanceof Error ) { process.exitCode = 1; return console.error( 'Error: %s', txt.message ); // eslint-disable-line no-console } debug( 'Writing to file...' ); err = writeFile( USAGE_TEXT_OUT, txt, opts ); if ( err ) { process.exitCode = 1; debug( 'Encountered an error when attempting to write usage text. Error: %s', err.message ); return console.error( 'Error: %s', err.message ); // eslint-disable-line no-console } debug( 'Successfully wrote to file.' ); debug( 'Finished.' ); } main();