読者です 読者をやめる 読者になる 読者になる

たいちょーの雑記

ぼくが3日に一度くらい雑記をかくところ

AAの画像作ってくれるSlackBotつくった

Pythonむずい

勉強が足りない

AAは面白いが崩れる

AAって面白いですよね。

dic.nicovideo.jp

でも2chの専ブラとかでないと本来の見た目でなかったり、そもそも崩壊していたり・・・。

slackbot1
ブーム君のAA

Slackでもそれは例外でないです。
この間某Slackで「いよいよ明日は~」というコピペをもじった告知がありました。
slackbot2
↑こういうやつです。

画像の通り、返信には「今日と明日だよ・・・」と返すのがそれっぽいと思うんですが・・・(押しつけ)
これをそのままSlackに張り付けると
slackbot3
こんなふうにちょっとずれます。
これはこのままでも見れないこともないですけど、大きなAAになるとめちゃめちゃになります。
特にスマホとかだと確定でめちゃめちゃになります。
slackbot4
めちゃめちゃになったAAの例

AAは モナーフォント で見せるようになっている(?)ので普通のフォントでは崩れるのは当然なのですが・・・

IPAモナーフォント

崩れてしまうと何がなんだか分からずに白けてしまうので何とかしたい・・・
しかし全員にこのフォントにするように言うのはなんか違いますよね。

文字列がダメなら画像にしちゃう

画像にAAを張り付けてUpすれば全員が同じ崩れていないAAを見ることができます。
でも普通の人はそんな手間なことしませんし、そんなことしてたら会話の流れが過ぎ去っていきます。オタクの会話はスピードがはやいですから。

ここはSlackBot AAGenBot とでも名付けて、コイツにAAを画像化するのをやってもらいましょう。
slackbot5
AAGenBotのイメージ1

こうすればAAをコピペするだけでAAがずれることなくみんな見れます (実際はテキストが張り付けられるのでタイムラインがガーッと流れたりしますが・・・)

他にも特定の発言に対してそれっぽいAAを張り付ける機能も欲しいです。
slackbot6
AAGenBotのイメージ2

こうすればいちいちAAをコピペせずともBotが勝手にAAを貼ってくれます(邪魔だという意見もありそうです)

つくってワクワク

使った言語はPythonBashシェルスクリプトです。
SlackAPIを使ってメッセージの送受信と画像の生成をするのがPythonのお仕事で、それっぽいAAの画像を探したり、AA用のテキストファイルを用意したりするのをBashシェルスクリプトでやります。
Pythonはやったことなかったのでワクワクしてます

だいたいの流れはこうです
slackbot7

SlackAPIの利用にはpython-slackclientを使いました
github.com
Slackのサイトからテキトーにクリックしたら見つかったのでこれにしました(頭わるい感)
Pythonで画像を生成するにはPillowを使いました
https://pillow.readthedocs.io/en/4.1.x/pillow.readthedocs.io
なんかPILとか呼ばれてたやつ?っていうかそれのforkらしい?(詳しく調べてない)

slackbot8

つくります

できました

github.com
色々説明は面倒なのでこれをみてね

py/SlackMessageGetter.pyでメッセージを受信(送信もするけどね)して機能分け
py/aagen.pyでAAを生成 sh/search.shsh/aaImageSearcher.shで発言に合いそうな画像をさがします。二つに分けたのはsh/search.shの方をパイプに挟みたかったからです。 このsh/search.sh形態素解析とか結構謎深い検索方法をしているので興味があれば見てみてください。
sh/FileUploader.shcurl使って画像をアップロードします。なぜか前述のpython-slackclientではうpできませんでした・・・

またsh/FileUploader.shではある程度Botの画像うpを自重するようにしてます。

  1. 30秒以内に画像をうpしてたら自重
  2. 候補の画像を30分以内にうpしてたら自重

これで自重できてるのかは・・・わからん・・・な

インストールのしかた

必要なものをapt-getとかpip3します。python3がなければそれもいれてね

# mecab  ファイルサーチにつかっちゃったw
sudo apt-get install mecab
# もとPIL
sudo pip3 install Pillow
# Slackのライブラリ
sudo pip3 slackclient
  • メモ:普段あんまり使わなさそうなコマンドとか
  • jpegtopnm
  • pnmquant
  • ppmtosixel
  • これらは生成した画像をターミナルに表示するのに使っています。Sixelに対応しているターミナルでないとダメなのでいらない場合はsh/aaTextRedirector.shの25行目をコメントアウトしてOKです

普通にgit cloneします

git clone https://github.com/xztaityozx/AAGenBot
cd AAGenBot

AAGenBotはシェル変数からTokenやユーザ名、フォントへのパスを読み取るのでexportします

