<commit_before>// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gc

import (
	"bufio"
	"internal/testenv"
	"io"
	"os/exec"
	"regexp"
	"runtime"
	"strings"
	"testing"
)

// TestIntendedInlining tests that specific runtime functions are inlined.
// This allows refactoring for code clarity and re-use without fear that
// changes to the compiler will cause silent performance regressions.
func TestIntendedInlining(t *testing.T) {
	if testing.Short() && testenv.Builder() == "" {
		t.Skip("skipping in short mode")
	}
	testenv.MustHaveGoRun(t)
	t.Parallel()

	// want is the list of function names (by package) that should
	// be inlined.
	want := map[string][]string{
		"runtime": {
			// TODO(mvdan): enable these once mid-stack
			// inlining is available
			// "adjustctxt",

			"add",
			"acquirem",
			"add1",
			"addb",
			"adjustpanics",
			"adjustpointer",
			"bucketMask",
			"bucketShift",
			"chanbuf",
			"deferArgs",
			"deferclass",
			"evacuated",
			"fastlog2",
			"fastrand",
			"float64bits",
			"funcPC",
			"getm",
			"isDirectIface",
			"itabHashFunc",
			"maxSliceCap",
			"noescape",
			"readUnaligned32",
			"readUnaligned64",
			"releasem",
			"round",
			"roundupsize",
			"selectsize",
			"stringStructOf",
			"subtract1",
			"subtractb",
			"tophash",
			"totaldefersize",
			"(*bmap).keys",
			"(*bmap).overflow",
			"(*waitq).enqueue",

			// GC-related ones
			"cgoInRange",
			"gclinkptr.ptr",
			"guintptr.ptr",
			"heapBits.bits",
			"heapBits.isPointer",
			"heapBits.morePointers",
			"heapBits.next",
			"heapBitsForAddr",
			"inheap",
			"markBits.isMarked",
			"muintptr.ptr",
			"puintptr.ptr",
			"spanOfUnchecked",
			"(*gcWork).putFast",
			"(*gcWork).tryGetFast",
			"(*guintptr).set",
			"(*markBits).advance",
			"(*mspan).allocBitsForIndex",
			"(*mspan).base",
			"(*mspan).markBitsForBase",
			"(*mspan).markBitsForIndex",
			"(*muintptr).set",
			"(*puintptr).set",
		},
		"runtime/internal/sys": {},
		"bytes": {
			"(*Buffer).Bytes",
			"(*Buffer).Cap",
			"(*Buffer).Len",
			"(*Buffer).Next",
			"(*Buffer).Read",
			"(*Buffer).ReadByte",
			"(*Buffer).Reset",
			"(*Buffer).String",
			"(*Buffer).UnreadByte",
			"(*Buffer).tryGrowByReslice",
		},
		"unicode/utf8": {
			"FullRune",
			"FullRuneInString",
			"RuneLen",
			"ValidRune",
		},
		"regexp": {
			"(*bitState).push",
		},
	}

	if runtime.GOARCH != "386" {
		// nextFreeFast calls sys.Ctz64, which on 386 is implemented in asm and is not inlinable.
		// We currently don't have midstack inlining so nextFreeFast is also not inlinable on 386.
		// So check for it only on non-386 platforms.
		want["runtime"] = append(want["runtime"], "nextFreeFast")
		// As explained above, Ctz64 and Ctz32 are not Go code on 386.
		// The same applies to Bswap32.
		want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Ctz64")
		want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Ctz32")
		want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Bswap32")
	}
	switch runtime.GOARCH {
	case "amd64", "amd64p32", "arm64", "mips64", "mips64le", "ppc64", "ppc64le", "s390x":
		// rotl_31 is only defined on 64-bit architectures
		want["runtime"] = append(want["runtime"], "rotl_31")
	}

	notInlinedReason := make(map[string]string)
	pkgs := make([]string, 0, len(want))
	for pname, fnames := range want {
		pkgs = append(pkgs, pname)
		for _, fname := range fnames {
			fullName := pname + "." + fname
			if _, ok := notInlinedReason[fullName]; ok {
				t.Errorf("duplicate func: %s", fullName)
			}
			notInlinedReason[fullName] = "unknown reason"
		}
	}

	args := append([]string{"build", "-a", "-gcflags=-m -m"}, pkgs...)
	cmd := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), args...))
	pr, pw := io.Pipe()
	cmd.Stdout = pw
	cmd.Stderr = pw
	cmdErr := make(chan error, 1)
	go func() {
		cmdErr <- cmd.Run()
		pw.Close()
	}()
	scanner := bufio.NewScanner(pr)
	curPkg := ""
	canInline := regexp.MustCompile(`: can inline ([^ ]*)`)
	cannotInline := regexp.MustCompile(`: cannot inline ([^ ]*): (.*)`)
	for scanner.Scan() {
		line := scanner.Text()
		if strings.HasPrefix(line, "# ") {
			curPkg = line[2:]
			continue
		}
		if m := canInline.FindStringSubmatch(line); m != nil {
			fname := m[1]
			delete(notInlinedReason, curPkg+"."+fname)
			continue
		}
		if m := cannotInline.FindStringSubmatch(line); m != nil {
			fname, reason := m[1], m[2]
			fullName := curPkg + "." + fname
			if _, ok := notInlinedReason[fullName]; ok {
				// cmd/compile gave us a reason why
				notInlinedReason[fullName] = reason
			}
			continue
		}
	}
	if err := <-cmdErr; err != nil {
		t.Fatal(err)
	}
	if err := scanner.Err(); err != nil {
		t.Fatal(err)
	}
	for fullName, reason := range notInlinedReason {
		t.Errorf("%s was not inlined: %s", fullName, reason)
	}
}
<commit_msg>cmd/compile: add reflect to TestIntendedInlining<commit_after>// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gc

import (
	"bufio"
	"internal/testenv"
	"io"
	"os/exec"
	"regexp"
	"runtime"
	"strings"
	"testing"
)

