NYAGOS 4.4.7 のバージョンアップ内容(予告)

こんな感じになる予定です。

(1)cd,pushd とその補完で、bash のような %CDPATH% をサポート

Go開発者の人ならば

nyagos.envadd("CDPATH",
    "~\\go\\src\\github.com\\zetamatta",
    "~\\go\\src\\bitbucket.org\\zetamatta")

とかやっておくと、自分のレポジトリに cd nyagos みたいに一発で移動できるようになるので便利です。envadd は環境変数セミコロンで区切ったリストを登録する時に、重複をしないように要素を追加する関数です(4.4.5 で追加)

なお、cd nyagos\ のように \ をパス中に含めると CDPATH が無効になるので注意が必要です。補完はそのあたり配慮はしてますので、そんなに困ることはないと思いますが。

(2) %APPDATA%\NYAOS_ORG\nyagos.dスクリプトも読むようにした

これは現在作成中の .msi 形式のインストーラーで C:\Program Files 以下へインストールした時、nyagos.d が編集できなくなるための配慮です。

(3)WindowsTerminal上では、サロゲートペアな Unicodeを <nnnnn> のようにエスケープしないようにした

普通のコマンドプロンプトではサロゲートペアは Unicode は文字化けするだけでなく、文字幅の計算が狂って、編集が困難な状態になるので といった形にエスケープして表示するようにしていました。

ですが、WindowsTerminal ではサロゲートペアな Unicode が表示可能になったので、その処理の解除をするようにしました。解除のスイッチは WindowsTerminal 使用中にセットされる環境変数 WT_PROFILE_ID , WT_SESSION の有無です。

(4)バイナリファイルを置くディレクトリを Cmd から bin へ変更した

これはソースツリーだけの話です。Go言語の開発作法としては「Cmd/コマンド名」ディレクトリの下にはそのコマンドの main パッケージソースが置かれます。nyagos では OS やアーキテクチャごとのバイナリの置き場所にしていて、これはよろしくないので bin に変えました。

(5)catalog\subcomplete.lua , git.lua の改善

スクリプトは git や svn などのサブコマンド名を持つコマンドに対する補完機能を実現するものです。少し起動に時間がかかるので、標準組み込みではなく、use "subcomplete.lua" と設定した時だけ有効になるオプションとしています。

今回、これにいろいろ手を入れました。(もらいもんなので、おそるおそる…)

(5-1) 新補完API nyagos.complete_for を使うようにした

補完APIは長らく nyagos.completion_hook というフックで提供されていましたが、API が細かすぎて、何でも出来る反面、利用する Lua コードが長くなりがちになるという問題がありました。

そこで bash を参考に 4.4.1 で nyagos.complete_for という新APIを用意しました。これはコマンドごとに関数を定義でき、かつ、与えられる引数が加工済みで、戻り値は候補一覧を雑に返せばよいだけとなっています。

今回、subcomplete.lua 、git.lua をこちらの API 用に書き換えることで、Lua 側コードを簡潔にしました。

(5-2) 起動を早くするため、補完するサブコマンド名をファイルにキャッシング

起動に時間がかかる原因は、起動時にヘルプを出力させるために補完対象のコマンドを呼び出すためです。これを %LOCALAPPDATA%\nyagos\***-subcommands.txt というファイルにキャッシングさせました。これで、起動が遅いのはそのPCで初めて起動させた時だけになります。

なお、%LOCALAPPDATA% は通常は %USERPROFILE%\AppData\local と同値で、アプリケーション独自のユーザごとのデータで、かつ、別のPCに移行などする必要のないデータをおくべきフォルダーらしいです。まぁ、普通はキャッシュ置き場とかそういう奴ですよね。 (別のPCに移行する必要があるデータなどは %APPDATA% = %USERPROFILE%\AppData\Roaming におくらしい)

(5-3) キャッシュクリアコマンド clear_subcommands_cache を実装

git や svn などをバージョンアップして、サブコマンドが増えた時など、キャッシュを作りなさなくてはいけません。そういった時、clear_subcommands_cache を実行して、キャッシュを消しましょう。