export SLACK_API_TOKEN="xxxx-xxxxxxxxxxxxxxxxxxxxxxxx" #APIトークン
export AAGENBOT_NAME="<@Uxxxxxxxxxx>" #Uから始まるBotのID?名前?みたいなやつ、<@ >で囲むこと
export AAGENBOT_MASTER_NAME="Uyyyyyyyyyy" #Uから始まる自分の名前?ID?。Botを管理する人の名前?ID?
export monafontPath="/home/user/.fonts/ipagp-mona.ttf" #AA作るときに使うフォント 絶対パスで書かないと見つからない?

~/.bashrcとかに書いておくと楽ちんです

シェルスクリプトに実行権限がなかったらchmodしておきます。

chmod +x sh/*

これだけだよー(^o^)/

起動方法

AAGenBotディレクトリ以下で

sh/Starter.sh

とするだけだよー(^o^)/

つかいかた

使いたいチャンネルにAAGenBotを参加させます。

生成

AAGenBotに対して生成してほしいAAの名前とAA本体を発言します
フォーマットは

@aagenbot [AAの名前]
[AA本体]

とします
slackbot9

こんなふうに結果がうpされます

ちなみにAAの名前はそのままファイル名に使われるので*/が入ると癇癪というかエラーを起こします。

呼び出し

AAGenBotに対して呼び出したい画像の名前をcallキーワードの後に付けて言うとうpしてくれます
slackbot10
こんな感じですね。
名前が間違ってるとうんともすんとも言いません(不親切)

発言に反応

これはAAGenBotに特に何か言うということはないです。
ユーザー同士の会話の中を勝手に読み取って適度にAAを張り付けます。
slackbot11
こういった感じですね

slackbot14
こんなふうに複雑な文章でもそれっぽいのを見つけてきます。
これはなんかアルゴリズムを使ったとかじゃないです。シェル芸を使ってます。(sh/search.shを見ればわかると思います。)
前述のとおり 自重システム がが備わっているので会話の流れが汚染されることは無いと思います()

終了

AAGenBotのMASTER(Botを管理するやつ)限定でSlackからAAGenBotを終了できます。
デバッグ用だったんですけど残しました。
slackbot12
こうです。煽ってるわけではないです

まとめ

Pythonはじめてでしたけどなかなか楽しかったです。またなんか作りたいですね・・・
最初は全部Pythonでやるつもりだったんですが
シェルスクリプトのが楽やわ!w
ってなったところはシェルスクリプトにしました。その結果Pythonシェルスクリプトが交互する意味の解らない構造になりましたがまあいいでしょう

まだ某Slack内で使ってないのでどれほど実用に堪えるのかわからないのですが、書く記事もちょうどなかったので書きました。
まだ生成失敗したときとか呼び出し失敗とか、エラーをユーザー達に伝えるような部分できてないのでその辺をもうちょっとだけメンテしてから某Slack内で公開したいです。どきどきする・・・

ちなみにこのAAGenBot副作用があります。
slackbot13
ALLFilesのところがめちゃめちゃになる

それでは良い AAライフ を!

参考

既知のバグ

  • 画像をUpしたあともう一度sh/FileUploader.shが呼ばれる。1行目からエラーが出て終わる
  • 下の方がはみ出る。謎

果物も食えないこんな世の中じゃ

ポイズン

最近はコタツを付けると熱いが消すと寒いので、雨が降ってほしいと願うばかりである。花粉も落ちるし。

更新をサボってたわけじゃないですよ。記事を推敲しまくってたんです(嘘)

果物を食うとだいたいのどが痒くなる

これわかる人いますか。他にもモヤシや火の通ってない豆腐などでも喉がかゆ~くなるやつです。
(僕の場合ですが)具体的に言うと のどちんこ のすぐ手前のあたり 軟口蓋 周辺がモーレツに痒くなります。(いまモーレツ宇宙海賊が予測変換に出てきて懐かしくなりました)

モヤシの場合は少しややこしくて、太めのモヤシ(緑豆や大豆モヤシ)はダメですが細めのモヤシ(黒豆モヤシ)はOKなんです。
他には高野豆腐もダメですね。
生の果物だけかと思ったら実は結構いろはすももでも来てるんですよね・・・
irohasu

だいたい花粉症のせい

僕は白樺の花粉症持ちなんですが、どうやらコイツがダメっぽいですね。
www.newsdigest.de

さらにブタクサもOUTなのでポピュラーな果物はほぼ死滅して、食えるのはブドウ、ミカンぐらいです。(バナナはなんかOK)
火を通せばだいたいのモノが食えるようになりますが・・・

わざわざ火なんか通してられるか!

という話です

かといって

かまわん!食ってやる!!

と食ってしまえば当然痒くなるので泣く泣く我慢しています。

しかし果物がダメなのはわかりましたが、大豆系がダメなのがよくわからなかったのでggりました
allabout.co.jp

どうやらここによると僕は クラス2の食物アレルギニスト のようですね  そしてその2ページ目

