2017/12/19


Vim に :terminal が入ってしばらく経ちます。そろそろ :terminal を使ったハック欲も溜まってきているかと思います。

:terminal から起動したシェル(Windowsだとコマンドプロンプト)では、VIM_SERVERNAME という環境変数が設定されています。これは --remote-send--remote-expr 等で別プロセスの Vim に命令を送る際のサーバ名で --servername により指定可能です。

:terminal の中から以下の様に実行すると :terminal を起動した Vim に命令を送って計算結果が返ってきます。
terminal

2017/12/05


この記事は Go Advent Calendar 2017 の記事... ではありません

追記あり

実はあったりします。

Golang で URL から charset を取得するのを書いたのですが、他にもっとよい方法があるとおもうのです... - Qiita

他によい方法がある気がするのですが...わからないので、書いてみました。

https://qiita.com/mochizukikotaro/items/ddc0c6b1b98cd33f451e

golang.org/x/net/html/charset を使うと良いです。

package main

import (
    "bufio"
    "fmt"
    "log"
    "net/http"

    "golang.org/x/net/html/charset"
)

func main() {
    resp, err := http.Get("https://google.com/")
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    br := bufio.NewReader(resp.Body)
    if data, err := br.Peek(1024); err == nil {
        if _, name, ok := charset.DetermineEncoding(data, resp.Header.Get("content-type")); ok {
            fmt.Println(name)
        }
    }
}

charset.DetermineEncoding を使うと BOM や Content-Type ヘッダ、meta タグ等といった情報から charset 名を得られます。日本のIPアドレスからであれば shift_jis と表示されると思います。

しかしながらこの charset 名を使ってどうやって HTML からテキストを得るかという問題が起きると思います。そこで go-encoding という物があります。

GitHub - mattn/go-encoding
https://github.com/mattn/go-encoding

オフィシャルが提供する golang.org/x/text/encoding には各エンコーディングに対する utf-8 へのデコード処理が書かれているのですがエンコーディング名とのマッチングがありません。そこで使うのが go-encoding です。

br := bufio.NewReader(resp.Body)
var r io.Reader = br
if enc := encoding.GetEncoding(name); enc != nil {
    r = enc.NewDecoder().Reader(br)
}

この様に名称から Encoding オブジェクトを得る事が出来ます。実装はただただ並べただけの物なので見ても面白い物はありません。あとはこれを使って HTML をパースすれば HTML からテキストのみを抽出する事が出来る様になります。

package main

import (
    "bufio"
    "bytes"
    "fmt"
    "io"
    "log"
    "net/http"
    "strings"

    "github.com/mattn/go-encoding"
    "golang.org/x/net/html"
    "golang.org/x/net/html/charset"
)

func text(resp *http.Response) (stringerror) {
    br := bufio.NewReader(resp.Body)
    var r io.Reader = br
    if data, err := br.Peek(1024); err == nil {
        if _, name, ok := charset.DetermineEncoding(data, resp.Header.Get("content-type")); ok {
            if enc := encoding.GetEncoding(name); enc != nil {
                r = enc.NewDecoder().Reader(br)
            }
        }
    }

    var buffer bytes.Buffer
    doc, err := html.Parse(r)
    if err != nil {
        return "", err
    }
    walk(doc, &buffer)
    return buffer.String(), nil
}
func walk(node *html.Node, buff *bytes.Buffer) {
    if node.Type == html.TextNode {
        data := strings.Trim(node.Data, "\r\n ")
        if data != "" {
            buff.WriteString("\n")
            buff.WriteString(data)
        }
    }
    for c := node.FirstChild; c != nil; c = c.NextSibling {
        switch strings.ToLower(node.Data) {
        case "script""style""title":
            continue
        }
        walk(c, buff)
    }
}
func main() {
    resp, err := http.Get("http://example.com/")
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    s, err := text(resp)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(s)
}

追記

ヘッダが shift_jis を返して来ない場合は DetermineEncoding の最後の戻り値が ok を戻さない様なので修正。

package main

