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

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

1、命令行參數解析

  在NodeJS中能夠經過如下代碼獲取命令行中傳遞的參數:css

process.argv.slice(2)
複製代碼

  可是這對於構建一個CLI工具遠遠不夠,首先須要考慮參數輸入的各類風格:html

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

  這裏能夠經過正則表達式對process.argv進行加工:vue

/** * 解析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
}
複製代碼

  經過以上的方法能夠獲得以下結果:java

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

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

npm --save-dev webpack
  npm -D webpack
複製代碼

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

2、命令行界面

  NodeJS中的readline模塊提供question和prompt方法構建命令行界面,下面是一個簡單的問答式的交互界面:git

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);
}); 
複製代碼

  固然交互界面的元素並不僅有這一種,在使用各種CLI工具時,你應該會遇到諸如:單項選擇、下載進度條...

  下面能夠嘗試實現一個單項選擇交互界面:github

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()
})
複製代碼

3、定製樣式

  爲了有效的區別命令行界面中信息的差別性,咱們能夠爲這裏輸出信息添加適當的樣式。web

  這裏介紹一下字符串添加樣式的語法:

\x1b[背景顏色編號;字體顏色編號m
複製代碼

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

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

4、自定義Node命令

  接下來就是自定義Node命令,首先須要建立一個命令執行的文件:

// hello.js 首行須要指定腳本的解釋程序
  #!/usr/bin/env node
  console.log('hello')
複製代碼

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

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

  執行npm的link命令:

npm link
  
  # 輸入自定義命令
  hello

  # 輸出 hello
複製代碼

5、總結

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

  • git風格的子命令;
  • 自動化的幫助信息;
  • ....

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

相關文章
相關標籤/搜索