リリースノートをさくっと作る

一行バッチを作った

mknote.cmd

git log --pretty="format:- %%s" %*

パラメータにリビジョンの範囲を入れる

$ git log --pretty="format:- %s" 20191102..
- ( remove ignoteSigwinch() because latest go-tty does not hang up )
- Surpress cursor blink on resize
- Fix screen broken on console window resized
- readme.md: wrote about Ctrl-L and `w`
- Implement Ctrl-L repaint
- "w": field separator for output becomes one for input now
- readme.md: modify about Quit
- "w": do not overwrite to a existing file
- "w": default fname is args[0] or "-"
- "w": filename '-' means stdout
- Use stderr rather than stdout
- "q": ask yes/no

また、

mknote.cmd 20191002.. | clip

とすると「20191002」とタグが打たれたコミットよりも後のログをクリップボードにコピーするので、それをテキストエディターで編集するなり、直接 GitHub のリリースのテキストに貼ろう。

そこへ前回のgo-package.cmd で作った zip ファイルをドロップだ。

そうやって、リリースしたのが、こちら

このリリースでは、ドキュメントの編集関係の Commit や、他の Commit に含まれてもよかったような内容は省き、その上で日本語行を足している。

以上

Go言語の実行ファイルのパッケージを作成するバッチファイル

GOOS=windows or linux、GOARCH=amd64 or 386 という組み合わせで、(フォルダー名)-(年月日)-(GOOS)-(GOARCH).zip という zip ファイルを作ります。

go-package.cmd

@echo off
setlocal
set "PROMPT=$ "
for %%I in (%CD%) do set "NAME=%%~nI"
for %%I in (windows linux) do for %%J in (amd64 386) do call :one %%I %%J
endlocal
exit /b

:one
    set GOOS=%1
    set GOARCH=%2
    set SUFFIX=
    if %GOOS% == windows set "SUFFIX=.exe"
    echo GOOS=%GOOS% GOARCH=%GOARCH%
    @echo on
    go build
    zip -9 %NAME%-%DATE:/=%-%GOOS%-%GOARCH%.zip %NAME%%SUFFIX%
    @echo off
    exit /b

これで作ったリリースがこちら

追記 (2019.11.02)

ちょっと直しました。

@echo off
setlocal
set "PROMPT=$ "
for %%I in (%CD%) do set "NAME=%%~nI"
for %%I in (windows linux) do for %%J in (386 amd64) do call :one %%I %%J
endlocal
exit /b

:one
    set GOOS=%1
    set GOARCH=%2
    set SUFFIX=
    if %GOOS% == windows set "SUFFIX=.exe"
    echo GOOS=%GOOS% GOARCH=%GOARCH%
    @echo on
    go build -ldflags "-s -w"
    zip -9 %NAME%-%DATE:/=%-%GOOS%-%GOARCH%.zip %NAME%%SUFFIX%
    @echo off
    exit /b
  • ビルドする順番が amd64 → 386 だったのを 386 → amd64 にした
    • あとに残った実行バイナリが 386 だと、WSL で Linux 版を即テストできないから
  • go のオプションに -ldflags "-s -w" を追加して実行ファイルのサイズを小さくした

以上です

CSV のビューアを作った

仕事でプログラム間の連携を TSV ファイルで行うことが多く、しかも Excel とか仮想マシン上に入ってなかったりすることが多い(し、あっても重くて使いたくない)ので、意を決して CSV ビューアを作りました。

f:id:zetamatta:20191027111947p:plain

先日のツイッタークライアントは、一応自分で使う分には問題ないけど、人に使ってもらうには足りない点がいろいろあって未だ完成していないのですが、こちらは着手から半日くらいであっさり出来ました。もっと早く作ればよかった!

  • セルの表示は背景色を青/黒の繰り返しで表現。
    • ただ、ちょっと目がチカチカするので、もうちょっとよい表現があれば、変えたい
  • 最大桁を12桁
    • それ以上は切り捨てる
    • 全データは最下行に表示する
    • 1セル内の行を複数表示する場所がないので、改行は「⤶」と表示
  • キーバインドは HJKL/Ctrl-FBNP/カーソルキー
  • 使用ライブラリは、いつもの mattn 先生の Go言語コンソール三種の神器go-colorable / go-tty / go-runewidth