花粉症との関係についてですが、シラカンバ、ハンノキなどのカバノキ科の植物の花粉には、PR-10というアレルゲンタンパクが含まれています。このPR-10 と似たアレルゲンが大豆にも含まれているため(大豆の アレルゲンタンパクは「Glym4」)に、カバノキ科の花粉症患者の中には、豆乳等を飲んだ時にアレルギーを発症するケースが見られるのです。

Ah…
なるほど納得です(納豆は大丈夫)

モモが食べたい

僕の場合は 食べられへん!!! というほどではないので食べることはできますが
無理に食うことで 、これが悪化してしまうと今度はまだ発症していない食べ物にも反応してしまうかもしれない ので控えています。ジャガイモなんかが発症したらえらいことですからね

花粉症がある人は一度ggってこういうのがないか確認してみた方がいいかと思います。

!!????!??????! !!?????!??!!?? !!?????!?!?!!! !!????!????!!! !!?????!!!!!?? !!?????!?!?!!! !!????!????!!! !!?????!!!!!?? !!?????!?!!??! !!????!???!?!!

!!????!!?!!!?? !!????!?!??!?? !!????!?!!!??! !!????!!!?!!?! !!????!?!??!?? !!????!!??!??! !!?????!??!!?? !!?????!!!!?!! !!?????!?!?!!! !!?????!???!??

!???!?!????!!??? !??!!!?!???!?!! !!?????!!?!??? !!?????!??!?!! !???!?!?!?!?!!?! !!?????!!!!!!? !!?????!?!!?!! !!?????!?!!!!! !!?????!???!??

!!?????!?!??!! !!?????!!?!!!? !!??!?!!????!!! !!!!?!?!!!????? !!?????!??!!?? !???!?!?!?!?!!?! !!????!??????! !!?????!!??!!? !!?????!???!?? !!????!???!?!! !!?????!!?!??? !!?????!???!?? !!?????!???!!? !!?????!?!??!! !!?????!!?!??? !!?????!!?!!!!

!!?????!?!??!! !!?????!!?!!!? !!??!?!!????!!! !!!!?!?!!!????? !!?????!??!!?? !???!?!?!?!?!!?! !!????!??????! !!?????!!??!!? !!?????!???!?? !!????!???!?!! !!?????!!?!??? !!?????!???!?? !!?????!???!!? !!?????!?!??!! !!?????!!?!??? !!?????!!?!!!! !!???????????! !?!?!?????!!?!! !!?????!!?!!!! !!?????!!?!??? !!?????!!???!! !!?????!!??!!? !!????!?????!? !!??!!?!????!!! !!?????!!?!?!? !!????!??!??!! !!?????!!????? !!?????!!?!!?! !!!!!!!!???!!!!!

!!????!?????!? !!?????!!????! !!????!???!!?! !!????!??!??!! !!???????????! !!?????!???!?? !!?????!!!!!!? !!?????!?!??!! !!????!???!!?? !!????!??!??!? !!??!!?!!!!!??? !!?????!???!?? !!?????!!??!!? !!?????!???!?? !!????!???!?!! !?!????!!?!?!?! !!????!?????!? !!??!!?!????!!! !??!!!?!?!!!?!? !!?????!!?!?!? !!????!??!??!! !!?????!!????? !!?????!?!???! !!?????!!?!??! !!?????!!?!!?!

!!?????!!!?!!? !!?????!!???!! !!?????!!????! !!????!?????!! !!?????!?!???! !??!!!!?!?!?!?! !!!?!?!?!!?!?!? !!!???!?!??!!!? !!?????!?!!??? !!?????!!????? !!????!???!??? !!?????!!???!! !!?????!!??!!? !!????!!??!!?! !!????!?!!!!!! !!?????!!??!!! !!?????!?!!??! !!?????!??!!?? !!?????!!?!!?!

!!?????!?!??!! !!????!??!??!! !!?????!!?!?!? !!?????!?!??!! !!?????!!?!??? !!????!??!??!? !!?????!?!??!! !!?????!?!??!! !!?????!!?!?!! !!??!!?!!!!!??? !!?????!??!!!! !!?????!!?!!!? !!?????!!?!!!! !!?????!!?!?!? !!????!??!??!! !!?????!!????? !!?????!?!???! !!?????!!?!??! !!???????????! !!?????!?!??!! !!?????!!?!!!? !???!?!????!!??? !??!!!?!???!?!! !!????!??!??!? !???!?!?!?!?!!?! !!????!??????? !!?????!!?!?!! !!?????!!?!!!! !??!!!?!!!??!?! !??!!!?????!?!! !!?????!!?!!!? !!????!?!!?!!! !!????!?!??!!! !!????!!!?!?!! !?????!?!?!!!??? !!?????!??!!?? !!??!!!????!??! !?!??!?!?!!!??! !!?????!!????? !!????!???!???

