<commit_before>/*
Command jqsh provides an interactive wrapper to the jq command line utility.

Shell syntax

The current shell syntax is rudimentory but it suffices.  Commands are prefixed
with a color ':' and a command name followed by a space separated list of
arguments.

	> :load test.json

The above loads the file "test.json" into the jqsh cache for inspection.  There
is no quoting of arguments.  A plus '+' may be used on the last argument to
include all charactors up to (but excluding) the next newline character.

	> :push +.items[] | select(.name | contains("hello"))

The above pushes the filter `.items[] | select(.name | contains("hello"))` on
to the jqsh filter stack. This is such a common operation that it has a special
shorthand.  A non-empty line that does not start with a colon causes the line's
contents to be pushed on the filter stack. So the above line could be
simplified.

	> .[] | select(.gender == "Female")

Blank lines are also a shorthand, printing the working filter stack applied to
the input, equivalent to the "write" command.

	> :write

Command reference

A list of commands and other interactive help topics can be found with through
the "help" command.

	> :help

Individual commands respond to the "-h" flag for usage documentation.
*/
package main

import (
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"strings"
	"sync"
	"sync/atomic"
	"unicode"
)

var ErrStackEmpty = fmt.Errorf("the stack is empty")

func main() {
	flag.Parse()
	args := flag.Args()

	// setup initial commands to play before reading input.  single files are
	// loaded with :load, multple files are loaded with :exec cat
	var initcmds [][]string
	doexec := func(cache bool, name string, args ...string) {
		cmd := make([]string, 0, 3+len(args))
		cmd = append(cmd, "exec")
		if !cache {
			cmd = append(cmd, "-c")
		}
		cmd = append(cmd, name)
		cmd = append(cmd, args...)
		initcmds = append(initcmds, cmd)
	}
	switch {
	case len(args) == 1:
		initcmds = [][]string{
			{"load", args[0]},
		}
	case len(args) > 1:
		doexec(false, "cat", args...)
	}

	// create a shell environment and wait for it to receive EOF or a 'quit'
	// command.
	fmt.Println("Welcome to jqsh!")
	fmt.Println()
	fmt.Println("To learn more about the environment type \":help\"")
	fmt.Println()
	fmt.Println("To learn more about jqsh see the online documentation")
	fmt.Println()
	fmt.Println("\thttps://github.com/bmatsuo/jqsh")
	fmt.Println()
	sh := NewInitShellReader(nil, initcmds)
	jq := NewJQShell(sh)
	err := jq.Wait()
	if err != nil {
		log.Fatal(err)
	}
}

