<commit_before>package connection

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"math/rand"
	"net"
	"os/exec"
)

func failOnError(err error, msg string) {
	if err != nil {
		log.Fatalf("%s: %s", msg, err)
		panic(fmt.Sprintf("%s: %s", msg, err))
	}
}

func panicIf(err error) {
	if err != nil {
		log.Println(fmt.Sprintf("%s", err))
	}
}

type NoTLSConnErr string

func (f NoTLSConnErr) Error() string {
	return fmt.Sprintf("No TLS Conn Received")
}

func Connect(domain, cipherscanbinPath string) ([]byte, error) {

	ip := getRandomIP(domain)

	if ip == "" {
		e := fmt.Errorf("Could not resolve ip for: ", domain)
		log.Println(e)
		return nil, e
	}

	cmd := cipherscanbinPath + " -j --curves -servername " + domain + " " + ip + ":443 "
	fmt.Println(cmd)
	comm := exec.Command("bash", "-c", cmd)
	var out bytes.Buffer
	var stderr bytes.Buffer
	comm.Stdout = &out
	comm.Stderr = &stderr
	err := comm.Run()
	if err != nil {
		log.Println(err)
		return nil, err
	}

	info := CipherscanOutput{}
	err = json.Unmarshal([]byte(out.String()), &info)
	if err != nil {
		log.Println(err)
		return nil, err
	}

	info.Target = domain
	info.IP = ip

	c, err := info.Stored()

	if err != nil {
		log.Println(err)
		return nil, err
	}

	return json.Marshal(c)
}

func getRandomIP(domain string) string {
	ips, err := net.LookupIP(domain)
	if err != nil {
		return ""
	}

	max := len(ips)

	for {
		if max == 0 {
			return ""
		}
		index := rand.Intn(len(ips))

		if ips[index].To4() != nil {
			return ips[index].String()
		} else {
			ips = append(ips[:index], ips[index+1:]...)
		}
		max--
	}
}
<commit_msg>retriever does not log anything, just returns the errors<commit_after>package connection

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"math/rand"
	"net"
	"os/exec"
)

type NoTLSConnErr string

func (f NoTLSConnErr) Error() string {
	return fmt.Sprintf("No TLS Conn Received")
}

func Connect(domain, cipherscanbinPath string) ([]byte, error) {

	ip := getRandomIP(domain)

	if ip == "" {
		e := fmt.Errorf("Could not resolve ip for: ", domain)
		log.Println(e)
		return nil, e
	}

	cmd := cipherscanbinPath + " -j --curves -servername " + domain + " " + ip + ":443 "
	fmt.Println(cmd)
	comm := exec.Command("bash", "-c", cmd)
	var out bytes.Buffer
	var stderr bytes.Buffer
	comm.Stdout = &out
	comm.Stderr = &stderr
	err := comm.Run()
	if err != nil {
		log.Println(err)
		return nil, err
	}

	info := CipherscanOutput{}
	err = json.Unmarshal([]byte(out.String()), &info)
	if err != nil {
		log.Println(err)
		return nil, err
	}

	info.Target = domain
	info.IP = ip

	c, err := info.Stored()

	if err != nil {
		log.Println(err)
		return nil, err
	}

	return json.Marshal(c)
}

