退職の意思のご連絡
$ taishoku -y 2019 -m 3 -d 18 --your-name あなた野なま江
代株 二退こ
表式 〇職の
取会 一いた
締社 九たび 退
役ジ 年し一
ョ 三た身 職
ジー 月く上
ョク 一この 願
ーコ 七こ都
クマ 日に合
山ン おに
悪ド 願よ
ふ いり
ざ 申、
け あジ し二
太 なョ 上〇
郎 たー げ一
殿 野ク ま九
なコ す年
まマ 。三
江ン 月
ド 一
印開 八
発 日
部 を
も 私
第 っ 儀
一 て
課
はじめに
退職願・退職届のアスキーアートを出力するコマンドを作成しました。
開発言語はGoです。
作った経緯
退職の意思を伝える方法は、何も手書きの紙だけである必要はない、そう思いませんか?
コマンドラインからリモートサーバ先にアスキーアートで伝えても良い
HTMLファイルでもよい、いろんなプローチがあっても良い...そう思ったからではないです。
アスキーアートで表現できれば、例えば以下のようにリモートサーバで共同で作業をしている方に
退職の意思を伝えるテロが可能です。
$ taishoku | wall
CLIから扱えれば、mail
コマンドやat
コマンドで時間差で退職の意思を伝えることが可能で、
明日からしばらく職場に復帰できない、でも退職の意思は伝えておきたいといった状況に対応できるかと思います。別にそういった用途を想定していたわけではありませんが。
本当に作った経緯は、単純にジョーク系のコマンドを何か作ってみたかったからというだけです。
インストール
以下のコマンド、あるいは前述のリンク先のGitHubReleaseから。
go get -u github.com/jiro4989/taishoku
使い方
ヘルプにも書いていますが、動的に変化しうる箇所についてはすべてコマンドラインオプションから指定できるようになっています。
Flags:
--todoke taishoku todoke
-o, --offset int offset (default 3)
--format string format [html]
-y, --year int year (default 2999)
-m, --month int month (default 12)
-d, --day int day (default 31)
-D, --department string your department (default "ジョークコマンド開発部")
-T, --team string your team (default "第一課")
-n, --your-name string your name (default "真面目田マジメ")
-C, --company string company name (default "株式会社ジョークコマンド")
-P, --president string president (default "代表取締役")
-N, --president-name string president name (default "ジョーク山悪ふざけ太郎")
-X, --debug debug logging flag.
-h, --help help for taishoku
HTML出力できるようにもなっています。退職届もだせます。
以下のように。
$ taishoku --format html > a.html
フロントエンドから今まで縁遠かったのでCSS縦書きを使うのはこれが初めてでした。
HTML出力はできますけれど、別に印刷用に幅を整えていたりはしないので
多分印刷して提出しようとしてもいい感じにはならないと思います。
印刷して確認してすらいないです。
動作確認はChromeとFirefoxだけ。
Firefoxはブラウザ間差異で左端によってしまってましたが、スルーしました。
Chromeでは中央に表示されます。
実装
AA出力のロジックは下記の通り。
- AAに関してはテンプレートになるテキストに動的に変化する値をReplaceで埋め込む
- 全角空白でパディング
- 横書きを縦書きに変換
- 出力
HTMLに関しては、AAと違い空白パディングや縦書き変換の必要はなくて
縦書きにする処理をすべてCSSに一任しています。
よって、前述の箇条書きの1と4だけです。
やってることはtemplate.Execute
だけですし、特に書くことがないので割愛。
テンプレートへのReplace埋め込み
何ら特殊なことをしていない、普通の置換です。雑です。
const taishokuNegai = `
退 職 願
私儀
このたび一身上の都合により、{taishokuDate}をもって
退職いたしたくここにお願い申し上げます。
{today}
{department} {team}
{yourName} 印
{company}
{president} {presidentName}殿
`
// makeTaishokuText は退職テキストを生成する。
func makeTaishokuText(templateText, taishokuDate, today, department, team, yourName, company, president, presidentName string) string {
text := templateText
convs := map[string]string{
"{taishokuDate}": taishokuDate,
"{today}": today,
"{department}": department,
"{team}": team,
"{yourName}": yourName,
"{company}": company,
"{president}": president,
"{presidentName}": presidentName,
}
for k, v := range convs {
text = strings.Replace(text, k, v, 1)
}
return text[1 : len(text)-1]
}
全角空白でのパディング
空白はすべて全角スペースで埋めています。
単純にテキストとして出力したときの見栄え的に、半角スペースで埋めると
プロポーショナルフォントのときに見た目が崩れるためです。
そのため、半角英数字は全角英数字に内部的に変換するようにしています。
// padSpace は文字列のスライスのうち、一番長い文字列にあわせてテキストの後ろに全角空白を埋める。
// 返却する文字列は新しい配列なので、引数に渡した配列に破壊的変更は与えない。
// また、空白はマルチバイト全角空白である。
// ここでの空白埋めは見た目上のテキストの長さを指す。
func padSpace(s []string) []string {
text := make([]string, len(s))
copy(text, s)
var maxLength int
// 一番長い文字列の長さを取得
for _, t := range text {
l := utf8.RuneCountInString(t)
if maxLength < l {
maxLength = l
}
}
// 一番長い文字列の長さに合わせて空白を追加
for i := 0; i < len(text); i++ {
diff := maxLength - utf8.RuneCountInString(text[i])
if 0 < diff {
pad := strings.Repeat(" ", diff)
text[i] += pad
}
}
return text
}
横書きを縦書きに変換
最初にReplaceで値を埋め込んだテキストは横書きなので、
このまま出力しても退職願っぽくないので、縦書きに変換します。
マルチバイト文字列に単純に配列のインデックスとしてアクセスすると化けるので
一度rune配列に変換してアクセスしています。
文字列の長さもlen
関数で単純に数えるとバイトサイズになってしまうので
マルチバイト文字列の文字の数、として数えるためにencoding/utf8パッケージの RuneCountInString(string)
を使用しています。
Goはマルチバイト文字が楽に扱えるのでテキスト処理がやりやすいですね。
// reverse は配列の並びを反転させる。
// 文字順で逆順ソートとかしたりはしない。
func reverse(s []string) []string {
s2 := make([]string, len(s))
for i, j := len(s)-1, 0; 0 <= i; i, j = i-1, j+1 {
s2[j] = s[i]
}
return s2
}
// toVertical は横書き文字列配列を縦書き配列に変換する。
// 前提として、引数の配列はrune文字列としてすべて長さが同じでなければならない。
func toVertical(s []string) (ret []string) {
if len(s) < 1 {
return []string{}
}
l := utf8.RuneCountInString(s[0])
for i := 0; i < l; i++ {
var line []string
for _, row := range s {
for colIdx, c := range []rune(row) {
if colIdx == i {
line = append(line, string(c))
}
}
}
line = reverse(line)
s := strings.Join(line, "")
ret = append(ret, s)
}
return
}
余談
余談ですが、今月(2019年3月)末で現職を退職します(マジ)。
別にこのコマンドは使ってないですし、普通に手書きで作成して提出しました。
お世話になった会社ですので流石に。
この記事を見た方も、いないとは思いますが
くれぐれも冗談以外でこんなコマンド使わないように。
追記 (2020/03/03)
退職後、数ヶ月遊んでから2019年7月末に転職しました。
現在は新しい職場で元気にやっています。