// Page returns an io.Writer whose input will be written to the pager program.
// The returned channel should be checked for an error using select before the
// writer is used.
//	w, errch := Page("less")
//	select {
//	case err := <-errch:
//		return err
//	default:
//		w.Write([]byte("boom"))
//	}
func Page(pager []string) (io.WriteCloser, <-chan error) {
	errch := make(chan error, 1)
	if len(pager) == 0 {
		pager = []string{"more", "-r"}
	}
	pagercmd := pager[0]
	pagerargs := pager[1:]
	cmd := exec.Command(pagercmd, pagerargs...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	stdin, err := cmd.StdinPipe()
	if err != nil {
		errch <- err
		return nil, errch
	}
	go func() {
		err := cmd.Run()
		stdin.Close()
		if err != nil {
			errch <- err
		}
		close(errch)
		fmt.Print("\033[0m")
	}()
	return stdin, errch
}

type writeCounter struct {
	n int64
	w io.Writer
}

func (w *writeCounter) Write(bs []byte) (int, error) {
	n, err := w.w.Write(bs)
	if n > 0 {
		atomic.AddInt64(&w.n, int64(n))
	}
	return n, err
}

type InvalidCommandError struct {
	Message string
}

func (err InvalidCommandError) Error() string {
	return err.Message
}

type ShellReader interface {
	ReadCommand() (cmd []string, eof bool, err error)
}

type SimpleShellReader struct {
	r  io.Reader
	br *bufio.Reader
}

func NewShellReader(r io.Reader) *SimpleShellReader {
	if r == nil {
		r = os.Stdin
	}
	br := bufio.NewReader(r)
	return &SimpleShellReader{r, br}
}

func (s *SimpleShellReader) ReadCommand() (cmd []string, eof bool, err error) {
	fmt.Print("> ")
	bs, err := s.br.ReadBytes('\n')
	eof = err == io.EOF
	if err != nil {
		if err == io.EOF && len(bs) > 0 {
			// this is ok
		} else {
			return nil, eof, err
		}
	}
	bs = bytes.TrimFunc(bs, unicode.IsSpace)

	if len(bs) == 0 {
		return []string{}, eof, nil
	} else if bs[0] != ':' {
		str := string(bs)
		cmd := []string{"push", str}
		return cmd, eof, nil
	}

	bs = bs[1:]
	plusi := bytes.Index(bs, []byte{'+'})
	var last *[]byte
	if plusi > 0 {
		lastp := bs[plusi+1:]
		last = &lastp
		bs = bs[:plusi]
	}
	cmd = strings.Fields(string(bs))
	if last != nil {
		cmd = append(cmd, string(*last))
	}
	if len(cmd) == 0 {
		cmd = []string{"write"}
	}
	return cmd, eof, nil
}

// An InitShellReader works like a SimpleShellReader but runs an init script
// before reading any input.
type InitShellReader struct {
	i    int
	init [][]string
	r    *SimpleShellReader
}

func NewInitShellReader(r io.Reader, initcmds [][]string) *InitShellReader {
	return &InitShellReader{0, initcmds, NewShellReader(r)}
}

func (sh *InitShellReader) ReadCommand() ([]string, bool, error) {
	if sh == nil {
		panic("nil shell")
	}
	if sh.i < len(sh.init) {
		cmd := sh.init[sh.i]
		sh.i++
		return cmd, false, nil
	}
	return sh.r.ReadCommand()
}

type JQShell struct {
	Log      *log.Logger
	Stack    *JQStack
	inputfn  func() (io.ReadCloser, error)
	filename string
	istmp    bool // the filename at path should be deleted when changed
	lib      *Lib
	sh       ShellReader
	err      error
	wg       sync.WaitGroup
}

func NewJQShell(sh ShellReader) *JQShell {
	if sh == nil {
		sh = NewShellReader(nil)
	}
	st := new(JQStack)
	jq := &JQShell{
		Log:   log.New(os.Stderr, "jqsh: ", 0),
		Stack: st,
		sh:    sh,
	}
	jq.lib = Library()
	jq.lib.Register("push", JQShellCommandFunc(cmdPush))
	jq.lib.Register("pop", JQShellCommandFunc(cmdPop))
	jq.lib.Register("filter", JQShellCommandFunc(cmdFilter))
	jq.lib.Register("script", JQShellCommandFunc(cmdScript))
	jq.lib.Register("load", JQShellCommandFunc(cmdLoad))
	jq.lib.Register("exec", JQShellCommandFunc(cmdExec))
	jq.lib.Register("write", JQShellCommandFunc(cmdWrite))
	jq.lib.Register("raw", JQShellCommandFunc(cmdRaw))
	jq.lib.Register("quit", JQShellCommandFunc(cmdQuit))
	jq.wg.Add(1)
	go jq.loop()
	return jq
}

func (jq *JQShell) SetInputFile(path string, istmp bool) {
	jq.ClearInput()
	jq.inputfn = nil
	jq.filename = path
	jq.istmp = istmp
}

func (jq *JQShell) SetInput(fn func() (io.ReadCloser, error)) {
	jq.ClearInput()
	jq.inputfn = fn
}

func (jq *JQShell) Input() (io.ReadCloser, error) {
	switch {
	case jq.filename != "":
		jq.Log.Println("open", jq.filename)
		return os.Open(jq.filename)
	case jq.inputfn != nil:
		return jq.inputfn()
	default:
		return nil, fmt.Errorf("no input")
	}
}

func (jq *JQShell) Wait() error {
	jq.wg.Wait()
	return jq.err
}

func isShellExit(err error) bool {
	if err == nil {
		return false
	}
	if err == ShellExit {
		return true
	}
	if err, ok := err.(ExecError); ok {
		return isShellExit(err.err)
	}
	return false
}

func (jq *JQShell) ClearInput() {
	if jq.inputfn != nil {
		jq.inputfn = nil
	}
	if jq.filename != "" && jq.istmp {
		err := os.Remove(jq.filename)
		if err != nil {
			// not a critical error
			jq.Log.Printf("removingtemporary file %v: %v", jq.filename, err)
		}
	}
}

func (jq *JQShell) loop() {
	stop := make(chan struct{})
	_stop := func() { close(stop) }
	ready := make(chan struct{}, 1)
	ready <- struct{}{}
	type cmdin struct {
		cmd []string
		eof bool
		err error
	}
	cmdch := make(chan cmdin)
	for {
		select {
		case <-stop:
			// remove any temporary file
			if jq.filename != "" && jq.istmp {
				err := os.Remove(jq.filename)
				if err != nil {
					// not a critical error
					jq.Log.Printf("removingtemporary file %v: %v", jq.filename, err)
				}
			}
			jq.wg.Done()
			return
		case <-ready:
			go func() {
				cmd, eof, err := jq.sh.ReadCommand()
				cmdch <- cmdin{cmd, eof, err}
			}()
		case cmd := <-cmdch:
			if err, ok := cmd.err.(InvalidCommandError); ok {
				jq.Log.Println(err)
				ready <- struct{}{}
				continue
			}
			go func() {
				err := cmd.err
				if err == io.EOF {
					err = nil
				}
				err = jq.execute(cmd.cmd, err)
				if isShellExit(err) {
					_stop()
					return
				}
				if cmd.eof {
					_stop()
					return
				}
				if err != nil {
					jq.Log.Print(err)
				} else if cmd.cmd[0] != "write" && cmd.cmd[0] != "raw" && cmd.cmd[0] != "filter" && cmd.cmd[0] != "script" && cmd.cmd[0] != "help" {
					// TODO clean this up. (cmdPushInteractive, cmdPeek)
					err := jq.execute([]string{"write"}, nil)
					if err != nil {
						jq.Log.Print(err)
						if cmd.cmd[0] == "push" {
							npush := len(cmd.cmd) - 1
							if npush == 0 {
								npush = 1
							}
							jq.Log.Print("reverting push operation")
							err := jq.execute([]string{"pop", fmt.Sprint(npush)}, nil)
							if err != nil {
								jq.Log.Print(err)
							}
						}
					}
				}
				ready <- struct{}{}
			}()
		}
	}
	panic("unreachable")
}

func (jq *JQShell) log(v ...interface{}) {
	jq.Log.Print(v...)
}

func (jq *JQShell) logf(format string, v ...interface{}) {
	jq.Log.Printf(format, v...)
}

type ExecError struct {
	cmd []string
	err error
}

func (err ExecError) Error() string {
	return fmt.Sprintf("%s: %v", err.cmd[0], err.err)
}

func (jq *JQShell) execute(cmd []string, err error) error {
	if isShellExit(err) {
		return err
	}
	if err, ok := err.(InvalidCommandError); ok {
		jq.Log.Print(err)
		return nil
	}
	if err != nil {
		return err
	}
	if len(cmd) > 0 {
		name, args := cmd[0], cmd[1:]
		return jq.lib.Execute(jq, name, args)
	}
	return nil
}

type JQFilter interface {
	JQFilter() []string
}

var JQFilterJoin = " | "

func JoinFilter(filter JQFilter) string {
	return strings.Join(filter.JQFilter(), JQFilterJoin)
}

type JQFilterString string

func (s JQFilterString) JQFilter() []string {
	return []string{string(s)}
}

type JQStack struct {
	pipe []JQFilter
}

// Args returns arguments for the jq command line utility.
func (s *JQStack) JQFilter() []string {
	if s == nil {
		return []string{"."}
	}
	var args []string
	for _, cmd := range s.pipe {
		args = append(args, cmd.JQFilter()...)
	}
	return args
}

func (s *JQStack) Push(cmd JQFilter) {
	s.pipe = append(s.pipe, cmd)
}

func (s *JQStack) Pop() (JQFilter, error) {
	if len(s.pipe) == 0 {
		return nil, ErrStackEmpty
	}
	n := len(s.pipe)
	filt := s.pipe[n-1]
	s.pipe = s.pipe[:n-1]
	return filt, nil
}
<commit_msg>godoc: typo<commit_after>/*
Command jqsh provides an interactive wrapper to the jq command line utility.

Shell syntax

The current shell syntax is rudimentory but it suffices.  Commands are prefixed
with a colon ':' and a command name followed by a space separated list of
arguments.

	> :load test.json

The above loads the file "test.json" into the jqsh cache for inspection.  There
is no quoting of arguments.  A plus '+' may be used on the last argument to
include all charactors up to (but excluding) the next newline character.

	> :push +.items[] | select(.name | contains("hello"))

The above pushes the filter `.items[] | select(.name | contains("hello"))` on
to the jqsh filter stack. This is such a common operation that it has a special
shorthand.  A non-empty line that does not start with a colon causes the line's
contents to be pushed on the filter stack. So the above line could be
simplified.

	> .[] | select(.gender == "Female")

Blank lines are also a shorthand, printing the working filter stack applied to
the input, equivalent to the "write" command.

	> :write

Command reference

A list of commands and other interactive help topics can be found with through
the "help" command.

	> :help

Individual commands respond to the "-h" flag for usage documentation.
*/
package main

import (
	"bufio"
	"bytes"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"strings"
	"sync"
	"sync/atomic"
	"unicode"
)

var ErrStackEmpty = fmt.Errorf("the stack is empty")

func main() {
	flag.Parse()
	args := flag.Args()

	// setup initial commands to play before reading input.  single files are
	// loaded with :load, multple files are loaded with :exec cat
	var initcmds [][]string
	doexec := func(cache bool, name string, args ...string) {
		cmd := make([]string, 0, 3+len(args))
		cmd = append(cmd, "exec")
		if !cache {
			cmd = append(cmd, "-c")
		}
		cmd = append(cmd, name)
		cmd = append(cmd, args...)
		initcmds = append(initcmds, cmd)
	}
	switch {
	case len(args) == 1:
		initcmds = [][]string{
			{"load", args[0]},
		}
	case len(args) > 1:
		doexec(false, "cat", args...)
	}

	// create a shell environment and wait for it to receive EOF or a 'quit'
	// command.
	fmt.Println("Welcome to jqsh!")
	fmt.Println()
	fmt.Println("To learn more about the environment type \":help\"")
	fmt.Println()
	fmt.Println("To learn more about jqsh see the online documentation")
	fmt.Println()
	fmt.Println("\thttps://github.com/bmatsuo/jqsh")
	fmt.Println()
	sh := NewInitShellReader(nil, initcmds)
	jq := NewJQShell(sh)
	err := jq.Wait()
	if err != nil {
		log.Fatal(err)
	}
}

// Page returns an io.Writer whose input will be written to the pager program.
// The returned channel should be checked for an error using select before the
// writer is used.
//	w, errch := Page("less")
//	select {
//	case err := <-errch:
//		return err
//	default:
//		w.Write([]byte("boom"))
//	}
func Page(pager []string) (io.WriteCloser, <-chan error) {
	errch := make(chan error, 1)
	if len(pager) == 0 {
		pager = []string{"more", "-r"}
	}
	pagercmd := pager[0]
	pagerargs := pager[1:]
	cmd := exec.Command(pagercmd, pagerargs...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	stdin, err := cmd.StdinPipe()
	if err != nil {
		errch <- err
		return nil, errch
	}
	go func() {
		err := cmd.Run()
		stdin.Close()
		if err != nil {
			errch <- err
		}
		close(errch)
		fmt.Print("\033[0m")
	}()
	return stdin, errch
}

type writeCounter struct {
	n int64
	w io.Writer
}

func (w *writeCounter) Write(bs []byte) (int, error) {
	n, err := w.w.Write(bs)
	if n > 0 {
		atomic.AddInt64(&w.n, int64(n))
	}
	return n, err
}

type InvalidCommandError struct {
	Message string
}

func (err InvalidCommandError) Error() string {
	return err.Message
}

type ShellReader interface {
	ReadCommand() (cmd []string, eof bool, err error)
}

type SimpleShellReader struct {
	r  io.Reader
	br *bufio.Reader
}

func NewShellReader(r io.Reader) *SimpleShellReader {
	if r == nil {
		r = os.Stdin
	}
	br := bufio.NewReader(r)
	return &SimpleShellReader{r, br}
}

func (s *SimpleShellReader) ReadCommand() (cmd []string, eof bool, err error) {
	fmt.Print("> ")
	bs, err := s.br.ReadBytes('\n')
	eof = err == io.EOF
	if err != nil {
		if err == io.EOF && len(bs) > 0 {
			// this is ok
		} else {
			return nil, eof, err
		}
	}
	bs = bytes.TrimFunc(bs, unicode.IsSpace)

	if len(bs) == 0 {
		return []string{}, eof, nil
	} else if bs[0] != ':' {
		str := string(bs)
		cmd := []string{"push", str}
		return cmd, eof, nil
	}

	bs = bs[1:]
	plusi := bytes.Index(bs, []byte{'+'})
	var last *[]byte
	if plusi > 0 {
		lastp := bs[plusi+1:]
		last = &lastp
		bs = bs[:plusi]
	}
	cmd = strings.Fields(string(bs))
	if last != nil {
		cmd = append(cmd, string(*last))
	}
	if len(cmd) == 0 {
		cmd = []string{"write"}
	}
	return cmd, eof, nil
}

// An InitShellReader works like a SimpleShellReader but runs an init script
// before reading any input.
type InitShellReader struct {
	i    int
	init [][]string
	r    *SimpleShellReader
}

func NewInitShellReader(r io.Reader, initcmds [][]string) *InitShellReader {
	return &InitShellReader{0, initcmds, NewShellReader(r)}
}

func (sh *InitShellReader) ReadCommand() ([]string, bool, error) {
	if sh == nil {
		panic("nil shell")
	}
	if sh.i < len(sh.init) {
		cmd := sh.init[sh.i]
		sh.i++
		return cmd, false, nil
	}
	return sh.r.ReadCommand()
}

type JQShell struct {
	Log      *log.Logger
	Stack    *JQStack
	inputfn  func() (io.ReadCloser, error)
	filename string
	istmp    bool // the filename at path should be deleted when changed
	lib      *Lib
	sh       ShellReader
	err      error
	wg       sync.WaitGroup
}

func NewJQShell(sh ShellReader) *JQShell {
	if sh == nil {
		sh = NewShellReader(nil)
	}
	st := new(JQStack)
	jq := &JQShell{
		Log:   log.New(os.Stderr, "jqsh: ", 0),
		Stack: st,
		sh:    sh,
	}
	jq.lib = Library()
	jq.lib.Register("push", JQShellCommandFunc(cmdPush))
	jq.lib.Register("pop", JQShellCommandFunc(cmdPop))
	jq.lib.Register("filter", JQShellCommandFunc(cmdFilter))
	jq.lib.Register("script", JQShellCommandFunc(cmdScript))
	jq.lib.Register("load", JQShellCommandFunc(cmdLoad))
	jq.lib.Register("exec", JQShellCommandFunc(cmdExec))
	jq.lib.Register("write", JQShellCommandFunc(cmdWrite))
	jq.lib.Register("raw", JQShellCommandFunc(cmdRaw))
	jq.lib.Register("quit", JQShellCommandFunc(cmdQuit))
	jq.wg.Add(1)
	go jq.loop()
	return jq
}

func (jq *JQShell) SetInputFile(path string, istmp bool) {
	jq.ClearInput()
	jq.inputfn = nil
	jq.filename = path
	jq.istmp = istmp
}

func (jq *JQShell) SetInput(fn func() (io.ReadCloser, error)) {
	jq.ClearInput()
	jq.inputfn = fn
}

func (jq *JQShell) Input() (io.ReadCloser, error) {
	switch {
	case jq.filename != "":
		jq.Log.Println("open", jq.filename)
		return os.Open(jq.filename)
	case jq.inputfn != nil:
		return jq.inputfn()
	default:
		return nil, fmt.Errorf("no input")
	}
}

func (jq *JQShell) Wait() error {
	jq.wg.Wait()
	return jq.err
}

func isShellExit(err error) bool {
	if err == nil {
		return false
	}
	if err == ShellExit {
		return true
	}
	if err, ok := err.(ExecError); ok {
		return isShellExit(err.err)
	}
	return false
}

func (jq *JQShell) ClearInput() {
	if jq.inputfn != nil {
		jq.inputfn = nil
	}
	if jq.filename != "" && jq.istmp {
		err := os.Remove(jq.filename)
		if err != nil {
			// not a critical error
			jq.Log.Printf("removingtemporary file %v: %v", jq.filename, err)
		}
	}
}

func (jq *JQShell) loop() {
	stop := make(chan struct{})
	_stop := func() { close(stop) }
	ready := make(chan struct{}, 1)
	ready <- struct{}{}
	type cmdin struct {
		cmd []string
		eof bool
		err error
	}
	cmdch := make(chan cmdin)
	for {
		select {
		case <-stop:
			// remove any temporary file
			if jq.filename != "" && jq.istmp {
				err := os.Remove(jq.filename)
				if err != nil {
					// not a critical error
					jq.Log.Printf("removingtemporary file %v: %v", jq.filename, err)
				}
			}
			jq.wg.Done()
			return
		case <-ready:
			go func() {
				cmd, eof, err := jq.sh.ReadCommand()
				cmdch <- cmdin{cmd, eof, err}
			}()
		case cmd := <-cmdch:
			if err, ok := cmd.err.(InvalidCommandError); ok {
				jq.Log.Println(err)
				ready <- struct{}{}
				continue
			}
			go func() {
				err := cmd.err
				if err == io.EOF {
					err = nil
				}
				err = jq.execute(cmd.cmd, err)
				if isShellExit(err) {
					_stop()
					return
				}
				if cmd.eof {
					_stop()
					return
				}
				if err != nil {
					jq.Log.Print(err)
				} else if cmd.cmd[0] != "write" && cmd.cmd[0] != "raw" && cmd.cmd[0] != "filter" && cmd.cmd[0] != "script" && cmd.cmd[0] != "help" {
					// TODO clean this up. (cmdPushInteractive, cmdPeek)
					err := jq.execute([]string{"write"}, nil)
					if err != nil {
						jq.Log.Print(err)
						if cmd.cmd[0] == "push" {
							npush := len(cmd.cmd) - 1
							if npush == 0 {
								npush = 1
							}
							jq.Log.Print("reverting push operation")
							err := jq.execute([]string{"pop", fmt.Sprint(npush)}, nil)
							if err != nil {
								jq.Log.Print(err)
							}
						}
					}
				}
				ready <- struct{}{}
			}()
		}
	}
	panic("unreachable")
}

func (jq *JQShell) log(v ...interface{}) {
	jq.Log.Print(v...)
}

func (jq *JQShell) logf(format string, v ...interface{}) {
	jq.Log.Printf(format, v...)
}

type ExecError struct {
	cmd []string
	err error
}

func (err ExecError) Error() string {
	return fmt.Sprintf("%s: %v", err.cmd[0], err.err)
}

func (jq *JQShell) execute(cmd []string, err error) error {
	if isShellExit(err) {
		return err
	}
	if err, ok := err.(InvalidCommandError); ok {
		jq.Log.Print(err)
		return nil
	}
	if err != nil {
		return err
	}
	if len(cmd) > 0 {
		name, args := cmd[0], cmd[1:]
		return jq.lib.Execute(jq, name, args)
	}
	return nil
}

type JQFilter interface {
	JQFilter() []string
}

var JQFilterJoin = " | "

func JoinFilter(filter JQFilter) string {
	return strings.Join(filter.JQFilter(), JQFilterJoin)
}

type JQFilterString string

func (s JQFilterString) JQFilter() []string {
	return []string{string(s)}
}

type JQStack struct {
	pipe []JQFilter
}

// Args returns arguments for the jq command line utility.
func (s *JQStack) JQFilter() []string {
	if s == nil {
		return []string{"."}
	}
	var args []string
	for _, cmd := range s.pipe {
		args = append(args, cmd.JQFilter()...)
	}
	return args
}

func (s *JQStack) Push(cmd JQFilter) {
	s.pipe = append(s.pipe, cmd)
}

func (s *JQStack) Pop() (JQFilter, error) {
	if len(s.pipe) == 0 {
		return nil, ErrStackEmpty
	}
	n := len(s.pipe)
	filt := s.pipe[n-1]
	s.pipe = s.pipe[:n-1]
	return filt, nil
}
<|endoftext|>


<commit_before>package cli

import (
	"fmt"
	"os"
	"regexp"

	"github.com/gruntwork-io/terragrunt/config"
	"github.com/gruntwork-io/terragrunt/errors"
	"github.com/gruntwork-io/terragrunt/locks"
	"github.com/gruntwork-io/terragrunt/remote"
	"github.com/gruntwork-io/terragrunt/shell"
	"github.com/gruntwork-io/terragrunt/util"
	"github.com/urfave/cli"
)

// Since Terragrunt is just a thin wrapper for Terraform, and we don't want to repeat every single Terraform command
// in its definition, we don't quite fit into the model of any Go CLI library. Fortunately, urfave/cli allows us to
// override the whole template used for the Usage Text.
const CUSTOM_USAGE_TEXT = `DESCRIPTION:
   {{.Name}} - {{.UsageText}}

USAGE:
   {{.Usage}}

COMMANDS:
   apply                Acquire a lock and run 'terraform apply'
   destroy              Acquire a lock and run 'terraform destroy'
   import               Acquire a lock and run 'terraform import'
   release-lock         Release a lock that is left over from some previous command
   *                    Terragrunt forwards all other commands directly to Terraform
{{if .VisibleFlags}}
GLOBAL OPTIONS:
   {{range .VisibleFlags}}{{.}}
   {{end}}{{end}}
VERSION:
   {{.Version}}{{if len .Authors}}

AUTHOR(S):
   {{range .Authors}}{{.}}{{end}}
   {{end}}
`

var MODULE_REGEX = regexp.MustCompile(`module ".+"`)

const TERRAFORM_EXTENSION_GLOB = "*.tf"

// Create the Terragrunt CLI App
func CreateTerragruntCli(version string) *cli.App {
	cli.AppHelpTemplate = CUSTOM_USAGE_TEXT

	app := cli.NewApp()

	app.Name = "terragrunt"
	app.Author = "Gruntwork <www.gruntwork.io>"
	app.Version = version
	app.Action = runApp
	app.Usage = "terragrunt <COMMAND>"
	app.UsageText = `Terragrunt is a thin wrapper for [Terraform](https://www.terraform.io/) that supports locking
   via Amazon's DynamoDB and enforces best practices. Terragrunt forwards almost all commands, arguments, and options
   directly to Terraform, using whatever version of Terraform you already have installed. However, before running
   Terraform, Terragrunt will ensure your remote state is configured according to the settings in the .terragrunt file.
   Moreover, for the apply and destroy commands, Terragrunt will first try to acquire a lock using DynamoDB. For
   documentation, see https://github.com/gruntwork-io/terragrunt/.`

	var defaultConfigFilePath = config.ConfigFilePath
	if os.Getenv("TERRAGRUNT_CONFIG") != "" {
		defaultConfigFilePath = os.Getenv("TERRAGRUNT_CONFIG")
	}

	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "terragrunt-config",
			Value: defaultConfigFilePath,
			Usage: ".terragrunt file to use",
		},
	}

	return app
}

