RustでBash風のaliasをさらに魔改造したalias-exeを作成した


# alias-exeについて

Rustで作成したシングルバイナリのCUIコマンドで、Windowsにalias相当の機能を提供します。

こちらが基本操作

```cmd
# 1 hello aliasを作成
> alias edit hello

# 2. 開いたエディタで内容を編集
echo hello $(echo $1)

# 3. 実行
> hello world
hello world
```

GitHubは [wordijp/alias-exe](https://github.com/wordijp/alias-exe)
マニュアルとして README.md と、詳しい仕様についての[ドキュメントサイト](https://wordijp.github.io/alias-exe/)を用意しています。
[バイナリ](https://github.com/wordijp/alias-exe/releases)を用意していますが、 [scoop.sh](https://scoop.sh/) 対応ですのでこちらの方がインストールや更新追従も手軽です、aliasは`list`ディレクトリに作成されますが、こちらのPATHも自動で設定してくれます。

```cmd
> scoop bucket add wordijp https://github.com/wordijp/scoop-bucket
> scoop install wordijp/alias-exe
```

機能としては、aliasの登録/更新/削除などの管理機能、実行時に引数処理用の変数(`$0`、`$1`、`"$@"`、etc)、コマンドのネスト(`$( ... )`)の基本に加え、Bash functionのように複数コマンドの実行や、cmd/Bashから欲しい機能を盛り込み、グルーコードとしてmrubyに対応したりなどの結果、魔改造めいたものとなりました。
mrubyは単体では機能が乏しかったため、欲しい機能をRust側で実装して対応しています[^1]。

[^1]: STDIN、STDOUT、STDERR、ARGV、cmdとmruby間の変数のやり取りを対応

# 作った経緯

Windowsでalias相当の事をしようとしたら`doskey`コマンドでマクロを登録し、癖が強い[^2]batファイルで頑張るくらいしかありませんでした。
ターミナル環境ではパスや実行環境の差異に悩まされるのでいまいち踏み切れず、そしてつい先月の[環境変数のPATHが長すぎてWSL2が突然動かなくなる](https://qiita.com/kotauchisunsun/items/777a77dab43e7b61ff1a)という一波乱が引き金となり、PATHを短くする為にもalias-exeの作成に踏み切りました。

[^2]: しかも「バッチ ジョブを終了しますか (Y/N)?」が煩わしい

## サンプルコード

aliasとしては冒頭の使い方で十分なのですが、紹介用に他の機能を網羅したコードを記載します、[ドキュメントの機能一覧コード](https://wordijp.github.io/alias-exe/#supportOverview)そのままですが、以下のような事が出来ます。

まずoverview aliasを作成し

```cmd
> alias edit overview
```

内容の編集、この時にVimかnotepadエディタが開きます。

```cmd
# overview.txt

# command
echo command

# nested command
echo nested $(echo command)

# arguments
echo argc=$# args="$@"

# built-in commands
@set ENV=builtin_set_environment
echo %ENV%

CD
@pushd ..
CD
@popd
CD

# mruby as glue code
```ruby
# this is mruby comment
puts "mruby code"
def add(a, b)
  a + b
end
``` DUMMY
# ↑Qiitaのスタイル崩れ防止のDUMMY、取り除いてください

# nested mruby
echo 2 + 3 = <%= add(2, 3) %>
```

実行

```cmd
> overview hoge foo "hello world"
command
nested command
argc=3 args=hoge foo "hello world"
builtin_set_environment
C:\path\to
C:\path
C:\path\to
mruby code
2 + 3 = 5
```

# FizzBuzzを実装してみる

これだけだとなんなので、機能紹介も兼ねて色んな方法で[FizzBuzz](https://ja.wikipedia.org/wiki/Fizz_Buzz)を実装してみようかと思います。
どれも`> fizzbuzz 1 100`で実行し、同じ結果になります。

<details>
<summary>mrubyでオーソドックスにFizzBuzz</summary><div>

```cmd
# fizzbuzz.txt
```ruby
for i in $1..$2 do
  if i % 15 == 0
    puts 'FizzBuzz'
  elsif i % 3 == 0
    puts 'Fizz'
  elsif i % 5 == 0
    puts 'Buzz'
  else
    puts i
  end
end
``` DUMMY
```

</div></details>

<details>
<summary>複数aliasを組み合わせてFizzBuzz</summary><div>

内部用aliasはprivateスコープの意味合いとして _ 始まりにしています。

```cmd
# _rng.txt
```ruby
for i in $1..$2 do
  puts i
end
``` DUMMY
```

```cmd
# _fizzbuzz.txt
```ruby
while s = STDIN.gets
  n = s.to_i
  if n > 0 && n % 15 == 0
    puts "FizzBuzz"
  else
    puts s.chomp
  end
end
``` DUMMY
```

```cmd
# _fizz.txt
```ruby
while s = STDIN.gets
  n = s.to_i
  if n > 0 && n % 3 == 0
    puts "Fizz"
  else
    puts s.chomp
  end
end
``` DUMMY
```

```cmd
# _buzz.txt
```ruby
while s = STDIN.gets
  n = s.to_i
  if n > 0 && n % 5 == 0
    puts "Buzz"
  else
    puts s.chomp
  end
end
``` DUMMY
```

```cmd
# fizzbuzz.txt
_rng $1 $2 | _fizzbuzz | _fizz | _buzz
```

</div></details>

<details>
<summary>ネストしまくったFizzBuzz</summary><div>

良く分からないコードになった

```cmd
# _args.txt

```ruby
def add(a, b)
  a + b
end
def ary(a, b)
  (a..b).to_a
end
``` DUMMY

# 1から100までのruby配列を作成
```ruby
a = [].from_cmd %q( <%= ($(echo $1)..$(echo <%= add($#, 3) %>0)).to_a.to_cmd %> <%= ary(51,$2).to_cmd %> )
``` DUMMY

echo <%= a.to_cmd %>
```

```cmd
# _fizzbuzz_args.txt
```ruby
Buzz = 'Buzz'
``` DUMMY

```ruby
for x in ARGV
  x = x.to_i
  s = ((x%3==0)?'$(echo Fizz)':'')+((x%5==0)?<%= Bu$(echo zz)%>:'')
  puts (s!="") ? s : x
end
``` DUMMY
```

```cmd
# fizzbuzz.txt
_fizzbuzz_args $(_args $1 $2)
```

</div></details>

# おわりに

alias作成先へのPATHは先頭に追加して優先度を上げた方が使いやすいかとは思いますが、既存コマンドとの名前被りの時に思いもよらないハマりを引き起こしてしまうかもしれません、僕はそれを回避するためにalias名を`]hoge`と ] 始まりの名前にしています、ほんとは @ 始まりにしたかったのですが実行時に無視されてしまう為、他の押しやすく被らない接頭語を探した結果 ] 始まりとなりました。