!!??!?! !!???!! !!?!??? !!?!!!! !????? !???!? !!????!?????!? !!?????!?!!??? !!????!???!!?? !!?????!!??!?? !???!? !!!!!?? !!!??!! !!??!?! !!??!?? !????? !??!!! !!!??!! !?!!!! !!???! !?!!!! !????! !?!!!! !!??!!! !!!?!! !!!??!! !?!!!! !!???? !?!!!! !!!!!! !?!!!! !!??!!! !!!?!! !!!??!! !?!!!! !????? !?!!!! !?!!!?? !!?!!!? !?!!!! !!??!!! !??!!! !!!!!?? !!????! !!!?!!! !!?!?!! !????? !??!!! !!!!?!! !!!???? !!!??!? !!?!??! !!?!!!? !!!?!?? !????? !???!? !!?!??! !!???!? !!????! !!!??!! !!??!?! !!!!?! !!??!? !!!?!! !???!? !??!?? !!???! !!!!!?! !??!!! !!!!!?? !!???!? !!???!! !!!!!?? !!????! !!!?!!! !!?!?!! !????? !??!!! !!!!!?? !!!???? !!!??!? !!?!??! !!?!!!? !!!?!?? !!??!!? !????? !???!? !??!?! !!???!! !???!? !?!!?? !??!?? !!???! !!!?!! !!??!?! !!?!??? !!???!! !!?!!!!

!!?????!????!? !!?????!!?!??? !!?????!!?!!!! !?????! !?!??!! !????!! !??!??! !??!??! !!?????!!?!?!! !?!!??!????!??! !!???!!!!?!!?!! !!?????!?!!??! !!????!???!?!! !!?????!!????? !!?????!?!???! !!?????!!??!!! !!?????!?!!??! !!?????!!?!!?! !!!!!!!!???????!

!??!!!!?!?!?!?! !!?????!!?!!!? !!?????!?!!!!! !!????!??????! !!?????!!?!!!? !???!?!????!!??? !??!!!?!???!?!! !!?????!!?!?!? !!?????!!?!!!? !!!!!!!!???!!!!!

!!????!?????!? !!?????!??!!!! !!?????!!??!!? !!?????!??!!?! !!?????!!?!!!! !!?????!!?!??? !!?????!??!!!! !!?????!!?!?!! !!?????!!?!?!? !!?????!???!?? !!?????!!??!!! !!?????!?!!??! !!????!???!??? !!????!!!!!?!! !!????!!!!!?!! !!????!!!!!?!! !!!!!!!!???????! !!?????!?!?!!! !!?????!???!?? !!?????!!??!!? !???!?!????????? !!?????!???!!? !!?????!!?!?!? !!????!???!??! !!?????!!!???! !!?????!!???!! !!?????!!?!??? !???!??!!???!?!! !!?????!!??!!! !?!!!!!???!?!?! !!?????!???!?? !!?????!!??!!? !!?????!!!!?!! !!?????!?!?!!! !!?????!??!?!! !!?????!!???!! !!?????!?!!!!! !!?????!!????? !!?????!?!???! !!?????!!??!!! !!?????!?!!??! !!?????!!?!!?! !!!!!!!!???????!

!!?????!?!!!?! !!????!???!!?! !!?????!?!!!?! !!????!???!!?! !??????!!!!?!?!? !?!??!??????!!? !!?????!!??!!! !!????!?????!? !??!!!!?!?!?!?! !!?????!??!?!! !!?????!???!?? !!?????!!??!!? !!????!???!?!! !!?????!??!?!! !!????!???!!!! !!?????!??!?!! !!????!???!??! !!?????!!?!?!? !!?????!??!!!! !!?????!!?!?!? !!?????!!???!! !!?????!!??!!? !!?????!??!!?! !!?????!?!!!!! !!?????!!?!!!? !!?????!!??!!! !!?????!?!??!! !!?????!!?!!!? !???!!!!!?!!!?!? !!?????!!??!!! !!!!!?!?!????!? !!????!???!!!! !!????!???!?!? !!?????!!!!!!? !!?????!?!!??!

!!?????!?!!!?! !!????!???!!?? !!?????!!??!!! !!?????!!?!!!! !?????!??!!?!!!! !!?????!???!?? !??!!!?!???!!?? !??!??????!!??!? !!??!?!?!!!???? !!????!!!?!??! !!????!?!??!?? !!????!!?!?!?! !!????!??!??!? !!!!!!!!???????!

!?!??!!!!????!? !?????????????!!

!?????! !?!??!! !????!! !??!??! !??!??! !!????!?!!??!! !!????!!!!!!?? !!????!!??!??! !?!!??!????!??! !!???!!!!?!!?!! !!?!?!??!?!!!!!

たのしい拡張メソッド

肘の痛みが取れない

たぶんしぬ

VSがないといきていけない

VisualStudio2017がリリースされましたね。 www.visualstudio.com
自分はUnityとかC#でなんかするときはエディターにVSCommunityを使ってますので新バージョンのリリースはわくわくもんです。