// The sole action for the app. It forwards all commands directly to Terraform, enforcing a few best practices along
// the way, such as configuring remote state or acquiring a lock.
func runApp(cliContext *cli.Context) (finalErr error) {
	defer errors.Recover(func(cause error) { finalErr = cause })

	// If someone calls us with no args at all, show the help text and exit
	if !cliContext.Args().Present() {
		cli.ShowAppHelp(cliContext)
		return nil
	}

	conf, err := config.ReadTerragruntConfig(cliContext.String("terragrunt-config"))
	if err != nil {
		return err
	}

	if err := downloadModules(cliContext); err != nil {
		return err
	}

	if conf.RemoteState != nil {
		if err := configureRemoteState(cliContext, conf.RemoteState); err != nil {
			return err
		}
	}

	if conf.Lock == nil {
		util.Logger.Printf("WARNING: you have not configured locking in your .terragrunt file. Concurrent changes to your .tfstate files may cause conflicts!")
		return runTerraformCommand(cliContext)
	}

	return runTerraformCommandWithLock(cliContext, conf.Lock)
}

// A quick sanity check that calls `terraform get` to download modules, if they aren't already downloaded.
func downloadModules(cliContext *cli.Context) error {
	switch cliContext.Args().First() {
	case "apply", "destroy", "graph", "output", "plan", "show", "taint", "untaint", "validate":
		shouldDownload, err := shouldDownloadModules()
		if err != nil {
			return err
		}
		if shouldDownload {
			return shell.RunShellCommand("terraform", "get", "-update")
		}
	}

	return nil
}

// Return true if modules aren't already downloaded and the Terraform templates in this project reference modules.
// Note that to keep the logic in this code very simple, this code ONLY detects the case where you haven't downloaded
// modules at all. Detecting if your downloaded modules are out of date (as opposed to missing entirely) is more
// complicated and not something we handle at the moment.
func shouldDownloadModules() (bool, error) {
	if util.FileExists(".terraform/modules") {
		return false, nil
	}

	return util.Grep(MODULE_REGEX, TERRAFORM_EXTENSION_GLOB)
}

// If the user entered a Terraform command that uses state (e.g. plan, apply), make sure remote state is configured
// before running the command.
func configureRemoteState(cliContext *cli.Context, remoteState *remote.RemoteState) error {
	// We only configure remote state for the commands that use the tfstate files. We do not configure it for
	// commands such as "get" or "version".
	switch cliContext.Args().First() {
	case "apply", "destroy", "import", "graph", "output", "plan", "push", "refresh", "show", "taint", "untaint", "validate":
		return remoteState.ConfigureRemoteState()
	case "remote":
		if cliContext.Args().Get(1) == "config" {
			// Encourage the user to configure remote state by defining it in .terragrunt and letting
			// Terragrunt handle it for them
			return errors.WithStackTrace(DontManuallyConfigureRemoteState)
		} else {
			// The other "terraform remote" commands explicitly push or pull state, so we shouldn't mess
			// with the configuration
			return nil
		}
	}

	return nil
}

// Run the given Terraform command with the given lock (if the command requires locking)
func runTerraformCommandWithLock(cliContext *cli.Context, lock locks.Lock) error {
	switch cliContext.Args().First() {
	case "apply", "destroy", "import":
		return locks.WithLock(lock, func() error { return runTerraformCommand(cliContext) })
	case "release-lock":
		return runReleaseLockCommand(cliContext, lock)
	default:
		return runTerraformCommand(cliContext)
	}
}

// Run the given Terraform command
func runTerraformCommand(cliContext *cli.Context) error {
	return shell.RunShellCommand("terraform", cliContext.Args()...)
}

// Release a lock, prompting the user for confirmation first
func runReleaseLockCommand(cliContext *cli.Context, lock locks.Lock) error {
	proceed, err := shell.PromptUserForYesNo(fmt.Sprintf("Are you sure you want to release %s?", lock))
	if err != nil {
		return err
	}

	if proceed {
		return lock.ReleaseLock()
	} else {
		return nil
	}
}

var DontManuallyConfigureRemoteState = fmt.Errorf("Instead of manually using the 'remote config' command, define your remote state settings in .terragrunt and Terragrunt will automatically configure it for you (and all your team members) next time you run it.")
<commit_msg>Add remote push command to lock state action<commit_after>package cli

import (
	"fmt"
	"os"
	"regexp"

	"github.com/gruntwork-io/terragrunt/config"
	"github.com/gruntwork-io/terragrunt/errors"
	"github.com/gruntwork-io/terragrunt/locks"
	"github.com/gruntwork-io/terragrunt/remote"
	"github.com/gruntwork-io/terragrunt/shell"
	"github.com/gruntwork-io/terragrunt/util"
	"github.com/urfave/cli"
)

// Since Terragrunt is just a thin wrapper for Terraform, and we don't want to repeat every single Terraform command
// in its definition, we don't quite fit into the model of any Go CLI library. Fortunately, urfave/cli allows us to
// override the whole template used for the Usage Text.
const CUSTOM_USAGE_TEXT = `DESCRIPTION:
   {{.Name}} - {{.UsageText}}

USAGE:
   {{.Usage}}

COMMANDS:
   apply                Acquire a lock and run 'terraform apply'
   destroy              Acquire a lock and run 'terraform destroy'
   import               Acquire a lock and run 'terraform import'
   remote push          Acquire a lock and run 'terraform remote push'
   release-lock         Release a lock that is left over from some previous command
   *                    Terragrunt forwards all other commands directly to Terraform
{{if .VisibleFlags}}
GLOBAL OPTIONS:
   {{range .VisibleFlags}}{{.}}
   {{end}}{{end}}
VERSION:
   {{.Version}}{{if len .Authors}}

AUTHOR(S):
   {{range .Authors}}{{.}}{{end}}
   {{end}}
`

var MODULE_REGEX = regexp.MustCompile(`module ".+"`)

const TERRAFORM_EXTENSION_GLOB = "*.tf"

// Create the Terragrunt CLI App
func CreateTerragruntCli(version string) *cli.App {
	cli.AppHelpTemplate = CUSTOM_USAGE_TEXT

	app := cli.NewApp()

	app.Name = "terragrunt"
	app.Author = "Gruntwork <www.gruntwork.io>"
	app.Version = version
	app.Action = runApp
	app.Usage = "terragrunt <COMMAND>"
	app.UsageText = `Terragrunt is a thin wrapper for [Terraform](https://www.terraform.io/) that supports locking
   via Amazon's DynamoDB and enforces best practices. Terragrunt forwards almost all commands, arguments, and options
   directly to Terraform, using whatever version of Terraform you already have installed. However, before running
   Terraform, Terragrunt will ensure your remote state is configured according to the settings in the .terragrunt file.
   Moreover, for the apply and destroy commands, Terragrunt will first try to acquire a lock using DynamoDB. For
   documentation, see https://github.com/gruntwork-io/terragrunt/.`

	var defaultConfigFilePath = config.ConfigFilePath
	if os.Getenv("TERRAGRUNT_CONFIG") != "" {
		defaultConfigFilePath = os.Getenv("TERRAGRUNT_CONFIG")
	}

	app.Flags = []cli.Flag{
		cli.StringFlag{
			Name:  "terragrunt-config",
			Value: defaultConfigFilePath,
			Usage: ".terragrunt file to use",
		},
	}

	return app
}

// The sole action for the app. It forwards all commands directly to Terraform, enforcing a few best practices along
// the way, such as configuring remote state or acquiring a lock.
func runApp(cliContext *cli.Context) (finalErr error) {
	defer errors.Recover(func(cause error) { finalErr = cause })

	// If someone calls us with no args at all, show the help text and exit
	if !cliContext.Args().Present() {
		cli.ShowAppHelp(cliContext)
		return nil
	}

	conf, err := config.ReadTerragruntConfig(cliContext.String("terragrunt-config"))
	if err != nil {
		return err
	}

	if err := downloadModules(cliContext); err != nil {
		return err
	}

	if conf.RemoteState != nil {
		if err := configureRemoteState(cliContext, conf.RemoteState); err != nil {
			return err
		}
	}

	if conf.Lock == nil {
		util.Logger.Printf("WARNING: you have not configured locking in your .terragrunt file. Concurrent changes to your .tfstate files may cause conflicts!")
		return runTerraformCommand(cliContext)
	}

	return runTerraformCommandWithLock(cliContext, conf.Lock)
}

// A quick sanity check that calls `terraform get` to download modules, if they aren't already downloaded.
func downloadModules(cliContext *cli.Context) error {
	switch cliContext.Args().First() {
	case "apply", "destroy", "graph", "output", "plan", "show", "taint", "untaint", "validate":
		shouldDownload, err := shouldDownloadModules()
		if err != nil {
			return err
		}
		if shouldDownload {
			return shell.RunShellCommand("terraform", "get", "-update")
		}
	}

	return nil
}

// Return true if modules aren't already downloaded and the Terraform templates in this project reference modules.
// Note that to keep the logic in this code very simple, this code ONLY detects the case where you haven't downloaded
// modules at all. Detecting if your downloaded modules are out of date (as opposed to missing entirely) is more
// complicated and not something we handle at the moment.
func shouldDownloadModules() (bool, error) {
	if util.FileExists(".terraform/modules") {
		return false, nil
	}

	return util.Grep(MODULE_REGEX, TERRAFORM_EXTENSION_GLOB)
}

