【NYAGOS】プロンプトで使える特殊文字と ANSI エスケープシーケンスを Lua でラップする

2018/12/01 追記

前書き

前回の記事でプロンプトをごにょごにょしていたんですが、プロンプトだけで使える特殊文字だとか、ANSI エスケープシーケンスだとかをいちいち文字列で書くのが面倒になりました。

そもそもぱぱっと書けませんし読めませんよそんなもん。ゆとり教育では学びませんでした。

しかし、現代では教育だけでなくマシンの CPU やメモリもゆとりが出てきています。可読性を高めるため、全部 Lua でラップしてしまいましょう。

プロンプトだけで使える特殊文字

色々探していたらMSDNに一覧がありました。ここには転記しません。かわりに今回使うコードを載せます。

share.prompt = {
  eq='$q',     -- equal '='
  dollar='$$', -- dollar '$'
  time='$t',   -- current time
  date='$d',   -- current date
  path='$p',   -- current path (with drive letter)
  ver='$v',    -- windows version
  drive='$n',  -- current drive letter
  gt='$g',     -- greater than '>'
  lt='$l',     -- less than '<'
  pipe='$b',   -- pipe '|'
  crlf='$_',   -- line break
  esc='$e',    -- escape
  bs='$h',     -- backspace
  amp='$a',    -- ampersand '&'
  l_par='$c',  -- left parenthesis '('
  r_par='$f',  -- right parenthesis ')'
  space='$s',  -- space
}

ちなみに$の後の文字は大文字・小文字を区別しないそうです。

また、$h(backspace)は$t(time)とか$d(date)とかでとってきた文字列を頑張って削る時に使うそうです。全くもってゆとりがありません。

ANSI エスケープシーケンス

ANSI エスケープシーケンス自体についてはこの記事が非常にわかりやすかったです。歴史的経緯や古き良き C 言語のサンプルもちょこっと書いてあるので、暇な人は読んでみましょう。

細かい一覧は英語版 Wikipediaを読むのが一番いいんですが、何をどれだけ読もうとも、Nyagos で使えるのはansicolor書いてあるやつだけです。なので、ここだけラップします。

ANSI エスケープシーケンスのラップ

ANSI エスケープシーケンスはプロンプト以外でも普通に使えるので、先ほど作ったshare.promptテーブルにぶちこむのは流石に乱暴です。

別途share.escape_sequenceと言うテーブルを作ります。

share.escape_sequence = {
  -- attribute
  attr = {
    off='0',
    bold='1',
    bold_off='21',
    underline='4',
    underline_off='24',
    blink='5',
    blink_off='25'
  },

  -- foreground
  fg = {
    black='30',
    red='31',
    green='32',
    yellow='33',
    blue='34',
    magenta='35',
    cyan='36',
    white='37',
    default='39',
    -- prefix 'l' is 'Light'
    l_gray='90',
    l_red='91',
    l_green='92',
    l_yellow='93',
    l_blue='94',
    l_magenta='95',
    l_cyan='96',
    l_white='97'
  },

  -- background
  bg = {
    black='40',
    red='41',
    green='42',
    yellow='43',
    blue='44',
    magenta='45',
    cyan='46',
    white='47',
    default='49',
    -- prefix 'l' is 'Light'
    l_gray='100',
    l_red='101',
    l_green='102',
    l_yellow='103',
    l_blue='104',
    l_magenta='105',
    l_cyan='106',
    l_white='107'
  },
}

ANSI エスケープシーケンスの文法

厳密にはANSI エスケープシーケンスにおける色付けの文法ですが…。

基本的には\x1b[(該当するコード)mと言う文字列を作成します。例えば、赤字にしたいなら\x1b[31mですね。

「ここからここまでの文字列に適応する」と言った指定はできないため、適用させた後は再度 ANSI エスケープシーケンスを作って元に戻す必要があります。(そのためにdefaultとかoffと言った要素がある。)ちなみにこれを忘れるとその後の標準出力がずっと真っ赤になったりします。怖いですね。

また、複数の要素を一気に指定したい場合は\x1b[(該当するコード);(該当するコード);...mと記述することができます。例えば、文字色は赤字、背景色は灰色、ついでに下線も引いてやろう、って場合は、\x1b[31;100;4mとなります。指定するコードの順番に制限はないです。(つまり、\x1b[4;100;31mでも同じ効果を得られる。)

ANSI エスケープシーケンスを作成する関数

上記の内容を踏まえ、share.escape_sequenceに各コードのテーブルを渡すと ANSI エスケープシーケンスを作ってくれる関数を作ります。

share.escape_sequence = {
  -- ...省略...

  create_sequence = function(...)
    local attrs = {...}
    local joined_attrs = ''

    for n, v in pairs(attrs) do
      local val = tostring(v)
      if val ~= nil then
        joined_attrs = joined_attrs ~= ''
          and joined_attrs .. ';' .. val
          or val
      end
    end

    return string.format('\x1b[%sm', joined_attrs)
  end
}

呼び出しはこんなイメージです。何をやっているのか大分わかりやすくなったと思いませんか?

local esc = share.escape_sequence
-- colorに'\x1b[2;31;49m'が返ってくる
local color = esc.create_sequence(esc.attr.bold, esc.fg.red, esc.bg.default)

実践

実践って言うか、単に私の.nyagosの中身から抜粋しただけですが…。

share.org_prompter=nyagos.prompt
nyagos.prompt = function(this)
    local prompt = share.prompt
    local esc = share.escape_sequence

    -- add dir name (with drive letter)
    local c_dir = esc.create_sequence(esc.attr.bold, esc.fg.red)

    local prompt_message = string.format('%s[%s]', c_dir, prompt.path)

    -- check git branch
    local git_branch_name = nyagos.eval('git rev-parse --abbrev-ref HEAD 2>nul')

    if (git_branch_name ~= '') then
        -- add git branch name
        local c_branch = esc.create_sequence(esc.fg.l_yellow)
        prompt_message = string.format(prompt_message .. ' %s[%s]', c_branch, git_branch_name)
    end

    -- add line break
    prompt_message = prompt_message .. prompt.crlf

    -- add dollar
    local c_dollar = esc.create_sequence(esc.fg.red)

    prompt_message = prompt_message .. c_dollar .. prompt.dollar

    -- add input command color
    local c_input_command = esc.create_sequence(esc.fg.white)

    prompt_message = string.format(prompt_message .. ' %s', c_input_command)

    return share.org_prompter(prompt_message)
end

元々が冗長な仕組みなので仕方ないんですが、どうしても長くなってしまいますね。

まとめ

プロンプトの方はともかく、ANSI エスケープシーケンスの方は何かに応用できるかもしれません。(エラーメッセージに色を付ける、とか…。)

現状は全くのノーアイデアです。何か思いついたらまた作りましょう。

参考