ぼくはインテリセンスにどっぷり浸かってしまっているので他のではできないですね・・・おそるべし

インストーラーの段階でUnityのツールが同時にインスコできたりするので楽になりましたね。

何よりUIがかっこいいのよね

拡張メソッドたのしい

前回の記事でちょいと書いた拡張メソッドなんですけど、あれからそれにはまってしまいました。
拡張メソッドにハマるってなんだよ って思うと思うんですけど、拡張メソッドばかり書いて肝心の作品ができてないってことです。沼ですね。

最近ではメソッドチェーンとか三項演算子を見ていると

纏めて一つの拡張メソッドにしたい

というわけのわからない衝動がわくようになりました。
例えば

str = str.Length < limit ? str : str.Substring(0,limit);

こういうのがあれば

public static string Truncate(this string s,int boader) {
    if (s.Length < boader) return s;
    return s.Substring(0, boader);
}

こうして

str = str.Truncate(limit);

こうしたくなるんです!可読性もいいし精神的に良いでしょ?変な変数も増えないし?補完も変数名かいて.打てばインテリセンスがしてくれるし

だからなんか作るのが止まらないです。おかげできれいにはなったんじゃないかな・・・・・・
最近は文字列処理系が多くて前回の記事にあったDocking()は派生が結構できちゃってたのしくなっちゃう

public static string RangeDocking<T>(this IEnumerable<T> list, int start, int range, Func<T, string> filter = null) {
    string str = "";
    T[] array = list.ToArray();
    for (int i = start; i < range; i++) {
        str += (filter != null ? filter(array[i]) : array[i] + "");
    }
    return str;
}

コレクションのここからここまで連結したい!なんていうのは日常茶飯事だし、デリゲート渡せばちょいとした加工もしながら連結できるし気に入ってる一つです。
ほかにも個人的に気に入ってるのがこちら

[Conditional("UNITY_EDITOR")]
public static void UnityDebugLog(this object s) {
    UnityEngine.Debug.Log("UnityDebugLog value = " + s);
}

てきとうに値が見たいだけなのにDebug.Log()打つのが面倒だったので拡張しちゃったほしみ
単純だけどすごくべんり・・・とけちゃう

文字列の後ろから何文字か削除とかいうメソッドも(たぶん)ないし、いちいちSubstringするのたるいし

public static string RemoveRangeFromLast(this string s,int range) {
    if (range <= 0) return s;
    return s.Substring(0, s.Length - range);
}

自分しか使わないので需要とかパフォーマンスも気にする必要ないし

/// <summary>
/// T1tT2のIEnumerableからUniRx.Tupleのリストを生成して返す罪深いメソッド
/// </summary>
public static List<Tuple<T1, T2>> MakeTupleList<T1, T2>(this IEnumerable<T1> list, IEnumerable<T2> combination) {
    List<Tuple<T1, T2>> rt = new List<Tuple<T1, T2>>();
    var listRange = list.Count() > combination.Count() ? combination.Count() : list.Count();
    var array_list = list.ToArray();
    var array_combi = combination.ToArray();
    for (int i = 0; i < listRange; i++) {
        rt.Add(new Tuple<T1, T2>(array_list[i], array_combi[i]));
    }
    return rt;
}

インデックスが範囲外になっちゃうの気にするのが楽になるし

/// <summary>
/// 定義域を超えないようにしてくれる心優しいメソッド
/// </summary>
public static int AutoDomain(this int value, int _min, int _max) {
    return value < _min ? _min : value > _max ? _max : value;
}

三項演算子りすぎてわけわからんくなった

さて作業にもどりますかね

UnityのTextでシンタックスハイライトしたい

半袖半ズボンにはまだ早い

さむい

今回はちょっと雑記

シンタックスハイライトしたい

シンタックスハイライト(英: Syntax highlighting)とは、テキストエディタの機能であり、テキスト中の一部分をその分類ごとに異なる色やフォントで表示するものである。シンタックスカラーリング(英: Syntax coloring)とも。 Wikipedia -シンタックスハイライト

したい

まずは手法をググってみたんですが、パーサかいたりするのとかツール使うのとかあるみたいですね

でも大体はテキストマッチングみたいですね・・・

普通にテキストマッチング

リッチテキスト形式を使って色付けするのでテキストマッチングはむしろ良い手泣きがします。

早速書くんですがその前に練習がてら3つほど拡張メソッド書きました。

public static string[] Split(this string s,char[] separator,bool containSeparator) {
    List<string> rt = new List<string>();
    rt.Add("");
    int index = 0;
    for(int i = 0; i < s.Length; i++) {
        var item = s[i];
        int _sepaIndex = Array.IndexOf(separator, item);
        if (_sepaIndex != -1 && containSeparator) {
            rt.Add(separator[_sepaIndex]+"");
            rt.Add("");
            index += 2;
        } else {
            rt[index] += s[i];
        }
    }
    return rt.ToArray();
}
public static string Docking(this string[] s) {
    var length = s.Length;
    string str = "";
    for (int i = 0; i < length; i++) {
        str += s[i];
    }
    return str;
}
public static string ToRichTextColor(this string s, Color col) 
    => "<color=#" + ColorUtility.ToHtmlStringRGB(col) + ">" + s + "</color>";