// If the user entered a Terraform command that uses state (e.g. plan, apply), make sure remote state is configured
// before running the command.
func configureRemoteState(cliContext *cli.Context, remoteState *remote.RemoteState) error {
	// We only configure remote state for the commands that use the tfstate files. We do not configure it for
	// commands such as "get" or "version".
	switch cliContext.Args().First() {
	case "apply", "destroy", "import", "graph", "output", "plan", "push", "refresh", "show", "taint", "untaint", "validate":
		return remoteState.ConfigureRemoteState()
	case "remote":
		if cliContext.Args().Get(1) == "config" {
			// Encourage the user to configure remote state by defining it in .terragrunt and letting
			// Terragrunt handle it for them
			return errors.WithStackTrace(DontManuallyConfigureRemoteState)
		} else {
			// The other "terraform remote" commands explicitly push or pull state, so we shouldn't mess
			// with the configuration
			return nil
		}
	}

	return nil
}

// Run the given Terraform command with the given lock (if the command requires locking)
func runTerraformCommandWithLock(cliContext *cli.Context, lock locks.Lock) error {
	switch cliContext.Args().First() {
	case "apply", "destroy", "import":
		return locks.WithLock(lock, func() error { return runTerraformCommand(cliContext) })
	case "remote":
		if cliContext.Args().Get(1) == "push" {
			return locks.WithLock(lock, func() error { return runTerraformCommand(cliContext) })
		} else {
			return runTerraformCommand(cliContext)
		}
	case "release-lock":
		return runReleaseLockCommand(cliContext, lock)
	default:
		return runTerraformCommand(cliContext)
	}
}

// Run the given Terraform command
func runTerraformCommand(cliContext *cli.Context) error {
	return shell.RunShellCommand("terraform", cliContext.Args()...)
}

// Release a lock, prompting the user for confirmation first
func runReleaseLockCommand(cliContext *cli.Context, lock locks.Lock) error {
	proceed, err := shell.PromptUserForYesNo(fmt.Sprintf("Are you sure you want to release %s?", lock))
	if err != nil {
		return err
	}

	if proceed {
		return lock.ReleaseLock()
	} else {
		return nil
	}
}

var DontManuallyConfigureRemoteState = fmt.Errorf("Instead of manually using the 'remote config' command, define your remote state settings in .terragrunt and Terragrunt will automatically configure it for you (and all your team members) next time you run it.")
<|endoftext|>


<commit_before>package redis

import (
	"strconv"
)

func (r *Redis) Del(keys ...string) (int, error) {
	args := []string{"DEL"}
	args = append(args, keys...)
	if err := r.send_command(args...); err != nil {
		return -1, err
	}
	return r.integer_reply()
}

func (r *Redis) Dump(key string) (string, error) {
	if err := r.send_command("DUMP", key); err != nil {
		return "", err
	}
	bulk, err := r.bulk_reply()
	if err != nil {
		return "", err
	}
	if bulk == nil {
		return "", NilBulkError
	}
	return *bulk, nil
}

func (r *Redis) Exists(key string) (bool, error) {
	if err := r.send_command("EXISTS", key); err != nil {
		return false, err
	}
	return r.bool_reply()
}

func (r *Redis) Expire(key string, seconds int) (bool, error) {
	if err := r.send_command("EXPIRE", key, strconv.Itoa(seconds)); err != nil {
		return false, err
	}
	return r.bool_reply()
}

func (r *Redis) Expireat(key string, timestamp int) (bool, error) {
	if err := r.send_command("EXPIREAT", key, strconv.Itoa(timestamp)); err != nil {
		return false, err
	}
	return r.bool_reply()
}

func (r *Redis) Keys(pattern string) ([]string, error) {
	if err := r.send_command("KEYS", pattern); err != nil {
		return []string{}, err
	}
	multibulk, err := r.multibulk_reply()
	if err != nil {
		return []string{}, err
	}
	if multibulk == nil {
		return []string{}, NilBulkError
	}
	result := make([]string, len(*multibulk))
	for _, key := range *multibulk {
		result = append(result, *key)
	}
	return result, nil
}
<commit_msg>add the rest of keys command[ignore apart]<commit_after>package redis

import (
	"strconv"
)

func (r *Redis) Del(keys ...string) (int, error) {
	args := []string{"DEL"}
	args = append(args, keys...)
	if err := r.send_command(args...); err != nil {
		return -1, err
	}
	return r.integer_reply()
}

func (r *Redis) Dump(key string) (string, error) {
	if err := r.send_command("DUMP", key); err != nil {
		return "", err
	}
	bulk, err := r.bulk_reply()
	if err != nil {
		return "", err
	}
	if bulk == nil {
		return "", NilBulkError
	}
	return *bulk, nil
}

func (r *Redis) Exists(key string) (bool, error) {
	if err := r.send_command("EXISTS", key); err != nil {
		return false, err
	}
	return r.bool_reply()
}

func (r *Redis) Expire(key string, seconds int) (bool, error) {
	if err := r.send_command("EXPIRE", key, strconv.Itoa(seconds)); err != nil {
		return false, err
	}
	return r.bool_reply()
}

func (r *Redis) Expireat(key string, timestamp int) (bool, error) {
	if err := r.send_command("EXPIREAT", key, strconv.Itoa(timestamp)); err != nil {
		return false, err
	}
	return r.bool_reply()
}

func (r *Redis) Keys(pattern string) ([]string, error) {
	if err := r.send_command("KEYS", pattern); err != nil {
		return []string{}, err
	}
	multibulk, err := r.multibulk_reply()
	if err != nil {
		return []string{}, err
	}
	if multibulk == nil {
		return []string{}, NilBulkError
	}
	result := make([]string, len(*multibulk))
	for _, key := range *multibulk {
		result = append(result, *key)
	}
	return result, nil
}

func (r *Redis) Move(key string, db int) (bool, error) {
	if err := r.send_command("MOVE", key, strconv.Itoa(db)); err != nil {
		return false, err
	}
	return r.bool_reply()
}

func (r *Redis) Persist(key string) (bool, error) {
	if err := r.send_command("PERSIST", key); err != nil {
		return false, err
	}
	return r.bool_reply()
}

func (r *Redis) Pexpire(key string, milliseconds int) (bool, error) {
	if err := r.send_command("PEXPIRE", key, strconv.Itoa(milliseconds)); err != nil {
		return false, err
	}
	return r.bool_reply()
}

func (r *Redis) Pexpireat(key string, timestamp int) (bool, error) {
	if err := r.send_command("PEXPIREAT", key, strconv.Itoa(timestamp)); err != nil {
		return false, err
	}
	return r.bool_reply()
}

func (r *Redis) Pttl(key string) (int, error) {
	if err := r.send_command("PTTL", key); err != nil {
		return -1, err
	}
	return r.integer_reply()
}

func (r *Redis) RandomKey() (string, error) {
	if err := r.send_command("RANDOMKEY"); err != nil {
		return "", err
	}
	bulk, err := r.bulk_reply()
	if err != nil {
		return "", err
	}
	if bulk == nil {
		return "", nil
	}
	return *bulk, nil
}

func (r *Redis) Rename(key, newkey string) error {
	if err := r.send_command("RENAME", key, newkey); err != nil {
		return err
	}
	_, err := r.status_reply()
	if err != nil {
		return err
	}
	return nil
}

func (r *Redis) Renamenx(key, newkey string) (bool, error) {
	if err := r.send_command("RENAMENX", key, newkey); err != nil {
		return false, err
	}
	return r.bool_reply()
}

func (r *Redis) Restore(key string, ttl int, serialized string) error {
	if err := r.send_command("RESTORE", key, strconv.Itoa(ttl), serialized); err != nil {
		return err
	}
	return r.ok_reply()
}

func (r *Redis) TTL(key string) (int, error) {
	if err := r.send_command("TTL", key); err != nil {
		return -1, err
	}
	return r.integer_reply()
}

func (r *Redis) Type(key string) (string, error) {
	if err := r.send_command("TYPE", key); err != nil {
		return "", err
	}
	return r.status_reply()
}
<|endoftext|>


<commit_before>package goketo

import (
	"encoding/json"
	"net/url"
	"strconv"
	"strings"

	"github.com/Sirupsen/logrus"
)

// Convert all to Interfaces for re-usability
// Add fmt.Sprintf("%v:%v", host, port) to build strings

// LeadResponse response from list request
type LeadResponse struct {
	client    *Client
	RequestID string          `json:"requestId"`
	Result    json.RawMessage `json:"result"`
	Success   bool            `json:"success"`
	Next      string          `json:"nextPageToken,omitempty"`
	More      bool            `json:"moreResult,omitempty"`
	Errors    []struct {
		Code    string `json:"code"`
		Message string `json:"message"`
	} `json:"errors,omitempty"`
}

// LeadResult default result struct as part of the lead - can be customized to allow greater fields
type LeadResult struct {
	ID        int    `json:"id"`
	FirstName string `json:"firstName"`
	LastName  string `json:"lastName"`
	Email     string `json:"email"`
	Created   string `json:"createdAt"`
	Updated   string `json:"updatedAt"`
}

// LeadRequest builds a request for data retrieval
type LeadRequest struct {
	ID     int    // List ID
	Next   string // Next page Token
	Fields string
}

// LeadUpdate builds the data for an update
type LeadUpdate struct {
	Action string          `json:"action"` // createOnly - updateOnly - createOrUpdate(default request) - createDuplicate
	Lookup string          `json:"lookupField"`
	Input  json.RawMessage `json:"input"`
}

// LeadUpdateResponse data format for update response
type LeadUpdateResponse struct {
	ID      string             `json:"requestId"`
	Success bool               `json:"success"`
	Result  []LeadUpdateResult `json:"result,omitempty"`
	Error   []LeadError        `json:"errors,omitempty"`
}

// LeadUpdateResult holds result for all updates
type LeadUpdateResult struct {
	ID     int    `json:"id"`
	Status string `json:"status"`
}

// LeadError shows the error code and message for response
type LeadError struct {
	Code    string `json:"code"`
	Message string `json:"message"`
}

// LeadFieldResponse response for all fields
type LeadFieldResponse struct {
	client    *Client
	RequestID string      `json:"requestId"`
	Result    []LeadField `json:"result"`
	Success   bool        `json:"success"`
}

// LeadField describes all possible fields for Leads
type LeadField struct {
	ID     int    `json:"id"`
	Name   string `json:"displayName"`
	Type   string `json:"dataType"`
	Length int    `json:"length"`
	Rest   struct {
		Name     string `json:"name"`
		ReadOnly bool   `json:"readOnly"`
	} `json:"rest"`
	Soap struct {
		Name     string `json:"name"`
		ReadOnly bool   `json:"readOnly"`
	} `json:"soap"`
}

// DeletedLeadResponse response of Deleted lead request
type DeletedLeadResponse struct {
	*LeadResponse
	Result []DeletedLead `json:"result"`
}

// DeletedLead result
type DeletedLead struct {
	ID         int      `json:"id"`
	LeadID     int      `json:"leadId"`
	Date       string   `json:"activityDate"`
	TypeID     int      `json:"activityTypeId"`
	PrimaryID  int      `json:"primaryAttributeValueId"`
	PrimaryVal string   `json:"primaryAttributeValue"`
	Attributes []string `json:"attributes"`
}

// Leads Get leads by list Id
func (c *Client) Leads(leadReq *LeadRequest) (leads *LeadResponse, err error) {
	nextPage := url.Values{}
	if leadReq.Next != "" {
		nextPage.Set("&nextPageToken", leadReq.Next)
	}
	fields := url.Values{}
	if len(leadReq.Fields) > 0 {
		fields.Set("&fields", strings.Join(strings.Fields(leadReq.Fields), ""))
	}
	logrus.Debug("Fields: ", fields.Encode())
	body, err := c.Get("/list/" + strconv.Itoa(leadReq.ID) + "/leads.json" + "?" + nextPage.Encode() + fields.Encode())
	if err != nil {
		return nil, err
	}
	err = json.Unmarshal(body, &leads)
	leads.client = c
	return leads, err
}

// Lead Get lead by Id - aka member by ID
func (c *Client) Lead(leadReq *LeadRequest) (lead *LeadResponse, err error) {
	fields := url.Values{}
	if len(leadReq.Fields) > 0 {
		fields.Set("fields", strings.Join(strings.Fields(leadReq.Fields), ""))
	}
	logrus.Debug("Fields: ", fields.Encode())
	body, err := c.Get("/lead/" + strconv.Itoa(leadReq.ID) + ".json" + "?" + fields.Encode())
	if err != nil {
		return
	}
	err = json.Unmarshal(body, &lead)
	lead.client = c
	return lead, err
}