// TestIntendedInlining tests that specific runtime functions are inlined.
// This allows refactoring for code clarity and re-use without fear that
// changes to the compiler will cause silent performance regressions.
func TestIntendedInlining(t *testing.T) {
	if testing.Short() && testenv.Builder() == "" {
		t.Skip("skipping in short mode")
	}
	testenv.MustHaveGoRun(t)
	t.Parallel()

	// want is the list of function names (by package) that should
	// be inlined.
	want := map[string][]string{
		"runtime": {
			// TODO(mvdan): enable these once mid-stack
			// inlining is available
			// "adjustctxt",

			"add",
			"acquirem",
			"add1",
			"addb",
			"adjustpanics",
			"adjustpointer",
			"bucketMask",
			"bucketShift",
			"chanbuf",
			"deferArgs",
			"deferclass",
			"evacuated",
			"fastlog2",
			"fastrand",
			"float64bits",
			"funcPC",
			"getm",
			"isDirectIface",
			"itabHashFunc",
			"maxSliceCap",
			"noescape",
			"readUnaligned32",
			"readUnaligned64",
			"releasem",
			"round",
			"roundupsize",
			"selectsize",
			"stringStructOf",
			"subtract1",
			"subtractb",
			"tophash",
			"totaldefersize",
			"(*bmap).keys",
			"(*bmap).overflow",
			"(*waitq).enqueue",

			// GC-related ones
			"cgoInRange",
			"gclinkptr.ptr",
			"guintptr.ptr",
			"heapBits.bits",
			"heapBits.isPointer",
			"heapBits.morePointers",
			"heapBits.next",
			"heapBitsForAddr",
			"inheap",
			"markBits.isMarked",
			"muintptr.ptr",
			"puintptr.ptr",
			"spanOfUnchecked",
			"(*gcWork).putFast",
			"(*gcWork).tryGetFast",
			"(*guintptr).set",
			"(*markBits).advance",
			"(*mspan).allocBitsForIndex",
			"(*mspan).base",
			"(*mspan).markBitsForBase",
			"(*mspan).markBitsForIndex",
			"(*muintptr).set",
			"(*puintptr).set",
		},
		"runtime/internal/sys": {},
		"bytes": {
			"(*Buffer).Bytes",
			"(*Buffer).Cap",
			"(*Buffer).Len",
			"(*Buffer).Next",
			"(*Buffer).Read",
			"(*Buffer).ReadByte",
			"(*Buffer).Reset",
			"(*Buffer).String",
			"(*Buffer).UnreadByte",
			"(*Buffer).tryGrowByReslice",
		},
		"unicode/utf8": {
			"FullRune",
			"FullRuneInString",
			"RuneLen",
			"ValidRune",
		},
		"reflect": {
			"Value.CanAddr",
			"Value.CanSet",
			"Value.IsValid",
			"add",
			"align",
			"flag.kind",
			"flag.ro",

			// TODO: these use panic, need mid-stack
			// inlining
			// "Value.CanInterface",
			// "Value.pointer",
			// "flag.mustBe",
			// "flag.mustBeAssignable",
			// "flag.mustBeExported",
		},
		"regexp": {
			"(*bitState).push",
		},
	}

	if runtime.GOARCH != "386" {
		// nextFreeFast calls sys.Ctz64, which on 386 is implemented in asm and is not inlinable.
		// We currently don't have midstack inlining so nextFreeFast is also not inlinable on 386.
		// So check for it only on non-386 platforms.
		want["runtime"] = append(want["runtime"], "nextFreeFast")
		// As explained above, Ctz64 and Ctz32 are not Go code on 386.
		// The same applies to Bswap32.
		want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Ctz64")
		want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Ctz32")
		want["runtime/internal/sys"] = append(want["runtime/internal/sys"], "Bswap32")
	}
	switch runtime.GOARCH {
	case "amd64", "amd64p32", "arm64", "mips64", "mips64le", "ppc64", "ppc64le", "s390x":
		// rotl_31 is only defined on 64-bit architectures
		want["runtime"] = append(want["runtime"], "rotl_31")
	}

	notInlinedReason := make(map[string]string)
	pkgs := make([]string, 0, len(want))
	for pname, fnames := range want {
		pkgs = append(pkgs, pname)
		for _, fname := range fnames {
			fullName := pname + "." + fname
			if _, ok := notInlinedReason[fullName]; ok {
				t.Errorf("duplicate func: %s", fullName)
			}
			notInlinedReason[fullName] = "unknown reason"
		}
	}

	args := append([]string{"build", "-a", "-gcflags=-m -m"}, pkgs...)
	cmd := testenv.CleanCmdEnv(exec.Command(testenv.GoToolPath(t), args...))
	pr, pw := io.Pipe()
	cmd.Stdout = pw
	cmd.Stderr = pw
	cmdErr := make(chan error, 1)
	go func() {
		cmdErr <- cmd.Run()
		pw.Close()
	}()
	scanner := bufio.NewScanner(pr)
	curPkg := ""
	canInline := regexp.MustCompile(`: can inline ([^ ]*)`)
	cannotInline := regexp.MustCompile(`: cannot inline ([^ ]*): (.*)`)
	for scanner.Scan() {
		line := scanner.Text()
		if strings.HasPrefix(line, "# ") {
			curPkg = line[2:]
			continue
		}
		if m := canInline.FindStringSubmatch(line); m != nil {
			fname := m[1]
			delete(notInlinedReason, curPkg+"."+fname)
			continue
		}
		if m := cannotInline.FindStringSubmatch(line); m != nil {
			fname, reason := m[1], m[2]
			fullName := curPkg + "." + fname
			if _, ok := notInlinedReason[fullName]; ok {
				// cmd/compile gave us a reason why
				notInlinedReason[fullName] = reason
			}
			continue
		}
	}
	if err := <-cmdErr; err != nil {
		t.Fatal(err)
	}
	if err := scanner.Err(); err != nil {
		t.Fatal(err)
	}
	for fullName, reason := range notInlinedReason {
		t.Errorf("%s was not inlined: %s", fullName, reason)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"regexp"

	"github.com/Smartling/api-sdk-go"
	"github.com/reconquest/hierr-go"
	"github.com/tcnksm/go-input"
)

func doInit(config Config, args map[string]interface{}) error {
	fmt.Printf("Generating %s...\n\n", config.path)

	prompt := func(
		message string,
		value interface{},
		zero bool,
		hidden bool,
		variable interface{},
	) {
		display := regexp.MustCompile(`^(.{1,3}).*$`).ReplaceAllString(
			fmt.Sprint(value),
			`$1***`,
		)

		if !zero {
			message = fmt.Sprintf("%s [default is %q]", message, display)
		}

		read, err := input.DefaultUI().Ask(
			message,
			&input.Options{
				Default:     fmt.Sprint(value),
				Hide:        hidden,
				HideDefault: true,
			},
		)
		if err != nil {
			if input.ErrInterrupted == err {
				os.Exit(1)
			}
		}

		fmt.Sscanln(read, variable)
	}

	var input Config

	prompt(
		"Smartling API V2.0 User Identifier",
		config.UserID,
		config.UserID == "",
		false,
		&input.UserID,
	)

	if input.UserID != "" {
		config.UserID = input.UserID
	}

	prompt(
		"Smartling API V2.0 Token Secret",
		config.Secret,
		config.Secret == "",
		true,
		&input.Secret,
	)

	if input.Secret != "" {
		config.Secret = input.Secret
	}

	prompt(
		"Account ID (optional)",
		config.AccountID,
		config.AccountID == "",
		false,
		&input.AccountID,
	)

	if input.AccountID != "" {
		config.AccountID = input.AccountID
	}

	prompt(
		"Project ID",
		config.ProjectID,
		config.ProjectID == "",
		false,
		&input.ProjectID,
	)

	if input.ProjectID != "" {
		config.ProjectID = input.ProjectID
	}

	var result bytes.Buffer
	err := configTemplate.Execute(&result, config)
	if err != nil {
		return hierr.Errorf(
			err,
			"unable to compile config template",
		)
	}

	logger.HideFromConfig(config)

	fmt.Println("Testing connection to Smartling API...")

	client, err := createClient(config, args)
	if err != nil {
		return hierr.Errorf(
			err,
			"unable to create client for testing connection",
		)
	}

	err = client.Authenticate()
	if err != nil {
		if _, ok := err.(smartling.NotAuthorizedError); ok {
			return NewError(
				errors.New("Not authorized."),
				"Your credentials are invalid. Double check them and run "+
					"init command again.",
			)
		} else {
			return NewError(
				hierr.Errorf(err, "failure while testing connection"),
				"Contact developer for more info.",
			)
		}
	}

	fmt.Println("Connection is successfull.")

	if args["--dry-run"].(bool) {
		fmt.Println()
		fmt.Println("Dry run is specified, do not writing config.")
		fmt.Println("New config is displayed below.")
		fmt.Println()

		fmt.Println(result.String())
	} else {
		err = ioutil.WriteFile(config.path, result.Bytes(), 0644)
		if err != nil {
			return hierr.Errorf(
				err,
				"unable to write new config file",
			)
		}
	}

	return nil
}
<commit_msg>Update do_init.go<commit_after>package main

import (
	"bytes"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"regexp"

	"github.com/Smartling/api-sdk-go"
	"github.com/reconquest/hierr-go"
	"github.com/tcnksm/go-input"
)

func doInit(config Config, args map[string]interface{}) error {
	fmt.Printf("Generating %s...\n\n", config.path)

	prompt := func(
		message string,
		value interface{},
		zero bool,
		hidden bool,
		variable interface{},
	) {
		display := regexp.MustCompile(`^(.{1,3}).*$`).ReplaceAllString(
			fmt.Sprint(value),
			`$1***`,
		)

		if !zero {
			message = fmt.Sprintf("%s [default is %q]", message, display)
		}

		read, err := input.DefaultUI().Ask(
			message,
			&input.Options{
				Default:     fmt.Sprint(value),
				Hide:        hidden,
				HideDefault: true,
			},
		)
		if err != nil {
			if input.ErrInterrupted == err {
				os.Exit(1)
			}
		}

		fmt.Sscanln(read, variable)
	}

	var input Config

	prompt(
		"Smartling API V2.0 User Identifier",
		config.UserID,
		config.UserID == "",
		false,
		&input.UserID,
	)

	if input.UserID != "" {
		config.UserID = input.UserID
	}

	prompt(
		"Smartling API V2.0 Token Secret",
		config.Secret,
		config.Secret == "",
		true,
		&input.Secret,
	)

	if input.Secret != "" {
		config.Secret = input.Secret
	}

	prompt(
		"Account ID (optional)",
		config.AccountID,
		config.AccountID == "",
		false,
		&input.AccountID,
	)

	if input.AccountID != "" {
		config.AccountID = input.AccountID
	}

	prompt(
		"Project ID",
		config.ProjectID,
		config.ProjectID == "",
		false,
		&input.ProjectID,
	)

	if input.ProjectID != "" {
		config.ProjectID = input.ProjectID
	}

	var result bytes.Buffer
	err := configTemplate.Execute(&result, config)
	if err != nil {
		return hierr.Errorf(
			err,
			"unable to compile config template",
		)
	}

	logger.HideFromConfig(config)

	fmt.Println("Testing connection to Smartling API...")

	client, err := createClient(config, args)
	if err != nil {
		return hierr.Errorf(
			err,
			"unable to create client for testing connection",
		)
	}

	err = client.Authenticate()
	if err != nil {
		if _, ok := err.(smartling.NotAuthorizedError); ok {
			return NewError(
				errors.New("Not authorized."),
				"Your credentials are invalid. Double check them and run "+
					"init command again.",
			)
		} else {
			return NewError(
				hierr.Errorf(err, "failure while testing connection"),
				"Contact developer for more info.",
			)
		}
	}

	fmt.Println("Connection is successful.")

	if args["--dry-run"].(bool) {
		fmt.Println()
		fmt.Println("Configured for Dry run. Not writing config file.")
		fmt.Println("New config is displayed below.")
		fmt.Println()

		fmt.Println(result.String())
	} else {
		err = ioutil.WriteFile(config.path, result.Bytes(), 0644)
		if err != nil {
			return hierr.Errorf(
				err,
				"unable to write new config file",
			)
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package perm

import (
	"bytes"
	"encoding/json"
	"fmt"
	"reflect"
	"strings"

	"github.com/abh/errorutil"
	"github.com/webx-top/echo"
	"github.com/webx-top/echo/param"
)

type BehaviorPerms map[string]Behavior

func (b BehaviorPerms) Add(be *Behavior, value ...interface{}) {
	if be == nil {
		return
	}
	bCopy := *be
	if len(value) > 0 {
		bCopy.Value = value[0]
	}
	if bCopy.Value == nil && be.valueInitor != nil {
		bCopy.Value = be.valueInitor()
	}
	b[be.Name] = bCopy
}

func (b BehaviorPerms) Get(name string) Behavior {
	r, _ := b[name]
	return r
}

type CheckedBehavior struct {
	Value   interface{}
	Checked bool
}

func (b BehaviorPerms) CheckBehavior(perm string) *CheckedBehavior {
	v, y := b[perm]
	if !y {
		return &CheckedBehavior{}
	}
	return &CheckedBehavior{Value: v.Value, Checked: true}
}

func JSONBytesParseError(err error, jsonBytes []byte) error {
	var offset int64 = -1
	switch rErr := err.(type) {
	case *json.UnmarshalTypeError:
		offset = rErr.Offset
	case *json.SyntaxError:
		offset = rErr.Offset
	}
	if offset > -1 {
		byteReader := bytes.NewReader(jsonBytes)
		line, col, highlight := errorutil.HighlightBytePosition(byteReader, offset)
		extra := fmt.Sprintf(":\nError at line %d, column %d (offset %d):\n%s",
			line, col, offset, highlight)
		return fmt.Errorf("error parsing json object%s\n%w",
			extra, err)
	}
	return fmt.Errorf("%w: %s", err, string(jsonBytes))
}

func ParseBehavior(permBehaviors string, behaviors *Behaviors) (BehaviorPerms, error) {
	b := BehaviorPerms{}
	if len(permBehaviors) == 0 {
		return b, nil
	}
	data := map[string]json.RawMessage{}
	dataBytes := []byte(permBehaviors)
	if err := json.Unmarshal(dataBytes, &data); err != nil {
		err = JSONBytesParseError(err, dataBytes)
		return b, err
	}
	for name, jsonBytes := range data {
		if len(jsonBytes) == 0 {
			continue
		}
		item := behaviors.GetItem(name)
		if item == nil {
			continue
		}
		behavior, ok := item.X.(*Behavior)
		if !ok {
			continue
		}
		var value interface{}
		if behavior.Value != nil {
			rv := reflect.New(reflect.Indirect(reflect.ValueOf(behavior.Value)).Type())
			if rv.CanInterface() {
				value = rv.Interface()
			}
		}
		if value == nil && behavior.valueInitor != nil {
			value = behavior.valueInitor()
		}
		if err := json.Unmarshal(jsonBytes, &value); err != nil {
			err = JSONBytesParseError(err, jsonBytes)
			return b, err
		}
		b.Add(behavior, value)
	}
	return b, nil
}

func SerializeBehaviorValues(permBehaviors map[string][]string, behaviors *Behaviors) (string, error) {
	data := echo.H{}
	for name, values := range permBehaviors {
		item := behaviors.GetItem(name)
		if item == nil {
			continue
		}
		behavior, ok := item.X.(*Behavior)
		if !ok {
			continue
		}
		if behavior.formValueEncoder != nil {
			if val, err := behavior.formValueEncoder(values); err == nil {
				data[name] = val
			}
			continue
		}
		switch behavior.ValueType {
		case `list`:
			data[name] = strings.Join(values, ",")
		case `number`, `float64`, `float`:
			if len(values) > 0 && len(values[0]) > 0 {
				data[name] = param.AsFloat64(values[0])
			}
		case `float32`:
			if len(values) > 0 && len(values[0]) > 0 {
				data[name] = param.AsFloat32(values[0])
			}
		case `int32`:
			if len(values) > 0 && len(values[0]) > 0 {
				data[name] = param.AsInt32(values[0])
			}
		case `int`:
			if len(values) > 0 && len(values[0]) > 0 {
				data[name] = param.AsInt(values[0])
			}
		case `int64`:
			if len(values) > 0 && len(values[0]) > 0 {
				data[name] = param.AsInt64(values[0])
			}
		case `uint32`:
			if len(values) > 0 && len(values[0]) > 0 {
				data[name] = param.AsUint32(values[0])
			}
		case `uint`:
			if len(values) > 0 && len(values[0]) > 0 {
				data[name] = param.AsUint(values[0])
			}
		case `uint64`:
			if len(values) > 0 && len(values[0]) > 0 {
				data[name] = param.AsUint64(values[0])
			}
		case `json`:
			if len(values) > 0 && len(values[0]) > 0 {
				var recv interface{}
				if behavior.valueInitor != nil {
					recv = behavior.valueInitor()
				} else if behavior.Value != nil {
					v := reflect.Indirect(reflect.ValueOf(behavior.Value))
					if v.CanInterface() {
						newValue := reflect.New(v.Type()).Interface()
						recv = &newValue
					}
				}
				if recv == nil {
					recv = &echo.H{}
				}
				dataBytes := []byte(values[0])
				if err := json.Unmarshal(dataBytes, recv); err != nil {
					return ``, JSONBytesParseError(err, dataBytes)
				}
				data[name] = recv
			}
		case `slice`:
			data[name] = values
		default:
			if len(values) > 0 && len(values[0]) > 0 {
				if behavior.valueInitor != nil || behavior.Value != nil {
					var recv interface{}
					var v reflect.Value
					if behavior.valueInitor != nil {
						recv = behavior.valueInitor()
						v = reflect.Indirect(reflect.ValueOf(recv))
					} else {
						v = reflect.Indirect(reflect.ValueOf(behavior.Value))
						if v.CanInterface() {
							newValue := reflect.New(v.Type()).Interface()
							recv = &newValue
						}
					}
					if recv == nil {
						data[name] = values[0]
					} else {
						switch v.Kind() {
						case reflect.Slice, reflect.Map, reflect.Struct, reflect.Array:
							dataBytes := []byte(values[0])
							if err := json.Unmarshal(dataBytes, recv); err != nil {
								return ``, JSONBytesParseError(err, dataBytes)
							}
							data[name] = recv
						case reflect.Int:
							data[name] = param.AsInt(values[0])
						case reflect.Int8:
							data[name] = param.AsInt8(values[0])
						case reflect.Int16:
							data[name] = param.AsInt16(values[0])
						case reflect.Int32:
							data[name] = param.AsInt32(values[0])
						case reflect.Int64:
							data[name] = param.AsInt64(values[0])
						case reflect.Uint8:
							data[name] = param.AsUint8(values[0])
						case reflect.Uint16:
							data[name] = param.AsUint16(values[0])
						case reflect.Uint:
							data[name] = param.AsUint(values[0])
						case reflect.Uint32:
							data[name] = param.AsUint32(values[0])
						case reflect.Uint64:
							data[name] = param.AsUint64(values[0])
						case reflect.Float32:
							data[name] = param.AsFloat32(values[0])
						case reflect.Float64:
							data[name] = param.AsFloat64(values[0])
						case reflect.Bool:
							data[name] = param.AsBool(values[0])
						default:
							data[name] = values[0]
						}
					}
				} else {
					data[name] = values[0]
				}
			}
		}
	}
	b, err := json.Marshal(data)
	if err != nil {
		return "", err
	}
	return string(b), nil
}
<commit_msg>update<commit_after>package perm

import (
	"bytes"
	"encoding/json"
	"fmt"
	"reflect"
	"strings"

	"github.com/abh/errorutil"
	"github.com/webx-top/echo"
	"github.com/webx-top/echo/param"
)

type BehaviorPerms map[string]Behavior

func (b BehaviorPerms) Add(be *Behavior, value ...interface{}) {
	if be == nil {
		return
	}
	bCopy := *be
	if len(value) > 0 {
		bCopy.Value = value[0]
	}
	if bCopy.Value == nil && be.valueInitor != nil {
		bCopy.Value = be.valueInitor()
	}
	b[be.Name] = bCopy
}

func (b BehaviorPerms) Get(name string) Behavior {
	r, _ := b[name]
	return r
}

type CheckedBehavior struct {
	Value   interface{}
	Checked bool
}

func (b BehaviorPerms) CheckBehavior(perm string) *CheckedBehavior {
	v, y := b[perm]
	if !y {
		return &CheckedBehavior{}
	}
	return &CheckedBehavior{Value: v.Value, Checked: true}
}

func JSONBytesParseError(err error, jsonBytes []byte) error {
	var offset int64 = -1
	switch rErr := err.(type) {
	case *json.UnmarshalTypeError:
		offset = rErr.Offset
	case *json.SyntaxError:
		offset = rErr.Offset
	}
	if offset > -1 {
		byteReader := bytes.NewReader(jsonBytes)
		line, col, highlight := errorutil.HighlightBytePosition(byteReader, offset)
		extra := fmt.Sprintf(":\nError at line %d, column %d (offset %d):\n%s",
			line, col, offset, highlight)
		return fmt.Errorf("error parsing json object%s\n%w",
			extra, err)
	}
	return fmt.Errorf("%w: %s", err, string(jsonBytes))
}

func ParseBehavior(permBehaviors string, behaviors *Behaviors) (BehaviorPerms, error) {
	b := BehaviorPerms{}
	if len(permBehaviors) == 0 {
		return b, nil
	}
	data := map[string]json.RawMessage{}
	dataBytes := []byte(permBehaviors)
	if err := json.Unmarshal(dataBytes, &data); err != nil {
		err = JSONBytesParseError(err, dataBytes)
		return b, err
	}
	for name, jsonBytes := range data {
		if len(jsonBytes) == 0 {
			continue
		}
		item := behaviors.GetItem(name)
		if item == nil {
			continue
		}
		behavior, ok := item.X.(*Behavior)
		if !ok {
			continue
		}
		var value interface{}
		if behavior.Value != nil {
			rv := reflect.New(reflect.Indirect(reflect.ValueOf(behavior.Value)).Type())
			if rv.CanInterface() {
				value = rv.Interface()
			}
		}
		if value == nil && behavior.valueInitor != nil {
			value = behavior.valueInitor()
		}
		if err := json.Unmarshal(jsonBytes, &value); err != nil {
			err = JSONBytesParseError(err, jsonBytes)
			return b, err
		}
		b.Add(behavior, value)
	}
	return b, nil
}

func SerializeBehaviorValues(permBehaviors map[string][]string, behaviors *Behaviors) (string, error) {
	data := echo.H{}
	for name, values := range permBehaviors {
		item := behaviors.GetItem(name)
		if item == nil {
			continue
		}
		behavior, ok := item.X.(*Behavior)
		if !ok {
			continue
		}
		if behavior.formValueEncoder != nil {
			if val, err := behavior.formValueEncoder(values); err == nil {
				data[name] = val
			}
			continue
		}
		switch behavior.ValueType {
		case `list`:
			data[name] = strings.Join(values, ",")
		case `number`, `float64`, `float`:
			if len(values) > 0 && len(values[0]) > 0 {
				data[name] = param.AsFloat64(values[0])
			}
		case `float32`:
			if len(values) > 0 && len(values[0]) > 0 {
				data[name] = param.AsFloat32(values[0])
			}
		case `int32`:
			if len(values) > 0 && len(values[0]) > 0 {
				data[name] = param.AsInt32(values[0])
			}
		case `int`:
			if len(values) > 0 && len(values[0]) > 0 {
				data[name] = param.AsInt(values[0])
			}
		case `int64`:
			if len(values) > 0 && len(values[0]) > 0 {
				data[name] = param.AsInt64(values[0])
			}
		case `uint32`:
			if len(values) > 0 && len(values[0]) > 0 {
				data[name] = param.AsUint32(values[0])
			}
		case `uint`:
			if len(values) > 0 && len(values[0]) > 0 {
				data[name] = param.AsUint(values[0])
			}
		case `uint64`:
			if len(values) > 0 && len(values[0]) > 0 {
				data[name] = param.AsUint64(values[0])
			}
		case `json`:
			if len(values) > 0 && len(values[0]) > 0 {
				var recv interface{}
				if behavior.valueInitor != nil {
					recv = behavior.valueInitor()
				} else if behavior.Value != nil {
					v := reflect.Indirect(reflect.ValueOf(behavior.Value))
					if v.CanInterface() {
						newValue := reflect.New(v.Type()).Interface()
						recv = &newValue
					}
				}
				if recv == nil {
					recv = &echo.H{}
				}
				dataBytes := []byte(values[0])
				if err := json.Unmarshal(dataBytes, recv); err != nil {
					return ``, JSONBytesParseError(err, dataBytes)
				}
				data[name] = recv
			}
		case `slice`:
			data[name] = values
		default:
			if len(values) > 0 {
				if behavior.valueInitor != nil || behavior.Value != nil {
					var recv interface{}
					var v reflect.Value
					if behavior.valueInitor != nil {
						recv = behavior.valueInitor()
						v = reflect.Indirect(reflect.ValueOf(recv))
					} else {
						v = reflect.Indirect(reflect.ValueOf(behavior.Value))
						if v.CanInterface() {
							newValue := reflect.New(v.Type()).Interface()
							recv = &newValue
						}
					}
					if recv == nil {
						data[name] = values[0]
					} else {
						switch v.Kind() {
						case reflect.Slice, reflect.Map, reflect.Struct, reflect.Array:
							if len(values[0]) > 0 {
								dataBytes := []byte(values[0])
								if err := json.Unmarshal(dataBytes, recv); err != nil {
									return ``, JSONBytesParseError(err, dataBytes)
								}
								data[name] = recv
							}
						case reflect.Int:
							if len(values[0]) > 0 {
								data[name] = param.AsInt(values[0])
							}
						case reflect.Int8:
							if len(values[0]) > 0 {
								data[name] = param.AsInt8(values[0])
							}
						case reflect.Int16:
							if len(values[0]) > 0 {
								data[name] = param.AsInt16(values[0])
							}
						case reflect.Int32:
							if len(values[0]) > 0 {
								data[name] = param.AsInt32(values[0])
							}
						case reflect.Int64:
							if len(values[0]) > 0 {
								data[name] = param.AsInt64(values[0])
							}
						case reflect.Uint8:
							if len(values[0]) > 0 {
								data[name] = param.AsUint8(values[0])
							}
						case reflect.Uint16:
							if len(values[0]) > 0 {
								data[name] = param.AsUint16(values[0])
							}
						case reflect.Uint:
							if len(values[0]) > 0 {
								data[name] = param.AsUint(values[0])
							}
						case reflect.Uint32:
							if len(values[0]) > 0 {
								data[name] = param.AsUint32(values[0])
							}
						case reflect.Uint64:
							if len(values[0]) > 0 {
								data[name] = param.AsUint64(values[0])
							}
						case reflect.Float32:
							if len(values[0]) > 0 {
								data[name] = param.AsFloat32(values[0])
							}
						case reflect.Float64:
							if len(values[0]) > 0 {
								data[name] = param.AsFloat64(values[0])
							}
						case reflect.Bool:
							if len(values[0]) > 0 {
								data[name] = param.AsBool(values[0])
							}
						default:
							data[name] = values[0]
						}
					}
				} else {
					data[name] = values[0]
				}
			}
		}
	}
	b, err := json.Marshal(data)
	if err != nil {
		return "", err
	}
	return string(b), nil
}
<|endoftext|>


<commit_before>package piwik

import (
	"log"
	"net/http"
	"net/url"
	"strings"

	"gopkg.in/macaron.v1"
)

// Options configures the piwik middleware
type Options struct {
	// The URL of your piwik installation (with our without /piwik.php)
	PiwikURL string

	// Ignore the Do not Track header that is sent by the browser. This is not recommended
	IgnoreDoNotTrack bool

	// The ID of the website in piwik
	WebsiteID string

	// The piwik API's access token
	Token string
}

func prepareOptions(options []Options) Options {
	var opt Options
	if len(options) > 0 {
		opt = options[0]
	}
	opt.PiwikURL = strings.TrimSuffix(strings.TrimSuffix(opt.PiwikURL, "piwik.php"), "/") + "/piwik.php?"

	return opt
}

// Piwik returns a new macaron.Handler that sends every request to the piwik tracking API
func Piwik(options ...Options) macaron.Handler {
	opt := prepareOptions(options)

	return func(ctx *macaron.Context, logger *log.Logger) {
		if !opt.IgnoreDoNotTrack && ctx.Req.Header.Get("DNT") == "1" {
			return
		}

		params := make(url.Values)
		params.Set("idsite", opt.WebsiteID)
		params.Set("rec", "1")

		// using http cause its only purpose is logging
		params.Set("url", "http://"+ctx.Req.Host+ctx.Req.URL.String())
		params.Set("apiv", "1")

		h := ctx.Req.Header
		params.Set("urlref", h.Get("Referer"))
		params.Set("ua", h.Get("User-Agent"))
		params.Set("lang", h.Get("Accept-Language"))

		params.Set("token_auth", opt.Token)
		ip := ctx.RemoteAddr()
		if strings.Contains(ip, ",") {
			ipv6 := strings.Split(ip, ",")
			ip = strings.TrimPrefix(strings.TrimSpace(ipv6[0]), "::ffff:")
		}

		params.Set("cip", ip)

		// collecting data is finished, go async now
		go func() {
			res, err := http.Get(opt.PiwikURL + params.Encode())
			if err != nil {
				logger.Println("Error contacting piwik:", err)
			}
			if res.StatusCode != http.StatusOK {
				logger.Println("Error contacting piwik:", res.Status)
			}
		}()

		ctx.Next()
	}
}
<commit_msg>fixed url behind reverse proxy<commit_after>package piwik

import (
	"log"
	"net/http"
	"net/url"
	"strings"

	"gopkg.in/macaron.v1"
)

// Options configures the piwik middleware
type Options struct {
	// The URL of your piwik installation (with our without /piwik.php)
	PiwikURL string

	// Ignore the Do not Track header that is sent by the browser. This is not recommended
	IgnoreDoNotTrack bool

	// The ID of the website in piwik
	WebsiteID string

	// The piwik API's access token
	Token string
}

func prepareOptions(options []Options) Options {
	var opt Options
	if len(options) > 0 {
		opt = options[0]
	}
	opt.PiwikURL = strings.TrimSuffix(strings.TrimSuffix(opt.PiwikURL, "piwik.php"), "/") + "/piwik.php?"

	return opt
}

// Piwik returns a new macaron.Handler that sends every request to the piwik tracking API
func Piwik(options ...Options) macaron.Handler {
	opt := prepareOptions(options)

	return func(ctx *macaron.Context, logger *log.Logger) {
		h := ctx.Req.Header
		if !opt.IgnoreDoNotTrack && h.Get("DNT") == "1" {
			return
		}

		params := make(url.Values)
		params.Set("idsite", opt.WebsiteID)
		params.Set("rec", "1")

		proto := h.Get("X-Forwarded-Proto")
		if proto == "" {
			if ctx.Req.TLS != nil {
				proto = "https"
			} else {
				proto = "http"
			}
		}
		host := h.Get("X-Forwarded-Host")
		if host == "" {
			host = ctx.Req.Host
		}

		params.Set("url", proto+"://"+host+ctx.Req.URL.String())
		params.Set("apiv", "1")
		params.Set("urlref", h.Get("Referer"))
		params.Set("ua", h.Get("User-Agent"))
		params.Set("lang", h.Get("Accept-Language"))

		ip := ctx.RemoteAddr()
		if strings.Contains(ip, ",") {
			ipv6 := strings.Split(ip, ",")
			ip = strings.TrimPrefix(strings.TrimSpace(ipv6[0]), "::ffff:")
		}
		params.Set("token_auth", opt.Token)
		params.Set("cip", ip)

		// collecting data is finished, go async now
		go func() {
			res, err := http.Get(opt.PiwikURL + params.Encode())
			if err != nil {
				logger.Println("Error contacting piwik:", err)
			}
			if res.StatusCode != http.StatusOK {
				logger.Println("Error contacting piwik:", res.Status)
			}
		}()

		ctx.Next()
	}
}
<|endoftext|>


<commit_before>//  Copyright (c) 2014 Couchbase, Inc.
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the
//  License. You may obtain a copy of the License at
//    http://www.apache.org/licenses/LICENSE-2.0
//  Unless required by applicable law or agreed to in writing,
//  software distributed under the License is distributed on an "AS
//  IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
//  express or implied. See the License for the specific language
//  governing permissions and limitations under the License.

package cbft

import (
	"io"
	"testing"
)

type TestDest struct{}

func (s *TestDest) Close() error {
	return nil
}

func (s *TestDest) OnDataUpdate(partition string,
	key []byte, seq uint64, val []byte) error {
	return nil
}

func (s *TestDest) OnDataDelete(partition string,
	key []byte, seq uint64) error {
	return nil
}

func (s *TestDest) OnSnapshotStart(partition string,
	snapStart, snapEnd uint64) error {
	return nil
}

func (s *TestDest) SetOpaque(partition string,
	value []byte) error {
	return nil
}

func (s *TestDest) GetOpaque(partition string) (
	value []byte, lastSeq uint64, err error) {
	return nil, 0, nil
}

func (s *TestDest) Rollback(partition string,
	rollbackSeq uint64) error {
	return nil
}

func (s *TestDest) ConsistencyWait(partition string,
	consistencyLevel string,
	consistencySeq uint64,
	cancelCh <-chan bool) error {
	return nil
}

func (t *TestDest) Count(pindex *PIndex,
	cancelCh <-chan bool) (uint64, error) {
	return 0, nil
}

func (t *TestDest) Query(pindex *PIndex, req []byte, res io.Writer,
	cancelCh <-chan bool) error {
	return nil
}

func (t *TestDest) Stats(w io.Writer) error {
	return nil
}

func TestBasicPartitionFunc(t *testing.T) {
	dest := &TestDest{}
	dest2 := &TestDest{}
	s, err := BasicPartitionFunc("", nil, map[string]Dest{"": dest})
	if err != nil || s != dest {
		t.Errorf("expected BasicPartitionFunc to work")
	}
	s, err = BasicPartitionFunc("foo", nil, map[string]Dest{"": dest})
	if err != nil || s != dest {
		t.Errorf("expected BasicPartitionFunc to hit the catch-all dest")
	}
	s, err = BasicPartitionFunc("", nil, map[string]Dest{"foo": dest})
	if err == nil || s == dest {
		t.Errorf("expected BasicPartitionFunc to not work")
	}
	s, err = BasicPartitionFunc("foo", nil, map[string]Dest{"foo": dest})
	if err != nil || s != dest {
		t.Errorf("expected BasicPartitionFunc to work on partition hit")
	}
	s, err = BasicPartitionFunc("foo", nil, map[string]Dest{"foo": dest, "": dest2})
	if err != nil || s != dest {
		t.Errorf("expected BasicPartitionFunc to work on partition hit")
	}
}
<commit_msg>TestErrorOnlyDestProviderWithDestForwarder()<commit_after>//  Copyright (c) 2014 Couchbase, Inc.
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the
//  License. You may obtain a copy of the License at
//    http://www.apache.org/licenses/LICENSE-2.0
//  Unless required by applicable law or agreed to in writing,
//  software distributed under the License is distributed on an "AS
//  IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
//  express or implied. See the License for the specific language
//  governing permissions and limitations under the License.

package cbft

import (
	"fmt"
	"io"
	"testing"
)

type TestDest struct{}

func (s *TestDest) Close() error {
	return nil
}

func (s *TestDest) OnDataUpdate(partition string,
	key []byte, seq uint64, val []byte) error {
	return nil
}

func (s *TestDest) OnDataDelete(partition string,
	key []byte, seq uint64) error {
	return nil
}

func (s *TestDest) OnSnapshotStart(partition string,
	snapStart, snapEnd uint64) error {
	return nil
}

func (s *TestDest) SetOpaque(partition string,
	value []byte) error {
	return nil
}

func (s *TestDest) GetOpaque(partition string) (
	value []byte, lastSeq uint64, err error) {
	return nil, 0, nil
}

func (s *TestDest) Rollback(partition string,
	rollbackSeq uint64) error {
	return nil
}

func (s *TestDest) ConsistencyWait(partition string,
	consistencyLevel string,
	consistencySeq uint64,
	cancelCh <-chan bool) error {
	return nil
}

func (t *TestDest) Count(pindex *PIndex,
	cancelCh <-chan bool) (uint64, error) {
	return 0, nil
}

func (t *TestDest) Query(pindex *PIndex, req []byte, res io.Writer,
	cancelCh <-chan bool) error {
	return nil
}

func (t *TestDest) Stats(w io.Writer) error {
	return nil
}

func TestBasicPartitionFunc(t *testing.T) {
	dest := &TestDest{}
	dest2 := &TestDest{}
	s, err := BasicPartitionFunc("", nil, map[string]Dest{"": dest})
	if err != nil || s != dest {
		t.Errorf("expected BasicPartitionFunc to work")
	}
	s, err = BasicPartitionFunc("foo", nil, map[string]Dest{"": dest})
	if err != nil || s != dest {
		t.Errorf("expected BasicPartitionFunc to hit the catch-all dest")
	}
	s, err = BasicPartitionFunc("", nil, map[string]Dest{"foo": dest})
	if err == nil || s == dest {
		t.Errorf("expected BasicPartitionFunc to not work")
	}
	s, err = BasicPartitionFunc("foo", nil, map[string]Dest{"foo": dest})
	if err != nil || s != dest {
		t.Errorf("expected BasicPartitionFunc to work on partition hit")
	}
	s, err = BasicPartitionFunc("foo", nil, map[string]Dest{"foo": dest, "": dest2})
	if err != nil || s != dest {
		t.Errorf("expected BasicPartitionFunc to work on partition hit")
	}
}

type ErrorOnlyDestProvider struct{}

func (dp *ErrorOnlyDestProvider) Dest(partition string) (Dest, error) {
	return nil, fmt.Errorf("always error for testing")
}

func (dp *ErrorOnlyDestProvider) Count(pindex *PIndex,
	cancelCh <-chan bool) (uint64, error) {
	return 0, fmt.Errorf("always error for testing")
}

func (dp *ErrorOnlyDestProvider) Query(pindex *PIndex, req []byte, res io.Writer,
	cancelCh <-chan bool) error {
	return fmt.Errorf("always error for testing")
}

func (dp *ErrorOnlyDestProvider) Stats(io.Writer) error {
	return fmt.Errorf("always error for testing")
}

func (dp *ErrorOnlyDestProvider) Close() error {
	return fmt.Errorf("always error for testing")
}

func TestErrorOnlyDestProviderWithDestForwarder(t *testing.T) {
	df := &DestForwarder{&ErrorOnlyDestProvider{}}
	if df.OnDataUpdate("", nil, 0, nil) == nil {
		t.Errorf("expected err")
	}
	if df.OnDataDelete("", nil, 0) == nil {
		t.Errorf("expected err")
	}
	if df.OnSnapshotStart("", 0, 0) == nil {
		t.Errorf("expected err")
	}
	if df.SetOpaque("", nil) == nil {
		t.Errorf("expected err")
	}
	value, lastSeq, err := df.GetOpaque("")
	if err == nil || value != nil || lastSeq != 0 {
		t.Errorf("expected err")
	}
	if df.Rollback("", 0) == nil {
		t.Errorf("expected err")
	}
	if df.ConsistencyWait("", "", 0, nil) == nil {
		t.Errorf("expected err")
	}
}
<|endoftext|>


<commit_before>package rpc

import (
	"encoding/json"
	"strconv"
	"testing"

	"github.com/ethereum/go-ethereum/common/compiler"
	"github.com/ethereum/go-ethereum/eth"
	"github.com/ethereum/go-ethereum/xeth"
)

func TestWeb3Sha3(t *testing.T) {
	jsonstr := `{"jsonrpc":"2.0","method":"web3_sha3","params":["0x68656c6c6f20776f726c64"],"id":64}`
	expected := "0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad"

	api := &EthereumApi{}

	var req RpcRequest
	json.Unmarshal([]byte(jsonstr), &req)

	var response interface{}
	_ = api.GetRequestReply(&req, &response)

	if response.(string) != expected {
		t.Errorf("Expected %s got %s", expected, response)
	}
}

const solcVersion = "0.9.23"

func TestCompileSolidity(t *testing.T) {

	solc, err := compiler.New("")
	if solc == nil {
		t.Skip("no solc found: skip")
	} else if solc.Version() != solcVersion {
		t.Logf("WARNING: solc different version found (%v, test written for %v, may need to update)", solc.Version(), solcVersion)
	}
	source := `contract test {\n` +
		"   /// @notice Will multiply `a` by 7." + `\n` +
		`   function multiply(uint a) returns(uint d) {\n` +
		`       return a * 7;\n` +
		`   }\n` +
		`}\n`

	jsonstr := `{"jsonrpc":"2.0","method":"eth_compileSolidity","params":["` + source + `"],"id":64}`

	expCode := "0x605880600c6000396000f3006000357c010000000000000000000000000000000000000000000000000000000090048063c6888fa114602e57005b603d6004803590602001506047565b8060005260206000f35b60006007820290506053565b91905056"
	expAbiDefinition := `[{"constant":false,"inputs":[{"name":"a","type":"uint256"}],"name":"multiply","outputs":[{"name":"d","type":"uint256"}],"type":"function"}]`
	expUserDoc := `{"methods":{"multiply(uint256)":{"notice":"Will multiply ` + "`a`" + ` by 7."}}}`
	expDeveloperDoc := `{"methods":{}}`
	expCompilerVersion := solc.Version()
	expLanguage := "Solidity"
	expLanguageVersion := "0"
	expSource := source

	api := NewEthereumApi(xeth.NewTest(&eth.Ethereum{}, nil))

	var req RpcRequest
	json.Unmarshal([]byte(jsonstr), &req)

	var response interface{}
	err = api.GetRequestReply(&req, &response)
	if err != nil {
		t.Errorf("expected no error, got %v", err)
	}
	respjson, err := json.Marshal(response)
	if err != nil {
		t.Errorf("expected no error, got %v", err)
	}

	var contracts = make(map[string]*compiler.Contract)
	err = json.Unmarshal(respjson, &contracts)
	if err != nil {
		t.Errorf("expected no error, got %v", err)
	}

	if len(contracts) != 1 {
		t.Errorf("expected one contract, got %v", len(contracts))
	}

	contract := contracts["test"]

	if contract.Code != expCode {
		t.Errorf("Expected \n%s got \n%s", expCode, contract.Code)
	}

	if strconv.Quote(contract.Info.Source) != `"`+expSource+`"` {
		t.Errorf("Expected \n'%s' got \n'%s'", expSource, strconv.Quote(contract.Info.Source))
	}

	if contract.Info.Language != expLanguage {
		t.Errorf("Expected %s got %s", expLanguage, contract.Info.Language)
	}

	if contract.Info.LanguageVersion != expLanguageVersion {
		t.Errorf("Expected %s got %s", expLanguageVersion, contract.Info.LanguageVersion)
	}

	if contract.Info.CompilerVersion != expCompilerVersion {
		t.Errorf("Expected %s got %s", expCompilerVersion, contract.Info.CompilerVersion)
	}

	userdoc, err := json.Marshal(contract.Info.UserDoc)
	if err != nil {
		t.Errorf("expected no error, got %v", err)
	}

	devdoc, err := json.Marshal(contract.Info.DeveloperDoc)
	if err != nil {
		t.Errorf("expected no error, got %v", err)
	}

	abidef, err := json.Marshal(contract.Info.AbiDefinition)
	if err != nil {
		t.Errorf("expected no error, got %v", err)
	}

	if string(abidef) != expAbiDefinition {
		t.Errorf("Expected \n'%s' got \n'%s'", expAbiDefinition, string(abidef))
	}

	if string(userdoc) != expUserDoc {
		t.Errorf("Expected \n'%s' got \n'%s'", expUserDoc, string(userdoc))
	}

	if string(devdoc) != expDeveloperDoc {
		t.Errorf("Expected %s got %s", expDeveloperDoc, string(devdoc))
	}
}

// func TestDbStr(t *testing.T) {
// 	jsonput := `{"jsonrpc":"2.0","method":"db_putString","params":["testDB","myKey","myString"],"id":64}`
// 	jsonget := `{"jsonrpc":"2.0","method":"db_getString","params":["testDB","myKey"],"id":64}`
// 	expected := "myString"

// 	xeth := &xeth.XEth{}
// 	api := NewEthereumApi(xeth)
// 	var response interface{}

// 	var req RpcRequest
// 	json.Unmarshal([]byte(jsonput), &req)
// 	_ = api.GetRequestReply(&req, &response)

// 	json.Unmarshal([]byte(jsonget), &req)
// 	_ = api.GetRequestReply(&req, &response)

// 	if response.(string) != expected {
// 		t.Errorf("Expected %s got %s", expected, response)
// 	}
// }

// func TestDbHexStr(t *testing.T) {
// 	jsonput := `{"jsonrpc":"2.0","method":"db_putHex","params":["testDB","beefKey","0xbeef"],"id":64}`
// 	jsonget := `{"jsonrpc":"2.0","method":"db_getHex","params":["testDB","beefKey"],"id":64}`
// 	expected := "0xbeef"

// 	xeth := &xeth.XEth{}
// 	api := NewEthereumApi(xeth)
// 	defer api.db.Close()
// 	var response interface{}

// 	var req RpcRequest
// 	json.Unmarshal([]byte(jsonput), &req)
// 	_ = api.GetRequestReply(&req, &response)

// 	json.Unmarshal([]byte(jsonget), &req)
// 	_ = api.GetRequestReply(&req, &response)

// 	if response.(string) != expected {
// 		t.Errorf("Expected %s got %s", expected, response)
// 	}
// }

// func TestFilterClose(t *testing.T) {
// 	t.Skip()
// 	api := &EthereumApi{
// 		logs:     make(map[int]*logFilter),
// 		messages: make(map[int]*whisperFilter),
// 		quit:     make(chan struct{}),
// 	}

// 	filterTickerTime = 1
// 	api.logs[0] = &logFilter{}
// 	api.messages[0] = &whisperFilter{}
// 	var wg sync.WaitGroup
// 	wg.Add(1)
// 	go api.start()
// 	go func() {
// 		select {
// 		case <-time.After(500 * time.Millisecond):
// 			api.stop()
// 			wg.Done()
// 		}
// 	}()
// 	wg.Wait()
// 	if len(api.logs) != 0 {
// 		t.Error("expected logs to be empty")
// 	}

// 	if len(api.messages) != 0 {
// 		t.Error("expected messages to be empty")
// 	}
// }
<commit_msg>rpc: skip test if solc version doesn't match<commit_after>package rpc

import (
	"encoding/json"
	"strconv"
	"testing"

	"github.com/ethereum/go-ethereum/common/compiler"
	"github.com/ethereum/go-ethereum/eth"
	"github.com/ethereum/go-ethereum/xeth"
)

func TestWeb3Sha3(t *testing.T) {
	jsonstr := `{"jsonrpc":"2.0","method":"web3_sha3","params":["0x68656c6c6f20776f726c64"],"id":64}`
	expected := "0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad"

	api := &EthereumApi{}

	var req RpcRequest
	json.Unmarshal([]byte(jsonstr), &req)

	var response interface{}
	_ = api.GetRequestReply(&req, &response)

	if response.(string) != expected {
		t.Errorf("Expected %s got %s", expected, response)
	}
}

const solcVersion = "0.9.23"

func TestCompileSolidity(t *testing.T) {

	solc, err := compiler.New("")
	if solc == nil {
		t.Skip("no solc found: skip")
	} else if solc.Version() != solcVersion {
		t.Skip("WARNING: skipping test because of solc different version (%v, test written for %v, may need to update)", solc.Version(), solcVersion)
	}
	source := `contract test {\n` +
		"   /// @notice Will multiply `a` by 7." + `\n` +
		`   function multiply(uint a) returns(uint d) {\n` +
		`       return a * 7;\n` +
		`   }\n` +
		`}\n`

	jsonstr := `{"jsonrpc":"2.0","method":"eth_compileSolidity","params":["` + source + `"],"id":64}`

	expCode := "0x605880600c6000396000f3006000357c010000000000000000000000000000000000000000000000000000000090048063c6888fa114602e57005b603d6004803590602001506047565b8060005260206000f35b60006007820290506053565b91905056"
	expAbiDefinition := `[{"constant":false,"inputs":[{"name":"a","type":"uint256"}],"name":"multiply","outputs":[{"name":"d","type":"uint256"}],"type":"function"}]`
	expUserDoc := `{"methods":{"multiply(uint256)":{"notice":"Will multiply ` + "`a`" + ` by 7."}}}`
	expDeveloperDoc := `{"methods":{}}`
	expCompilerVersion := solc.Version()
	expLanguage := "Solidity"
	expLanguageVersion := "0"
	expSource := source

	api := NewEthereumApi(xeth.NewTest(&eth.Ethereum{}, nil))

	var req RpcRequest
	json.Unmarshal([]byte(jsonstr), &req)

	var response interface{}
	err = api.GetRequestReply(&req, &response)
	if err != nil {
		t.Errorf("expected no error, got %v", err)
	}
	respjson, err := json.Marshal(response)
	if err != nil {
		t.Errorf("expected no error, got %v", err)
	}

	var contracts = make(map[string]*compiler.Contract)
	err = json.Unmarshal(respjson, &contracts)
	if err != nil {
		t.Errorf("expected no error, got %v", err)
	}

	if len(contracts) != 1 {
		t.Errorf("expected one contract, got %v", len(contracts))
	}

	contract := contracts["test"]

	if contract.Code != expCode {
		t.Errorf("Expected \n%s got \n%s", expCode, contract.Code)
	}

	if strconv.Quote(contract.Info.Source) != `"`+expSource+`"` {
		t.Errorf("Expected \n'%s' got \n'%s'", expSource, strconv.Quote(contract.Info.Source))
	}

	if contract.Info.Language != expLanguage {
		t.Errorf("Expected %s got %s", expLanguage, contract.Info.Language)
	}

	if contract.Info.LanguageVersion != expLanguageVersion {
		t.Errorf("Expected %s got %s", expLanguageVersion, contract.Info.LanguageVersion)
	}

	if contract.Info.CompilerVersion != expCompilerVersion {
		t.Errorf("Expected %s got %s", expCompilerVersion, contract.Info.CompilerVersion)
	}

	userdoc, err := json.Marshal(contract.Info.UserDoc)
	if err != nil {
		t.Errorf("expected no error, got %v", err)
	}

	devdoc, err := json.Marshal(contract.Info.DeveloperDoc)
	if err != nil {
		t.Errorf("expected no error, got %v", err)
	}

	abidef, err := json.Marshal(contract.Info.AbiDefinition)
	if err != nil {
		t.Errorf("expected no error, got %v", err)
	}

	if string(abidef) != expAbiDefinition {
		t.Errorf("Expected \n'%s' got \n'%s'", expAbiDefinition, string(abidef))
	}

	if string(userdoc) != expUserDoc {
		t.Errorf("Expected \n'%s' got \n'%s'", expUserDoc, string(userdoc))
	}

	if string(devdoc) != expDeveloperDoc {
		t.Errorf("Expected %s got %s", expDeveloperDoc, string(devdoc))
	}
}

// func TestDbStr(t *testing.T) {
// 	jsonput := `{"jsonrpc":"2.0","method":"db_putString","params":["testDB","myKey","myString"],"id":64}`
// 	jsonget := `{"jsonrpc":"2.0","method":"db_getString","params":["testDB","myKey"],"id":64}`
// 	expected := "myString"

// 	xeth := &xeth.XEth{}
// 	api := NewEthereumApi(xeth)
// 	var response interface{}

// 	var req RpcRequest
// 	json.Unmarshal([]byte(jsonput), &req)
// 	_ = api.GetRequestReply(&req, &response)

// 	json.Unmarshal([]byte(jsonget), &req)
// 	_ = api.GetRequestReply(&req, &response)

// 	if response.(string) != expected {
// 		t.Errorf("Expected %s got %s", expected, response)
// 	}
// }

// func TestDbHexStr(t *testing.T) {
// 	jsonput := `{"jsonrpc":"2.0","method":"db_putHex","params":["testDB","beefKey","0xbeef"],"id":64}`
// 	jsonget := `{"jsonrpc":"2.0","method":"db_getHex","params":["testDB","beefKey"],"id":64}`
// 	expected := "0xbeef"

// 	xeth := &xeth.XEth{}
// 	api := NewEthereumApi(xeth)
// 	defer api.db.Close()
// 	var response interface{}

// 	var req RpcRequest
// 	json.Unmarshal([]byte(jsonput), &req)
// 	_ = api.GetRequestReply(&req, &response)

// 	json.Unmarshal([]byte(jsonget), &req)
// 	_ = api.GetRequestReply(&req, &response)

// 	if response.(string) != expected {
// 		t.Errorf("Expected %s got %s", expected, response)
// 	}
// }

// func TestFilterClose(t *testing.T) {
// 	t.Skip()
// 	api := &EthereumApi{
// 		logs:     make(map[int]*logFilter),
// 		messages: make(map[int]*whisperFilter),
// 		quit:     make(chan struct{}),
// 	}

// 	filterTickerTime = 1
// 	api.logs[0] = &logFilter{}
// 	api.messages[0] = &whisperFilter{}
// 	var wg sync.WaitGroup
// 	wg.Add(1)
// 	go api.start()
// 	go func() {
// 		select {
// 		case <-time.After(500 * time.Millisecond):
// 			api.stop()
// 			wg.Done()
// 		}
// 	}()
// 	wg.Wait()
// 	if len(api.logs) != 0 {
// 		t.Error("expected logs to be empty")
// 	}

// 	if len(api.messages) != 0 {
// 		t.Error("expected messages to be empty")
// 	}
// }
<|endoftext|>


<commit_before>package rpc

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/gorilla/websocket"
	"github.com/tendermint/tendermint/binary"
	"github.com/tendermint/tendermint/events"
	"io/ioutil"
	"net/http"
	"reflect"
	"sync/atomic"
	"time"
)

func RegisterRPCFuncs(mux *http.ServeMux, funcMap map[string]*RPCFunc) {
	// HTTP endpoints
	for funcName, rpcFunc := range funcMap {
		mux.HandleFunc("/"+funcName, makeHTTPHandler(rpcFunc))
	}

	// JSONRPC endpoints
	mux.HandleFunc("/", makeJSONRPCHandler(funcMap))
}

func RegisterEventsHandler(mux *http.ServeMux, evsw *events.EventSwitch) {
	// websocket endpoint
	wm := NewWebsocketManager(evsw)
	mux.HandleFunc("/events", wm.websocketHandler) // 	websocket.Handler(w.eventsHandler))
}

//-------------------------------------
// function introspection

// holds all type information for each function
type RPCFunc struct {
	f        reflect.Value  // underlying rpc function
	args     []reflect.Type // type of each function arg
	returns  []reflect.Type // type of each return arg
	argNames []string       // name of each argument
}

// wraps a function for quicker introspection
func NewRPCFunc(f interface{}, args []string) *RPCFunc {
	return &RPCFunc{
		f:        reflect.ValueOf(f),
		args:     funcArgTypes(f),
		returns:  funcReturnTypes(f),
		argNames: args,
	}
}

// return a function's argument types
func funcArgTypes(f interface{}) []reflect.Type {
	t := reflect.TypeOf(f)
	n := t.NumIn()
	types := make([]reflect.Type, n)
	for i := 0; i < n; i++ {
		types[i] = t.In(i)
	}
	return types
}

// return a function's return types
func funcReturnTypes(f interface{}) []reflect.Type {
	t := reflect.TypeOf(f)
	n := t.NumOut()
	types := make([]reflect.Type, n)
	for i := 0; i < n; i++ {
		types[i] = t.Out(i)
	}
	return types
}

// function introspection
//-----------------------------------------------------------------------------
// rpc.json

// jsonrpc calls grab the given method's function info and runs reflect.Call
func makeJSONRPCHandler(funcMap map[string]*RPCFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if len(r.URL.Path) > 1 {
			WriteRPCResponse(w, NewRPCResponse(nil, fmt.Sprintf("Invalid JSONRPC endpoint %s", r.URL.Path)))
			return
		}
		b, _ := ioutil.ReadAll(r.Body)
		var request RPCRequest
		err := json.Unmarshal(b, &request)
		if err != nil {
			WriteRPCResponse(w, NewRPCResponse(nil, err.Error()))
			return
		}
		rpcFunc := funcMap[request.Method]
		if rpcFunc == nil {
			WriteRPCResponse(w, NewRPCResponse(nil, "RPC method unknown: "+request.Method))
			return
		}
		args, err := jsonParamsToArgs(rpcFunc, request.Params)
		if err != nil {
			WriteRPCResponse(w, NewRPCResponse(nil, err.Error()))
			return
		}
		returns := rpcFunc.f.Call(args)
		log.Debug("HTTPJSONRPC", "method", request.Method, "args", args, "returns", returns)
		response, err := unreflectResponse(returns)
		if err != nil {
			WriteRPCResponse(w, NewRPCResponse(nil, err.Error()))
			return
		}
		WriteRPCResponse(w, NewRPCResponse(response, ""))
	}
}

// covert a list of interfaces to properly typed values
func jsonParamsToArgs(rpcFunc *RPCFunc, params []interface{}) ([]reflect.Value, error) {
	if len(rpcFunc.argNames) != len(params) {
		return nil, errors.New(fmt.Sprintf("Expected %v parameters (%v), got %v (%v)",
			len(rpcFunc.argNames), rpcFunc.argNames, len(params), params))
	}
	values := make([]reflect.Value, len(params))
	for i, p := range params {
		ty := rpcFunc.args[i]
		v, err := _jsonObjectToArg(ty, p)
		if err != nil {
			return nil, err
		}
		values[i] = v
	}
	return values, nil
}

func _jsonObjectToArg(ty reflect.Type, object interface{}) (reflect.Value, error) {
	var err error
	v := reflect.New(ty)
	binary.ReadJSONObject(v.Interface(), object, &err)
	if err != nil {
		return v, err
	}
	v = v.Elem()
	return v, nil
}

// rpc.json
//-----------------------------------------------------------------------------
// rpc.http

// convert from a function name to the http handler
func makeHTTPHandler(rpcFunc *RPCFunc) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		args, err := httpParamsToArgs(rpcFunc, r)
		if err != nil {
			WriteRPCResponse(w, NewRPCResponse(nil, err.Error()))
			return
		}
		returns := rpcFunc.f.Call(args)
		log.Debug("HTTPRestRPC", "method", r.URL.Path, "args", args, "returns", returns)
		response, err := unreflectResponse(returns)
		if err != nil {
			WriteRPCResponse(w, NewRPCResponse(nil, err.Error()))
			return
		}
		WriteRPCResponse(w, NewRPCResponse(response, ""))
	}
}

