動かざることバグの如し

近づきたいよ 君の理想に

Node.jsでコマンドライン引数処理を行うならcommand-line-argsがよさげ

最近だとコマンドラインツールはGolangで書かれることが多くなっていたが、Nodejsでコマンドラインで処理を受け付けたい、ということも全然あると思う。

そのときに必ず当たる壁がコマンドライン引数の処理である。

引数のパース、必須項目の扱い、ヘルプの表示等々自前で実装するのは色々ツラい。

そこでargvを使ってる人も多いと思う。が、最終更新が6年前でそれ以降一切メンテがされていない。。。流石に不安になる。

ってことで代役を探していたら、「command-line-args」というよさげなライブラリを見つけた。

インストール

yarn add command-line-args

or

npm install command-line-args

サンプル

const commandLineArgs = require('command-line-args');

const optionDefinitions = [
  {
    name: 'verbose',
    alias: 'v',
    type: Boolean
  },
  {
    name: 'src',
    type: String,
  },
  {
    name: 'timeout',
    alias: 't',
    type: Number,
    defaultValue: 3
  }
];
const options = commandLineArgs(optionDefinitions);

console.log(options);

で、実行するとoptionsに値が入っている

$node index.js --src image.png -t 10
{ src: 'image.png', timeout: 10 }