// UpdateLeads post update of data for a lead
func (c *Client) UpdateLeads(update *LeadUpdate) ([]byte, error) {
	data, err := json.Marshal(update)
	if err != nil {
		return nil, err
	}
	body, err := c.Post("/leads.json", data)
	return body, err
}

// LeadFields return all fields and the data type of a lead object
func (c *Client) LeadFields() (fields *LeadFieldResponse, err error) {
	body, err := c.Get("/leads/describe.json")
	err = json.Unmarshal(body, &fields)
	return fields, err
}

// DeletedLeads returns a list of leads that were deleted
func (c *Client) DeletedLeads(leadReq *LeadRequest) (deletedLeads *DeletedLeadResponse, err error) {
	nextPage := url.Values{}
	if leadReq.Next != "" {
		nextPage.Set("&nextPageToken", leadReq.Next)
	}
	body, err := c.Get("/activities/deletedleads.json?" + nextPage.Encode())
	err = json.Unmarshal(body, &deletedLeads)
	return deletedLeads, err
}
<commit_msg>better debug of url<commit_after>package goketo

import (
	"encoding/json"
	"fmt"
	"net/url"
	"strconv"
	"strings"

	"github.com/Sirupsen/logrus"
)

// Convert all to Interfaces for re-usability
// Add fmt.Sprintf("%v:%v", host, port) to build strings

// LeadResponse response from list request
type LeadResponse struct {
	client    *Client
	RequestID string          `json:"requestId"`
	Result    json.RawMessage `json:"result"`
	Success   bool            `json:"success"`
	Next      string          `json:"nextPageToken,omitempty"`
	More      bool            `json:"moreResult,omitempty"`
	Errors    []struct {
		Code    string `json:"code"`
		Message string `json:"message"`
	} `json:"errors,omitempty"`
}

// LeadResult default result struct as part of the lead - can be customized to allow greater fields
type LeadResult struct {
	ID        int    `json:"id"`
	FirstName string `json:"firstName"`
	LastName  string `json:"lastName"`
	Email     string `json:"email"`
	Created   string `json:"createdAt"`
	Updated   string `json:"updatedAt"`
}

// LeadRequest builds a request for data retrieval
type LeadRequest struct {
	ID     int    // List ID
	Next   string // Next page Token
	Fields string
}

// LeadUpdate builds the data for an update
type LeadUpdate struct {
	Action string          `json:"action"` // createOnly - updateOnly - createOrUpdate(default request) - createDuplicate
	Lookup string          `json:"lookupField"`
	Input  json.RawMessage `json:"input"`
}

// LeadUpdateResponse data format for update response
type LeadUpdateResponse struct {
	ID      string             `json:"requestId"`
	Success bool               `json:"success"`
	Result  []LeadUpdateResult `json:"result,omitempty"`
	Error   []LeadError        `json:"errors,omitempty"`
}

// LeadUpdateResult holds result for all updates
type LeadUpdateResult struct {
	ID     int    `json:"id"`
	Status string `json:"status"`
}

// LeadError shows the error code and message for response
type LeadError struct {
	Code    string `json:"code"`
	Message string `json:"message"`
}

// LeadFieldResponse response for all fields
type LeadFieldResponse struct {
	client    *Client
	RequestID string      `json:"requestId"`
	Result    []LeadField `json:"result"`
	Success   bool        `json:"success"`
}

// LeadField describes all possible fields for Leads
type LeadField struct {
	ID     int    `json:"id"`
	Name   string `json:"displayName"`
	Type   string `json:"dataType"`
	Length int    `json:"length"`
	Rest   struct {
		Name     string `json:"name"`
		ReadOnly bool   `json:"readOnly"`
	} `json:"rest"`
	Soap struct {
		Name     string `json:"name"`
		ReadOnly bool   `json:"readOnly"`
	} `json:"soap"`
}

// DeletedLeadResponse response of Deleted lead request
type DeletedLeadResponse struct {
	*LeadResponse
	Result []DeletedLead `json:"result"`
}

// DeletedLead result
type DeletedLead struct {
	ID         int      `json:"id"`
	LeadID     int      `json:"leadId"`
	Date       string   `json:"activityDate"`
	TypeID     int      `json:"activityTypeId"`
	PrimaryID  int      `json:"primaryAttributeValueId"`
	PrimaryVal string   `json:"primaryAttributeValue"`
	Attributes []string `json:"attributes"`
}

// Leads Get leads by list Id
func (c *Client) Leads(leadReq *LeadRequest) (leads *LeadResponse, err error) {
	nextPage := url.Values{}
	if leadReq.Next != "" {
		nextPage.Set("nextPageToken", leadReq.Next)
	}
	fields := url.Values{}
	if len(leadReq.Fields) > 0 {
		fields.Set("fields", strings.Join(strings.Fields(leadReq.Fields), ""))
	}
	url := fmt.Sprintf("/list/%s/leads.json?%s&%s", strconv.Itoa(leadReq.ID), nextPage.Encode(), fields.Encode())
	logrus.Debug("Get: ", url)
	body, err := c.Get(url)
	if err != nil {
		return nil, err
	}
	logrus.Debug("Body: ", string(body))
	err = json.Unmarshal(body, &leads)
	leads.client = c
	return leads, err
}

// Lead Get lead by Id - aka member by ID
func (c *Client) Lead(leadReq *LeadRequest) (lead *LeadResponse, err error) {
	fields := url.Values{}
	if len(leadReq.Fields) > 0 {
		fields.Set("fields", strings.Join(strings.Fields(leadReq.Fields), ""))
	}
	url := fmt.Sprintf("/lead/%s.json?%s", strconv.Itoa(leadReq.ID), fields.Encode())
	logrus.Debug("Get: ", url)
	body, err := c.Get(url)
	if err != nil {
		return
	}
	logrus.Debug("Body: ", string(body))
	err = json.Unmarshal(body, &lead)
	lead.client = c
	return lead, err
}

// UpdateLeads post update of data for a lead
func (c *Client) UpdateLeads(update *LeadUpdate) ([]byte, error) {
	data, err := json.Marshal(update)
	if err != nil {
		return nil, err
	}
	body, err := c.Post("/leads.json", data)
	return body, err
}

// LeadFields return all fields and the data type of a lead object
func (c *Client) LeadFields() (fields *LeadFieldResponse, err error) {
	body, err := c.Get("/leads/describe.json")
	err = json.Unmarshal(body, &fields)
	return fields, err
}

// DeletedLeads returns a list of leads that were deleted
func (c *Client) DeletedLeads(leadReq *LeadRequest) (deletedLeads *DeletedLeadResponse, err error) {
	nextPage := url.Values{}
	if leadReq.Next != "" {
		nextPage.Set("&nextPageToken", leadReq.Next)
	}
	body, err := c.Get("/activities/deletedleads.json?" + nextPage.Encode())
	err = json.Unmarshal(body, &deletedLeads)
	return deletedLeads, err
}
<|endoftext|>


<commit_before>package main

import (
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
)

type lead struct {
	Id                 bson.NewObjectId `bson:"_id"`
	Contact            *mgo.DBRef       `bson:"contact,omitempty"`
	Source             string           `bson:"source,omitempty"`
	Owner              string           `bson:"owner,omitempty"`
	Status             string           `bson:"status,omitempty"`
	TeamSize           float            `bson:"teamsize,omitempty"`
	RatePerHour        float            `bson:"rateperhour,omitempty"`
	DurationInMonths   float            `bson:"durationinmonths,omitempty"`
	EstimatedStartDate string           `bson:"estimatedstartdate,omitempty"`
	//Here we choose not to use time.Time because omitempty isn't supported for time.Time
	Comments []string `bson:"comments,omitempty"`
}

// NewLead takes the fields of a lead, initializes a struct of lead type and returns
// the pointer to that struct.
// Also, It inserts the lead data into a mongoDB collection, which is passed as the first parameter.
func NewLead(c *mgo.Collection, r *mgo.DBRef, source, owner, status,
	teamsize, rate, duration, start string, comments []string) (*lead, error) {

	doc := lead{
		Id:                 bson.NewObjectId(),
		Contact:            r,
		Source:             source,
		Owner:              owner,
		Status:             status,
		TeamSize:           teamsize,
		RatePerHour:        rate,
		DurationInMonths:   duration,
		EstimatedStartDate: start,
		Comments:           comments,
	}
	err := c.Insert(doc)
	if err != nil {
		return &lead{}, err
	}
	return &doc, nil
}
<commit_msg>Correct ObjectId type<commit_after>package main

import (
	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
)

type lead struct {
	Id                 bson.ObjectId `bson:"_id"`
	Contact            *mgo.DBRef    `bson:"contact,omitempty"`
	Source             string        `bson:"source,omitempty"`
	Owner              string        `bson:"owner,omitempty"`
	Status             string        `bson:"status,omitempty"`
	TeamSize           float         `bson:"teamsize,omitempty"`
	RatePerHour        float         `bson:"rateperhour,omitempty"`
	DurationInMonths   float         `bson:"durationinmonths,omitempty"`
	EstimatedStartDate string        `bson:"estimatedstartdate,omitempty"`
	//Here we choose not to use time.Time because omitempty isn't supported for time.Time
	Comments []string `bson:"comments,omitempty"`
}

// NewLead takes the fields of a lead, initializes a struct of lead type and returns
// the pointer to that struct.
// Also, It inserts the lead data into a mongoDB collection, which is passed as the first parameter.
func NewLead(c *mgo.Collection, r *mgo.DBRef, source, owner, status,
	teamsize, rate, duration, start string, comments []string) (*lead, error) {

	doc := lead{
		Id:                 bson.NewObjectId(),
		Contact:            r,
		Source:             source,
		Owner:              owner,
		Status:             status,
		TeamSize:           teamsize,
		RatePerHour:        rate,
		DurationInMonths:   duration,
		EstimatedStartDate: start,
		Comments:           comments,
	}
	err := c.Insert(doc)
	if err != nil {
		return &lead{}, err
	}
	return &doc, nil
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/md5"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strings"
	"unicode/utf8"
)

// locates this directory's parent `.git` directory and returns it, or an error
// if no parent `.git` directory could be found.
func gitPath() (string, error) {
	// start at the current directory
	cur, err := os.Getwd()
	if err != nil {
		return "", err
	}

	// clean up the path and ensure it's absolute so we can traverse all the way
	// to the root directory if necessary.
	cur, err = filepath.Abs(filepath.Clean(cur))
	if err != nil {
		return "", err
	}

	// walk our way up the directory tree, attempting to find a `.git` directory
	const gitDirectoryName = ".git"
	for cur != "/" {
		// list all this directory's children
		children, err := ioutil.ReadDir(cur)
		if err != nil {
			return "", err
		}

		// look for a `.git` directory in the children
		for _, info := range children {
			name := info.Name()

			// if we find a directory with the appropriate name, return its path
			if name == gitDirectoryName && info.IsDir() {
				return path.Join(cur, name), nil
			}
		}

		// if we failed, move up to the parent path
		cur = filepath.Dir(cur)
	}

	// if we've reached the root and haven't found a `.git` directory, return an
	// error.
	return "", fmt.Errorf("No Git directory found.")
}

// finds the current branch of the current Git repository
func gitCurrentBranch() string {
	gitPath, err := gitPath()
	if err != nil {
		return ""
	}

	// this file contains a pointer to the current branch which we can parse to
	// determine the branch name.
	headPath := path.Join(gitPath, "HEAD")

	// read the HEAD file
	data, err := ioutil.ReadFile(headPath)
	if err != nil {
		return ""
	}

	refSpec := strings.TrimSpace(string(data))

	// parse the HEAD file to get the branch name. the HEAD file contents look
	// something like: `ref: refs/heads/master`. we split into three parts, then
	// use whatever's left over as the branch name. If it doesn't split, it's
	// probably a commit hash, in which case we use the first 8 characters of it
	// as the branch name.
	refSpecParts := strings.SplitN(refSpec, "/", 3)
	branchName := ""
	if len(refSpecParts) == 3 {
		// use the last part as the branch name
		branchName = strings.TrimSpace(refSpecParts[2])
	} else if len(refSpecParts) == 1 && len(refSpec) == 40 {
		// we got a commit hash, use the first 7 characters as the branch name
		branchName = refSpec[0:7]
	} else {
		// notify that we failed
		branchName = "BAD_REF_SPEC (" + refSpec + ")"
	}

	// return the third part of our split ref spec, the branch name
	return branchName
}