func getRandomIP(domain string) string {
	ips, err := net.LookupIP(domain)
	if err != nil {
		return ""
	}

	max := len(ips)

	for {
		if max == 0 {
			return ""
		}
		index := rand.Intn(len(ips))

		if ips[index].To4() != nil {
			return ips[index].String()
		} else {
			ips = append(ips[:index], ips[index+1:]...)
		}
		max--
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"http"
	"net"
	"os"

	"junta/paxos"
	"junta/mon"
	"junta/store"
	"junta/util"
	"junta/client"
	"junta/server"
	"junta/web"
)

const (
	alpha = 50
)

// Flags
var (
	listenAddr *string = flag.String("l", "", "The address to bind to. Must correspond to a single public interface.")
	attachAddr *string = flag.String("a", "", "The address of another node to attach to.")
	webAddr *string = flag.String("w", "", "Serve web requests on this address.")
	clusterName *string = flag.String("c", "local", "The non-empty cluster name.")
)

func activate(st *store.Store, self, prefix string, c *client.Client) {
	logger := util.NewLogger("activate")
	ch := make(chan store.Event)
	st.Watch("/junta/slot/*", ch)
	for ev := range ch {
		// TODO ev.IsEmpty()
		if ev.IsSet() && ev.Body == "" {
			_, err := c.Set(prefix+ev.Path, self, ev.Cas)
			if err == nil {
				return
			}
			logger.Log(err)
		}
	}
}

func Usage() {
	fmt.Fprintf(os.Stderr, "Usage: %s [OPTIONS] <cluster-name>\n", os.Args[0])
	fmt.Fprintf(os.Stderr, "\nOptions:\n")
	flag.PrintDefaults()
}

func main() {
	util.LogWriter = os.Stderr
	logger := util.NewLogger("main")

	flag.Parse()
	flag.Usage = Usage

	prefix := "/j/" + *clusterName

	if *listenAddr == "" {
		logger.Log("require a listen address")
		flag.Usage()
		os.Exit(1)
	}

	var webListener net.Listener
	if *webAddr != "" {
		wl, err := net.Listen("tcp", *webAddr)
		if err != nil {
			panic(err)
		}
		webListener = wl
	}

	listener, err := net.Listen("tcp", *listenAddr)
	if err != nil {
		panic(err)
	}

	outs := make(paxos.ChanPutCloserTo)

	var cl *client.Client
	self := util.RandId()
	st := store.New()
	seqn := uint64(0)
	if *attachAddr == "" { // we are the only node in a new cluster
		seqn = addPublicAddr(st, seqn + 1, self, *listenAddr)
		seqn = addHostname(st, seqn + 1, self, os.Getenv("HOSTNAME"))
		seqn = addMember(st, seqn + 1, self, *listenAddr)
		seqn = claimSlot(st, seqn + 1, "1", self)
		seqn = claimLeader(st, seqn + 1, self)
		seqn = claimSlot(st, seqn + 1, "2", "")
		seqn = claimSlot(st, seqn + 1, "3", "")
		seqn = claimSlot(st, seqn + 1, "4", "")
		seqn = claimSlot(st, seqn + 1, "5", "")
		seqn = addPing(st, seqn + 1, "pong")

		cl, err = client.Dial(*listenAddr)
		if err != nil {
			panic(err)
		}
	} else {
		cl, err = client.Dial(*attachAddr)
		if err != nil {
			panic(err)
		}

		path := prefix + "/junta/info/"+ self +"/public-addr"
		_, err = cl.Set(path, *listenAddr, store.Clobber)
		if err != nil {
			panic(err)
		}

		path = prefix + "/junta/info/"+ self +"/hostname"
		_, err = cl.Set(path, os.Getenv("HOSTNAME"), store.Clobber)
		if err != nil {
			panic(err)
		}

		var snap string
		seqn, snap, err = cl.Join(self, *listenAddr)
		if err != nil {
			panic(err)
		}

		ch := make(chan store.Event)
		st.Wait(seqn + alpha, ch)
		st.Apply(1, snap)

		go func() {
			<-ch
			activate(st, self, prefix, cl)
		}()

		// TODO sink needs a way to pick up missing values if there are any
		// gaps in its sequence
	}
	mg := paxos.NewManager(self, seqn, alpha, st, outs)

	if *attachAddr == "" {
		// Skip ahead alpha steps so that the registrar can provide a
		// meaningful cluster.
		for i := seqn + 1; i < seqn + alpha; i++ {
			go st.Apply(i, store.Nop)
		}
	}

	sv := &server.Server{*listenAddr, st, mg, self, prefix}

	go func() {
		panic(mon.Monitor(self, prefix, st, cl))
	}()

	go func() {
		panic(sv.Serve(listener))
	}()

	go func() {
		panic(sv.ListenAndServeUdp(outs))
	}()

	if webListener != nil {
		web.Store = st
		web.MainInfo.ClusterName = *clusterName
		// http handlers are installed in the init function of junta/web.
		go http.Serve(webListener, nil)
	}

	for {
		st.Apply(mg.Recv())
	}
}

func addPublicAddr(st *store.Store, seqn uint64, self, addr string) uint64 {
	// TODO pull out path as a const
	path := "/junta/info/"+ self +"/public-addr"
	mx, err := store.EncodeSet(path, addr, store.Missing)
	if err != nil {
		panic(err)
	}
	st.Apply(seqn, mx)
	return seqn
}

func addHostname(st *store.Store, seqn uint64, self, addr string) uint64 {
	// TODO pull out path as a const
	path := "/junta/info/"+ self +"/hostname"
	mx, err := store.EncodeSet(path, addr, store.Missing)
	if err != nil {
		panic(err)
	}
	st.Apply(seqn, mx)
	return seqn
}

func addMember(st *store.Store, seqn uint64, self, addr string) uint64 {
	// TODO pull out path as a const
	mx, err := store.EncodeSet("/junta/members/"+self, addr, store.Missing)
	if err != nil {
		panic(err)
	}
	st.Apply(seqn, mx)
	return seqn
}

func claimSlot(st *store.Store, seqn uint64, slot, self string) uint64 {
	// TODO pull out path as a const
	mx, err := store.EncodeSet("/junta/slot/"+slot, self, store.Missing)
	if err != nil {
		panic(err)
	}
	st.Apply(seqn, mx)
	return seqn
}

func claimLeader(st *store.Store, seqn uint64, self string) uint64 {
	// TODO pull out path as a const
	mx, err := store.EncodeSet("/junta/leader", self, store.Missing)
	if err != nil {
		panic(err)
	}
	st.Apply(seqn, mx)
	return seqn
}

func addPing(st *store.Store, seqn uint64, v string) uint64 {
	// TODO pull out path as a const
	mx, err := store.EncodeSet("/ping", v, store.Missing)
	if err != nil {
		panic(err)
	}
	st.Apply(seqn, mx)
	return seqn
}
<commit_msg>publishAddr is back<commit_after>package main

import (
	"flag"
	"fmt"
	"http"
	"net"
	"os"

	"junta/paxos"
	"junta/mon"
	"junta/store"
	"junta/util"
	"junta/client"
	"junta/server"
	"junta/web"
)

const (
	alpha = 50
)

// Flags
var (
	listenAddr *string = flag.String("l", "127.0.0.1:8046", "The address to bind to.")
	publishAddr *string = flag.String("L", "", "The address puslished for remote clients (default is listen address)")
	attachAddr *string = flag.String("a", "", "The address of another node to attach to.")
	webAddr *string = flag.String("w", ":8080", "Serve web requests on this address.")
	clusterName *string = flag.String("c", "local", "The non-empty cluster name.")
)

func activate(st *store.Store, self, prefix string, c *client.Client) {
	logger := util.NewLogger("activate")
	ch := make(chan store.Event)
	st.Watch("/junta/slot/*", ch)
	for ev := range ch {
		// TODO ev.IsEmpty()
		if ev.IsSet() && ev.Body == "" {
			_, err := c.Set(prefix+ev.Path, self, ev.Cas)
			if err == nil {
				return
			}
			logger.Log(err)
		}
	}
}

func Usage() {
	fmt.Fprintf(os.Stderr, "Usage: %s [OPTIONS] <cluster-name>\n", os.Args[0])
	fmt.Fprintf(os.Stderr, "\nOptions:\n")
	flag.PrintDefaults()
}

func main() {
	util.LogWriter = os.Stderr
	logger := util.NewLogger("main")

	flag.Parse()
	flag.Usage = Usage

	prefix := "/j/" + *clusterName

	if *listenAddr == "" {
		logger.Log("require a listen address")
		flag.Usage()
		os.Exit(1)
	}

	if *publishAddr == "" {
		publishAddr = listenAddr
	}

	var webListener net.Listener
	if *webAddr != "" {
		wl, err := net.Listen("tcp", *webAddr)
		if err != nil {
			panic(err)
		}
		webListener = wl
	}

	listener, err := net.Listen("tcp", *listenAddr)
	if err != nil {
		panic(err)
	}

	publishParts := strings.Split(*publishAddr, ":", 2)
	if len(publishParts) < 2 && publishParts[0] == "" {
		logger.Log("invalid publish address")
		flag.Usage()
		os.Exit(1)
	}

	outs := make(paxos.ChanPutCloserTo)

	var cl *client.Client
	self := util.RandId()
	st := store.New()
	seqn := uint64(0)
	if *attachAddr == "" { // we are the only node in a new cluster
		seqn = addPublicAddr(st, seqn + 1, self, *listenAddr)
		seqn = addHostname(st, seqn + 1, self, publishParts[0])
		seqn = addMember(st, seqn + 1, self, *listenAddr)
		seqn = claimSlot(st, seqn + 1, "1", self)
		seqn = claimLeader(st, seqn + 1, self)
		seqn = claimSlot(st, seqn + 1, "2", "")
		seqn = claimSlot(st, seqn + 1, "3", "")
		seqn = claimSlot(st, seqn + 1, "4", "")
		seqn = claimSlot(st, seqn + 1, "5", "")
		seqn = addPing(st, seqn + 1, "pong")

		cl, err = client.Dial(*listenAddr)
		if err != nil {
			panic(err)
		}
	} else {
		cl, err = client.Dial(*attachAddr)
		if err != nil {
			panic(err)
		}

		path := prefix + "/junta/info/"+ self +"/public-addr"
		_, err = cl.Set(path, *listenAddr, store.Clobber)
		if err != nil {
			panic(err)
		}

		path = prefix + "/junta/info/"+ self +"/hostname"
		_, err = cl.Set(path, os.Getenv("HOSTNAME"), store.Clobber)
		if err != nil {
			panic(err)
		}

		var snap string
		seqn, snap, err = cl.Join(self, *listenAddr)
		if err != nil {
			panic(err)
		}

		ch := make(chan store.Event)
		st.Wait(seqn + alpha, ch)
		st.Apply(1, snap)

		go func() {
			<-ch
			activate(st, self, prefix, cl)
		}()

		// TODO sink needs a way to pick up missing values if there are any
		// gaps in its sequence
	}
	mg := paxos.NewManager(self, seqn, alpha, st, outs)

	if *attachAddr == "" {
		// Skip ahead alpha steps so that the registrar can provide a
		// meaningful cluster.
		for i := seqn + 1; i < seqn + alpha; i++ {
			go st.Apply(i, store.Nop)
		}
	}

	sv := &server.Server{*listenAddr, st, mg, self, prefix}

	go func() {
		panic(mon.Monitor(self, prefix, st, cl))
	}()

	go func() {
		panic(sv.Serve(listener))
	}()

	go func() {
		panic(sv.ListenAndServeUdp(outs))
	}()

	if webListener != nil {
		web.Store = st
		web.MainInfo.ClusterName = *clusterName
		// http handlers are installed in the init function of junta/web.
		go http.Serve(webListener, nil)
	}

	for {
		st.Apply(mg.Recv())
	}
}

func addPublicAddr(st *store.Store, seqn uint64, self, addr string) uint64 {
	// TODO pull out path as a const
	path := "/junta/info/"+ self +"/public-addr"
	mx, err := store.EncodeSet(path, addr, store.Missing)
	if err != nil {
		panic(err)
	}
	st.Apply(seqn, mx)
	return seqn
}

func addHostname(st *store.Store, seqn uint64, self, addr string) uint64 {
	// TODO pull out path as a const
	path := "/junta/info/"+ self +"/hostname"
	mx, err := store.EncodeSet(path, addr, store.Missing)
	if err != nil {
		panic(err)
	}
	st.Apply(seqn, mx)
	return seqn
}

func addMember(st *store.Store, seqn uint64, self, addr string) uint64 {
	// TODO pull out path as a const
	mx, err := store.EncodeSet("/junta/members/"+self, addr, store.Missing)
	if err != nil {
		panic(err)
	}
	st.Apply(seqn, mx)
	return seqn
}

func claimSlot(st *store.Store, seqn uint64, slot, self string) uint64 {
	// TODO pull out path as a const
	mx, err := store.EncodeSet("/junta/slot/"+slot, self, store.Missing)
	if err != nil {
		panic(err)
	}
	st.Apply(seqn, mx)
	return seqn
}

func claimLeader(st *store.Store, seqn uint64, self string) uint64 {
	// TODO pull out path as a const
	mx, err := store.EncodeSet("/junta/leader", self, store.Missing)
	if err != nil {
		panic(err)
	}
	st.Apply(seqn, mx)
	return seqn
}

func addPing(st *store.Store, seqn uint64, v string) uint64 {
	// TODO pull out path as a const
	mx, err := store.EncodeSet("/ping", v, store.Missing)
	if err != nil {
		panic(err)
	}
	st.Apply(seqn, mx)
	return seqn
}
<|endoftext|>


<commit_before>// Package main provides ...
package main

import (
	"fmt"
	"os"

	docopt "github.com/docopt/docopt-go"
	tui "github.com/marcusolsson/tui-go"
	"github.com/yml/keep"
)

var (
	filter      = ""
	currentAcct = &keep.Account{}
)

const (
	exitCodeOk     = 0
	exitCodeNotOk  = 1
	hiddenPassword = "*************"
)

func main() {

	usage := `keep-ui is a terminal user interface for keep
Usage:
	keep-ui [options]

Options:
	-p --profile=NAME      Profile name
`

	args, err := docopt.Parse(usage, nil, true, "keep cli version: 0.2", false)
	if err != nil {
		fmt.Println("Docopt specification cannot be parsed")
		os.Exit(exitCodeNotOk)
	}

	store, err := keep.LoadProfileStore()
	if err != nil {
		panic(err)
	}

	// defaulting to the first profile
	profile := store[0]
	profileName, ok := args["--profile"].(string)
	if ok {
		profileFound := false
		for _, p := range store {
			if profileName == p.Name {
				profile = p
				profileFound = true
				break
			}
		}
		if !profileFound {
			fmt.Printf("Profile (%s) not found\n", profileName)
			os.Exit(exitCodeNotOk)
		}
	}
	// TODO: writing the profile.Name in the status bar
	//fmt.Println("Using profile : ", profile.Name)

	conf := keep.NewConfig(&profile)

	// Setting up the interface
	username := tui.NewLabel("")
	notes := tui.NewLabel("")
	password := tui.NewLabel("")

	showPasswordState := false
	showPasswordBtn := tui.NewButton("[ show ]")
	showPasswordBtn.OnActivated(func(b *tui.Button) {
		if showPasswordState {
			password.SetText(hiddenPassword)
			showPasswordState = false
		} else {
			password.SetText(currentAcct.Password)
			showPasswordState = true
		}
	})

	accountDetail := tui.NewGrid(0, 0)
	accountDetail.AppendRow(tui.NewLabel("Username: "), username)
	accountDetail.AppendRow(tui.NewLabel("Notes: "), notes)
	accountDetail.AppendRow(tui.NewLabel(" Password: "), password, showPasswordBtn)

	accountDetailBox := tui.NewVBox(accountDetail)
	accountDetailBox.SetBorder(true)
	accountDetailBox.SetSizePolicy(tui.Preferred, tui.Preferred)

	accountList := tui.NewList()
	accountList.SetSelected(0)
	accountList.OnSelectionChanged(func(l *tui.List) {
		fname := accountList.SelectedItem()
		currentAcct = getAccount(conf, fname)
		username.SetText(currentAcct.Name)
		notes.SetText(currentAcct.Notes)
		password.SetText(hiddenPassword)
	})

	accountBox := tui.NewHBox(accountList, accountDetailBox)
	accountBox.SetTitle(" Accounts ")
	accountBox.SetBorder(true)
	accountBox.SetSizePolicy(tui.Expanding, tui.Expanding)

	filterEntry := tui.NewEntry()
	filterEntry.SetText(filter)
	filterEntry.OnSubmit(func(e *tui.Entry) {
		filter = e.Text()
		accountList.RemoveItems()
		accountList.AddItems(fetchAccounts(conf, filter)...)
		accountList.SetSelected(0)
	})

	filterBox := tui.NewVBox(filterEntry)
	filterBox.SetTitle("Search an account")
	filterBox.SetBorder(true)

	tui.DefaultFocusChain.Set(filterEntry, accountList, showPasswordBtn)
	listSreen := tui.NewVBox(filterBox, accountBox)
	ui := tui.New(listSreen)
	ui.SetKeybinding(tui.KeyEsc, func() { ui.Quit() })

	if err := ui.Run(); err != nil {
		panic(err)
	}
}

func fetchAccounts(conf *keep.Config, filter string) []string {
	files, err := conf.ListAccountFiles(filter)
	if err != nil {
		panic(err)
	}
	lst := make([]string, len(files))
	for i, f := range files {
		lst[i] = f.Name()
	}
	return lst
}

func getAccount(conf *keep.Config, fname string) *keep.Account {
	account, err := keep.NewAccountFromFile(conf, fname)
	if err != nil {
		fmt.Println("An error occured while getting the account: ", err)
	}
	return account
}
<commit_msg>tui: Adding a statusBar and a Btn to copy to clipboard<commit_after>// Package main provides ...
package main

import (
	"fmt"
	"os"
	"time"

	"github.com/atotto/clipboard"
	docopt "github.com/docopt/docopt-go"
	tui "github.com/marcusolsson/tui-go"
	"github.com/yml/keep"
)

var (
	filter      = ""
	currentAcct = &keep.Account{}
)

const (
	exitCodeOk     = 0
	exitCodeNotOk  = 1
	hiddenPassword = "*************"
)

func main() {

	usage := `keep-ui is a terminal user interface for keep
Usage:
	keep-ui [options]

Options:
	-p --profile=NAME      Profile name
`

	args, err := docopt.Parse(usage, nil, true, "keep cli version: 0.2", false)
	if err != nil {
		fmt.Println("Docopt specification cannot be parsed")
		os.Exit(exitCodeNotOk)
	}

	store, err := keep.LoadProfileStore()
	if err != nil {
		panic(err)
	}

	// defaulting to the first profile
	profile := store[0]
	profileName, ok := args["--profile"].(string)
	if ok {
		profileFound := false
		for _, p := range store {
			if profileName == p.Name {
				profile = p
				profileFound = true
				break
			}
		}
		if !profileFound {
			fmt.Printf("Profile (%s) not found\n", profileName)
			os.Exit(exitCodeNotOk)
		}
	}

	statusBar := tui.NewStatusBar("")
	statusBox := tui.NewVBox(statusBar)
	statusBox.SetTitle("Status")
	statusBox.SetBorder(true)

	statusBar.SetText(fmt.Sprintf("Using profile : %s", profile.Name))

	conf := keep.NewConfig(&profile)

	// Setting up the interface
	username := tui.NewLabel("")
	notes := tui.NewLabel("")
	password := tui.NewLabel("")

	showPasswordState := false
	showPasswordBtn := tui.NewButton("[ show ]")
	showPasswordBtn.OnActivated(func(b *tui.Button) {
		if showPasswordState {
			password.SetText(hiddenPassword)
			showPasswordState = false
		} else {
			password.SetText(currentAcct.Password)
			showPasswordState = true
		}
	})

	copyPasswordBtn := tui.NewButton("[ Copy ]")
	copyPasswordBtn.OnActivated(func(b *tui.Button) {
		// Grab the original clipboard value before changing it
		originalClipboard, err := clipboard.ReadAll()
		if err != nil {
			statusBar.SetText(fmt.Sprintf("Error: Could not copy from clipboard : %s", err))
		}
		err = clipboard.WriteAll(currentAcct.Password)
		if err != nil {
			statusBar.SetText(fmt.Sprintf("Error: Could not paste to clipboard : %s", err))
		}
		go func(s string) {
			time.Sleep(15 * time.Second)
			err = clipboard.WriteAll(s)
			statusBar.SetText(fmt.Sprintf("Error: Could not restore the clipboard: %s", err))
		}(originalClipboard)
	})

	accountDetail := tui.NewGrid(0, 0)
	accountDetail.AppendRow(tui.NewLabel("Username: "), username)
	accountDetail.AppendRow(tui.NewLabel("Notes: "), notes)
	accountDetail.AppendRow(tui.NewLabel(" Password: "), password, showPasswordBtn, copyPasswordBtn)

	accountDetailBox := tui.NewVBox(accountDetail)
	accountDetailBox.SetBorder(true)
	accountDetailBox.SetSizePolicy(tui.Preferred, tui.Preferred)

	accountList := tui.NewList()
	accountList.SetSelected(0)
	accountList.OnSelectionChanged(func(l *tui.List) {
		fname := accountList.SelectedItem()
		currentAcct = getAccount(conf, fname)
		username.SetText(currentAcct.Name)
		notes.SetText(currentAcct.Notes)
		password.SetText(hiddenPassword)
	})

	accountBox := tui.NewHBox(accountList, accountDetailBox)
	accountBox.SetTitle(" Accounts ")
	accountBox.SetBorder(true)
	accountBox.SetSizePolicy(tui.Expanding, tui.Expanding)

	filterEntry := tui.NewEntry()
	filterEntry.SetText(filter)
	filterEntry.OnSubmit(func(e *tui.Entry) {
		filter = e.Text()
		accountList.RemoveItems()
		accounts := fetchAccounts(conf, filter)
		accountList.AddItems(accounts...)
		accountList.SetSelected(0)
		if len(accounts) == 0 {
			statusBar.SetText("No account matching: " + filter)
		}
	})

	filterBox := tui.NewVBox(filterEntry)
	filterBox.SetTitle("Search an account")
	filterBox.SetBorder(true)

	tui.DefaultFocusChain.Set(filterEntry, accountList, showPasswordBtn, copyPasswordBtn)
	listSreen := tui.NewVBox(filterBox, accountBox, statusBox)
	ui := tui.New(listSreen)
	ui.SetKeybinding(tui.KeyEsc, func() { ui.Quit() })

	if err := ui.Run(); err != nil {
		panic(err)
	}
}

func fetchAccounts(conf *keep.Config, filter string) []string {
	files, err := conf.ListAccountFiles(filter)
	if err != nil {
		panic(err)
	}
	lst := make([]string, len(files))
	for i, f := range files {
		lst[i] = f.Name()
	}
	return lst
}

func getAccount(conf *keep.Config, fname string) *keep.Account {
	account, err := keep.NewAccountFromFile(conf, fname)
	if err != nil {
		fmt.Println("An error occured while getting the account: ", err)
	}
	return account
}
<|endoftext|>


<commit_before>// keyify transforms unkeyed struct literals into a keyed ones.
package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"go/ast"
	"go/build"
	"go/constant"
	"go/printer"
	"go/token"
	"go/types"
	"log"
	"os"
	"path/filepath"

	"honnef.co/go/tools/version"

	"golang.org/x/tools/go/ast/astutil"
	"golang.org/x/tools/go/buildutil"
	"golang.org/x/tools/go/loader"
)

var (
	fRecursive bool
	fOneLine   bool
	fJSON      bool
	fMinify    bool
	fModified  bool
	fVersion   bool
)

func init() {
	flag.BoolVar(&fRecursive, "r", false, "keyify struct initializers recursively")
	flag.BoolVar(&fOneLine, "o", false, "print new struct initializer on a single line")
	flag.BoolVar(&fJSON, "json", false, "print new struct initializer as JSON")
	flag.BoolVar(&fMinify, "m", false, "omit fields that are set to their zero value")
	flag.BoolVar(&fModified, "modified", false, "read an archive of modified files from standard input")
	flag.BoolVar(&fVersion, "version", false, "Print version and exit")
}

func usage() {
	fmt.Printf("Usage: %s [flags] <position>\n\n", os.Args[0])
	flag.PrintDefaults()
}

