2015/06/09


golang 1.5 から、x86_64 のみですが -buildmode=c-shared というビルドオプションが足される事になりました。

これは、golang で共有ライブラリを生成する為のオプションで、例えば

package main

import (
    "C"
    "fmt"
)

var (
    c chan string
)

func init() {
    c = make(chan string)
    go func() {
        n := 1
        for {
            switch {
            case n%15 == 0:
                c <- "FizzBuzz"
            case n%3 == 0:
                c <- "Fizz"
            case n%5 == 0:
                c <- "Buzz"
            default:
                c <- fmt.Sprint(n)
            }
            n++
        }
    }()
}

//export fizzbuzz
func fizzbuzz(n int) *C.char {
    return C.CString(<-c)
}

func main() {
}

こういう golang のコードlibfizzbuzz.goがあったとして

$ go build -buildmode=c-shared -o libfizzbuzz.so libfizzbuzz.go

この様にビルドすると libfizzbuzz.so が出力されます。python を使えば

from ctypes import *
lib = CDLL("./libfizzbuzz.so")
lib.fizzbuzz.restype = c_char_p
print lib.fizzbuzz()
print lib.fizzbuzz()
print lib.fizzbuzz()
print lib.fizzbuzz()
print lib.fizzbuzz()
print lib.fizzbuzz()
1
2
Fizz
4
Buzz
Fizz

この様に関数を呼ぶ度に FizzBuzz が生成される関数も簡単に作れます。Vim には if_python という python 拡張もありますのでこれを使えば Vim プラグインの機能の一部として golang を使う事が出来る様になります。新しめの Vim であれば if_python はスレッドセーフなので

mattn/vim-go-fizzbuzz - GitHub
https://github.com/mattn/vim-go-fizzbuzz
let s:libdir = expand('<sfile>:p:h:h') . '/go'

if !filereadable(s:libdir . '/libfizzbuzz.so')
  exe "!cd " . s:libdir . " && make"
endif

function! fizzbuzz#start()
python<<EOS
import vim
import thread
import time
from ctypes import *

libfizzbuzz = CDLL(vim.eval('s:libdir') + '/libfizzbuzz.so')
libfizzbuzz.fizzbuzz.restype = c_char_p

def run():
  while True:
    vim.eval("fizzbuzz#on_fizzbuzz('%s')" % libfizzbuzz.fizzbuzz())
    time.sleep(1)

thread.start_new_thread(run, ())
EOS
endfunction

function! fizzbuzz#on_fizzbuzz(s)
  if mode() != 'n'
    return
  endif
  echo a:s
endfunction

1秒に1回、golang の goroutine で非同期にループしながら channel を経由して渡される FizzBuzz を、python のスレッドを介して Vim のメッセージが表示されるという、一見誰の役にも立ちそうにない事ながら未来感が溢れてきますね。どうしても python を書きたくない、でも非同期がやりたい!golang が書きたい!という人にはとても良い時代になってきました。もうすぐ、いろんなライブラリを golang だけで書ける日がやってきそうです。

ちなみに、libcallex-vim というプラグインを使えば

let fizzbuzz = libcallex#load("/home/mattn/dev/go-sandbox/libfizzbuzz.so")
echo fizzbuzz.call("fizzbuzz", [], "string")
echo fizzbuzz.call("fizzbuzz", [], "string")
echo fizzbuzz.call("fizzbuzz", [], "string")
echo fizzbuzz.call("fizzbuzz", [], "string")
echo fizzbuzz.call("fizzbuzz", [], "string")
echo fizzbuzz.call("fizzbuzz", [], "string")
if_python も使わなくても(ただしCライブラリのビルドが必要) FizzBuzz 出来ますね。
Posted at by



2015/05/27


willnet/gimei - GitHub