// gets the current status symbols for the existing git repository as a map of
// file name to status symbol, or nil if there's no repository.
func gitCurrentStatus() map[string]string {
	out, err := exec.Command("git", "status", "--porcelain").CombinedOutput()
	if err != nil {
		return nil
	}

	// turn the output into a map of file to status string
	files := make(map[string]string)
	for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
		// trim whitespace so we can reliably split out the status/name
		line = strings.TrimSpace(line)

		// split into a (status, file) pair
		parts := strings.SplitN(line, " ", 2)
		if len(parts) == 2 {
			files[parts[1]] = parts[0]
		}
	}

	return files
}

func compressWithTruncator(s string, truncator rune, maxLen int) string {
	lenS := utf8.RuneCountInString(s)

	// if we're already short enough, bail
	if lenS <= maxLen {
		return s
	}

	// otherwise, calculate the reduction we need to fit into the max length
	reductionAmount := lenS - maxLen

	// remove the middle characters and replace them with our truncator
	middle := float64(lenS) / 2
	startIExact := middle - (float64(reductionAmount) / 2.0)
	endIExact := startIExact + float64(reductionAmount)
	startI := int(startIExact)
	endI := int(endIExact)

	// protect against overruns
	if startI < 0 {
		startI = 0
	}

	if endI >= lenS {
		endI = lenS
	}

	// construct a new string out of our old string's runes, replacing the
	// truncated ones with our truncator rune.
	truncatedS := make([]rune, 0, lenS-reductionAmount)
	truncated := false
	for i, ch := range s {
		if i < startI {
			truncatedS = append(truncatedS, ch)
		} else if !truncated {
			// add the truncator character if we haven't done so already
			truncatedS = append(truncatedS, truncator)
			truncated = true
		} else if i > endI {
			truncatedS = append(truncatedS, ch)
		}
	}

	return string(truncatedS)
}

// shortens and prettifies the given path, keeping it at or under the target
// length in runes.
func prettifyPath(p string, targetLength int) (string, error) {
	// clean up the path first
	p, err := filepath.Abs(filepath.Clean(p))
	if err != nil {
		return "", err
	}

	// if this path is in the current HOME directory, replace that dir with `~`
	homePath := os.Getenv("HOME")
	const homeTruncator = "~"
	if homePath != "" && strings.HasPrefix(p, homePath) {
		// mark that we're in the home directory for later
		p = homeTruncator + p[len(homePath):]
	}

	// save an original copy in case we can't do smart truncation well enough
	origP := p

	// determine how much we need to shorten our path to get it under the target,
	// i.e. how many characters of space we need to regain.
	neededGain := utf8.RuneCountInString(p) - targetLength

	// ALGORITHM:
	// truncate parent directories
	// * skips any leading home directory marker
	// * skips the base directory
	// * minimally truncates paths in order from longest to shortest

	const pathSeparator = string(os.PathSeparator)
	const segmentTruncator = '…'
	segments := strings.Split(p, pathSeparator)

	// inclusive/exclusive start/end indexes for the segments we'll try to
	// truncate in this pass.
	segmentsStartI := 0
	segmentsEndI := len(segments) - 1

	// truncate path segments by the minimum possible amount to try to reduce the
	// size of the overall path string.
	for i := segmentsStartI; i < segmentsEndI && neededGain > 0; i++ {
		// find the index of the longest remaining segment. linear search should be
		// fast enough for us since we'll probably never have more than 20 paths (on
		// a typical system at least, no?).
		longestI := segmentsStartI
		for j := segmentsStartI; j < segmentsEndI; j++ {
			// mark this as the longest segment if that's the case
			if len(segments[j]) > len(segments[longestI]) {
				longestI = j
			}
		}

		// operate on the longest segment
		segment := segments[longestI]
		lenSegment := utf8.RuneCountInString(segment)

		// calculate how much we can possibly gain from this segment, omitting the
		// start/end runes and one for the segment truncator.
		maxGain := lenSegment - 3

		// if we can reduce this segment...
		if maxGain > 0 {
			// reduce the segment by the smaller of the needed gain and the most we
			// can gain from this segment.
			reductionAmount := neededGain
			if reductionAmount > maxGain {
				reductionAmount = maxGain
			}

			// replace this segment with its truncated version
			segments[longestI] = compressWithTruncator(
				segment,
				segmentTruncator,
				lenSegment-reductionAmount,
			)

			// reduce the needed gain by the amount we just reduced our segment by
			neededGain -= reductionAmount
		}
	}

	// ALGORITHM:
	// * compress paths of length 3 to the first character and a truncator
	for i := segmentsStartI; i < segmentsEndI && neededGain > 0; i++ {
		segment := segments[i]
		lenSegment := utf8.RuneCountInString(segment)

		// if this segment is small enough, truncate to the first character and a
		// single truncator, saving a single character overall.
		if lenSegment == 3 {
			truncatedSegment := make([]rune, 0, 2)

			// append the first character, followed by a single truncator, then end.
			// this is a ghetto hack to easily pull out the first rune.
			for _, ch := range segment {
				truncatedSegment = append(truncatedSegment, ch, segmentTruncator)
				break
			}

			segments[i] = string(truncatedSegment)

			// reduce the needed gain by the amount we just reduced our segment by
			neededGain -= 1
		}
	}

	// ALGORITHM:
	// * compress already-compressed paths to a single character
	for i := segmentsStartI; i < segmentsEndI && neededGain > 0; i++ {
		segment := segments[i]
		lenSegment := utf8.RuneCountInString(segment)

		// if this segment is small enough and has already been truncated, truncate
		// to the first character alone.
		if lenSegment == 2 {
			lastRune, size := utf8.DecodeLastRuneInString(segment)
			if size > 0 && lastRune == segmentTruncator {
				for _, ch := range segment {
					segments[i] = string(ch)
					break
				}

				// reduce the needed gain by the single character
				neededGain -= 1
			}
		}
	}

	// ALGORITHM:
	// * if we're still out of space, just truncate the original path with a
	//   single truncator character.
	if neededGain > 0 {
		// compress the path by just truncating the original since we've lost so
		// much fidelity at this point it looks nicer this way. otherwise, the
		// result can become littered with random truncators.
		p = compressWithTruncator(origP, segmentTruncator, targetLength)
	} else {
		// put the path back together now that we're done modifying it by segment
		p = path.Join(segments...)
	}

	return p, nil
}

// returns the user/hostname of the system with a specifically-colored `@`
func userAndHost() string {
	// never mind the error, just use whatever came back
	host, _ := os.Hostname()
	user := os.Getenv("USER")

	// turn the user/host combination into a color, then use that color as the
	// foreground color of the `@` symbol, to help distinguish between terminals
	// running on different hosts.
	md5Hash := md5.New()
	io.WriteString(md5Hash, user)
	io.WriteString(md5Hash, host)
	sum := md5Hash.Sum(nil)

	// use the first three bytes as an RGB color, then convert to HSL so we can
	// easily keep the color in a nice range. then convert back to RGB, then back
	// to hex so we can display it!
	r := int(sum[0])
	g := int(sum[1])
	b := int(sum[2])

	h, s, l := rgbToHSL(r, g, b)

	// scale our lightness to keep it readable against a dark background
	minLightness := 0.3
	maxLightness := 0.85
	l = (l * (maxLightness - minLightness)) + minLightness

	r, g, b = hslToRGB(h, s, l)
	hex := rgbToHex(r, g, b)

	return user + trueColored("@", hex) + host
}

// print the status line!
func main() {
	cwd, _ := os.Getwd()
	prettyPath, _ := prettifyPath(cwd, 20)
	branch := gitCurrentBranch()

	// pick a color for the branch depending on status output
	branchColor := COLOR_GREEN
	statuses := gitCurrentStatus()
	if statuses != nil && len(statuses) > 0 {
		hasUntracked := false
		hasModified := false

		for _, status := range statuses {
			// true if we have untracked or added files
			hasUntracked = hasUntracked || strings.ContainsAny(status, "A?")

			// true if we have modified, renamed, deleted, or unstaged files
			hasModified = hasModified || strings.ContainsAny(status, "MRDU")
		}

		if hasUntracked && !hasModified {
			branchColor = COLOR_YELLOW
		} else if hasModified {
			branchColor = COLOR_RED
		}
	}

	fmt.Printf("%s %s %s ➙ \n",
		userAndHost(),
		colored(prettyPath, COLOR_BLUE),
		colored(branch, branchColor))
}
<commit_msg>Experiment with multi-line prompt<commit_after>package main

import (
	"crypto/md5"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strings"
	"time"
	"unicode/utf8"
)

// locates this directory's parent `.git` directory and returns it, or an error
// if no parent `.git` directory could be found.
func gitPath() (string, error) {
	// start at the current directory
	cur, err := os.Getwd()
	if err != nil {
		return "", err
	}

	// clean up the path and ensure it's absolute so we can traverse all the way
	// to the root directory if necessary.
	cur, err = filepath.Abs(filepath.Clean(cur))
	if err != nil {
		return "", err
	}

	// walk our way up the directory tree, attempting to find a `.git` directory
	const gitDirectoryName = ".git"
	for cur != "/" {
		// list all this directory's children
		children, err := ioutil.ReadDir(cur)
		if err != nil {
			return "", err
		}

		// look for a `.git` directory in the children
		for _, info := range children {
			name := info.Name()

			// if we find a directory with the appropriate name, return its path
			if name == gitDirectoryName && info.IsDir() {
				return path.Join(cur, name), nil
			}
		}

		// if we failed, move up to the parent path
		cur = filepath.Dir(cur)
	}

	// if we've reached the root and haven't found a `.git` directory, return an
	// error.
	return "", fmt.Errorf("No Git directory found.")
}

// finds the current branch of the current Git repository
func gitCurrentBranch() string {
	gitPath, err := gitPath()
	if err != nil {
		return ""
	}

	// this file contains a pointer to the current branch which we can parse to
	// determine the branch name.
	headPath := path.Join(gitPath, "HEAD")

	// read the HEAD file
	data, err := ioutil.ReadFile(headPath)
	if err != nil {
		return ""
	}

	refSpec := strings.TrimSpace(string(data))

	// parse the HEAD file to get the branch name. the HEAD file contents look
	// something like: `ref: refs/heads/master`. we split into three parts, then
	// use whatever's left over as the branch name. If it doesn't split, it's
	// probably a commit hash, in which case we use the first 8 characters of it
	// as the branch name.
	refSpecParts := strings.SplitN(refSpec, "/", 3)
	branchName := ""
	if len(refSpecParts) == 3 {
		// use the last part as the branch name
		branchName = strings.TrimSpace(refSpecParts[2])
	} else if len(refSpecParts) == 1 && len(refSpec) == 40 {
		// we got a commit hash, use the first 7 characters as the branch name
		branchName = refSpec[0:7]
	} else {
		// notify that we failed
		branchName = "BAD_REF_SPEC (" + refSpec + ")"
	}

	// return the third part of our split ref spec, the branch name
	return branchName
}

// gets the current status symbols for the existing git repository as a map of
// file name to status symbol, or nil if there's no repository.
func gitCurrentStatus() map[string]string {
	out, err := exec.Command("git", "status", "--porcelain").CombinedOutput()
	if err != nil {
		return nil
	}

	// turn the output into a map of file to status string
	files := make(map[string]string)
	for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
		// trim whitespace so we can reliably split out the status/name
		line = strings.TrimSpace(line)

		// split into a (status, file) pair
		parts := strings.SplitN(line, " ", 2)
		if len(parts) == 2 {
			files[parts[1]] = parts[0]
		}
	}

	return files
}

func compressWithTruncator(s string, truncator rune, maxLen int) string {
	lenS := utf8.RuneCountInString(s)

	// if we're already short enough, bail
	if lenS <= maxLen {
		return s
	}

	// otherwise, calculate the reduction we need to fit into the max length
	reductionAmount := lenS - maxLen

	// remove the middle characters and replace them with our truncator
	middle := float64(lenS) / 2
	startIExact := middle - (float64(reductionAmount) / 2.0)
	endIExact := startIExact + float64(reductionAmount)
	startI := int(startIExact)
	endI := int(endIExact)

	// protect against overruns
	if startI < 0 {
		startI = 0
	}

	if endI >= lenS {
		endI = lenS
	}

	// construct a new string out of our old string's runes, replacing the
	// truncated ones with our truncator rune.
	truncatedS := make([]rune, 0, lenS-reductionAmount)
	truncated := false
	for i, ch := range s {
		if i < startI {
			truncatedS = append(truncatedS, ch)
		} else if !truncated {
			// add the truncator character if we haven't done so already
			truncatedS = append(truncatedS, truncator)
			truncated = true
		} else if i > endI {
			truncatedS = append(truncatedS, ch)
		}
	}

	return string(truncatedS)
}

