Node交互式命令行工具開發——自動化文檔工具

  nodejs開發命令行工具,流程相對簡單,但一套完整的命令行程序開發流程下來,仍是須要下點功夫,網上資料大多零散,這篇教程意在整合一下完整的開發流程。
  npm上命令行開發相關包不少,例如minimistoptimistnoptcommander.jsyargs等等,使用方法和效果相似。其中用得比較多的是TJ大神的commanderyargs,本文以commander爲基礎講述,能夠參考這篇教程,yargs教程能夠參考阮大神的或者這一篇
  另外,一個完整的命令行工具開發,還須要瞭解processshelljspathlinebyline等模塊,這些都是node基礎模塊或一些簡單模塊,很是簡單,就很少說了,另外若是你不想用回調函數處理異步還須要瞭解一下PromiseGenerator函數。這是教程:i5ting大神的《深刻淺出js(Node.js)異步流程控制》和阮大神的異步編程教程以及promise小人書,另外想嘗試ES7 stage3階段的async/await異步解決方案,可參考這篇教程async/await解決方案須要babel轉碼,這是教程。本人喜歡async/await(哪一個node開發者不喜歡呢?)但不喜歡倒騰,何況async/await自己就是Promise的語法糖,因此沒選擇使用,據江湖消息,nodejs將在今年晚些時候(10月份?)支持async/await,非常期待。
  如下是文章末尾實例用到的一些依賴。html

"dependencies": {
    "bluebird": "^3.4.1",
    "co": "^4.6.0",
    "colors": "^1.1.2",
    "commander": "^2.9.0",
    "dox": "^0.9.0",
    "handlebars": "^4.0.5",
    "linebyline": "^1.3.0",
    "mkdirp": "^0.5.1"
  }

  其中bluebird用於Promise化,TJ大神的co用於執行Generator函數,handlebars是一種模板,linebyline用於分行讀取文件,colors用於美化輸出,mkdirp用於建立目錄,另外教程中的示例是一款工具,能夠自動化生成數據庫和API接口的markdown文檔,並經過修改git hooks,使項目的每次commit都會自動更新文檔,藉助了TJ大神的dox模塊。
  <span style="color:rgb(0, 136, 204)">全部推薦教程/教材,僅供參考,自行甄選閱讀。</span>node

安裝Node

  各操做系統下安裝見Nodejs官網,安裝完成以後用node -v或者which node等命令測試安裝是否成功。which在命令行開發中是一個很是有用的命令,使用which命令確保你的系統中不存在名字相同的命令行工具,例如which commandName,例如which testdev命令返回空白那麼說明testdev命令名稱尚未被使用。git

初始化

  1. 新建一個.js文件,便是你的命令要執行的主程序入口文件,例如testdev.js。在文件第一行加入#!/usr/bin/env node指明系統在運行這個文件的時候使用node做爲解釋器,等價於node testdev.js命令。
  2. 初始化package.json文件,使用npm init命令根據提示信息建立,也能夠是使用npm init -y使用默認設置建立。建立完成以後須要修改package.json文件內容加入"bin": {"testdev": "./testdev.js"}這條信息用於告訴npm你的命令(testdev)要執行的腳本文件的路徑和名字,這裏咱們指定testdev命令的執行文件爲當前目錄下的testdev.js文件。
  3. 爲了方便測試在testdev.js文件中加入代碼console.log('hello world');,這裏只是用於測試環境是否搭建成功,更加複雜的程序邏輯和過程須要按照實際狀況進行編寫

測試

  使用npm link命令,能夠在本地安裝剛剛建立的包,而後就能夠用testdev來運行命令了,若是正常的話在控制檯會打印出hello worldes6

commander

  TJ的commander很是簡潔,README.md已經把使用方法寫的很是清晰。下面是例子中的代碼:github

const program = require('commander'),
  co = require('co');

const appInfo = require('./../package.json'),
  asyncFunc = require('./../common/asyncfunc.js');

program.allowUnknownOption();
program.version(appInfo.version);

program
  .command('init')
  .description('初始化當前目錄doc.json文件')
  .action(() => co(asyncFunc.initAction));

program
  .command('show')
  .description('顯示配置文件狀態')
  .action(() => co(asyncFunc.showAction));