// Covert an http query to a list of properly typed values.
// To be properly decoded the arg must be a concrete type from tendermint (if its an interface).
func httpParamsToArgs(rpcFunc *RPCFunc, r *http.Request) ([]reflect.Value, error) {
	argTypes := rpcFunc.args
	argNames := rpcFunc.argNames

	var err error
	values := make([]reflect.Value, len(argNames))
	for i, name := range argNames {
		ty := argTypes[i]
		arg := GetParam(r, name)
		values[i], err = _jsonStringToArg(ty, arg)
		if err != nil {
			return nil, err
		}
	}
	return values, nil
}

func _jsonStringToArg(ty reflect.Type, arg string) (reflect.Value, error) {
	var err error
	v := reflect.New(ty)
	binary.ReadJSON(v.Interface(), []byte(arg), &err)
	if err != nil {
		return v, err
	}
	v = v.Elem()
	return v, nil
}

// rpc.http
//-----------------------------------------------------------------------------
// rpc.websocket

const (
	WSConnectionReaperSeconds = 5
	MaxFailedSends            = 10
	WriteChanBufferSize       = 10
)

// for requests coming in
type WSRequest struct {
	Type  string // subscribe or unsubscribe
	Event string
}

// for responses going out
type WSResponse struct {
	Event string
	Data  interface{}
	Error string
}

