<commit_before>// Package ryba is a RYe Bridge Adapater for chirp (but it sounds fishy).

package ryba

import (
	"github.com/strickyak/rye"
	"github.com/yak-labs/chirp-lang"

	. "fmt"
	R "reflect"
)

func P2T(p rye.P) chirp.T {
	var z chirp.T
	c := p.Contents()
	println(Sprintf("P2T <<< %v <<< %v", c, p))

	switch t := c.(type) {
	case []rye.P:
		n := len(t)
		tt := make([]chirp.T, n, n)
		for i, e := range t {
			tt[i] = P2T(e) // Recurse.
		}
		z = chirp.MkList(tt)

	default:
		z = chirp.MkT(c)
	}

	println(Sprintf("P2T >>> %v", z))
	return z
}

func T2P(t chirp.T) rye.P {
	// Rye C_Objects and Go reflect.Values.
	if val := t.QuickReflectValue(); val.IsValid() {
		guts := val.Interface()
		if gs, ok := guts.(rye.GetSelfer); ok {
			return gs.GetSelf()
		}
		return rye.MkValue(val)
	}

	raw := t.Raw()
	val := R.ValueOf(raw)
	switch val.Kind() {
	case R.Invalid:
		return rye.None
	case R.Bool:
		return rye.MkBool(val.Bool())
	case R.Int:
		return rye.MkInt(int64(val.Int()))
	case R.Int8:
		return rye.MkInt(int64(val.Int()))
	case R.Int16:
		return rye.MkInt(int64(val.Int()))
	case R.Int32:
		return rye.MkInt(int64(val.Int()))
	case R.Int64:
		return rye.MkInt(int64(val.Int()))
	case R.Uint:
		return rye.MkInt(int64(val.Uint()))
	case R.Uint8:
		return rye.MkInt(int64(val.Uint()))
	case R.Uint16:
		return rye.MkInt(int64(val.Uint()))
	case R.Uint32:
		return rye.MkInt(int64(val.Uint()))
	case R.Uint64:
		return rye.MkInt(int64(val.Uint()))
	case R.Uintptr:
		return rye.MkInt(int64(val.Uint()))
	case R.Float32:
		return rye.MkFloat(float64(val.Float()))
	case R.Float64:
		return rye.MkFloat(float64(val.Float()))
	case R.Complex64:
		panic("cannot complex")
	case R.Complex128:
		panic("cannot complex")
	case R.Array:
	case R.Chan:
	case R.Func:
	case R.Interface:
	case R.Map:
	case R.Ptr:
	case R.Slice:
	case R.String:
		return rye.MkStr(val.String())
	case R.Struct:
	case R.UnsafePointer:
	}

	panic(Sprintf("T2P: Bad Kind: %v", val.Kind()))
}

func cmdRyCall(fr *chirp.Frame, argv []chirp.T) chirp.T {
	fnT, argsT := chirp.Arg1v(argv)
	//fnP := T2P(fnT)
	n := len(argsT)
	argsP := make([]rye.P, n, n)
	for i, t := range argsT {
		argsP[i] = T2P(t)
	}

	if p, ok := fnT.Raw().(rye.ICallV); ok {
		z := p.CallV(argsP, nil, nil, nil)
		return P2T(z)
	}
	panic("rycall: fn not an ICallV")
}

func init() {
	if chirp.Unsafes == nil {
		chirp.Unsafes = make(map[string]chirp.Command, 333)
	}

	chirp.Unsafes["rycall"] = cmdRyCall
	// chirp.Unsafes["rysend"] = cmdRySend
}
<commit_msg>Convert for P-based to B-based Rye.<commit_after>// Package ryba is a RYe Bridge Adapater for chirp (but it sounds fishy).

package ryba

import (
	"github.com/strickyak/rye"
	"github.com/yak-labs/chirp-lang"

	. "fmt"
	R "reflect"
)

func B2T(p rye.B) chirp.T {
	var z chirp.T
	c := p.Self.Contents()
	println(Sprintf("B2T <<< %v <<< %v", c, p))

	switch t := c.(type) {
	case []rye.B:
		n := len(t)
		tt := make([]chirp.T, n, n)
		for i, e := range t {
			tt[i] = B2T(e) // Recurse.
		}
		z = chirp.MkList(tt)

	default:
		z = chirp.MkT(c)
	}

	println(Sprintf("B2T >>> %v", z))
	return z
}

func T2B(t chirp.T) rye.B {
	// Rye C_Objects and Go reflect.Values.
	if val := t.QuickReflectValue(); val.IsValid() {
		guts := val.Interface()
		if gs, ok := guts.(rye.GetSelfer); ok {
			return gs.GetSelf().B()
		}
		return rye.MkValue(val)
	}

	raw := t.Raw()
	val := R.ValueOf(raw)
	switch val.Kind() {
	case R.Invalid:
		return rye.None
	case R.Bool:
		return rye.MkBool(val.Bool())
	case R.Int:
		return rye.MkInt(int64(val.Int()))
	case R.Int8:
		return rye.MkInt(int64(val.Int()))
	case R.Int16:
		return rye.MkInt(int64(val.Int()))
	case R.Int32:
		return rye.MkInt(int64(val.Int()))
	case R.Int64:
		return rye.MkInt(int64(val.Int()))
	case R.Uint:
		return rye.MkInt(int64(val.Uint()))
	case R.Uint8:
		return rye.MkInt(int64(val.Uint()))
	case R.Uint16:
		return rye.MkInt(int64(val.Uint()))
	case R.Uint32:
		return rye.MkInt(int64(val.Uint()))
	case R.Uint64:
		return rye.MkInt(int64(val.Uint()))
	case R.Uintptr:
		return rye.MkInt(int64(val.Uint()))
	case R.Float32:
		return rye.MkFloat(float64(val.Float()))
	case R.Float64:
		return rye.MkFloat(float64(val.Float()))
	case R.Complex64:
		panic("cannot complex")
	case R.Complex128:
		panic("cannot complex")
	case R.Array:
	case R.Chan:
	case R.Func:
	case R.Interface:
	case R.Map:
	case R.Ptr:
	case R.Slice:
	case R.String:
		return rye.MkStr(val.String())
	case R.Struct:
	case R.UnsafePointer:
	}

	panic(Sprintf("T2B: Bad Kind: %v", val.Kind()))
}

func cmdRyCall(fr *chirp.Frame, argv []chirp.T) chirp.T {
	fnT, argsT := chirp.Arg1v(argv)
	//fnP := T2P(fnT)
	n := len(argsT)
	argsP := make([]rye.B, n, n)
	for i, t := range argsT {
		argsP[i] = T2B(t)
	}

	if p, ok := fnT.Raw().(rye.ICallV); ok {
		z := p.CallV(argsP, nil, nil, nil)
		return B2T(z)
	}
	panic("rycall: fn not an ICallV")
}

func init() {
	if chirp.Unsafes == nil {
		chirp.Unsafes = make(map[string]chirp.Command, 333)
	}

	chirp.Unsafes["rycall"] = cmdRyCall
	// chirp.Unsafes["rysend"] = cmdRySend
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"reflect"
	"testing"
	"text/template"
)

// node represents a file tree or a VCS repo
type node struct {
	path    string  // file name or commit type
	body    string  // file contents or commit tag
	entries []*node // nil if the entry is a file
}

var (
	pkgtpl = template.Must(template.New("package").Parse(`
package {{.Name}}
import ({{range .Imports}}{{printf "%q" .}}; {{end}})
`))
)

func pkg(name string, pkg ...string) string {
	v := struct {
		Name    string
		Imports []string
	}{name, pkg}
	var buf bytes.Buffer
	err := pkgtpl.Execute(&buf, v)
	if err != nil {
		panic(err)
	}
	return buf.String()
}

func TestSave(t *testing.T) {
	var cases = []struct {
		cwd   string
		args  []string
		start []*node
		want  []*node
		wdep  Godeps
	}{
		{ // simple case, one dependency
			cwd: "C",
			start: []*node{
				{
					"C",
					"",
					[]*node{
						{"main.go", pkg("main", "D"), nil},
						{"+git", "", nil},
					},
				},
				{
					"D",
					"",
					[]*node{
						{"main.go", pkg("D"), nil},
						{"+git", "D1", nil},
					},
				},
			},
			want: []*node{
				{"C/main.go", pkg("main", "D"), nil},
				{"C/Godeps/_workspace/src/D/main.go", pkg("D"), nil},
			},
			wdep: Godeps{
				ImportPath: "C",
				Deps: []Dependency{
					{ImportPath: "D", Comment: "D1"},
				},
			},
		},
		{
			// dependency in same repo with existing manifest
			// see bug https://github.com/tools/godep/issues/69
			cwd:  "P",
			args: []string{"./..."},
			start: []*node{
				{
					"P",
					"",
					[]*node{
						{"main.go", pkg("P", "P/Q"), nil},
						{"Q/main.go", pkg("Q"), nil},
						{"Godeps/Godeps.json", `{}`, nil},
						{"+git", "C1", nil},
					},
				},
			},
			want: []*node{
				{"P/main.go", pkg("P", "P/Q"), nil},
				{"P/Q/main.go", pkg("Q"), nil},
			},
			wdep: Godeps{
				ImportPath: "P",
				Deps:       []Dependency{},
			},
		},
		{
			// dependency on parent directory in same repo
			// see bug https://github.com/tools/godep/issues/70
			cwd:  "P",
			args: []string{"./..."},
			start: []*node{
				{
					"P",
					"",
					[]*node{
						{"main.go", pkg("P"), nil},
						{"Q/main.go", pkg("Q", "P"), nil},
						{"+git", "C1", nil},
					},
				},
			},
			want: []*node{
				{"P/main.go", pkg("P"), nil},
				{"P/Q/main.go", pkg("Q", "P"), nil},
			},
			wdep: Godeps{
				ImportPath: "P",
				Deps:       []Dependency{},
			},
		},
		{ // transitive dependency
			cwd: "C",
			start: []*node{
				{
					"C",
					"",
					[]*node{
						{"main.go", pkg("main", "D"), nil},
						{"+git", "", nil},
					},
				},
				{
					"D",
					"",
					[]*node{
						{"main.go", pkg("D", "T"), nil},
						{"+git", "D1", nil},
					},
				},
				{
					"T",
					"",
					[]*node{
						{"main.go", pkg("T"), nil},
						{"+git", "T1", nil},
					},
				},
			},
			want: []*node{
				{"C/main.go", pkg("main", "D"), nil},
				{"C/Godeps/_workspace/src/D/main.go", pkg("D", "T"), nil},
				{"C/Godeps/_workspace/src/T/main.go", pkg("T"), nil},
			},
			wdep: Godeps{
				ImportPath: "C",
				Deps: []Dependency{
					{ImportPath: "D", Comment: "D1"},
					{ImportPath: "T", Comment: "T1"},
				},
			},
		},
		{ // two packages, one in a subdirectory
			cwd: "C",
			start: []*node{
				{
					"C",
					"",
					[]*node{
						{"main.go", pkg("main", "D", "D/P"), nil},
						{"+git", "", nil},
					},
				},
				{
					"D",
					"",
					[]*node{
						{"main.go", pkg("D"), nil},
						{"P/main.go", pkg("P"), nil},
						{"+git", "D1", nil},
					},
				},
			},
			want: []*node{
				{"C/main.go", pkg("main", "D", "D/P"), nil},
				{"C/Godeps/_workspace/src/D/main.go", pkg("D"), nil},
				{"C/Godeps/_workspace/src/D/P/main.go", pkg("P"), nil},
			},
			wdep: Godeps{
				ImportPath: "C",
				Deps: []Dependency{
					{ImportPath: "D", Comment: "D1"},
				},
			},
		},
		{ // repo root is not a package (no go files)
			cwd: "C",
			start: []*node{
				{
					"C",
					"",
					[]*node{
						{"main.go", pkg("main", "D/P", "D/Q"), nil},
						{"+git", "", nil},
					},
				},
				{
					"D",
					"",
					[]*node{
						{"P/main.go", pkg("P"), nil},
						{"Q/main.go", pkg("Q"), nil},
						{"+git", "D1", nil},
					},
				},
			},
			want: []*node{
				{"C/main.go", pkg("main", "D/P", "D/Q"), nil},
				{"C/Godeps/_workspace/src/D/P/main.go", pkg("P"), nil},
				{"C/Godeps/_workspace/src/D/Q/main.go", pkg("Q"), nil},
			},
			wdep: Godeps{
				ImportPath: "C",
				Deps: []Dependency{
					{ImportPath: "D/P", Comment: "D1"},
					{ImportPath: "D/Q", Comment: "D1"},
				},
			},
		},
	}

	wd, err := os.Getwd()
	if err != nil {
		t.Fatal(err)
	}
	const gopath = "godeptest"
	defer os.RemoveAll(gopath)
	for _, test := range cases {
		err = os.RemoveAll(gopath)
		if err != nil {
			t.Fatal(err)
		}
		src := filepath.Join(gopath, "src")
		makeTree(t, &node{src, "", test.start})

		dir := filepath.Join(wd, src, test.cwd)
		err = os.Chdir(dir)
		if err != nil {
			panic(err)
		}
		err = os.Setenv("GOPATH", filepath.Join(wd, gopath))
		if err != nil {
			panic(err)
		}
		err = save(test.args)
		if err != nil {
			t.Error("save:", err)
			continue
		}
		err = os.Chdir(wd)
		if err != nil {
			panic(err)
		}

		checkTree(t, &node{src, "", test.want})

		f, err := os.Open(filepath.Join(dir, "Godeps/Godeps.json"))
		if err != nil {
			t.Error(err)
		}
		g := new(Godeps)
		err = json.NewDecoder(f).Decode(g)
		if err != nil {
			t.Error(err)
		}
		f.Close()

		if g.ImportPath != test.wdep.ImportPath {
			t.Errorf("ImportPath = %s want %s", g.ImportPath, test.wdep.ImportPath)
		}
		for i := range g.Deps {
			g.Deps[i].Rev = ""
		}
		if !reflect.DeepEqual(g.Deps, test.wdep.Deps) {
			t.Errorf("Deps = %v want %v", g.Deps, test.wdep.Deps)
		}
	}
}