func main() {
	log.SetFlags(0)
	flag.Usage = usage
	flag.Parse()

	if fVersion {
		version.Print()
		os.Exit(0)
	}

	if flag.NArg() != 1 {
		flag.Usage()
		os.Exit(2)
	}
	pos := flag.Args()[0]
	name, start, _, err := parsePos(pos)
	if err != nil {
		log.Fatal(err)
	}
	eval, err := filepath.EvalSymlinks(name)
	if err != nil {
		log.Fatal(err)
	}
	name, err = filepath.Abs(eval)
	if err != nil {
		log.Fatal(err)
	}
	cwd, err := os.Getwd()
	if err != nil {
		log.Fatal(err)
	}
	ctx := &build.Default
	if fModified {
		overlay, err := buildutil.ParseOverlayArchive(os.Stdin)
		if err != nil {
			log.Fatal(err)
		}
		ctx = buildutil.OverlayContext(ctx, overlay)
	}
	bpkg, err := buildutil.ContainingPackage(ctx, cwd, name)
	if err != nil {
		log.Fatal(err)
	}
	conf := &loader.Config{
		Build: ctx,
	}
	conf.TypeCheckFuncBodies = func(s string) bool {
		return s == bpkg.ImportPath || s == bpkg.ImportPath+"_test"
	}
	conf.ImportWithTests(bpkg.ImportPath)
	lprog, err := conf.Load()
	if err != nil {
		log.Fatal(err)
	}
	var tf *token.File
	var af *ast.File
	pkg := lprog.InitialPackages()[0]
	for _, ff := range pkg.Files {
		file := lprog.Fset.File(ff.Pos())
		if file.Name() == name {
			af = ff
			tf = file
			break
		}
	}
	tstart, tend, err := fileOffsetToPos(tf, start, start)
	if err != nil {
		log.Fatal(err)
	}
	path, _ := astutil.PathEnclosingInterval(af, tstart, tend)
	var complit *ast.CompositeLit
	for _, p := range path {
		if p, ok := p.(*ast.CompositeLit); ok {
			complit = p
			break
		}
	}
	if complit == nil {
		log.Fatal("no composite literal found near point")
	}
	if len(complit.Elts) == 0 {
		printComplit(complit, complit, lprog.Fset, lprog.Fset)
		return
	}
	if _, ok := complit.Elts[0].(*ast.KeyValueExpr); ok {
		lit := complit
		if fOneLine {
			lit = copyExpr(complit, 1).(*ast.CompositeLit)
		}
		printComplit(complit, lit, lprog.Fset, lprog.Fset)
		return
	}
	_, ok := pkg.TypeOf(complit).Underlying().(*types.Struct)
	if !ok {
		log.Fatal("not a struct initialiser")
		return
	}

	newComplit, lines := keyify(pkg, complit)
	newFset := token.NewFileSet()
	newFile := newFset.AddFile("", -1, lines)
	for i := 1; i <= lines; i++ {
		newFile.AddLine(i)
	}
	printComplit(complit, newComplit, lprog.Fset, newFset)
}

func keyify(
	pkg *loader.PackageInfo,
	complit *ast.CompositeLit,
) (*ast.CompositeLit, int) {
	var calcPos func(int) token.Pos
	if fOneLine {
		calcPos = func(int) token.Pos { return token.Pos(1) }
	} else {
		calcPos = func(i int) token.Pos { return token.Pos(2 + i) }
	}

	st, _ := pkg.TypeOf(complit).Underlying().(*types.Struct)
	newComplit := &ast.CompositeLit{
		Type:   complit.Type,
		Lbrace: 1,
		Rbrace: token.Pos(st.NumFields() + 2),
	}
	if fOneLine {
		newComplit.Rbrace = 1
	}
	numLines := 2 + st.NumFields()
	n := 0
	for i := 0; i < st.NumFields(); i++ {
		field := st.Field(i)
		val := complit.Elts[i]
		if fRecursive {
			if val2, ok := val.(*ast.CompositeLit); ok {
				if _, ok := pkg.TypeOf(val2.Type).Underlying().(*types.Struct); ok {
					var lines int
					numLines += lines
					val, lines = keyify(pkg, val2)
				}
			}
		}
		_, isIface := st.Field(i).Type().Underlying().(*types.Interface)
		if fMinify && (isNil(val, pkg) || (!isIface && isZero(val, pkg))) {
			continue
		}
		elt := &ast.KeyValueExpr{
			Key:   &ast.Ident{NamePos: calcPos(n), Name: field.Name()},
			Value: copyExpr(val, calcPos(n)),
		}
		newComplit.Elts = append(newComplit.Elts, elt)
		n++
	}
	return newComplit, numLines
}

func isNil(val ast.Expr, pkg *loader.PackageInfo) bool {
	ident, ok := val.(*ast.Ident)
	if !ok {
		return false
	}
	if _, ok := pkg.ObjectOf(ident).(*types.Nil); ok {
		return true
	}
	if c, ok := pkg.ObjectOf(ident).(*types.Const); ok {
		if c.Val().Kind() != constant.Bool {
			return false
		}
		return !constant.BoolVal(c.Val())
	}
	return false
}

func isZero(val ast.Expr, pkg *loader.PackageInfo) bool {
	switch val := val.(type) {
	case *ast.BasicLit:
		switch val.Value {
		case `""`, "``", "0", "0.0", "0i", "0.":
			return true
		default:
			return false
		}
	case *ast.Ident:
		return isNil(val, pkg)
	case *ast.CompositeLit:
		typ := pkg.TypeOf(val.Type)
		if typ == nil {
			return false
		}
		isIface := false
		switch typ := typ.Underlying().(type) {
		case *types.Struct:
		case *types.Array:
			_, isIface = typ.Elem().Underlying().(*types.Interface)
		default:
			return false
		}
		for _, elt := range val.Elts {
			if isNil(elt, pkg) || (!isIface && !isZero(elt, pkg)) {
				return false
			}
		}
		return true
	}
	return false
}

func printComplit(oldlit, newlit *ast.CompositeLit, oldfset, newfset *token.FileSet) {
	buf := &bytes.Buffer{}
	cfg := printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 8}
	_ = cfg.Fprint(buf, newfset, newlit)
	if fJSON {
		output := struct {
			Start       int    `json:"start"`
			End         int    `json:"end"`
			Replacement string `json:"replacement"`
		}{
			oldfset.Position(oldlit.Pos()).Offset,
			oldfset.Position(oldlit.End()).Offset,
			buf.String(),
		}
		_ = json.NewEncoder(os.Stdout).Encode(output)
	} else {
		fmt.Println(buf.String())
	}
}

func copyExpr(expr ast.Expr, line token.Pos) ast.Expr {
	switch expr := expr.(type) {
	case *ast.BasicLit:
		cp := *expr
		cp.ValuePos = 0
		return &cp
	case *ast.BinaryExpr:
		cp := *expr
		cp.X = copyExpr(cp.X, line)
		cp.OpPos = 0
		cp.Y = copyExpr(cp.Y, line)
		return &cp
	case *ast.CallExpr:
		cp := *expr
		cp.Fun = copyExpr(cp.Fun, line)
		cp.Lparen = 0
		for i, v := range cp.Args {
			cp.Args[i] = copyExpr(v, line)
		}
		if cp.Ellipsis != 0 {
			cp.Ellipsis = line
		}
		cp.Rparen = 0
		return &cp
	case *ast.CompositeLit:
		cp := *expr
		cp.Type = copyExpr(cp.Type, line)
		cp.Lbrace = 0
		for i, v := range cp.Elts {
			cp.Elts[i] = copyExpr(v, line)
		}
		cp.Rbrace = 0
		return &cp
	case *ast.Ident:
		cp := *expr
		cp.NamePos = 0
		return &cp
	case *ast.IndexExpr:
		cp := *expr
		cp.X = copyExpr(cp.X, line)
		cp.Lbrack = 0
		cp.Index = copyExpr(cp.Index, line)
		cp.Rbrack = 0
		return &cp
	case *ast.KeyValueExpr:
		cp := *expr
		cp.Key = copyExpr(cp.Key, line)
		cp.Colon = 0
		cp.Value = copyExpr(cp.Value, line)
		return &cp
	case *ast.ParenExpr:
		cp := *expr
		cp.Lparen = 0
		cp.X = copyExpr(cp.X, line)
		cp.Rparen = 0
		return &cp
	case *ast.SelectorExpr:
		cp := *expr
		cp.X = copyExpr(cp.X, line)
		cp.Sel = copyExpr(cp.Sel, line).(*ast.Ident)
		return &cp
	case *ast.SliceExpr:
		cp := *expr
		cp.X = copyExpr(cp.X, line)
		cp.Lbrack = 0
		cp.Low = copyExpr(cp.Low, line)
		cp.High = copyExpr(cp.High, line)
		cp.Max = copyExpr(cp.Max, line)
		cp.Rbrack = 0
		return &cp
	case *ast.StarExpr:
		cp := *expr
		cp.Star = 0
		cp.X = copyExpr(cp.X, line)
		return &cp
	case *ast.TypeAssertExpr:
		cp := *expr
		cp.X = copyExpr(cp.X, line)
		cp.Lparen = 0
		cp.Type = copyExpr(cp.Type, line)
		cp.Rparen = 0
		return &cp
	case *ast.UnaryExpr:
		cp := *expr
		cp.OpPos = 0
		cp.X = copyExpr(cp.X, line)
		return &cp
	case *ast.MapType:
		cp := *expr
		cp.Map = 0
		cp.Key = copyExpr(cp.Key, line)
		cp.Value = copyExpr(cp.Value, line)
		return &cp
	case *ast.ArrayType:
		cp := *expr
		cp.Lbrack = 0
		cp.Len = copyExpr(cp.Len, line)
		cp.Elt = copyExpr(cp.Elt, line)
		return &cp
	case *ast.Ellipsis:
		cp := *expr
		cp.Elt = copyExpr(cp.Elt, line)
		cp.Ellipsis = line
		return &cp
	case *ast.InterfaceType:
		cp := *expr
		cp.Interface = 0
		return &cp
	case *ast.StructType:
		cp := *expr
		cp.Struct = 0
		return &cp
	case *ast.FuncLit:
		return expr
	case *ast.ChanType:
		cp := *expr
		cp.Arrow = 0
		cp.Begin = 0
		cp.Value = copyExpr(cp.Value, line)
		return &cp
	case nil:
		return nil
	default:
		panic(fmt.Sprintf("shouldn't happen: unknown ast.Expr of type %T", expr))
	}
	return nil
}
<commit_msg>keyify: don't assume that file is in first package<commit_after>// keyify transforms unkeyed struct literals into a keyed ones.
package main

import (
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"go/ast"
	"go/build"
	"go/constant"
	"go/printer"
	"go/token"
	"go/types"
	"log"
	"os"
	"path/filepath"

	"honnef.co/go/tools/version"

	"golang.org/x/tools/go/ast/astutil"
	"golang.org/x/tools/go/buildutil"
	"golang.org/x/tools/go/loader"
)

var (
	fRecursive bool
	fOneLine   bool
	fJSON      bool
	fMinify    bool
	fModified  bool
	fVersion   bool
)

func init() {
	flag.BoolVar(&fRecursive, "r", false, "keyify struct initializers recursively")
	flag.BoolVar(&fOneLine, "o", false, "print new struct initializer on a single line")
	flag.BoolVar(&fJSON, "json", false, "print new struct initializer as JSON")
	flag.BoolVar(&fMinify, "m", false, "omit fields that are set to their zero value")
	flag.BoolVar(&fModified, "modified", false, "read an archive of modified files from standard input")
	flag.BoolVar(&fVersion, "version", false, "Print version and exit")
}

func usage() {
	fmt.Printf("Usage: %s [flags] <position>\n\n", os.Args[0])
	flag.PrintDefaults()
}

func main() {
	log.SetFlags(0)
	flag.Usage = usage
	flag.Parse()

	if fVersion {
		version.Print()
		os.Exit(0)
	}

	if flag.NArg() != 1 {
		flag.Usage()
		os.Exit(2)
	}
	pos := flag.Args()[0]
	name, start, _, err := parsePos(pos)
	if err != nil {
		log.Fatal(err)
	}
	eval, err := filepath.EvalSymlinks(name)
	if err != nil {
		log.Fatal(err)
	}
	name, err = filepath.Abs(eval)
	if err != nil {
		log.Fatal(err)
	}
	cwd, err := os.Getwd()
	if err != nil {
		log.Fatal(err)
	}
	ctx := &build.Default
	if fModified {
		overlay, err := buildutil.ParseOverlayArchive(os.Stdin)
		if err != nil {
			log.Fatal(err)
		}
		ctx = buildutil.OverlayContext(ctx, overlay)
	}
	bpkg, err := buildutil.ContainingPackage(ctx, cwd, name)
	if err != nil {
		log.Fatal(err)
	}
	conf := &loader.Config{
		Build: ctx,
	}
	conf.TypeCheckFuncBodies = func(s string) bool {
		return s == bpkg.ImportPath || s == bpkg.ImportPath+"_test"
	}
	conf.ImportWithTests(bpkg.ImportPath)
	lprog, err := conf.Load()
	if err != nil {
		log.Fatal(err)
	}
	var tf *token.File
	var af *ast.File
	var pkg *loader.PackageInfo
outer:
	for _, pkg = range lprog.InitialPackages() {
		for _, ff := range pkg.Files {
			file := lprog.Fset.File(ff.Pos())
			if file.Name() == name {
				af = ff
				tf = file
				break outer
			}
		}
	}
	tstart, tend, err := fileOffsetToPos(tf, start, start)
	if err != nil {
		log.Fatal(err)
	}
	path, _ := astutil.PathEnclosingInterval(af, tstart, tend)
	var complit *ast.CompositeLit
	for _, p := range path {
		if p, ok := p.(*ast.CompositeLit); ok {
			complit = p
			break
		}
	}
	if complit == nil {
		log.Fatal("no composite literal found near point")
	}
	if len(complit.Elts) == 0 {
		printComplit(complit, complit, lprog.Fset, lprog.Fset)
		return
	}
	if _, ok := complit.Elts[0].(*ast.KeyValueExpr); ok {
		lit := complit
		if fOneLine {
			lit = copyExpr(complit, 1).(*ast.CompositeLit)
		}
		printComplit(complit, lit, lprog.Fset, lprog.Fset)
		return
	}
	_, ok := pkg.TypeOf(complit).Underlying().(*types.Struct)
	if !ok {
		log.Fatal("not a struct initialiser")
		return
	}

	newComplit, lines := keyify(pkg, complit)
	newFset := token.NewFileSet()
	newFile := newFset.AddFile("", -1, lines)
	for i := 1; i <= lines; i++ {
		newFile.AddLine(i)
	}
	printComplit(complit, newComplit, lprog.Fset, newFset)
}