// a single websocket connection
// contains listener id, underlying ws connection,
// and the event switch for subscribing to events
type WSConnection struct {
	id          string
	wsConn      *websocket.Conn
	writeChan   chan WSResponse
	failedSends uint
	started     uint32
	stopped     uint32

	evsw *events.EventSwitch
}

// new websocket connection wrapper
func NewWSConnection(wsConn *websocket.Conn) *WSConnection {
	return &WSConnection{
		id:        wsConn.RemoteAddr().String(),
		wsConn:    wsConn,
		writeChan: make(chan WSResponse, WriteChanBufferSize), // buffered. we keep track when its full
	}
}

// start the connection and hand her the event switch
func (con *WSConnection) Start(evsw *events.EventSwitch) {
	if atomic.CompareAndSwapUint32(&con.started, 0, 1) {
		con.evsw = evsw

		// read subscriptions/unsubscriptions to events
		go con.read()
		// write responses
		con.write()
	}
}

// close the connection
func (con *WSConnection) Stop() {
	if atomic.CompareAndSwapUint32(&con.stopped, 0, 1) {
		con.wsConn.Close()
		close(con.writeChan)
	}
}

// attempt to write response to writeChan and record failures
func (con *WSConnection) safeWrite(resp WSResponse) {
	select {
	case con.writeChan <- resp:
		// yay
		con.failedSends = 0
	default:
		// channel is full
		// if this happens too many times in a row,
		// close connection
		con.failedSends += 1
	}
}