あとは公式ドキュメントを見ればなんとかなる(投げやりのスタイル

デフォルトオプションを定義したい

defaultValueを使う。さっきの例だと、timeoutのdefaultValueを3にしたのでtimeoutを指定しないで実行すると、

node index.js --src image.png
{ timeout: 3, src: 'image.png' }

となる。

1つのオプションで複数受け付けられるようにしてほしい

multipleをつける。さっきの例でいうと

{
  name: 'src',
  type: String,
  multiple: true
},

にすると

node index.js --src image1.png --src image2.png
{ timeout: 3, src: [ 'image1.png', 'image2.png' ] }

とできる。逆にmultiple指定指定してない状態で複数指定すると、 ALREADY_SET: Singular option already set [src=image1.png] のように例外エラーになる。

ヘルプ(usage)を表示したい

command-line-argsの特徴的な点として、それ自体には表示機能は実装されていない点。

じゃあどうすんのって話だが同じ作者のcommand-line-usageを入れればおk

yarn add command-line-usage

さっきの例をベースに改良したバージョンが以下

const commandLineArgs = require('command-line-args');
const commandLineUsage = require('command-line-usage');

const optionDefinitions = [
  {
    name: 'verbose',
    alias: 'v',
    type: Boolean
  },
  {
    name: 'src',
    type: String,
    defaultOption: true,
    multiple: true,
    description: 'file path'
  },
  {
    name: 'timeout',
    alias: 't',
    type: Number,
    defaultValue: 3,
    description: 'convert timeout'
  },
  {
    name: 'help',
    alias: 'h',
    type: Boolean,
    description: 'show help'
  }
];

const sections = [
  {
    header: 'Sample app',
    content: 'this is sample app for command-line-args'
  },
  {
    header: 'Options',
    optionList: optionDefinitions
  }
];

const options = commandLineArgs(optionDefinitions);
if(options.help) {
  const usage = commandLineUsage(sections);
  console.log(usage);
  process.exit(0);
}

console.log(options);

すると以下のようなヘルプが出来上がる。

~/tmp/node_options $node index.js --help

Sample app

  this is sample app for command-line-args

Options

  -v, --verbose
  --src string[]         file path
  -t, --timeout number   convert timeout
  -h, --help             show help

こっちに関しても結構カスタマイズできるので詳しくは公式ドキュメントを見てほしい。

バリデーション機能をつけたい

自分で実装しろ

と、いうのもcommand-line-args自体がそもそも対応してないし、対応する気もない。その理由は公式ドキュメントに書かれており、

Because this module specialises in one task - finding and extracting values set against defined options in process.argv.

要は昔は対応してた時期もあったけど、対応し始めるとキリなくてメンテできへんしつけねぇって話らしい。

IDCFクラウドを300円運用する

メモになってしまうが、

  • マシンタイプを一番安い「Light.S1」で選択
  • イメージをおすすめ Template ではなく、 ISOタブにある任意のISOを選択
    • 今回はUbuntu 18.04を選択したがうまくいった
  • ディスクを5GBにする

これでマシン=200円、ディスク=100円で300円で国内にサーバーを持てる。最強か

ただデメリットはテンプレートによるインストールではないのでインストールはされていない

自分でIDCFのWEBコンソールに入ってUbuntuのインストールする必要がある。

その他

任意のイメージをアップして使うこともできるらしい。以下はSnappy Ubuntu Coreでインストールした例

参考リンク

シミュレーター使っても最低15GBしか指定できなかったし、この方法裏技だと思ったけど結構これについて書いてる記事が多い。結構有名な話だったのか。。。

Chrome拡張機能で特定のHeaderのときだけブロックする方法

Chrome拡張機能はすごくて、chrome.webRequestのイベントを駆使すると特定の条件のURLをブロックしたり、リダイレクトすることができる

以下はexample.comを含む画像を白紙にするコード 画像かどうかはtypesの「images」で見てる

var pattern = "https://example.com*";

function redirect(details) {
  console.log('blocked', details.url);
  return {cancel: true};
}

chrome.webRequest.onBeforeRequest.addListener(
  redirect,
  {urls:[pattern], types:["image"]},
  ["blocking"]
);

が、↑のようにchrome.webRequest.onBeforeRequest.addListener()は実はURLしか条件に入れることが出来ない だからヘッダーを見て〜のときはブロック、といったことができない

解決策

Headerを見れるchrome.webRequest.onHeadersReceived.addListener()を使う

以下はサイズが1MB以下のJPEG画像をブロックする例

'use strict';
function blockImages(details) {
  var responseHeaders = details.responseHeaders;
  var flag = 0;
  for (var i = 0; i < responseHeaders.length; i++) {
    if (responseHeaders[i].name.toLowerCase() == 'content-length') {
      if(responseHeaders[i].value < 100000) {
        flag++;
      }
    }
  }
  for (var i = 0; i < responseHeaders.length; i++) {
    if (responseHeaders[i].name.toLowerCase() == 'content-type') {
      if(responseHeaders[i].value == 'image/jpeg') {
        flag++;
      }
    }
  }
  if(flag == 2) {
    console.log('blocked', details.url);
    return {cancel: true};
  } else {
    return { responseHeaders: responseHeaders };
  }
}

chrome.webRequest.onHeadersReceived.addListener(
  blockImages,
  {urls: ['https://example.com*']},
  ['responseHeaders', 'blocking']
);

なんでわざわざfor文で回しているかって話だが、responseHeadersはヘッダー名前がそのままKeyになってくれないので何番目かわからない。よって回すしかない。。。

ちなみに監視対象URLをすべてにする場合は

urls: ['<all_urls>'],

とすればいい

Rubyで複数のバージョン(version number)をソートしたい

かなりニッチなニーズかもしれないけどメモ

やりたいこと

例えば以下のようなバージョンの配列があったとして

versions = ["1.1", "2.0", "1.1.4", "1.6", "2.4.1"]

これを古→新順にソートしたいとする

解決策1 ライブラリを使う

探したらあった なんとGithub

irb(main):002:0> versions = ["1.1", "2.0", "1.1.4", "1.6", "2.4.1"]
=> ["1.1", "2.0", "1.1.4", "1.6", "2.4.1"]
irb(main):003:0> VersionSorter.sort(versions)
=> ["1.1", "1.1.4", "1.6", "2.0", "2.4.1"]

ちゃんとソートできている

解決策2 Gem::Versionを使う

どういうこっちゃって話だけど見たほうが早い

irb(main):004:0> versions = ["1.1", "2.0", "1.1.4", "1.6", "2.4.1"]
=> ["1.1", "2.0", "1.1.4", "1.6", "2.4.1"]
irb(main):005:0> versions.sort_by { |v| Gem::Version.new(v) }
=> ["1.1", "1.1.4", "1.6", "2.0", "2.4.1"]

このGemクラスは特にインストールとかしなくても使えるっぽい。

docs.ruby-lang.org

もともとGihtubもRubygemのホスティングしてたっぽいし中身は実は同じなのかもしれない。。。。

NodejsでファイルからMD5ハッシュを取得したい

md5はこういうの

~/tmp $md5 tmp.wav
MD5 (tmp.wav) = a7b3e6a4865d0b5ae39db05371008c2a

をNodejsで求めたい

環境

  • nodejs 8
    • けど見てる感じ4以上なら動きそう 10でも動いたし

コード

const crypto = require("crypto");
const fs = require("fs");

function md5file(filePath) {
    const target = fs.readFileSync(filePath);
    const md5hash = crypto.createHash('md5');
    md5hash.update(target);
    return md5hash.digest("hex");
}

console.log(md5file('./tmp.wav'));
~/tmp $node md5.js
a7b3e6a4865d0b5ae39db05371008c2a

あってるっぽい(当然

参考リンク先でも言及されてるが、〜GB単位のファイルを扱う場合だとfs.readFileSync()が死ぬ(多分メモリに乗せるので

それも考慮した作りにしたい場合はfs.createReadStream()でストリームを扱う(以下リンク先に丸投げ

参考リンク