標準愚痴出力

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

Subversion で git show とか stash とかの動作をエミュレーション

あまりしっかりテストしていないので、ご利用はバックアップをとった上で、自己責任で… (自分の用事が終ったら放置モード)

svn-show.cmd

svn diff -r1000:1001 とかやっていられないので、svn-show 1001 で同じことをする。

@echo off
setlocal
if "%1" == "" (
    for /F "skip=1 tokens=1" %%I in ('svn log -l 1') do call :show %%I & exit /b
) else (
    call :show "%1"
)
endlocal
exit /b

:show
    set "REV2=%~1"
    set REV2=%REV2:r=%
    set /A REV1=REV2-1
    svn diff -r%REV1%:%REV2%
    exit /b

svn-stash.cmd

svn-stash とすると、コミットされていない修正が svn-stach.1 などというファイルに保存され、修正は消える。元に戻すには svn-stash svn-stash.1 とパッチファイルを引数に渡す。

setlocal
if "%1" == "" ( call :diff ) else ( call :patch "%~1")
endlocal
exit /b

:diff
    set /A cnt=cnt+1
    if exist svn-stash.%cnt%  goto :diff
    svn diff > svn-stash.%cnt%
    svn revert -R .
    exit /b

:patch
    svn patch "%~1"
    exit /b

NYAGOS 4.4.3_0 を公開していただと…

