粗筋

Android上で、ファイル関連の雑務を片付けるスクリプト環境が欲しくなる。

具体的にはmicroSDを128GBに乗り換えた事で、以前ScanSnapでスキャンしたpdfファイルを幾つか突っ込める余裕が出てきた。 この中身は無圧縮のjpegが埋め込まれているだけなので、単純にjpegのヘッダをseekしてmemcpyするだけでjpegファイルは取り出せる。 で、これが50ページ単位くらいのpdfに分かれているのを、一つのフォルダに連番jpegとして突っ込んでzipで固めたりしたい。

こういう類のファイル関連の雑務を軽くこなすスクリプト環境をAndroid上にも欲しくなってきた。

数日前にそういう話も書いた。

blog: Android上の日常的な雑務をやるスクリプト環境が欲しい

という事で作ろう。
まず名前をつける。名前はFileScriptingとした。

コンセプト

キーボードはつなげる前提で考えている。 PowerShell ISVみたいな環境があれば良い。

イメージ的にはこういう感じで。

まず言語はとりあえずBeanShellというので行ってみようかな、と思っている。 luaでもいいかな、と思っていて、どっちにするかはまだ未定。

コマンドラインの所ではcdとかlsとかするし、パイプも作りたい。 ここはスクリプト言語とは別の簡易言語にするしか無いかな、と思っている。 cdするとグローバル変数のCWDとかが変更されて、上の窓のスクリプトと真ん中のコマンドラインの環境は共有されている。

イコールの左辺に変数をドル付きで書けて、これは上のスクリプトのドルなしグローバル変数として扱われる。 イコールの右辺を全て実行した最終的結果が左辺に入る。 ストリーミングも全部arrayに変換して突っ込む。

出来たらpowershellのwhenに相当する物とか作りたいが、まぁ無理にとは言わない。 適当にlsとかの結果を変数に突っ込んで、細かい事は上のスクリプトでやる。 ただlsはワイルドカードくらいは頑張りたい。catも出来たら文字列のリストにする位はやりたいなぁ。

スクリプトは今の所SQLiteに突っ込んで、ListViewから適当に選んで開く、でタブで複数開いて、このスクリプトを複数組み合わせて手動でevalしていく事で目的を達成していく。

もともとの目的からバイナリを扱う必要があるので、下のJavaをそのまま触れるのがいいかな、と思う。 権限的には今の所ネットワークはなしでいいかなぁ。 EXTERNAL_STORAGEのwritableとreadableくらいでなんとかならないかなぁ。

実装順序

  1. 単発コマンドでcdとlsくらいだけをサポートしたやる気無いコマンドラインで、上のスクリプトウィンドウと下のコンソール出力をとりあえず動かす
  2. コマンドラインをまともなパーサーコンビネータで書き直し、イコールをサポート
  3. 過去スクリプトのListViewと複数タブをサポート
  4. パイプをサポート
  5. スクリプトのexportをサポート

3くらいまで終われば使い物にはなる気がする。 1さえ終われば3まではイケる気がするので、使う所までは行くんじゃないか。

5まで終われば一般にも公開していいかなぁ。

最終的にはimportとかも必要だろうけれど、とりあえずバックアップさえ出来ればimportは必要になったらやればいいか、とは思っている。

ソースコード置き場

ということで作り始めた。 https://github.com/karino2/FileScripting

lsとかprintはオリジナルのBeanShellとは変えたい

BeanShellはコマンドが.bshファイルでいろいろ定義されてて、それが必要に応じて自動でロードされるという仕組みとなっているっぽい。 だからAndroidならassets下に置いてそれをロードするようにした方が良いのだろうな、 という気はするのだが、ローダー的な物を差し替えないと行けなくて、これが意外とめんどいので、最初は.bshファイルの中身をraw stringにコピペして起動時にevalしている。

で、dirは全部テキストをprintする感じのコマンドになっている。 だが、PowerShell民としてはこれはFileのIterableであって欲しい。 まぁ実用上はFileのArrayListでも良い。

で、そうすると出力の所も書き換える必要があって、formatterが要る、という話になると思う。 まずはハードコードでいいかなぁ、という気がするが。

そうすると、そもそもprintの実装もその辺いろいろやってしまっているが、 スクリプトレイヤでやるんじゃなくて、Kotlin側でやる方が筋が良いよな。 そうしよう。

ついでにAndroidだとdirじゃなくてlsだよな、という気もするので、コマンド名も変えよう。

パイプはIterable的なのにするかListにしてしまうか、ちょっと悩む。まぁパイプまで行ってから考えよう。

そう考えるとcatもIterable<String>だよな。 そういう感じにしておこう。

一行コマンドラインのインタープリタ文法

一行コマンドラインは、やはり実用的には独自の言語を作る必要がありそう。 カッコとか書くのかったるいし。やはりワンライナーに特化してcdとかlsするだけ言語は要るだろう。

なんか名前が欲しいなぁ。OneLineScript、略称olsとしよう。

空白区切りで引数は文字列

まず、引数はデフォルトでダブルクォートなしで文字列とみなされるべきだろう。 で、例えば以下は、

mv file1 file2

以下のBeanShellスクリプトとして実行されるべき。

mv("file1", "file2")

intとかも全部文字列になるのはダサいか。floatは使う予定は無いが、intくらいはサポートしよう。 で、16進とかは使わなくてよかろう。

だが、BeanShellは可変長引数をサポートしていない!まじか!イマドキ!

という事で、ols側でその辺は面倒みてやって、

mv(String, String)

が無かったら、

mv(Object[])