gimei は、日本人の名前や、日本の住所をランダムに返すライブラリです。テストの時などに使います。似たようなライブラリにfakerがあります。fakerはとても優れたライブラリで、多言語対応もしていますが、ふりがな(フリガナ)は流石に対応していません。gimei ふりがな(及びフリガナ)に対応しています。

https://github.com/willnet/gimei

オリジナルは ruby gems です。

mattn/go-gimei - GitHub

golang port of gimei

https://github.com/mattn/go-gimei

作者の方に ok を貰ったのでデータも同梱しています。使い方もほぼ同じです。

package main

import (
    "fmt"

    "github.com/mattn/go-gimei"
)

func main() {
    name := gimei.NewName()
    fmt.Println(name)                  // 斎藤 陽菜
    fmt.Println(name.Kanji())          // 斎藤 陽菜
    fmt.Println(name.Hiragana())       // さいとう はるな
    fmt.Println(name.Katakana())       // サイトウ ハルナ
    fmt.Println(name.Last.Kanji())     // 斎藤
    fmt.Println(name.Last.Hiragana())  // さいとう
    fmt.Println(name.Last.Katakana())  // サイトウ
    fmt.Println(name.First.Kanji())    // 陽菜
    fmt.Println(name.First.Hiragana()) // はるな
    fmt.Println(name.First.Katakana()) // ハルナ
    fmt.Println(name.IsMale())         // false

    male := gimei.NewMale()
    fmt.Println(male)            // 小林 顕士
    fmt.Println(male.IsMale())   // true
    fmt.Println(male.IsFemale()) // false

    address := gimei.NewAddress()
    fmt.Println(address)                       // 岡山県大島郡大和村稲木町
    fmt.Println(address.Kanji())               // 岡山県大島郡大和村稲木町
    fmt.Println(address.Hiragana())            // おかやまけんおおしまぐんやまとそんいなぎちょう
    fmt.Println(address.Katakana())            // オカヤマケンオオシマグンヤマトソンイナギチョウ
    fmt.Println(address.Prefecture)            // 岡山県
    fmt.Println(address.Prefecture.Kanji())    // 岡山県
    fmt.Println(address.Prefecture.Hiragana()) // おかやまけん
    fmt.Println(address.Prefecture.Katakana()) // オカヤマケン
    fmt.Println(address.Town)                  // 大島郡大和村
    fmt.Println(address.Town.Kanji())          // 大島郡大和村
    fmt.Println(address.Town.Hiragana())       // おおしまぐんやまとそん
    fmt.Println(address.Town.Katakana())       // オオシマグンヤマトソン
    fmt.Println(address.City)                  // 稲木町
    fmt.Println(address.City.Kanji())          // 稲木町
    fmt.Println(address.City.Hiragana())       // いなぎちょう
    fmt.Println(address.City.Katakana())       // イナギチョウ

    prefecture := gimei.NewPrefecture()
    fmt.Println(prefecture) // 青森県
}

注意点としては漢字の名前に対して読みが複数ある場合もあります。例えば上記の「陽菜」だと「はな」と「はるな」の2つがあります。Stringer の実装は漢字出力にしています。テストの際にお役立て下さい。

Posted at by



2015/05/21


slack から stackoverflow を検索出来る slack-overflow の lingr 版を作ってみた。

karan/slack-overflow - GitHub
https://github.com/karan/slack-overflow
akechi/stackoverflow-lingrbot - GitHub
https://github.com/akechi/stackoverflow-lingrbot

今回はなんとなく gin で書いた。

Gin Web Framework

Low Overhead Powerful API You can add global, per-group, and per-route middlewares, thousands of nes...

https://gin-gonic.github.io/gin/

コード短いのでのっけておく。

package main

import (
    "encoding/json"
    "flag"
    "fmt"
    "net/http"
    "net/url"
    "os"
    "regexp"

    "github.com/gin-gonic/gin"
    "github.com/mattn/go-lingr"
)

