テキストを左右中央寄せするalignコマンドをGoで作った
テキストを左右中央寄せするalignコマンドをGoで作りました。 なぜ作ったのか、と何ができるのか、について記載します。
作ったもの:
なぜ作ったのか
シェル芸bot環境で位置揃えを簡単にできるようにしたかったからです。 シェル芸bot環境では基本的にTwitterの140文字が文字数の限界で (引用リツイートを使うことで140文字以上入力することも可能ですが) 文字数を少しでも削りたい、という思いで作りました。
位置揃えの例
例えば以下のテキストがあります。
12345 abc zzzzzzzzzzzzzzz
このテキストを右揃えするシェルは下記のようになります。
#!/bin/bash align_right() { local p="$1" local max_line_width=0 local lines=() while read -r line; do lines+=("$line") width=$(echo "$line" | wc -c) if [ "$max_line_width" -lt "$width" ]; then max_line_width=$width fi done for line in ${lines[@]}; do width=$(echo "$line" | wc -c) diff=$((max_line_width - width)) pad=$(seq $diff | xargs -I@ echo -n "$p") echo "$pad$line" done } cat << EOS | align_right " " 12345 abc zzzzzzzzzzzzzzz EOS
無事実装できました。 中央寄せも同じように実装できます。
ただ毎回こんなのを実装するのも面倒ですし、 これだど日本語が混在するテキストの場合にきちんと位置を揃えられません。 位置揃えに使える文字も半角文字に限定されます。
入力のテキストに全角文字が混在していてもいい感じに処理できて、
位置揃えに全角文字も指定できるようにしたのが
今回作成したalign
コマンドです。
alignコマンドの使い方
前述の例をalign
を使うように書きかえると以下のようになります。
cat << EOS | align right -p " " 12345 abc zzzzzzzzzzzzzzz
実行結果はこちら。
12345 abc zzzzzzzzzzzzzzz
サブコマンドにはleft, center, rightが指定できます。 全角文字が混在する場合の例は下記。
cat << EOS | align right -p " " あいうえお abc zzzzzzzzzzzzzzz EOS
実行結果はこちら。
あいうえお abc zzzzzzzzzzzzzzz
無事、きちんと位置を揃えられています。 ※はてなブログ上ではフォントの関係でずれて表示されてますが・・・。
実装
位置を揃えるロジックについては前述のbashのコードと 同じようなことをやっています。
- 一番長い文字幅を取得する
- 差分を文字で埋める
重要なのは「文字幅をどう取得するか」です。
alignではgo-runewidthという外部ライブラリを使用することで 文字列の見た目上の文字幅を取得しています。
go-runewidthでは「全角文字なら文字幅2」「半角文字なら文字幅1」という具合に 見た目上のテキストの幅を返してくれます。 これを利用し、位置を揃えるようにしています。
Goで実装した箇所を抜粋します。
func MaxStringWidth(lines []string) (max int) { for _, v := range lines { l := runewidth.StringWidth(v) if max < l { max = l } } return max } // AlignRight は文字列を右寄せする。 // 右寄せは見た目上の文字列の長さで揃える。 // length = -1のときは、引数文字列の最長の長さに合わせる。 // padは埋める文字列を指定する。埋める文字が見た目上でマルチバイトの場合は // たとえlengthが奇数でも+1して偶数になるように調整する。 func AlignRight(lines []string, length int, pad string) []string { if length == 0 || len(lines) < 1 { return lines } // 空白埋めする文字列がマルチバイト文字かどうか padWidtn := runewidth.StringWidth(pad) padIsMultiByteString := padWidtn == 2 // -1のときは文字列の長さをalignの長さにする // パッディングの長さと、処理対象の文字列のより長い方を揃える数値に指定 maxWidth := MaxStringWidth(lines) if length == -1 { length = maxWidth } else if length < maxWidth { length = maxWidth } // マルチバイト文字を使うときは長さを偶数に揃える if padIsMultiByteString && length%2 != 0 { length++ } ret := []string{} for _, line := range lines { l := runewidth.StringWidth(line) diff := length - l if diff%2 != 0 { line = " " + line diff-- } // Repeatするときにマルチバイト文字を使うときは2分の1にする if padIsMultiByteString { diff /= 2 } s := strings.Repeat(pad, diff) + line ret = append(ret, s) } return ret }
まとめ
自作のコマンドalign
についてとその実装について一部紹介しました。
この程度ならシェルスクリプトだけでも実現できるようにも思いましたが Goの勉強もしたかったのでGoで実装しました。
余談
こういう自作のコマンドをはてなブログに書くかScrapboxに書くかQiitaに書くか悩みます。 前まではQiitaに書いてたけれど、自作のコマンドの紹介とかははてなブログに 書いたほうが良いみたい。
まぁQiitaは一般的な技術的TIPS、事実を述べる場所で 自作のコマンドは「作った人個人」に紐づくと考えると ブログに書くのが適当なのも納得がいきました。
Scrapboxに書くことも検討したんですが、Scrapboxは意図的に 外部に広く知ってもらう機能を実装していないそうです。 それだとせっかく作ったツールを知ってもらいたくても知ってもらえないように 感じたので書かないことにしました。
物語におけるキャラ同士のやり取りをシーケンス図で整理する
はじめに
FF6みたいに複数のチームを切り替えながら操作するシーンであったり 世界崩壊後の仲間が散り散りになったシーンで、 キャラ同士のやり取りの整合性をキチンと取りながらストーリーを考えるにはどうすればいいだろう。
って考えた時に、シーケンス図を書くと割といい感じに整理できることに気づいた。 Twitterに数カ月前にちょろっとだけ載せたけれど、 せっかくだったのでキチンと整理して公開しておこうと思った次第。
目次
わかりにくいストーリーの例
あるところ日、青年田中は精霊祭の日に神のお告げを聞いた。
その日、田中は町をでた。100年に一度復活を遂げる魔王を倒すために。
田中は最初の町で魔王の手先を倒した。
田中は2つめの町で魔王の手先と対峙した、しかし敵は強かった。
その時、偶然居合わせた山田と鈴木が助けてくれて、協力して敵を倒した。
山田と鈴木の話を聞くに、彼らの故郷は魔王の手下に滅ぼされたそうだ。
なんやかんやあって、いい感じにストーリーが進んで海の神殿についた。
そこには封印された勇者が眠っていた。
田中はそのとき、なんかすごいパワーが覚醒して封印を解き放った。
おまけに敵も復活したけれど、いい感じに倒せた。
山田と鈴木は勇者が居眠りぶっこいていたせいで故郷を滅ぼされたことに激昂して問い詰めた。
勇者は魔王に単身挑んだものの返りうちにされたこととか諸々説明して無罪放免。
あとはなんかいい感じに魔王を倒してハッピーエンド。
適当に書いたからすごくわかりにくいけれど、わかりにくいことが伝わったなら十分。 ストーリーを思いついた順番に説明してもいい感じに伝わるわけがない。 そんな時こそシーケンス図。
たぶんわかりやすくまとまったストーリー図の例
こうしてみると、キャラが裏でどういうことしてたのかがよくわかる。 プレイヤーが操作可能になるまえから、物語は始まっていた。 これがシーケンス図のパワー。
シーケンス図ってなんぞや
Wikipediaを読めばわかる
もともとはシステム設計をする時のデータのやり取りや オブジェクトの振る舞いを定義するためのモデリング言語であって ゲームのシナリオを整理するためのものじゃない。
こんな複雑な図を書けというのか?
PlantUMLを使うとすごく簡単に書ける。
Open-source tool that uses simple textual descriptions to draw beautiful UML diagrams.
手軽にWebブラウザ上で試したいなら以下のサイトがすごく便利。
テキストファイルとして管理できるのでGitHubとの相性も良い。 ちなみに前述の図は以下のテキストから生成されている。
@startuml img/chara_seq.png title 仲間が全員合流するまでの行動 actor 田中 as tanaka actor 山田 as yamada actor 鈴木 as suzuki actor 勇者山勇者太郎 as hogeyama actor ラスボス as last_boss activate last_boss last_boss -> last_boss : 復活した last_boss -> last_boss : 世界侵略を開始 activate hogeyama hogeyama -> hogeyama : 100年の眠りから目覚める note right : ほげ山は過去の勇者とかそのへん activate yamada hogeyama -> last_boss : 一人で戦いに挑む yamada -> suzuki : 一緒に遠方の街に\n買い出しにでかける deactivate yamada activate suzuki last_boss -> yamada : 手下だけ向かわせて故郷を焼き討ちにかける suzuki -> yamada : 故郷に戻ってきた activate yamada yamada -> yamada : 壊滅した故郷を目の当たりにする yamada -> yamada : last_bossの手先を撃退する yamada -> yamada : 魔王討伐のたびにでる last_boss -> hogeyama : 返り討ちにして、\n海底神殿に封印する deactivate hogeyama suzuki -> yamada : 同行する deactivate suzuki === ゲーム開始 === activate tanaka tanaka -> tanaka : 年に一度の精霊祭に参加する hogeyama -> tanaka : 封印されながらも根性と神通力で語りかける tanaka -> tanaka : 始まりの街をでる tanaka -> tanaka : 最初のボスを倒す tanaka -> tanaka : 第二の街に到着する tanaka -> tanaka : 第二の街を襲っている魔物と対峙する yamada -> tanaka : 戦いに参加する deactivate yamada deactivate suzuki tanaka -> team : チーム結成 deactivate tanaka activate team team -> team : なんやかんやある team -> team : 海底神殿につく team -> hogeyama : 田中のなんかすごいフォースで\n封印を解く activate hogeyama team -> team : 一緒にボスも復活 hogeyama -> team : 共闘でボスを倒す hogeyama -> team : チーム加入 deactivate hogeyama team -> team : なんやかんや team -> team : ラスボス城に到達 team -> last_boss : 決戦に挑む team -> last_boss : 勝利 deactivate last_boss deactivate team @enduml
すごく簡単というわけではないけれど、 パワーポイントよりは簡単なはず。
以上
Scrapboxから戻ってきました
はじめに
お久しぶりです。次郎です。 はてなブログに復帰したという記事です。
GitHubPagesに移行したあと、また移行してScrapboxを使っていたのですが、結局戻ってきました。 戻ってきた理由と、これからについて書きます。
戻ってきた理由
主に集客的な側面からです。
ScrapboxはWikiであってブログではなく、 集客の側面については意図的に排除しているとの記事を見かけまして戻ってきました。
別に集客には特にこだわりもなかったですし、今も余り興味ないのですが 絵の配布、ついては別だったので、それのためだけに戻ってきました。
使われるための絵
今まで、たまに絵を書いて、気に入ったものがあったら公開して配布できる形にしてきました。
Scrapboxでも同様のことを行っていたので すが、Scrapboxよりははてなブログのほうがより効果的に人の目につくのでは、と考え ました。
もともと僕は自分がゲームを作る時に自分が使うように絵を書いて、 ゲームが完成するまで潜ませとくのがもったいなくて公開するようになったのですが 最近はゲームを作る熱が冷めて、何かの実装か絵だけを書くような感じになりました。
書いた絵をより使ってもらえるように、よりひと目につきやすいとこに置いとこう、という感じです。
まぁ、Wikiサービスをブログとかの代わりに使っていたのが、 本来の用途とアンマッチしていた感じ...でしょうか。
とはいえ、Scrapboxを使っているから人が集まらない、ということは絶対になくて 実際Scrapboxのサービス製作者のプロジェクトは色んな人に閲覧されています。 単純に、僕の発信力、影響力が弱い、というだけです。
戻ってくる == Scrapboxを使わない というわけではない
Scrapboxは引き続き使います。 が、メインで告知する環境にはしないというだけです。
依然として思考するためのサービスとしてScrapboxは非常に素晴らしいので 引き続きScrapboxに技術的なメモは残していきます。
これから
じゃあこっちでは何を書くのか、というとイラストの配布やツール配布とかだけにしようと思ってます。
このブログも、以前は技術の記事とイラストの記事が混在していて それぞれカテゴリが全然違うものだったので、このブログの立ち位置がわからなく感じてました。
Scrapboxに移行してから技術用とイラスト用でプロジェクトを分けていて、 イラスト配布用のプロジェクトの機能だけをこのブログが引き継ぐ形になります。 よって、ゲームとイラストに関連した情報のみになる予定です。
引き続き技術系の情報はScrapboxに書きます。あちらのほうが居心地が良いので。
しっかりとした情報として公開したいものは、多分Qiitaに書くのかなぁと思います。 Qiitaだと情報が間違ってる場合に指摘してもらえるので。
GitHubPagesにブログ移転します。
GitHubPagesにブログを移転します。 移転先は下記。
配布イラスト一覧は下記 https://jiro4989.github.io/dist-illust/
はてなブログはエンジニアの利用率が結構高い印象です。 一番のメリットはMarkdown記法に対応していることだと思ってます。 僕もMarkdownが使えるから、ということでFC2から移ってきました。
他にもメール送信で記事の投稿ができるので スクリプトと連携して自動化したり Vimで記事を書いたりできる。 SEO対策もしっかりされてますし、 はてなブックマーク数やスター数が表示されるのも結構便利だったんですが 投稿後に記事を修正するための編集ページが重たかったり もろもろ不満もありました。
github.ioのドメインと柔軟性の高さ、 ブログ記事の更新履歴を残せる、 管理がやりやすい、 エディタが選べる、コマンドラインと相性が良い、自動化が簡単など いろんなメリットから移転を決意しました。
移行こちらのブログはおそらく更新しません。 イラストの配布についても新しいブログの方に告知していきます。
以上です。
自分が今まで書いたイラストの書いた時期と枚数を集計する
概要
集計の勉強がてらにbashとawkで自分が今まで書いたイラストの枚数と傾向を集計して分析します。
目的
自分の絵師としてのモチベーションの傾向を分析し、未来の活動指針を決定するため。 というのは大嘘でbashとawkで集計の練習がしたかったからです。
成果物は月単位、年単位で集計したグラフです。 本目的の達成は、上記のグラフを作成し可視化することをもって完了とします。
コード
成果物
csv形式で出力してLibreOfficeでグラフ化しました。
(コードの方ではcsv形式で出力するようにはしていないので、
スクリプト呼び出し時に| tr '[:blank:]' ','
しました。
年単位での集計結果
月単位での集計結果
考察
2016年のころ、僕はまだ大学4年生でした。 大学4年は卒論の提出と就活が終わってしまえばあとはひたすら暇になるので その時間をイラストとプログラミングに最大に使っていたことがよく統計に現れているように思います。
2016年1月〜3月は冬休みでしたし、卒業研究も就活もまだ本腰でなかった時期なので その時間はひたすら絵を書いていたみたいですね。
しかし僕って2011年から絵を書いていたんですね...。 意外と結構期間が長いものです。
最近はめっきり絵を書く熱は冷めてしまって、プログラミングの熱ばかりあがっていますが、 それでもこれからも、年に1,2枚くらいは何かしら書くと思います。
Clojureでアクセスログの処理時間を集計
概要
最近Bashで集計をすることが多いです。集計作業楽しいです。 と同時に、Clojureという言語にも興味ができたので、せっかくなので 簡単な集計作業を両方の言語でやってみたというだけの話です。
目的
access_log というログファイルが存在します。 ログのデータはすべて行単位ですが、共通することは行の一番最後の数値は アクセス時の処理時間です。
このアクセスログの時間を集計して、合計秒と行数を出力したいです。
作成したコード
まとめ
集計作業のコード量の短さや読みやすさ、手軽さはやっぱり BashとAwkのほうが断然上だなぁ...と。 Awkとかはもろに集計のための言語なんでかなり便利。 Clojureに集計やらせるもんじゃないかな...。 でも大量の便利関数群をまだまだ把握できていないので、 それらを駆使したらもっと短くシンプルになるのかも?
個人的にはBashとClojureってなんか似ているような気がします。 BashのパイプとClojureの関数型言語の性質が似てて、 コードを書いていてパズルを解いているような感じがしてコーディングがすごく楽しい。 逆にGoだとClojureほどコーディングが楽しくなかったり... 実装自体は複雑な機能がないので、つまづくこともあまりなく高速で実装できますが 味気ない感じがどうもあります(気にするべきじゃないのかもですが)。
余談
集計作業を行うとき、今まではなんとなくPythonとかでやっていたのですが、 そこまで複雑ではない処理だったらむしろPythonよりもBashとAwkのほうが 短く素早く実装できるなぁと実感しています。
シェルのコードをPythonに置き換えるような話をちらほら聞く中 時代を逆走するようにBashとAwkの勉強を最近はしています。 でも使ってみるとかなり便利なので、これからも勉強しようと思います。
Windows環境でgoreleaserを使用してGitHubにデプロイしてみた
前書き
どうもです。
以前Ubuntu環境でgoreleaserを使用してGitHubにデプロイする作業について記事を作成しましたが、今回はそれのWindows環境版です。
前の記事については下記を参照してください。 http://usernameon666.hatenablog.com/entry/2017/10/08/125842
やってることはほとんど同じですが、デプロイのスクリプトをbashでやってるかPowerShellでやってるかという違いだけですね。
開発環境
項目 | 値 |
---|---|
OS | Windows 10 Home |
Go version | 1.9.2 |
使用スクリプト | PowerShell |
成果物
game-manager
ゲームのプレイ時間を記録してCSVファイルとして保存するTUIアプリ
わざわざ作るほどのものでもないのですが、まぁ勉強用ということで...
同様のゲームのプレイ時間が把握できるもので僕が使用したことがあるのは
SteamやMoeL2などがあったのですが、あれと同じようなもので
ターミナル上で動作するものを作成してみたかったんです。
「みんなのGo言語」に登場したtermbox-goとHOME配下のconfigフォルダにデータを格納する手順を勉強するために作成しました。
リリース手順
GitHubにリポジトリの作成
今回は下記の通り game-manager というリポジトリを作成しました。 https://github.com/jiro4989/game-manager
GoReleaserの設定ファイルの作成
.goreleaserという設定ファイルを$env:GOPATH/src/game-manager配下に作成します。
PS C:\Users\username\go\src\game-manager> pwd Path ---- C:\Users\username\go\src\game-manager PS C:\Users\username\go\src\game-manager> ls ディレクトリ: C:\Users\username\go\src\game-manager Mode LastWriteTime Length Name ---- ------------- ------ ---- d----- 2017/12/24 20:05 data d----- 2017/12/24 20:55 dist d----- 2017/12/24 19:59 game d----- 2017/12/24 20:51 res d----- 2017/12/24 20:54 script -a---- 2017/12/24 20:48 19 .gitignore -a---- 2017/12/24 20:22 322 .goreleaser.yml -a---- 2017/12/24 20:04 5082 game-manager.go -a---- 2017/12/23 5:53 622 README.md
.goreleaserに設定を記入
.goreleaser.ymlの中身を下記の通りにする。
builds: - binary: game-manager goos: - windows - darwin - linux goarch: - amd64 - 386 archive: format: tar.gz replacements: amd64: 64-bit darwin: mac linux: linux name_template: "{{ .ProjectName }}_{{ .Os }}_{{ .Arch }}" files: - README.md
デプロイスクリプトを作成
$env:GOPATH\src\game-manager\script
に下記の内容のデプロイ用のPowerShellスクリプト"deploy.ps1"を作成します。
※別にスクリプトを作成しないといけないわけではないです。
git tag $args[0] $env:GITHUB_TOKEN = cat ".\res\token.txt" goreleaser --rm-dist go install
GitHubのアクセストークンを配置
$env:GOPATH\src\game-manager\res\token.txt
というテキストファイルを作成して、GitHubのアクセストークンを貼り付けます。
あんまりこういう運用方法って良くないと思いますけれど、まぁ個人で行う分には気にしないことにしています。
.gitignoreを作成する
デプロイするバイナリとtoken.txtがリポジトリ上に見えてしまうとまずいので それらを除外するように設定します。
.gitignoreの中身は下記のとおりです。
配置するパスは$env:GOPATH\src\game-manager
です。
token.txt /dist/
スクリプトを実行する
下記のパスがカレントディレクトリであることを確認します。
PS C:\Users\username\go\src\game-manager> pwd Path ---- C:\Users\username\go\src\game-manager
下記のコマンドを実行します。
.\script\deploy.ps1 v1.0.0
※v1.0.0はタグ情報になるので、アップデートするたびに番号を更新する必要があります。
あとはひたすら待機...
※PowerShell環境だと、制御コードがむき出しで表示されてパッと見バグったか失敗したように見えることがありますが、正常に動いているので、信じて待ちます。
リリースの確認
GitHubのリリースページを確認しにいきます。 https://github.com/jiro4989/game-manager/releases