Goの"erning/gorun"パッケージの紹介。

#"erning/gorun"パッケージって？
Github: https://github.com/erning/gorun
用途は非常にシンプルで、「GoのソースコードをPythonやRubyみたいに実行ファイルとして動かす。」と言う感じです。まさにアイディアの勝利という感じですね、Zhang Erning氏、素晴らしいと思います。

```go:hello.go
#!/usr/bin/env gorun //goにgorunのシェバングを書けば完成。

package main

func main() {
    println("Hello world!")
}
```

```bash
$ chmod +x hello.go
$ ./hello.go #「go run hello.go」じゃなくて実行しているだけ！！
Hello world!
```

このcloudflare社の記事で一躍有名になりましたね。
[Using Go as a scripting language in Linux](https://blog.cloudflare.com/using-go-as-a-scripting-language-in-linux/)

さてみなさん、紹介なんてのはもう終わってしまうレベルにシンプルなパッケージだったわけですが、それだけじゃつまらないですよね。
「このパッケージって内部でどう言う動きをして実現しているの？」「ただ本体のgo run にフックしているだけなのか？」など動作が気になりませんか？そうでも無い？😢

いやそうは言わずに見てみましょうや😆ってこと残りはソースコードリーディングしていきます。

#内部の動き
ソースコード:https://github.com/erning/gorun/blob/master/gorun.go
main関数から順に見て行きます。`gorun`はシェバングを1行目に突っ込んでるわけですから、実行ファイルとして実行したとしても内包されている動作としては`gorun hello.go`となんら変わりません。
ソースを見ても、ただ第一引数を`Run`関数に代入しているだけです。

```go
func main() {
	args := os.Args[1:]
...
	err := Run(args)
...
}
```

続いて`Run`関数ではまず引数のソースファイルから実行用のバイナリを置くためのディレクトリもしくはテンポラリファイルを`RunFile`関数を使って作成しています。

```go
func Run(args []string) error {
	sourcefile := args[0]
	rundir, runfile, err := RunFile(sourcefile)
...
}

func RunFile(sourcefile string) (rundir, runfile string, err error) {
	rundir, err = RunDir()
	if err != nil {
		return "", "", err
	}
	sourcefile, err = filepath.Abs(sourcefile)
	if err != nil {
		return "", "", err
	}
	sourcefile, err = filepath.EvalSymlinks(sourcefile)
	if err != nil {
		return "", "", err
	}
	runfile = strings.Replace(sourcefile, "%", "%%", -1)
	runfile = strings.Replace(runfile, string(filepath.Separator), "%", -1)
	runfile = filepath.Join(rundir, runfile)
	runfile += ".gorun"
	return rundir, runfile, nil
}

```
その後ファイルがあるかどうか見たり、時間で差分見て変化無いか見たりなんやかんやしてエラー分岐した後に問題なければ`Compile`関数に突っ込んで行きます。

```go
func Run(args []string) error {
...
	err := Compile(sourcefile, runfile)
...
}
```
ここが動きの本丸となっています。
見ればわかる通り、マジで「`pid`でフックして`go build`でコンパイルして実行」ですね。

```go
func Compile(sourcefile, runfile string) (err error) {
	pid := strconv.Itoa(os.Getpid())

	content, err := ioutil.ReadFile(sourcefile)
	if len(content) > 2 && content[0] == '#' && content[1] == '!' {
		content[0] = '/'
		content[1] = '/'
		sourcefile = runfile + "." + pid + ".go"
		ioutil.WriteFile(sourcefile, content, 0600)
		defer os.Remove(sourcefile)
	}

	gotool := filepath.Join(runtime.GOROOT(), "bin", "go")
	if _, err := os.Stat(gotool); err != nil {
		if gotool, err = exec.LookPath("go"); err != nil {
			return errors.New("can't find go tool")
		}
	}

	out := runfile + "." + pid
	err = Exec([]string{gotool, "build", "-o", out, sourcefile})
	if err != nil {
		return err
	}
	return os.Rename(out, runfile)

func Exec(args []string) error {
	cmd := exec.Command(args[0], args[1:]...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err := cmd.Run()
	base := filepath.Base(args[0])
	if err != nil {
		return errors.New("failed to run " + base + ": " + err.Error())
	}
	return nil
}
```
そんなこんな、このパッケージ自体は「Go言語で`go run`の動きをシェル上でオンデマンドに再現している」という動きになっているのでした。

#どういったところで使っていけば良いか。
人それぞれだとは思いますが・・・・。

- シェルスクリプトの代わりGoで書く時に、ビルド忘れを気にせず使いたい時。

- 自作playground環境とか作る際に。

- Goでメタプログラミングの生成ソースとして（多分これが本命）。

そんな所でしょうか。便利なので使って行きましょう。