func makeTree(t *testing.T, tree *node) (gopath string) {
	walkTree(tree, tree.path, func(path string, n *node) {
		switch {
		case n.path == "+git":
			dir := filepath.Dir(path)
			run(t, dir, "git", "init") // repo might already exist, but ok
			run(t, dir, "git", "add", ".")
			run(t, dir, "git", "commit", "-m", "godep")
			if n.body != "" {
				run(t, dir, "git", "tag", n.body)
			}
		case n.entries == nil:
			os.MkdirAll(filepath.Dir(path), 0770)
			err := ioutil.WriteFile(path, []byte(n.body), 0660)
			if err != nil {
				t.Errorf("makeTree: %v", err)
			}
		default:
			os.MkdirAll(path, 0770)
		}
	})
	return gopath
}

func checkTree(t *testing.T, want *node) {
	walkTree(want, want.path, func(path string, n *node) {
		switch {
		case n.path == "+git":
			panic("is this real life")
		case n.entries == nil:
			body, err := ioutil.ReadFile(path)
			if err != nil {
				t.Errorf("checkTree: %v", err)
				return
			}
			got := string(body)
			if got != n.body {
				t.Errorf("%s = %s want %s", path, got, n.body)
			}
		default:
			os.MkdirAll(path, 0770)
		}
	})
}

func walkTree(n *node, path string, f func(path string, n *node)) {
	f(path, n)
	for _, e := range n.entries {
		walkTree(e, filepath.Join(path, filepath.FromSlash(e.path)), f)
	}
}

func run(t *testing.T, dir, name string, args ...string) {
	cmd := exec.Command(name, args...)
	cmd.Dir = dir
	cmd.Stderr = os.Stderr
	err := cmd.Run()
	if err != nil {
		t.Fatal(err)
	}
}
<commit_msg>test saving symlinks<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"reflect"
	"strings"
	"testing"
	"text/template"
)

// node represents a file tree or a VCS repo
type node struct {
	path    string  // file name or commit type
	body    string  // file contents or commit tag
	entries []*node // nil if the entry is a file
}

var (
	pkgtpl = template.Must(template.New("package").Parse(`
package {{.Name}}
import ({{range .Imports}}{{printf "%q" .}}; {{end}})
`))
)

func pkg(name string, pkg ...string) string {
	v := struct {
		Name    string
		Imports []string
	}{name, pkg}
	var buf bytes.Buffer
	err := pkgtpl.Execute(&buf, v)
	if err != nil {
		panic(err)
	}
	return buf.String()
}

func TestSave(t *testing.T) {
	var cases = []struct {
		cwd   string
		args  []string
		start []*node
		want  []*node
		wdep  Godeps
	}{
		{ // simple case, one dependency
			cwd: "C",
			start: []*node{
				{
					"C",
					"",
					[]*node{
						{"main.go", pkg("main", "D"), nil},
						{"+git", "", nil},
					},
				},
				{
					"D",
					"",
					[]*node{
						{"main.go", pkg("D"), nil},
						{"+git", "D1", nil},
					},
				},
			},
			want: []*node{
				{"C/main.go", pkg("main", "D"), nil},
				{"C/Godeps/_workspace/src/D/main.go", pkg("D"), nil},
			},
			wdep: Godeps{
				ImportPath: "C",
				Deps: []Dependency{
					{ImportPath: "D", Comment: "D1"},
				},
			},
		},
		{
			// dependency in same repo with existing manifest
			// see bug https://github.com/tools/godep/issues/69
			cwd:  "P",
			args: []string{"./..."},
			start: []*node{
				{
					"P",
					"",
					[]*node{
						{"main.go", pkg("P", "P/Q"), nil},
						{"Q/main.go", pkg("Q"), nil},
						{"Godeps/Godeps.json", `{}`, nil},
						{"+git", "C1", nil},
					},
				},
			},
			want: []*node{
				{"P/main.go", pkg("P", "P/Q"), nil},
				{"P/Q/main.go", pkg("Q"), nil},
			},
			wdep: Godeps{
				ImportPath: "P",
				Deps:       []Dependency{},
			},
		},
		{
			// dependency on parent directory in same repo
			// see bug https://github.com/tools/godep/issues/70
			cwd:  "P",
			args: []string{"./..."},
			start: []*node{
				{
					"P",
					"",
					[]*node{
						{"main.go", pkg("P"), nil},
						{"Q/main.go", pkg("Q", "P"), nil},
						{"+git", "C1", nil},
					},
				},
			},
			want: []*node{
				{"P/main.go", pkg("P"), nil},
				{"P/Q/main.go", pkg("Q", "P"), nil},
			},
			wdep: Godeps{
				ImportPath: "P",
				Deps:       []Dependency{},
			},
		},
		{ // transitive dependency
			cwd: "C",
			start: []*node{
				{
					"C",
					"",
					[]*node{
						{"main.go", pkg("main", "D"), nil},
						{"+git", "", nil},
					},
				},
				{
					"D",
					"",
					[]*node{
						{"main.go", pkg("D", "T"), nil},
						{"+git", "D1", nil},
					},
				},
				{
					"T",
					"",
					[]*node{
						{"main.go", pkg("T"), nil},
						{"+git", "T1", nil},
					},
				},
			},
			want: []*node{
				{"C/main.go", pkg("main", "D"), nil},
				{"C/Godeps/_workspace/src/D/main.go", pkg("D", "T"), nil},
				{"C/Godeps/_workspace/src/T/main.go", pkg("T"), nil},
			},
			wdep: Godeps{
				ImportPath: "C",
				Deps: []Dependency{
					{ImportPath: "D", Comment: "D1"},
					{ImportPath: "T", Comment: "T1"},
				},
			},
		},
		{ // two packages, one in a subdirectory
			cwd: "C",
			start: []*node{
				{
					"C",
					"",
					[]*node{
						{"main.go", pkg("main", "D", "D/P"), nil},
						{"+git", "", nil},
					},
				},
				{
					"D",
					"",
					[]*node{
						{"main.go", pkg("D"), nil},
						{"P/main.go", pkg("P"), nil},
						{"+git", "D1", nil},
					},
				},
			},
			want: []*node{
				{"C/main.go", pkg("main", "D", "D/P"), nil},
				{"C/Godeps/_workspace/src/D/main.go", pkg("D"), nil},
				{"C/Godeps/_workspace/src/D/P/main.go", pkg("P"), nil},
			},
			wdep: Godeps{
				ImportPath: "C",
				Deps: []Dependency{
					{ImportPath: "D", Comment: "D1"},
				},
			},
		},
		{ // repo root is not a package (no go files)
			cwd: "C",
			start: []*node{
				{
					"C",
					"",
					[]*node{
						{"main.go", pkg("main", "D/P", "D/Q"), nil},
						{"+git", "", nil},
					},
				},
				{
					"D",
					"",
					[]*node{
						{"P/main.go", pkg("P"), nil},
						{"Q/main.go", pkg("Q"), nil},
						{"+git", "D1", nil},
					},
				},
			},
			want: []*node{
				{"C/main.go", pkg("main", "D/P", "D/Q"), nil},
				{"C/Godeps/_workspace/src/D/P/main.go", pkg("P"), nil},
				{"C/Godeps/_workspace/src/D/Q/main.go", pkg("Q"), nil},
			},
			wdep: Godeps{
				ImportPath: "C",
				Deps: []Dependency{
					{ImportPath: "D/P", Comment: "D1"},
					{ImportPath: "D/Q", Comment: "D1"},
				},
			},
		},
		{ // symlink
			cwd: "C",
			start: []*node{
				{
					"C",
					"",
					[]*node{
						{"main.x", pkg("main", "D"), nil},
						{"main.go", "symlink:main.x", nil},
						{"+git", "", nil},
					},
				},
				{
					"D",
					"",
					[]*node{
						{"main.go", pkg("D"), nil},
						{"+git", "D1", nil},
					},
				},
			},
			want: []*node{
				{"C/main.go", pkg("main", "D"), nil},
				{"C/Godeps/_workspace/src/D/main.go", pkg("D"), nil},
			},
			wdep: Godeps{
				ImportPath: "C",
				Deps: []Dependency{
					{ImportPath: "D", Comment: "D1"},
				},
			},
		},
	}

	wd, err := os.Getwd()
	if err != nil {
		t.Fatal(err)
	}
	const gopath = "godeptest"
	defer os.RemoveAll(gopath)
	for _, test := range cases {
		err = os.RemoveAll(gopath)
		if err != nil {
			t.Fatal(err)
		}
		src := filepath.Join(gopath, "src")
		makeTree(t, &node{src, "", test.start})

		dir := filepath.Join(wd, src, test.cwd)
		err = os.Chdir(dir)
		if err != nil {
			panic(err)
		}
		err = os.Setenv("GOPATH", filepath.Join(wd, gopath))
		if err != nil {
			panic(err)
		}
		err = save(test.args)
		if err != nil {
			t.Error("save:", err)
			continue
		}
		err = os.Chdir(wd)
		if err != nil {
			panic(err)
		}

		checkTree(t, &node{src, "", test.want})

		f, err := os.Open(filepath.Join(dir, "Godeps/Godeps.json"))
		if err != nil {
			t.Error(err)
		}
		g := new(Godeps)
		err = json.NewDecoder(f).Decode(g)
		if err != nil {
			t.Error(err)
		}
		f.Close()

		if g.ImportPath != test.wdep.ImportPath {
			t.Errorf("ImportPath = %s want %s", g.ImportPath, test.wdep.ImportPath)
		}
		for i := range g.Deps {
			g.Deps[i].Rev = ""
		}
		if !reflect.DeepEqual(g.Deps, test.wdep.Deps) {
			t.Errorf("Deps = %v want %v", g.Deps, test.wdep.Deps)
		}
	}
}

func makeTree(t *testing.T, tree *node) (gopath string) {
	walkTree(tree, tree.path, func(path string, n *node) {
		switch {
		case n.path == "+git":
			dir := filepath.Dir(path)
			run(t, dir, "git", "init") // repo might already exist, but ok
			run(t, dir, "git", "add", ".")
			run(t, dir, "git", "commit", "-m", "godep")
			if n.body != "" {
				run(t, dir, "git", "tag", n.body)
			}
		case n.entries == nil && strings.HasPrefix(n.body, "symlink:"):
			target := strings.TrimPrefix(n.body, "symlink:")
			os.Symlink(target, path)
		case n.entries == nil:
			os.MkdirAll(filepath.Dir(path), 0770)
			err := ioutil.WriteFile(path, []byte(n.body), 0660)
			if err != nil {
				t.Errorf("makeTree: %v", err)
			}
		default:
			os.MkdirAll(path, 0770)
		}
	})
	return gopath
}

func checkTree(t *testing.T, want *node) {
	walkTree(want, want.path, func(path string, n *node) {
		switch {
		case n.path == "+git":
			panic("is this real life")
		case n.entries == nil && strings.HasPrefix(n.body, "symlink:"):
			panic("why is this happening to me")
		case n.entries == nil:
			body, err := ioutil.ReadFile(path)
			if err != nil {
				t.Errorf("checkTree: %v", err)
				return
			}
			got := string(body)
			if got != n.body {
				t.Errorf("%s = %s want %s", path, got, n.body)
			}
		default:
			os.MkdirAll(path, 0770)
		}
	})
}

func walkTree(n *node, path string, f func(path string, n *node)) {
	f(path, n)
	for _, e := range n.entries {
		walkTree(e, filepath.Join(path, filepath.FromSlash(e.path)), f)
	}
}

func run(t *testing.T, dir, name string, args ...string) {
	cmd := exec.Command(name, args...)
	cmd.Dir = dir
	cmd.Stderr = os.Stderr
	err := cmd.Run()
	if err != nil {
		t.Fatal(err)
	}
}
<|endoftext|>


<commit_before>package fleet

import (
	"bytes"
	"encoding/json"
	"net/http"
)

const (
	unitsPath = "/units"
)

type Unit struct {
	Name         string        `json:"name,omitempty"`
	Options      []*UnitOption `json:"options,omitempty"`
	DesiredState string        `json:"desiredState,omitempty"`
	CurrentState string        `json:"currentState,omitempty"`
	MachineID    string        `json:"machineID,omitempty"`
}

