node.jsでCLI

コマンドラインツールまたはシェルスクリプト相当を書こうというお話。node.jsで書く利点はWindowsでもOSXでもLinuxデスクトップでも全く同じように実行可能だから。node.jsが入っているという前提条件だけど。

## 前準備

オフィシャルサイトやQiitaを参考にnode.jsとnpmが使えるようにする。

## プロジェクトの作成

特別なことは何もなくいつも通り ``npm init`` で。

## shebang

```
#!/usr/bin/env node
```

shebangはUnix系の実行に必要。Windowだとこれは無視される。詳しくは https://docs.npmjs.com/files/package.json で。

## オプションパーサー

コマンド引数を素のnode.jsで扱うのはちょっと辛いので ``commander`` パッケージを利用する。

https://github.com/tj/commander.js

## 入力プロンプト

これもやはり便利な ``co-prompt`` ``co`` パッケージを使う。

https://github.com/tj/co-prompt

## Unixライクコマンド実行

``shelljs`` を使うとnode.jsで ``ls`` や ``echo`` っぽいコードが簡単に書ける素晴らしいパッケージ。勿論Windowsでも使えます。

https://github.com/shelljs/shelljs

## package.json

上記のパッケージをプロジェクトにインストール。

```
$ npm i commander co-prompt co shelljs --save
$ npm list --depth=0
+-- co@4.6.0
+-- co-prompt@1.0.0
+-- commander@2.9.0
`-- shelljs@0.7.0
```

## サンプルコード

プロジェクト名は``mycli``で。

```package.json
{
  "name": "mycli",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "bin": {
    "mycli": "./index.js"
  },
  "dependencies": {
    "co": "^4.6.0",
    "co-prompt": "^1.0.0",
    "commander": "^2.9.0",
    "shelljs": "^0.7.0"
  }
}
```

今回は紹介だけなので ``index.js`` をこんな風に。ファイル名指定とユーザー名パスワード入力ができます。

```index.js
#!/usr/bin/env node
require('shelljs/global')

program = require('commander')
prompt = require('co-prompt')
co = require('co')

program
  .arguments('<file>')
  .option('-u, --username <username>', 'The user to authenticate as')
  .option('-p, --password <password>', 'The user\'s password')
  .action(function(file) {
    co(function *() {
      var username, password
      if (program.username) {
        username = program.username
      } else {
        username = yield prompt('username: ')
      }
      password = yield prompt.password('password: ')
      display(username, password, file)
    })
  })
  .parse(process.argv)

display = (username, password, file) => {
  echo(`user: ${username} pass: ${password} file: ${file}`)
}
```

``index.js``に実行権を付与して実行。まずはヘルプから。

```
$ index.js --help

  Usage: index [options] <file>

  Options:

    -h, --help                 output usage information
    -u, --username <username>  The user to authenticate as
    -p, --password <password>  The user's password

```

オプション関連は``commander``が全部処理してくれてヘルプも自動的に生成される。すごく便利。

実際の実行イメージはこんな感じ。

```
$ index.js hoge.txt
username: foo     
password: ***
user: foo pass: bar file: hoge.txt

$ index.js hoge.txt -u foo
password: ***
user: foo pass: bar file: hoge.txt

```

入力関連は``co-prompt``で実現。パスワードに``***``とマスクをかけて入力するのも簡単。オプションで直接指定する時はさすがにマスクかからないけど実用性抜群。

``shelljs``は今回``echo()``だけ使ったが、基本的なUnixコマンドライクな関数が揃っててこれもまた便利。npmのscriptsでも使えるので簡単なことならタスクマネージャーよりこっちがフットワーク軽いです。

``shelljs`` で使える関数の例。

```
cd([dir])
pwd()
ls([options,] [path, ...])
ls([options,] path_array)
find(path [, path ...])
find(path_array)
cp([options,] source [, source ...], dest)
cp([options,] source_array, dest)
rm([options,] file [, file ...])
rm([options,] file_array)
mv([options ,] source [, source ...], dest')
mv([options ,] source_array, dest')
mkdir([options,] dir [, dir ...])
mkdir([options,] dir_array)
test(expression)
cat(file [, file ...])
cat(file_array)
head([{'-n', },] file [, file ...])
head([{'-n', },] file_array)
tail([{'-n', },] file [, file ...])
tail([{'-n', },] file_array)
〜
```

``shelljs``サンプルコード。

```:shelljs-example.js
require('shelljs/global');

if (!which('git')) {
  echo('Sorry, this script requires git');
  exit(1);
}

// Copy files to release dir
rm('-rf', 'out/Release');
cp('-R', 'stuff/', 'out/Release');

// Replace macros in each .js file
cd('lib');
ls('*.js').forEach(function(file) {
  sed('-i', 'BUILD_VERSION', 'v0.1.2', file);
  sed('-i', /^.*REMOVE_THIS_LINE.*$/, '', file);
  sed('-i', /.*REPLACE_LINE_WITH_MACRO.*\n/, cat('macro.js'), file);
});
cd('..');

// Run external tool synchronously
if (exec('git commit -am "Auto-commit"').code !== 0) {
  echo('Error: Git commit failed');
  exit(1);
}
```

他にもおすすめのnode.jsパッケージは出力テキストをカラーリングする``chalk``や進捗を出力する``progress``などなど。

おすすめパッケージがあればコメント下さい🙇

## mycliインストール

パッケージのルートにて``npm link``を実行する。

```
$ npm link
npm WARN mycli@1.0.0 No description
npm WARN mycli@1.0.0 No repository field.
/usr/local/bin/mycli -> /usr/local/lib/node_modules/mycli/index.js
/usr/local/lib/node_modules/mycli -> /Users/uchcode/Desktop/mycli
$ mycli --help

  Usage: mycli [options] <file>

  Options:

    -h, --help                 output usage information
    -u, --username <username>  The user to authenticate as
    -p, --password <password>  The user's password

```

[パッケージを公開](http://qiita.com/TsutomuNakamura/items/f943e0490d509f128ae2)すればどの環境でも簡単にインストール実行ができます。

node.jsすごいね。