標準愚痴出力

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

git で特定の2つのフォルダーを使ってる履歴のみ残して、後は消す

フォルダー一つだけなら git filter-branch --subdirectory-filter が使えるが、複数の場合はそうもいかない。 複数のフォルダーだけを残すために、Lua スクリプトをさくっと書いた。

function getgitlog(files)
    local fd = io.popen("git log " .. table.concat(files," ") )
    local commit = {}
    if fd then
        for line in fd:lines() do
            if string.match(line,"^commit ") then
                commit[ #commit + 1 ] = string.sub(line,8)
                print( commit[ #commit ])
            end
        end
        fd:close()
    end
    return commit
end


local commit = getgitlog( {"argf","seek"} )
local all = getgitlog( {} )
local first = all[ #all ]

os.execute("git checkout " .. first)
os.execute("git branch tmp")
os.execute("git checkout tmp")
for i=#commit,1,-1 do
    os.execute("git cherry-pick "..commit[i])
end

自分の都合だけで作ったので、下記のような制限がある。

  • 1回限りの利用なので、残すフォルダー「argf」「seek」は決め打ち
  • argf , seek フォルダーに関わっている commit だけをcherry-pick するという方式なので、厳密には argf , seek だけを抽出するわけではない
  • 最初の commit だけは argf , seek フォルダーに関わってなくとも残ってしまう(あとで revert した)
  • git filter-branch --subdirectory-filter みたいに、フォルダーの解消まではしない(二つフォルダーがあるから実際無理)。
  • フォルダー名に空白が含まれると多分誤動作

これを使って公開したのが、先に公開した seek コマンドだったりする。

うっかり、オレオレ grep (seek)を書いてしまった。

先日、while( <> ){ } 的なものの Go 版ライブラリ argfを書いてみたわけだけれども、そのサンプルとして 簡単な grep (seek)を書いてみた。

最初は必要最小限にしていたんだけど、jvgrep みたいに「検索文字列の部分をハイライト表示」できないかなと思って頑張ってみたら、Go言語のライブラリが充実していることもあって、案外簡単に出来てしまった。

で、調子にのって、自分が grep に望む機能を実装してみた。

  • UTF8 と ANSI(現在のコードページマルチバイト文字列:日本なら SJIS~CP932)の行単位での簡易自動判定
    • UTF8 と解釈できなければ、ANSI と判断するという安直判定
    • 前は ShiftJIS の時は Windows 標準の findstr.exe、UTF8 なら jvgrep を使っていたが、いちいち使い分けるのは面倒くさかった。
    • jvgrep はテキストファイルチェックが厳しくて、すぐバイナリファイル扱いにしてしまう。
    • git で SJIS テキストの差分を見る時、git の UTF8 と、本文の SJIS が混ざってしまう。両方とも化けずに見たい場合があるので、行単位で別々の文字コード判定にしたかった。

本当に自分が必要とした機能しか入れていないので、以下のような制限がある

  • オプションは -i (大文字・小文字を区別しない)、-r再帰的にファイルを検索する)だけ
  • 速度はあまり重視しておらず、工夫なし
  • 常にカラー表示。「リダイレクトしていたらエスケープシーケンスを抑制」とか別にしない
  • 色使いは jvgrepパクリ 互換

貴殿も、オレオレ grep を作ってみませんか?

Lua 5.1 向け shebang for Windows

@lua5.1.exe -e "_,_,b=io.input([[%~f0]]):read('*l','*l','*a');assert(loadstring('\n'..b,[[%~f0]]))()"
@exit /b %ERRORLEVEL%

print('ahaha')
x

5行目にわざと Lua としては不適切な文字 x を入れている。これを実行すると

lua: (command line):1: [string "C:\Users\USERNAME\foo.cmd"]:5: syntax error near <
eof>
stack traceback:
        [C]: in function 'assert'
        (command line):1: in main chunk
        [C]: in ?
exit status 1

5行目がおかしいというエラーがちゃんと出る。x を消すと

$ foo
ahaha

とちゃんと実行される。

while( <> ){…} みたいなことを Go で

UNIX風のテキストファイルフィルターを書くのに便利なように Perl には

while( <> ){
   print $_
}

という構文がある。これと同じような手軽さで Go でもフィルターを書くためのライブラリ argf を書いてみた。

package main

import (
    "fmt"
    "os"

    "github.com/zetamatta/experimental/argf"
)

func main() {
    r := argf.New()
    for r.Scan() {
        fmt.Println(r.Text())
    }
    if err := r.Err(); err != nil {
        fmt.Fprintln(os.Stderr, err.Error())
    }
}
  • 基本インターフェイスbufio.Scanner に準拠した(=ほぼ同じメソッド構成)
  • len(os.Args) < 2 の時は、標準入力から読み取るが、そうでない時は os.Args[1:] のファイルを順に読み込んでゆく
  • argf.New()インスタンスには、AWK の組み込み変数と同じ名前のメソッドを実装している
    • NR() … 読み込みレコード数(行数)
    • Filename() … 現在読み込んでいるファイルの名前
    • FNR() … 現在のファイルで読み込んだレコード数(行数)

かんたんな grep もどきも書いてみた。

package main

import (
    "errors"
    "fmt"
    "github.com/zetamatta/experimental/argf"
    "os"
    "regexp"
)

func main1() error {
    if len(os.Args) < 2 {
        return errors.New("Usage: grep.exe REGEXP Files...")
    }
    rx, err := regexp.Compile(os.Args[1])
    if err != nil {
        return err
    }
    r := argf.NewFiles(os.Args[2:])
    for r.Scan() {
        if rx.MatchString(r.Text()) {
            fmt.Printf("%s(%d): %s\n", r.Filename(), r.FNR(), r.Text())
        }
    }
    return r.Err()
}

func main() {
    if err := main1(); err != nil {
        fmt.Fprintln(os.Stderr, err.Error())
        os.Exit(1)
    }
    os.Exit(0)
}

argf.New()os.Args[1:] を全て読み込み対象としてしまうが、grep もどきでは os.Args[1] は検索文字列となるので、読み込み対象から除きたい。そのために argf.NewFiles(args []string) という、読み込み対象のファイル名を、引数で与えるタイプのコンストラクタも用意した。

今週の zetamatta/experimental は以上です。

Go言語についての3つくらいの誤解

以下、自分の思い込みもあるとは思うが、あまり気にしないで書いてみる。

Goは短く書くための言語ではない

短く書けるようにした結果、落とし穴が発生したり、 読みにくくなったり誤解が生じる余地があるならば、 そのような書き方は排除されている。

貴方が書きたいコードは、誰にでも読みやすいコードなのか、 誤解の余地がまったくないコードなのか、そのあたりをよく考えてほしい。

サポートされていない機能は未来永劫サポートしないとは限らない

例外は将来的にもサポートされないだろうが、 ジェネリクスは優先度が低いとされているだけで、 別に未来永劫サポートしないわけではない。

ただ、複雑性が増すなど課題が多いのと、 ジェネリクスがなくても済ませられるケースが多いため、 後回しにされているだけである。

Goはあらゆる問題を解決する言語ではないし、誰にでも使ってもらおう等と譲歩した言語でもない

RubyJava のように、あらゆる課題を解決しようなどと思い上がった言語ではない。 不得意分野は不得意分野と割り切っている。

また、誰にでも使ってもらうなどと考えている言語でもない。 気に入らないのなら、選択肢は他に幾らでもあるだろう。

以上、ソースはないよ。コメントもらっても回答しないかも

PowerShell で書く、Go言語プログラムのリリース管理的なアレ(3)バージョン文字列の設定

バッチファイルの話もするとたいへんなので、以下 PowerShell だけという方向で:

Go ソースへの埋め込み

これは簡単で、

go build "-o" nyagos.exe -ldflags "$ldflags -X main.version=$version"

だけでよい。これで main パッケージの version という変数に $version の内容が設定される。 Go ソース側で受け皿となる変数の宣言を忘れないように。

var version string

Windows プロパティーへの埋め込み

ちょっとめんどう。goversioninfo というツールを使う。 (make.cmd では Make-SysO という関数内で行っている)

    .\goversioninfo.exe `
        "-file-version=$version" `
        "-product-version=$version" `
        "-icon=mains\nyagos.ico" `
        ("-ver-major=" + $v[0]) `
        ("-ver-minor=" + $v[1]) `
        ("-ver-patch=" + $v[2]) `
        ("-ver-build=" + $v[3]) `
        ("-product-ver-major=" + $v[0]) `
        ("-product-ver-minor=" + $v[1]) `
        ("-product-ver-patch=" + $v[2]) `
        ("-product-ver-build=" + $v[3]) `
        "-o" nyagos.syso `
        versioninfo.json

(PowerShell で逆クォートは、UNIX系のバックスラッシュみたいな役割を担っている)

基本の情報は versioninfo.json に記しておいて、変更がよく発生するところだけ、オプションで変更する。 このコマンドで生成されたnyagos.sysoというファイルを go build するフォルダーにおいておくだけで、 これらの情報が実行ファイルに埋め込まれる。

Windows にはバージョン番号のプロパティーが複数ある。

  • 製品バージョン(product-version)
  • ファイルバージョン(file-version)

NYAGOS では両者は同じ扱いにしているが、大きなパッケージソフトウェアでは、パッケージ全体のバージョンを product-version とし、個別ファイルのバージョンはファイルバージョンとするのが普通のようだ。

さて、これらのバージョンには「バージョン文字列」と4つの数値:「メジャーバージョン」「マイナーバージョン」「ビルドナンバー」「パッチナンバー」をそれぞれに持っている。

Windowsエクスプローラで EXE ファイルのプロパティー→詳細タブを開くと、バージョン情報が表示されるが

  • 製品バージョンは、バージョン文字列(-product-version=)がそのまま表示される
  • ファイルバージョンは、「メジャーバージョン(-ver-major=)」「マイナーバージョン(-ver-minor=)」「パッチナンバー(-ver-patch=)」「ビルドナンバー(-ver-build=)」をピリオドで連結したテキストが表示される。

ようになっているようだ。

なお、古い goversioninfo.exe には「-product-ver-*****」という引数はない (わしがプルリク送った)。

最新版をゲットしてくれ…といいたいところだが、make.cmd には最新版の goversioninfo.exe を自動で入手してくれる機能がある(続く)

PowerShell で書く、Go言語プログラムのリリース管理的なアレ(2)バージョン文字列の取得

方針としては

  • リリースビルドの時は、テキストファイル(Misc\version.txt)に記載のテキスト
  • スナップショットの時は、git describe --tags の結果

を使うことにしている。これはバッチファイルでも比較的簡単だ。 リリースビルドなら

for /F %%I in (%~dp0Misc\version.txt) do set "VERSION=%%I"

だし、スナップショットなら

for /F %%I in ('git describe --tags') do set "X_VERSION=-X main.version=%%I"

だ(若干変数が違うがスルーしていただきたい)。

これが PowerShell だと、もっと簡単で、リリースビルド:

Build  (Get-Content Misc\version.txt)  ""

となる。Build はビルドするための関数で第一引数のバージョン文字列を取る形になっている。スナップショットは

 Build (git describe --tags) ""

だけで済む。PowerShell では括弧が逆クォート的な役割を担っている(文法的には式を展開する的な意味)。

うん、つまらないところなので、早くも飽きてきた(おい)。 次はもう少し面白いはずの、バージョンを設定するところを書こう。