type UnitOption struct {
	Section string `json:"section,omitempty"`
	Name    string `json:"name,omitempty"`
	Value   string `json:"value,omitempty"`
}

type unitResponse struct {
	Units []Unit `json:"units,omitempty"`
}

func (c *Client) Units() ([]Unit, error) {
	var unitsRes unitResponse

	req, err := http.NewRequest("GET", c.URL+basePath+unitsPath, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")

	httpClient := http.Client{}
	res, err := httpClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	err = json.NewDecoder(res.Body).Decode(&unitsRes)
	if err != nil {
		return nil, err
	}

	return unitsRes.Units, nil
}

func (c *Client) Submit(name string, opts []*UnitOption) error {
	unit := Unit{
		Options:     opts,
		DesireState: "inactive",
	}

	j, err := json.Marshal(unit)
	if err != nil {
		return err
	}

	req, err := http.NewRequest("PUT", c.URL+basePath+unitsPath+"/"+name, bytes.NewReader(j))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")

	httpClient := http.Client{}
	res, err := httpClient.Do(req)
	if err != nil {
		return err
	}
	defer res.Body.Close()

	if res.StatusCode == 201 {
		return nil
	}

	return nil
}

func (c *Client) Load(name string)    {}
func (c *Client) Start(name string)   {}
func (c *Client) Stop(name string)    {}
func (c *Client) Unload(name string)  {}
func (c *Client) Destroy(name string) {}
<commit_msg>Add support for loading an unit<commit_after>package fleet

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
)

const (
	unitsPath = "/units"
)

type Unit struct {
	Name         string        `json:"name,omitempty"`
	Options      []*UnitOption `json:"options,omitempty"`
	DesiredState string        `json:"desiredState,omitempty"`
	CurrentState string        `json:"currentState,omitempty"`
	MachineID    string        `json:"machineID,omitempty"`
}

type UnitOption struct {
	Section string `json:"section,omitempty"`
	Name    string `json:"name,omitempty"`
	Value   string `json:"value,omitempty"`
}

type unitResponse struct {
	Units []Unit `json:"units,omitempty"`
}