(5-4) fsutilgo のサブコマンド補完

ついでに

(5-5) catalog/git.lua で、 subcomplete.lua を自動でロードするようにした

subcomplete.lua 側で何回読み込まれても1回だけ初期化するように修正してから、git.lua 側で subcomplete.lua を自動で読み込むようにしました。


また、何か追加で盛り込むかもしれませんが、今のところは以上です。

Go をバージョンアップする PowerShell スクリプト

次の Go 1.15 に備えて:

  1. Go のダウンロードページをクロールして、最新の go*.windows-amd64.zip を探す(β版は対象外)
  2. 現在インストールされているものよりも新しくなければ終了
  3. ZIP ファイルをダウンロード
  4. $GOROOT を C:\goC:\go1.14.4 のようにリネーム
  5. $GOROOT の親ディレクトリで ZIP ファイルを unzip

といったことをする PowerShell を書いてみました。

function Get-GoZipUrl($web_client){
    $download_page = $web_client.DownloadString('https://golang.org/dl/')

    $re = New-Object regex('<a[^>]+href="([^"]*)">')
    $m = $re.Matches($download_page)
    if ( -not $m.Success ) {
        return $null
    }
    $latest_version = 0
    $latest_url = $null
    $m | foreach-object{
        $url = $_.Groups[1].Value
        if ( $url -match 
            "https://dl\.google\.com/go/go(\d+)\.(\d+)(?:\.(\d+))?\.windows-amd64.zip" ){
            $major = [int]$Matches[1]
            $minor = [int]$Matches[2]
            $patch = [int]$Matches[3]
            if ( $patch -eq $null ){
                $patch = [int]0
            }
            $version = ( $major * 100  + $minor ) * 100 + $patch
            if ( $version -gt $latest_version ){
                $latest_version = $version
                $latest_url = $url
            }
        }
    }
    return @{
        version_number=$latest_version 
        url=$latest_url 
    }
}

function Get-CurrentGoVersion(){
    $go_exe_path = (where.exe go)
    if ($go_exe_path -eq "") {
        return $null
    }
    $last_go_version = (( go version ) -split " ")[2]
    $go_bin = (Split-Path -Parent $go_exe_path)
    $goroot = (Split-Path -Parent $go_bin)

    $s = $last_go_version.SubString(2)
    $s = ($s -split "\.")
    $v = (([int]$s[0] * 100) + [int]$s[1])*100
    if ( $s.Count -ge 3 ) {
        $v = $v + $s[2]
    }
    return @{
        version_number=$v
        goroot=$goroot
        version_word=$last_go_version
    }
}

function download-go($web_client,$url) {
    $filename = (Split-Path -leaf $url)
    Write-Host "Download $url -> $filename"
    $web_client.downloadFile($url,$filename)
    Write-Host "Done"
    return $filename
}

function main(){
    $web_client = New-Object System.Net.WebClient
    $web_client.Headers['User-Agent'] = 'go_update.ps1'

    $cur = (Get-CurrentGoVersion)
    $new = (Get-GoZipUrl $web_client)

    if ( $new -eq $null ){
        return "golang.org not found"
    }

    if ( $cur -ne $null ){
        Write-Host "Installed Version=" $cur.version_number
    }else{
        Write-Host "Installed Version= (not installed)"
    }
    Write-Host " Web Last Version=" $new.version_number

    if ( $cur -ne $null -and $new.version_number -le $cur.version_number ){
        return "Go is not updated."
    }

    if ( $cur -ne $null ){
        $parent = (Split-Path -Parent $cur.goroot)
        $goroot = $cur.goroot
        $goroot_bak = (Join-Path $parent $cur.version_word)
        if (Test-Path $goroot_bak) {
            $goroot_bak = $goroot_bak + "-" +
                (Get-Date -UFormat "%Y%m%d_%H%M%S")
        }
        Write-Host "Rename $goroot to $goroot_bak"
        Move-Item $goroot $goroot_bak
    }else{
        $parent = "C:\"
    }
    $filename = (download-go $web_client $new.url)
    Expand-Archive -Path $filename -DestinationPath $parent
    return $null
}