func keyify(
	pkg *loader.PackageInfo,
	complit *ast.CompositeLit,
) (*ast.CompositeLit, int) {
	var calcPos func(int) token.Pos
	if fOneLine {
		calcPos = func(int) token.Pos { return token.Pos(1) }
	} else {
		calcPos = func(i int) token.Pos { return token.Pos(2 + i) }
	}

	st, _ := pkg.TypeOf(complit).Underlying().(*types.Struct)
	newComplit := &ast.CompositeLit{
		Type:   complit.Type,
		Lbrace: 1,
		Rbrace: token.Pos(st.NumFields() + 2),
	}
	if fOneLine {
		newComplit.Rbrace = 1
	}
	numLines := 2 + st.NumFields()
	n := 0
	for i := 0; i < st.NumFields(); i++ {
		field := st.Field(i)
		val := complit.Elts[i]
		if fRecursive {
			if val2, ok := val.(*ast.CompositeLit); ok {
				if _, ok := pkg.TypeOf(val2.Type).Underlying().(*types.Struct); ok {
					var lines int
					numLines += lines
					val, lines = keyify(pkg, val2)
				}
			}
		}
		_, isIface := st.Field(i).Type().Underlying().(*types.Interface)
		if fMinify && (isNil(val, pkg) || (!isIface && isZero(val, pkg))) {
			continue
		}
		elt := &ast.KeyValueExpr{
			Key:   &ast.Ident{NamePos: calcPos(n), Name: field.Name()},
			Value: copyExpr(val, calcPos(n)),
		}
		newComplit.Elts = append(newComplit.Elts, elt)
		n++
	}
	return newComplit, numLines
}

func isNil(val ast.Expr, pkg *loader.PackageInfo) bool {
	ident, ok := val.(*ast.Ident)
	if !ok {
		return false
	}
	if _, ok := pkg.ObjectOf(ident).(*types.Nil); ok {
		return true
	}
	if c, ok := pkg.ObjectOf(ident).(*types.Const); ok {
		if c.Val().Kind() != constant.Bool {
			return false
		}
		return !constant.BoolVal(c.Val())
	}
	return false
}

func isZero(val ast.Expr, pkg *loader.PackageInfo) bool {
	switch val := val.(type) {
	case *ast.BasicLit:
		switch val.Value {
		case `""`, "``", "0", "0.0", "0i", "0.":
			return true
		default:
			return false
		}
	case *ast.Ident:
		return isNil(val, pkg)
	case *ast.CompositeLit:
		typ := pkg.TypeOf(val.Type)
		if typ == nil {
			return false
		}
		isIface := false
		switch typ := typ.Underlying().(type) {
		case *types.Struct:
		case *types.Array:
			_, isIface = typ.Elem().Underlying().(*types.Interface)
		default:
			return false
		}
		for _, elt := range val.Elts {
			if isNil(elt, pkg) || (!isIface && !isZero(elt, pkg)) {
				return false
			}
		}
		return true
	}
	return false
}

func printComplit(oldlit, newlit *ast.CompositeLit, oldfset, newfset *token.FileSet) {
	buf := &bytes.Buffer{}
	cfg := printer.Config{Mode: printer.UseSpaces | printer.TabIndent, Tabwidth: 8}
	_ = cfg.Fprint(buf, newfset, newlit)
	if fJSON {
		output := struct {
			Start       int    `json:"start"`
			End         int    `json:"end"`
			Replacement string `json:"replacement"`
		}{
			oldfset.Position(oldlit.Pos()).Offset,
			oldfset.Position(oldlit.End()).Offset,
			buf.String(),
		}
		_ = json.NewEncoder(os.Stdout).Encode(output)
	} else {
		fmt.Println(buf.String())
	}
}

func copyExpr(expr ast.Expr, line token.Pos) ast.Expr {
	switch expr := expr.(type) {
	case *ast.BasicLit:
		cp := *expr
		cp.ValuePos = 0
		return &cp
	case *ast.BinaryExpr:
		cp := *expr
		cp.X = copyExpr(cp.X, line)
		cp.OpPos = 0
		cp.Y = copyExpr(cp.Y, line)
		return &cp
	case *ast.CallExpr:
		cp := *expr
		cp.Fun = copyExpr(cp.Fun, line)
		cp.Lparen = 0
		for i, v := range cp.Args {
			cp.Args[i] = copyExpr(v, line)
		}
		if cp.Ellipsis != 0 {
			cp.Ellipsis = line
		}
		cp.Rparen = 0
		return &cp
	case *ast.CompositeLit:
		cp := *expr
		cp.Type = copyExpr(cp.Type, line)
		cp.Lbrace = 0
		for i, v := range cp.Elts {
			cp.Elts[i] = copyExpr(v, line)
		}
		cp.Rbrace = 0
		return &cp
	case *ast.Ident:
		cp := *expr
		cp.NamePos = 0
		return &cp
	case *ast.IndexExpr:
		cp := *expr
		cp.X = copyExpr(cp.X, line)
		cp.Lbrack = 0
		cp.Index = copyExpr(cp.Index, line)
		cp.Rbrack = 0
		return &cp
	case *ast.KeyValueExpr:
		cp := *expr
		cp.Key = copyExpr(cp.Key, line)
		cp.Colon = 0
		cp.Value = copyExpr(cp.Value, line)
		return &cp
	case *ast.ParenExpr:
		cp := *expr
		cp.Lparen = 0
		cp.X = copyExpr(cp.X, line)
		cp.Rparen = 0
		return &cp
	case *ast.SelectorExpr:
		cp := *expr
		cp.X = copyExpr(cp.X, line)
		cp.Sel = copyExpr(cp.Sel, line).(*ast.Ident)
		return &cp
	case *ast.SliceExpr:
		cp := *expr
		cp.X = copyExpr(cp.X, line)
		cp.Lbrack = 0
		cp.Low = copyExpr(cp.Low, line)
		cp.High = copyExpr(cp.High, line)
		cp.Max = copyExpr(cp.Max, line)
		cp.Rbrack = 0
		return &cp
	case *ast.StarExpr:
		cp := *expr
		cp.Star = 0
		cp.X = copyExpr(cp.X, line)
		return &cp
	case *ast.TypeAssertExpr:
		cp := *expr
		cp.X = copyExpr(cp.X, line)
		cp.Lparen = 0
		cp.Type = copyExpr(cp.Type, line)
		cp.Rparen = 0
		return &cp
	case *ast.UnaryExpr:
		cp := *expr
		cp.OpPos = 0
		cp.X = copyExpr(cp.X, line)
		return &cp
	case *ast.MapType:
		cp := *expr
		cp.Map = 0
		cp.Key = copyExpr(cp.Key, line)
		cp.Value = copyExpr(cp.Value, line)
		return &cp
	case *ast.ArrayType:
		cp := *expr
		cp.Lbrack = 0
		cp.Len = copyExpr(cp.Len, line)
		cp.Elt = copyExpr(cp.Elt, line)
		return &cp
	case *ast.Ellipsis:
		cp := *expr
		cp.Elt = copyExpr(cp.Elt, line)
		cp.Ellipsis = line
		return &cp
	case *ast.InterfaceType:
		cp := *expr
		cp.Interface = 0
		return &cp
	case *ast.StructType:
		cp := *expr
		cp.Struct = 0
		return &cp
	case *ast.FuncLit:
		return expr
	case *ast.ChanType:
		cp := *expr
		cp.Arrow = 0
		cp.Begin = 0
		cp.Value = copyExpr(cp.Value, line)
		return &cp
	case nil:
		return nil
	default:
		panic(fmt.Sprintf("shouldn't happen: unknown ast.Expr of type %T", expr))
	}
	return nil
}
<|endoftext|>


<commit_before>package controller

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/url"
	"strings"
	"time"

	ct "github.com/flynn/flynn-controller/types"
	"github.com/flynn/flynn-controller/utils"
	"github.com/flynn/go-discoverd"
	"github.com/flynn/go-discoverd/dialer"
	"github.com/flynn/go-flynn/pinned"
	"github.com/flynn/rpcplus"
	"github.com/flynn/strowger/types"
)

func NewClient(uri, key string) (*Client, error) {
	if uri == "" {
		uri = "discoverd+http://flynn-controller"
	}
	u, err := url.Parse(uri)
	if err != nil {
		return nil, err
	}
	c := &Client{
		url:  uri,
		addr: u.Host,
		http: http.DefaultClient,
		key:  key,
	}
	if u.Scheme == "discoverd+http" {
		if err := discoverd.Connect(""); err != nil {
			return nil, err
		}
		dialer := dialer.New(discoverd.DefaultClient, nil)
		c.dial = dialer.Dial
		c.dialClose = dialer
		c.http = &http.Client{Transport: &http.Transport{Dial: c.dial}}
		u.Scheme = "http"
		c.url = u.String()
	}
	return c, nil
}

func NewClientWithPin(uri, key string, pin []byte) (*Client, error) {
	u, err := url.Parse(uri)
	if err != nil {
		return nil, err
	}
	c := &Client{
		dial: (&pinned.Config{Pin: pin}).Dial,
		key:  key,
	}
	if _, port, _ := net.SplitHostPort(u.Host); port == "" {
		u.Host += ":443"
	}
	c.addr = u.Host
	u.Scheme = "http"
	c.url = u.String()
	c.http = &http.Client{Transport: &http.Transport{Dial: c.dial}}
	return c, nil
}

type Client struct {
	url  string
	key  string
	addr string
	http *http.Client

	dial      rpcplus.DialFunc
	dialClose io.Closer
}

func (c *Client) Close() error {
	if c.dialClose != nil {
		c.dialClose.Close()
	}
	return nil
}

var ErrNotFound = errors.New("controller: not found")

func toJSON(v interface{}) (io.Reader, error) {
	data, err := json.Marshal(v)
	return bytes.NewBuffer(data), err
}

func (c *Client) rawReq(method, path string, contentType string, in, out interface{}) (*http.Response, error) {
	var payload io.Reader
	switch v := in.(type) {
	case io.Reader:
		payload = v
	case nil:
	default:
		var err error
		payload, err = toJSON(in)
		if err != nil {
			return nil, err
		}
	}

	req, err := http.NewRequest(method, c.url+path, payload)
	if err != nil {
		return nil, err
	}
	if contentType == "" {
		contentType = "application/json"
	}
	req.Header.Set("Content-Type", contentType)
	req.SetBasicAuth("", c.key)
	res, err := c.http.Do(req)
	if err != nil {
		return nil, err
	}
	if res.StatusCode == 404 {
		res.Body.Close()
		return res, ErrNotFound
	}
	if res.StatusCode == 400 {
		var body ct.ValidationError
		defer res.Body.Close()
		if err = json.NewDecoder(res.Body).Decode(&body); err != nil {
			return res, err
		}
		return res, body
	}
	if res.StatusCode != 200 {
		res.Body.Close()
		return res, &url.Error{
			Op:  req.Method,
			URL: req.URL.String(),
			Err: fmt.Errorf("controller: unexpected status %d", res.StatusCode),
		}
	}
	if out != nil {
		defer res.Body.Close()
		return res, json.NewDecoder(res.Body).Decode(out)
	}
	return res, nil
}

func (c *Client) send(method, path string, in, out interface{}) error {
	_, err := c.rawReq(method, path, "", in, out)
	return err
}

func (c *Client) put(path string, in, out interface{}) error {
	return c.send("PUT", path, in, out)
}

func (c *Client) post(path string, in, out interface{}) error {
	return c.send("POST", path, in, out)
}

func (c *Client) get(path string, out interface{}) error {
	_, err := c.rawReq("GET", path, "", nil, out)
	return err
}

func (c *Client) delete(path string) error {
	res, err := c.rawReq("DELETE", path, "", nil, nil)
	if err == nil {
		res.Body.Close()
	}
	return err
}

func (c *Client) StreamFormations(since *time.Time) (<-chan *ct.ExpandedFormation, *error) {
	if since == nil {
		s := time.Unix(0, 0)
		since = &s
	}
	dial := c.dial
	if dial == nil {
		dial = net.Dial
	}
	ch := make(chan *ct.ExpandedFormation)
	conn, err := dial("tcp", c.addr)
	if err != nil {
		close(ch)
		return ch, &err
	}
	header := make(http.Header)
	header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(":"+c.key)))
	client, err := rpcplus.NewHTTPClient(conn, rpcplus.DefaultRPCPath, header)
	if err != nil {
		close(ch)
		return ch, &err
	}
	return ch, &client.StreamGo("Controller.StreamFormations", since, ch).Error
}

func (c *Client) CreateArtifact(artifact *ct.Artifact) error {
	return c.post("/artifacts", artifact, artifact)
}

func (c *Client) CreateRelease(release *ct.Release) error {
	return c.post("/releases", release, release)
}

func (c *Client) CreateApp(app *ct.App) error {
	return c.post("/apps", app, app)
}

func (c *Client) CreateProvider(provider *ct.Provider) error {
	return c.post("/providers", provider, provider)
}

func (c *Client) ProvisionResource(req *ct.ResourceReq) (*ct.Resource, error) {
	if req.ProviderID == "" {
		return nil, errors.New("controller: missing provider id")
	}
	res := &ct.Resource{}
	err := c.post(fmt.Sprintf("/providers/%s/resources", req.ProviderID), req, res)
	return res, err
}