を呼ぶ事にしよう。Object配列は決め打ちで、Stringの可変長配列とかはサポートしない。 ちょっとかったるいが、BeanShell側でサポートしてないのをそこまで頑張って補う気も起こらないのでいいでしょう。

ワイルドカード

ワイルドカードの展開はolsのレベルではやらないで、文字列をそのまま渡す事にする。 ただし、展開してList<File>を返すユーティリティを提供しよう。 だからmvとかを複数ファイル対応にしたいなら、最初からmv(Object[])で実装して、 配列の最後の要素をdestinationとするようにコードを書く必要がある。

ワイルドカード以外の複数ファイルのmvとかはまぁ最初は要らないだろう。 この辺は必要になる都度やっていこう。

catやlsの出力はIterable

lsはIterable<File>を返す。ただ最終的にreplがprintする所でそれっぽいformatterで出力される。

catもIterable<String>を返すようにしよう。

変数と代入とIterable

さて、変数が欲しい。変数はドル記号で始めよう。ドル記号で始まっている変数は、その値に置き換えられて関数が呼び出される。

イコールで変数に代入出来るとする。

代入する時は、IterableをArrayListに変換して代入する事にしよう。 PowerShellもそう振る舞っていたので。

イコールの右辺はパイプまで含めて全てのコマンドを実行した結果を変数に入れる事にする。

で、ドル付き変数はBeanShellのグローバル変数にされる。 逆にBeanShell側からもグローバル変数として触れるようにしよう。

現状はfor文回したい時はBeanShell側で回すようにする。

とりあえず入出力回りだけをやって、他はBeanShell側で

あまりいろいろと頑張らずに、とりあえずlsとかcdとかしつつ変数に突っ込んで、 そこから先はBeanShell側でへこへこ書けばいいかなぁ、と思っている。 まずは動いて使える所までやってからいろいろ追加していきたい。

パイプも一応考えてはおく

パイプライニングしようと思えば、オプションの引数とパイプラインから流れてくるオブジェクト列の両方を対応する必要がある。 だから専用のインターフェースを作ってしまう方が手早いだろう。

で、このインターフェースは引数とパイプからの受け取りをちゃんと区別出来る感じにする必要がある。 パイプからの受け取りはIterable<Object>で良かろう。

とりあえずlsした結果を正規表現とかでフィルタリングしたいので、matchくらいは実装する必要があるかなぁ。

ただこの辺は実装が面倒な割に、別に変数に突っ込んでからBeanShellでfor文回してもそんなに変わらんので、やはり後回しが筋が良さそう。

近況

上記実装予定のうち、2に相当するあたりまで終わった。 cdとlsで移動して、lsの結果を変数に入れてスクリプトから触ったり出来る。 最初はvarargsサポートが無くてやる気を失いかけていたが、overloadは動いている事に気づいてlsは引数なし版も実装したら実用上は問題なくなってやる気が出た。

パーサーはbetter parseというのを使っているが、これがいい感じ。 たいしてドキュメントが無いが、こう動いて欲しい、という感じで書くとその通りに動くので、何も不満が無い。 Kotlinはライブラリもセンスが良くて触ってて楽しいね。

アプリの方はというと、少し触った感じ結構いいのが出来たな、と思っている。 poor man’s PowerShell ISVくらいにはなっている。

次はスクリプトを保存したり読み出したりする所を実装したら、 ドッグフード開始かな。SQLite周辺はAnkoというのを使ってみる予定。

近況2

AnkoはCursorをロードしてSimpleCursorAdapterに渡す、 という基本的なシナリオが実現出来ないと知って割と衝撃を受ける。 ただ、SQLiteDatabaseのextentionは使えるので、 それだけ使っている。

で、タブのタイトルがいつも「New」のままだが、 それ以外は一通りの構成要素は出来た。上記実装予定の3までが終わった、という事になる。
ちょっと仕上げやってアイコン書けばPlayに公開も出来るレベルだが、 公開よりも前に自分で使ってみようかなぁ、と今は思っている。

さて、いざスクリプトを書いてみると、バグってたらファイルが消滅してしまうので、 ひとまずtmpとかフォルダを掘って、その中にコピーして作業したい、 と思うが、これがまだ出来ない。 やはりワイルドカードに対応したmvやcpが要るな。

あとはついでにsortも欲しいかなぁ。

この辺は割と簡単なので、とりあえず使える所まではあと一歩、という所までは来た気がする。
当初考えていたjpeg埋め込みpdfをバラすコードを書いてみて、使い心地を確かめたい。

ここまで触っている印象だと、結構いい線いってる気がする。 もう作業系のスクリプトはPCじゃなくてもいいかなぁ。 ここ一年で、だいぶPCでの作業をAndroidタブに持っていくのには成功した気がする。

近況3

ワイルドカードを実装して、ls, cp, mv, rmあたりが実用的に動くようになった。

これでとりあえず複数に分かれた自炊pdfからjpeg抜き出してフォルダに吐く、は作れるようになったはず。

今の所はextSdCardの方は書き込み出来ない。これはどうするのがいいかなぁ。 DocumentTree使うようにコードを書いてもいいのだが、internalのストレージとコードが2つになるのもださいし、でもinternalもいちいち許可取る、というのも馬鹿らしい。
SAFはシェルと相性が悪いのだよな、そもそも。

という事で、当面はinternalのsdにコピーして作業する事にする。コピーは無駄だが、当面はそこが耐えられないほどの事はやらないだろうし。

結構出来てきたなぁ。 なかなか革新的なアプリになった。よしよし。さすが俺。