Splitオーバーロードですね。既にあるSplitだとセパレーターに指定した文字列を失ってしまうので作りました。
セパレーターが1種類なら連結するときに指定すればいい話なんですが、今回は複数個あったので作りました。
Dockingstring.Joinに似たやつってかそのものですがJoinを使わなかったのはメソッドチェーンに組み込めな・・・いとおもうから(あいまい)
ToRichTextColorはそのままリッチテキスト形式に書き換えるだけですね

Dockingの方は使う予定がないけどもう一つ書きました

public static string Docking<T>(this IEnumerable<T> list,Func<T,string> toStringer=null) {
    string rt = "";
    foreach (var item in list) {
        if (toStringer != null) {
            rt += toStringer(item);
        } else {
            rt += item.ToString();
        }
    }
    return rt;
}

list.ForEach(a => str += a);とかするのちょっとカッコ悪く感じてたのでいいでしょう。うん まぁJoinでいいんですが・・・

それらを組み合わせてシンタックスハイライトするのがこちら

//テキスト更新
TX_OneLine.text = user_inputString
                    .Split(separator, true)
                    .Select(splited =>
                    {
                        if (separator.Any(___=>___.ToString()==splited)) {
                            return splited.ToRichTextColor(color_separator);
                        } else if (syntaxArray.Any(___ => splited == ___)) {
                            return splited.ToRichTextColor(color_syntax);
                        }
                        return splited;
                    }).ToArray().Docking();

シンタックスしたいのは大した量でないのでこれでいいでしょう

それでは良いシンタックスハイライトを!

UnityでInputFieldを使わずに文字列を受け取る

掃除するときは気を付けろ

奴は大事なものを捨てていっちまったんだ

InputFieldでいいんじゃないのか?

最近せっせこ作っている作品では、キーボードからの文字列入力と同じ場所に出力が必要だったのでUnityがuGUIで提供しているInputFieldを使おうと思いました。
docs.unity3d.com

ふつーに文字列を受け取るところがあって、背景やフォントも変えられるふつーの入力フォームです。イベントの通知もできます。
Textを子に持っていてInputField.textに書き込んでやれば出力もできると踏んでました。
NoIF1

しかし問題点が一つありました。Enterを押すともう一度Enterを押さないと入力待ち状態にならない点です。
ぼくが欲しかったのは コマンドラインのような入出力環境 だったのです。
NoIF2
こういうの

Enterを押したらC#に入力文字列を送信して、また次の入力待ちしてほしいのです。
InputFieldのリファレンスを読んでみましたが、それっぽいプロパティは見つかりませんでした。
唯一Interactableというプロパティがそれっぽかったのですが、どちらかというとenabledに近い動作だったので期待した動作を実現できませんでした。

つくった

こういう痒いところに手が届かないときはワクワクして自前で用意していきます。
使うのは同じuGUIのTextです。
docs.unity3d.com
NoIF3
見づらいな

入力を受け取るソースはこちら

this.UpdateAsObservable().Subscribe(_ =>
{
    foreach (char item in Input.inputString) {
        if (item == '\b' && user_inputString.Length > 0) {
            user_inputString = user_inputString.Substring(0, user_inputString.Length - 1);
        } else if (item == '\r' || item == '\n') {
            user_inputString = "";
        } else {
            user_inputString += item;
        }
    }
    updateCommandLine();
});

Updateの代わりにUniRxを使っています。理由は特にないですけど
InputクラスのinputStringプロパティからそのフレーム中に入力された文字列を受け取って1字ずつ解読していきます。
もし\b(backspace)だったらSubstringで一字消し、\n(Enter)であれば文字列をクリアします。それ以外なら入力文字列として追加していきます。

こんなんでうまくいくんかいって思いながら書いてたんですが
NoIF4

ありえんぐらいうまくいった

ひとつ問題点があるとすれば日本語はEnterで確定するまで表示されないところですね。まあ使う予定がないのでいいですが

いつもこれくらいトントン進めばいいんですけどね。

Unityと文字コードでレッツダンスパーティ!

続・僕らの文字コード

なんかVSCodeでMarkdown書くの使いやすくなってますね

文字コードという無限の闇

前回に続いて文字列に関する話題です
xztaityozx.hatenablog.com

bashからの出力を得てUnityに表示したいだけなんだけれども中々うまくいかなかったというお話だったわけですが
今回もなかなかうまくいかなかったお話です