func (c *Client) PutResource(resource *ct.Resource) error {
	if resource.ID == "" || resource.ProviderID == "" {
		return errors.New("controller: missing id and/or provider id")
	}
	return c.put(fmt.Sprintf("/providers/%s/resources/%s", resource.ProviderID, resource.ID), resource, resource)
}

func (c *Client) PutFormation(formation *ct.Formation) error {
	if formation.AppID == "" || formation.ReleaseID == "" {
		return errors.New("controller: missing app id and/or release id")
	}
	return c.put(fmt.Sprintf("/apps/%s/formations/%s", formation.AppID, formation.ReleaseID), formation, formation)
}

func (c *Client) PutJob(job *ct.Job) error {
	if job.ID == "" || job.AppID == "" {
		return errors.New("controller: missing job id and/or app id")
	}
	return c.put(fmt.Sprintf("/apps/%s/jobs/%s", job.AppID, job.ID), job, job)
}

func (c *Client) SetAppRelease(appID, releaseID string) error {
	return c.put(fmt.Sprintf("/apps/%s/release", appID), &ct.Release{ID: releaseID}, nil)
}

func (c *Client) GetAppRelease(appID string) (*ct.Release, error) {
	release := &ct.Release{}
	return release, c.get(fmt.Sprintf("/apps/%s/release", appID), release)
}

func (c *Client) RouteList(appID string) ([]*strowger.Route, error) {
	var routes []*strowger.Route
	return routes, c.get(fmt.Sprintf("/apps/%s/routes", appID), &routes)
}

func (c *Client) CreateRoute(appID string, route *strowger.Route) error {
	return c.post(fmt.Sprintf("/apps/%s/routes", appID), route, route)
}

func (c *Client) DeleteRoute(appID string, routeID string) error {
	return c.delete(fmt.Sprintf("/apps/%s/routes/%s", appID, routeID))
}

func (c *Client) GetFormation(appID, releaseID string) (*ct.Formation, error) {
	formation := &ct.Formation{}
	return formation, c.get(fmt.Sprintf("/apps/%s/formations/%s", appID, releaseID), formation)
}

func (c *Client) GetRelease(releaseID string) (*ct.Release, error) {
	release := &ct.Release{}
	return release, c.get(fmt.Sprintf("/releases/%s", releaseID), release)
}

func (c *Client) GetArtifact(artifactID string) (*ct.Artifact, error) {
	artifact := &ct.Artifact{}
	return artifact, c.get(fmt.Sprintf("/artifacts/%s", artifactID), artifact)
}

func (c *Client) GetApp(appID string) (*ct.App, error) {
	app := &ct.App{}
	return app, c.get(fmt.Sprintf("/apps/%s", appID), app)
}

func (c *Client) GetJobLog(appID, jobID string) (io.ReadCloser, error) {
	res, err := c.rawReq("GET", fmt.Sprintf("/apps/%s/jobs/%s/log", appID, jobID), "", nil, nil)
	if err != nil {
		return nil, err
	}
	return res.Body, nil
}

func (c *Client) RunJobAttached(appID string, job *ct.NewJob) (utils.ReadWriteCloser, error) {
	data, err := toJSON(job)
	if err != nil {
		return nil, err
	}
	req, err := http.NewRequest("POST", fmt.Sprintf("%s/apps/%s/jobs", c.url, appID), data)
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/vnd.flynn.attach")
	req.SetBasicAuth("", c.key)
	var dial rpcplus.DialFunc
	if c.dial != nil {
		dial = c.dial
	}
	res, rwc, err := utils.HijackRequest(req, dial)
	if err != nil {
		res.Body.Close()
		return nil, err
	}
	return rwc, nil
}

func (c *Client) RunJobDetached(appID string, req *ct.NewJob) (*ct.Job, error) {
	job := &ct.Job{}
	return job, c.post(fmt.Sprintf("/apps/%s/jobs", appID), req, job)
}

func (c *Client) JobList(appID string) ([]*ct.Job, error) {
	var jobs []*ct.Job
	return jobs, c.get(fmt.Sprintf("/apps/%s/jobs", appID), &jobs)
}

func (c *Client) KeyList() ([]*ct.Key, error) {
	var keys []*ct.Key
	return keys, c.get("/keys", &keys)
}

func (c *Client) CreateKey(pubKey string) (*ct.Key, error) {
	key := &ct.Key{}
	return key, c.post("/keys", &ct.Key{Key: pubKey}, key)
}

func (c *Client) DeleteKey(id string) error {
	return c.delete("/keys/" + strings.Replace(id, ":", "", -1))
}

func (c *Client) ProviderList() ([]*ct.Provider, error) {
	var providers []*ct.Provider
	return providers, c.get("/providers", &providers)
}
<commit_msg>controller: Merge pull request #23 from archSeer/extend-cli<commit_after>package controller

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/url"
	"strings"
	"time"

	ct "github.com/flynn/flynn-controller/types"
	"github.com/flynn/flynn-controller/utils"
	"github.com/flynn/go-discoverd"
	"github.com/flynn/go-discoverd/dialer"
	"github.com/flynn/go-flynn/pinned"
	"github.com/flynn/rpcplus"
	"github.com/flynn/strowger/types"
)

func NewClient(uri, key string) (*Client, error) {
	if uri == "" {
		uri = "discoverd+http://flynn-controller"
	}
	u, err := url.Parse(uri)
	if err != nil {
		return nil, err
	}
	c := &Client{
		url:  uri,
		addr: u.Host,
		http: http.DefaultClient,
		key:  key,
	}
	if u.Scheme == "discoverd+http" {
		if err := discoverd.Connect(""); err != nil {
			return nil, err
		}
		dialer := dialer.New(discoverd.DefaultClient, nil)
		c.dial = dialer.Dial
		c.dialClose = dialer
		c.http = &http.Client{Transport: &http.Transport{Dial: c.dial}}
		u.Scheme = "http"
		c.url = u.String()
	}
	return c, nil
}

func NewClientWithPin(uri, key string, pin []byte) (*Client, error) {
	u, err := url.Parse(uri)
	if err != nil {
		return nil, err
	}
	c := &Client{
		dial: (&pinned.Config{Pin: pin}).Dial,
		key:  key,
	}
	if _, port, _ := net.SplitHostPort(u.Host); port == "" {
		u.Host += ":443"
	}
	c.addr = u.Host
	u.Scheme = "http"
	c.url = u.String()
	c.http = &http.Client{Transport: &http.Transport{Dial: c.dial}}
	return c, nil
}

type Client struct {
	url  string
	key  string
	addr string
	http *http.Client

	dial      rpcplus.DialFunc
	dialClose io.Closer
}

func (c *Client) Close() error {
	if c.dialClose != nil {
		c.dialClose.Close()
	}
	return nil
}

var ErrNotFound = errors.New("controller: not found")

func toJSON(v interface{}) (io.Reader, error) {
	data, err := json.Marshal(v)
	return bytes.NewBuffer(data), err
}

func (c *Client) rawReq(method, path string, contentType string, in, out interface{}) (*http.Response, error) {
	var payload io.Reader
	switch v := in.(type) {
	case io.Reader:
		payload = v
	case nil:
	default:
		var err error
		payload, err = toJSON(in)
		if err != nil {
			return nil, err
		}
	}

	req, err := http.NewRequest(method, c.url+path, payload)
	if err != nil {
		return nil, err
	}
	if contentType == "" {
		contentType = "application/json"
	}
	req.Header.Set("Content-Type", contentType)
	req.SetBasicAuth("", c.key)
	res, err := c.http.Do(req)
	if err != nil {
		return nil, err
	}
	if res.StatusCode == 404 {
		res.Body.Close()
		return res, ErrNotFound
	}
	if res.StatusCode == 400 {
		var body ct.ValidationError
		defer res.Body.Close()
		if err = json.NewDecoder(res.Body).Decode(&body); err != nil {
			return res, err
		}
		return res, body
	}
	if res.StatusCode != 200 {
		res.Body.Close()
		return res, &url.Error{
			Op:  req.Method,
			URL: req.URL.String(),
			Err: fmt.Errorf("controller: unexpected status %d", res.StatusCode),
		}
	}
	if out != nil {
		defer res.Body.Close()
		return res, json.NewDecoder(res.Body).Decode(out)
	}
	return res, nil
}

func (c *Client) send(method, path string, in, out interface{}) error {
	_, err := c.rawReq(method, path, "", in, out)
	return err
}

func (c *Client) put(path string, in, out interface{}) error {
	return c.send("PUT", path, in, out)
}

func (c *Client) post(path string, in, out interface{}) error {
	return c.send("POST", path, in, out)
}

func (c *Client) get(path string, out interface{}) error {
	_, err := c.rawReq("GET", path, "", nil, out)
	return err
}

func (c *Client) delete(path string) error {
	res, err := c.rawReq("DELETE", path, "", nil, nil)
	if err == nil {
		res.Body.Close()
	}
	return err
}

func (c *Client) StreamFormations(since *time.Time) (<-chan *ct.ExpandedFormation, *error) {
	if since == nil {
		s := time.Unix(0, 0)
		since = &s
	}
	dial := c.dial
	if dial == nil {
		dial = net.Dial
	}
	ch := make(chan *ct.ExpandedFormation)
	conn, err := dial("tcp", c.addr)
	if err != nil {
		close(ch)
		return ch, &err
	}
	header := make(http.Header)
	header.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(":"+c.key)))
	client, err := rpcplus.NewHTTPClient(conn, rpcplus.DefaultRPCPath, header)
	if err != nil {
		close(ch)
		return ch, &err
	}
	return ch, &client.StreamGo("Controller.StreamFormations", since, ch).Error
}

func (c *Client) CreateArtifact(artifact *ct.Artifact) error {
	return c.post("/artifacts", artifact, artifact)
}

func (c *Client) CreateRelease(release *ct.Release) error {
	return c.post("/releases", release, release)
}

func (c *Client) CreateApp(app *ct.App) error {
	return c.post("/apps", app, app)
}

func (c *Client) CreateProvider(provider *ct.Provider) error {
	return c.post("/providers", provider, provider)
}

func (c *Client) ProvisionResource(req *ct.ResourceReq) (*ct.Resource, error) {
	if req.ProviderID == "" {
		return nil, errors.New("controller: missing provider id")
	}
	res := &ct.Resource{}
	err := c.post(fmt.Sprintf("/providers/%s/resources", req.ProviderID), req, res)
	return res, err
}

func (c *Client) PutResource(resource *ct.Resource) error {
	if resource.ID == "" || resource.ProviderID == "" {
		return errors.New("controller: missing id and/or provider id")
	}
	return c.put(fmt.Sprintf("/providers/%s/resources/%s", resource.ProviderID, resource.ID), resource, resource)
}

func (c *Client) PutFormation(formation *ct.Formation) error {
	if formation.AppID == "" || formation.ReleaseID == "" {
		return errors.New("controller: missing app id and/or release id")
	}
	return c.put(fmt.Sprintf("/apps/%s/formations/%s", formation.AppID, formation.ReleaseID), formation, formation)
}

func (c *Client) PutJob(job *ct.Job) error {
	if job.ID == "" || job.AppID == "" {
		return errors.New("controller: missing job id and/or app id")
	}
	return c.put(fmt.Sprintf("/apps/%s/jobs/%s", job.AppID, job.ID), job, job)
}

func (c *Client) SetAppRelease(appID, releaseID string) error {
	return c.put(fmt.Sprintf("/apps/%s/release", appID), &ct.Release{ID: releaseID}, nil)
}

func (c *Client) GetAppRelease(appID string) (*ct.Release, error) {
	release := &ct.Release{}
	return release, c.get(fmt.Sprintf("/apps/%s/release", appID), release)
}

func (c *Client) RouteList(appID string) ([]*strowger.Route, error) {
	var routes []*strowger.Route
	return routes, c.get(fmt.Sprintf("/apps/%s/routes", appID), &routes)
}

func (c *Client) CreateRoute(appID string, route *strowger.Route) error {
	return c.post(fmt.Sprintf("/apps/%s/routes", appID), route, route)
}

func (c *Client) DeleteRoute(appID string, routeID string) error {
	return c.delete(fmt.Sprintf("/apps/%s/routes/%s", appID, routeID))
}

func (c *Client) GetFormation(appID, releaseID string) (*ct.Formation, error) {
	formation := &ct.Formation{}
	return formation, c.get(fmt.Sprintf("/apps/%s/formations/%s", appID, releaseID), formation)
}

func (c *Client) GetRelease(releaseID string) (*ct.Release, error) {
	release := &ct.Release{}
	return release, c.get(fmt.Sprintf("/releases/%s", releaseID), release)
}

func (c *Client) GetArtifact(artifactID string) (*ct.Artifact, error) {
	artifact := &ct.Artifact{}
	return artifact, c.get(fmt.Sprintf("/artifacts/%s", artifactID), artifact)
}

func (c *Client) GetApp(appID string) (*ct.App, error) {
	app := &ct.App{}
	return app, c.get(fmt.Sprintf("/apps/%s", appID), app)
}

func (c *Client) GetJobLog(appID, jobID string) (io.ReadCloser, error) {
	res, err := c.rawReq("GET", fmt.Sprintf("/apps/%s/jobs/%s/log", appID, jobID), "", nil, nil)
	if err != nil {
		return nil, err
	}
	return res.Body, nil
}

func (c *Client) RunJobAttached(appID string, job *ct.NewJob) (utils.ReadWriteCloser, error) {
	data, err := toJSON(job)
	if err != nil {
		return nil, err
	}
	req, err := http.NewRequest("POST", fmt.Sprintf("%s/apps/%s/jobs", c.url, appID), data)
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("Accept", "application/vnd.flynn.attach")
	req.SetBasicAuth("", c.key)
	var dial rpcplus.DialFunc
	if c.dial != nil {
		dial = c.dial
	}
	res, rwc, err := utils.HijackRequest(req, dial)
	if err != nil {
		res.Body.Close()
		return nil, err
	}
	return rwc, nil
}

func (c *Client) RunJobDetached(appID string, req *ct.NewJob) (*ct.Job, error) {
	job := &ct.Job{}
	return job, c.post(fmt.Sprintf("/apps/%s/jobs", appID), req, job)
}