// read from the socket and subscribe to or unsubscribe from events
func (con *WSConnection) read() {
	reaper := time.Tick(time.Second * WSConnectionReaperSeconds)
	for {
		select {
		case <-reaper:
			if con.failedSends > MaxFailedSends {
				// sending has failed too many times.
				// kill the connection
				con.Stop()
				return
			}
		default:
			var in []byte
			_, in, err := con.wsConn.ReadMessage()
			if err != nil {
				// an error reading the connection,
				// kill the connection
				con.Stop()
				return
			}
			var req WSRequest
			err = json.Unmarshal(in, &req)
			if err != nil {
				errStr := fmt.Sprintf("Error unmarshaling data: %s", err.Error())
				con.safeWrite(WSResponse{Error: errStr})
				continue
			}
			switch req.Type {
			case "subscribe":
				log.Info("New event subscription", "con id", con.id, "event", req.Event)
				con.evsw.AddListenerForEvent(con.id, req.Event, func(msg interface{}) {
					resp := WSResponse{
						Event: req.Event,
						Data:  msg,
					}
					con.safeWrite(resp)
				})
			case "unsubscribe":
				if req.Event != "" {
					con.evsw.RemoveListenerForEvent(req.Event, con.id)
				} else {
					con.evsw.RemoveListener(con.id)
				}
			default:
				con.safeWrite(WSResponse{Error: "Unknown request type: " + req.Type})
			}

		}
	}
}