// shortens and prettifies the given path, keeping it at or under the target
// length in runes.
func prettifyPath(p string, targetLength int) (string, error) {
	// clean up the path first
	p, err := filepath.Abs(filepath.Clean(p))
	if err != nil {
		return "", err
	}

	// if this path is in the current HOME directory, replace that dir with `~`
	homePath := os.Getenv("HOME")
	const homeTruncator = "~"
	if homePath != "" && strings.HasPrefix(p, homePath) {
		// mark that we're in the home directory for later
		p = homeTruncator + p[len(homePath):]
	}

	// save an original copy in case we can't do smart truncation well enough
	origP := p

	// determine how much we need to shorten our path to get it under the target,
	// i.e. how many characters of space we need to regain.
	neededGain := utf8.RuneCountInString(p) - targetLength

	// ALGORITHM:
	// truncate parent directories
	// * skips any leading home directory marker
	// * skips the base directory
	// * minimally truncates paths in order from longest to shortest

	const pathSeparator = string(os.PathSeparator)
	const segmentTruncator = '…'
	segments := strings.Split(p, pathSeparator)

	// inclusive/exclusive start/end indexes for the segments we'll try to
	// truncate in this pass.
	segmentsStartI := 0
	segmentsEndI := len(segments) - 1

	// truncate path segments by the minimum possible amount to try to reduce the
	// size of the overall path string.
	for i := segmentsStartI; i < segmentsEndI && neededGain > 0; i++ {
		// find the index of the longest remaining segment. linear search should be
		// fast enough for us since we'll probably never have more than 20 paths (on
		// a typical system at least, no?).
		longestI := segmentsStartI
		for j := segmentsStartI; j < segmentsEndI; j++ {
			// mark this as the longest segment if that's the case
			if len(segments[j]) > len(segments[longestI]) {
				longestI = j
			}
		}

		// operate on the longest segment
		segment := segments[longestI]
		lenSegment := utf8.RuneCountInString(segment)

		// calculate how much we can possibly gain from this segment, omitting the
		// start/end runes and one for the segment truncator.
		maxGain := lenSegment - 3

		// if we can reduce this segment...
		if maxGain > 0 {
			// reduce the segment by the smaller of the needed gain and the most we
			// can gain from this segment.
			reductionAmount := neededGain
			if reductionAmount > maxGain {
				reductionAmount = maxGain
			}

			// replace this segment with its truncated version
			segments[longestI] = compressWithTruncator(
				segment,
				segmentTruncator,
				lenSegment-reductionAmount,
			)

			// reduce the needed gain by the amount we just reduced our segment by
			neededGain -= reductionAmount
		}
	}

	// ALGORITHM:
	// * compress paths of length 3 to the first character and a truncator
	for i := segmentsStartI; i < segmentsEndI && neededGain > 0; i++ {
		segment := segments[i]
		lenSegment := utf8.RuneCountInString(segment)

		// if this segment is small enough, truncate to the first character and a
		// single truncator, saving a single character overall.
		if lenSegment == 3 {
			truncatedSegment := make([]rune, 0, 2)

			// append the first character, followed by a single truncator, then end.
			// this is a ghetto hack to easily pull out the first rune.
			for _, ch := range segment {
				truncatedSegment = append(truncatedSegment, ch, segmentTruncator)
				break
			}

			segments[i] = string(truncatedSegment)

			// reduce the needed gain by the amount we just reduced our segment by
			neededGain -= 1
		}
	}

	// ALGORITHM:
	// * compress already-compressed paths to a single character
	for i := segmentsStartI; i < segmentsEndI && neededGain > 0; i++ {
		segment := segments[i]
		lenSegment := utf8.RuneCountInString(segment)

		// if this segment is small enough and has already been truncated, truncate
		// to the first character alone.
		if lenSegment == 2 {
			lastRune, size := utf8.DecodeLastRuneInString(segment)
			if size > 0 && lastRune == segmentTruncator {
				for _, ch := range segment {
					segments[i] = string(ch)
					break
				}

				// reduce the needed gain by the single character
				neededGain -= 1
			}
		}
	}

	// ALGORITHM:
	// * if we're still out of space, just truncate the original path with a
	//   single truncator character.
	if neededGain > 0 {
		// compress the path by just truncating the original since we've lost so
		// much fidelity at this point it looks nicer this way. otherwise, the
		// result can become littered with random truncators.
		p = compressWithTruncator(origP, segmentTruncator, targetLength)
	} else {
		// put the path back together now that we're done modifying it by segment
		p = path.Join(segments...)
	}

	return p, nil
}

// given a string, returns a hex color based on its contents
func colorHash(input string) int {
	// turn the user/host combination into a color, then use that color as the
	// foreground color of the `@` symbol, to help distinguish between terminals
	// running on different hosts.
	md5Hash := md5.New()
	io.WriteString(md5Hash, input)
	sum := md5Hash.Sum(nil)

	// use the first three bytes as an RGB color, then convert to HSL so we can
	// easily keep the color in a nice range. then convert back to RGB, then back
	// to hex so we can display it!
	r := int(sum[0])
	g := int(sum[1])
	b := int(sum[2])

	h, s, l := rgbToHSL(r, g, b)

	// scale our lightness to keep it readable against a dark background
	minLightness := 0.3
	maxLightness := 0.85
	l = (l * (maxLightness - minLightness)) + minLightness

	r, g, b = hslToRGB(h, s, l)
	return rgbToHex(r, g, b)
}

// returns the user/hostname of the system with a specifically-colored `@`
func userAndHost() string {
	// never mind the error, just use whatever came back
	host, _ := os.Hostname()
	user := os.Getenv("USER")

	c := colorHash(user + host)

	return trueColored("[", c) + user + trueColored("@", c) + host + trueColored("]", c)
}

func currentTime() string {
	return fmt.Sprintf("%d", time.Now().Unix())
}

// print the status line!
func main() {
	cwd, _ := os.Getwd()
	prettyPath, _ := prettifyPath(cwd, 60)
	branch := gitCurrentBranch()

	// pick a color for the branch depending on status output
	branchColor := COLOR_GREEN
	statuses := gitCurrentStatus()
	if statuses != nil && len(statuses) > 0 {
		hasUntracked := false
		hasModified := false

		for _, status := range statuses {
			// true if we have untracked or added files
			hasUntracked = hasUntracked || strings.ContainsAny(status, "A?")

			// true if we have modified, renamed, deleted, or unstaged files
			hasModified = hasModified || strings.ContainsAny(status, "MRDU")
		}

		if hasUntracked && !hasModified {
			branchColor = COLOR_YELLOW
		} else if hasModified {
			branchColor = COLOR_RED
		}
	}

	fmt.Printf("┌╼ %s %s %s %s\n└╼ \n",
		colored(currentTime(), COLOR_MAGENTA),
		userAndHost(),
		colored(prettyPath, COLOR_BLUE),
		colored(branch, branchColor))
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"strings"
	"text/tabwriter"

	"github.com/codegangsta/cli"
)

type kiteInfo struct {
	IP           string
	VMName       string
	Hostname     string
	MachineLabel string
	MountedPaths []string
	Teams        []string
}

// ListCommand returns list of remote machines belonging to user or that can be
// accessed by the user.
func ListCommand(c *cli.Context) int {
	k, err := CreateKlientClient(NewKlientOptions())
	if err != nil {
		fmt.Printf("Error connecting to %s: '%s'\n", KlientName, err)
		return 1
	}

	if err = k.Dial(); err != nil {
		fmt.Printf("Error connecting to %s: '%s'\n", KlientName, err)
		return 1
	}

	res, err := k.Tell("remote.list")
	if err != nil {
		fmt.Printf("Error fetching list of machines from %s: '%s'\n", KlientName, err)
		return 1
	}

	var infos []kiteInfo
	if err := res.Unmarshal(&infos); err != nil {
		fmt.Printf("Error fetching list of machines from %s: '%s'\n", KlientName, err)
	}

	w := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0)
	fmt.Fprintf(w, "\tMACHINE NAME\tTEAM\tLABEL\tMACHINE IP\tHOSTNAME\tMOUNTED PATHS\n")
	for i, info := range infos {
		// Join multiple teams into a single identifier
		team := strings.Join(info.Teams, ",")

		if team == "Koding" {
			team = "koding.com"
		}

		fmt.Fprintf(w, "  %d.\t%s\t%s\t%s\t%s\t%s\t%s\n",
			i+1, info.VMName, team, info.MachineLabel, info.IP, info.Hostname, strings.Join(info.MountedPaths, ", "))
	}
	w.Flush()

	return 0
}
<commit_msg>docstring: Added small note<commit_after>package main

import (
	"fmt"
	"os"
	"strings"
	"text/tabwriter"

	"github.com/codegangsta/cli"
)

type kiteInfo struct {
	IP           string
	VMName       string
	Hostname     string
	MachineLabel string
	MountedPaths []string
	Teams        []string
}

// ListCommand returns list of remote machines belonging to user or that can be
// accessed by the user.
func ListCommand(c *cli.Context) int {
	k, err := CreateKlientClient(NewKlientOptions())
	if err != nil {
		fmt.Printf("Error connecting to %s: '%s'\n", KlientName, err)
		return 1
	}

	if err = k.Dial(); err != nil {
		fmt.Printf("Error connecting to %s: '%s'\n", KlientName, err)
		return 1
	}

	res, err := k.Tell("remote.list")
	if err != nil {
		fmt.Printf("Error fetching list of machines from %s: '%s'\n", KlientName, err)
		return 1
	}

	var infos []kiteInfo
	if err := res.Unmarshal(&infos); err != nil {
		fmt.Printf("Error fetching list of machines from %s: '%s'\n", KlientName, err)
	}

	w := tabwriter.NewWriter(os.Stdout, 2, 0, 2, ' ', 0)
	fmt.Fprintf(w, "\tMACHINE NAME\tTEAM\tLABEL\tMACHINE IP\tHOSTNAME\tMOUNTED PATHS\n")
	for i, info := range infos {
		// Join multiple teams into a single identifier
		team := strings.Join(info.Teams, ",")

		// For a more clear UX, replace the team name of the default Koding team,
		// with Koding.com
		if team == "Koding" {
			team = "koding.com"
		}

		fmt.Fprintf(w, "  %d.\t%s\t%s\t%s\t%s\t%s\t%s\n",
			i+1, info.VMName, team, info.MachineLabel, info.IP, info.Hostname, strings.Join(info.MountedPaths, ", "))
	}
	w.Flush()

	return 0
}
<|endoftext|>


<commit_before>package couchdb

import (
	"reflect"
	"regexp"
	"testing"
)

var client, _ = NewClient("http://127.0.0.1:5984/")

func TestInfo(t *testing.T) {
	info, err := client.Info()
	if err != nil {
		t.Fatal(err)
	}
	if info.Couchdb != "Welcome" {
		t.Error("Couchdb error")
	}
}

func TestLog(t *testing.T) {
	log, err := client.Log()
	if err != nil {
		t.Fatal(err)
	}
	valid := regexp.MustCompile("[info]")
	if valid.MatchString(log) == false {
		t.Error("invalid log")
	}
}

func TestActiveTasks(t *testing.T) {
	res, err := client.ActiveTasks()
	if err != nil {
		t.Fatal(err)
	}
	out := make([]Task, 0)
	if reflect.DeepEqual(out, res) == false {
		t.Error("active tasks should be an empty array")
	}
}

func TestAll(t *testing.T) {
	res, err := client.All()
	if err != nil {
		t.Fatal(err)
	}
	if res[0] != "_replicator" || res[1] != "_users" {
		t.Error("slice error")
	}
}

func TestGet(t *testing.T) {
	info, err := client.Get("_users")
	if err != nil {
		t.Fatal(err)
	}
	if info.DbName != "_users" {
		t.Error("DbName error")
	}
	if info.CompactRunning != false {
		t.Error("CompactRunning error")
	}
}

func TestCreate(t *testing.T) {
	status, err := client.Create("dummy")
	if err != nil {
		t.Fatal(err)
	}
	if status.Ok != true {
		t.Error("status error")
	}
}

func TestCreateFail(t *testing.T) {
	_, err := client.Create("dummy")
	if err == nil {
		t.Fatal("should not create duplicate database")
	}
}