連休前の話ですが、NYAGOS 4.4.3_0 公開したので、更新内容をご説明します。

  • (#116) readline: Ctrl-Z,Ctrl-_ による操作取り消しを実装

3年前に issue を起案してた UNDO をようやく実装しました。やってみたら、意外と簡単でした。まぁ、技術的に難しいというよりも胆力不足で先送りしてたという感じでしょうか(最近、介護中の母を介護施設が預かっていただける曜日が増えたので、体力やら時間やらがかなり改善したのも大きいと思います)

なお、bash と違って、連続する UNDO 動作をまとめるようなことはしません。具体的にいうと、BSキーを4回タイプした後、これを元に戻す時、bash では1回のUNDOで4文字戻りますが、nyagos では4回のUNDOが必要です。これについては、bash のようにまとめても必ずしも使いやすいとも限らないので、無理して合わせる必要ないかなと考えました。

  • (#194) コンソールウインドウの左上のアイコンを更新するようにした。

2年前の起案していた件とありますが、これ、本来は「コンソール窓の左上のアイコン」ではなく、「タスクバーのアイコン」の方。これが nyagos.exe 内蔵のアイコンではなく、CMD.EXE アイコンになってしまうという問題。

今回「コンソール窓」の左上アイコンだけでも変えられるようになったので、関係事項として同issueに書きましたが…本命の方がまだ変えられていないので、クローズならずです。どうすりゃいいんでしょうね、これ。SendMesssage という API は初めて使ったのですが、タスクバーも似たようなことやるんでしょうかね。

CMD.EXE 内蔵コマンドについては、いちいち CMD /C 内蔵コマンド とか打ってられないので、nyagos.d/comspec.luaエイリアスを定義しているのですが、そこに date/time を追加しました(単に気づいてなかっただけ)

date/time なんて使う機会は滅多にないのですが、会社で「現在日時を変えてテストするテスト」をしなくちゃいけないことがあったのですが、一番、手軽に変えるのが date コマンドということで多用したので、追加した次第です。

  • cd 相対パス の後のドライブ毎のカレントディレクトリが狂う不具合を修正
    ( cd C:\x\y\z ; cd .. ; cd \\localhost\c$ ; c: ; pwd -> C:\x (not C:\x\y) )

DOSの頃はカレントディレクトリはドライブごとにあったのですが、Windows では全ドライブで1つしかありません。CMD.EXE(と MSVCRT.DLL)では「=X:」という変な名前の隠し環境変数で X ドライブのカレントディレクトリを保持するようにして、DOSの頃の動作をエミュレートしています。

で、NYAGOS もそれにならっています。(昔は MSVCRT.DLL の機能を使っていた)。で、今回の不具合は、違うドライブに移動する際に、それまでのドライブのカレントディレクトリを保存するのですが、そのタイミングを間違ってしまったという感じです。


で、今回の修正の目玉は事実上「UNDO」しかなくて、ちょっと物足りないのではあったのですが、連休中に結構ガリガリ書くことが予想されたため、一旦、連休前に安定版を出しておいた方が安全ではないかと考えた次第です。実際、かなりガリガリ書いたわけですが、それについては 4.4.4_0 の更新報告にて…

【解決編】UNCパスの補完機能の強化のためにコンピューター名一覧を出したいんだが、違うそうじゃない

記事を書いたら、自己解決する法則でもあるのかなぁ。

// +build run

package main

import (
    "fmt"
    "unsafe"

    "golang.org/x/sys/windows"
)

const RESOURCE_CONNECTED = 1
const RESOURCE_CONTEXT = 5
const RESOURCE_GLOBALNET = 2
const RESOURCE_REMEMBERED = 3
const RESOURCETYPE_ANY = 0
const RESOURCETYPE_DISK = 1
const RESOURCETYPE_PRINT = 2
const RESOURCEDISPLAYTYPE_NETWORK = 6
const RESOURCEUSAGE_CONNECTABLE = 1
const RESOURCEUSAGE_CONTAINER = 2
const RESOURCEUSAGE_ATTACHED = 16
const RESOURCEUSAGE_ALL = 19
const ERROR_NO_MORE_ITEMS = 259

var mpr = windows.NewLazySystemDLL("mpr.dll")
var procWNetOpenEnum = mpr.NewProc("WNetOpenEnumW")
var procWNetEnumResource = mpr.NewProc("WNetEnumResourceW")
var procWNetCloseEnum = mpr.NewProc("WNetCloseEnum")

type netresourceT struct {
    Scope       uint32
    Type        uint32
    DisplayType uint32
    Usage       uint32
    LocalName   *uint16
    RemoteName  *uint16
    Comment     *uint16
    Provider    *uint16
}

func u2str(u *uint16) string {
    if u == nil {
        return ""
    }
    buffer := make([]uint16, 0, 100)
    for *u != 0 {
        buffer = append(buffer, *u)
        u = (*uint16)(unsafe.Pointer(uintptr(unsafe.Pointer(u)) + 2))
    }
    return windows.UTF16ToString(buffer)
}

func WNetEnum(nr *netresourceT, handler func(localName string, remoteName string)) error {
    var handle uintptr

    // localNameW,err := windows.UTF16PtrFromString("localhost")
    // if err != nil {
    // return err
    // }

    rc, _, err := procWNetOpenEnum.Call(
        RESOURCE_GLOBALNET,
        RESOURCETYPE_ANY,
        0,
        uintptr(unsafe.Pointer(nr)),
        uintptr(unsafe.Pointer(&handle)))
    if rc != windows.NO_ERROR {
        return fmt.Errorf("NetOpenEnum: %s", err)
    }
    defer procWNetCloseEnum.Call(handle)
    for {
        var buffer [16*1024]byte
        count := int32(-1)
        size := len(buffer)
        rc, _, err := procWNetEnumResource.Call(
            handle,
            uintptr(unsafe.Pointer(&count)),
            uintptr(unsafe.Pointer(&buffer[0])),
            uintptr(unsafe.Pointer(&size)))

        if rc == windows.NO_ERROR {
            println("open")
            for i := int32(0) ; i < count ; i++ {
                var p *netresourceT
                p = (*netresourceT)(unsafe.Pointer(&buffer[ uintptr(i)*unsafe.Sizeof(*p) ]))
                handler(u2str(p.LocalName), u2str(p.RemoteName))
                WNetEnum(p,handler)
            }
            println("close")
        } else if rc == ERROR_NO_MORE_ITEMS {
            return nil
        } else {
            return fmt.Errorf("NetEnumResource: %s", err)
        }
    }
}

func main() {
    err := WNetEnum(nil,func(localName, remoteName string) {
        println(localName, " -> ", remoteName)
    })
    if err != nil {
        println(err.Error())
    }
}

// https://msdn.microsoft.com/ja-jp/library/cc447030.aspx
// http://eternalwindows.jp/security/share/share06.html

要は再帰的に呼ばないとダメというわけですね

$ go run eachnetdrive_run.go
open
  ->  Microsoft Terminal Services
  ->  Microsoft Windows Network
open
  ->  MICROSOFTACCOUNT
  ->  WORKGROUP
open
  ->  \\ATERM-71C395
  ->  \\DESKTOP-LGGUCRA
open
  ->  \\DESKTOP-LGGUCRA\tmp
close
close
close
  ->  Web Client Network
close

UNCパスの補完機能の強化のためにコンピューター名一覧を出したいんだが、違うそうじゃない

追記:解決編あり

いまひとつうまくゆかん。

// +build gorun

package main

import (
    "fmt"
    "unsafe"

    "golang.org/x/sys/windows"
)

const RESOURCE_CONNECTED = 1
const RESOURCE_CONTEXT = 5
const RESOURCE_GLOBALNET = 2
const RESOURCE_REMEMBERED = 3
const RESOURCETYPE_ANY = 0
const RESOURCETYPE_DISK = 1
const RESOURCETYPE_PRINT = 2
const RESOURCEDISPLAYTYPE_NETWORK = 6
const RESOURCEUSAGE_CONNECTABLE = 1
const RESOURCEUSAGE_CONTAINER = 2
const RESOURCEUSAGE_ATTACHED = 16
const RESOURCEUSAGE_ALL = 19
const ERROR_NO_MORE_ITEMS = 259

var mpr = windows.NewLazySystemDLL("mpr.dll")
var procWNetOpenEnum = mpr.NewProc("WNetOpenEnumW")
var procWNetEnumResource = mpr.NewProc("WNetEnumResourceW")
var procWNetCloseEnum = mpr.NewProc("WNetCloseEnum")

type netresourceT struct {
    Scope       uint32
    Type        uint32
    DisplayType uint32
    Usage       uint32
    LocalName   *uint16
    RemoteName  *uint16
    Comment     *uint16
    Provider    *uint16
    _           [16 * 1024]byte
}

func u2str(u *uint16) string {
    if u == nil {
        return ""
    }
    buffer := make([]uint16, 0, 100)
    for *u != 0 {
        buffer = append(buffer, *u)
        u = (*uint16)(unsafe.Pointer(uintptr(unsafe.Pointer(u)) + 2))
    }
    return windows.UTF16ToString(buffer)
}

func WNetEnum(handler func(localName string, remoteName string)) error {
    var handle uintptr

    // localNameW,err := windows.UTF16PtrFromString("localhost")
    // if err != nil {
    //  return err
    // }

    buffer := make([]byte, 1024)

    nr := (*netresourceT)(unsafe.Pointer(&buffer[0]))

    nr.Scope = RESOURCE_GLOBALNET
    nr.Type = RESOURCETYPE_ANY
    nr.DisplayType = RESOURCEDISPLAYTYPE_NETWORK
    nr.Usage = RESOURCEUSAGE_CONTAINER

    rc, _, err := procWNetOpenEnum.Call(
        RESOURCE_GLOBALNET,
        RESOURCETYPE_ANY,
        RESOURCEUSAGE_CONTAINER,
        uintptr(unsafe.Pointer(&buffer[0])),
        uintptr(unsafe.Pointer(&handle)))
    if rc != windows.NO_ERROR {
        return fmt.Errorf("NetOpenEnum: %s", err)
    }
    defer procWNetCloseEnum.Call(handle)
    for {
        count := uint32(1)
        size := 1024
        rc, _, err := procWNetEnumResource.Call(
            handle,
            uintptr(unsafe.Pointer(&count)),
            uintptr(unsafe.Pointer(&buffer[0])),
            uintptr(unsafe.Pointer(&size)))

        if rc == windows.NO_ERROR {
            handler(u2str(nr.LocalName), u2str(nr.RemoteName))
        } else if rc == ERROR_NO_MORE_ITEMS {
            return nil
        } else {
            return fmt.Errorf("NetEnumResource: %s", err)
        }
    }
}

func main() {
    err := WNetEnum(func(localName, remoteName string) {
        println(localName, " -> ", remoteName)
    })
    if err != nil {
        println(err.Error())
    }
}

// https://msdn.microsoft.com/ja-jp/library/cc447030.aspx
// http://eternalwindows.jp/security/share/share06.html

なんか、いい線まで言ってるような気はするんだが

$ go run eachnetdrive_run.go
  ->  Microsoft Terminal Services
  ->  Microsoft Windows Network
  ->  Web Client Network

違うそうじゃない。

NYAGOS保守日記: 空白文字が含まれたパスの補完とチルダ

補完の際、Windowsでは空白を打ち消す文字の定番が決まってないのでパス全体を引用符で囲む。すると ~\Program Files"~\Program Files" みたいになるが、~ が先頭でなくなってしまうので、 ~ を%USERPROFILE% に変換できなくなってしまう。

かといって、~\"Program Files" だと、引用符の前の \ が " の機能を打ち消すように解釈できてしまう。結局、~"\Program Files" という変態補完になったのが現在の nyagos。

だが、最近「~\Program" "Files\ でもいいんじゃね?」という妙案も浮かんでしまって、どうしたもんかなーとか悩んでいたり

一方で、ファイル名の空白を ⌴(U+2334)で表現して、引数を分割する空白と区別させることは出来ないかなぁ…とか考えている。しかし、⌴ を " " へ置換するタイミングが漏れると、不具合のもと。なかなか難しい

一回、展開関数まで書いたりしたんですけどね:

const AntiSpace="\u2334"

func FromAntiSpace(s string) string {
    var buf strings.Builder
    q := false
    for _,c := range s {
        if c == '"' {
            q = !q
        }
        if c != AntiSpace {
            buf.WriteRune(c)
        }else if q {
            buf.WriteByte(' ')
        }else{
            buf.WriteString(`" "`)
        }
    }
    return buf.String()
}

しかし、仮に、空白を ⌴ に置き換えて、二重引用符で囲まなくてもよくしたとしても、& とか | とか、別の機能文字があったりしたら、結局囲まなくてはいけなかったりするしなぁ…。あんまり意味ないか

ちなみに、nyaos 3000で \Share\Program Filesを補完すると~\"Share\Program Files\" となるなぁ。この時は \" を問題視しなかったのかな、俺

VC++で書いたアドオンDLLの中で独自ダイアログが表示できなかった件

とあるアプリのアドオンを VC++ で書いていたんだけれども、自作のダイアログを出そうとしてもなぜか DoModal が素通りしてしまう。

しかも、Visual Studio 自体もなぜか不安定で、クラスウィザードのクラッシュも多発して、原因の切り分けができない!!!

1日半ほど苦闘した結果、ようやく以下の情報に巡り合った

どうやら、EXE側からDllMainの第一引数で与えられているハンドルを DLL側で適切に設定しなくては、DLL側でリソースを使えないらしい (クラッシュの原因は別件なので、ここでは省く)

昨今、ウェブの情報も永遠ではないので、ミラーというかバックアップとして、こちらのソースも晒しておこう。

// DLL のモジュールハンドル
extern "C" HINSTANCE _hdllInstance = NULL;
HINSTANCE GetInstance()
{
    return _hdllInstance;
}

BOOL APIENTRY DllMain(
    HINSTANCE hInstance, 
    DWORD dwReason, 
    LPVOID pReserved)
{
    _hdllInstance = hInstance;
    ; // 中略
}

void example()
{
    const HINSTANCE hInstance = AfxGetResourceHandle();
    AfxSetResourceHandle(GetInstance());

    CSelectDwg dlg1;
    dlg1.DoModal();

    AfxSetResourceHandle(hInstance);
}

NYAGOS 4.4.2_1 を公開しました。

4.4.2_0(報告記事リンク)を出して間もないのですが、看過できない不具合がありましたので、修正版を公開しました。

修正点は以下の2点

  • diskfree: 行末の空白を削除

これは大した問題ではなく、余計な空白が多くて要らぬ改行をしてしまうので、空白を trim したというだけの話です。問題は次:

  • ~"\Program Files" の最初の引用符が消えて、Files が引数に含まれない不具合を修正

これは 4.4.2_0 の「~ユーザ名 の展開を実装」でエンバグしてしまったようです。~ の後にユーザ名がなかった時に元通りに戻さないといけないのですが、その際の二重引用符の扱いが不適切で、引用の始まり・終わりの対応の認識がおかしくなってしまいました。

このあたりのパーサーのソースは結構〈地獄〉になっているので、いずれちゃんと書き直したいところですが、今回のところは必要最小限の不具合修正で止めておきました。

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