// receives on a write channel and writes out on the socket
func (con *WSConnection) write() {
	n, err := new(int64), new(error)
	for {
		msg, more := <-con.writeChan
		if !more {
			// the channel was closed, so ensure
			// connection is stopped and return
			con.Stop()
			return
		}
		buf := new(bytes.Buffer)
		binary.WriteJSON(msg, buf, n, err)
		if *err != nil {
			log.Error("Failed to write JSON WSResponse", "error", err)
		} else {
			if err := con.wsConn.WriteMessage(websocket.TextMessage, buf.Bytes()); err != nil {
				log.Error("Failed to write response on websocket", "error", err)
				con.Stop()
				return
			}
		}
	}
}

// main manager for all websocket connections
// holds the event switch
type WebsocketManager struct {
	websocket.Upgrader
	evsw *events.EventSwitch
}

func NewWebsocketManager(evsw *events.EventSwitch) *WebsocketManager {
	return &WebsocketManager{
		evsw: evsw,
		Upgrader: websocket.Upgrader{
			ReadBufferSize:  1024,
			WriteBufferSize: 1024,
			CheckOrigin: func(r *http.Request) bool {
				// TODO
				return true
			},
		},
	}
}

func (wm *WebsocketManager) websocketHandler(w http.ResponseWriter, r *http.Request) {
	wsConn, err := wm.Upgrade(w, r, nil)
	if err != nil {
		// TODO - return http error
		log.Error("Failed to upgrade to websocket connection", "error", err)
		return
	}

	// register connection
	con := NewWSConnection(wsConn)
	log.Info("New websocket connection", "origin", con.id)
	con.Start(wm.evsw)
}

// rpc.websocket
//-----------------------------------------------------------------------------

// returns is Response struct and error. If error is not nil, return it
func unreflectResponse(returns []reflect.Value) (interface{}, error) {
	errV := returns[1]
	if errV.Interface() != nil {
		return nil, fmt.Errorf("%v", errV.Interface())
	}
	return returns[0].Interface(), nil
}
<commit_msg>fix websocket handler<commit_after>package rpc

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/gorilla/websocket"
	"github.com/tendermint/tendermint/binary"
	"github.com/tendermint/tendermint/events"
	"io/ioutil"
	"net/http"
	"reflect"
	"sync/atomic"
	"time"
)

func RegisterRPCFuncs(mux *http.ServeMux, funcMap map[string]*RPCFunc) {
	// HTTP endpoints
	for funcName, rpcFunc := range funcMap {
		mux.HandleFunc("/"+funcName, makeHTTPHandler(rpcFunc))
	}

	// JSONRPC endpoints
	mux.HandleFunc("/", makeJSONRPCHandler(funcMap))
}

func RegisterEventsHandler(mux *http.ServeMux, evsw *events.EventSwitch) {
	// websocket endpoint
	wm := NewWebsocketManager(evsw)
	mux.HandleFunc("/events", wm.websocketHandler) // 	websocket.Handler(w.eventsHandler))
}

//-------------------------------------
// function introspection

// holds all type information for each function
type RPCFunc struct {
	f        reflect.Value  // underlying rpc function
	args     []reflect.Type // type of each function arg
	returns  []reflect.Type // type of each return arg
	argNames []string       // name of each argument
}

// wraps a function for quicker introspection
func NewRPCFunc(f interface{}, args []string) *RPCFunc {
	return &RPCFunc{
		f:        reflect.ValueOf(f),
		args:     funcArgTypes(f),
		returns:  funcReturnTypes(f),
		argNames: args,
	}
}

// return a function's argument types
func funcArgTypes(f interface{}) []reflect.Type {
	t := reflect.TypeOf(f)
	n := t.NumIn()
	types := make([]reflect.Type, n)
	for i := 0; i < n; i++ {
		types[i] = t.In(i)
	}
	return types
}

// return a function's return types
func funcReturnTypes(f interface{}) []reflect.Type {
	t := reflect.TypeOf(f)
	n := t.NumOut()
	types := make([]reflect.Type, n)
	for i := 0; i < n; i++ {
		types[i] = t.Out(i)
	}
	return types
}

// function introspection
//-----------------------------------------------------------------------------
// rpc.json

// jsonrpc calls grab the given method's function info and runs reflect.Call
func makeJSONRPCHandler(funcMap map[string]*RPCFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		if len(r.URL.Path) > 1 {
			WriteRPCResponse(w, NewRPCResponse(nil, fmt.Sprintf("Invalid JSONRPC endpoint %s", r.URL.Path)))
			return
		}
		b, _ := ioutil.ReadAll(r.Body)
		var request RPCRequest
		err := json.Unmarshal(b, &request)
		if err != nil {
			WriteRPCResponse(w, NewRPCResponse(nil, err.Error()))
			return
		}
		rpcFunc := funcMap[request.Method]
		if rpcFunc == nil {
			WriteRPCResponse(w, NewRPCResponse(nil, "RPC method unknown: "+request.Method))
			return
		}
		args, err := jsonParamsToArgs(rpcFunc, request.Params)
		if err != nil {
			WriteRPCResponse(w, NewRPCResponse(nil, err.Error()))
			return
		}
		returns := rpcFunc.f.Call(args)
		log.Debug("HTTPJSONRPC", "method", request.Method, "args", args, "returns", returns)
		response, err := unreflectResponse(returns)
		if err != nil {
			WriteRPCResponse(w, NewRPCResponse(nil, err.Error()))
			return
		}
		WriteRPCResponse(w, NewRPCResponse(response, ""))
	}
}