Process process_bash = new Process();
ProcessStartInfo psi_bash = new ProcessStartInfo();
//設定
psi_bash.FileName = @"C:\cygwin64\bin\bash.exe";

psi_bash.RedirectStandardInput = true;
psi_bash.RedirectStandardOutput = true;
psi_bash.RedirectStandardError = true;

psi_bash.CreateNoWindow = false;
psi_bash.UseShellExecute = false;
psi_bash.Arguments = "--login";

//プロセス起動
process_bash = Process.Start(psi_bash);
process_bash.BeginOutputReadLine();
process_bash.BeginErrorReadLine();
process_bash.OutputDataReceived += onBashOutput;
process_bash.ErrorDataReceived += onBashError;

コード見てもらったらわかる通りOutputやErrorが出るたびハンドルを介して出力が得られる非常に簡単な構造
間違えそうなところがない

しかし!
MojiCode1
ご覧の通り文字がばけとります

www.bandai.co.jp

cygwinbashから受け取ったデータをUnityのTextに表示しているだけなんですが何がダメなのかな?
ともかくこのままじゃまともに使えないですね

文字コードをさがせ

文字化けについてggっていたところどうやらUTF-8でも BOM付き ってのと BOM無し ってのがあるみたいで?
site.oukasei.com

詳しいことはおいといて、UnityがBOMないとアカンのかどうかを調べてみると
MojiCode4

どうやら ソースについてはBOM付き である必要があるみたい
UnityのTextがどうかはわからないけどBOM付きUTF-8だろ~な~と思いました。

bashの方の出力がBOM付なのかどうかが問題ですね。
先ずは$LANGechoします

% echo $LANG
ja_JP.UTF-8

次に適当にコマンドをリダイレクトしてBOMついてるか見ます

% date > date_res.txt
% file date_res.txt
date_res.txt: UTF-8 Unicode text

BOM付きの場合は

date_res.txt: UTF-8 Unicode (with BOM) text

と出力されるのでどうやらBOM無しUTF-8のようです

このままではC#に渡したところで正常に出力されまへん
MojiCode5

さしあたってbashの出力にBOMをくっつければ解決するわけですから適当にBOMつけてみました。
MojiCode6

Encoding enc_src = new UTF8Encoding();
Encoding enc_dst = Encoding.UTF8;
byte[] noBOMbytes = enc_src.GetBytes(data);
byte[] BOM = enc_dst.GetPreamble();
byte[] appendedBOMBytes = BOM.Concat(noBOMbytes).ToArray();
string encodedStr = enc_dst.GetString(appendedBOMBytes);

そーれ

・・・
MojiCode1
画像は使いまわしです

あかんやんけ!!!!!!!!!

まぁ想定の範囲内でしたが(嘘)

実はShift_JISに浮気していた

そうこうしているうちにTwitterで心強いリプが

  

ええ!?Shift_JIS!?
んえ!?

いままでUnityのTextがUTF-8であると踏んでいましたがなんかShift_JISのようです・・・?
MojiCode7

こうなったらEncoding.Convertを使ってShift_JISにしてやります。

byte[] convBytes=Encoding.Convert(Encoding.UTF8,Encoding.GetEncoding("Shift_JIS"),data);
string convStr=Encoding.GetEncoding("Shift_JIS").GetString(convBytes);

これで円満解決。ね。

・・・
MojiCode1
画像は使いまわしです

ちょw

stdoutはすごぉい美少女

色々ggったりsrcとdst入れ替えたりProcessStandardOutputEncodingとかもいじってみたんですけどどうにもこうにもダメでした
なんかな~と思ってハンドラをやめてProcessStandardOutputから直接読み取ってみると文字化けしませんでした。

MojiCode8
たぶんstdoutがすごい美少女だから素直に言うこと聞くけど、ハンドラの方はさえない男なのでこういう塩対応なんでしょう

でも便利なハンドラの方使いたいです。さえないってところで親近感もわきますし

base64って

このままじゃ全く前に進まないので死んだ目をしながらggっていたんですがまたTwitterにて心強いリプが

base64ってなんやねん

Base64は、データを64種類の印字可能な英数字のみを用いて、それ以外の文字を扱うことの出来ない通信環境にてマルチバイト文字やバイナリデータを扱うためのエンコード方式である。MIMEによって規定されていて、7ビットのデータしか扱うことの出来ない電子メールにて広く利用されている。具体的には、A–Z, a–z, 0–9 までの62文字と、記号2つ (+, /)、さらにパディング(余った部分を詰める)のための記号として = が用いられる。この変換によって、データ量は4/3(約133%)になる[1]。また、MIMEの基準では76文字ごとに改行コードが入るため、この分の2バイトを計算に入れるとデータ量は約137%となる[2]。 Wikipedia -Base64

なるほど
どうやら変換用のbase64コマンドもCoreutilsに入っているようなのでだいたいの環境で使えそうです
適当にbase64に通して結果を見てみましょう