func (c *Client) JobList(appID string) ([]*ct.Job, error) {
	var jobs []*ct.Job
	return jobs, c.get(fmt.Sprintf("/apps/%s/jobs", appID), &jobs)
}

func (c *Client) AppList() ([]*ct.App, error) {
	var apps []*ct.App
	return apps, c.get("/apps", &apps)
}

func (c *Client) KeyList() ([]*ct.Key, error) {
	var keys []*ct.Key
	return keys, c.get("/keys", &keys)
}

func (c *Client) CreateKey(pubKey string) (*ct.Key, error) {
	key := &ct.Key{}
	return key, c.post("/keys", &ct.Key{Key: pubKey}, key)
}

func (c *Client) DeleteKey(id string) error {
	return c.delete("/keys/" + strings.Replace(id, ":", "", -1))
}

func (c *Client) ProviderList() ([]*ct.Provider, error) {
	var providers []*ct.Provider
	return providers, c.get("/providers", &providers)
}
<|endoftext|>


<commit_before>package hummingbird

import (
	"testing"
)

func TestUnpicklingVersion1Map(t *testing.T) {
	data, err := PickleLoads([]byte("(dp1\nS'hi'\np2\nS'there'\np3\ns."))
	if err != nil {
		t.Fatal("Error parsing pickle: " + err.Error())
	}
	if dataVal, ok := data.(map[interface{}]interface{}); ok {
		if dataVal["hi"] != "there" {
			t.Fatal("Return data not correct.")
		}
	} else {
		t.Fatal("Invalid return type.")
	}
}

func TestUnpicklingVersion2Map(t *testing.T) {
	data, err := PickleLoads([]byte("\x80\x02}q\x01U\x02hiq\x02U\x05thereq\x03s."))
	if err != nil {
		t.Fatal("Error parsing pickle: " + err.Error())
	}
	if dataVal, ok := data.(map[interface{}]interface{}); ok {
		if dataVal["hi"] != "there" {
			t.Fatal("Return data not correct.")
		}
	} else {
		t.Fatal("Invalid return type.")
	}
}

func PickleRoundTrip(t *testing.T, v interface{}) interface{} {
	ret, err := PickleLoads(PickleDumps(v))
	if err != nil {
		t.Fatal("Error parsing pickle: " + err.Error())
	}
	return ret
}

func TestRoundTrip1(t *testing.T) {
	if dataVal, ok := PickleRoundTrip(t, 2).(int64); ok {
		if dataVal != 2 {
			t.Fatal("Return data not correct.")
		}
	} else {
		t.Fatal("Invalid return type.")
	}
}

func TestRoundTrip2(t *testing.T) {
	if dataVal, ok := PickleRoundTrip(t, "hi").(string); ok {
		if dataVal != "hi" {
			t.Fatal("Return data not correct.")
		}
	} else {
		t.Fatal("Invalid return type.")
	}
}

func TestRoundTrip3(t *testing.T) {
	data := map[string]string{"1": "test1", "2": "test2"}
	if dataVal, ok := PickleRoundTrip(t, data).(map[interface{}]interface{}); ok {
		if dataVal["1"] != "test1" {
			t.Fatal("Return data not correct.")
		}
		if dataVal["2"] != "test2" {
			t.Fatal("Return data not correct.")
		}
	} else {
		t.Fatal("Invalid return type.")
	}
}
<commit_msg>pickle unit test<commit_after>package hummingbird

import (
	"testing"
)

func TestUnpicklingVersion1Map(t *testing.T) {
	data, err := PickleLoads([]byte("(dp1\nS'hi'\np2\nS'there'\np3\ns."))
	if err != nil {
		t.Fatal("Error parsing pickle: " + err.Error())
	}
	if dataVal, ok := data.(map[interface{}]interface{}); ok {
		if dataVal["hi"] != "there" {
			t.Fatal("Return data not correct.")
		}
	} else {
		t.Fatal("Invalid return type.")
	}
}

func TestUnpicklingVersion2Map(t *testing.T) {
	data, err := PickleLoads([]byte("\x80\x02}q\x01U\x02hiq\x02U\x05thereq\x03s."))
	if err != nil {
		t.Fatal("Error parsing pickle: " + err.Error())
	}
	if dataVal, ok := data.(map[interface{}]interface{}); ok {
		if dataVal["hi"] != "there" {
			t.Fatal("Return data not correct.")
		}
	} else {
		t.Fatal("Invalid return type.")
	}
}

func PickleRoundTrip(t *testing.T, v interface{}) interface{} {
	ret, err := PickleLoads(PickleDumps(v))
	if err != nil {
		t.Fatal("Error parsing pickle: " + err.Error())
	}
	return ret
}

func TestRoundTrip1(t *testing.T) {
	if dataVal, ok := PickleRoundTrip(t, 2).(int64); ok {
		if dataVal != 2 {
			t.Fatal("Return data not correct.")
		}
	} else {
		t.Fatal("Invalid return type.")
	}
}

func TestRoundTrip2(t *testing.T) {
	if dataVal, ok := PickleRoundTrip(t, "hi").(string); ok {
		if dataVal != "hi" {
			t.Fatal("Return data not correct.")
		}
	} else {
		t.Fatal("Invalid return type.")
	}
}

func TestRoundTrip3(t *testing.T) {
	data := map[string]string{"1": "test1", "2": "test2"}
	if dataVal, ok := PickleRoundTrip(t, data).(map[interface{}]interface{}); ok {
		if dataVal["1"] != "test1" {
			t.Fatal("Return data not correct.")
		}
		if dataVal["2"] != "test2" {
			t.Fatal("Return data not correct.")
		}
	} else {
		t.Fatal("Invalid return type.")
	}
}

func TestRoundTrip4(t *testing.T) {
	data := []interface{}{1, 2, 3}
	if dataVal, ok := PickleRoundTrip(t, data).([]interface{}); ok {
		if dataVal[0].(int64) != 1 {
			t.Fatal("Return data not correct.")
		}
		if dataVal[1].(int64) != 2 {
			t.Fatal("Return data not correct.")
		}
		if dataVal[2].(int64) != 3 {
			t.Fatal("Return data not correct.")
		}
	} else {
		t.Fatal("Invalid return type.")
	}
}
<|endoftext|>


<commit_before>// Package igc implements an IGC parser.
package igc

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"regexp"
	"strings"
	"time"

	"github.com/twpayne/go-geom"
)

var (
	// ErrInvalidCharacter is returned when an invalid character is encountered.
	ErrInvalidCharacter = errors.New("invalid character")
	// ErrInvalidCharactersBeforeARecord is returned when invalid characters are encountered before the A record.
	ErrInvalidCharactersBeforeARecord = errors.New("invalid characters before A record")
	// ErrInvalidBRecord is returned when an invalid B record is encountered.
	ErrInvalidBRecord = errors.New("invalid B record")
	// ErrInvalidHRecord is returned when an invalid H record is encountered.
	ErrInvalidHRecord = errors.New("invalid H record")
	// ErrInvalidIRecord is returned when an invalid I record is encountered.
	ErrInvalidIRecord = errors.New("invalid I record")
	// ErrEmptyLine is returned when an empty line is encountered.
	ErrEmptyLine = errors.New("empty line")
	// ErrMissingARecord is returned when no A record is found.
	ErrMissingARecord = errors.New("missing A record")
	// ErrOutOfRange is returned when a value is out of range.
	ErrOutOfRange = errors.New("out of range")

	hRegexp = regexp.MustCompile(`H([FP])([A-Z]{3})(.*?):(.*?)\s*\z`)
)

// An Errors is a map of errors encountered at each line.
type Errors map[int]error

// A Header is an IGC header.
type Header struct {
	Source   string
	Key      string
	KeyExtra string
	Value    string
}

// A T represents a parsed IGC file.
type T struct {
	Headers    []Header
	LineString *geom.LineString
}

func (es Errors) Error() string {
	var ss []string
	for lineno, e := range es {
		ss = append(ss, fmt.Sprintf("%d: %s", lineno, e.Error()))
	}
	return strings.Join(ss, "\n")
}

// parseDec parses a decimal value in s[start:stop].
func parseDec(s string, start, stop int) (int, error) {
	result := 0
	for i := start; i < stop; i++ {
		if c := s[i]; '0' <= c && c <= '9' {
			result = 10*result + int(c) - '0'
		} else {
			return 0, ErrInvalidCharacter
		}
	}
	return result, nil
}

// parseDecInRange parsers a decimal value in s[start:stop], and returns an
// error if it is outside the range [min, max).
func parseDecInRange(s string, start, stop, min, max int) (int, error) {
	if result, err := parseDec(s, start, stop); err != nil {
		return result, err
	} else if result < min || max <= result {
		return result, ErrOutOfRange
	} else {
		return result, nil
	}
}

// parser contains the state of a parser.
type parser struct {
	state             int
	headers           []Header
	coords            []float64
	year, month, day  int
	startAt           time.Time
	lastDate          time.Time
	ladStart, ladStop int
	lodStart, lodStop int
	tdsStart, tdsStop int
	bRecordLen        int
}

// newParser creates a new parser.
func newParser() *parser {
	return &parser{bRecordLen: 35}
}

// parseB parses a B record from line and updates the state of p.
func (p *parser) parseB(line string) error {

	if len(line) != p.bRecordLen {
		return ErrInvalidBRecord
	}

	var err error

	var hour, minute, second, nsec int
	if hour, err = parseDecInRange(line, 1, 3, 0, 24); err != nil {
		return err
	}
	if minute, err = parseDecInRange(line, 3, 5, 0, 60); err != nil {
		return err
	}
	if second, err = parseDecInRange(line, 5, 7, 0, 60); err != nil {
		return err
	}
	if p.tdsStart != 0 {
		decisecond, err := parseDecInRange(line, p.tdsStart, p.tdsStop, 0, 10)
		if err != nil {
			return err
		}
		nsec = decisecond * 1e8
	}
	date := time.Date(p.year, time.Month(p.month), p.day, hour, minute, second, nsec, time.UTC)
	if date.Before(p.lastDate) {
		p.day++
		date = time.Date(p.year, time.Month(p.month), p.day, hour, minute, second, nsec, time.UTC)
	}

	if p.startAt.IsZero() {
		p.startAt = date
	}

	var latDeg, latMilliMin int
	if latDeg, err = parseDecInRange(line, 7, 9, 0, 90); err != nil {
		return err
	}
	// special case: latMilliMin should be in the range [0, 60000) but a number of flight recorders generate latMilliMins of 60000
	// FIXME check what happens in negative (S, W) hemispheres
	if latMilliMin, err = parseDecInRange(line, 9, 14, 0, 60000+1); err != nil {
		return err
	}
	lat := float64(60000*latDeg+latMilliMin) / 60000.
	if p.ladStart != 0 {
		if lad, err := parseDec(line, p.ladStart, p.ladStop); err == nil {
			lat += float64(lad) / 6000000.
		} else {
			return err
		}
	}
	switch c := line[14]; c {
	case 'N':
	case 'S':
		lat = -lat
	default:
		return ErrInvalidCharacter
	}

	var lngDeg, lngMilliMin int
	if lngDeg, err = parseDecInRange(line, 15, 18, 0, 180); err != nil {
		return err
	}
	if lngMilliMin, err = parseDecInRange(line, 18, 23, 0, 60000+1); err != nil {
		return err
	}
	lng := float64(60000*lngDeg+lngMilliMin) / 60000.
	if p.lodStart != 0 {
		if lod, err := parseDec(line, p.lodStart, p.lodStop); err == nil {
			lng += float64(lod) / 6000000.
		} else {
			return err
		}
	}
	switch c := line[23]; c {
	case 'E':
	case 'W':
		lng = -lng
	default:
		return ErrInvalidCharacter
	}

	var pressureAlt, ellipsoidAlt int
	if pressureAlt, err = parseDec(line, 25, 30); err != nil {
		return err
	}
	if ellipsoidAlt, err = parseDec(line, 30, 35); err != nil {
		return err
	}

	p.coords = append(p.coords, lng, lat, float64(ellipsoidAlt), float64(date.UnixNano())/1e9, float64(pressureAlt))
	p.lastDate = date

	return nil

}

// parseB parses an H record from line and updates the state of p.
func (p *parser) parseH(line string) error {
	if m := hRegexp.FindStringSubmatch(line); m != nil {
		p.headers = append(p.headers, Header{
			Source:   m[1],
			Key:      m[2],
			KeyExtra: m[3],
			Value:    m[4],
		})
	}
	switch {
	case strings.HasPrefix(line, "HFDTE"):
		return p.parseHFDTE(line)
	default:
		return nil
	}
}

// parseB parses an HFDTE record from line and updates the state of p.
func (p *parser) parseHFDTE(line string) error {
	var err error
	var day, month, year int
	if len(line) != 11 {
		return ErrInvalidHRecord
	}
	if day, err = parseDecInRange(line, 5, 7, 1, 31+1); err != nil {
		return err
	}
	if month, err = parseDecInRange(line, 7, 9, 1, 12+1); err != nil {
		return err
	}
	if year, err = parseDec(line, 9, 11); err != nil {
		return err
	}
	// FIXME check for invalid dates
	p.day = day
	p.month = month
	if year < 70 {
		p.year = 2000 + year
	} else {
		p.year = 1970 + year
	}
	return nil
}

// parseB parses an I record from line and updates the state of p.
func (p *parser) parseI(line string) error {
	var err error
	var n int
	if len(line) < 3 {
		return ErrInvalidIRecord
	}
	if n, err = parseDec(line, 1, 3); err != nil {
		return err
	}
	if len(line) < 7*n+3 {
		return ErrInvalidIRecord
	}
	for i := 0; i < n; i++ {
		var start, stop int
		if start, err = parseDec(line, 7*i+3, 7*i+5); err != nil {
			return err
		}
		if stop, err = parseDec(line, 7*i+5, 7*i+7); err != nil {
			return err
		}
		if start != p.bRecordLen+1 || stop < start {
			return ErrInvalidIRecord
		}
		p.bRecordLen = stop
		switch line[7*i+7 : 7*i+10] {
		case "LAD":
			p.ladStart, p.ladStop = start-1, stop
		case "LOD":
			p.lodStart, p.lodStop = start-1, stop
		case "TDS":
			p.tdsStart, p.tdsStop = start-1, stop
		}
	}
	return nil
}