// covert a list of interfaces to properly typed values
func jsonParamsToArgs(rpcFunc *RPCFunc, params []interface{}) ([]reflect.Value, error) {
	if len(rpcFunc.argNames) != len(params) {
		return nil, errors.New(fmt.Sprintf("Expected %v parameters (%v), got %v (%v)",
			len(rpcFunc.argNames), rpcFunc.argNames, len(params), params))
	}
	values := make([]reflect.Value, len(params))
	for i, p := range params {
		ty := rpcFunc.args[i]
		v, err := _jsonObjectToArg(ty, p)
		if err != nil {
			return nil, err
		}
		values[i] = v
	}
	return values, nil
}

func _jsonObjectToArg(ty reflect.Type, object interface{}) (reflect.Value, error) {
	var err error
	v := reflect.New(ty)
	binary.ReadJSONObject(v.Interface(), object, &err)
	if err != nil {
		return v, err
	}
	v = v.Elem()
	return v, nil
}

// rpc.json
//-----------------------------------------------------------------------------
// rpc.http

// convert from a function name to the http handler
func makeHTTPHandler(rpcFunc *RPCFunc) func(http.ResponseWriter, *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		args, err := httpParamsToArgs(rpcFunc, r)
		if err != nil {
			WriteRPCResponse(w, NewRPCResponse(nil, err.Error()))
			return
		}
		returns := rpcFunc.f.Call(args)
		log.Debug("HTTPRestRPC", "method", r.URL.Path, "args", args, "returns", returns)
		response, err := unreflectResponse(returns)
		if err != nil {
			WriteRPCResponse(w, NewRPCResponse(nil, err.Error()))
			return
		}
		WriteRPCResponse(w, NewRPCResponse(response, ""))
	}
}

// Covert an http query to a list of properly typed values.
// To be properly decoded the arg must be a concrete type from tendermint (if its an interface).
func httpParamsToArgs(rpcFunc *RPCFunc, r *http.Request) ([]reflect.Value, error) {
	argTypes := rpcFunc.args
	argNames := rpcFunc.argNames

	var err error
	values := make([]reflect.Value, len(argNames))
	for i, name := range argNames {
		ty := argTypes[i]
		arg := GetParam(r, name)
		values[i], err = _jsonStringToArg(ty, arg)
		if err != nil {
			return nil, err
		}
	}
	return values, nil
}

func _jsonStringToArg(ty reflect.Type, arg string) (reflect.Value, error) {
	var err error
	v := reflect.New(ty)
	binary.ReadJSON(v.Interface(), []byte(arg), &err)
	if err != nil {
		return v, err
	}
	v = v.Elem()
	return v, nil
}

// rpc.http
//-----------------------------------------------------------------------------
// rpc.websocket

const (
	WSConnectionReaperSeconds = 5
	MaxFailedSends            = 10
	WriteChanBufferSize       = 10
)

// for requests coming in
type WSRequest struct {
	Type  string // subscribe or unsubscribe
	Event string
}

// for responses going out
type WSResponse struct {
	Event string
	Data  interface{}
	Error string
}

// a single websocket connection
// contains listener id, underlying ws connection,
// and the event switch for subscribing to events
type WSConnection struct {
	id          string
	wsConn      *websocket.Conn
	writeChan   chan WSResponse
	quitChan    chan struct{}
	failedSends uint
	started     uint32
	stopped     uint32

	evsw *events.EventSwitch
}

// new websocket connection wrapper
func NewWSConnection(wsConn *websocket.Conn) *WSConnection {
	return &WSConnection{
		id:        wsConn.RemoteAddr().String(),
		wsConn:    wsConn,
		writeChan: make(chan WSResponse, WriteChanBufferSize), // buffered. we keep track when its full
		quitChan:  make(chan struct{}),
	}
}

// start the connection and hand her the event switch
func (con *WSConnection) Start(evsw *events.EventSwitch) {
	if atomic.CompareAndSwapUint32(&con.started, 0, 1) {
		con.evsw = evsw

		// read subscriptions/unsubscriptions to events
		go con.read()
		// write responses
		con.write()
	}
}

// close the connection
func (con *WSConnection) Stop() {
	if atomic.CompareAndSwapUint32(&con.stopped, 0, 1) {
		close(con.quitChan)
		// the write loop closes the websocket connection
		// when it exits its loop, and the read loop
		// closes the writeChan
	}
}

// attempt to write response to writeChan and record failures
func (con *WSConnection) safeWrite(resp WSResponse) {
	select {
	case con.writeChan <- resp:
		// yay
		con.failedSends = 0
	default:
		// channel is full
		// if this happens too many times in a row,
		// close connection
		con.failedSends += 1
	}
}

// read from the socket and subscribe to or unsubscribe from events
func (con *WSConnection) read() {
	defer close(con.writeChan)
	reaper := time.Tick(time.Second * WSConnectionReaperSeconds)
	for {
		select {
		case <-reaper:
			if con.failedSends > MaxFailedSends {
				// sending has failed too many times.
				// kill the connection
				con.Stop()
				return
			}
		default:
			var in []byte
			_, in, err := con.wsConn.ReadMessage()
			if err != nil {
				// an error reading the connection,
				// kill the connection
				con.Stop()
				return
			}
			var req WSRequest
			err = json.Unmarshal(in, &req)
			if err != nil {
				errStr := fmt.Sprintf("Error unmarshaling data: %s", err.Error())
				con.safeWrite(WSResponse{Error: errStr})
				continue
			}
			switch req.Type {
			case "subscribe":
				log.Info("New event subscription", "con id", con.id, "event", req.Event)
				con.evsw.AddListenerForEvent(con.id, req.Event, func(msg interface{}) {
					resp := WSResponse{
						Event: req.Event,
						Data:  msg,
					}
					con.safeWrite(resp)
				})
			case "unsubscribe":
				if req.Event != "" {
					con.evsw.RemoveListenerForEvent(req.Event, con.id)
				} else {
					con.evsw.RemoveListener(con.id)
				}
			default:
				con.safeWrite(WSResponse{Error: "Unknown request type: " + req.Type})
			}
		}
	}
}

// receives on a write channel and writes out on the socket
func (con *WSConnection) write() {
	defer con.wsConn.Close()
	n, err := new(int64), new(error)
	for {
		select {
		case msg := <-con.writeChan:
			buf := new(bytes.Buffer)
			binary.WriteJSON(msg, buf, n, err)
			if *err != nil {
				log.Error("Failed to marshal WSResponse to JSON", "error", err)
			} else {
				if err := con.wsConn.WriteMessage(websocket.TextMessage, buf.Bytes()); err != nil {
					log.Error("Failed to write response on websocket", "error", err)
					con.Stop()
					return
				}
			}
		case <-con.quitChan:
			return
		}
	}
}

// main manager for all websocket connections
// holds the event switch
type WebsocketManager struct {
	websocket.Upgrader
	evsw *events.EventSwitch
}

func NewWebsocketManager(evsw *events.EventSwitch) *WebsocketManager {
	return &WebsocketManager{
		evsw: evsw,
		Upgrader: websocket.Upgrader{
			ReadBufferSize:  1024,
			WriteBufferSize: 1024,
			CheckOrigin: func(r *http.Request) bool {
				// TODO
				return true
			},
		},
	}
}

func (wm *WebsocketManager) websocketHandler(w http.ResponseWriter, r *http.Request) {
	wsConn, err := wm.Upgrade(w, r, nil)
	if err != nil {
		// TODO - return http error
		log.Error("Failed to upgrade to websocket connection", "error", err)
		return
	}

	// register connection
	con := NewWSConnection(wsConn)
	log.Info("New websocket connection", "origin", con.id)
	con.Start(wm.evsw)
}

// rpc.websocket
//-----------------------------------------------------------------------------

// returns is Response struct and error. If error is not nil, return it
func unreflectResponse(returns []reflect.Value) (interface{}, error) {
	errV := returns[1]
	if errV.Interface() != nil {
		return nil, fmt.Errorf("%v", errV.Interface())
	}
	return returns[0].Interface(), nil
}
<|endoftext|>


<commit_before>package dex

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	. "polydawn.net/pogo/gosh"
	. "polydawn.net/docket/crocker"
	"polydawn.net/docket/util"
	"strings"
)

type Graph struct {
	/*
		Absolute path to the base/working dir for of the graph git repository.
	*/
	dir string

	/*
		Cached command template for exec'ing git with this graph's cwd.
	*/
	cmd Command
}

/*
	Loads a Graph if there is a git repo initialized at the given dir; returns nil if a graph repo not found.
	The dir must be the root of the working tree of the git dir.

	A graph git repo is distingushed by containing branches that start with "docket/" -- this is how docket outputs branches that contain its data.
*/
func LoadGraph(dir string) *Graph {
	// optimistically, set up the struct we're checking out
	g := newGraph(dir)

	// ask git what it thinks of all this.
	if g.isDocketGraphRepo() {
		return g
	} else {
		return nil
	}
}

/*
	Attempts to load a Graph at the given dir, or creates a new one if no graph repo is found.
	If a new graph is fabricated, it will be initialized by:
	 - creating a new git repo,
	 - making a blank root commit,
	 - and tagging it with a branch name that declares it to be a graph repo.

	Note if your cwd is already in a git repo, the new graph will not be commited, nor will it be made a submodule.
	You're free to make it a submodule yourself, but git quite wants you to have a remote url before it accepts your submodule.
*/
func NewGraph(dir string) *Graph {
	g := newGraph(dir)
	if g.isDocketGraphRepo() {
		// if we can just be a load, do it
		return g
	} else if g.isRepoRoot() {
		// if this is a repo root, but didn't look like a real graph...
		util.ExitGently("Attempted to make a docket graph at ", g.dir, ", but there is already a git repo there and it does not appear to belong to docket.")
	} // else carry on, make it!

	// we'll make exactly one new dir if the path doesn't exist yet.  more is probably argument error and we abort.
	// this is actually implemented via MkdirAll here (because Mkdir errors on existing, and I can't be arsed) and letting the SaneDir check earlier blow up if we're way out.
	err := os.MkdirAll(g.dir, 0755)
	if err != nil { panic(err); }

	// git init
	g.cmd("init")("--bare")()

	g.withTempTree(func (cmd Command) {
		// set up basic repo to identify as graph repo
		cmd("commit", "--allow-empty", "-mdocket")()
		cmd("checkout", "-b", "docket/init")()

		// discard master branch.  a docket graph has no real use for it.
		cmd("branch", "-D", "master")()
	})

	// should be good to go
	return g
}

func newGraph(dir string) *Graph {
	dir = util.SanePath(dir)

	// optimistically, set up the struct.
	// we still need to either verify or initalize git here.
	return &Graph{
		dir: dir,
		cmd: Sh("git")(DefaultIO)(Opts{Cwd: dir}),
	}
}

func (g *Graph) isRepoRoot() (v bool) {
	defer func() {
		// if the path doesn't even exist, launching the command will panic, and that's fine.
		// if the path isn't within a git repo at all, it will exit with 128, gosh will panic, and that's fine.
		if recover() != nil {
			v = false
		}
	}()
	revp := g.cmd(NullIO)("rev-parse", "--is-bare-repository").Output()
	v = (revp == "true\n")
	return
}

/*
	Creates a temporary working tree in a new directory.  Changes the cwd to that location.
	The directory will be empty.  The directory will be removed when your function returns.
*/
func (g *Graph) withTempTree(fn func(cmd Command)) {
	// ensure zone for temp trees is established
	tmpTreeBase := filepath.Join(g.dir, "worktrees")
	err := os.MkdirAll(tmpTreeBase, 0755)
	if err != nil { panic(err); }

	// make temp dir for tree
	tmpdir, err := ioutil.TempDir(tmpTreeBase, "tree.")
	if err != nil { panic(err); }
	defer os.RemoveAll(tmpdir)

	// set cwd
	retreat, err := os.Getwd()
	if err != nil { panic(err); }
	defer os.Chdir(retreat)
	err = os.Chdir(tmpdir)
	if err != nil { panic(err); }

	// construct git command template that knows what's up
	gt := g.cmd(
		Opts{
			Cwd:tmpdir,
		},
		Env{
			"GIT_WORK_TREE": tmpdir,
			"GIT_DIR": g.dir,
		},
	)

	// go time
	fn(gt)
}