func (c *Client) Units() ([]Unit, error) {
	var unitsRes unitResponse

	req, err := http.NewRequest("GET", c.URL+basePath+unitsPath, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")

	httpClient := http.Client{}
	res, err := httpClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	err = json.NewDecoder(res.Body).Decode(&unitsRes)
	if err != nil {
		return nil, err
	}

	return unitsRes.Units, nil
}

func (c *Client) Submit(name string, opts []*UnitOption) error {
	unit := Unit{
		Options:      opts,
		DesiredState: "inactive",
	}

	j, err := json.Marshal(unit)
	if err != nil {
		return err
	}

	req, err := http.NewRequest("PUT", c.URL+basePath+unitsPath+"/"+name, bytes.NewReader(j))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")

	httpClient := http.Client{}
	res, err := httpClient.Do(req)
	if err != nil {
		return err
	}
	defer res.Body.Close()

	if res.StatusCode == 201 {
		return nil
	}

	return nil
}

func (c *Client) Load(name string) error {
	unit := Unit{
		DesiredState: "loaded",
	}

	j, err := json.Marshal(unit)
	if err != nil {
		return err
	}

	req, err := http.NewRequest("PUT", c.URL+basePath+unitsPath+"/"+name, bytes.NewReader(j))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")

	httpClient := http.Client{}
	res, err := httpClient.Do(req)
	if err != nil {
		return err
	}
	defer res.Body.Close()

	switch res.StatusCode {
	case 204:
		return nil
	case 400:
		// Attempting to modify an Unit with an invalid entity
		return errors.New("400 Bad Request")
	case 409:
		// Attempting to create an entity without options
		return errors.New("409 Conflict")
	default:
		message := fmt.Sprintf("%d Faild to load an unit", res.StatusCode)
		return errors.New(message)
	}
}

func (c *Client) Start(name string)   {}
func (c *Client) Stop(name string)    {}
func (c *Client) Unload(name string)  {}
func (c *Client) Destroy(name string) {}
<|endoftext|>


<commit_before>package builder

import (
	"bytes"
	"strings"
)

//BuildQuery - get sql string from expression
func (query *Query) BuildQuery() string {
	var buffer bytes.Buffer
	buffer.WriteString(query.TypeQuery)
	buffer.WriteString(" ")
	columns := interfaceToString(query.Columns)
	buffer.WriteString(strings.Join(columns[:], ", "))

	buffer.WriteString(" From ")
	buffer.WriteString(query.TableName)

	if len(query.WhereCond) != 0 {
		buffer.WriteString(" Where ")
		for index, element := range query.WhereCond {
			if index == 0 {
				buffer.WriteString(
					strings.Replace(element.Expression, "?", convertValueToString(element.Value), "-1"),
				)
			} else {
				buffer.WriteString(element.Delimiter)
				buffer.WriteString(
					strings.Replace(expr, "?", convertValueToString(element.Expression, element.Value), "-1"),
				)
			}
		}
	}

	if len(query.GroupBy) != 0 {
		buffer.WriteString(" Group By ")
		for index, element := range query.GroupBy {
			buffer.WriteString(
				convertValueToString(element.Value),
			)
		}
	}
	buffer.WriteString(";")
	return buffer.String()
}
<commit_msg>change '-1' to -1<commit_after>package builder

import (
	"bytes"
	"strings"
)

//BuildQuery - get sql string from expression
func (query *Query) BuildQuery() string {
	var buffer bytes.Buffer
	buffer.WriteString(query.TypeQuery)
	buffer.WriteString(" ")
	columns := interfaceToString(query.Columns)
	buffer.WriteString(strings.Join(columns[:], ", "))

	buffer.WriteString(" From ")
	buffer.WriteString(query.TableName)

	if len(query.WhereCond) != 0 {
		buffer.WriteString(" Where ")
		for index, element := range query.WhereCond {
			if index == 0 {
				buffer.WriteString(
					strings.Replace(element.Expression, "?", convertValueToString(element.Value), -1),
				)
			} else {
				buffer.WriteString(element.Delimiter)
				buffer.WriteString(
					strings.Replace(expr, "?", convertValueToString(element.Expression, element.Value), -1),
				)
			}
		}
	}

	if len(query.GroupBy) != 0 {
		buffer.WriteString(" Group By ")
		for index, element := range query.GroupBy {
			buffer.WriteString(
				convertValueToString(element.Value),
			)
		}
	}
	buffer.WriteString(";")
	return buffer.String()
}
<|endoftext|>


<commit_before>package dsbldr

import (
	"encoding/csv"
	"io/ioutil"
	"net/http"
)

// Builder is main type for this tool.
type Builder struct {
	BaseURL        string
	RequestHeaders map[string]string // Custom Request Headers including auth
	featureMap     map[string]*Feature
	data           [][]string // Strings of Data to be read in to CSV
	records        int        // Number of records to be retrieved for dataset
}

// NewBuilder creates new Builder struct
func NewBuilder(featureCount, recordCount int) *Builder {
	// Add extra row for header
	preallocatedData := make([][]string, recordCount+1)
	for i := range preallocatedData {
		preallocatedData[i] = make([]string, featureCount)
	}
	return &Builder{
		RequestHeaders: make(map[string]string),
		featureMap:     make(map[string]*Feature),
		data:           preallocatedData,
		records:        recordCount,
	}
}

func (b *Builder) addDataFeature(featureName string, values []string) error {
	writeStringColumn(&b.data, featureName, values)
	return nil
}

func (b *Builder) getDataFeature(featureName string) []string {
	items := make([]string, b.records-1) // all items excluding header row
	var colIndex int

	for i := range b.data[0] {
		// Find first column with same header
		if b.data[0][i] == featureName {
			colIndex = i
			break
		}
	}

	// Add all the values as well (remember that Builder.data is pre-allocated)
	for i := 1; i < len(b.data); i++ {
		items[i-1] = b.data[i][colIndex]
	}

	return items
}

// GetFeature returns a feature in the detaset based on it's name
func (b *Builder) GetFeature(name string) *Feature {
	var feat *Feature
	if val, ok := b.featureMap[name]; ok {
		feat = val
		return feat
	}
	return nil
}

// Save commits the downloaded features to a file
func (b *Builder) Save(writer csv.Writer) error {
	for i := range b.data {
		var record []string
		for index, j := range b.data[i] {
			// if data header (feature name) has noSave == false, else don't write
			if !b.featureMap[b.data[0][index]].noSave {
				record = append(record, j)
			}
		}
		err := writer.Write(record)
		if err != nil {
			return err
		}
	}
	return nil
}

// AddFeatures adds a Feature struct to the "Features" Field on Builder
func (b *Builder) AddFeatures(features ...*Feature) {
	for _, feature := range features {
		feature.noSave = false
		b.featureMap[feature.Name] = feature
		// TODO: Return error if feature with same name has been added
	}
	// Increase size of data matrix if feature map is larger than initially allocated
	if len(b.featureMap) > len(b.data[0]) {
		for i := range b.data {
			b.data[i] = append(b.data[i], "")
		}
	}
}

func (b *Builder) createRequest(
	endpoint string,
	headers map[string]string,
) (*http.Request, error) {

	req, err := http.NewRequest("GET", b.BaseURL+endpoint, nil)
	if err != nil {
		return nil, err
	}

	for k, v := range headers {
		req.Header.Add(k, v)
	}
	return req, nil
}

// resolve endpoint templates to actual values for endpoints based on parent features
func (b *Builder) resolveFeatureEndpoints(feature *Feature) ([]string, error) {
	endpoints := make([]string, b.records)
	parents, err := feature.getParentNames()
	if err != nil {
		return nil, err
	}

	for i := 0; i < b.records; i++ {
		parentValuesMap := make(map[string]string)
		for _, j := range parents {
			parentValuesMap[j] = b.getDataFeature(j)[i]
		}
		var resolveEndpointError error
		endpoints[i], resolveEndpointError = feature.resolveEndpoint(parentValuesMap)
		if resolveEndpointError != nil {
			return nil, resolveEndpointError
		}
	}

	return endpoints, nil
}

// retrieveFeature returns string dumps of responses and an error if any
func (b *Builder) retrieveFeature(feature *Feature) ([]string, error) {
	client := &http.Client{}
	responses := make([]*http.Response, b.records)
	responseDumps := make([]string, b.records)
	endpoints, err := b.resolveFeatureEndpoints(feature)
	if err != nil {
		return nil, err
	}

	for i := 0; i < b.records; i++ {
		// endpoints should be the same as the number of
		req, err := b.createRequest(endpoints[i], b.RequestHeaders)
		if err != nil {
			return nil, err
		}

		resp, err := client.Do(req)
		if err != nil {
			return nil, err
		}

		defer resp.Body.Close()

		if resp.StatusCode == 200 { // OK
			bodyBytes, _ := ioutil.ReadAll(resp.Body)
			responseDumps[i] = string(bodyBytes)
		}
	}

	return responseDumps, nil
}

// Run Builder to aggregate all features and manage concurrent operations
func (b *Builder) Run() error {
	// flag.Parse()

	// Set noSave to true for any features listed in the call
	// for _, i := range ignoredFeatures {
	// 	b.GetFeature(i).noSave = true
	// }

	for _, feature := range b.featureMap {
		parents, err := feature.getParentNames()
		if err != nil {
			return err
		}

		var retrieveError error
		go func(feature *Feature) {
			for _, i := range parents {
				// Loop through parent features and wait till
				// they finish excecuting
				<-b.GetFeature(i).finished
			}

			parsedResponses := make([]string, b.records)
			parsedResponses, retrieveError = b.retrieveFeature(feature)

			output := feature.RunFunc(parsedResponses)
			b.addDataFeature(feature.Name, output)

			feature.finished <- true // Mark feature as true
		}(feature)

		if retrieveError != nil {
			return retrieveError
		}

	}
	return nil
}
<commit_msg>Rename some Builder methods<commit_after>package dsbldr

import (
	"encoding/csv"
	"io/ioutil"
	"net/http"
)

// Builder is main type for this tool.
type Builder struct {
	BaseURL        string
	RequestHeaders map[string]string // Custom Request Headers including auth
	featureMap     map[string]*Feature
	data           [][]string // Strings of Data to be read in to CSV
	records        int        // Number of records to be retrieved for dataset
}

// NewBuilder creates new Builder struct
func NewBuilder(featureCount, recordCount int) *Builder {
	// Add extra row for header
	preallocatedData := make([][]string, recordCount+1)
	for i := range preallocatedData {
		preallocatedData[i] = make([]string, featureCount)
	}
	return &Builder{
		RequestHeaders: make(map[string]string),
		featureMap:     make(map[string]*Feature),
		data:           preallocatedData,
		records:        recordCount,
	}
}

func (b *Builder) addFeatureData(featureName string, values []string) error {
	writeStringColumn(&b.data, featureName, values)
	return nil
}

func (b *Builder) getFeatureData(featureName string) []string {
	items := make([]string, b.records-1) // all items excluding header row
	var colIndex int

	for i := range b.data[0] {
		// Find first column with same header
		if b.data[0][i] == featureName {
			colIndex = i
			break
		}
	}

	for i := 1; i < len(b.data); i++ {
		items[i-1] = b.data[i][colIndex]
	}

	return items
}

// GetFeature returns a feature in the detaset based on it's name
func (b *Builder) GetFeature(name string) *Feature {
	var feat *Feature
	if val, ok := b.featureMap[name]; ok {
		feat = val
		return feat
	}
	return feat
}

// Save commits the downloaded features to a file
func (b *Builder) Save(writer csv.Writer) error {
	for i := range b.data {
		var record []string
		for index, j := range b.data[i] {
			// if data header (feature name) has noSave == false, else don't write
			if !b.featureMap[b.data[0][index]].noSave {
				record = append(record, j)
			}
		}
		err := writer.Write(record)
		if err != nil {
			return err
		}
	}
	return nil
}

// AddFeatures adds a Feature struct to the "Features" Field on Builder
func (b *Builder) AddFeatures(features ...*Feature) {
	for _, feature := range features {
		feature.noSave = false
		b.featureMap[feature.Name] = feature
		// TODO: Return error if feature with same name has been added
	}
	// Increase size of data matrix if feature map is larger than initially allocated
	if len(b.featureMap) > len(b.data[0]) {
		for i := range b.data {
			b.data[i] = append(b.data[i], "")
		}
	}
}

func (b *Builder) createRequest(
	endpoint string,
) (*http.Request, error) {
	headers := b.RequestHeaders

	req, err := http.NewRequest("GET", b.BaseURL+endpoint, nil)
	if err != nil {
		return nil, err
	}

	for k, v := range headers {
		req.Header.Add(k, v)
	}
	return req, nil
}

// resolve endpoint templates to actual values for endpoints based on parent features
func (b *Builder) resolveFeatureEndpoints(feature *Feature) ([]string, error) {
	endpoints := make([]string, b.records)
	parents, err := feature.getParentNames()
	if err != nil {
		return nil, err
	}

	for i := 0; i < b.records; i++ {
		parentValuesMap := make(map[string]string)
		for _, j := range parents {
			parentValuesMap[j] = b.getFeatureData(j)[i]
		}
		var resolveEndpointError error
		endpoints[i], resolveEndpointError = feature.resolveEndpoint(parentValuesMap)
		if resolveEndpointError != nil {
			return nil, resolveEndpointError
		}
	}

	return endpoints, nil
}

// populateFeatureData returns string dumps of responses and an error if any
func (b *Builder) populateFeatureData(feature *Feature) ([]string, error) {
	client := &http.Client{}
	responseDumps := make([]string, b.records)
	endpoints, err := b.resolveFeatureEndpoints(feature)
	if err != nil {
		return nil, err
	}

	for i := 0; i < b.records; i++ {
		// endpoints should be the same as the number of
		req, err := b.createRequest(endpoints[i])
		if err != nil {
			return nil, err
		}

		resp, err := client.Do(req)
		if err != nil {
			return nil, err
		}

		defer resp.Body.Close()

		if resp.StatusCode == 200 { // OK
			bodyBytes, _ := ioutil.ReadAll(resp.Body)
			responseDumps[i] = string(bodyBytes)
		}
	}

	return responseDumps, nil
}

// Run Builder to aggregate all features and manage concurrent operations
func (b *Builder) Run() error {
	// flag.Parse()

	// Set noSave to true for any features listed in the call
	// for _, i := range ignoredFeatures {
	// 	b.GetFeature(i).noSave = true
	// }

	for _, feature := range b.featureMap {
		parents, err := feature.getParentNames()
		if err != nil {
			return err
		}

		var populateError error
		go func(feature *Feature) {
			for _, i := range parents {
				// Loop through parent features and wait till
				// they finish excecuting
				<-b.GetFeature(i).finished
			}

			parsedResponses := make([]string, b.records)
			parsedResponses, populateError = b.populateFeatureData(feature)

			output := feature.RunFunc(parsedResponses)
			b.addFeatureData(feature.Name, output)

			feature.finished <- true // Mark feature as true
		}(feature)

		if populateError != nil {
			return populateError
		}

	}
	return nil
}
<|endoftext|>


<commit_before>package dsbldr

import (
	"encoding/csv"
	"io/ioutil"
	"net/http"
)

// Builder is main type for this tool.
type Builder struct {
	BaseURL        string
	RequestHeaders map[string]string // Custom Request Headers including auth
	featureMap     map[string]*Feature
	data           [][]string // Strings of Data to be read in to CSV
	records        int        // Number of records to be retrieved for dataset
}

// NewBuilder creates new Builder struct
func NewBuilder(featureCount, recordCount int) *Builder {
	// Add extra row for header
	preallocatedData := make([][]string, recordCount+1)
	for i := range preallocatedData {
		preallocatedData[i] = make([]string, featureCount)
	}
	return &Builder{
		RequestHeaders: make(map[string]string),
		featureMap:     make(map[string]*Feature),
		data:           preallocatedData,
		records:        recordCount,
	}
}

func (b *Builder) addFeatureData(featureName string, values []string) error {
	writeStringColumn(b.data, featureName, values)
	return nil
}

func (b *Builder) getFeatureData(featureName string) []string {
	items := make([]string, b.records)
	if _, ok := b.featureMap[featureName]; ok {
		readStringColumn(items, featureName, b.data)
		return items
	}
	return items
}

// GetFeature returns a feature in the detaset based on it's name
func (b *Builder) GetFeature(name string) *Feature {
	var feat *Feature
	if val, ok := b.featureMap[name]; ok {
		feat = val
		return feat
	}
	return feat
}

func (b *Builder) writeRecord(writer csv.Writer, i int) error {
	var record []string
	for index, j := range b.data[i] {
		// if data header (feature name) has noSave == false, else don't write
		if !b.featureMap[b.data[0][index]].noSave {
			record = append(record, j)
		}
	}
	err := writer.Write(record)
	if err != nil {
		return err
	}
	return nil
}

// Save commits the downloaded features to a file
func (b *Builder) Save(writer csv.Writer) error {
	for i := range b.data {
		err := b.writeRecord(writer, i)
		if err != nil {
			return err
		}
	}
	return nil
}

// SaveIf daves records only if they evaluate to true
func (b *Builder) SaveIf(writer csv.Writer, saveCond func(r []string) bool) error {
	for i := range b.data {
		if saveCond(b.data[i]) {
			err := b.writeRecord(writer, i)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

// AddFeatures adds a Feature struct to the "Features" Field on Builder
func (b *Builder) AddFeatures(features ...*Feature) {
	for _, feature := range features {
		feature.noSave = false
		b.featureMap[feature.Name] = feature
		// TODO: Return error if feature with same name has been added
	}
	// Increase size of data matrix if feature map is larger than initially allocated
	if len(b.featureMap) > len(b.data[0]) {
		for i := range b.data {
			b.data[i] = append(b.data[i], "")
		}
	}
}

func (b *Builder) createRequest(
	endpoint string,
) (*http.Request, error) {
	headers := b.RequestHeaders

	req, err := http.NewRequest("GET", b.BaseURL+endpoint, nil)
	if err != nil {
		return nil, err
	}

	for k, v := range headers {
		req.Header.Add(k, v)
	}
	return req, nil
}

// resolve endpoint templates to actual values for endpoints based on parent features
func (b *Builder) resolveFeatureEndpoints(feature *Feature) ([]string, error) {
	endpoints := make([]string, b.records)
	parents, err := feature.getParentNames()
	if err != nil {
		return nil, err
	}

	for i := 0; i < b.records; i++ {
		parentValuesMap := make(map[string]string)
		for _, j := range parents {
			parentValuesMap[j] = b.getFeatureData(j)[i]
		}
		var resolveEndpointError error
		endpoints[i], resolveEndpointError = feature.resolveEndpoint(parentValuesMap)
		if resolveEndpointError != nil {
			return nil, resolveEndpointError
		}
	}

	return endpoints, nil
}

// populateFeatureData returns string dumps of responses and an error if any
func (b *Builder) populateFeatureData(feature *Feature, client *http.Client) ([]string, error) {
	responseDumps := make([]string, b.records)
	endpoints, err := b.resolveFeatureEndpoints(feature)
	if err != nil {
		return nil, err
	}

	for i := 0; i < b.records; i++ {
		// endpoints should be the same as the number of
		req, err := b.createRequest(endpoints[i])
		if err != nil {
			return nil, err
		}

		resp, err := client.Do(req)
		if err != nil {
			return nil, err
		}

		defer resp.Body.Close()

		if resp.StatusCode == 200 { // OK
			bodyBytes, _ := ioutil.ReadAll(resp.Body)
			responseDumps[i] = string(bodyBytes)
		}

	}

	return responseDumps, nil
}

// Run Builder to aggregate all features and manage concurrent operations
func (b *Builder) Run(client *http.Client) error {

	for _, feature := range b.featureMap {
		parents, err := feature.getParentNames()
		if err != nil {
			return err
		}

		var populateError error
		go func(feature *Feature) {
			// Block till parent features finish executing
			for _, i := range parents {
				<-b.GetFeature(i).finished
			}

			parsedResponses := make([]string, b.records)
			parsedResponses, populateError = b.populateFeatureData(feature, client)

			output := feature.RunFunc(parsedResponses)
			b.addFeatureData(feature.Name, output)

			feature.finished <- true // Mark feature as true
			close(feature.finished)
		}(feature)

		if populateError != nil {
			return populateError
		}
	}

	// wait for all channels to be written before exiting function
	for _, feature := range b.featureMap {
		<-feature.finished
	}

	return nil
}
<commit_msg>Add endpointClient interface to allow for mocking http.Client<commit_after>package dsbldr

import (
	"encoding/csv"
	"io/ioutil"
	"net/http"
)

// Builder is main type for this tool.
type Builder struct {
	BaseURL        string
	RequestHeaders map[string]string // Custom Request Headers including auth
	featureMap     map[string]*Feature
	data           [][]string // Strings of Data to be read in to CSV
	records        int        // Number of records to be retrieved for dataset
}

// NewBuilder creates new Builder struct
func NewBuilder(featureCount, recordCount int) *Builder {
	// Add extra row for header
	preallocatedData := make([][]string, recordCount+1)
	for i := range preallocatedData {
		preallocatedData[i] = make([]string, featureCount)
	}
	return &Builder{
		RequestHeaders: make(map[string]string),
		featureMap:     make(map[string]*Feature),
		data:           preallocatedData,
		records:        recordCount,
	}
}

func (b *Builder) addFeatureData(featureName string, values []string) error {
	writeStringColumn(b.data, featureName, values)
	return nil
}

func (b *Builder) getFeatureData(featureName string) []string {
	items := make([]string, b.records)
	if _, ok := b.featureMap[featureName]; ok {
		readStringColumn(items, featureName, b.data)
		return items
	}
	return items
}

// GetFeature returns a feature in the detaset based on it's name
func (b *Builder) GetFeature(name string) *Feature {
	var feat *Feature
	if val, ok := b.featureMap[name]; ok {
		feat = val
		return feat
	}
	return feat
}

func (b *Builder) writeRecord(writer csv.Writer, i int) error {
	var record []string
	for index, j := range b.data[i] {
		// if data header (feature name) has noSave == false, else don't write
		if !b.featureMap[b.data[0][index]].noSave {
			record = append(record, j)
		}
	}
	err := writer.Write(record)
	if err != nil {
		return err
	}
	return nil
}

// Save commits the downloaded features to a file
func (b *Builder) Save(writer csv.Writer) error {
	for i := range b.data {
		err := b.writeRecord(writer, i)
		if err != nil {
			return err
		}
	}
	return nil
}

// SaveIf saves records only if they evaluate to true
func (b *Builder) SaveIf(writer csv.Writer, saveCond func(r []string) bool) error {
	for i := range b.data {
		if saveCond(b.data[i]) {
			err := b.writeRecord(writer, i)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

// AddFeatures adds a Feature struct to the "Features" Field on Builder
func (b *Builder) AddFeatures(features ...*Feature) {
	for _, feature := range features {
		feature.noSave = false
		b.featureMap[feature.Name] = feature
		// TODO: Return error if feature with same name has been added
	}
	// Increase size of data matrix if feature map is larger than initially allocated
	if len(b.featureMap) > len(b.data[0]) {
		for i := range b.data {
			b.data[i] = append(b.data[i], "")
		}
	}
}

func (b *Builder) createRequest(
	endpoint string,
) (*http.Request, error) {
	headers := b.RequestHeaders

	req, err := http.NewRequest("GET", b.BaseURL+endpoint, nil)
	if err != nil {
		return nil, err
	}

	for k, v := range headers {
		req.Header.Add(k, v)
	}
	return req, nil
}

// resolve endpoint templates to actual values for endpoints based on parent features
func (b *Builder) resolveFeatureEndpoints(feature *Feature) ([]string, error) {
	endpoints := make([]string, b.records)
	parents, err := feature.getParentNames()
	if err != nil {
		return nil, err
	}

	for i := 0; i < b.records; i++ {
		parentValuesMap := make(map[string]string)
		for _, j := range parents {
			parentValuesMap[j] = b.getFeatureData(j)[i]
		}
		var resolveEndpointError error
		endpoints[i], resolveEndpointError = feature.resolveEndpoint(parentValuesMap)
		if resolveEndpointError != nil {
			return nil, resolveEndpointError
		}
	}

	return endpoints, nil
}

type endpointClient interface {
	Do(req http.Request) (*http.Response, error)
}

// populateFeatureData returns string dumps of responses and an error if any
func (b *Builder) populateFeatureData(feature *Feature, client endpointClient) ([]string, error) {
	responseDumps := make([]string, b.records)
	endpoints, err := b.resolveFeatureEndpoints(feature)
	if err != nil {
		return nil, err
	}

	for i := 0; i < b.records; i++ {
		// endpoints should be the same as the number of
		req, err := b.createRequest(endpoints[i])
		if err != nil {
			return nil, err
		}

		resp, err := client.Do(*req)
		if err != nil {
			return nil, err
		}

		defer resp.Body.Close()

		if resp.StatusCode == 200 { // OK
			bodyBytes, _ := ioutil.ReadAll(resp.Body)
			responseDumps[i] = string(bodyBytes)
		}

	}

	return responseDumps, nil
}

// Run Builder to aggregate all features and manage concurrent operations
func (b *Builder) Run(client endpointClient) error {

	for _, feature := range b.featureMap {
		parents, err := feature.getParentNames()
		if err != nil {
			return err
		}

		var populateError error
		go func(feature *Feature) {
			// Block till parent features finish executing
			for _, i := range parents {
				<-b.GetFeature(i).finished
			}

			parsedResponses := make([]string, b.records)
			parsedResponses, populateError = b.populateFeatureData(feature, client)

			output := feature.RunFunc(parsedResponses)
			b.addFeatureData(feature.Name, output)

			feature.finished <- true // Write to feature.finished channel
			close(feature.finished)
		}(feature)

		if populateError != nil {
			return populateError
		}
	}

	// wait for all channels to be written before exiting function
	for _, feature := range b.featureMap {
		<-feature.finished
	}

	return nil
}
<|endoftext|>


<commit_before>package symlink

import (
	"fmt"
	"io"
	"os"
	"os/exec"
	"strings"
	"testing"
)

// Making a symlink means:
// - making sure the destination exists
// - making sure the source don't exist and the parent folder is created
// - if the source does exist and
//   - is a symlink pointing to a different destination: rmdir
//   - is a symlink pointing to the same
//   - is a folder x, rename it to x.1 (if x.1 exists, x.2, ...)

type test struct {
	src string
	dst string
	err string
	sl  *SL
}

func TestDestination(t *testing.T) {
	// t.Skip("Skip TestDestination")
	// only a nil bit will make filepath.Abs() fail:
	// https://github.com/golang/go/blob/d16c7f8004bd1c9f896367af7ea86f5530596b39/src/syscall/syscall_windows.go#L41
	// from UTF16FromString (https://github.com/golang/go/blob/d16c7f8004bd1c9f896367af7ea86f5530596b39/src/syscall/syscall_windows.go#L71)
	// from FullPath (https://github.com/golang/go/blob/d16c7f8004bd1c9f896367af7ea86f5530596b39/src/syscall/exec_windows.go#L134)
	// from abs (https://github.com/golang/go/blob/d16c7f8004bd1c9f896367af7ea86f5530596b39/src/path/filepath/path_windows.go#L109)
	// from Abs (https://github.com/golang/go/blob/d16c7f8004bd1c9f896367af7ea86f5530596b39/src/path/filepath/path.go#L235)

	osStat = testOsStat
	execRun = testExecRun
	tests := []*test{
		&test{dst: "unknown/dst", err: "The system cannot find the path specified"},
		&test{dst: string([]byte{0}), err: "invalid argument"},
		&test{dst: "err", err: "Test error on os.Stat with non-nil fi"},
		&test{dst: "badsymlink/dir", err: "unreadable dir on symlink"},
		&test{dst: "nojunction/dir", err: "Unable to find junction symlink in parent dir"},
		&test{dst: "cmdRun/dir", err: "The system cannot find the file specified"},
		&test{dst: "WarningOnDir/dir", err: "Warning on run"},
	}
	var sl *SL
	var err error
	for _, test := range tests {
		sl, err = New(".", test.dst)
		if err == nil || strings.Contains(err.Error(), test.err) == false {
			t.Errorf("Err '%v', expected '%s'", err, test.err)
		}
		if sl != nil {
			t.Errorf("SL '%v', expected <nil>", sl)
		}
	}
	// destination is a symlink
	_, err = New(`.`, `symlink`)
	// destination exists
	_, err = New(`x`, `.`)
	// fmt.Printf("%+v\n", err)
}

func TestSource(t *testing.T) {
	osStat = testOsStat
	osLstat = testOsStat
	execRun = testExecRun
	osMkdirAll = testOsMkdirAll
	osRename = testOsRename
	osModeSymlink = testOsModeSymlink

	tests := []*test{
		&test{src: "parentNotYetCreated/newlink"},
		&test{src: "badSrcParent/newlink", err: "Test error badSrcParent on os.Stat with non-nil fi"},
		&test{src: "badSrcParentMdirAll/newlink", err: "Error on mkDirAll for"},
		&test{src: "symlinkdir/newlink", err: ""},
		&test{src: "badsrcparentdir/newlink", err: "Impossible to check/access link parent folder"},
		&test{src: string([]byte{0}), err: "invalid argument"},
		&test{src: "parentnomovesymlinkdir/newlink", err: "Unable to rename "},
		&test{src: "parent/newlinkBadStat", err: "newlinkBadStat cannot be stat"},
		&test{src: "existingparent/existingsymlink", err: ""},
		&test{src: "existingparent/existingsymlinkdiff", err: ""},
		&test{src: "existingparent/existingsymlinkdiffnomove", err: "Unable to rename"},
		&test{src: "parent/failedmklink", err: "Unable to run "},
		&test{src: "parent/existingsymlinkbadstat", err: "existingsymlinkbadstat.1 cannot be stat'd"},
		&test{src: "parentbaddir/existingsymlinkbaddir", err: "Impossible to check/access"},
		&test{src: "parentnosymlink/existingnosymlink", err: "Unable to find junction symlink in parent dir"},
		&test{src: "parentNoDir/newlinkNoDir"},
	}
	var sl *SL
	var err error
	for _, test := range tests {
		sl, err = New(test.src, ".")
		if err != nil && strings.Contains(err.Error(), test.err) == false {
			t.Errorf("Err '%v', expected '%s'", err, test.err)
			os.Exit(-1)
		}
		if err == nil && test.err != "" {
			t.Errorf("Err nil, expected '%s'", test.err)
			os.Exit(-2)
		}
		if sl == nil && err == nil {
			t.Errorf("SL '%v', expected NOT <nil>", sl)
			os.Exit(-3)
		}
		fmt.Println("------------------")
	}
}

func testOsStat(name string) (os.FileInfo, error) {
	// fmt.Printf("testOsStat name='%+v'\n", name)
	if strings.HasSuffix(name, `prj\symlink\err\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("Test error on os.Stat with non-nil fi")
	}
	if strings.HasSuffix(name, `prj\symlink\symlink\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for symlink")
	}
	if strings.HasSuffix(name, `prj\symlink\badsymlink\dir\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for bad symlink")
	}
	if strings.HasSuffix(name, `prj\symlink\nojunction\dir\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for no junction")
	}
	if strings.HasSuffix(name, `prj\symlink\cmdRun\dir\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for no junction")
	}
	if strings.HasSuffix(name, `prj\symlink\WarningOnDir\dir\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for warning on dir")
	}
	if strings.HasSuffix(name, `prj\symlink\badSrcParent\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("Test error badSrcParent on os.Stat with non-nil fi")
	}
	if strings.HasSuffix(name, `prj\symlink\symlinkdir\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for symlinkdir")
	}
	if strings.HasSuffix(name, `prj\symlink\parentnomovesymlinkdir\`) {
		fi, _ := os.Stat(".")
		return fi, nil
	}
	if strings.HasSuffix(name, `prj\symlink\badsrcparentdir\`) {
		return nil, fmt.Errorf("badsrcparentdir cannot be stat'd")
	}
	if strings.HasSuffix(name, `prj\symlink\parent\newlinkBadStat\`) {
		return nil, fmt.Errorf("newlinkBadStat cannot be stat'd")
	}
	if strings.HasSuffix(name, `prj\symlink\parent\existingsymlinkbadstat.1`) {
		return nil, fmt.Errorf("existingsymlinkbadstat.1 cannot be stat'd")
	}
	if strings.HasSuffix(name, `prj\symlink\parent\existingsymlinkbadstat\`) {
		fi, _ := os.Stat(".")
		return fi, nil
	}
	if strings.HasSuffix(name, `prj\symlink\parentbaddir\existingsymlinkbaddir\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for src no dir")
	}
	if strings.HasSuffix(name, `prj\symlink\existingparent\`) {
		fi, _ := os.Stat(".")
		return fi, nil
	}
	if strings.HasSuffix(name, `prj\symlink\existingparent\existingsymlink\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for existingsymlink")
	}
	if strings.HasSuffix(name, `prj\symlink\parentnosymlink\existingnosymlink\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for parentnosymlink existingnosymlink")
	}
	if strings.HasSuffix(name, `prj\symlink\existingparent\existingsymlinkdiff\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for existingsymlinkdiff")
	}
	if strings.HasSuffix(name, `prj\symlink\existingparent\existingsymlinkdiffnomove\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for existingsymlinkdiffnomove")
	}
	if strings.HasSuffix(name, `prj\symlink\parentNoDir\newlinkNoDir\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for newlinkNoDir")
	}
	if strings.HasSuffix(name, `.1`) {
		fi, _ := os.Stat(".")
		return fi, nil
	}
	return os.Stat(name)
}

func testOsMkdirAll(path string, perm os.FileMode) error {
	fmt.Printf("testOsMkdirAll path='%+v'\n", path)
	if strings.HasSuffix(path, `badSrcParentMdirAll\`) {
		return fmt.Errorf("Error on mkDirAll for '%s'", path)
	}
	return nil
}

var junctionOut = `
 Répertoire de C:\Users\VonC\prog\git\ggb\deps\src\github.com\VonC

22/06/2015  11:03    <REP>          .
22/06/2015  11:03    <REP>          ..
22/06/2015  11:03    <JONCTION>     symlink [C:\Users\VonC\prog\git\ggb\]
22/06/2015  11:03    <JONCTION>     symlinkdir [C:\Users\VonC\prog\git\ggb\]
22/06/2015  11:03    <JONCTION>     parentnomovesymlinkdir [C:\Users\VonC\prog\git\ggb\]
22/06/2015  11:03    <JONCTION>     existingsymlink [C:\Users\VonC\prog\git\ggb\prj\symlink\]
22/06/2015  11:03    <JONCTION>     existingsymlinkdiff [C:\Users\VonC\prog\git\ggb\prj\symlink\diff]
22/06/2015  11:03    <JONCTION>     existingsymlinkdiffnomove [C:\Users\VonC\prog\git\ggb\prj\symlink\diff]
`

func testExecRun(cmd *exec.Cmd) error {
	tmsg := fmt.Sprintf("testExecRun cmd='%v' in '%s'", cmd.Args, cmd.Dir)
	// fmt.Println(tmsg)
	if strings.Contains(tmsg, `\failedmklink`) {
		return fmt.Errorf("mklink fails")
	}
	if strings.Contains(tmsg, "/J") {
		return nil
	}
	if strings.HasSuffix(cmd.Dir, `\WarningOnDir`) {
		io.WriteString(cmd.Stdout, "dummy content")
		io.WriteString(cmd.Stderr, "Some warning on dir")
		return nil
	}
	if strings.HasSuffix(cmd.Dir, `\nojunction`) {
		io.WriteString(cmd.Stdout, "dummy content without any junction")
		return nil
	}
	if strings.HasSuffix(cmd.Dir, `\badsymlink`) {
		return fmt.Errorf("unreadable dir on symlink")
	}
	if strings.HasSuffix(cmd.Dir, `\parentbaddir`) {
		return fmt.Errorf("unreadable dir on parentbaddir")
	}

	path := ""
	if strings.Contains(cmd.Dir, `ggb\`) {
		i := strings.Index(cmd.Dir, `ggb\`)
		path = cmd.Dir[:i+len(`ggb\`)]
	}
	jjunctionOut := strings.Replace(junctionOut, `C:\Users\VonC\prog\git\ggb\`, path, -1)

	if strings.HasSuffix(cmd.Dir, `\symlink`) {
		io.WriteString(cmd.Stdout, jjunctionOut)
		return nil
	}
	if strings.HasSuffix(cmd.Dir, `\parentnomovesymlinkdir`) {
		io.WriteString(cmd.Stdout, jjunctionOut)
		return nil
	}
	if strings.HasSuffix(cmd.Dir, `\existingparent`) {
		io.WriteString(cmd.Stdout, jjunctionOut)
		return nil
	}
	if strings.HasSuffix(cmd.Dir, `\parentnosymlink`) {
		io.WriteString(cmd.Stdout, jjunctionOut)
		return nil
	}
	return cmdRun(cmd)
}

func testOsRename(oldpath, newpath string) error {
	// fmt.Printf("testOsRename oldpath='%v', newpath '%s'\n", oldpath, newpath)
	if strings.HasSuffix(oldpath, `\parentnomovesymlinkdir`) {
		return fmt.Errorf("Unable to rename '%s' to '%s'", oldpath, newpath)
	}
	if strings.HasSuffix(oldpath, `\existingsymlinkdiffnomove`) {
		return fmt.Errorf("Unable to rename '%s' to '%s'", oldpath, newpath)
	}
	return nil
}

func testOsModeSymlink(fil os.FileInfo, path string) bool {
	// fmt.Printf("testOsModeSymlink path='%+v'\n", path)
	if strings.HasSuffix(path, `prj\symlink\parentnomovesymlinkdir\`) {
		return false
	}
	return fosModeSymlink(fil, path)
}
<commit_msg>prj/symlink/symlink_test.go: make testFunctions silent more<commit_after>package symlink

import (
	"fmt"
	"io"
	"os"
	"os/exec"
	"strings"
	"testing"
)

// Making a symlink means:
// - making sure the destination exists
// - making sure the source don't exist and the parent folder is created
// - if the source does exist and
//   - is a symlink pointing to a different destination: rmdir
//   - is a symlink pointing to the same
//   - is a folder x, rename it to x.1 (if x.1 exists, x.2, ...)

type test struct {
	src string
	dst string
	err string
	sl  *SL
}

func TestDestination(t *testing.T) {
	// t.Skip("Skip TestDestination")
	// only a nil bit will make filepath.Abs() fail:
	// https://github.com/golang/go/blob/d16c7f8004bd1c9f896367af7ea86f5530596b39/src/syscall/syscall_windows.go#L41
	// from UTF16FromString (https://github.com/golang/go/blob/d16c7f8004bd1c9f896367af7ea86f5530596b39/src/syscall/syscall_windows.go#L71)
	// from FullPath (https://github.com/golang/go/blob/d16c7f8004bd1c9f896367af7ea86f5530596b39/src/syscall/exec_windows.go#L134)
	// from abs (https://github.com/golang/go/blob/d16c7f8004bd1c9f896367af7ea86f5530596b39/src/path/filepath/path_windows.go#L109)
	// from Abs (https://github.com/golang/go/blob/d16c7f8004bd1c9f896367af7ea86f5530596b39/src/path/filepath/path.go#L235)

	osStat = testOsStat
	execRun = testExecRun
	tests := []*test{
		&test{dst: "unknown/dst", err: "The system cannot find the path specified"},
		&test{dst: string([]byte{0}), err: "invalid argument"},
		&test{dst: "err", err: "Test error on os.Stat with non-nil fi"},
		&test{dst: "badsymlink/dir", err: "unreadable dir on symlink"},
		&test{dst: "nojunction/dir", err: "Unable to find junction symlink in parent dir"},
		&test{dst: "cmdRun/dir", err: "The system cannot find the file specified"},
		&test{dst: "WarningOnDir/dir", err: "Warning on run"},
	}
	var sl *SL
	var err error
	for _, test := range tests {
		sl, err = New(".", test.dst)
		if err == nil || strings.Contains(err.Error(), test.err) == false {
			t.Errorf("Err '%v', expected '%s'", err, test.err)
		}
		if sl != nil {
			t.Errorf("SL '%v', expected <nil>", sl)
		}
	}
	// destination is a symlink
	_, err = New(`.`, `symlink`)
	// destination exists
	_, err = New(`x`, `.`)
	// fmt.Printf("%+v\n", err)
}

func TestSource(t *testing.T) {
	osStat = testOsStat
	osLstat = testOsStat
	execRun = testExecRun
	osMkdirAll = testOsMkdirAll
	osRename = testOsRename
	osModeSymlink = testOsModeSymlink

	tests := []*test{
		&test{src: "parentNotYetCreated/newlink"},
		&test{src: "badSrcParent/newlink", err: "Test error badSrcParent on os.Stat with non-nil fi"},
		&test{src: "badSrcParentMdirAll/newlink", err: "Error on mkDirAll for"},
		&test{src: "symlinkdir/newlink", err: ""},
		&test{src: "badsrcparentdir/newlink", err: "Impossible to check/access link parent folder"},
		&test{src: string([]byte{0}), err: "invalid argument"},
		&test{src: "parentnomovesymlinkdir/newlink", err: "Unable to rename "},
		&test{src: "parent/newlinkBadStat", err: "newlinkBadStat cannot be stat"},
		&test{src: "existingparent/existingsymlink", err: ""},
		&test{src: "existingparent/existingsymlinkdiff", err: ""},
		&test{src: "existingparent/existingsymlinkdiffnomove", err: "Unable to rename"},
		&test{src: "parent/failedmklink", err: "Unable to run "},
		&test{src: "parent/existingsymlinkbadstat", err: "existingsymlinkbadstat.1 cannot be stat'd"},
		&test{src: "parentbaddir/existingsymlinkbaddir", err: "Impossible to check/access"},
		&test{src: "parentnosymlink/existingnosymlink", err: "Unable to find junction symlink in parent dir"},
		&test{src: "parentNoDir/newlinkNoDir"},
	}
	var sl *SL
	var err error
	for _, test := range tests {
		sl, err = New(test.src, ".")
		if err != nil && strings.Contains(err.Error(), test.err) == false {
			t.Errorf("Err '%v', expected '%s'", err, test.err)
			os.Exit(-1)
		}
		if err == nil && test.err != "" {
			t.Errorf("Err nil, expected '%s'", test.err)
			os.Exit(-2)
		}
		if sl == nil && err == nil {
			t.Errorf("SL '%v', expected NOT <nil>", sl)
			os.Exit(-3)
		}
		// fmt.Println("------------------")
	}
}

func testOsStat(name string) (os.FileInfo, error) {
	// fmt.Printf("testOsStat name='%+v'\n", name)
	if strings.HasSuffix(name, `prj\symlink\err\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("Test error on os.Stat with non-nil fi")
	}
	if strings.HasSuffix(name, `prj\symlink\symlink\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for symlink")
	}
	if strings.HasSuffix(name, `prj\symlink\badsymlink\dir\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for bad symlink")
	}
	if strings.HasSuffix(name, `prj\symlink\nojunction\dir\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for no junction")
	}
	if strings.HasSuffix(name, `prj\symlink\cmdRun\dir\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for no junction")
	}
	if strings.HasSuffix(name, `prj\symlink\WarningOnDir\dir\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for warning on dir")
	}
	if strings.HasSuffix(name, `prj\symlink\badSrcParent\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("Test error badSrcParent on os.Stat with non-nil fi")
	}
	if strings.HasSuffix(name, `prj\symlink\symlinkdir\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for symlinkdir")
	}
	if strings.HasSuffix(name, `prj\symlink\parentnomovesymlinkdir\`) {
		fi, _ := os.Stat(".")
		return fi, nil
	}
	if strings.HasSuffix(name, `prj\symlink\badsrcparentdir\`) {
		return nil, fmt.Errorf("badsrcparentdir cannot be stat'd")
	}
	if strings.HasSuffix(name, `prj\symlink\parent\newlinkBadStat\`) {
		return nil, fmt.Errorf("newlinkBadStat cannot be stat'd")
	}
	if strings.HasSuffix(name, `prj\symlink\parent\existingsymlinkbadstat.1`) {
		return nil, fmt.Errorf("existingsymlinkbadstat.1 cannot be stat'd")
	}
	if strings.HasSuffix(name, `prj\symlink\parent\existingsymlinkbadstat\`) {
		fi, _ := os.Stat(".")
		return fi, nil
	}
	if strings.HasSuffix(name, `prj\symlink\parentbaddir\existingsymlinkbaddir\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for src no dir")
	}
	if strings.HasSuffix(name, `prj\symlink\existingparent\`) {
		fi, _ := os.Stat(".")
		return fi, nil
	}
	if strings.HasSuffix(name, `prj\symlink\existingparent\existingsymlink\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for existingsymlink")
	}
	if strings.HasSuffix(name, `prj\symlink\parentnosymlink\existingnosymlink\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for parentnosymlink existingnosymlink")
	}
	if strings.HasSuffix(name, `prj\symlink\existingparent\existingsymlinkdiff\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for existingsymlinkdiff")
	}
	if strings.HasSuffix(name, `prj\symlink\existingparent\existingsymlinkdiffnomove\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for existingsymlinkdiffnomove")
	}
	if strings.HasSuffix(name, `prj\symlink\parentNoDir\newlinkNoDir\`) {
		fi, _ := os.Stat(".")
		return fi, fmt.Errorf("readlink for newlinkNoDir")
	}
	if strings.HasSuffix(name, `.1`) {
		fi, _ := os.Stat(".")
		return fi, nil
	}
	return os.Stat(name)
}

func testOsMkdirAll(path string, perm os.FileMode) error {
	// fmt.Printf("testOsMkdirAll path='%+v'\n", path)
	if strings.HasSuffix(path, `badSrcParentMdirAll\`) {
		return fmt.Errorf("Error on mkDirAll for '%s'", path)
	}
	return nil
}

var junctionOut = `
 Répertoire de C:\Users\VonC\prog\git\ggb\deps\src\github.com\VonC

22/06/2015  11:03    <REP>          .
22/06/2015  11:03    <REP>          ..
22/06/2015  11:03    <JONCTION>     symlink [C:\Users\VonC\prog\git\ggb\]
22/06/2015  11:03    <JONCTION>     symlinkdir [C:\Users\VonC\prog\git\ggb\]
22/06/2015  11:03    <JONCTION>     parentnomovesymlinkdir [C:\Users\VonC\prog\git\ggb\]
22/06/2015  11:03    <JONCTION>     existingsymlink [C:\Users\VonC\prog\git\ggb\prj\symlink\]
22/06/2015  11:03    <JONCTION>     existingsymlinkdiff [C:\Users\VonC\prog\git\ggb\prj\symlink\diff]
22/06/2015  11:03    <JONCTION>     existingsymlinkdiffnomove [C:\Users\VonC\prog\git\ggb\prj\symlink\diff]
`

func testExecRun(cmd *exec.Cmd) error {
	tmsg := fmt.Sprintf("testExecRun cmd='%v' in '%s'", cmd.Args, cmd.Dir)
	// fmt.Println(tmsg)
	if strings.Contains(tmsg, `\failedmklink`) {
		return fmt.Errorf("mklink fails")
	}
	if strings.Contains(tmsg, "/J") {
		return nil
	}
	if strings.HasSuffix(cmd.Dir, `\WarningOnDir`) {
		io.WriteString(cmd.Stdout, "dummy content")
		io.WriteString(cmd.Stderr, "Some warning on dir")
		return nil
	}
	if strings.HasSuffix(cmd.Dir, `\nojunction`) {
		io.WriteString(cmd.Stdout, "dummy content without any junction")
		return nil
	}
	if strings.HasSuffix(cmd.Dir, `\badsymlink`) {
		return fmt.Errorf("unreadable dir on symlink")
	}
	if strings.HasSuffix(cmd.Dir, `\parentbaddir`) {
		return fmt.Errorf("unreadable dir on parentbaddir")
	}

	path := ""
	if strings.Contains(cmd.Dir, `ggb\`) {
		i := strings.Index(cmd.Dir, `ggb\`)
		path = cmd.Dir[:i+len(`ggb\`)]
	}
	jjunctionOut := strings.Replace(junctionOut, `C:\Users\VonC\prog\git\ggb\`, path, -1)

	if strings.HasSuffix(cmd.Dir, `\symlink`) {
		io.WriteString(cmd.Stdout, jjunctionOut)
		return nil
	}
	if strings.HasSuffix(cmd.Dir, `\parentnomovesymlinkdir`) {
		io.WriteString(cmd.Stdout, jjunctionOut)
		return nil
	}
	if strings.HasSuffix(cmd.Dir, `\existingparent`) {
		io.WriteString(cmd.Stdout, jjunctionOut)
		return nil
	}
	if strings.HasSuffix(cmd.Dir, `\parentnosymlink`) {
		io.WriteString(cmd.Stdout, jjunctionOut)
		return nil
	}
	return cmdRun(cmd)
}

func testOsRename(oldpath, newpath string) error {
	// fmt.Printf("testOsRename oldpath='%v', newpath '%s'\n", oldpath, newpath)
	if strings.HasSuffix(oldpath, `\parentnomovesymlinkdir`) {
		return fmt.Errorf("Unable to rename '%s' to '%s'", oldpath, newpath)
	}
	if strings.HasSuffix(oldpath, `\existingsymlinkdiffnomove`) {
		return fmt.Errorf("Unable to rename '%s' to '%s'", oldpath, newpath)
	}
	return nil
}

func testOsModeSymlink(fil os.FileInfo, path string) bool {
	// fmt.Printf("testOsModeSymlink path='%+v'\n", path)
	if strings.HasSuffix(path, `prj\symlink\parentnomovesymlinkdir\`) {
		return false
	}
	return fosModeSymlink(fil, path)
}
<|endoftext|>


<commit_before>// Copyright (c) 2014 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package dcrjson

// GetTransactionDetailsResult models the details data from the gettransaction command.
//
// This models the "short" version of the ListTransactionsResult type, which
// excludes fields common to the transaction.  These common fields are instead
// part of the GetTransactionResult.
type GetTransactionDetailsResult struct {
	Account           string   `json:"account"`
	Address           string   `json:"address,omitempty"`
	Amount            float64  `json:"amount"`
	Category          string   `json:"category"`
	InvolvesWatchOnly bool     `json:"involveswatchonly,omitempty"`
	Fee               *float64 `json:"fee,omitempty"`
	Vout              uint32   `json:"vout"`
}

// GetTransactionResult models the data from the gettransaction command.
type GetTransactionResult struct {
	Amount          float64                       `json:"amount"`
	Fee             float64                       `json:"fee,omitempty"`
	Confirmations   int64                         `json:"confirmations"`
	BlockHash       string                        `json:"blockhash"`
	BlockIndex      int64                         `json:"blockindex"`
	BlockTime       int64                         `json:"blocktime"`
	TxID            string                        `json:"txid"`
	WalletConflicts []string                      `json:"walletconflicts"`
	Time            int64                         `json:"time"`
	TimeReceived    int64                         `json:"timereceived"`
	Details         []GetTransactionDetailsResult `json:"details"`
	Hex             string                        `json:"hex"`
}

// InfoWalletResult models the data returned by the wallet server getinfo
// command.
type InfoWalletResult struct {
	Version         int32   `json:"version"`
	ProtocolVersion int32   `json:"protocolversion"`
	WalletVersion   int32   `json:"walletversion"`
	Balance         float64 `json:"balance"`
	Blocks          int32   `json:"blocks"`
	TimeOffset      int64   `json:"timeoffset"`
	Connections     int32   `json:"connections"`
	Proxy           string  `json:"proxy"`
	Difficulty      float64 `json:"difficulty"`
	TestNet         bool    `json:"testnet"`
	KeypoolOldest   int64   `json:"keypoololdest"`
	KeypoolSize     int32   `json:"keypoolsize"`
	UnlockedUntil   int64   `json:"unlocked_until"`
	PaytxFee        float64 `json:"paytxfee"`
	RelayFee        float64 `json:"relayfee"`
	Errors          string  `json:"errors"`
}

// ListTransactionsTxType defines the type used in the listtransactions JSON-RPC
// result for the TxType command field.
type ListTransactionsTxType string

const (
	// LTTTRegular indicates a regular transaction.
	LTTTRegular ListTransactionsTxType = "regular"

	// LTTTTicket indicates a ticket.
	LTTTTicket ListTransactionsTxType = "ticket"

	// LTTTVote indicates a vote.
	LTTTVote ListTransactionsTxType = "vote"

	// LTTTRevocation indicates a revocation.
	LTTTRevocation ListTransactionsTxType = "revocation"
)

// ListTransactionsResult models the data from the listtransactions command.
type ListTransactionsResult struct {
	Account           string                  `json:"account"`
	Address           string                  `json:"address,omitempty"`
	Amount            float64                 `json:"amount"`
	BlockHash         string                  `json:"blockhash,omitempty"`
	BlockIndex        *int64                  `json:"blockindex,omitempty"`
	BlockTime         int64                   `json:"blocktime,omitempty"`
	Category          string                  `json:"category"`
	Confirmations     int64                   `json:"confirmations"`
	Fee               *float64                `json:"fee,omitempty"`
	Generated         bool                    `json:"generated,omitempty"`
	InvolvesWatchOnly bool                    `json:"involveswatchonly,omitempty"`
	Time              int64                   `json:"time"`
	TimeReceived      int64                   `json:"timereceived"`
	TxID              string                  `json:"txid"`
	TxType            *ListTransactionsTxType `json:"txtype,omitempty"`
	Vout              uint32                  `json:"vout"`
	WalletConflicts   []string                `json:"walletconflicts"`
	Comment           string                  `json:"comment,omitempty"`
	OtherAccount      string                  `json:"otheraccount,omitempty"`
}

// ListReceivedByAccountResult models the data from the listreceivedbyaccount
// command.
type ListReceivedByAccountResult struct {
	Account       string  `json:"account"`
	Amount        float64 `json:"amount"`
	Confirmations uint64  `json:"confirmations"`
}

// ListReceivedByAddressResult models the data from the listreceivedbyaddress
// command.
type ListReceivedByAddressResult struct {
	Account           string   `json:"account"`
	Address           string   `json:"address"`
	Amount            float64  `json:"amount"`
	Confirmations     uint64   `json:"confirmations"`
	TxIDs             []string `json:"txids,omitempty"`
	InvolvesWatchonly bool     `json:"involvesWatchonly,omitempty"`
}

// ListSinceBlockResult models the data from the listsinceblock command.
type ListSinceBlockResult struct {
	Transactions []ListTransactionsResult `json:"transactions"`
	LastBlock    string                   `json:"lastblock"`
}

// ListUnspentResult models a successful response from the listunspent request.
// Contains Decred additions.
type ListUnspentResult struct {
	TxID          string  `json:"txid"`
	Vout          uint32  `json:"vout"`
	Tree          int8    `json:"tree"`
	TxType        int     `json:"txtype"`
	Address       string  `json:"address"`
	Account       string  `json:"account"`
	ScriptPubKey  string  `json:"scriptPubKey"`
	RedeemScript  string  `json:"redeemScript,omitempty"`
	Amount        float64 `json:"amount"`
	Confirmations int64   `json:"confirmations"`
	Spendable     bool    `json:"spendable"`
}

// SignRawTransactionError models the data that contains script verification
// errors from the signrawtransaction request.
type SignRawTransactionError struct {
	TxID      string `json:"txid"`
	Vout      uint32 `json:"vout"`
	ScriptSig string `json:"scriptSig"`
	Sequence  uint32 `json:"sequence"`
	Error     string `json:"error"`
}

// SignRawTransactionResult models the data from the signrawtransaction
// command.
type SignRawTransactionResult struct {
	Hex      string                    `json:"hex"`
	Complete bool                      `json:"complete"`
	Errors   []SignRawTransactionError `json:"errors,omitempty"`
}

// ValidateAddressWalletResult models the data returned by the wallet server
// validateaddress command.
type ValidateAddressWalletResult struct {
	IsValid      bool     `json:"isvalid"`
	Address      string   `json:"address,omitempty"`
	IsMine       bool     `json:"ismine,omitempty"`
	IsWatchOnly  bool     `json:"iswatchonly,omitempty"`
	IsScript     bool     `json:"isscript,omitempty"`
	PubKeyAddr   string   `json:"pubkeyaddr,omitempty"`
	PubKey       string   `json:"pubkey,omitempty"`
	IsCompressed bool     `json:"iscompressed,omitempty"`
	Account      string   `json:"account,omitempty"`
	Addresses    []string `json:"addresses,omitempty"`
	Hex          string   `json:"hex,omitempty"`
	Script       string   `json:"script,omitempty"`
	SigsRequired int32    `json:"sigsrequired,omitempty"`
}

// GetBestBlockResult models the data from the getbestblock command.
type GetBestBlockResult struct {
	Hash   string `json:"hash"`
	Height int64  `json:"height"`
}
<commit_msg>dcrjson: Add GetAccountBalanceResult and GetBalanceResult (#547)<commit_after>// Copyright (c) 2014 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package dcrjson

// GetAccountBalanceResult models the account data from the getbalance command.
type GetAccountBalanceResult struct {
	AccountName             string  `json:"accountname"`
	ImmatureCoinbaseRewards float64 `json:"immaturecoinbaserewards"`
	ImmatureStakeGeneration float64 `json:"immaturestakegeneration"`
	LockedByTickets         float64 `json:"lockedbytickets"`
	Spendable               float64 `json:"spendable"`
	Total                   float64 `json:"total"`
	VotingAuthority         float64 `json:"votingauthority"`
}

// GetBalanceResult models the data from the getbalance command.
type GetBalanceResult struct {
	Balances  []GetAccountBalanceResult `json:"balances"`
	BlockHash string                    `json:"blockhash"`
}

// GetTransactionDetailsResult models the details data from the gettransaction command.
//
// This models the "short" version of the ListTransactionsResult type, which
// excludes fields common to the transaction.  These common fields are instead
// part of the GetTransactionResult.
type GetTransactionDetailsResult struct {
	Account           string   `json:"account"`
	Address           string   `json:"address,omitempty"`
	Amount            float64  `json:"amount"`
	Category          string   `json:"category"`
	InvolvesWatchOnly bool     `json:"involveswatchonly,omitempty"`
	Fee               *float64 `json:"fee,omitempty"`
	Vout              uint32   `json:"vout"`
}

// GetTransactionResult models the data from the gettransaction command.
type GetTransactionResult struct {
	Amount          float64                       `json:"amount"`
	Fee             float64                       `json:"fee,omitempty"`
	Confirmations   int64                         `json:"confirmations"`
	BlockHash       string                        `json:"blockhash"`
	BlockIndex      int64                         `json:"blockindex"`
	BlockTime       int64                         `json:"blocktime"`
	TxID            string                        `json:"txid"`
	WalletConflicts []string                      `json:"walletconflicts"`
	Time            int64                         `json:"time"`
	TimeReceived    int64                         `json:"timereceived"`
	Details         []GetTransactionDetailsResult `json:"details"`
	Hex             string                        `json:"hex"`
}

// InfoWalletResult models the data returned by the wallet server getinfo
// command.
type InfoWalletResult struct {
	Version         int32   `json:"version"`
	ProtocolVersion int32   `json:"protocolversion"`
	WalletVersion   int32   `json:"walletversion"`
	Balance         float64 `json:"balance"`
	Blocks          int32   `json:"blocks"`
	TimeOffset      int64   `json:"timeoffset"`
	Connections     int32   `json:"connections"`
	Proxy           string  `json:"proxy"`
	Difficulty      float64 `json:"difficulty"`
	TestNet         bool    `json:"testnet"`
	KeypoolOldest   int64   `json:"keypoololdest"`
	KeypoolSize     int32   `json:"keypoolsize"`
	UnlockedUntil   int64   `json:"unlocked_until"`
	PaytxFee        float64 `json:"paytxfee"`
	RelayFee        float64 `json:"relayfee"`
	Errors          string  `json:"errors"`
}

// ListTransactionsTxType defines the type used in the listtransactions JSON-RPC
// result for the TxType command field.
type ListTransactionsTxType string

const (
	// LTTTRegular indicates a regular transaction.
	LTTTRegular ListTransactionsTxType = "regular"

	// LTTTTicket indicates a ticket.
	LTTTTicket ListTransactionsTxType = "ticket"

	// LTTTVote indicates a vote.
	LTTTVote ListTransactionsTxType = "vote"

	// LTTTRevocation indicates a revocation.
	LTTTRevocation ListTransactionsTxType = "revocation"
)

// ListTransactionsResult models the data from the listtransactions command.
type ListTransactionsResult struct {
	Account           string                  `json:"account"`
	Address           string                  `json:"address,omitempty"`
	Amount            float64                 `json:"amount"`
	BlockHash         string                  `json:"blockhash,omitempty"`
	BlockIndex        *int64                  `json:"blockindex,omitempty"`
	BlockTime         int64                   `json:"blocktime,omitempty"`
	Category          string                  `json:"category"`
	Confirmations     int64                   `json:"confirmations"`
	Fee               *float64                `json:"fee,omitempty"`
	Generated         bool                    `json:"generated,omitempty"`
	InvolvesWatchOnly bool                    `json:"involveswatchonly,omitempty"`
	Time              int64                   `json:"time"`
	TimeReceived      int64                   `json:"timereceived"`
	TxID              string                  `json:"txid"`
	TxType            *ListTransactionsTxType `json:"txtype,omitempty"`
	Vout              uint32                  `json:"vout"`
	WalletConflicts   []string                `json:"walletconflicts"`
	Comment           string                  `json:"comment,omitempty"`
	OtherAccount      string                  `json:"otheraccount,omitempty"`
}

// ListReceivedByAccountResult models the data from the listreceivedbyaccount
// command.
type ListReceivedByAccountResult struct {
	Account       string  `json:"account"`
	Amount        float64 `json:"amount"`
	Confirmations uint64  `json:"confirmations"`
}

// ListReceivedByAddressResult models the data from the listreceivedbyaddress
// command.
type ListReceivedByAddressResult struct {
	Account           string   `json:"account"`
	Address           string   `json:"address"`
	Amount            float64  `json:"amount"`
	Confirmations     uint64   `json:"confirmations"`
	TxIDs             []string `json:"txids,omitempty"`
	InvolvesWatchonly bool     `json:"involvesWatchonly,omitempty"`
}

// ListSinceBlockResult models the data from the listsinceblock command.
type ListSinceBlockResult struct {
	Transactions []ListTransactionsResult `json:"transactions"`
	LastBlock    string                   `json:"lastblock"`
}

// ListUnspentResult models a successful response from the listunspent request.
// Contains Decred additions.
type ListUnspentResult struct {
	TxID          string  `json:"txid"`
	Vout          uint32  `json:"vout"`
	Tree          int8    `json:"tree"`
	TxType        int     `json:"txtype"`
	Address       string  `json:"address"`
	Account       string  `json:"account"`
	ScriptPubKey  string  `json:"scriptPubKey"`
	RedeemScript  string  `json:"redeemScript,omitempty"`
	Amount        float64 `json:"amount"`
	Confirmations int64   `json:"confirmations"`
	Spendable     bool    `json:"spendable"`
}

// SignRawTransactionError models the data that contains script verification
// errors from the signrawtransaction request.
type SignRawTransactionError struct {
	TxID      string `json:"txid"`
	Vout      uint32 `json:"vout"`
	ScriptSig string `json:"scriptSig"`
	Sequence  uint32 `json:"sequence"`
	Error     string `json:"error"`
}

// SignRawTransactionResult models the data from the signrawtransaction
// command.
type SignRawTransactionResult struct {
	Hex      string                    `json:"hex"`
	Complete bool                      `json:"complete"`
	Errors   []SignRawTransactionError `json:"errors,omitempty"`
}

// ValidateAddressWalletResult models the data returned by the wallet server
// validateaddress command.
type ValidateAddressWalletResult struct {
	IsValid      bool     `json:"isvalid"`
	Address      string   `json:"address,omitempty"`
	IsMine       bool     `json:"ismine,omitempty"`
	IsWatchOnly  bool     `json:"iswatchonly,omitempty"`
	IsScript     bool     `json:"isscript,omitempty"`
	PubKeyAddr   string   `json:"pubkeyaddr,omitempty"`
	PubKey       string   `json:"pubkey,omitempty"`
	IsCompressed bool     `json:"iscompressed,omitempty"`
	Account      string   `json:"account,omitempty"`
	Addresses    []string `json:"addresses,omitempty"`
	Hex          string   `json:"hex,omitempty"`
	Script       string   `json:"script,omitempty"`
	SigsRequired int32    `json:"sigsrequired,omitempty"`
}

// GetBestBlockResult models the data from the getbestblock command.
type GetBestBlockResult struct {
	Hash   string `json:"hash"`
	Height int64  `json:"height"`
}
<|endoftext|>


<commit_before>package greyhound

import (
	"bufio"
	"errors"
	"fmt"
	"html/template"
	"io"
	"math"
	"net/http"
	"os/exec"
	"sync"
	"time"
)

// A PhpHandler represents a single PHP process running the built-in Web server.
//
// Due to the need to check for errors in the STDERR of the process it only allows one call to ServeHTTP() at a time (using sync.Mutex).
type PhpHandler struct {
	dir        string
	port       int
	host       string
	cmd        *exec.Cmd
	stderr     *bufio.Reader
	errorLog   chan string
	requestLog chan string
	mutex      *sync.Mutex
	timeout    int
}

// NewPhpHandler starts a new PHP server listening on the first free port (between port 8001 and 2^16).
//
// Usage:
// 	ph, err := NewPhpHandler("/path/to/web/root", 1000)
// 	if err != nil {
// 	        panic(err)
// 	}
// 	defer ph.Close()
//
// timeout is in milliseconds
func NewPhpHandler(dir string, timeout int) (ph *PhpHandler, err error) {
	for p := 8001; p < int(math.Pow(2, 16)); p++ {
		ph = &PhpHandler{
			dir: dir,
			// Use 127.0.0.1 here instead of localhost
			// otherwise PHP only listens on ::1
			host: fmt.Sprintf("127.0.0.1:%d", p),
		}
		cmd, stderr, err := runPhp(ph.dir, ph.host)

		if err == nil {
			ph.timeout = timeout
			ph.cmd = cmd
			ph.stderr = bufio.NewReader(stderr)
			ph.errorLog = make(chan string)
			ph.requestLog = make(chan string)
			ph.mutex = &sync.Mutex{}
			go ph.listenForErrors()
			return ph, nil
		}
	}
	return nil, errors.New("no free ports found")
}

// Close must be called after a successful call to NewPhpHandler otherwise you may get stray PHP processes floating around.
func (ph *PhpHandler) Close() {
	err := ph.cmd.Process.Kill()
	if err != nil {
		panic(err)
	}
}

// ServeHTTP sends an http.Request to the PHP process, writes what it gets to an http.ResponseWriter.
//
// If an error gets printed to STDERR during the request, it shows the error instead of what PHP returned. If the request takes too long it shows a message saying that the request took too long (see timeout option on NewPhpHandler).
func (ph *PhpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	ph.mutex.Lock()
	defer ph.mutex.Unlock()

	var err error

	r.URL.Scheme = "http"
	r.URL.Host = ph.host

	// Make the request
	tr := &http.Transport{}

	// Timeout stuff
	var resp *http.Response
	wait := make(chan bool)

	go func() {
		resp, err = tr.RoundTrip(r)
		wait <- true
	}()

	select {
	case <-wait:
	case <-time.After(time.Millisecond * time.Duration(ph.timeout)):
		renderTimeout(w, ph.timeout)
		return
	}
	// End timeout stuff

	if err != nil {
		renderError(w, "uh oh")
		return
	}
	defer resp.Body.Close()

	// Check for errors
	thereWereErrors := false

	// The request gets printed to STDERR only after the errors
	// So it's a reliable way to confirm that the page was returned

FOR:
	for {
		select {
		case <-ph.requestLog:
			break FOR
		case line := <-ph.errorLog:
			renderError(w, line)
			thereWereErrors = true
		}
	}

	if thereWereErrors {
		return
	}

	// Headers
	headers := w.Header()
	for k, v := range resp.Header {
		headers[k] = v
	}

	// Status code
	w.WriteHeader(resp.StatusCode)

	// Body
	bufWriter := bufio.NewWriter(w)
	bufWriter.ReadFrom(resp.Body)
	bufWriter.Flush()

	return
}

// Converts bufio.Reader into chan for ease of use during the request
func (ph *PhpHandler) listenForErrors() {
	for {
		line, err := ph.stderr.ReadString('\n')
		if err != nil {
			if err.Error() == "EOF" {
				return
			}
			panic(err)
		}

		if line[25:37] != "] 127.0.0.1:" {
			ph.errorLog <- line[40:]
		} else {
			ph.requestLog <- line[38:]
		}
	}
}

// Render the error template
func renderError(w http.ResponseWriter, s string) {
	w.WriteHeader(http.StatusInternalServerError)

	tmpl := template.Must(template.New("").Parse(`<pre>{{.}}</pre>`))
	tmpl.Execute(w, s)
}

// Render the timeout template
func renderTimeout(w http.ResponseWriter, timeout int) {
	w.WriteHeader(http.StatusInternalServerError)

	tmpl := template.Must(template.New("").Parse(`<p>Waited for {{.}}ms and PHP didn't respond!</p>`))
	tmpl.Execute(w, timeout)
}

// A low-level command
// Starts PHP running, waits one second, returns an error if PHP stopped during that time
func runPhp(dir string, host string) (cmd *exec.Cmd, stderr io.ReadCloser, err error) {
	cmd = exec.Command(
		"php",
		"-n", // do not read php.ini
		"-S", host,
		"-t", dir,
		"-d", "display_errors=Off",
		"-d", "log_errors=On",
		"-d", "error_reporting=E_ALL",
	)

	// Connect stderr
	stderr, err = cmd.StderrPipe()
	if err != nil {
		return
	}

	// Let's go
	err = cmd.Start()
	if err != nil {
		return
	}

	// Wait 1 second for the command to terminate
	// If it exits early, that's bad whatever the exit status
	e := make(chan error)

	go func() {
		err := cmd.Wait()
		if err != nil {
			e <- err
		} else {
			e <- errors.New("command exited early")
		}
	}()

	select {
	case <-time.After(time.Millisecond * 500):
		return
	case err = <-e:
		return
	}
}
<commit_msg>Improve error template slightly<commit_after>package greyhound

import (
	"bufio"
	"errors"
	"fmt"
	"html/template"
	"io"
	"math"
	"net/http"
	"os/exec"
	"sync"
	"time"
)

// A PhpHandler represents a single PHP process running the built-in Web server.
//
// Due to the need to check for errors in the STDERR of the process it only allows one call to ServeHTTP() at a time (using sync.Mutex).
type PhpHandler struct {
	dir        string
	port       int
	host       string
	cmd        *exec.Cmd
	stderr     *bufio.Reader
	errorLog   chan string
	requestLog chan string
	mutex      *sync.Mutex
	timeout    int
}

type errorType int

const (
	phpError errorType = iota
	timeoutError
	requestError
)

// NewPhpHandler starts a new PHP server listening on the first free port (between port 8001 and 2^16).
//
// Usage:
// 	ph, err := NewPhpHandler("/path/to/web/root", 1000)
// 	if err != nil {
// 	        panic(err)
// 	}
// 	defer ph.Close()
//
// timeout is in milliseconds
func NewPhpHandler(dir string, timeout int) (ph *PhpHandler, err error) {
	for p := 8001; p < int(math.Pow(2, 16)); p++ {
		ph = &PhpHandler{
			dir: dir,
			// Use 127.0.0.1 here instead of localhost
			// otherwise PHP only listens on ::1
			host: fmt.Sprintf("127.0.0.1:%d", p),
		}
		cmd, stderr, err := runPhp(ph.dir, ph.host)

		if err == nil {
			ph.timeout = timeout
			ph.cmd = cmd
			ph.stderr = bufio.NewReader(stderr)
			ph.errorLog = make(chan string)
			ph.requestLog = make(chan string)
			ph.mutex = &sync.Mutex{}
			go ph.listenForErrors()
			return ph, nil
		}
	}
	return nil, errors.New("no free ports found")
}

// Close must be called after a successful call to NewPhpHandler otherwise you may get stray PHP processes floating around.
func (ph *PhpHandler) Close() {
	err := ph.cmd.Process.Kill()
	if err != nil {
		panic(err)
	}
}

// ServeHTTP sends an http.Request to the PHP process, writes what it gets to an http.ResponseWriter.
//
// If an error gets printed to STDERR during the request, it shows the error instead of what PHP returned. If the request takes too long it shows a message saying that the request took too long (see timeout option on NewPhpHandler).
func (ph *PhpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	ph.mutex.Lock()
	defer ph.mutex.Unlock()

	var err error

	r.URL.Scheme = "http"
	r.URL.Host = ph.host

	// Make the request
	tr := &http.Transport{}

	// Timeout stuff
	var resp *http.Response
	wait := make(chan bool)

	go func() {
		resp, err = tr.RoundTrip(r)
		wait <- true
	}()

	select {
	case <-wait:
	case <-time.After(time.Millisecond * time.Duration(ph.timeout)):
		renderError(w, timeoutError, fmt.Sprintf("Took too long to respond (%dms)", ph.timeout))
		return
	}
	// End timeout stuff

	if err != nil {
		renderError(w, requestError, "uh oh")
		return
	}
	defer resp.Body.Close()

	// Check for errors
	thereWereErrors := false

	// The request gets printed to STDERR only after the errors
	// So it's a reliable way to confirm that the page was returned

FOR:
	for {
		select {
		case <-ph.requestLog:
			break FOR
		case line := <-ph.errorLog:
			renderError(w, phpError, line)
			thereWereErrors = true
		}
	}

	if thereWereErrors {
		return
	}

	// Headers
	headers := w.Header()
	for k, v := range resp.Header {
		headers[k] = v
	}

	// Status code
	w.WriteHeader(resp.StatusCode)

	// Body
	bufWriter := bufio.NewWriter(w)
	bufWriter.ReadFrom(resp.Body)
	bufWriter.Flush()

	return
}

// Converts bufio.Reader into chan for ease of use during the request
func (ph *PhpHandler) listenForErrors() {
	for {
		line, err := ph.stderr.ReadString('\n')
		if err != nil {
			if err.Error() == "EOF" {
				return
			}
			panic(err)
		}

		if line[25:37] != "] 127.0.0.1:" {
			ph.errorLog <- line[40:]
		} else {
			ph.requestLog <- line[38:]
		}
	}
}

// Render the error template
func renderError(w http.ResponseWriter, t errorType, s string) {
	w.WriteHeader(http.StatusInternalServerError)

	tmpl := template.Must(template.New("").Parse(`<!doctype html>
	<title>Error!</title>

Error!

{{.}}
`))
	tmpl.Execute(w, s)
}

// A low-level command
// Starts PHP running, waits one second, returns an error if PHP stopped during that time
func runPhp(dir string, host string) (cmd *exec.Cmd, stderr io.ReadCloser, err error) {
	cmd = exec.Command(
		"php",
		"-n", // do not read php.ini
		"-S", host,
		"-t", dir,
		"-d", "display_errors=Off",
		"-d", "log_errors=On",
		"-d", "error_reporting=E_ALL",
	)

	// Connect stderr
	stderr, err = cmd.StderrPipe()
	if err != nil {
		return
	}

	// Let's go
	err = cmd.Start()
	if err != nil {
		return
	}

	// Wait 1 second for the command to terminate
	// If it exits early, that's bad whatever the exit status
	e := make(chan error)

	go func() {
		err := cmd.Wait()
		if err != nil {
			e <- err
		} else {
			e <- errors.New("command exited early")
		}
	}()

	select {
	case <-time.After(time.Millisecond * 500):
		return
	case err = <-e:
		return
	}
}
<|endoftext|>