カラーコードを渡すと色のプレビューをしてくれるコマンドラインツールを作った

引数としてカラーコードを渡すと,指定した色のプレビューをしてくれるコマンドラインツールを Rust で書いた.

github.com

もちろん 24-bit color 対応の端末エミュレータを使っていないとちゃんと動かない.2018年上半期に作った役に立たないものランキング第一位.

モチベーション

思い付いたので作った.最近は Google でさえカラーコードを打つと色を教えてくれる時代なので,実用性は皆無.「コマンドラインツールと言えばとりあえず Go で書く」みたいなご時世なので,「たまには Go でも書くか〜〜」という気持ちで最初は Go で書いていたが,途中からあまりにつらくなってきたので*1シュッと Rust で書き直した.

コマンドラインオプションパーザ

コマンドラインオプションのパーズ処理を自分で書きたいわけではなかったので,clap というライブラリを用いた.私は目的にフォーカスする男である.

github.com

いくつか usage があるが,今回は一番過激(?)と思われる,YAML を読み込む方式にした.

まず,以下のような YAML ファイルを用意しておく.

name: farbe
version: "0.1.0"
author: Ryota Kameoka <kameoka.ryota@gmail.com>
about: Command-line color preview tool
settings:
    - ArgRequiredElseHelp
args:
    - hex:
        long: hex
        value_name: HEX
        help: 6-digit hexadecimal color code (e.g. \#123456)
        takes_value: true
    - red:
        short: r
        long: red
        help: Red component of the color, used along with --green and --blue
        takes_value: true
    - green:
        short: g
        long: green
        help: Green component of the color, used along with --red and --blue
        takes_value: true
    - blue:
        short: b
        long: blue
        help: Blue component of the color, used along with --red and --green
        takes_value: true
    - width:
        short: w
        long: width
        help: Width of the colored block
        takes_value: true
    - height:
        short: h
        long: height
        help: width of the colored block
        takes_value: true

次に,load_yaml! というマクロが提供されているので,これに上記のファイルのパス (以下では仮に cli.yml としている) を渡す.

let yaml = load_yaml!("./cli.yml");
let app = App::from_yaml(yaml);
let matches = app.get_matches();

すると,コンパイル時に YAML が読み込まれ,いい感じにしてくれる.自分で --version--help の実装を書く必要もない.あとは matches.is_present() とか matches.value_of() とかでよしなに.

パーザコンビネータ

カラーコードを読む必要があったので,regex でやっていってもよかったが,「Rust やったらパーザコンビネータぐらいいい感じのやつあるやろ〜」と思って調べると,nom というライブラリを見つけたので,これを採用した.

github.com

README を読んでいたら,カラーコードをパーズするというお誂え向きなコードが Example セクションに書かれていた.以下はその引用.

#[macro_use]
extern crate nom;

#[derive(Debug,PartialEq)]
pub struct Color {
  pub red:   u8,
  pub green: u8,
  pub blue:  u8,
}

fn from_hex(input: &str) -> Result<u8, std::num::ParseIntError> {
  u8::from_str_radix(input, 16)
}

fn is_hex_digit(c: char) -> bool {
  match c {
    '0'..='9' | 'a'..='f' | 'A'..='F' => true,
    _ => false,
  }
}

named!(hex_primary<&str, u8>,
  map_res!(take_while_m_n!(2, 2, is_hex_digit), from_hex)
);

named!(hex_color<&str, Color>,
  do_parse!(
           tag!("#")   >>
    red:   hex_primary >>
    green: hex_primary >>
    blue:  hex_primary >>
    (Color { red, green, blue })
  )
);

#[test]
fn parse_color() {
  assert_eq!(hex_color("#2F14DF"), Ok(("", Color {
    red: 47,
    green: 20,
    blue: 223,
  })));
}

map_res! とかその辺可読性ちょっとキツくないか,などとは思うものの,まぁ便利.

久し振りに Rust を書いて

Rust は v1.0.0 alpha くらいのときからしばらく watch していて,その後はあまり追っていなかったのだが,久し振りに触ってみると,マクロが広く*2活用されていることに驚いた.Template HaskellGHC の独自拡張に過ぎないが,Rust は言語のコアに含まれているからだろうか.今回利用した clap nom ともに,結構アグレッシヴな使い方をしていて,すごい,となった (こなみかん)

まとめ

Rust でコマンドラインツール,ええんちゃう?

*1:Go,つらくないですか?

*2:n = 2