// parseLine parses a single record from line and updates the state of p.
func (p *parser) parseLine(line string) error {
	switch line[0] {
	case 'B':
		return p.parseB(line)
	case 'H':
		return p.parseH(line)
	case 'I':
		return p.parseI(line)
	default:
		return nil
	}
}

// doParse reads r, parsers all the records it finds, updating the state of p.
func doParse(r io.Reader) (*parser, Errors) {
	errors := make(Errors)
	p := newParser()
	s := bufio.NewScanner(r)
	foundA := false
	leadingNoise := false
	for lineno := 1; s.Scan(); lineno++ {
		line := s.Text()
		if len(line) == 0 {
			// errors[lineno] = ErrEmptyLine
		} else if foundA {
			if err := p.parseLine(line); err != nil {
				errors[lineno] = err
			}
		} else {
			if c := line[0]; c == 'A' {
				foundA = true
			} else if 'A' <= c && c <= 'Z' {
				// All records that start with an uppercase character must be valid.
				leadingNoise = true
				continue
			} else if i := strings.IndexRune(line, 'A'); i != -1 {
				// Strip any leading noise.
				// The noise must include at least one unprintable character (like XOFF or a fragment of a Unicode BOM).
				for _, c := range line[:i] {
					if !(c == ' ' || ('A' <= c && c <= 'Z')) {
						foundA = true
						leadingNoise = true
						line = line[i:]
						break
					}
				}
			}
		}
	}
	if !foundA {
		errors[1] = ErrMissingARecord
	} else if leadingNoise {
		errors[1] = ErrInvalidCharactersBeforeARecord
	}
	return p, errors
}

// Read reads a igc.T from r, which should contain IGC records.
func Read(r io.Reader) (*T, error) {
	p, errors := doParse(r)
	if len(errors) != 0 {
		return nil, errors
	}
	return &T{
		Headers:    p.headers,
		LineString: geom.NewLineStringFlat(geom.Layout(5), p.coords),
	}, nil
}
<commit_msg>Remove unused struct element<commit_after>// Package igc implements an IGC parser.
package igc

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"regexp"
	"strings"
	"time"

	"github.com/twpayne/go-geom"
)

var (
	// ErrInvalidCharacter is returned when an invalid character is encountered.
	ErrInvalidCharacter = errors.New("invalid character")
	// ErrInvalidCharactersBeforeARecord is returned when invalid characters are encountered before the A record.
	ErrInvalidCharactersBeforeARecord = errors.New("invalid characters before A record")
	// ErrInvalidBRecord is returned when an invalid B record is encountered.
	ErrInvalidBRecord = errors.New("invalid B record")
	// ErrInvalidHRecord is returned when an invalid H record is encountered.
	ErrInvalidHRecord = errors.New("invalid H record")
	// ErrInvalidIRecord is returned when an invalid I record is encountered.
	ErrInvalidIRecord = errors.New("invalid I record")
	// ErrEmptyLine is returned when an empty line is encountered.
	ErrEmptyLine = errors.New("empty line")
	// ErrMissingARecord is returned when no A record is found.
	ErrMissingARecord = errors.New("missing A record")
	// ErrOutOfRange is returned when a value is out of range.
	ErrOutOfRange = errors.New("out of range")

	hRegexp = regexp.MustCompile(`H([FP])([A-Z]{3})(.*?):(.*?)\s*\z`)
)

// An Errors is a map of errors encountered at each line.
type Errors map[int]error

// A Header is an IGC header.
type Header struct {
	Source   string
	Key      string
	KeyExtra string
	Value    string
}

// A T represents a parsed IGC file.
type T struct {
	Headers    []Header
	LineString *geom.LineString
}

func (es Errors) Error() string {
	var ss []string
	for lineno, e := range es {
		ss = append(ss, fmt.Sprintf("%d: %s", lineno, e.Error()))
	}
	return strings.Join(ss, "\n")
}

// parseDec parses a decimal value in s[start:stop].
func parseDec(s string, start, stop int) (int, error) {
	result := 0
	for i := start; i < stop; i++ {
		if c := s[i]; '0' <= c && c <= '9' {
			result = 10*result + int(c) - '0'
		} else {
			return 0, ErrInvalidCharacter
		}
	}
	return result, nil
}

// parseDecInRange parsers a decimal value in s[start:stop], and returns an
// error if it is outside the range [min, max).
func parseDecInRange(s string, start, stop, min, max int) (int, error) {
	if result, err := parseDec(s, start, stop); err != nil {
		return result, err
	} else if result < min || max <= result {
		return result, ErrOutOfRange
	} else {
		return result, nil
	}
}

// parser contains the state of a parser.
type parser struct {
	headers           []Header
	coords            []float64
	year, month, day  int
	startAt           time.Time
	lastDate          time.Time
	ladStart, ladStop int
	lodStart, lodStop int
	tdsStart, tdsStop int
	bRecordLen        int
}

// newParser creates a new parser.
func newParser() *parser {
	return &parser{bRecordLen: 35}
}

// parseB parses a B record from line and updates the state of p.
func (p *parser) parseB(line string) error {

	if len(line) != p.bRecordLen {
		return ErrInvalidBRecord
	}

	var err error

	var hour, minute, second, nsec int
	if hour, err = parseDecInRange(line, 1, 3, 0, 24); err != nil {
		return err
	}
	if minute, err = parseDecInRange(line, 3, 5, 0, 60); err != nil {
		return err
	}
	if second, err = parseDecInRange(line, 5, 7, 0, 60); err != nil {
		return err
	}
	if p.tdsStart != 0 {
		decisecond, err := parseDecInRange(line, p.tdsStart, p.tdsStop, 0, 10)
		if err != nil {
			return err
		}
		nsec = decisecond * 1e8
	}
	date := time.Date(p.year, time.Month(p.month), p.day, hour, minute, second, nsec, time.UTC)
	if date.Before(p.lastDate) {
		p.day++
		date = time.Date(p.year, time.Month(p.month), p.day, hour, minute, second, nsec, time.UTC)
	}

	if p.startAt.IsZero() {
		p.startAt = date
	}

	var latDeg, latMilliMin int
	if latDeg, err = parseDecInRange(line, 7, 9, 0, 90); err != nil {
		return err
	}
	// special case: latMilliMin should be in the range [0, 60000) but a number of flight recorders generate latMilliMins of 60000
	// FIXME check what happens in negative (S, W) hemispheres
	if latMilliMin, err = parseDecInRange(line, 9, 14, 0, 60000+1); err != nil {
		return err
	}
	lat := float64(60000*latDeg+latMilliMin) / 60000.
	if p.ladStart != 0 {
		if lad, err := parseDec(line, p.ladStart, p.ladStop); err == nil {
			lat += float64(lad) / 6000000.
		} else {
			return err
		}
	}
	switch c := line[14]; c {
	case 'N':
	case 'S':
		lat = -lat
	default:
		return ErrInvalidCharacter
	}

	var lngDeg, lngMilliMin int
	if lngDeg, err = parseDecInRange(line, 15, 18, 0, 180); err != nil {
		return err
	}
	if lngMilliMin, err = parseDecInRange(line, 18, 23, 0, 60000+1); err != nil {
		return err
	}
	lng := float64(60000*lngDeg+lngMilliMin) / 60000.
	if p.lodStart != 0 {
		if lod, err := parseDec(line, p.lodStart, p.lodStop); err == nil {
			lng += float64(lod) / 6000000.
		} else {
			return err
		}
	}
	switch c := line[23]; c {
	case 'E':
	case 'W':
		lng = -lng
	default:
		return ErrInvalidCharacter
	}

	var pressureAlt, ellipsoidAlt int
	if pressureAlt, err = parseDec(line, 25, 30); err != nil {
		return err
	}
	if ellipsoidAlt, err = parseDec(line, 30, 35); err != nil {
		return err
	}

	p.coords = append(p.coords, lng, lat, float64(ellipsoidAlt), float64(date.UnixNano())/1e9, float64(pressureAlt))
	p.lastDate = date

	return nil

}

// parseB parses an H record from line and updates the state of p.
func (p *parser) parseH(line string) error {
	if m := hRegexp.FindStringSubmatch(line); m != nil {
		p.headers = append(p.headers, Header{
			Source:   m[1],
			Key:      m[2],
			KeyExtra: m[3],
			Value:    m[4],
		})
	}
	switch {
	case strings.HasPrefix(line, "HFDTE"):
		return p.parseHFDTE(line)
	default:
		return nil
	}
}

// parseB parses an HFDTE record from line and updates the state of p.
func (p *parser) parseHFDTE(line string) error {
	var err error
	var day, month, year int
	if len(line) != 11 {
		return ErrInvalidHRecord
	}
	if day, err = parseDecInRange(line, 5, 7, 1, 31+1); err != nil {
		return err
	}
	if month, err = parseDecInRange(line, 7, 9, 1, 12+1); err != nil {
		return err
	}
	if year, err = parseDec(line, 9, 11); err != nil {
		return err
	}
	// FIXME check for invalid dates
	p.day = day
	p.month = month
	if year < 70 {
		p.year = 2000 + year
	} else {
		p.year = 1970 + year
	}
	return nil
}

// parseB parses an I record from line and updates the state of p.
func (p *parser) parseI(line string) error {
	var err error
	var n int
	if len(line) < 3 {
		return ErrInvalidIRecord
	}
	if n, err = parseDec(line, 1, 3); err != nil {
		return err
	}
	if len(line) < 7*n+3 {
		return ErrInvalidIRecord
	}
	for i := 0; i < n; i++ {
		var start, stop int
		if start, err = parseDec(line, 7*i+3, 7*i+5); err != nil {
			return err
		}
		if stop, err = parseDec(line, 7*i+5, 7*i+7); err != nil {
			return err
		}
		if start != p.bRecordLen+1 || stop < start {
			return ErrInvalidIRecord
		}
		p.bRecordLen = stop
		switch line[7*i+7 : 7*i+10] {
		case "LAD":
			p.ladStart, p.ladStop = start-1, stop
		case "LOD":
			p.lodStart, p.lodStop = start-1, stop
		case "TDS":
			p.tdsStart, p.tdsStop = start-1, stop
		}
	}
	return nil
}

// parseLine parses a single record from line and updates the state of p.
func (p *parser) parseLine(line string) error {
	switch line[0] {
	case 'B':
		return p.parseB(line)
	case 'H':
		return p.parseH(line)
	case 'I':
		return p.parseI(line)
	default:
		return nil
	}
}

// doParse reads r, parsers all the records it finds, updating the state of p.
func doParse(r io.Reader) (*parser, Errors) {
	errors := make(Errors)
	p := newParser()
	s := bufio.NewScanner(r)
	foundA := false
	leadingNoise := false
	for lineno := 1; s.Scan(); lineno++ {
		line := s.Text()
		if len(line) == 0 {
			// errors[lineno] = ErrEmptyLine
		} else if foundA {
			if err := p.parseLine(line); err != nil {
				errors[lineno] = err
			}
		} else {
			if c := line[0]; c == 'A' {
				foundA = true
			} else if 'A' <= c && c <= 'Z' {
				// All records that start with an uppercase character must be valid.
				leadingNoise = true
				continue
			} else if i := strings.IndexRune(line, 'A'); i != -1 {
				// Strip any leading noise.
				// The noise must include at least one unprintable character (like XOFF or a fragment of a Unicode BOM).
				for _, c := range line[:i] {
					if !(c == ' ' || ('A' <= c && c <= 'Z')) {
						foundA = true
						leadingNoise = true
						line = line[i:]
						break
					}
				}
			}
		}
	}
	if !foundA {
		errors[1] = ErrMissingARecord
	} else if leadingNoise {
		errors[1] = ErrInvalidCharactersBeforeARecord
	}
	return p, errors
}

// Read reads a igc.T from r, which should contain IGC records.
func Read(r io.Reader) (*T, error) {
	p, errors := doParse(r)
	if len(errors) != 0 {
		return nil, errors
	}
	return &T{
		Headers:    p.headers,
		LineString: geom.NewLineStringFlat(geom.Layout(5), p.coords),
	}, nil
}
<|endoftext|>


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

package route

import (
	"fmt"
	"regexp"
	"strings"
)

var (
	regexpWhitespacePattern          = regexp.MustCompile(`\s+`)
	regexpBackSlashPattern           = regexp.MustCompile(`\\+`)
	regexpdoubleForwardSlashPattern  = regexp.MustCompile(`/+`)
	regexpForbiddenCharactersPattern = regexp.MustCompile(`[&%§\)\(}{\]\["|]`)
)

type Route struct {
	value         string
	originalValue string
}

func NewFromItemPath(basePath, itemPath string) (*Route, error) {

	// normalize the base path
	normalizedBasePath := normalize(basePath)

	// normalize the item path
	normalizedItemPath := normalize(itemPath)

	// prepare the route value:
	// strip the repository path from the item path
	routeValue := strings.Replace(normalizedItemPath, normalizedBasePath, "", 1)

	// strip the file name
	routeValue = routeValue[:strings.LastIndex(routeValue, "/")]

	// trim leading slashes
	routeValue = strings.TrimLeft(routeValue, "/")

	return &Route{
		value:         toUrl(routeValue),
		originalValue: routeValue,
	}, nil
}

func NewFromFilePath(basePath, itemPath string) (*Route, error) {

	// normalize the base path
	normalizedBasePath := normalize(basePath)

	// normalize the item path
	normalizedItemPath := normalize(itemPath)

	// prepare the route value:
	// strip the repository path from the item path
	routeValue := strings.Replace(normalizedItemPath, normalizedBasePath, "", 1)

	// trim leading slashes
	routeValue = strings.TrimLeft(routeValue, "/")

	return &Route{
		value:         toUrl(routeValue),
		originalValue: routeValue,
	}, nil
}

