CLI上でインタラクティブに電卓が叩けるツール「dntk」を書きました

#これ is なに
Go言語で書かれた軽量で鮮やかな見た目でインタラクティブに電卓が叩けるCLIアプリです。
https://github.com/nnao45/dntk
![dntk-demo01.gif](https://qiita-image-store.s3.amazonaws.com/0/124502/1c03de75-715e-e41a-1df4-dfd9fe1f97eb.gif)
最大のポイントは`peco`のような***インタラクティブなインプット***が可能なアプリな所であり、１文字１文字計算していくので楽しく電卓を叩ける所にあります。入れた入力は`delete`キーや`backspace`キーで消せますし、消してる最中もインタラクティブに計算します（これは頑張った😓✌️）
またGNU bcという標準的なツールのラッパーですので、多くのLinux/UNIX/BSD/Mac上で動作が可能です👍

終了するときは、`q`か`Enter`か`Esc`か`Ctrl + c`です、
## 各種インストール方法
### Mac
```bash
$ brew install nnao45/dntk/dntk
```

### Linux
```bash
$ wget https://github.com/nnao45/dntk/releases/download/{最新のバージョン}/dntk-linux-amd64-{最新のバージョン}.tar.gz
$ tar xvfz dntk-linux-amd64-{最新のバージョン}.tar.gz
```

### zplug
```bash
$ zplug 'nnao45/dntk', as:command, from:gh-r
```

#なんで作ったの？

#### 僕が欲しかったから作った。
んですけど、それだと話が終わってしまうのでそれっぽく書きます。

bcコマンドが、

```bash
$ bc -q                                                    
123 * 2
246
```
と無機質なアウトプットで寂しいなと思ったのと、
これがインタラクティブにダララララララララと表示されたら格好良く無いですか！？！？！？！？！？？？と思い作りました✌️（適当）
![dntk-demo-qiita.gif](https://qiita-image-store.s3.amazonaws.com/0/124502/fa90897e-d987-cfc5-f3e5-3c19ce62097c.gif)


またデザインはbash等で`Ctrl + r`を打った時の、

```bash
$ #Ctrl + rを打つ
(reverse-i-search)`':
```

が好きだったのでそれに近しいデザインにするよう設計しました。

#GNU bcとシンタックスが同じ
`dntk`最大の特徴として、`GNU bc`の資源を全て使う事にあります（ラッパーしてるからね😜）

### 足し算

```bash
$ dntk
(dntk): 123 + 456 = 579
```

### 引き算

```bash
$ dntk
(dntk): 987 - 543 = 444
```

### 掛け算

```bash
$ dntk
(dntk): 345 * 984 = 339480
```

### 割り算

```bash
$ dntk
(dntk): 999 / 67 = 14.9104477611
```

デフォルトで小数点以下１０位切り捨てですが、もちろん`--scale`オプションで桁数は指定できます

```bash
$ dntk --scale 20
(dntk): 999 / 67 = 14.91044776119402985074
```

### べき乗計算

```bash
$ dntk
(dntk): 2 ^ 2 ^ 2 ^ 2 = 65536
```

## 各種関数も対応

`a(123)`のような関数入力に対応していて、しかも入力補助機能も搭載しております。
![dntk-demo02.gif](https://qiita-image-store.s3.amazonaws.com/0/124502/029e861a-e794-1911-439e-b8907fc49666.gif)

`a（123）`と打つのに、普通は

```
a
(
1
2
3
)
```

と打たないといけない所、

```
a
1
2
3
Enter
```

で入力が出来るようにしてあります。
意外とこれだけでも相当入力が簡単になるのがわかるかも。。。？

<table>
    <tr>
        <td>function</td>
        <td>command</td>
        <td>detail</td>
    </tr>
    <tr>
        <td>(x)</td>
        <td>(</td>
        <td>Simple round bracket</td>
    </tr>
    <tr>
        <td>sin(x)</td>
        <td>s</td>
        <td>Sin of trigonometric function</td>
    </tr>
    <tr>
        <td>cos(x)</td>
        <td>c</td>
        <td>Cosin of trigonometric function</td>
    </tr>
    <tr>
        <td>atan(x)</td>
        <td>a</td>
        <td>Tangent of inverse trigonometric function</td>
    </tr>
    <tr>
        <td>log(x)</td>
        <td>l</td>
        <td>Logarithm function</td>
    </tr>
    <tr>
        <td>exp(x)</td>
        <td>e</td>
        <td>Exponential function</td>
    </tr>
    <tr>
        <td>j(n,x)</td>
        <td>j</td>
        <td>The n-order Bessel function</td>
    </tr>
</table>

（これもbcをラッパーしてるからってのはそうなんだけど、それもあるからただ乗っかっただけだと悔しいので入力補助機能も入れてみました）

他にもエイリアス張ったりとかの機能がありますが、残りは[README](https://github.com/nnao45/dntk/blob/master/README.md)をご覧ください。

# ちょこっとだけ実装の話

いつもながら実装の話はいいやって人はここでご静聴いただきありがとうございました😆
もし需要があれば詳しく書きますが、まぁ長くなってもなと思いますので、ちょっとだけ。
## termbox-goを使わない選択
こういう事がやりたいときは`termbox-go`が大変便利なのですが今回、僕がやりたかったのはbash等で`Ctrl + r`を打った時の、

```bash
$ #Ctrl + rを打つ
(reverse-i-search)`':
```

のこれでした。これを実現するためには、ターミナル画面全てを別の仮想端末に飛ばしてしまうような仕様は合っていなく、あいにく使っておりません。かなり僕の中でも挑戦でしたが、**しかしこれがとんでも無い沼でした。。。。**以下の問題は、termbox-goを使ってさえいれば簡単に解決します。。。。笑

## キー入力
一番最初につまづいた所はキー入力対応でした。
キー入力したものをインタラクティブに処理するためには、一旦標準入力で受けたキーを全てハンドリングしてなにかしらのバッファに詰めておいた物を操作する流れにしなければいけません。
#### しかし、そもそもキー入力を受け取る事自体大変でした。

まずそもそもgoが標準入力をキー操作から受け取る際には、`io.EOF`を受け取った時点で読み込みを終了する仕様なので、あれ、そもそも無理なんじゃ無いかという所でスタートしました。
これは仕方ないので以下の無理やりシェルコマンドで標準入力を特殊なインプット形式でハンドリングする事で可能としています。

```go
	// disable input buffering
	exec.Command("stty", "-F", "/dev/tty", "cbreak", "min", "1").Run()
	// delete \n
	exec.Command("stty", "-F", "/dev/tty", "erase", "\n").Run()
	// do not display entered characters on the screen
	exec.Command("stty", "-F", "/dev/tty", "-echo").Run()
	// restore the echoing state when exiting
	defer exec.Command("stty", "-F", "/dev/tty", "echo").Run()
```

ポイントは1-2行目で、これを入れる事で`/dev/tty`からの入力をバッファリングする機能を無効にしてしまいます。これにより、１文字ずつ入力が終了するような仕様にしてしまいます。
## 特殊な事をさせたいキー入力を読み取る
例えば`Enter`キーとか`delete`キーとかそういう特殊な事をさせたいキーに対して、とても困りました。
数字や英語はとにかく全部もらったバイト列ごと突っ込んであとで`string`にでも変えてやればいいのですが、`Enter`キーとか`delete`キーとかはそうにもいきません。さらにキー入力で入れるバイト列はASCIIコードなのでただ数字が入ってるだけでも無いのでハンドリングが難しいので、ここでgoの魔力を借ります。goはキー入力に対し、なぜか`string`に変える際にラッパーの経緯により`[123]`のようなASCIIの後ろの２文字３文字の数字だけを表示させる謎仕様です。
これを利用して、受け取ったキー入力をハンドリングさせています。

## ➗0問題
dntkではこの数学界のタブーについては`nil`と表示させています。
普通こんなもの入力されたら、とっととpanicでもexitでもしておけばよいのですが、そう、`dntk`はインタラクティブに計算していくので、例えば以下のような、

```bash
$ dntk
(dntk): dntk): 999 / 0.000067 = 14910447.7611940298
```

をやりたいときどうしても、

```bash
$ dntk
(dntk): dntk): 999 / 0
```

の瞬間ができてしまいます。これでpanicなんて起こしてたら小数点以下の割り算ができないポンコツ電卓になってしまいます、困りました😓

そこで、dntkは`GNU bc`コマンドが数学的エラーを起こした時のみ標準エラー入力を出力する事に注目して、bcコマンドの標準出力と標準エラー出力をハンドリングし、標準エラー入力になにかしら出力されたときは`nil`と表示するだけ、という対策をとりました。

#終わりに
なかなかカッコいい仕上がりになったかなと思っていますが、バグなどまだあるかと思うので、何かあればissueをお待ちしております🐶