標準愚痴出力

個人的なIT作業ログです。もしかしたら一般的に参考になることが書いているかもしれません(弱気

「git new」コマンドをでっち上げる(Windowsでgitのサブコマンドを作る)

自分の場合、新規レポジトリを作る際、いつも定番的に実行しなければいけない作業がある

  • git init
  • 最初に空コミット作成
  • 改行コード変換の無効設定
  • 日本語ファイル名はそのまま表示させる
  • メールアドレス等の設定

次の記事によると、「git-XXXX」という実行ファイルを作ると、git XXXX というサブコマンドを作成できるようだ。

Windowsなので git-new.cmd というバッチファイルを作成して、自分の定番初期化作業を一括化してみよう。

git init 
git commit -m "empty" --allow-empty
echo "* -text" > .gitattributes
git add .gitattributes 
git commit -m "Add .gitattributes as autocrlf=false"
git config --local core.quotepath false
git config --local user.email zetamatta@example.com 
git config --local user.name  zetamatta

テスト

$ git new
git: 'new' is not a git command. See 'git --help'.

Did you mean this?
        notes
exit status 1

だめでした!

EXEなどの実行ファイルでないとダメなのか。でも、スクリプトレベルのものをいちいち実行ファイル作るのは面倒すぎるなぁ。Linux だとシェルスクリプトが大丈夫なのに…

#!/bin/sh

if [ ! -e .git ] ; then
    git init 
    git commit -m "empty" --allow-empty
fi
if [ ! -e .gitattributes ] ; then
    echo "* -text" > .gitattributes
    git add .gitattributes 
    git commit -m "Add .gitattributes as autocrlf=false"
fi
git config --local core.quotepath false
git config --local user.email zetamatta@example.com 
git config --local user.name  zetamatta

(最終版なので、いろいろ機能追加してますが、気にしないでください)

Git for Windows は MSYS ベースで動作しているので、普通に「git-new」という拡張子無しのテキストファイルを %PATH% 上において、シェルスクリプトを書けば Ok でした!

$ git new
Initialized empty Git repository in C:/Users/Hayama/Share/cmds/.git/
[master (root-commit) 6b0d022] empty
[master f9ca464] Add .gitattributes as autocrlf=false
 1 file changed, 1 insertion(+)
 create mode 100644 .gitattributes

以上

github の Releases のダウンロード数を curl と jq でお手軽に調べたいッ

次のような API があるようだ。

GET /repos/:owner/:repo/releases」の意味が分かりにくいが、「:owner」と「:repo」を、レポジトリのオーナー名とレポジトリ名に置き換えればよいようだ。つまり、https://github.com/zetamatta/nyagos の情報を見たい時は https://api.github.com/repos/zetamatta/nyagos/releases を開けばよろしい。ブラウザでも大丈夫で、別に認証も要らないようだ。

得られるデータは JSON なので、これを加工するツールが必要だ。自前で作ってもよいが、お手軽に jq とやらを使ってみよう。初めて使うので、コマンドがよく分からないが、jq コマンドを使う日常のご紹介 - Qiita やら、jq Manual (development version)などを見て、試行錯誤してみた結果、次の1行で期待する出力を得ることができだ。

curl https://api.github.com/repos/zetamatta/nyagos/releases | jq-win64.exe ".[].assets[] | .name, .download_count"
"nyagos-4.3.1_3-386.zip"
98
"nyagos-4.3.1_3-amd64.zip"
229
"nyagos-4.3.1_2-386.zip"
65
"nyagos-4.3.1_2-amd64.zip"
86
"nyagos-4.3.1_1-386.zip"
9
"nyagos-4.3.1_1-amd64.zip"
36
:以下省略

アクティブユーザ 200人くらいか。やっぱり、もう 64bit版をダウンロードするユーザの方が多いんだなぁ。

これが知りたかっただけだけど、ついでに jq の使い方が分かったので、得たものは大きい

自前の io.* ライブラリを構築中

ファイルまわりはシェル本体との密接な連携が必要なので、結局、io.* 以下の関数を全部自前バージョンで置き換えることにした。

最初からそうすればよかった。

あと、bit32.* については、自前で途中まで作っていたが、GopherLua 用のBixData/gluabit32: A bit32 library for the GopherLua VMという既製のものを利用することにした。utf8.* はないので、やはり自分で作るしかないようだ。

例外がエラー戻り値よりも使いにくいシーンの例(説明用)

Go言語は例外がないのでアウトという人が多いわけですが、自分は「例外が取り扱いにくいシーン」というものを経験しているので、その点は実は肯定的なんですよね。

たとえば、コンストラクタで「様々な例外」が発生する時、下手に try の範囲を広げると「予期せぬ場所での例外」まで拾ってしまうので、できればコンストラクタだけを小さく try で囲みたいのです (try の範囲を広げるのであれば System.Exception ではなくて、細かい例外を全部列挙しなければいけない)

しかしながら

Try
    Dim foo = New System.IO.StreamWriter(fname,false,System.Text.Encoding.Default)
Catch ex as Exception
End Try

だと foo が他で参照できなくて困るので、結果として

Dim foo as System.IO.StreamWriter
Try
    foo = New System.IO.StreamWriter(fname,false,System.Text.Encoding.Default)
Catch ex as Exception
End Try

と書かなくてはいけません。でも、これ面倒くさいですよね。結局は

Try
   '*** 事前に書き込むフォルダーが存在するか程度は確認する if 文などは書いておく ***
    Using foo As New System.IO.StreamWriter(fname,false,System.text.Encoding.Default)


    End Using
Catch ex As Exception
    MsgBox(ex.ToString) ' このあたりは本当はもっとちゃんと書いてるので、ツッコミ勘弁
End Try

みたいに書くようになってしまいました。むむむ

nyagos内蔵Luaで「lua_e "for line in io.lines() do print(line) end" < FILE」が FILE から読み込めない

うん、標準入力をうまいこと GopherLua 内の標準入力にアサインできていないせい で、GopherLua の開発元に相談してみた。

「そんな簡単にいかへんでー」という話なので(いや、わしもそう思てた)、ちょっと何かうまい方法考えてプルリクを送ってみるかー(決意)

つらい、さすがの reflect も構造体の非公開フィールドに値を無理やりセットする用途には使えないかー

package main

import (
    "reflect"
)

type privateOne struct {
    m1 string
    m2 string
}

func sub(from interface{}) interface{} {
    t := reflect.ValueOf(from).Elem().Type()
    obj := reflect.New(t)
    p := obj.Elem()
    p.Field(0).SetString("foo")
    p.Field(1).SetString("bar")
    return obj.Interface()
}

func main() {
    value := sub(&privateOne{"a", "b"})

    if val, ok := value.(*privateOne); ok {
        println(val.m1, val.m2)
    } else {
        println(reflect.TypeOf(value).String())
    }
}

実行してみよう!

$ ./privateStruct.exe
panic: reflect: reflect.Value.SetString using value obtained using unexported field

goroutine 1 [running]:
reflect.flag.mustBeAssignable(0x1b8)
    c:/go/src/reflect/value.go:231 +0x1fa
reflect.Value.SetString(0x478260, 0xc042052040, 0x1b8, 0x492f30, 0x3)
    c:/go/src/reflect/value.go:1551 +0x32
main.sub(0x473aa0, 0xc042052020, 0x0, 0x4660bc)
    C:/Users/hymko/go/src/github.com/zetamatta/experimental/privateStruct/main.go:16 +0x141
main.main()
    C:/Users/hymko/go/src/github.com/zetamatta/experimental/privateStruct/main.go:22 +0x97
exit status 2

普通に怒られた。ちなみに m1 → M1 、m2 → M2 などとフィールドの名前を変えると、期待動作する。

$ ./privateStruct.exe
foo bar

仕様動作を無理やり超えようとしたので、無理かなーとは思ってたんだが、やっぱりか!残念!(強引な解決が無理なら、ライブラリの開発元への「お願い」が通るのを期待するしかないなー)

CSV を「安全」に Excel に読み込む Go プログラムを書いたー(pipe2excel.exe)

  • 5/2 が「5/2」でなく、日付と解釈されて「5月2日」になってしまう
  • =1+2 が「=1+2」ではなく、計算されて「3」になってしまう
    • 関数まで実行されてしまうので、脆弱性となってしまう

f:id:zetamatta:20180703162021p:plain

f:id:zetamatta:20180703162132p:plain

これを解決するには勝手に変な解釈がされないように、あくまで「文字列」としてセルに貼るプログラムを作ればよろしい。

仕様のイメージとしては「変換プログラムを「送るメニュー」とか、拡張子 CSV に割り当てると、CSVファイルをクリックした時、自動で Excel シートに安全に変換される」というのが望ましい。

拡張子に割り当てる、送るメニューに登録するとなると、スクリプト言語よりは、EXEファイルの方がよい。となると Go言語が適している。

Go言語で開発するとなると、2つのアプローチがある

  1. .xlsx ファイルを xlsxexclといったライブラリで静的に生成する
  2. go-ole を使って、Excel を COM で呼び出して、Excel 経由でシートを作成する

「1.」は速度的に有利だが、テンポラリな .xlsx ファイルを作成してしまう。いきなり独立した .xlsx ファイルではなく、一度内容をチェックしてから、問題ないことを確認してから、ちゃんとした名前で保存する形の方が個人的には望ましく思えた。

ということで「2.」のアプローチを採用した。

で、完成品がこちらに。

変換した後、起動した Excel は敢えてそのままに。加工なり、セーブなりはユーザが自分でしてくださいということで(その方が「自分は」使いやすいと思った。もちろん .SaveAs や .Quit も出来るが、余計なお世話感強い)

f:id:zetamatta:20180703162106p:plain

  • CSV は普通に標準ライブラリ "encoding/csv" で読み込む
    • ただし、そのまま読み込むと、1行あたりの項目数が変化した時に、csv.ErrFieldCount エラーになるので、それだけは無視するようにする。
  • CSV"unicode/utf8".Valid で UTF8 であれば UTF8 で、さもなければ現在のコードページ(日本語環境なら cp932=ShiftJIS)と解釈する
  • セルにテキストを貼り付ける時、いきなりセルの .Value プロパティーに代入するのではなく、直前に .NumberFormatLocalプロパティーに "@" を設定して、セルの型を文字列にする。
    • ただし、あきらかに数値として解釈してさしえなさそうなテキストが文字列として設定してあると、Excelの警告マークがうるさいし、Excel自体で数値として扱いたい時に困るので、正規表現 /^[1-9]\d*(\.\d*[1-9])?$/と一致する場合は数値扱いとする(.NumberFormatLocalプロパティーに "0_"を設定する)
  • 標準入力経由でも入力できるようにした
C:\> type foo.csv | pipe2excel
  • CSVファイル名は複数指定できて、その場合は1ファイル名→1ワークシートとするようにした(ワークシート名はファイル名由来とした)
C:\> pipe2excel foo.csv bar.csv

いやー、しかし、Windows の分散オブジェクト(COM)の仕組み、それを操作する go-ole ライブラリ、Go言語の標準ライブラリ群の充実度はすばらしいぞ、デミウルゴス

以上