標準愚痴出力

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

続・GoでCygterm っぽいことをやりたいので、ソケットサーバーを書いている

アドバイスをいただきました(ありがとうございます)

試してみたところ、確かに

$ diff TERATERM-orig.INI TERATERM.INI
491c491
< EnableLineMode=on
---
> EnableLineMode=off

と変えたところ、プログラムは無修正で1文字単位での送受信ができるようになりました(なお、LocalEcho については、はじめから「LocalEcho=off」になってました)。

教えていただいた参考ページによると、ssh の場合とか、サーバーからの応答によっては最初からEnableLineMode=off になるみたいですね。

それと同じようなことを自動でやればよさそうです。nyag?os のTeraTerm 接続は長年の懸案ですが、これはもしかしていけるか!?

a[:0] と append の秘密

a = a[:0] は領域のサイズをリセットするが、a = make([]T,0,cap(a)) と違って、使っていたメモリブロックを再利用するため、allocation 回数を削減できる。

だが、旧a の領域が他で使われていないかを気にせず、無頓着に使うと append でおかしいことになる。たとえば

a := []string{ "a","b","c" }
b := a[:0]
b = append(b,"1")

fmt.Printf("%+v\n",a)

とすると a の内容が [1 b c] となる。これは append が領域を上書きで使用するためだ。

append 側で他所で使われているかチェックしてくれたらよさそうな話ではあるが、おそらく物理的に無理だろう。というのも参照カウンタ方式ではなく、マーク&スイープ方式の Garbage Collector を使っている場合、参照されている場所が自分を含めて2個以上あるか、調べようがないのだ。

となると、常に他で使われているという前提で append を実装しないと、上のような状況は避けられないわけだ。そういう append ちょっと作ってみよう。

package main

import (
    "fmt"
)

func appendStr(a []string, b string) []string {
    r := make([]string, len(a)+1)
    copy(r,a)
    r[len(a)] = b
    return r
}

func main() {
    a := []string{}
    for i := 0; i < 10; i++ {
        a = appendStr(a, fmt.Sprintf("%d", i))
    }
    fmt.Printf("%+v\n", a)
}

こんな実行効率わるそうなの、みんな使いたいと思います?結局、みんなオリジナル append を使いますよね? (でも、スクリプト言語では効率が悪くても平気でやってそうだなぁ)。

Go で Cygterm っぽいことをやりたいので、ソケットサーバーを書いている(勉強中)

続きの記事 あり)

serv.go :

package main

import (
    "fmt"
    "net"
    "os"
    "strings"
)

func handler(conn net.Conn) {
    defer conn.Close()

    var buffer [255]byte

    for {
        n, err := conn.Read(buffer[:])
        if err != nil {
            fmt.Fprintln(os.Stderr, err)
            return
        }
        text := string(buffer[:n])
        text = strings.Replace(text,"\r","\n",-1)
        conn.Write(buffer[:n])
        fmt.Printf("[%s]\n",text )
    }
}

func main1() error {
    ln, err := net.Listen("tcp", ":8080")
    if err != nil {
        return err
    }
    for {
        conn, err := ln.Accept()
        if err != nil {
            return err
        }
        go handler(conn)
    }
}

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

go run serv.go すると、teratermtelnetlocalhost:8080 に接続できる。が、TeraTerm 側でEnter を入力しないと通信が届かない。これ、Enter なしで送受信するにはどうすればいいんだろう

("bufio".Reader) ReadString('\n') と ("bufio".Scanner) Scan() の違い

違い

  • ("bufio".Reader) ReadString('\n')
    • 改行をカットしない
  • ("bufio".Scanner) Scan()
    • 改行をカットする

改行コードのない最終行の扱い

最後の行が abcd(EOF) というテキストの場合

  • ("bufio".Reader) ReadString('\n')
    • "abcd",io.EOF という戻り値となる
  • ("bufio".Scanner) Scan()
    • Text() は "abcd" という戻り値を返し、そのScan() が false になる

Scanner の方は定番である

for sc.Scan() {
   line := sc.Text()
   fmt.Println(line)
}

という書き方で問題ないが、Reader の方は

for{
    line,err := r.ReadString('\n')
    if err != nil {
        break
    }
    fmt.Print(line)
}

だと、最後の行が抜けてしまう。

for{
    line,err := r.ReadString('\n')
    fmt.Print(line)
    if err != nil {
        break
    }
}

と書かないといけない。こう書くと Scanner の方が常に正しいように思われるが、Scanner は最終行の改行の有無がわからないという問題がある。

go mod 試験導入

nyagos の依存パッケージである zetamatta/go-texts/mbcs に不具合があったので、それをちゃんと import するよう、go modules を設定してみた。 そのメモ。間違っている箇所があればご指摘歓迎

go-texts 側

go modules を使用するモードに切り替える

set GO111MODULE=on

各パッケージフォルダーで go.mod を作成し、レポジトリへ反映する。これは v0~v1のトップだけではダメのようだ。v2 とかのメジャーバージョンフォルダーにも必要。また、go.mod は go build で更新されるので、そのあとに commit するのも忘れてはいけない。

cd ~/go/src/github.com/zetamatta/go-texts/
go mod init
go build
git add go.mod
cd mbcs
go mod init
go build
git add go.mod
git commit -m "add go.mod"

