続・NYAOS 3000 で bat ファイルとかを拡張子なしで実行する

id:zetamatta さんがfilter2を速攻で実装してくれたid:zetamatta++

で、前回のコードに微妙にバグがあったのとNYAOS 3000更新に伴い書き直してみた。

まず、前回の奴は無駄に suffix の拡張子まで展開していたんだけど、この展開は alias や suffix 自体の展開前にやらないと意味がない。一方、PATHEXT の展開は alias の後にやらないとダメ。なので、両者を別々に書くことに。

修正

function nyaos.filter(cmdline)
  cmdline = cmdline:gsub('^%S+', function(cmd)
    if cmd:match('%.') then
      return false
    end

    local path='.;' .. os.getenv('PATH')

    local cmdl=cmd:lower()
    local variation={}
    for key,val in pairs(nyaos.suffix) do
      variation[ cmdl .. '.' .. key:lower() ] = true
    end

    for path1 in path:gmatch('[^;]+') do
      for fname in nyaos.dir(path1) do
        if variation[ fname:lower() ] then
          return fname
        end
      end
    end
  end)
  return cmdline
end

function nyaos.filter2(cmdline)
  cmdline = cmdline:gsub('^%S+', function(cmd)
    if cmd:match('%.') then
      return false
    end

    local path='.;' .. os.getenv('PATH')

    local cmdl=cmd:lower()
    local variation={}
    for ext in os.getenv('PATHEXT'):gmatch('[^;]+') do
      variation[ cmdl .. ext:lower() ] = true
    end

    for path1 in path:gmatch('[^;]+') do
      for fname in nyaos.dir(path1) do
        if variation[ fname:lower() ] then
          return fname
        end
      end
    end
  end)
  return cmdline
end

filter を分けた以外の前回との違いは、

  • gsub のパターンを '^%S+' に修正
  • path:gmatch のパターンを '[^;]+' に修正
  • ext を :lower() してなかったのを修正
  • nyaos.suffix を pairs() で回すように

最初の3つは単純にバグで、手元の最新版では直ってたんだけど、ブログに貼る時に古いバージョンを貼ってしまったらしいorz

改善

で、似たようなコードが並んでるとまとめたくなるのがプログラマ。あまりにも酷いので改善を試みた。この辺りから完全に無駄な作業。

function keys(t)
  local keylist = {}
  for k in pairs(t) do
    table.insert(keylist, k)
  end
  local i = 0
  return function()
    i = i + 1
    return keylist[i]
  end
end

function addext(exts, mid)
  mid = mid or ''
  return function(cmd)
    if cmd:match('%.') then
      return false
    end

    local variation={}
    local cmdl=cmd:lower()
    for ext, _ in exts do
      variation[ cmdl .. mid .. ext:lower() ] = true
    end

    local path='.;' .. os.getenv('PATH')

    for path1 in path:gmatch('[^;]+') do
      for fname in nyaos.dir(path1) do
        if variation[ fname:lower() ] then
          return fname
        end
      end
    end
  end
end

function nyaos.filter2(cmdline)
  return cmdline:gsub('^%S+',  addext(os.getenv('PATHEXT'):gmatch('[^;]+')))
end

function nyaos.filter(cmdline)
  return cmdline:gsub('^%S+',  addext(keys(nyaos.suffix), '.'))
end

改善したはずだけどできあがったコードもそれなりに酷い。
まず、pairs() が多値を返しやがるので関数のパラメータとして一般化しづらい。素直にイテレータ関数作ってくれればいいのに…。
仕方なく keys() とかいう怪しい関数を作ってみた。最初は以下のようにしてみたんだけど上手くいかんかった。よくわかってない。

function keys(t)
  local next, t, i = pairs(t)
  local value = nil
  return function()
    i, value = next(t, i)
    return value
  end
end

更に改善

改善と言うか改悪と言うか。私は実は filter に、

  • %(...) を lua コードとして解釈して実行結果で置き換える
  • $(...) をコマンドとして解釈して実行結果で置き換える

と言うのも使いもしないのに入れていたんだけど、これらをスマートに挿入したくなったので filter の値をテーブルで置き換えてみることにした。

function hooks(t)
  return setmetatable(t or {},
    { __call =
    function(self, cmdline)
      for k, v in pairs(self) do
        cmdline = v(cmdline)
      end
      return cmdline
    end
    })
end

こんな感じのを用意して、

nyaos.filter = hooks()
function nyaos.filter.ext(cmdline)
  return cmdline:gsub('^%S+',  addext(keys(nyaos.suffix), '.'))
end

function nyaos.filter.luacode(cmdline)
  return cmdline:gsub('%%(%b())', function(m)
    local status, result = pcall( loadstring('return '..m) )
    if status then
      return result
    end
    print('Ignore invalid Lua expression: '..m)
  end)
end

function nyaos.filter.command(cmdline)
  return cmdline:gsub('$(%b())', function(m)
    return nyaos.eval(m:sub(2,-2))
  end)
end

こんな風にすればフィルタを追加しやすい!(TODO: 順序の制御)


…が、しかし。filter は呼ばれなかったorz
うーん、filter の値が関数か調べてから呼んでるのだろうか。残念。素直に 1 つの関数に書きます。