これ is なに
Go言語で書かれた軽量で鮮やかな見た目でインタラクティブに電卓が叩けるCLIアプリです。
https://github.com/nnao45/dntk
最大のポイントはpeco
のようなインタラクティブなインプットが可能なアプリな所であり、1文字1文字計算していくので楽しく電卓を叩ける所にあります。入れた入力はdelete
キーやbackspace
キーで消せますし、消してる最中もインタラクティブに計算します(これは頑張った😓✌️)
またGNU bcという標準的なツールのラッパーですので、多くのLinux/UNIX/BSD/Mac上で動作が可能です👍
終了するときは、q
かEnter
かEsc
かCtrl + c
です、
各種インストール方法
Mac
$ brew install nnao45/dntk/dntk
Linux
$ wget https://github.com/nnao45/dntk/releases/download/{最新のバージョン}/dntk-linux-amd64-{最新のバージョン}.tar.gz
$ tar xvfz dntk-linux-amd64-{最新のバージョン}.tar.gz
zplug
$ zplug 'nnao45/dntk', as:command, from:gh-r
なんで作ったの?
僕が欲しかったから作った。
んですけど、それだと話が終わってしまうのでそれっぽく書きます。
bcコマンドが、
$ bc -q
123 * 2
246
と無機質なアウトプットで寂しいなと思ったのと、
これがインタラクティブにダララララララララと表示されたら格好良く無いですか!?!?!?!?!???と思い作りました✌️(適当)
またデザインはbash等でCtrl + r
を打った時の、
$ #Ctrl + rを打つ
(reverse-i-search)`':
が好きだったのでそれに近しいデザインにするよう設計しました。
GNU bcとシンタックスが同じ
dntk
最大の特徴として、GNU bc
の資源を全て使う事にあります(ラッパーしてるからね😜)
足し算
$ dntk
(dntk): 123 + 456 = 579
引き算
$ dntk
(dntk): 987 - 543 = 444
掛け算
$ dntk
(dntk): 345 * 984 = 339480
割り算
$ dntk
(dntk): 999 / 67 = 14.9104477611
デフォルトで小数点以下10位切り捨てですが、もちろん--scale
オプションで桁数は指定できます
$ dntk --scale 20
(dntk): 999 / 67 = 14.91044776119402985074
べき乗計算
$ dntk
(dntk): 2 ^ 2 ^ 2 ^ 2 = 65536
各種関数も対応
a(123)
のような関数入力に対応していて、しかも入力補助機能も搭載しております。
a(123)
と打つのに、普通は
a
(
1
2
3
)
と打たないといけない所、
a
1
2
3
Enter
で入力が出来るようにしてあります。
意外とこれだけでも相当入力が簡単になるのがわかるかも。。。?
function | command | detail |
(x) | ( | Simple round bracket |
sin(x) | s | Sin of trigonometric function |
cos(x) | c | Cosin of trigonometric function |
atan(x) | a | Tangent of inverse trigonometric function |
log(x) | l | Logarithm function |
exp(x) | e | Exponential function |
j(n,x) | j | The n-order Bessel function |
(これもbcをラッパーしてるからってのはそうなんだけど、それもあるからただ乗っかっただけだと悔しいので入力補助機能も入れてみました)
他にもエイリアス張ったりとかの機能がありますが、残りはREADMEをご覧ください。
ちょこっとだけ実装の話
いつもながら実装の話はいいやって人はここでご静聴いただきありがとうございました😆
もし需要があれば詳しく書きますが、まぁ長くなってもなと思いますので、ちょっとだけ。
termbox-goを使わない選択
こういう事がやりたいときはtermbox-go
が大変便利なのですが今回、僕がやりたかったのはbash等でCtrl + r
を打った時の、
$ #Ctrl + rを打つ
(reverse-i-search)`':
のこれでした。これを実現するためには、ターミナル画面全てを別の仮想端末に飛ばしてしまうような仕様は合っていなく、あいにく使っておりません。かなり僕の中でも挑戦でしたが、**しかしこれがとんでも無い沼でした。。。。**以下の問題は、termbox-goを使ってさえいれば簡単に解決します。。。。笑
キー入力
一番最初につまづいた所はキー入力対応でした。
キー入力したものをインタラクティブに処理するためには、一旦標準入力で受けたキーを全てハンドリングしてなにかしらのバッファに詰めておいた物を操作する流れにしなければいけません。
しかし、そもそもキー入力を受け取る事自体大変でした。
まずそもそもgoが標準入力をキー操作から受け取る際には、io.EOF
を受け取った時点で読み込みを終了する仕様なので、あれ、そもそも無理なんじゃ無いかという所でスタートしました。
これは仕方ないので以下の無理やりシェルコマンドで標準入力を特殊なインプット形式でハンドリングする事で可能としています。
// 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
からの入力をバッファリングする機能を無効にしてしまいます。これにより、1文字ずつ入力が終了するような仕様にしてしまいます。
特殊な事をさせたいキー入力を読み取る
例えばEnter
キーとかdelete
キーとかそういう特殊な事をさせたいキーに対して、とても困りました。
数字や英語はとにかく全部もらったバイト列ごと突っ込んであとでstring
にでも変えてやればいいのですが、Enter
キーとかdelete
キーとかはそうにもいきません。さらにキー入力で入れるバイト列はASCIIコードなのでただ数字が入ってるだけでも無いのでハンドリングが難しいので、ここでgoの魔力を借ります。goはキー入力に対し、なぜかstring
に変える際にラッパーの経緯により[123]
のようなASCIIの後ろの2文字3文字の数字だけを表示させる謎仕様です。
これを利用して、受け取ったキー入力をハンドリングさせています。
➗0問題
dntkではこの数学界のタブーについてはnil
と表示させています。
普通こんなもの入力されたら、とっととpanicでもexitでもしておけばよいのですが、そう、dntk
はインタラクティブに計算していくので、例えば以下のような、
$ dntk
(dntk): dntk): 999 / 0.000067 = 14910447.7611940298
をやりたいときどうしても、
$ dntk
(dntk): dntk): 999 / 0
の瞬間ができてしまいます。これでpanicなんて起こしてたら小数点以下の割り算ができないポンコツ電卓になってしまいます、困りました😓
そこで、dntkはGNU bc
コマンドが数学的エラーを起こした時のみ標準エラー入力を出力する事に注目して、bcコマンドの標準出力と標準エラー出力をハンドリングし、標準エラー入力になにかしら出力されたときはnil
と表示するだけ、という対策をとりました。
終わりに
なかなかカッコいい仕上がりになったかなと思っていますが、バグなどまだあるかと思うので、何かあればissueをお待ちしております🐶