func TestCreateUser(t *testing.T) {
	user := NewUser("john", "password", []string{})
	res, err := client.CreateUser(user)
	if err != nil {
		t.Fatal(err)
	}
	if res.Ok == false || res.Id != "org.couchdb.user:john" {
		t.Error("create user error")
	}
}

// func TestCreateSession(t *testing.T) {
// 	res, err := client.CreateSession("john", "password")
// 	if err != nil {
// 		t.Fatal(err)
// 	}
// 	if res.Ok == false || res.Name != "john" {
// 		t.Error("create session error")
// 	}
// }
//
// func TestGetSession(t *testing.T) {
// 	session, err := client.GetSession()
// 	if err != nil {
// 		t.Fatal(err)
// 	}
// 	if session.Ok == false || session.UserContext.Name != "john" {
// 		t.Error("get session error")
// 	}
// }
//
// func TestDeleteSession(t *testing.T) {
// 	res, err := c.DeleteSession()
// 	if err != nil {
// 		t.Fatal(err)
// 	}
// 	if res.Ok == false {
// 		t.Error("delete session error")
// 	}
// }

func TestGetSessionAdmin(t *testing.T) {
	session, err := client.GetSession()
	if err != nil {
		t.Fatal(err)
	}
	if session.Ok == false {
		t.Error("session response is false")
	}
	roles := []string{"_admin"}
	if reflect.DeepEqual(roles, session.UserContext.Roles) == false {
		t.Error("session roles are wrong")
	}
}

func TestDelete(t *testing.T) {
	status, err := client.Delete("dummy")
	if err != nil {
		t.Fatal(err)
	}
	if status.Ok != true {
		t.Error("status error")
	}
}

func TestDeleteFail(t *testing.T) {
	_, err := client.Delete("dummy")
	if err == nil {
		t.Fatal("should not delete non existing database")
	}
}

func TestUse(t *testing.T) {
	db := client.Use("_users")
	if db.Url != "http://127.0.0.1:5984/_users/" {
		t.Error("use error")
	}
}
<commit_msg>add tests for get and delete session<commit_after>package couchdb

import (
	"reflect"
	"regexp"
	"testing"
)

var client, _ = NewClient("http://127.0.0.1:5984/")

func TestInfo(t *testing.T) {
	info, err := client.Info()
	if err != nil {
		t.Fatal(err)
	}
	if info.Couchdb != "Welcome" {
		t.Error("Couchdb error")
	}
}

func TestLog(t *testing.T) {
	log, err := client.Log()
	if err != nil {
		t.Fatal(err)
	}
	valid := regexp.MustCompile("[info]")
	if valid.MatchString(log) == false {
		t.Error("invalid log")
	}
}

func TestActiveTasks(t *testing.T) {
	res, err := client.ActiveTasks()
	if err != nil {
		t.Fatal(err)
	}
	out := make([]Task, 0)
	if reflect.DeepEqual(out, res) == false {
		t.Error("active tasks should be an empty array")
	}
}

func TestAll(t *testing.T) {
	res, err := client.All()
	if err != nil {
		t.Fatal(err)
	}
	if res[0] != "_replicator" || res[1] != "_users" {
		t.Error("slice error")
	}
}

func TestGet(t *testing.T) {
	info, err := client.Get("_users")
	if err != nil {
		t.Fatal(err)
	}
	if info.DbName != "_users" {
		t.Error("DbName error")
	}
	if info.CompactRunning != false {
		t.Error("CompactRunning error")
	}
}

func TestCreate(t *testing.T) {
	status, err := client.Create("dummy")
	if err != nil {
		t.Fatal(err)
	}
	if status.Ok != true {
		t.Error("status error")
	}
}

func TestCreateFail(t *testing.T) {
	_, err := client.Create("dummy")
	if err == nil {
		t.Fatal("should not create duplicate database")
	}
}

func TestCreateUser(t *testing.T) {
	user := NewUser("john", "password", []string{})
	res, err := client.CreateUser(user)
	if err != nil {
		t.Fatal(err)
	}
	if res.Ok == false || res.Id != "org.couchdb.user:john" {
		t.Error("create user error")
	}
}

func TestCreateSession(t *testing.T) {
	res, err := client.CreateSession("john", "password")
	if err != nil {
		t.Fatal(err)
	}
	if res.Ok == false || res.Name != "john" {
		t.Error("create session error")
	}
}

func TestGetSession(t *testing.T) {
	session, err := client.GetSession()
	if err != nil {
		t.Fatal(err)
	}
	if session.Ok == false || session.UserContext.Name != "john" {
		t.Error("get session error")
	}
}

func TestDeleteSession(t *testing.T) {
	res, err := client.DeleteSession()
	if err != nil {
		t.Fatal(err)
	}
	if res.Ok == false {
		t.Error("delete session error")
	}
}

func TestGetSessionAdmin(t *testing.T) {
	session, err := client.GetSession()
	if err != nil {
		t.Fatal(err)
	}
	if session.Ok == false {
		t.Error("session response is false")
	}
	roles := []string{"_admin"}
	if reflect.DeepEqual(roles, session.UserContext.Roles) == false {
		t.Error("session roles are wrong")
	}
}

func TestDelete(t *testing.T) {
	status, err := client.Delete("dummy")
	if err != nil {
		t.Fatal(err)
	}
	if status.Ok != true {
		t.Error("status error")
	}
}

func TestDeleteFail(t *testing.T) {
	_, err := client.Delete("dummy")
	if err == nil {
		t.Fatal("should not delete non existing database")
	}
}

func TestUse(t *testing.T) {
	db := client.Use("_users")
	if db.Url != "http://127.0.0.1:5984/_users/" {
		t.Error("use error")
	}
}
<|endoftext|>


<commit_before>package paranoidhttp

import (
	"net"
	"testing"
)

func TestRequest(t *testing.T) {
	resp, err := DefaultClient.Get("http://www.example.org")
	if err != nil && resp.StatusCode == 200 {
		t.Error("The request with an ordinal url should be successful")
	}

	resp, err = DefaultClient.Get("http://localhost")
	if err == nil {
		t.Errorf("The request for localhost should be fail")
	}

	if _, err := DefaultClient.Get("http://192.168.0.1"); err == nil {
		t.Errorf("The request for localhost should be fail")
	}

	if _, err := DefaultClient.Get("http://[::]"); err == nil {
		t.Errorf("The request for IPv6 unspecified address should be fail")
	}
}

func TestIsHostForbidden(t *testing.T) {
	badHosts := []string{
		"localhost",
		"host has space",
	}

	for _, h := range badHosts {
		if !basicConfig().isHostForbidden(h) {
			t.Errorf("%s should be forbidden", h)
		}
	}

	notBadHosts := []string{
		"www.hatena.ne.jp",
		"www.google.com",
		"xn--t8jx73hngb.jp",
	}

	for _, h := range notBadHosts {
		if basicConfig().isHostForbidden(h) {
			t.Errorf("%s should not be forbidden", h)
		}
	}
}

func TestIsIpForbidden(t *testing.T) {
	badIPs := []string{
		"0.0.0.0",                      // Unspecified
		"127.0.0.0", "127.255.255.255", // Loopback
		"10.0.0.0", "10.255.255.255", // Private A
		"172.16.0.0", "172.31.255.255", // Private B
		"192.168.0.0", "192.168.255.255", // Private C
		"192.0.2.0", "192.0.2.255", // Test-Net
		"192.88.99.0", "192.88.99.255", // 6to4 relay
		"224.0.0.0", "239.255.255.255", // Multicast
		"169.254.0.0", "169.254.255.255", // Link local
	}

	for _, ip := range badIPs {
		if !basicConfig().isIPForbidden(net.ParseIP(ip)) {
			t.Errorf("%s should be forbidden", ip)
		}
	}

	notBadIPs := []string{
		"0.0.0.1", "8.8.8.8",
		"126.255.255.255", "128.0.0.0",
		"9.255.255.255", "11.0.0.0",
		"172.15.255.255", "172.32.0.0",
		"192.167.255.255", "192.169.0.0",
		"192.88.98.255", "192.88.100.0",
		"223.255.255.255", "240.0.0.0",
		"169.253.255.255", "169.255.0.0",
	}

	for _, ip := range notBadIPs {
		if basicConfig().isIPForbidden(net.ParseIP(ip)) {
			t.Errorf("%s should not be forbidden", ip)
		}
	}

	c := basicConfig()
	ip := "172.18.0.1"
	if !c.isIPForbidden(net.ParseIP(ip)) {
		t.Errorf("%s should be forbidden", ip)
	}

	c.PermittedIPNets = append(c.PermittedIPNets, mustParseCIDR("172.18.0.1/32"))
	if c.isIPForbidden(net.ParseIP(ip)) {
		t.Errorf("%s should not be forbidden", ip)
	}
}
<commit_msg>Add IPv6 test cases<commit_after>package paranoidhttp

import (
	"net"
	"testing"
)

func TestRequest(t *testing.T) {
	resp, err := DefaultClient.Get("http://www.example.org")
	if err != nil && resp.StatusCode == 200 {
		t.Error("The request with an ordinal url should be successful")
	}

	resp, err = DefaultClient.Get("http://localhost")
	if err == nil {
		t.Errorf("The request for localhost should be fail")
	}

	if _, err := DefaultClient.Get("http://192.168.0.1"); err == nil {
		t.Errorf("The request for localhost should be fail")
	}

	if _, err := DefaultClient.Get("http://[::]"); err == nil {
		t.Errorf("The request for IPv6 unspecified address should be fail")
	}
	if _, err := DefaultClient.Get("http://[fd00::1234]"); err == nil {
		t.Errorf("The request for IPv6 ULA should fail")
	}
}

func TestIsHostForbidden(t *testing.T) {
	badHosts := []string{
		"localhost",
		"host has space",
	}

	for _, h := range badHosts {
		if !basicConfig().isHostForbidden(h) {
			t.Errorf("%s should be forbidden", h)
		}
	}

	notBadHosts := []string{
		"www.hatena.ne.jp",
		"www.google.com",
		"xn--t8jx73hngb.jp",
	}

	for _, h := range notBadHosts {
		if basicConfig().isHostForbidden(h) {
			t.Errorf("%s should not be forbidden", h)
		}
	}
}

func TestIsIpForbidden(t *testing.T) {
	badIPs := []string{
		"0.0.0.0", "::", // Unspecified
		"127.0.0.0", "127.255.255.255", "::1", // Loopback
		"10.0.0.0", "10.255.255.255", // Private A
		"172.16.0.0", "172.31.255.255", // Private B
		"192.168.0.0", "192.168.255.255", // Private C
		"fc00::", "fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", // Private v6
		"192.0.2.0", "192.0.2.255", // Test-Net
		"192.88.99.0", "192.88.99.255", // 6to4 relay
		"224.0.0.0", "239.255.255.255", // Multicast
		"ff00::", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", //Multicast 6
		"169.254.0.0", "169.254.255.255", // Link local
		"fe80::", "febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff", // Link Local v6
		"::ffff:0:255.255.255.255", "::ffff:255.255.255.255", //v4 to v6 mapping
		"2001:db8::", "2001:db8:ffff:ffff:ffff:ffff:ffff:ffff", //v6 documentation
	}

	for _, ip := range badIPs {
		if !basicConfig().isIPForbidden(net.ParseIP(ip)) {
			t.Errorf("%s should be forbidden", ip)
		}
	}

	notBadIPs := []string{
		"0.0.0.1", "8.8.8.8",
		"126.255.255.255", "128.0.0.0",
		"9.255.255.255", "11.0.0.0",
		"172.15.255.255", "172.32.0.0",
		"192.167.255.255", "192.169.0.0",
		"192.88.98.255", "192.88.100.0",
		"223.255.255.255", "240.0.0.0",
		"169.253.255.255", "169.255.0.0",
		"2000::1", "3fff::1",
		// real examples
		"2606:4700:4700::1111", "2001:4860:4860::8888",
	}

	for _, ip := range notBadIPs {
		if basicConfig().isIPForbidden(net.ParseIP(ip)) {
			t.Errorf("%s should not be forbidden", ip)
		}
	}

	c := basicConfig()
	ip := "172.18.0.1"
	if !c.isIPForbidden(net.ParseIP(ip)) {
		t.Errorf("%s should be forbidden", ip)
	}

	c.PermittedIPNets = append(c.PermittedIPNets, mustParseCIDR("172.18.0.1/32"))
	if c.isIPForbidden(net.ParseIP(ip)) {
		t.Errorf("%s should not be forbidden", ip)
	}
}
<|endoftext|>