必知必會的Node-CLI開發基礎

descire發表於2018-10-15

本文帶你瞭解建立一個Node-CLI工具所需知識點。

一、命令列引數解析

  在NodeJS中可以通過以下程式碼獲取命令列中傳遞的引數:

  process.argv.slice(2)
複製程式碼

  但是這對於構建一個CLI工具遠遠不夠,首先需要考慮引數輸入的各種風格:

  • Unix引數風格:前面加-,不過後面跟的是單個字元,例如-abc解析為['a', 'b', 'c']。
  • GNU引數風格:前面加--,例如npm中的命令,npm --save-dev webpack。
  • BSD引數風格:前面不加修飾符。

  這裡可以通過正規表示式對process.argv進行加工:

/**
 * 解析Unix、BSD和GNU引數風格
 * @param {Array} argv 命令列引數陣列
 * @returns
 */
function parseArgv (argv) {
  const max = argv.length
  const result = {
    _: []
  }
  for (let i = 0; i < max; i++) {
    const arg = argv[i]
    const next = argv[i + 1]
    if (/^--.+/.test(arg)) {
      // GNU風格
      const key = arg.match(/^--(.+)/)[1]
      if (next != null && !/^-.+/.test(next)) {
        result[key] = next
        i++
      } else {
        result[key] = true
      }
    } else if (/^-[^-]+/.test(arg)) {
      // Unix風格
      const items = arg.match(/^-([^-]+)/)[1].split('')
      for (let j = 0, max = items.length; j < max; j++) {
        const item = items[j]
        // 非字母不解析
        if (!/[a-zA-Z]/.test(item)) {
          continue
        }
        if (next != null && !/^-.+/.test(next) && j === max - 1) {
          result[item] = next
          i++
        } else {
          result[item] = true
        }
      }
    } else {
      // BSD風格
      result._.push(arg)
    }
  }
  return result
}
複製程式碼

  通過以上的方法可以得到如下結果:

  node example1.js --save-dev -age 20 some
  // => 結果
  {
    _: ['some'],
    'save-dev': true,
    a: true,
    g: true,
    e: 20
  }
複製程式碼

  上面這個示例不僅僅為了展示解析的結果,而且還強調了Unix引數風格只解析單個字母,所以這種風格的引數可能表達的意思不太明確並且數量有限,那麼就需要在正確的場景中使用這種風格的引數:

  npm --save-dev webpack
  npm -D webpack
複製程式碼

  npm中採用Unix引數風格表示簡寫,這就是一種很恰當的方式,那麼前面示例中的-age按照語義應該改為--age更加合理一點。

二、命令列介面

  NodeJS中的readline模組提供question和prompt方法構建命令列介面,下面是一個簡單的問答式的互動介面:

const readline = require('readline');
const question = ['請輸入您的姓名', '請輸入您的年齡']
const result = []
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
  prompt: `?${question[0]} `
});
rl.prompt();

rl.on('line', (line) => {
  result.push(line.trim())
  const max = result.length
  if (max === question.length) {
    rl.close()
  }
  rl.setPrompt(`?${question[max]} `)
  rl.prompt();
}).on('close', () => {
  console.log(`謝謝參與問答 *** 姓名: ${result[0]} 年齡: ${result[1]}`);
  process.exit(0);
}); 
複製程式碼

必知必會的Node-CLI開發基礎
  當然互動介面的元素並不只有這一種,在使用各類CLI工具時,你應該會遇到諸如:單項選擇、下載進度條...

  下面可以嘗試實現一個單項選擇互動介面:

const readline = require('readline')
let selected = 0
const choices = ['javascript', 'css', 'html']
let lineCount = 0
const rl = readline.createInterface(process.stdin, process.stdout)
function reader () {
  let str = ''
  for (let i = 0; i < choices.length; i++) {
    lineCount++
    str += `${selected === i ? '[X]' : '[ ]'} ${choices[i]}\r\n`
  }
  process.stdout.write(str)
}

reader()

process.stdin.on('keypress', (s, key) => {
  const name = key.name
  const max = choices.length
  if (name === 'up' && selected > 0) {
    selected--
  } else if (name === 'down' && selected < max - 1) {
    selected++
  } else if (name === 'down' && selected === max - 1) {
    selected = 0
  } else if (name === 'up' && selected === 0) {
    selected = max - 1
  } else {
    return true
  }
  // 移動游標至起始位置,確保後續輸入覆蓋當前內容
  readline.moveCursor(process.stdout, 0, -lineCount)
  lineCount -= choices.length
  reader()
})

rl.on('line', () => {
  console.log(`you choose ${choices[selected]}`)
  process.exit(0)
}).on('close', () => {
  rl.close()
})
複製程式碼

必知必會的Node-CLI開發基礎

三、定製樣式

  為了有效的區別命令列介面中資訊的差異性,我們可以為這裡輸出資訊新增適當的樣式。

  這裡介紹一下字串新增樣式的語法:

  \x1b[背景顏色編號;字型顏色編號m
複製程式碼

  每條樣式都要以\x1b[開頭:

  // \x1b[0m 清除樣式
  process.stdout.write('\x1b[44;37m OK \x1b[0m just do it\n')
複製程式碼

必知必會的Node-CLI開發基礎

四、自定義Node命令

  接下來就是自定義Node命令,首先需要建立一個命令執行的檔案:

  // hello.js 首行需要指定指令碼的解釋程式
  #!/usr/bin/env node
  console.log('hello')
複製程式碼

  再利用package.json中的bin配置:

  {
    "bin": {
      "hello": "./hello.js"
    },
  }
複製程式碼

  執行npm的link命令:

  npm link
  
  # 輸入自定義命令
  hello

  # 輸出 hello
複製程式碼

五、總結

  上面介紹了開發Node-CLI時所需要的一些基本知識,但是對於用過諸如webpack-cli、vue-cli工具的你可能會發現這些優秀的CLI工具還具有:

  • git風格的子命令;
  • 自動化的幫助資訊;
  • ....

  那麼下面這些成熟的框架會給你很大的幫助:

相關文章