func NewFromRequest(requestPath string) (*Route, error) {

	// normalize the request path
	routeValue := normalize(requestPath)

	return &Route{
		value:         toUrl(routeValue),
		originalValue: routeValue,
	}, nil
}

func New() *Route {

	// normalize the request path
	routeValue := normalize("")

	return &Route{
		value:         toUrl(routeValue),
		originalValue: routeValue,
	}
}

// combines two routes
func Combine(route1, route2 *Route) (*Route, error) {
	return NewFromRequest(route1.OriginalValue() + "/" + route2.OriginalValue())
}

func (route *Route) String() string {
	return route.originalValue
}

func (route *Route) OriginalValue() string {
	return route.originalValue
}

func (route *Route) Components() []string {
	return strings.Split(route.OriginalValue(), "/")
}

func (route *Route) Value() string {
	return route.value
}

func (route *Route) IsEmpty() bool {
	return len(route.value) == 0
}

func (route *Route) LastComponentName() string {
	lastSlashPosition := strings.LastIndex(route.originalValue, "/")
	if lastSlashPosition == -1 {
		return route.originalValue
	}

	return strings.TrimPrefix(route.originalValue[lastSlashPosition:], "/")
}

func (route *Route) Level() int {

	// empty routes have the level 0
	if route.value == "" {
		return 0
	}

	// routes without a slash are 1st level
	if !strings.Contains(route.value, "/") {
		return 1
	}

	// routes with slashes have a level equal to the number of slashes
	return strings.Count(route.value, "/") + 1
}

func (route *Route) SubRoute(level int) (*Route, error) {

	// root level
	if level == 0 {
		return NewFromRequest("")
	}

	// same level
	if level == route.Level() {
		return route, nil
	}

	// split path into components
	components := strings.Split(route.value, "/")

	// abort if the requested level is out of range
	if level > len(components)-1 {
		return nil, fmt.Errorf("The route %q does nof a have a sub-route with the level %d.", route, level)
	}

	// assemble the sub route
	subset := components[0:level]
	subRoutePath := strings.Join(subset, "/")

	subRoute, err := NewFromRequest(subRoutePath)
	if err != nil {
		return nil, fmt.Errorf("Unable to create a route from the path %q. Error: %s", subRoutePath, err)
	}

	return subRoute, nil
}

func (route *Route) IsMatch(path string) bool {
	cleanedRoute := strings.ToLower(route.Value())
	normalizedPath := strings.ToLower(toUrl(normalize(path)))

	// check if the current route ends with the supplied path
	routeEndsWithSpecifiedPath := strings.HasSuffix(cleanedRoute, normalizedPath)

	return routeEndsWithSpecifiedPath
}

func (route *Route) Parent() *Route {

	if route.IsEmpty() {
		return nil
	}

	routeValue := route.Value()

	// if there is no slash, the parent must be the root
	if !strings.Contains(routeValue, "/") {
		return New()
	}

	positionOfLastSlash := strings.LastIndex(routeValue, "/")
	parentRouteValue := routeValue[:positionOfLastSlash]

	parentRoute, err := NewFromRequest(parentRouteValue)
	if err != nil {
		return nil
	}

	return parentRoute
}

// Check if the the current route is direct parent for the supplied (child) route.
func (parent *Route) IsParentOf(child *Route) bool {
	parentRoute := parent.Value()
	childRoute := child.Value()

	// the current route cannot be a parent for the supplied (child) route if the parent route length greater or equal than the child route length.
	if len(parentRoute) >= len(childRoute) {
		return false
	}

	// if the parent route is not the start of the child route it cannot be its parent
	if !strings.HasPrefix(childRoute, parentRoute) {
		return false
	}

	// if there is more than one slash in the relative child route, the child is not a direct descendant of the parent route
	relativeChildRoute := strings.TrimLeft(strings.Replace(childRoute, parentRoute, "", 1), "/")
	if strings.Count(relativeChildRoute, "/") > 0 {
		return false
	}

	// the child is a direct desecendant of the parent
	return true

}

// Check if the current route is a child of the supplied (parent) route.
func (child *Route) IsChildOf(parent *Route) bool {
	childRoute := child.Value()
	parentRoute := parent.Value()

	// the current route cannot be a child of the supplied (parent) route if the child route length less or equal than the parent route length.
	if len(childRoute) <= len(parentRoute) {
		return false
	}

	// if the child route does not start with the parent route it cannot be a child
	if !strings.HasPrefix(childRoute, parentRoute) {
		return false
	}

	return true
}

// Returns a normalized version of the supplied path
func normalize(path string) string {

	// trim spaces
	path = strings.TrimSpace(path)

	// check if the path is empty
	if path == "" {
		return ""
	}

	path = fromUrl(path)

	// replace all forbidden characters
	path = regexpForbiddenCharactersPattern.ReplaceAllString(path, "")

	// replace all backslashes with a (single) forward slash
	path = regexpBackSlashPattern.ReplaceAllString(path, "/")

	// replace multiple forward slashes with a single forward slash
	path = regexpdoubleForwardSlashPattern.ReplaceAllString(path, "/")

	// remove leading slashes
	path = strings.TrimLeft(path, "/")

	// remove trailing slashes
	path = strings.TrimRight(path, "/")

	// replace duplicate spaces
	path = regexpWhitespacePattern.ReplaceAllString(path, " ")

	return path
}

// Returns an "url-safe" version of the supplied path
func toUrl(path string) string {

	// replace duplicate spaces with a (single) url safe character
	path = strings.Replace(path, " ", "-", -1)

	// replace brackets
	path = strings.Replace(path, "(", "%28", -1)
	path = strings.Replace(path, ")", "%29", -1)

	return path
}

// Returns an "url-safe" version of the supplied path
func fromUrl(path string) string {

	// replace duplicate spaces with a (single) url safe character
	path = strings.Replace(path, "-", " ", -1)

	// replace brackets
	path = strings.Replace(path, "%28", "(", -1)
	path = strings.Replace(path, "%29", ")", -1)

	return path
}
<commit_msg>Route factory methods: Return empty routes if the base path is the same as the item path<commit_after>// Copyright 2013 Andreas Koch. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package route

import (
	"fmt"
	"regexp"
	"strings"
)

var (
	regexpWhitespacePattern          = regexp.MustCompile(`\s+`)
	regexpBackSlashPattern           = regexp.MustCompile(`\\+`)
	regexpdoubleForwardSlashPattern  = regexp.MustCompile(`/+`)
	regexpForbiddenCharactersPattern = regexp.MustCompile(`[&%§\)\(}{\]\["|]`)
)

type Route struct {
	value         string
	originalValue string
}

func NewFromItemPath(basePath, itemPath string) (*Route, error) {

	// normalize the base path
	normalizedBasePath := normalize(basePath)

	// normalize the item path
	normalizedItemPath := normalize(itemPath)

	// return a root if both paths are the same
	if normalizedItemPath == normalizedBasePath {
		return New(), nil
	}

	// prepare the route value:
	// strip the repository path from the item path
	routeValue := strings.Replace(normalizedItemPath, normalizedBasePath, "", 1)

	// strip the file name
	routeValue = routeValue[:strings.LastIndex(routeValue, "/")]

	// trim leading slashes
	routeValue = strings.TrimLeft(routeValue, "/")

	return &Route{
		value:         toUrl(routeValue),
		originalValue: routeValue,
	}, nil
}

func NewFromFilePath(basePath, itemPath string) (*Route, error) {

	// normalize the base path
	normalizedBasePath := normalize(basePath)

	// normalize the item path
	normalizedItemPath := normalize(itemPath)

	// return a root if both paths are the same
	if normalizedItemPath == normalizedBasePath {
		return New(), nil
	}

	// prepare the route value:
	// strip the repository path from the item path
	routeValue := strings.Replace(normalizedItemPath, normalizedBasePath, "", 1)

	// trim leading slashes
	routeValue = strings.TrimLeft(routeValue, "/")

	return &Route{
		value:         toUrl(routeValue),
		originalValue: routeValue,
	}, nil
}

func NewFromRequest(requestPath string) (*Route, error) {

	// normalize the request path
	routeValue := normalize(requestPath)

	return &Route{
		value:         toUrl(routeValue),
		originalValue: routeValue,
	}, nil
}

func New() *Route {

	// normalize the request path
	routeValue := normalize("")

	return &Route{
		value:         toUrl(routeValue),
		originalValue: routeValue,
	}
}

// combines two routes
func Combine(route1, route2 *Route) (*Route, error) {
	return NewFromRequest(route1.OriginalValue() + "/" + route2.OriginalValue())
}

func (route *Route) String() string {
	return route.originalValue
}

func (route *Route) OriginalValue() string {
	return route.originalValue
}

func (route *Route) Components() []string {
	return strings.Split(route.OriginalValue(), "/")
}

func (route *Route) Value() string {
	return route.value
}

func (route *Route) IsEmpty() bool {
	return len(route.value) == 0
}

func (route *Route) LastComponentName() string {
	lastSlashPosition := strings.LastIndex(route.originalValue, "/")
	if lastSlashPosition == -1 {
		return route.originalValue
	}

	return strings.TrimPrefix(route.originalValue[lastSlashPosition:], "/")
}

func (route *Route) Level() int {

	// empty routes have the level 0
	if route.value == "" {
		return 0
	}

	// routes without a slash are 1st level
	if !strings.Contains(route.value, "/") {
		return 1
	}

	// routes with slashes have a level equal to the number of slashes
	return strings.Count(route.value, "/") + 1
}

func (route *Route) SubRoute(level int) (*Route, error) {

	// root level
	if level == 0 {
		return NewFromRequest("")
	}

	// same level
	if level == route.Level() {
		return route, nil
	}

	// split path into components
	components := strings.Split(route.value, "/")

	// abort if the requested level is out of range
	if level > len(components)-1 {
		return nil, fmt.Errorf("The route %q does nof a have a sub-route with the level %d.", route, level)
	}

	// assemble the sub route
	subset := components[0:level]
	subRoutePath := strings.Join(subset, "/")

	subRoute, err := NewFromRequest(subRoutePath)
	if err != nil {
		return nil, fmt.Errorf("Unable to create a route from the path %q. Error: %s", subRoutePath, err)
	}

	return subRoute, nil
}

func (route *Route) IsMatch(path string) bool {
	cleanedRoute := strings.ToLower(route.Value())
	normalizedPath := strings.ToLower(toUrl(normalize(path)))

	// check if the current route ends with the supplied path
	routeEndsWithSpecifiedPath := strings.HasSuffix(cleanedRoute, normalizedPath)

	return routeEndsWithSpecifiedPath
}

func (route *Route) Parent() *Route {

	if route.IsEmpty() {
		return nil
	}

	routeValue := route.Value()

	// if there is no slash, the parent must be the root
	if !strings.Contains(routeValue, "/") {
		return New()
	}

	positionOfLastSlash := strings.LastIndex(routeValue, "/")
	parentRouteValue := routeValue[:positionOfLastSlash]

	parentRoute, err := NewFromRequest(parentRouteValue)
	if err != nil {
		return nil
	}

	return parentRoute
}

// Check if the the current route is direct parent for the supplied (child) route.
func (parent *Route) IsParentOf(child *Route) bool {
	parentRoute := parent.Value()
	childRoute := child.Value()

	// the current route cannot be a parent for the supplied (child) route if the parent route length greater or equal than the child route length.
	if len(parentRoute) >= len(childRoute) {
		return false
	}

	// if the parent route is not the start of the child route it cannot be its parent
	if !strings.HasPrefix(childRoute, parentRoute) {
		return false
	}

	// if there is more than one slash in the relative child route, the child is not a direct descendant of the parent route
	relativeChildRoute := strings.TrimLeft(strings.Replace(childRoute, parentRoute, "", 1), "/")
	if strings.Count(relativeChildRoute, "/") > 0 {
		return false
	}

	// the child is a direct desecendant of the parent
	return true

}

// Check if the current route is a child of the supplied (parent) route.
func (child *Route) IsChildOf(parent *Route) bool {
	childRoute := child.Value()
	parentRoute := parent.Value()

	// the current route cannot be a child of the supplied (parent) route if the child route length less or equal than the parent route length.
	if len(childRoute) <= len(parentRoute) {
		return false
	}

	// if the child route does not start with the parent route it cannot be a child
	if !strings.HasPrefix(childRoute, parentRoute) {
		return false
	}

	return true
}

// Returns a normalized version of the supplied path
func normalize(path string) string {

	// trim spaces
	path = strings.TrimSpace(path)

	// check if the path is empty
	if path == "" {
		return ""
	}

	path = fromUrl(path)

	// replace all forbidden characters
	path = regexpForbiddenCharactersPattern.ReplaceAllString(path, "")

	// replace all backslashes with a (single) forward slash
	path = regexpBackSlashPattern.ReplaceAllString(path, "/")

	// replace multiple forward slashes with a single forward slash
	path = regexpdoubleForwardSlashPattern.ReplaceAllString(path, "/")

	// remove leading slashes
	path = strings.TrimLeft(path, "/")

	// remove trailing slashes
	path = strings.TrimRight(path, "/")

	// replace duplicate spaces
	path = regexpWhitespacePattern.ReplaceAllString(path, " ")

	return path
}

// Returns an "url-safe" version of the supplied path
func toUrl(path string) string {

	// replace duplicate spaces with a (single) url safe character
	path = strings.Replace(path, " ", "-", -1)

	// replace brackets
	path = strings.Replace(path, "(", "%28", -1)
	path = strings.Replace(path, ")", "%29", -1)

	return path
}

// Returns an "url-safe" version of the supplied path
func fromUrl(path string) string {

	// replace duplicate spaces with a (single) url safe character
	path = strings.Replace(path, "-", " ", -1)

	// replace brackets
	path = strings.Replace(path, "%28", "(", -1)
	path = strings.Replace(path, "%29", ")", -1)

	return path
}
<|endoftext|>