program
  .command('run')
  .description('啓動程序')
  .action(() => co(asyncFunc.runAction));

program
  .command('modifyhook')
  .description('修改項目下的hook文件')
  .action(() => co(asyncFunc.modifyhookAction));

program
  .command('*')
  .action((env) => {
    console.error('不存在命令 "%s"', env);
  });

program.on('--help', () => {
  console.log('  Examples:');
  console.log('');
  console.log('    $ createDOC --help');
  console.log('    $ createDOC -h');
  console.log('    $ createDOC show');
  console.log('');
});

program.parse(process.argv);

  定義了四個命令和個性化幫助說明。shell

交互式命令行process

  commander只是實現了命令行參數與回覆一對一的固定功能,也就是一個命令必然對應一個回覆,那如何實現人機交互式的命令行呢,相似npm init或者eslint --init這樣的與用戶交互,交互以後根據用戶的不一樣需求反饋不一樣的結果呢。這裏就須要node內置的process模塊。
  這是我實現的一個init命令功能代碼:數據庫

exports.initAction = function* () {
  try {
    var docPath = yield exists(process.cwd() + '/doc.json');
    if (docPath) {
      func.initRepl(config.coverInit, arr => {
        co(newDoc(arr));
      })
    } else {
      func.initRepl(config.newInit, arr => {
        co(newDoc(arr));
      })
    }
  } catch (err) {
    console.warn(err);
  }

  首先檢查doc.json文件是否存在,若是存在執行覆蓋交互,若是不存在執行生成交互,try...catch捕獲錯誤。
  交互內容配置以下:npm

newInit:
    [
        {
            title:'initConfirm',
            description:'初始化createDOC,生成doc.json.確認?(y/n)  ',
            defaults: 'y'
        },
        {
            title:'defaultConfirm',
            description:'是否使用默認配置.(y/n)  ',
            defaults: 'y'
        },
        {
            title:'showConfig',
            description:'是否顯示doc.json當前配置?(y/n)  ',
            defaults: 'y'
        }
    ],
    coverInit:[
        {
            title:'modifyConfirm',
            description:'doc.json已存在,初始化將覆蓋文件.確認?(y/n)  ',
            defaults: 'y'
        },
        {
            title:'defaultConfirm',
            description:'是否使用默認配置.(y/n)  ',
            defaults: 'y'
        },
        {
            title:'showConfig',
            description:'是否顯示doc.json當前配置?(y/n)  ',
            defaults: 'y'
        }
    ],

  人機交互部分代碼也就是initRepl函數內容以下:編程

//初始化命令,人機交互控制
exports.initRepl = function (init, func) {
  var i = 1;
  var inputArr = [];
  var len = init.length;
  process.stdout.write(init[0].description);
  process.stdin.resume();
  process.stdin.setEncoding('utf-8');
  process.stdin.on('data', (chunk) => {
    chunk = chunk.replace(/[\s\n]/, '');
    if (chunk !== 'y' && chunk !== 'Y' && chunk !== 'n' && chunk !== 'N') {
      console.log(config.colors.red('您輸入的命令是: ' + chunk));
      console.warn(config.colors.red('請輸入正確指令:y/n'));
      process.exit();
    }
    if (
      (init[i - 1].title === 'modifyConfirm' || init[i - 1].title === 'initConfirm') &&
      (chunk === 'n' || chunk === 'N')
    ) {
      process.exit();
    }
    var inputJson = {
      title: init[i - 1].title,
      value: chunk,
    };
    inputArr.push(inputJson);
    if ((len--) > 1) {
      process.stdout.write(init[i++].description)
    } else {
      process.stdin.pause();
      func(inputArr);
    }
  });
}

  人機交互才用向用戶提問根據用戶不一樣輸入產生不一樣結果的形式進行,順序讀取提問列表並記錄用戶輸入結果,若是用戶輸入n/N則終止交互,用戶輸入非法字符(除y/Y/n/N之外)提示輸入命令錯誤。json

文檔自動化

  文檔自動化,其中數據庫文檔自動化,才用依賴sequelize的方法手寫(根據需求不一樣自行編寫邏輯),API文檔才用TJ的dox也很簡單。因爲此處代碼與命令行功能相關度不大,請讀者自行去示例地址查看代碼。

示例地址

github地址
npm地址

相關文章
相關標籤/搜索