$err = (main)
if ( $err -ne $null ){
    Write-Host $err
}

さて、うまく動作するかな…

一応、 $new.version_number -le $cur.version_number のところの不等号を -lt にして動作するのは確認したけどね

$ go-update.ps1
Installed Version= 11404
 Web Last Version= 11404
Rename C:\go to C:\go1.14.4-20200614_220928
Download https://dl.google.com/go/go1.14.4.windows-amd64.zip -> go1.14.4.windows-amd64.zip

※ nyagos 上なので、拡張子ps1のコマンド名の前には自動的に powershell -ExecutionPolicy RemoteSigned -file が挿入されている

NYAGOS 4.4.6_2 を公開しました。

修正点は以下だけになります。

  • Fix: Ctrl-C を押下すると、Ctrl-D のようにシェルが終了してしまう不具合を修正 (4.4.6_0 で #383 修正時に発生)

#383 は「ターミナルがクラッシュした時、一行入力関数がエラーを返していたが、それを無視していたために、一行入力関数がユーザの入力のないまま無限ループしてしまう」という不具合でした。これを直すために「一行入力関数がエラーを返した時、ただちに終了する」という対応を 4.4.6_0 にて入れました。

はい、「一行入力関数はCtrl-C を押下された結果もエラーとして扱っていた」ので、それでただちに終了してしまっていたということです。

Ctrl-C によるエラー終了の場合は普通に、一行入力関数を再度呼ぶよう、呼び出し元を直しました(4.4.6_2)。

現場からは以上です。

nyagos の自動アップデート機能(まだ道半ば)

自動アップデート機能なんてものは nyagos 自身に組み込むべきではないので、別の実行ファイルにする。だが、Goで組むとすぐ数メガバイトいってしまって望ましくない。

ということで、PowerShell で試作してみた。 今のところ、ZIPをダウンロードしてくるだけで、現行のものを差し替えることまではしない。

# $installed_version = (nyagos --show-version-only)
$installed_version = "4.4.6_0-windows-amd64"

function parseVer($s){
    $s = ($s -split "-")[0]
    $ver = 0
    foreach($v in $s -split "[_\.]"){
        try{
            $ver = $ver * 100 + [Convert]::ToInt32($v,10)
        }catch{
            break
        }
    }
    $ver
}

function getOsArch($s){
    $spec = $s -split "-"
    $spec[ $spec.Length - 2 ] + "-" + $spec[ $spec.Length - 1 ]
}

$download_pattern = "*" + (getOsArch $installed_version) + "*"
$installed_version = (parseVer $installed_version)

$cli = New-Object System.Net.WebClient
$cli.Headers['User-Agent'] = "nyagos_update"

$list = @{}

Invoke-RestMethod "https://api.github.com/repos/zetamatta/nyagos/releases" |
foreach-object {
    $_ | where-object{
        $_.tag_name -match "^[0-9]+\.[0-9]+\.[0-9]+_[0-9]+$" -and
        (parseVer $_.tag_name) -gt $installed_version
    } | foreach-object{
        $_.assets | Where-Object{ $_.name -like $download_pattern } |
        foreach-object{
            # $list[ (parseVer $_.name ) ] =
            #    @{ name=$_.name;url=$_.browser_download_url }
            Write-Output @{ name=$_.name;url=$_.browser_download_url }
            $cli.DownloadFile( $_.browser_download_url , $_.name )
        }
    }
}

nyagos は nyagos --show-version-only で自分のバージョンを標準出力へ吐いてくれる。これより大きいタグが Releases で見つけたら、要バージョンアップだ。今はテスト中なので固定値を放り込んでいる。

GitHub の Releases の履歴をとる API は、PowerShell ならば Invoke-RestMethod "https://api.github.com/repos/zetamatta/nyagos/releases" だけで Ok だ。戻り値は JSON オブジェクトに最初からなっている。

だが、その JSON オブジェクトは階層が一つ深いので、一回 ForEach-Object でトップ階層を読んで、その $_ を改めて解析してやらないといけない。

当初、これに気づかなくて、えらくハマってしまった。ForEach-Object を深くする前、$_.tag_name で得られるタグ名が現在走査しているリリースのタグネームではなく、全履歴のタグ名になっていた。おそらく、子要素の "tag_name" の値を全部検索してもってきてしまったのだろう。

その結果、$_.tag_name -match "~" の結果が True / False ではなくて、パターンにマッチする履歴のタグすべての集合になっていた。-match 演算子は左辺が文字列の時は True/False が得られるが、左辺が配列の時はマッチする要素のリストになってしまう。リストを Boolean として評価すると常に真になってしまう。そのため、本来排除したい 4.2.0_beta みたいな数字ではないタグがどうしても排除できず、小一時間悩むことになる。

アップデート対象となる履歴がわかれば ZIP ファイルのダウンロードだ。このダウンロードは System.Net.WebClient を使う。注意点としては GitHubを相手にする時は User-Agent を何でもいいのでちゃんと設定しておかないといけないという点だ。でないと 403 Forbidden されてしまう。

さて、ここまで来たら、ダウンロードした ZIP ファイルを展開して、それを現行のものと差し替えることになる。ところが、ここでやる気がなくなってしまう。次回乞うご期待

なんか、前に Go の GUI を検討したファイルが出てきたのでリサイクル

fyne-io/fyne: Cross platform GUI in Go based on Material Design

サンプルを自分の WIndows10 でビルド・実行した時の表示:

f:id:zetamatta:20200605102909p:plain

ちょっとフォントがアレかな?(設定かオプションで直るかな?)

実行ファイルのサイズ

$ ls -ltr
-rw-a--        369 Jan  1 2019     fynetest.go
-rwxa-- 13,018,210 Jan  1 2019     fynetest.exe*

13MB

lxn/walk: A Windows GUI toolkit for the Go Programming Language

サンプルを自分の WIndows10 でビルド・実行した時の表示:

f:id:zetamatta:20200605103705p:plain

見えにくいが、フォントとかは特に問題なし。

実行ファイルのサイズ

$ ls -ltr
-rwxa-- 6,592,000 Jan  1 2019     walk.exe*
-rw-a--       514 Jan  1 2019     main.go

6.5MB


たぶん実行環境によって、良い点・悪い点が左右すると思われるが、自分の Windows10 環境に限っていうと Walk の方が良さそう。

Windows の FileID とは

Windows は i-node がないから、2つのファイル名の同一性のチェックが面倒だ。

といったことをせにゃならん。それでもなお、ジャンクションとか、ドライブ文字へのディレクトアサインとかあって、無理

などということを言っていたら、ファイルIDがあるという情報をいただいた。

どうやら

  • コマンドラインでは、fsutil.exe file queryFileNameById で確認できる
    • ただし出力は ファイル ID は 0x00000000000000000072000000004033 です などというなんとも使いにくいテキスト
  • USBメモリなどの FAT や、VMware の共有フォルダー上のファイルでもとれた
  • ちゃんとした API が用意されているので、それを使えということらしい

これが COM (Scripting.FileSystemObject) とかで読み出せたら便利なんだがなぁ(まぁ贅沢はいうまい)

追記

File-ID を得る Go のパッケージを書いた。

Go 1.14.4 で直ったという、Windows で O_CREATE | O_TRUNC で既存のファイル開くと読み取り専用なってしまうバグとは

「あれ?これ nyagos のリダイレクトで発生するんじゃね?」と思ったが、発生しなかった。 どうやら、

  • os.OpenFile の第3引数でパーミッションを0などにした時に発生する
  • UNIX的な挙動としては、この第3引数は新規ファイルを作成する時だけ使うべきもので、 既存のファイルを0バイトにしてから開く場合は適用されてはダメ
    • Windows 版も UNIX と同じような振る舞いをすべきなのに、そうなっておらず、新規ファイル・既存ファイル関係なく、パーミッションが変わってしまっていた。

というものらしいです。うん、関係なかったな!