開発のプロセスは下記のような感じです。

  1. 最初は上から下に市松模様?に CSV をダンプするだけのプログラムを作る
  2. ライブラリ化して、それを n 行目から画面行数分だけ表示する形に改める。
  3. 指定した行の指定した列のセルだけ色を変えられるようにする。
  4. キー入力させて、そのセルの場所をプラスマイナスできるようにする。
  5. 表示開始位置もカーソル位置に応じて変えられるようにする

ね、簡単でしょ?

nyagos 4.4.5_2 を公開しました。

もう nyagos もアーカイブモードも近いかなと思っていたのですが、ちょっと致命的な不具合が見つかったので、急遽修正しました。

  • (#375) ~randomstring でクラッシュする不具合を修正

これは UNIX 系の ~username をホームディレクトリに置換するところなんですが、username が見つからんかったときに何もなかったかのように戻す処理があります。そこに不具合がありました。panic dump のリポートが出るので場所はすぐわかりましたが、そこを本来はどう直すべきであったか、思い出すのにちょっと手間取りました(汗

  • (#374) 未来のタイムスタンプのファイルのls -lで西暦がでなかった不具合を修正

以前、仕事で未来のタイムスタンプ(2020年)のファイルを作ったのですが、ls -ltr で年が出ず、「表示を見ると(年が出ないので)過去に見えるのに、ソート順では最も新しい側に見える」という妙な症状が出たことで発覚しました。

Git for Windows に付属の ls で本来の動作を確認したところ、ls -l で日付の後に年ではなく、時刻が出る条件は、タイムスタンプが半年前~今の間にある時だけのようです。nyagos 内蔵 ls では、1年前より未来がすべて時刻表記になっていたので、標準の ls に挙動をあわせました。

以上、よろしくお願いいたします。

2ペインテキストビューア、いろいろ作ってます。

20年くらい前にOS/2eff , ell という「テキストビューア」群を作ってましたが、それと同じような感じのものできないかなーと思って、Go言語で簡単なフレームワーク go-twopane を作ってみました。

  • コンソール画面を2分割して、上に件名一覧、下に詳細を表示する
  • go-twopane には配列を渡す。配列の要素は「件名」を得る Title() メソッドと、「詳細」を得る Contents() というメソッドを定義してあれば OK
  • カーソル上移動(,k)、下移動(,j)、詳細次ページ(SPACE)、終了(ESC,C-q)程度は標準で実装。他のキーに対するアクションを定義したい場合はコールバック関数を渡せば良い

という感じです。ね、簡単でしょ?

そして、Windows でも Linux でも動きます(ここ重要)

実装例1:git のログビューア (gitview

件名一覧は git log の結果。詳細は git show の結果です。git show の方は、git の出すカラーを維持し、かつ UTF8 と解釈できない行は ANSI → UTF8 変換を試みるので、文字化けがかなり少ないはずです。(ファイルの中身は ShiftJIS だが、git.exe がファイル名を Unicode で出すので、出力全部の文字コードが一貫しないことが Windows ではよくあるのですわ…)

f:id:zetamatta:20191002210300p:plain

実装例2: Subversion のビューア

こちらはソースだけ。 なぜなら、スクリーンショットが業務情報のカタマリのやつしかないから…。

git と違って git show みたいなやつはないんですが、svn log --xml でそこそこ情報が取れるので、詳細情報はログ全部+変更ファイル一覧みたいなものをかわりに載せてます。

実装例3:twitter client(tmt view

いわゆる「人間性はありません」(tmt.exe)という名前の謎ツイッタークライアントが正体がコレです。もともと tmt post で投稿、tmt timeline でタイムラインを標準出力に出すといった twtyパクったようなインスパイアした ツールなんですが(だから自分の趣味のサブコマンドしかない)、これに tmt view というビューアを起動するサブコマンドを追加しています。

f:id:zetamatta:20191002210459p:plain

twitter は文字種が多いので、go-twopane のすごい耐久テストができました。ANSI エスケープシーケンスだけでやっているので、うっかりすると行の桁数が想定よりオーバーフローして、画面がくずれてしまいます。なので、以下のような対策を go-twopane 側でとっています。

  • Windows の端末はサロゲートペアな Unicode が表示できないので、それらは &#xNNNN; といった形にエンコードする(豆腐にしてもよかったんですけどね)
  • 文字幅があいまい(全角か半角かというやつです)とされている文字については、あふれないように端末のマスを常に2セル消費することを前提にしてしまう

(注意)

tmt はソースそのままではビルドできません。twitter の ConsumerKey , ConsumerSecretKey がカラだからです。これらは twitter に開発申請を出してゲットしてから、secret/secret.go.sample にある変数名を埋めないといけません(埋めたあとファイル名から .sample を除くのも忘れないように)

なお、バイナリについては、リリースタブのところからダウンロードできます

利用技術

うむ、また mattn 先生のライブラリにおんぶにだっこだな! でも、この組み合わせだと、Windows/Linux 両方サポートが簡単なんだよ

痛い話など

  • 最初レポジトリを作った時、twopain と命名していました。これは痛い…

  • マインクラフトばっかりやってて GitHub の砂漠化が進んでいましたが、こいつを作るようになって、ちょっと緑が回復しました。

  • ブラウザで見るより twitter 目立たないかなーと思ってたんですが、黒いコンソールの中に日本語がみっちり入ってると意外と目立ちますね(おまえは何を懸念しているんだ

以上です

(続々) std::vector / std::set 両方からデータを取れる関数をあまりテンプレートにしたくない

(半日も経ってませんが、(続) std::vector / std::set 両方からデータを取れる関数をあまりテンプレートにしたくない - 標準愚痴出力からの続きです)

前回 std::function を使って、コールバック関数を関数オブジェクトにし、呼び出しを簡素にしてみたわけですが、まだ型パラメータが長めでした。それをなんとか一つまでに減らしてみました。

#include <iostream>
#include <vector>
#include <functional>
#include <set>

template <typename T>
class enumerator {
    typename T::iterator m_cursor,m_end;
public:
    enumerator(typename T::iterator begin,typename T::iterator end)
        : m_cursor(begin) , m_end(end) {}
    bool operator() (typename T::value_type &store) {
        if( m_cursor == m_end ){
            return false;
        }
        store = *m_cursor;
        m_cursor++;
        return true;
    };
};


void put(const std::function<bool(std::string&)> &each)
{
    std::string value;
    while( each(value) ){
        std::cout << value << std::endl;
    }

int main()
{
    std::vector<std::string> v;
    v.push_back( "a" );
    v.push_back( "b" );
    v.push_back( "c" );

    put( enumerator<std::vector<std::string>>(v.begin(),v.end()) );

    std::set<std::string> s;
    s.insert( "a" );
    s.insert( "b" );
    s.insert( "c" );

    put( enumerator<std::set<std::string>>(s.begin(),s.end()) );
}

よしよし

(続) std::vector / std::set 両方からデータを取れる関数をあまりテンプレートにしたくない

(前回:std::vector / std::set 両方からデータを取れる関数をあまりテンプレートにしたくない - 標準愚痴出力

関数オブジェクトで、少し改善できました。

#include <iostream>
#include <vector>
#include <functional>
#include <set>

template <class E,class T>
class enumerator {
    E m_cursor;
    E m_end;
public:
    enumerator(const E &begin,const E &end) : m_cursor(begin) , m_end(end) {}
    bool operator() (T &store) {
        if( m_cursor == m_end ){
            return false;
        }
        store = *m_cursor;
        m_cursor++;
        return true;
    };
};

void put(const std::function<bool(std::string&)> &each)
{
    std::string value;
    while( each(value) ){
        std::cout << value << std::endl;
    }
}

int main()
{
    std::vector<std::string> v;
    v.push_back( "a" );
    v.push_back( "b" );
    v.push_back( "c" );

    put( enumerator<std::vector<std::string>::iterator,std::string>(v.begin(),v.end()) );

    std::set<std::string> s;
    s.insert( "a" );
    s.insert( "b" );
    s.insert( "c" );

    put( enumerator<std::set<std::string>::iterator,std::string>(s.begin(),s.end()) );
}

しかし、enumerator<std::set<std::string>::iterator,std::string>(…) は長いすぎる。クラス名くらいは一つにしたい…