v1.0.1 というタグをつけておく。v1.0.0 ではないのは単に既に同タグが使用済みだったからというだけ

git tag v1.0.1

GitHub にも go.mod とタグを反映しておく。モジュールは別フォルダーに個別にダウンロードされるので、ローカルのパッケージにだけタグがあっても意味がない。

git push
git push --tag

go-box 側

今回の不具合ではないが、go-box という依存モジュールは v2 というフォルダーを既に使っていて、そのままビルドするとエラーが発生してしまった。

cd ~/go/src/github.com/zetamatta/go-box
go mod init
go build
git add go.mod
cd v2
git mod init
go build
git add go.mod
git commit -m "Add go.mod"

nyagos 本体

cd ~/go/src/github.com/zetamatta/nyagos
go mod init
go build
git add go.mod

これで Ok なはずだが、ビルドスクリプト修正

  • 環境変数 GO111MODULE を on にするコードを追加
  • go get が必要なくなるので、make get は何もしないようにする

ところで、これ、そのバージョン以降の最新版という指定をする方法はないんでしょうかねぇ。。。

追記

気になっていた import モジュールの最新化だけど、「Go言語の基礎〜Go 1.11 開発環境構築とパッケージバージョン管理〜 – RE:ENGINES」によると、

go get -u

でいいみたい。

NYAGOS開発ノート:新補完API(試作中)

現在の補完APILua 側の負担が重いので、bash と同じくらいにお手軽な新補完APIを試作中してます。以下は git のサブコマンドを補完する例:

nyagos.complete_for["git"] = function(args)
    if #args == 2 then
        local base = args[#args]
        local len = string.len(base)
        local result = {}
        for _,s in pairs{ "commit","push","pop","diff","rebase","log" } do
            if string.sub(s,1,string.len(base)) == base then
                result[#result+1] = s
            end
        end
        return result
    else
        return nil
    end
end
  • コマンド単位に、テーブル nyagos.complete_for["コマンド名"] に関数を割り当てる
  • 引数 args はカーソルより左側を空白で区切ったテキスト(二重引用符等は削除済み)
  • 戻り値には、補完候補の文字列群をテーブルとして返す
  • 戻り値を nil にすると、従来どおりのデフォルトのファイル名補完が実行される。

まだ、もうちょっと変えるかもしれません。

追記

候補の限定処理も Go 側でやるようにしてみました。この結果、上記の例は

nyagos.complete_for["git"] = function(args)
    if #args == 2 then
        return { "commit","push","pop","diff","rebase","log" }
    else
        return nil
    end
end

とすごく簡単になります。

さらに追記

決定版!

nyagos.complete_for.git = function(args)
    while #args > 2 and args[2]:sub(1,1) == "-" do
        table.remove(args,2)
    end
    if #args == 2 then
        return nyagos.fields([[
          add                 fsck-objects        rebase
          add--interactive    gc                  receive-pack
          am                  get-tar-commit-id   reflog
          annotate            grep                relink
          apply               gui                 remote
          archimport          gui--askpass        remote-ext
          archive             gui--askyesno       remote-fd
          bisect              gui.tcl             remote-ftp
          bisect--helper      hash-object         remote-ftps
          blame               help                remote-http
          branch              http-backend        remote-https
          bundle              http-fetch          repack
          cat-file            http-push           replace
          check-attr          imap-send           request-pull
          check-ignore        index-pack          rerere
          check-mailmap       init                reset
          check-ref-format    init-db             rev-list
          checkout            instaweb            rev-parse
          checkout-index      interpret-trailers  revert
          cherry              log                 rm
          cherry-pick         ls-files            send-email
          citool              ls-remote           send-pack
          clean               ls-tree             sh-i18n--envsubst
          clone               mailinfo            shortlog
          column              mailsplit           show
          commit              merge               show-branch
          commit-tree         merge-base          show-index
          config              merge-file          show-ref
          count-objects       merge-index         stage
          credential          merge-octopus       stash
          credential-manager  merge-one-file      status
          credential-store    merge-ours          stripspace
          credential-wincred  merge-recursive     submodule
          cvsexportcommit     merge-resolve       submodule--helper
          cvsimport           merge-subtree       subtree
          cvsserver           merge-tree          svn
          daemon              mergetool           symbolic-ref
          describe            mktag               tag
          diff                mktree              unpack-file
          diff-files          mv                  unpack-objects
          diff-index          name-rev            update-index
          diff-tree           notes               update-ref
          difftool            p4                  update-server-info
          difftool--helper    pack-objects        upload-archive
          fast-export         pack-redundant      upload-pack
          fast-import         pack-refs           var
          fetch               patch-id            verify-commit
          fetch-pack          prune               verify-pack
          filter-branch       prune-packed        verify-tag
          fmt-merge-msg       pull                web--browse
          for-each-ref        push                whatchanged
          format-patch        quiltimport         worktree
          fsck                read-tree           write-tree]])
    end
    if args[#args]:sub(1,1) == "-" then
        if args[2] == "commit" then
            return { "--amend" , "-a" , "--cleanup","--dry-run" }
        end
    end
    return nil
end

nyagos.fields は文字列を空白で分割する関数です。