2015/01/13


今まで golang で変数名や関数名のリネームには gofmt の -r オプションを使ってきましたが、これからは gorename を使いましょう。

文法を解析して正しくリネームしてくれるので、gofmt で起き得た誤爆も心配ありません。インストールは以下の様に実行します。

$ go get golang.org/x/tools/cmd/gorename

使用方法は以下の通り。

gorename: precise type-safe renaming of identifiers in Go source code.

Usage:

 gorename (-from <spec> | -offset <file>:#<byte-offset>) -to <name> [-force]

You must specify the object (named entity) to rename using the -offset
or -from flag.  Exactly one must be specified.

Flags:

-offset    specifies the filename and byte offset of an identifier to rename.
           This form is intended for use by text editors.

-from      specifies the object to rename using a query notation;
           This form is intended for interactive use at the command line.
           A legal -from query has one of the following forms:

  "encoding/json".Decoder.Decode        method of package-level named type
  (*"encoding/json".Decoder).Decode     ditto, alternative syntax
  "encoding/json".Decoder.buf           field of package-level named struct type
  "encoding/json".HTMLEscape            package member (const, func, var, type)
  "encoding/json".Decoder.Decode::x     local object x within a method
  "encoding/json".HTMLEscape::x         local object x within a function
  "encoding/json"::x                    object x anywhere within a package
  json.go::x                            object x within file json.go

           For methods, the parens and '*' on the receiver type are both
           optional.

           Double-quotes may be omitted for single-segment import paths
           such as fmt.  They may need to be escaped when writing a
           shell command.

           It is an error if one of the ::x queries matches multiple
           objects.

-to        the new name.

-force     causes the renaming to proceed even if conflicts were reported.
           The resulting program may be ill-formed, or experience a change
           in behaviour.

           WARNING: this flag may even cause the renaming tool to crash.
           (In due course this bug will be fixed by moving certain
           analyses into the type-checker.)

-dryrun    causes the tool to report conflicts but not update any files.

-v         enables verbose logging.

gorename automatically computes the set of packages that might be
affected.  For a local renaming, this is just the package specified by
-from or -offset, but for a potentially exported name, gorename scans
the workspace ($GOROOT and $GOPATH).

gorename rejects renamings of concrete methods that would change the
assignability relation between types and interfaces.  If the interface
change was intentional, initiate the renaming at the interface method.

gorename rejects any renaming that would create a conflict at the point
of declaration, or a reference conflict (ambiguity or shadowing), or
anything else that could cause the resulting program not to compile.


Examples:

% gorename -offset file.go:#123 -to foo

  Rename the object whose identifier is at byte offset 123 within file file.go.

% gorename -from '"bytes".Buffer.Len' -to Size

  Rename the "Len" method of the *bytes.Buffer type to "Size".

---- TODO ----

Correctness:
- handle dot imports correctly
- document limitations (reflection, 'implements' algorithm).
- sketch a proof of exhaustiveness.

Features:
- support running on packages specified as *.go files on the command line
- support running on programs containing errors (loader.Config.AllowErrors)
- allow users to specify a scope other than "global" (to avoid being
  stuck by neglected packages in $GOPATH that don't build).
- support renaming the package clause (no object)
- support renaming an import path (no ident or object)
  (requires filesystem + SCM updates).
- detect and reject edits to autogenerated files (cgo, protobufs)
  and optionally $GOROOT packages.
- report all conflicts, or at least all qualitatively distinct ones.
  Sometimes we stop to avoid redundancy, but
  it may give a disproportionate sense of safety in -force mode.
- support renaming all instances of a pattern, e.g.
  all receiver vars of a given type,
  all local variables of a given type,
  all PkgNames for a given package.
- emit JSON output for other editors and tools.

go-runewidth で試してみます。

https://github.com/mattn/go-runewidth/blob/master/runewidth.go#L192-L199

runewidth パッケージの、IsAmbiguousWidth という関数の中にある、iv という変数名を ヒノノニトン にリネームしたいと思います。

$ gorename -from '"github.com/mattn/go-runewidth".IsAmbiguousWidth::iv' -to ヒノノニトン

Windows だと以下の様に実行します。

gorename -from """github.com/mattn/go-runewidth""".IsAmbiguousWidth::iv -to ヒノノニトン

git diff を見ると

diff --git a/runewidth.go b/runewidth.go
index 1050395..5f2c1b0 100644
--- a/runewidth.go
+++ b/runewidth.go
@@ -190,8 +190,8 @@ func RuneWidth(r rune) int {
 
 // IsAmbiguousWidth returns whether is ambiguous width or not.
 func IsAmbiguousWidth(r rune) bool {
-   for _, iv := range ambiguous {
-       if iv.first <= r && r <= iv.last {
+   for _, ヒノノニトン := range ambiguous {
+       if ヒノノニトン.first <= r && r <= ヒノノニトン.last {
            return true
        }
    }

正しくリネームされているのが分かります。この他にも、構造体のメンバ名、関数名、メソッド名、ローカル変数名を置換出来るだけでなく、ファイル名を指定し、かつオフセット位置を指定して、「ここにある何か」という指定方法も出来る為、今後 IDE との連携により一層便利になるのではないかと思います。


2014/12/25


golang には go build というビルド機能があり、C言語と golang をまぜた cgo というC言語拡張も同じコマンドでビルド出来ます。

その際、ソースコードのコメントに CFLAGS や LDFLAGS を自ら指定する事が出来るので

package gtk

// #include "gtk.go.h"
// #cgo pkg-config: gtk+-2.0
import "C"
import (
    "fmt"
    "log"
    "reflect"
    "runtime"
    "strings"
    "unsafe"

    "github.com/mattn/go-gtk/gdk"
    "github.com/mattn/go-gtk/gdkpixbuf"
    "github.com/mattn/go-gtk/glib"
    "github.com/mattn/go-gtk/pango"
)

上記は go-gtk のコードの一部。

そのライブラリやアプリケーションをビルドしたいユーザは特にライブラリへのパスを指定する事なく、ただ単に

go build

と実行するだけで実行モジュールが出来上がります。この便利さに目を付けた Pietro Gagliardi さんが qo という golang で書かれたプログラムを公開してくれています。

andlabs/qo · GitHub

Another build system for C/C++, I guess? Inspired by 'go build'

https://github.com/andlabs/qo

インストールは golang がインストールされている状態であれば

go get github.com/andlabs/qo

だけです。昔 golang のリポジトリから Makefile が消え去り ビルド構成ファイルが何もないという状況を見て軽いカルチャーショックを受けたのだけど、今となってはとても心地良いし「あー、Makefile いらんかったんやー」とも思います。その心地よさを C/C++ で扱えるツールです。例えば gtk を使ったプログラムを書く場合

#include <gtk/gtk.h>

int
main(int argc, char* argv[]) {
  GtkWidget* window;
  GtkWidget* label;
  gtk_init(&argc, &argv);
  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(window), "helloworld");
  g_signal_connect(G_OBJECT(window), "delete-event", gtk_main_quit, window);
  label = gtk_label_new("Hello World!");
  gtk_container_add(GTK_CONTAINER(window), label);
  gtk_widget_show_all(window);
  gtk_main();
  return 0;
}
pkg-config というツールを使いますが、このツールから得られた CFLAGS や LDFLAGS をコマンドラインに渡すか、以下の様な Makefile が必要でした。

SRCS \
    foo.c

OBJS $(subst .c,.o,$(SRCS))

CFLAGS `pkg-config --cflags gtk+-2.0`
LIBS `pkg-config --libs gtk+-2.0`
TARGET = qo-sandbox

all : $(TARGET)

$(TARGET) : $(OBJS)
    gcc -o $@ $(OBJS) $(LIBS)

.c.o :
    gcc -c $(CFLAGS) -I. $< -o $@

clean :
    rm -f *.o $(TARGET)

しかし qo を使うと Makefile は消して良く、以下の様なコメントを書くだけで良いのです。

// #qo pkg-config: gtk+-2.0
#include <gtk/gtk.h>

int
main(int argc, char* argv[]) {
  GtkWidget* window;
  GtkWidget* label;
  gtk_init(&argc, &argv);
  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(window), "helloworld");
  g_signal_connect(G_OBJECT(window), "delete-event", gtk_main_quit, window);
  label = gtk_label_new("Hello World!");
  gtk_container_add(GTK_CONTAINER(window), label);
  gtk_widget_show_all(window);
  gtk_main();
  return 0;
}

あとはおもむろに

qo

を実行するだけです。毎回フルビルドするのではなく .qoobj というフォルダにビルド済みのファイルが格納されるので更新されたファイルのみビルドされます。また CFLAGS や LDFLAGS も直接指定する事が出来ます。

// #qo CFLAGS: -I/opt/sqlite3/include
// #qo LIBS: sqlite3
さらに golang の go build と同様に、ファイル名に _windows が付いている物は windows のみ、_386 が付いている物は 386 系 CPU のみビルド対象となります。(サポートOS: windows, darwin, linux, freebsd, openbsd, netbsd, dragonfly, solaris)

リンクされる実行モジュールはディレクトリ名という割り切りなので、覚えてしまえば面倒臭さがなくなりとても心地よいです。

Makefile ほど細かな設定は出来ないですが、ちょっとした小さいプログラムを書きあげる際にとても便利なツールとなる事は間違いないと思います。



VimでURLをドメインだけに置換するコマンドを正規表現でうったら、本当に正規表現って意味不明なフォルムと思った。 - Qiita
http://qiita.com/mochizukikotaro/items/b15170dccb18d84f8cd2
:%s/\([:\/]\)\@<!\/.*$//g
なんか、とても意味不明なコマンドだ。きっと、もっとスマートなものがあるのだろう。コードゴルフで言えばトリプルボギー的な感じなのでしょうか。知らないけど。
この問題は
  • URL を扱うので / が多く、置換の区切りを / にする場合エスケープが多くなる
  • very magic でないのでエスケープがさらに増える

この2点から生まれます。1点目は

:%s#\([:/]\)\@<!/.*$##g
#
:%s,\([:/]\)\@<!/.*$,,g

, をセパレータにする事で少しは解消するかと思います。2点目は \v を付ける事でキャプチャ \(\)() と書く事が出来ます。

ただし今回の例はパターンが短いので効果は出ませんが、もう少し長いパターンだと効果が表れます。

あと、この例では Vim 独自の \zs を使う事でもう少しシンプルに書く事も出来ます。

:%s!//[^/]\+\zs/.*!!g
  • // で始まり
  • / で無い物が続き
  • \zs そこまでは置換対象から外す
  • /.* を置換対象とする(※1)

結果、/.*つまり URL のパス部分(※1)だけが空白に置き換えられるので URL のホスト名までが残ります。

もちろんちゃんとしたURLで判定する場合は、もう少し細かな条件が必要となります。