$ date | base64
MjAxN+W5tCAy5pyIIDE05pelIOeBq+abnOaXpSAxNTowMDoxOSBKU1QK

こいつを間に挟み込みましょう
bashのstdinに書き込むときに2>&1 | base64を付け加えておきます
MojiCode9
MojiCode10

デコードにはSystem.Convert.FromBase64String(String)メソッドをつかいます。

private void outputCommand(string o) {
    byte[] converted = System.Convert.FromBase64String(o);
    Encoding enc = new UTF8Encoding();
    string encoded = enc.GetString(converted);
    output = o + "\n" + encoded + "\n";
}
private void onBashOutput(object sender, DataReceivedEventArgs e) {
    outputCommand(e.Data);
}

それ~

MojiCode11
で、
でた~~~~~~~~~

いやー助かりました。

まだまだ終わらないぞ~~~~!

終わらないです。
この調子で行くかと思ったら違いました。不思議よね

まあ適当にコマンドの実験をしているとなんか変な結果が
MojiCode12

んw

このコマンドはないですという旨の出力を期待してたんですがなんかへんです
ちなみに本来なら

$ da 2>&1 | base64
YmFzaDogZGE6IOOCs+ODnuODs+ODieOBjOimi+OBpOOBi+OCiuOBvuOBm+OCkwo=

という文字列が得られるはずなんですが、gZvjgpMKしかとれてないしどことも部分一致しないんです・・・!

わーいwおもしろーいw

まぁとりあえず落ち着けよ
まずはstdoutとstderrを分けるんだ

da 2>&1 > /dev/null | base64 >&2;da 2> /dev/null | base64 >&1

そうそう・・・こうやって・・・
MojiCode12
画像は使いまわしです

まぁこういう問題じゃぁないことぐらいわかってるよHAHAHA

じゃあなんなのよ!

stderrの反乱

いままで構ってこなかったからかすっかりご機嫌ナナメのstderrがついに暴走しました
stderrはかわいい少年のイメージです
MojiCode15

まずはstdoutかstderrかもわからんのでTextのリッチテキスト形式を利用して赤くします

private void outputCommand(string o,bool isError=false) {
    byte[] converted = System.Convert.FromBase64String(o);
    Encoding enc = new UTF8Encoding();
    string encoded = enc.GetString(converted);
    if (isError) {
        encoded = "<color=red>" + encoded + "</color>";
    }
    output = o + "\n" + encoded + "\n";
}

private void onBashOutput(object sender, DataReceivedEventArgs e) {
    outputCommand(e.Data);
}

private void onBashError(object sender, DataReceivedEventArgs e) {
    outputCommand(e.Data,true);
}

うんうん

そしてとりあえずstderrをbase64に通さず素直に出力しましょうね

private void onBashError(object sender, DataReceivedEventArgs e) {
    UnityEngine.Debug.Log("Debug : "+e.Data);
    outputCommand(e.Data,true);
}

MojiCode13

まぁ化けるわな

ここでなんとなく同じ文字列をTextに出してみました。

private void onBashError(object sender, DataReceivedEventArgs e) {
    byte[] errorBytes = Encoding.GetEncoding("Shift_JIS").GetBytes(e.Data);
    string errorStr_base64 = System.Convert.ToBase64String(errorBytes);
    outputCommand(errorStr_base64,true);
}

こんなふうにC#エンコードデコードをどっちもやります
MojiCode14

!?

ええ!?Shift_JISでいいの!?
なんか一部化けてるけどほぼ正常に出てる!?なんだこれ! (ちなみにハンドラから直接e.Data渡したら全部化けました、もしかしてここか?)

え?ハンドラからでてくるstdoutは何かわからない文字コード(たぶんUTF-8)だったのにstderrの方はShift_JISなの?
なんだこれ?
MojiCode16

Unityの方のコンソールは化けたままなのでUTF-8・・・だとおもいます・・・
Textの方は文字コードを適当に合わせてくれるようです・・・?
stdoutの方が化けてしまったのはTextがUTF-8Shift_JISだと誤解してしまった、というかどこかで誤解させてしまったのか
もしかしたらもともとShift_JISだったのか
で、でもbashの$LANGはUTF-8だし・・・

ともあれ何とか日本語を表示できるようになったのでこの件はいいですがもう少しちゃんと調べる必要がありそうです。

まとめ

今回はかなり振り回されました。base64Shift_JISのヒントをもらったりしてなんとか漕ぎつけましたが・・・
無意識にこのクラスにクソコードを紛れ込ませてしまっているのかわかりませんがコメントをちゃんと残しつつ、整理したいと思います。

名前 文字コードだと思うもの
bash stdout UTF-8? Shift_JIS?
bash stderr Shift_JIS?
bash $LANG UTF-8
Unity Text たぶん合わせてくれる
Unity コンソール UTF-8だとおもう

そ、それでは良い文字コードライフを・・・