type resp struct {
    Items []struct {
        Tags  []string `json:"tags"`
        Owner struct {
            Reputation   int    `json:"reputation"`
            UserID       int    `json:"user_id"`
            UserType     string `json:"user_type"`
            ProfileImage string `json:"profile_image"`
            DisplayName  string `json:"display_name"`
            Link         string `json:"link"`
        } `json:"owner"`
        IsAnswered       bool   `json:"is_answered"`
        ViewCount        int    `json:"view_count"`
        AnswerCount      int    `json:"answer_count"`
        Score            int    `json:"score"`
        LastActivityDate int    `json:"last_activity_date"`
        CreationDate     int    `json:"creation_date"`
        QuestionID       int    `json:"question_id"`
        Link             string `json:"link"`
        Title            string `json:"title"`
    } `json:"items"`
    HasMore        bool `json:"has_more"`
    QuotaMax       int  `json:"quota_max"`
    QuotaRemaining int  `json:"quota_remaining"`
}

var re = regexp.MustCompile(`^stackoverflow(?:\w+) (.+)$`)

func defaultAddr() string {
    port := os.Getenv("PORT")
    if port == "" {
        return ":80"
    }
    return ":" + port
}

var addr = flag.String("addr", defaultAddr(), "server address")

func main() {
    flag.Parse()

    r := gin.Default()

    r.GET("/"func(c *gin.Context) {
        c.String(200"")
    })
    f := func(c *gin.Context) {
        site := c.Params.ByName("site")
        if site == "" {
            site = "stackoverflow"
        }
        var status lingr.Status
        if !c.EnsureBody(&status) {
            return
        }
        urls := ""
        for _, event := range status.Events {
            message := event.Message
            if message == nil {
                continue
            }
            if !re.MatchString(message.Text) {
                continue
            }
            question := re.FindStringSubmatch(message.Text)[1]
            params := url.Values{}
            params.Add("intitle", question)
            params.Add("site", site)
            params.Add("sort""activity")
            params.Add("order""desc")
            res, err := http.Get("https://api.stackexchange.com/2.2/search?" + params.Encode())
            println("https://api.stackexchange.com/2.2/search?" + params.Encode())
            if err != nil {
                println(err.Error())
                continue
            }
            defer res.Body.Close()
            var resp resp
            if err := json.NewDecoder(res.Body).Decode(&resp); err != nil {
                println(err.Error())
                continue
            }
            for _, item := range resp.Items {
                u := item.Link
                if len(u) > 300 {
                    u = fmt.Sprintf("http://%s.com/q/%d", site, item.QuestionID)
                }
                s := fmt.Sprintf("%s\n%s\n", item.Title, u)
                println(s)
                if len(urls+s) > 1000 {
                    break
                }
                urls += s
            }
        }
        c.String(200, urls)
        return
    }
    r.POST("/", f)
    r.POST("/:site", f)
    r.Run(*addr)
}

僕がこういうのを書く時は、一度 JSON を何等かの方法で得て

JSON-to-Go: Convert JSON to Go instantly

JSON-to-Go Convert JSON to Go struct This tool instantly converts JSON into a Go type definition. Pa...

http://mholt.github.io/json-to-go/

このサイトで golang の struct に変換、クライアント処理として stackoverflow の検索処理を実装しながら最後に Web のガワを付けていく。実質20~30分程度で出来た。

困ったのが stackoverflow の日本語版にも対応したのだけど、日本語版は URL にタイトルの日本語が含まれていて平気で1000文字を超える。しかし lingr の API は1000文字までしか発言出来なくて(bot triggered アクションで分割送信なら可能)困った。どうやら URL の日本語部分は無くてもアクセス出来るのが分かったので300文字(適当)超えたらそっちを使う様に対応した。

lingr だと stackoverflow という名前で居ますので invite して「stackoverflow jquery」とか「stackoverflowja ウェブ」とか発言すると反応します。

Posted at by