func (g *Graph) Publish(lineage string, ancestor string, gr GraphStoreRequest) (hash string) {
	// Handle tags - currently, we discard them when dealing with a graph repo.
	lineage, _  = SplitImageName(lineage)
	ancestor, _ = SplitImageName(ancestor)

	g.withTempTree(func(cmd Command) {
		fmt.Println("Starting publish of ", lineage, " <-- ", ancestor)

		// check if appropriate branches already exist, and make them if necesary
		if strings.Count(g.cmd("branch", "--list", lineage).Output(), "\n") >= 1 {
			fmt.Println("Lineage already existed.")
			// this is an existing lineage
			g.cmd("symbolic-ref", "HEAD", "refs/heads/"+lineage)()
		} else {
			// this is a new lineage
			if ancestor == "" {
				fmt.Println("New lineage!  Making orphan branch for it.")
				g.cmd("checkout", "--orphan", lineage)()	//TODO: docket/image/
			} else {
				fmt.Println("New lineage!  Forking it from ancestor branch.")
				g.cmd("branch", lineage, ancestor)()
				g.cmd("symbolic-ref", "HEAD", "refs/heads/"+lineage)()
			}
		}
		g.cmd("reset")

		// apply the GraphStoreRequest to unpack the fs (read from fs.tarReader, essentially)
		gr.place(".")	//TODO: verify that a relative path here is safe, or just replace is os.Getwd again.

		// exec git add, tree write, merge, commit.
		g.cmd("add", "--all")()
		g.forceMerge(ancestor, lineage)

		hash = ""	//FIXME
	})
	return
}

func (g *Graph) Load(lineage string, gr GraphLoadRequest) (hash string) {
	lineage, _ = SplitImageName(lineage) //Handle tags

	g.withTempTree(func(cmd Command) {
		// checkout lineage.
		// "-f" because otherwise if git thinks we already had this branch checked out, this working tree is just chock full of deletes.
		g.cmd("checkout", "-f", lineage)()

		// the gr consumes this filesystem and shoves it at whoever it deals with; we're actually hands free after handing over a dir.
		gr.receive(".")	//TODO: verify that a relative path here is safe, or just replace is os.Getwd again.

		hash = ""	//FIXME
	})
	return
}

// having a load-by-hash:
//   - you can't combine it with lineage, because git doesn't really know what branches are, historically speaking.
//       - we could traverse up from the lineage branch ref and make sure the hash is reachable from it, but more than one ref is going to be able to reach most hashes (i.e. hashes that are pd-base will be reachable from pd-nginx).
//       - unless we decide we're committing lineage in some structured form of the commit messages.
//            - which... yes, yes we are.  the first word of any commit is going to be the image lineage name.
//            - after the space can be anything, but we're going to default to a short description of where it came from.
//            - additional lines can be anything you want.
//            - if we need more attributes in the future, we'll start doing them with the git psuedo-standard of trailing "Signed-Off-By: %{name}\n" key-value pairs.
//            - we won't validate any of this if you're not using load-by-hash.

func (g *Graph) forceMerge(source string, target string) {
	writeTree := g.cmd("write-tree").Output()
	writeTree = strings.Trim(writeTree, "\n")
	commitMsg := ""
	if source == "" {
		commitMsg = fmt.Sprintf("%s imported from an external source", target)
	} else {
		commitMsg = fmt.Sprintf("%s updated from %s", target, source)
	}
	commitTreeCmd := g.cmd("commit-tree", writeTree, Opts{In: commitMsg})
	if source != "" {
		commitTreeCmd = commitTreeCmd("-p", source, "-p", target)
	}
	mergeTree := strings.Trim(commitTreeCmd.Output(), "\n")
	g.cmd("merge", "-q", mergeTree)()
}

//Checks if the graph has a branch.
func (g *Graph) HasBranch(branch string) bool {
	//Git magic is involved. Response will be of non-zero length if branch exists.
	result := g.cmd("ls-remote", ".", "refs/heads/" + branch).Output()
	return len(result) > 0
}

/*
	Check if a git repo exists and if it has the branches that declare it a docket graph.
*/
func (g *Graph) isDocketGraphRepo() bool {
	if !g.isRepoRoot() { return false; }
	if !g.HasBranch("docket/init") { return false; }
	// We could say a docket graph shouldn't have a master branch, but we won't.
	// We don't create one by default, but you're perfectly welcome to do so and put a readme for your coworkers in it or whatever.
	return true
}
<commit_msg>remove defunct todos<commit_after>package dex

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	. "polydawn.net/pogo/gosh"
	. "polydawn.net/docket/crocker"
	"polydawn.net/docket/util"
	"strings"
)

type Graph struct {
	/*
		Absolute path to the base/working dir for of the graph git repository.
	*/
	dir string

	/*
		Cached command template for exec'ing git with this graph's cwd.
	*/
	cmd Command
}

/*
	Loads a Graph if there is a git repo initialized at the given dir; returns nil if a graph repo not found.
	The dir must be the root of the working tree of the git dir.

	A graph git repo is distingushed by containing branches that start with "docket/" -- this is how docket outputs branches that contain its data.
*/
func LoadGraph(dir string) *Graph {
	// optimistically, set up the struct we're checking out
	g := newGraph(dir)

	// ask git what it thinks of all this.
	if g.isDocketGraphRepo() {
		return g
	} else {
		return nil
	}
}

/*
	Attempts to load a Graph at the given dir, or creates a new one if no graph repo is found.
	If a new graph is fabricated, it will be initialized by:
	 - creating a new git repo,
	 - making a blank root commit,
	 - and tagging it with a branch name that declares it to be a graph repo.

	Note if your cwd is already in a git repo, the new graph will not be commited, nor will it be made a submodule.
	You're free to make it a submodule yourself, but git quite wants you to have a remote url before it accepts your submodule.
*/
func NewGraph(dir string) *Graph {
	g := newGraph(dir)
	if g.isDocketGraphRepo() {
		// if we can just be a load, do it
		return g
	} else if g.isRepoRoot() {
		// if this is a repo root, but didn't look like a real graph...
		util.ExitGently("Attempted to make a docket graph at ", g.dir, ", but there is already a git repo there and it does not appear to belong to docket.")
	} // else carry on, make it!

	// we'll make exactly one new dir if the path doesn't exist yet.  more is probably argument error and we abort.
	// this is actually implemented via MkdirAll here (because Mkdir errors on existing, and I can't be arsed) and letting the SaneDir check earlier blow up if we're way out.
	err := os.MkdirAll(g.dir, 0755)
	if err != nil { panic(err); }

	// git init
	g.cmd("init")("--bare")()

	g.withTempTree(func (cmd Command) {
		// set up basic repo to identify as graph repo
		cmd("commit", "--allow-empty", "-mdocket")()
		cmd("checkout", "-b", "docket/init")()

		// discard master branch.  a docket graph has no real use for it.
		cmd("branch", "-D", "master")()
	})

	// should be good to go
	return g
}

func newGraph(dir string) *Graph {
	dir = util.SanePath(dir)

	// optimistically, set up the struct.
	// we still need to either verify or initalize git here.
	return &Graph{
		dir: dir,
		cmd: Sh("git")(DefaultIO)(Opts{Cwd: dir}),
	}
}

func (g *Graph) isRepoRoot() (v bool) {
	defer func() {
		// if the path doesn't even exist, launching the command will panic, and that's fine.
		// if the path isn't within a git repo at all, it will exit with 128, gosh will panic, and that's fine.
		if recover() != nil {
			v = false
		}
	}()
	revp := g.cmd(NullIO)("rev-parse", "--is-bare-repository").Output()
	v = (revp == "true\n")
	return
}

/*
	Creates a temporary working tree in a new directory.  Changes the cwd to that location.
	The directory will be empty.  The directory will be removed when your function returns.
*/
func (g *Graph) withTempTree(fn func(cmd Command)) {
	// ensure zone for temp trees is established
	tmpTreeBase := filepath.Join(g.dir, "worktrees")
	err := os.MkdirAll(tmpTreeBase, 0755)
	if err != nil { panic(err); }

	// make temp dir for tree
	tmpdir, err := ioutil.TempDir(tmpTreeBase, "tree.")
	if err != nil { panic(err); }
	defer os.RemoveAll(tmpdir)

	// set cwd
	retreat, err := os.Getwd()
	if err != nil { panic(err); }
	defer os.Chdir(retreat)
	err = os.Chdir(tmpdir)
	if err != nil { panic(err); }

	// construct git command template that knows what's up
	gt := g.cmd(
		Opts{
			Cwd:tmpdir,
		},
		Env{
			"GIT_WORK_TREE": tmpdir,
			"GIT_DIR": g.dir,
		},
	)

	// go time
	fn(gt)
}

func (g *Graph) Publish(lineage string, ancestor string, gr GraphStoreRequest) (hash string) {
	// Handle tags - currently, we discard them when dealing with a graph repo.
	lineage, _  = SplitImageName(lineage)
	ancestor, _ = SplitImageName(ancestor)

	g.withTempTree(func(cmd Command) {
		fmt.Println("Starting publish of ", lineage, " <-- ", ancestor)

		// check if appropriate branches already exist, and make them if necesary
		if strings.Count(g.cmd("branch", "--list", lineage).Output(), "\n") >= 1 {
			fmt.Println("Lineage already existed.")
			// this is an existing lineage
			g.cmd("symbolic-ref", "HEAD", "refs/heads/"+lineage)()
		} else {
			// this is a new lineage
			if ancestor == "" {
				fmt.Println("New lineage!  Making orphan branch for it.")
				g.cmd("checkout", "--orphan", lineage)()	//TODO: docket/image/
			} else {
				fmt.Println("New lineage!  Forking it from ancestor branch.")
				g.cmd("branch", lineage, ancestor)()
				g.cmd("symbolic-ref", "HEAD", "refs/heads/"+lineage)()
			}
		}
		g.cmd("reset")

		// apply the GraphStoreRequest to unpack the fs (read from fs.tarReader, essentially)
		gr.place(".")

		// exec git add, tree write, merge, commit.
		g.cmd("add", "--all")()
		g.forceMerge(ancestor, lineage)

		hash = ""	//FIXME
	})
	return
}

func (g *Graph) Load(lineage string, gr GraphLoadRequest) (hash string) {
	lineage, _ = SplitImageName(lineage) //Handle tags

	g.withTempTree(func(cmd Command) {
		// checkout lineage.
		// "-f" because otherwise if git thinks we already had this branch checked out, this working tree is just chock full of deletes.
		g.cmd("checkout", "-f", lineage)()

		// the gr consumes this filesystem and shoves it at whoever it deals with; we're actually hands free after handing over a dir.
		gr.receive(".")

		hash = ""	//FIXME
	})
	return
}

// having a load-by-hash:
//   - you can't combine it with lineage, because git doesn't really know what branches are, historically speaking.
//       - we could traverse up from the lineage branch ref and make sure the hash is reachable from it, but more than one ref is going to be able to reach most hashes (i.e. hashes that are pd-base will be reachable from pd-nginx).
//       - unless we decide we're committing lineage in some structured form of the commit messages.
//            - which... yes, yes we are.  the first word of any commit is going to be the image lineage name.
//            - after the space can be anything, but we're going to default to a short description of where it came from.
//            - additional lines can be anything you want.
//            - if we need more attributes in the future, we'll start doing them with the git psuedo-standard of trailing "Signed-Off-By: %{name}\n" key-value pairs.
//            - we won't validate any of this if you're not using load-by-hash.

func (g *Graph) forceMerge(source string, target string) {
	writeTree := g.cmd("write-tree").Output()
	writeTree = strings.Trim(writeTree, "\n")
	commitMsg := ""
	if source == "" {
		commitMsg = fmt.Sprintf("%s imported from an external source", target)
	} else {
		commitMsg = fmt.Sprintf("%s updated from %s", target, source)
	}
	commitTreeCmd := g.cmd("commit-tree", writeTree, Opts{In: commitMsg})
	if source != "" {
		commitTreeCmd = commitTreeCmd("-p", source, "-p", target)
	}
	mergeTree := strings.Trim(commitTreeCmd.Output(), "\n")
	g.cmd("merge", "-q", mergeTree)()
}

//Checks if the graph has a branch.
func (g *Graph) HasBranch(branch string) bool {
	//Git magic is involved. Response will be of non-zero length if branch exists.
	result := g.cmd("ls-remote", ".", "refs/heads/" + branch).Output()
	return len(result) > 0
}

/*
	Check if a git repo exists and if it has the branches that declare it a docket graph.
*/
func (g *Graph) isDocketGraphRepo() bool {
	if !g.isRepoRoot() { return false; }
	if !g.HasBranch("docket/init") { return false; }
	// We could say a docket graph shouldn't have a master branch, but we won't.
	// We don't create one by default, but you're perfectly welcome to do so and put a readme for your coworkers in it or whatever.
	return true
}
<|endoftext|>