import (
    "bufio"
    "bytes"
    "fmt"
    "io"
    "log"
    "net/http"
    "strings"

    "github.com/mattn/go-encoding"
    "golang.org/x/net/html"
    "golang.org/x/net/html/charset"
)

func text(resp *http.Response) (stringerror) {
    br := bufio.NewReader(resp.Body)
    var r io.Reader = br
    if data, err := br.Peek(4096); err == nil {
        enc, name, _ := charset.DetermineEncoding(data, resp.Header.Get("content-type"))
        if enc != nil {
            r = enc.NewDecoder().Reader(br)
        } else if name != "" {
            if enc := encoding.GetEncoding(name); enc != nil {
                r = enc.NewDecoder().Reader(br)
            }
        }
    }

    var buffer bytes.Buffer
    doc, err := html.Parse(r)
    if err != nil {
        return "", err
    }
    walk(doc, &buffer)
    return buffer.String(), nil
}
func walk(node *html.Node, buff *bytes.Buffer) {
    if node.Type == html.TextNode {
        data := strings.Trim(node.Data, "\r\n ")
        if data != "" {
            buff.WriteString("\n")
            buff.WriteString(data)
        }
    }
    for c := node.FirstChild; c != nil; c = c.NextSibling {
        switch strings.ToLower(node.Data) {
        case "script""style""title":
            continue
        }
        walk(c, buff)
    }
}
func main() {
    resp, err := http.Get("http://www.itmedia.co.jp/news/articles/1710/26/news006.html")
    if err != nil {
        log.Fatal(err)
    }
    defer resp.Body.Close()

    s, err := text(resp)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(s)
}
みんなのGo言語[現場で使える実践テクニック] みんなのGo言語[現場で使える実践テクニック]
松木雅幸, mattn, 藤原俊一郎, 中島大一, 牧大輔, 鈴木健太
技術評論社 / (2016-09-09)
 
発送可能時間:


2017/11/11


これまでの Go の archive/zip には問題があり、zip ファイルに格納されるファイルのタイムゾーンが UTC になっていた為、日本であれば9時間ずれてしまうという問題があった。

これは zip の NTFS/UNIX/ExtendedTS extra fields を扱っていなかったのが問題。以前一度僕のパッチがマージされたが問題がありリバートされてしまっていた。

archive/zip: add FileHeader.Modified field - golang/go@6e8894d - GitHub

The ModifiedTime and ModifiedDate fields are not expressive enough for many of the time extensions t...

https://github.com/golang/go/commit/6e8894d5ffca9acc635e0d7298167122ed52ce55

この修正により、Reader は NTFS/UNIX/ExtendedTS extra fields を見る様になったので、圧縮した際のファイルのタイムスタンプが正しく扱えるようになった。Modified という属性が FileHeader に追加された。Reader は時刻拡張がある場合には ModTime と Modified が同じ物になる。Writer については Modified に自分のタイムゾーンの時刻を設定する必要がある。

header.Modified = header.Modified.In(time.Local)

もう1点、archive/zip には日本語のファイル名が正しく扱えないという問題があった。

archive/zip: add FileHeader.NonUTF8 field - golang/go@4fcc835 - GitHub

The NonUTF8 field provides users with a way to explictly tell the ZIP writer to avoid setting the UT...

https://github.com/golang/go/commit/4fcc835971ad63cf913ebe074ef6191e35a44ab9

以前僕が Golang の文字列は UTF-8 である事から archive/zip のヘッダに UTF-8 ビット(11)を立てる修正を入れたけど、このビットを立てるかどうかを決定できる NonUTF8 というフラグが追加された。この修正により、Reader は FileHeader の NonUTF8 フラグを参照し、ファイル名が UTF-8 かそうでないかを判断できる。zip のフォーマットにはファイル名が何で書かれているかは書かれていないので、そこはさすがに解凍する方が Shift_JIS 等に変換する必要がある。また Writer もこのフラグを立て、ファイル名を Shift_JIS に変換して書く事で Shift_JIS しか対応していない zip アーカイバでも読む事の出来る zip ファイルを作れる様になった。これでずいぶん長いあいだ解決してこなかった Go の archive/zip の問題は一通り修正された事になる。めでたい。