<commit_before>package main

import (
	"bufio"
	"errors"
	"flag"
	"fmt"
	"log"
	"os"
	"path"
	"regexp"
	"strconv"
	"strings"

	"github.com/raff/govaluate"
)

var (
	VERSION     = "0.12.0"
	SPACES      = regexp.MustCompile("\\s+")
	INVALID_POS = errors.New("invalid position")

	OK              = 0
	MATCH_FOUND     = 100
	MATCH_NOT_FOUND = 101

	SET = struct{}{}

	gitCommit, buildDate string
)

type Pos struct {
	Start, End *int
	Value      *string
}

func (p Pos) String() (result string) {
	if p.Start != nil {
		result = strconv.Itoa(*p.Start)
	} else {
		result += "FIRST"
	}

	result += " TO "

	if p.End != nil {
		result += strconv.Itoa(*p.End)
	} else {
		result += "LAST"
	}

	return
}

func (p *Pos) Set(s string) error {
	p.Start = nil
	p.End = nil

	parts := strings.Split(s, ":")
	if len(parts) < 1 || len(parts) > 2 {
		return INVALID_POS
	}

	if len(parts[0]) > 0 {
		v, err := strconv.Atoi(parts[0])
		if err != nil {
			p.Value = &s
			return nil
		}

		p.Start = &v
	}

	if len(parts) == 1 {
		// not a slice
		// note: same pointer (to distinguish from *p.End == *p.Start that returns an empty slice)
		p.End = p.Start
	} else if len(parts[1]) > 0 {
		v, err := strconv.Atoi(parts[1])
		if err != nil {
			return err
		}

		p.End = &v
	}

	return nil
}

func Slice(source []string, p Pos) []string {
	if p.Value != nil {
		return []string{*p.Value}
	}

	var start, end int

	if p.Start == nil {
		start = 0
	} else if *p.Start >= len(source) {
		return source[0:0]
	} else if *p.Start < 0 {
		start = len(source) + *p.Start

		if start < 0 {
			start = 0
		}
	} else {
		start = *p.Start
	}

	if p.End == p.Start {
		// this should return source[start]
		end = start + 1
	} else if p.End == nil || *p.End >= len(source) {
		return source[start:]
	} else if *p.End < 0 {
		end = len(source) + *p.End
	} else {
		end = *p.End
	}

	if end < start {
		end = start
	}

	return source[start:end]
}

func Quote(a []string) []string {
	q := make([]string, len(a))
	for i, s := range a {
		q[i] = fmt.Sprintf("%q", s)
	}

	return q
}

func Unquote(a []string) []string {
	q := make([]string, len(a))
	for i, s := range a {
		q[i] = strings.Trim(s, `"'`)
	}

	return q
}

func Print(format string, a []string) {
	printable := make([]interface{}, len(a))

	for i, v := range a {
		printable[i] = v
	}

	fmt.Printf(format, printable...)
}

func MustEvaluate(expr string) *govaluate.EvaluableExpression {
	ee, err := govaluate.NewEvaluableExpressionWithFunctions(expr, funcs)
	if err != nil {
		log.Fatalf("%q: %v", expr, err)
	}

	return ee
}

type Context struct {
	vars   map[string]interface{}
	fields []string
}

func (p *Context) Get(name string) (interface{}, error) {

	if strings.HasPrefix(name, "$") {
		n, err := strconv.Atoi(name[1:])
		if err != nil {
			return nil, err
		}

		if n < len(p.fields) {
			return p.fields[n], nil
		}

		return nil, fmt.Errorf("No field %q", name)
	}

	if name == "NF" {
		if p.fields == nil {
			return 0, nil
		}
		return len(p.fields) - 1, nil
	}

	if value, ok := p.vars[name]; ok {
		return value, nil
	}

	return nil, fmt.Errorf("No variable %q", name)
}

func (p *Context) Set(name string, value interface{}) error {

	if strings.HasPrefix(name, "$") {
		return fmt.Errorf("Cannot override field %q", name)
	}

	if name == "NF" {
		return fmt.Errorf("Cannot override NF")
	}

	p.vars[name] = value
	return nil
}

func toFloat(arg interface{}) (float64, error) {
	switch v := arg.(type) {
	case string:
		f, err := strconv.ParseFloat(v, 64)
		return f, err
	case bool:
		if v {
			return 1.0, nil
		} else {
			return 0.0, nil
		}
	default:
		return v.(float64), nil
	}
}

var funcs = map[string]govaluate.ExpressionFunction{
	"num": func(arguments ...interface{}) (interface{}, error) {
		if len(arguments) != 1 {
			return nil, fmt.Errorf("- one parameter expected, got %d", len(arguments))
		}

		return toFloat(arguments[0])
	},

	"int": func(arguments ...interface{}) (interface{}, error) {
		if len(arguments) != 1 {
			return nil, fmt.Errorf("- one parameter expected, got %d", len(arguments))
		}

		f, err := toFloat(arguments[0])
		return float64(int(f)), err
	},

	"len": func(arguments ...interface{}) (interface{}, error) {
		if len(arguments) != 1 {
			return nil, fmt.Errorf("- one parameter expected, got %d", len(arguments))
		}

		if s, ok := arguments[0].(string); ok {
			return float64(len(s)), nil
		}

		return nil, fmt.Errorf("- input should be a string")
	},
}

func main() {
	version := flag.Bool("version", false, "print version and exit")
	quote := flag.Bool("quote", false, "quote returned fields")
	unquote := flag.Bool("unquote", false, "quote returned fields")
	ifs := flag.String("ifs", " ", "input field separator")
	ire := flag.String("ifs-re", "", "input field separator (as regular expression)")
	ofs := flag.String("ofs", " ", "output field separator")
	re := flag.String("re", "", "regular expression for parsing input")
	grep := flag.String("grep", "", "output only lines that match the regular expression")
	format := flag.String("printf", "", "output is formatted according to specified format")
	matches := flag.String("matches", "", "return status code 100 if any line matches the specified pattern, 101 otherwise")
	after := flag.String("after", "", "process fields in line after specified tag")
	afterline := flag.String("after-line", "", "process lines after lines that matches")
	afterlinen := flag.Int("after-linen", 0, "process lines after n lines")
	printline := flag.Bool("line", false, "print line numbers")
	debug := flag.Bool("debug", false, "print debug info")
	exprbegin := flag.String("begin", "", "expression to be executed before processing lines")
	exprend := flag.String("end", "", "expression to be executed after processing lines")
	exprline := flag.String("expr", "", "expression to be executed for each line")
	exprtest := flag.String("test", "", "test expression (skip line if false)")
	uniq := flag.Bool("uniq", false, "print only unique lines")

	flag.Parse()

	if *version {
		extra := ""
		if gitCommit != "" {
			extra = fmt.Sprintf(" (%.4v %v)", gitCommit, buildDate)
		}

		fmt.Printf("%s version %s%v\n", path.Base(os.Args[0]), VERSION, extra)
		return
	}

	pos := make([]Pos, len(flag.Args()))

	for i, arg := range flag.Args() {
		pos[i].Set(arg)
	}

	if len(*format) > 0 && !strings.HasSuffix(*format, "\n") {
		*format += "\n"
	}

	var split_re, split_pattern, match_pattern, grep_pattern *regexp.Regexp
	var expr_begin, expr_end, expr_line, expr_test *govaluate.EvaluableExpression

	status_code := OK

	if len(*matches) > 0 {
		match_pattern = regexp.MustCompile(*matches)
		status_code = MATCH_NOT_FOUND
	}

	if len(*grep) > 0 {
		if !strings.ContainsAny(*grep, "()") {
			*grep = "(" + *grep + ")"
		}
		grep_pattern = regexp.MustCompile(*grep)
	}

	if len(*re) > 0 {
		split_pattern = regexp.MustCompile(*re)
	}

	if len(*ire) > 0 {
		split_re = regexp.MustCompile(*ire)
	}

	if len(*exprbegin) > 0 {
		expr_begin = MustEvaluate(*exprbegin)
	}
	if len(*exprline) > 0 {
		expr_line = MustEvaluate(*exprline)
	}
	if len(*exprend) > 0 {
		expr_end = MustEvaluate(*exprend)
	}
	if len(*exprtest) > 0 {
		expr_test = MustEvaluate(*exprtest)
	}

	scanner := bufio.NewScanner(os.Stdin)
	len_after := len(*after)
	len_afterline := len(*afterline)
	lineno := 0
	uniques := map[string]struct{}{}

	expr_context := Context{vars: map[string]interface{}{}}

	if expr_begin != nil {
		_, err := expr_begin.Eval(&expr_context)
		if err != nil {
			log.Println("error in begin", err)
		}
		// else, should we print the result ?
	}

	for scanner.Scan() {
		if scanner.Err() != nil {
			log.Fatal(scanner.Err())
		}

		line := scanner.Text()

		lineno += 1

		if *afterlinen >= lineno {
			continue
		}

		if len_afterline > 0 {
			if strings.Contains(line, *afterline) {
				len_afterline = 0
			}

			continue
		}

		if len_after > 0 {
			i := strings.Index(line, *after)
			if i < 0 {
				continue // no match
			}

			line = line[i+len_after:]
		}

		expr_context.fields = []string{line} // $0 is the full line

		if grep_pattern != nil {
			if matches := grep_pattern.FindStringSubmatch(line); matches != nil {
				expr_context.fields = matches
			} else {
				continue
			}
		} else if split_pattern != nil {
			if matches := split_pattern.FindStringSubmatch(line); matches != nil {
				expr_context.fields = matches
			}
		} else if split_re != nil {
			// split line according to input regular expression
			expr_context.fields = append(expr_context.fields, split_re.Split(line, -1)...)
		} else if *ifs == " " {
			// split line on spaces (compact multiple spaces)
			expr_context.fields = append(expr_context.fields, SPACES.Split(strings.TrimSpace(line), -1)...)
		} else {
			// split line according to input field separator
			expr_context.fields = append(expr_context.fields, strings.Split(line, *ifs)...)
		}

		if *debug {
			log.Printf("input fields: %q\n", expr_context.fields)
			if len(pos) > 0 {
				log.Printf("output fields: %q\n", pos)
			}
		}

		var result []string

		// do some processing
		if len(pos) > 0 {
			result = make([]string, 0)

			for _, p := range pos {
				result = append(result, Slice(expr_context.fields, p)...)
			}
		} else {
			result = expr_context.fields[1:]
		}

		if *unquote {
			result = Unquote(result)
		}

		if *quote {
			result = Quote(result)
		}

		if *printline {
			fmt.Printf("%d: ", lineno)
		}

		if expr_test != nil {
			res, err := expr_test.Eval(&expr_context)
			if err != nil {
				log.Println("error in expr", err)
			} else {
				switch test := res.(type) {
				case bool:
					if !test {
						continue
					}

				default:
					log.Println("boolean expected, got", test)
					continue
				}
			}
		}

		if *uniq {
			l := strings.Join(result, " ")
			if _, ok := uniques[l]; ok {
				continue
			}

			uniques[l] = SET
		}

		if len(*format) > 0 {
			Print(*format, result)
		} else {
			// join the result according to output field separator
			fmt.Println(strings.Join(result, *ofs))
		}

		if match_pattern != nil && match_pattern.MatchString(line) {
			status_code = MATCH_FOUND
		}

		if expr_line != nil {
			_, err := expr_line.Eval(&expr_context)
			if err != nil {
				log.Println("error in expr", err)
			}
			// else, should we print the result ?
		}
	}

	if expr_end != nil {
		res, err := expr_end.Eval(&expr_context)
		if err != nil {
			log.Println("error in end", err)
		} else {
			fmt.Println(res)
		}
	}

	os.Exit(status_code)
}
<commit_msg>Added --contains. Also added --before that is useful with --after<commit_after>package main

import (
	"bufio"
	"errors"
	"flag"
	"fmt"
	"log"
	"os"
	"path"
	"regexp"
	"strconv"
	"strings"

	"github.com/raff/govaluate"
)

var (
	VERSION     = "0.12.0"
	SPACES      = regexp.MustCompile("\\s+")
	INVALID_POS = errors.New("invalid position")

	OK              = 0
	MATCH_FOUND     = 100
	MATCH_NOT_FOUND = 101

	SET = struct{}{}

	gitCommit, buildDate string
)

type Pos struct {
	Start, End *int
	Value      *string
}

func (p Pos) String() (result string) {
	if p.Start != nil {
		result = strconv.Itoa(*p.Start)
	} else {
		result += "FIRST"
	}

	result += " TO "

	if p.End != nil {
		result += strconv.Itoa(*p.End)
	} else {
		result += "LAST"
	}

	return
}

func (p *Pos) Set(s string) error {
	p.Start = nil
	p.End = nil

	parts := strings.Split(s, ":")
	if len(parts) < 1 || len(parts) > 2 {
		return INVALID_POS
	}

	if len(parts[0]) > 0 {
		v, err := strconv.Atoi(parts[0])
		if err != nil {
			p.Value = &s
			return nil
		}

		p.Start = &v
	}

	if len(parts) == 1 {
		// not a slice
		// note: same pointer (to distinguish from *p.End == *p.Start that returns an empty slice)
		p.End = p.Start
	} else if len(parts[1]) > 0 {
		v, err := strconv.Atoi(parts[1])
		if err != nil {
			return err
		}

		p.End = &v
	}

	return nil
}

func Slice(source []string, p Pos) []string {
	if p.Value != nil {
		return []string{*p.Value}
	}

	var start, end int

	if p.Start == nil {
		start = 0
	} else if *p.Start >= len(source) {
		return source[0:0]
	} else if *p.Start < 0 {
		start = len(source) + *p.Start

		if start < 0 {
			start = 0
		}
	} else {
		start = *p.Start
	}

	if p.End == p.Start {
		// this should return source[start]
		end = start + 1
	} else if p.End == nil || *p.End >= len(source) {
		return source[start:]
	} else if *p.End < 0 {
		end = len(source) + *p.End
	} else {
		end = *p.End
	}

	if end < start {
		end = start
	}

	return source[start:end]
}

func Quote(a []string) []string {
	q := make([]string, len(a))
	for i, s := range a {
		q[i] = fmt.Sprintf("%q", s)
	}

	return q
}

func Unquote(a []string) []string {
	q := make([]string, len(a))
	for i, s := range a {
		q[i] = strings.Trim(s, `"'`)
	}

	return q
}

func Print(format string, a []string) {
	printable := make([]interface{}, len(a))

	for i, v := range a {
		printable[i] = v
	}

	fmt.Printf(format, printable...)
}

func MustEvaluate(expr string) *govaluate.EvaluableExpression {
	ee, err := govaluate.NewEvaluableExpressionWithFunctions(expr, funcs)
	if err != nil {
		log.Fatalf("%q: %v", expr, err)
	}

	return ee
}

type Context struct {
	vars   map[string]interface{}
	fields []string
}

func (p *Context) Get(name string) (interface{}, error) {

	if strings.HasPrefix(name, "$") {
		n, err := strconv.Atoi(name[1:])
		if err != nil {
			return nil, err
		}

		if n < len(p.fields) {
			return p.fields[n], nil
		}

		return nil, fmt.Errorf("No field %q", name)
	}

	if name == "NF" {
		if p.fields == nil {
			return 0, nil
		}
		return len(p.fields) - 1, nil
	}

	if value, ok := p.vars[name]; ok {
		return value, nil
	}

	return nil, fmt.Errorf("No variable %q", name)
}

func (p *Context) Set(name string, value interface{}) error {

	if strings.HasPrefix(name, "$") {
		return fmt.Errorf("Cannot override field %q", name)
	}

	if name == "NF" {
		return fmt.Errorf("Cannot override NF")
	}

	p.vars[name] = value
	return nil
}

func toFloat(arg interface{}) (float64, error) {
	switch v := arg.(type) {
	case string:
		f, err := strconv.ParseFloat(v, 64)
		return f, err
	case bool:
		if v {
			return 1.0, nil
		} else {
			return 0.0, nil
		}
	default:
		return v.(float64), nil
	}
}

var funcs = map[string]govaluate.ExpressionFunction{
	"num": func(arguments ...interface{}) (interface{}, error) {
		if len(arguments) != 1 {
			return nil, fmt.Errorf("- one parameter expected, got %d", len(arguments))
		}

		return toFloat(arguments[0])
	},

	"int": func(arguments ...interface{}) (interface{}, error) {
		if len(arguments) != 1 {
			return nil, fmt.Errorf("- one parameter expected, got %d", len(arguments))
		}

		f, err := toFloat(arguments[0])
		return float64(int(f)), err
	},

	"len": func(arguments ...interface{}) (interface{}, error) {
		if len(arguments) != 1 {
			return nil, fmt.Errorf("- one parameter expected, got %d", len(arguments))
		}

		if s, ok := arguments[0].(string); ok {
			return float64(len(s)), nil
		}

		return nil, fmt.Errorf("- input should be a string")
	},
}

func main() {
	version := flag.Bool("version", false, "print version and exit")
	quote := flag.Bool("quote", false, "quote returned fields")
	unquote := flag.Bool("unquote", false, "quote returned fields")
	ifs := flag.String("ifs", " ", "input field separator")
	ire := flag.String("ifs-re", "", "input field separator (as regular expression)")
	ofs := flag.String("ofs", " ", "output field separator")
	re := flag.String("re", "", "regular expression for parsing input")
	grep := flag.String("grep", "", "output only lines that match the regular expression")
	contains := flag.String("contains", "", "output only lines that contains the pattern")
	format := flag.String("printf", "", "output is formatted according to specified format")
	matches := flag.String("matches", "", "return status code 100 if any line matches the specified pattern, 101 otherwise")
	after := flag.String("after", "", "process fields in line after specified tag (remove text before tag)")
	before := flag.String("before", "", "process fields in line before specified tag (remove text after tag)")
	afterline := flag.String("after-line", "", "process lines after lines that matches")
	afterlinen := flag.Int("after-linen", 0, "process lines after n lines")
	printline := flag.Bool("line", false, "print line numbers")
	debug := flag.Bool("debug", false, "print debug info")
	exprbegin := flag.String("begin", "", "expression to be executed before processing lines")
	exprend := flag.String("end", "", "expression to be executed after processing lines")
	exprline := flag.String("expr", "", "expression to be executed for each line")
	exprtest := flag.String("test", "", "test expression (skip line if false)")
	uniq := flag.Bool("uniq", false, "print only unique lines")

	flag.Parse()

	if *version {
		extra := ""
		if gitCommit != "" {
			extra = fmt.Sprintf(" (%.4v %v)", gitCommit, buildDate)
		}

		fmt.Printf("%s version %s%v\n", path.Base(os.Args[0]), VERSION, extra)
		return
	}

	pos := make([]Pos, len(flag.Args()))

	for i, arg := range flag.Args() {
		pos[i].Set(arg)
	}

	if len(*format) > 0 && !strings.HasSuffix(*format, "\n") {
		*format += "\n"
	}

	var split_re, split_pattern, match_pattern, grep_pattern *regexp.Regexp
	var expr_begin, expr_end, expr_line, expr_test *govaluate.EvaluableExpression

	status_code := OK

	if len(*matches) > 0 {
		match_pattern = regexp.MustCompile(*matches)
		status_code = MATCH_NOT_FOUND
	}

	if len(*grep) > 0 {
		if !strings.ContainsAny(*grep, "()") {
			*grep = "(" + *grep + ")"
		}
		grep_pattern = regexp.MustCompile(*grep)
	}

	if len(*re) > 0 {
		split_pattern = regexp.MustCompile(*re)
	}

	if len(*ire) > 0 {
		split_re = regexp.MustCompile(*ire)
	}

	if len(*exprbegin) > 0 {
		expr_begin = MustEvaluate(*exprbegin)
	}
	if len(*exprline) > 0 {
		expr_line = MustEvaluate(*exprline)
	}
	if len(*exprend) > 0 {
		expr_end = MustEvaluate(*exprend)
	}
	if len(*exprtest) > 0 {
		expr_test = MustEvaluate(*exprtest)
	}

	scanner := bufio.NewScanner(os.Stdin)
	len_after := len(*after)
	len_afterline := len(*afterline)
	lineno := 0
	uniques := map[string]struct{}{}

	expr_context := Context{vars: map[string]interface{}{}}

	if expr_begin != nil {
		_, err := expr_begin.Eval(&expr_context)
		if err != nil {
			log.Println("error in begin", err)
		}
		// else, should we print the result ?
	}

	for scanner.Scan() {
		if scanner.Err() != nil {
			log.Fatal(scanner.Err())
		}

		line := scanner.Text()

		lineno += 1

		if *afterlinen >= lineno {
			continue
		}

		if len_afterline > 0 {
			if strings.Contains(line, *afterline) {
				len_afterline = 0
			}

			continue
		}

		if len(*contains) > 0 && !strings.Contains(line, *contains) {
			continue
		}

		if len_after > 0 {
			i := strings.Index(line, *after)
			if i < 0 {
				continue // no match
			}

			line = line[i+len_after:]

			if len(*before) > 0 {
				i := strings.Index(line, *before)
				if i >= 0 {
					line = line[:i]
				}
			}
		}

		expr_context.fields = []string{line} // $0 is the full line

		if grep_pattern != nil {
			if matches := grep_pattern.FindStringSubmatch(line); matches != nil {
				expr_context.fields = matches
			} else {
				continue
			}
		} else if split_pattern != nil {
			if matches := split_pattern.FindStringSubmatch(line); matches != nil {
				expr_context.fields = matches
			}
		} else if split_re != nil {
			// split line according to input regular expression
			expr_context.fields = append(expr_context.fields, split_re.Split(line, -1)...)
		} else if *ifs == " " {
			// split line on spaces (compact multiple spaces)
			expr_context.fields = append(expr_context.fields, SPACES.Split(strings.TrimSpace(line), -1)...)
		} else {
			// split line according to input field separator
			expr_context.fields = append(expr_context.fields, strings.Split(line, *ifs)...)
		}

		if *debug {
			log.Printf("input fields: %q\n", expr_context.fields)
			if len(pos) > 0 {
				log.Printf("output fields: %q\n", pos)
			}
		}

		var result []string

		// do some processing
		if len(pos) > 0 {
			result = make([]string, 0)

			for _, p := range pos {
				result = append(result, Slice(expr_context.fields, p)...)
			}
		} else {
			result = expr_context.fields[1:]
		}

		if *unquote {
			result = Unquote(result)
		}

		if *quote {
			result = Quote(result)
		}

		if *printline {
			fmt.Printf("%d: ", lineno)
		}

		if expr_test != nil {
			res, err := expr_test.Eval(&expr_context)
			if err != nil {
				log.Println("error in expr", err)
			} else {
				switch test := res.(type) {
				case bool:
					if !test {
						continue
					}

				default:
					log.Println("boolean expected, got", test)
					continue
				}
			}
		}

		if *uniq {
			l := strings.Join(result, " ")
			if _, ok := uniques[l]; ok {
				continue
			}

			uniques[l] = SET
		}

		if len(*format) > 0 {
			Print(*format, result)
		} else {
			// join the result according to output field separator
			fmt.Println(strings.Join(result, *ofs))
		}

		if match_pattern != nil && match_pattern.MatchString(line) {
			status_code = MATCH_FOUND
		}

		if expr_line != nil {
			_, err := expr_line.Eval(&expr_context)
			if err != nil {
				log.Println("error in expr", err)
			}
			// else, should we print the result ?
		}
	}

	if expr_end != nil {
		res, err := expr_end.Eval(&expr_context)
		if err != nil {
			log.Println("error in end", err)
		} else {
			fmt.Println(res)
		}
	}

	os.Exit(status_code)
}
<|endoftext|>


<commit_before>package glow

import (
	"fmt"
	"reflect"
	"strings"
)

// ----------------------------------------------------------------------------
type Node struct {
	name     string          // The node's name.
	fi       interface{}     // The node's run function.
	ft       reflect.Type    // The function's type.
	fv       reflect.Value   // The function's value.
	argNames []string        // Names of arguments for the function.
	argVals  []reflect.Value // Argument values.
	argTypes []reflect.Type  // Argument types.
}

func NewNode(fn interface{}, name string, argNames ...string) *Node {
	node := new(Node)
	node.name = name
	node.fi = fn
	node.ft = reflect.TypeOf(fn)
	node.fv = reflect.ValueOf(fn)
	node.argNames = append([]string{"globals"}, argNames...)
	node.argVals = make([]reflect.Value, len(argNames)+1)
	node.argTypes = make([]reflect.Type, len(argNames)+1)
	for i := 0; i < node.ft.NumIn(); i++ {
		node.argTypes[i] = node.ft.In(i)
	}
	return node
}

func (node *Node) Run() {
	node.fv.Call(node.argVals)
}

func (node *Node) MakeChan(name string, size int) reflect.Value {
	for i, argName := range node.argNames {
		if argName == name {
			argType := node.argTypes[i]
			return reflect.MakeChan(argType, size)
		}
	}
	panic("Argument not found.")
}

func (node *Node) SetArg(name string, val reflect.Value) {
	for i, argName := range node.argNames {
		if argName == name {
			if node.argVals[i].IsValid() {
				panic("Argument alread set: " + name)
			}
			node.argVals[i] = val
			return
		}
	}
	panic("Argument not found.")
}

func (node *Node) DotString() string {
	s := node.name + " [\n"
	s += "label = \"" + node.name
	for _, name := range node.argNames[1:] {
		s += "|<" + name + ">" + name
	}
	s += "\n"
	s += "shape = record\n]"
	return s
}

// ----------------------------------------------------------------------------
type Graph struct {
	connStr  []string         // List of connections for dot file output.
	lastChan int              // Last channel number for dot file output.
	nodes    map[string]*Node // Map from node name to node.
	globals  reflect.Value    // Globals passed to each node.
	fgName   string           // Name of node to run in the foreground.
}

// NewGraph: Construct a new empty graph object. The value of globals
// will be passed as the first argument to each node function.
func NewGraph(globals interface{}) *Graph {
	graph := new(Graph)
	graph.globals = reflect.ValueOf(globals)
	graph.nodes = make(map[string]*Node)
	return graph
}

// AddNode: Add a new node to the graph. A node is implemented by a function,
// fn, and has a unique identifying name. Names of function arguments after
// the first must be given. The first argument will be the value of
// globals given when creating the graph.
func (g *Graph) AddNode(fn interface{}, name string, argNames ...string) {
	// If the node name is already in use, this is a programming error.
	_, ok := g.nodes[name]
	if ok {
		panic("Node already added: " + name)
	}
	node := NewNode(fn, name, argNames...)
	node.SetArg("globals", g.globals)
	g.nodes[name] = node
}

// Connect: Create a channel of the appropriate type to be passed to the
// given node's implementing function when the graph is run. The size of
// the channel buffer is the first argument. Additional arguments list the
// nodes that will be using the channel. The format for these arguments is
// "NodeName:ChannelName".
// Returns the new channel as a reflect.Value.
func (g *Graph) Connect(size int, nodeChans ...string) reflect.Value {
	name, port := splitNamePort(nodeChans[0])
	ch := g.nodes[name].MakeChan(port, size)

	chName := fmt.Sprintf("chan_%v", g.lastChan)
	g.lastChan += 1
	g.connStr = append(g.connStr,
		fmt.Sprintf("%v [\nlabel=\"%v\"\n]", chName, size))

	for _, s := range nodeChans {
		name, port = splitNamePort(s)
		g.nodes[name].SetArg(port, ch)

		if strings.HasSuffix(port, "Out") {
			g.connStr = append(g.connStr, name+":"+port+"->"+chName)
		} else {
			g.connStr = append(g.connStr, chName+"->"+name+":"+port)
		}
	}
	return ch
}

// SetForeground: Specify a node to run in the foreground when Run is called
// on the graph.
func (g *Graph) SetForeground(name string) {
	g.fgName = name
}

// DotString: Return a string containing a dot file suitable for processing
// by graphviz. On Linux, xdot can be used to view a dot file directly.
func (g *Graph) DotString() string {
	s := "digraph {"
	s += "\ngraph [ rankdir=\"LR\" ];"
	for _, node := range g.nodes {
		s += "\n" + node.DotString()
	}
	for _, conn := range g.connStr {
		s += "\n" + conn
	}
	s += "\n}"
	return s
}

// Run: Run each of the graph's nodes in a goroutine, with the exception of an
// optionally defined foreground node, which will run in the foreground.
func (g *Graph) Run() {
	var fgNode *Node

	for _, node := range g.nodes {
		if node.name != g.fgName {
			go node.Run()
		} else {
			fgNode = node
		}
	}

	if fgNode != nil {
		fgNode.Run()
	}
}

// ----------------------------------------------------------------------------
func splitNamePort(s string) (string, string) {
	x := strings.Split(s, ":")
	return x[0], x[1]
}
<commit_msg>Fixed dot file output missing quotes.<commit_after>package glow

import (
	"fmt"
	"reflect"
	"strings"
)

// ----------------------------------------------------------------------------
type Node struct {
	name     string          // The node's name.
	fi       interface{}     // The node's run function.
	ft       reflect.Type    // The function's type.
	fv       reflect.Value   // The function's value.
	argNames []string        // Names of arguments for the function.
	argVals  []reflect.Value // Argument values.
	argTypes []reflect.Type  // Argument types.
}

func NewNode(fn interface{}, name string, argNames ...string) *Node {
	node := new(Node)
	node.name = name
	node.fi = fn
	node.ft = reflect.TypeOf(fn)
	node.fv = reflect.ValueOf(fn)
	node.argNames = append([]string{"globals"}, argNames...)
	node.argVals = make([]reflect.Value, len(argNames)+1)
	node.argTypes = make([]reflect.Type, len(argNames)+1)
	for i := 0; i < node.ft.NumIn(); i++ {
		node.argTypes[i] = node.ft.In(i)
	}
	return node
}

func (node *Node) Run() {
	node.fv.Call(node.argVals)
}

func (node *Node) MakeChan(name string, size int) reflect.Value {
	for i, argName := range node.argNames {
		if argName == name {
			argType := node.argTypes[i]
			return reflect.MakeChan(argType, size)
		}
	}
	panic("Argument not found.")
}

func (node *Node) SetArg(name string, val reflect.Value) {
	for i, argName := range node.argNames {
		if argName == name {
			if node.argVals[i].IsValid() {
				panic("Argument alread set: " + name)
			}
			node.argVals[i] = val
			return
		}
	}
	panic("Argument not found.")
}

func (node *Node) DotString() string {
	s := node.name + " [\n"
	s += "label = \"" + node.name
	for _, name := range node.argNames[1:] {
		s += "|<" + name + ">" + name
	}
	s += "\"\n"
	s += "shape = record\n]"
	return s
}

// ----------------------------------------------------------------------------
type Graph struct {
	connStr  []string         // List of connections for dot file output.
	lastChan int              // Last channel number for dot file output.
	nodes    map[string]*Node // Map from node name to node.
	globals  reflect.Value    // Globals passed to each node.
	fgName   string           // Name of node to run in the foreground.
}

// NewGraph: Construct a new empty graph object. The value of globals
// will be passed as the first argument to each node function.
func NewGraph(globals interface{}) *Graph {
	graph := new(Graph)
	graph.globals = reflect.ValueOf(globals)
	graph.nodes = make(map[string]*Node)
	return graph
}

// AddNode: Add a new node to the graph. A node is implemented by a function,
// fn, and has a unique identifying name. Names of function arguments after
// the first must be given. The first argument will be the value of
// globals given when creating the graph.
func (g *Graph) AddNode(fn interface{}, name string, argNames ...string) {
	// If the node name is already in use, this is a programming error.
	_, ok := g.nodes[name]
	if ok {
		panic("Node already added: " + name)
	}
	node := NewNode(fn, name, argNames...)
	node.SetArg("globals", g.globals)
	g.nodes[name] = node
}

// Connect: Create a channel of the appropriate type to be passed to the
// given node's implementing function when the graph is run. The size of
// the channel buffer is the first argument. Additional arguments list the
// nodes that will be using the channel. The format for these arguments is
// "NodeName:ChannelName".
// Returns the new channel as a reflect.Value.
func (g *Graph) Connect(size int, nodeChans ...string) reflect.Value {
	name, port := splitNamePort(nodeChans[0])
	ch := g.nodes[name].MakeChan(port, size)

	chName := fmt.Sprintf("chan_%v", g.lastChan)
	g.lastChan += 1
	g.connStr = append(g.connStr,
		fmt.Sprintf("%v [\nlabel=\"%v\"\n]", chName, size))

	for _, s := range nodeChans {
		name, port = splitNamePort(s)
		g.nodes[name].SetArg(port, ch)

		if strings.HasSuffix(port, "Out") {
			g.connStr = append(g.connStr, name+":"+port+"->"+chName)
		} else {
			g.connStr = append(g.connStr, chName+"->"+name+":"+port)
		}
	}
	return ch
}

// SetForeground: Specify a node to run in the foreground when Run is called
// on the graph.
func (g *Graph) SetForeground(name string) {
	g.fgName = name
}

// DotString: Return a string containing a dot file suitable for processing
// by graphviz. On Linux, xdot can be used to view a dot file directly.
func (g *Graph) DotString() string {
	s := "digraph {"
	s += "\ngraph [ rankdir=\"LR\" ];"
	for _, node := range g.nodes {
		s += "\n" + node.DotString()
	}
	for _, conn := range g.connStr {
		s += "\n" + conn
	}
	s += "\n}"
	return s
}

// Run: Run each of the graph's nodes in a goroutine, with the exception of an
// optionally defined foreground node, which will run in the foreground.
func (g *Graph) Run() {
	var fgNode *Node

	for _, node := range g.nodes {
		if node.name != g.fgName {
			go node.Run()
		} else {
			fgNode = node
		}
	}

	if fgNode != nil {
		fgNode.Run()
	}
}

// ----------------------------------------------------------------------------
func splitNamePort(s string) (string, string) {
	x := strings.Split(s, ":")
	return x[0], x[1]
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"math"
	"reflect"
	"regexp"
	"strconv"
	"time"

	"github.com/bcgraham/tsumtsum/external/line"
)

type AddCommand struct {
	SourceInput string `short:"s" long:"source" default:"" description:"source of user IDs to be added to contact list. Tries to parse as a URL; if this fails, will assume it is a file."`
	Limit       int    `short:"l" long:"limit" default:"500" description:"how many contacts to add before stopping (defaults to 500; anything more than 800 will probably result in a tempban)"`
}

var addCommand AddCommand

func (x *AddCommand) Execute(args []string) error {
	session := MustNewSession(common.User, common.Device, common.ReportingServer)
	if x.SourceInput == "" {
		x.SourceInput = session.reportingServer.String()
	}
	err := session.LoadStrangers(x.SourceInput)
	if err != nil {
		log.Fatalf("Could not load strangers: %v", err)
	}

	t := time.Now()
	found, err := session.AddStrangers(x.Limit)
	fmt.Printf("\n\nElapsed time: %v. Averaged %.0f contacts per minute.\n", time.Since(t), avg(found, time.Since(t)))
	if err != nil {
		log.Print(err.Error())
		return err
	}
	return nil
}

func init() {
	if _, err := parser.AddCommand("add",
		"Add strangers",
		"The add command adds contacts from a source to your contact list.",
		&addCommand); err != nil {
		log.Fatal(err)
	}
}

func (s *Session) AddContact(id string) (mid string, err error) {

	AddContactFunc := (*line.TalkServiceClient).FindAndAddContactsByUserid
	if midMatcher.MatchString(id) {
		AddContactFunc = (*line.TalkServiceClient).FindAndAddContactsByMid
	}
	r, err := AddContactFunc(s.client, <-s.reqSeq, id)
	if err != nil {
		// http response code 400 basically means
		// reconnect. not sure what causes it.
		if err.Error() == "HTTP Response code: 400" {
			s.logger.Printf("Got error \"%v\"\n.", err.Error())
			err = s.Rebuild()
			if err != nil {
				return mid, err
			}
			r, err = AddContactFunc(s.client, <-s.reqSeq, id)
		}
		if err != nil {
			if isTransportError(err) {
				return mid, err
			}
			if isContactsListFull(err) {
				msg := "\nCan't continue adding contacts. Your contact list is probably full (5000 contacts). Sleeping for ten minutes, then will resume.\n"
				s.logger.Print(msg)
				return mid, err
			}
			if isAbuse(err) {
				s.logger.Print("Your usage has been flagged as abuse and you can't presently add friends. This is almost certainly from trying to add too many friends. This is usually a temporary ban that lasts between 12 and 24 hours, but they last longer if you're a repeat offender.\n")
				return mid, err
			}
		}
	}
	var userID string
	if !isMid(id) {
		userID = id
		if contact, ok := r[id]; ok {
			mid = contact.GetMid()
			s.strangers[userID] = mid
		}
	} else {
		mid = id
	}
	err = s.SendReport(Report{
		Submitter: s.username,
		UserID:    userID,
		MID:       mid,
		Type:      search,
	})
	if err != nil {
		log.Printf("error sending search result: %v\n", err)
	}
	return mid, err
}

func (s *Session) AddStrangers(limit int) (found int, err error) {
	max := limit
	if len(s.strangers) < max {
		max = len(s.strangers)
	}

	var count int
	for id := range s.strangers {
		if count >= max {
			break
		}

		if !s.isNewID(id) {
			continue
		}

		mid, err := s.AddContact(id)
		if err != nil {
			if isAbuse(err) || isContactsListFull(err) {
				return found, err
			}
			s.logger.Printf("error adding contact: %v\n", err)
		}

		count++
		if mid != "" {
			found++
		}
		addProgress(count, max, found)
	}
	return found, nil
}

var midMatcher *regexp.Regexp

func init() {
	midMatcher = regexp.MustCompile("^u[a-fA-F0-9]{32}$")
}

func isMid(id string) bool {
	return midMatcher.MatchString(id)
}

func avg(found int, d time.Duration) float64 {
	x := float64(found) * (float64(time.Minute) / float64(d))
	if math.IsNaN(x) {
		return 0
	}
	return x
}

func isTransportError(err error) bool {
	return reflect.TypeOf(err).String() == "*thrift.tTransportException"
}

func isContactsListFull(err error) bool {
	return reflect.TypeOf(err).String() == "*line.TalkException" && err.(*line.TalkException).GetCode() == line.ErrorCode_INVALID_STATE
}

func isAbuse(err error) bool {
	return reflect.TypeOf(err).String() == "*line.TalkException" && err.(*line.TalkException).GetCode() == line.ErrorCode_ABUSE_BLOCK
}

func addProgress(count, max, found int) {
	printProgress(prog{
		str: "%.2f%% completed. (%" + strconv.Itoa(len(strconv.Itoa(max))) + "d/%d) %" + strconv.Itoa(len(strconv.Itoa(max))) + "d of %" + strconv.Itoa(len(strconv.Itoa(max))) + "d found (%.2f%%).",
		args: []interface{}{
			100 * float64(count) / float64(max), count, max, found, count, 100 * float64(found) / float64(count),
		},
	})
}
<commit_msg>added an error for people who leave the LINELOGIN as the username<commit_after>package main

import (
	"fmt"
	"log"
	"math"
	"os"
	"reflect"
	"regexp"
	"strconv"
	"time"

	"github.com/bcgraham/tsumtsum/external/line"
)

type AddCommand struct {
	SourceInput string `short:"s" long:"source" default:"" description:"source of user IDs to be added to contact list. Tries to parse as a URL; if this fails, will assume it is a file."`
	Limit       int    `short:"l" long:"limit" default:"500" description:"how many contacts to add before stopping (defaults to 500; anything more than 800 will probably result in a tempban)"`
}

var addCommand AddCommand

func (x *AddCommand) Execute(args []string) error {
	if common.User == "LINELOGIN" {
		fmt.Print("You have to type your own LINE login after the \"-u=\" - see how you passed \r\nin LINELOGIN? That's just placeholder text for your username.\n")
		os.Exit(1)
	}
	session := MustNewSession(common.User, common.Device, common.ReportingServer)
	if x.SourceInput == "" {
		x.SourceInput = session.reportingServer.String()
	}
	err := session.LoadStrangers(x.SourceInput)
	if err != nil {
		log.Fatalf("Could not load strangers: %v", err)
	}

	t := time.Now()
	found, err := session.AddStrangers(x.Limit)
	fmt.Printf("\n\nElapsed time: %v. Averaged %.0f contacts per minute.\n", time.Since(t), avg(found, time.Since(t)))
	if err != nil {
		log.Print(err.Error())
		return err
	}
	return nil
}

func init() {
	if _, err := parser.AddCommand("add",
		"Add strangers",
		"The add command adds contacts from a source to your contact list.",
		&addCommand); err != nil {
		log.Fatal(err)
	}
}

func (s *Session) AddContact(id string) (mid string, err error) {

	AddContactFunc := (*line.TalkServiceClient).FindAndAddContactsByUserid
	if midMatcher.MatchString(id) {
		AddContactFunc = (*line.TalkServiceClient).FindAndAddContactsByMid
	}
	r, err := AddContactFunc(s.client, <-s.reqSeq, id)
	if err != nil {
		// http response code 400 basically means
		// reconnect. not sure what causes it.
		if err.Error() == "HTTP Response code: 400" {
			s.logger.Printf("Got error \"%v\"\n.", err.Error())
			err = s.Rebuild()
			if err != nil {
				return mid, err
			}
			r, err = AddContactFunc(s.client, <-s.reqSeq, id)
		}
		if err != nil {
			if isTransportError(err) {
				return mid, err
			}
			if isContactsListFull(err) {
				msg := "\nCan't continue adding contacts. Your contact list is probably full (5000 contacts). Sleeping for ten minutes, then will resume.\n"
				s.logger.Print(msg)
				return mid, err
			}
			if isAbuse(err) {
				s.logger.Print("Your usage has been flagged as abuse and you can't presently add friends. This is almost certainly from trying to add too many friends. This is usually a temporary ban that lasts between 12 and 24 hours, but they last longer if you're a repeat offender.\n")
				return mid, err
			}
		}
	}
	var userID string
	if !isMid(id) {
		userID = id
		if contact, ok := r[id]; ok {
			mid = contact.GetMid()
			s.strangers[userID] = mid
		}
	} else {
		mid = id
	}
	err = s.SendReport(Report{
		Submitter: s.username,
		UserID:    userID,
		MID:       mid,
		Type:      search,
	})
	if err != nil {
		log.Printf("error sending search result: %v\n", err)
	}
	return mid, err
}

func (s *Session) AddStrangers(limit int) (found int, err error) {
	max := limit
	if len(s.strangers) < max {
		max = len(s.strangers)
	}

	var count int
	for id := range s.strangers {
		if count >= max {
			break
		}

		if !s.isNewID(id) {
			continue
		}

		mid, err := s.AddContact(id)
		if err != nil {
			if isAbuse(err) || isContactsListFull(err) {
				return found, err
			}
			s.logger.Printf("error adding contact: %v\n", err)
		}

		count++
		if mid != "" {
			found++
		}
		addProgress(count, max, found)
	}
	return found, nil
}

var midMatcher *regexp.Regexp

func init() {
	midMatcher = regexp.MustCompile("^u[a-fA-F0-9]{32}$")
}

func isMid(id string) bool {
	return midMatcher.MatchString(id)
}

func avg(found int, d time.Duration) float64 {
	x := float64(found) * (float64(time.Minute) / float64(d))
	if math.IsNaN(x) {
		return 0
	}
	return x
}

func isTransportError(err error) bool {
	return reflect.TypeOf(err).String() == "*thrift.tTransportException"
}

func isContactsListFull(err error) bool {
	return reflect.TypeOf(err).String() == "*line.TalkException" && err.(*line.TalkException).GetCode() == line.ErrorCode_INVALID_STATE
}

func isAbuse(err error) bool {
	return reflect.TypeOf(err).String() == "*line.TalkException" && err.(*line.TalkException).GetCode() == line.ErrorCode_ABUSE_BLOCK
}

func addProgress(count, max, found int) {
	printProgress(prog{
		str: "%.2f%% completed. (%" + strconv.Itoa(len(strconv.Itoa(max))) + "d/%d) %" + strconv.Itoa(len(strconv.Itoa(max))) + "d of %" + strconv.Itoa(len(strconv.Itoa(max))) + "d found (%.2f%%).",
		args: []interface{}{
			100 * float64(count) / float64(max), count, max, found, count, 100 * float64(found) / float64(count),
		},
	})
}
<|endoftext|>


<commit_before>package golang

import (
	"bytes"
	"disposa.blue/margo/golang/internal/gocode"
	"disposa.blue/margo/mg"
	"disposa.blue/margo/sublime"
	"fmt"
	"go/ast"
	"go/build"
	"go/parser"
	"go/printer"
	"go/token"
	"io"
	"strings"
	"unicode"
)

var (
	gocodeClassTags = map[string]mg.CompletionTag{
		"const":   mg.ConstantTag,
		"func":    mg.FunctionTag,
		"package": mg.PackageTag,
		"import":  mg.PackageTag,
		"type":    mg.TypeTag,
		"var":     mg.VariableTag,
	}
)

type Gocode struct {
	InstallSuffix            string
	ProposeBuiltins          bool
	ProposeTests             bool
	Autobuild                bool
	UnimportedPackages       bool
	AllowExplicitCompletions bool
	AllowWordCompletions     bool
	ShowFuncParams           bool
	ShowFuncResultNames      bool
}

func (g *Gocode) Reduce(mx *mg.Ctx) *mg.State {
	st, gx := initGocodeReducer(mx, g)
	if gx == nil || !gx.query.completions {
		return st
	}

	candidates := gx.candidates()
	completions := make([]mg.Completion, 0, len(candidates))
	for _, v := range candidates {
		if c, ok := g.completion(gx, v); ok {
			completions = append(completions, c)
		}
	}
	return st.AddCompletions(completions...)
}

func (g *Gocode) funcTitle(fx *ast.FuncType, buf *bytes.Buffer, decl string) string {
	// TODO: caching

	buf.Reset()
	fset := token.NewFileSet()

	buf.WriteString("func(")
	if fx.Params != nil {
		switch {
		case g.ShowFuncParams:
			g.printFields(buf, fset, fx.Params.List, true)
		case fx.Params.NumFields() != 0:
			buf.WriteString("…")
		}
	}
	buf.WriteString(")")

	if fl := fx.Results; fl != nil {
		buf.WriteString(" ")
		hasNames := g.ShowFuncResultNames && len(fl.List) != 0 && len(fl.List[0].Names) != 0
		if hasNames {
			buf.WriteString("(")
		}
		g.printFields(buf, fset, fl.List, g.ShowFuncResultNames)
		if hasNames {
			buf.WriteString(")")
		}
	}

	return buf.String()
}

func (g *Gocode) funcSrc(fx *ast.FuncType, buf *bytes.Buffer, v gocode.MargoCandidate, gx *gocodeCtx) string {
	// TODO: caching
	// TODO: only output the name, if we're in a call, assignment, etc. that takes a func

	outputArgs := true
	for _, c := range gx.src[gx.pos:] {
		if c == '(' {
			outputArgs = false
			break
		}
		r := rune(c)
		if !IsLetter(r) && !unicode.IsSpace(r) {
			break
		}
	}

	buf.Reset()
	buf.WriteString(v.Name)
	if outputArgs {
		buf.WriteString("(")
		for i, field := range fx.Params.List {
			if i > 0 {
				fmt.Fprint(buf, ", ")
			}
			for j, name := range field.Names {
				if j > 0 {
					fmt.Fprint(buf, ", ")
				}
				fmt.Fprintf(buf, "${%d:%s}", i+1, name)
			}
		}
		buf.WriteString(")")
	}
	buf.WriteString("${0}")
	return buf.String()
}

func (g *Gocode) printFields(w io.Writer, fset *token.FileSet, list []*ast.Field, printNames bool) {
	for i, field := range list {
		if i > 0 {
			fmt.Fprint(w, ", ")
		}
		if printNames {
			for j, name := range field.Names {
				if j > 0 {
					fmt.Fprint(w, ", ")
				}
				fmt.Fprint(w, name.String())
			}
			if len(field.Names) != 0 {
				fmt.Fprint(w, " ")
			}
		}
		printer.Fprint(w, fset, field.Type)
	}
}

func (g *Gocode) completion(gx *gocodeCtx, v gocode.MargoCandidate) (c mg.Completion, ok bool) {
	buf := bytes.NewBuffer(nil)
	if v.Class.String() == "PANIC" {
		mg.Log.Printf("gocode panicked in '%s' at pos '%d'\n", gx.fn, gx.pos)
		return c, false
	}
	if !g.ProposeTests && g.matchTests(v) {
		return c, false
	}

	var fx *ast.FuncType
	if strings.HasPrefix(v.Type, "func(") {
		x, _ := parser.ParseExpr(v.Type)
		fx, _ = x.(*ast.FuncType)
	}

	c = mg.Completion{
		Query: g.compQuery(v),
		Tag:   g.compTag(v),
		Src:   g.compSrc(fx, buf, v, gx),
		Title: g.compTitle(fx, buf, v),
	}
	return c, true
}

func (g *Gocode) compQuery(v gocode.MargoCandidate) string {
	return v.Name
}

func (g *Gocode) compSrc(fx *ast.FuncType, buf *bytes.Buffer, v gocode.MargoCandidate, gx *gocodeCtx) string {
	if fx == nil {
		return v.Name
	}
	return g.funcSrc(fx, buf, v, gx)
}

func (g *Gocode) compTag(v gocode.MargoCandidate) mg.CompletionTag {
	if tag, ok := gocodeClassTags[v.Class.String()]; ok {
		return tag
	}
	return mg.UnknownTag
}

func (g *Gocode) compTitle(fx *ast.FuncType, buf *bytes.Buffer, v gocode.MargoCandidate) string {
	if fx != nil {
		return g.funcTitle(fx, buf, v.Type)
	}
	if v.Type == "" {
		return v.Class.String()
	}
	return v.Type
}

func (g *Gocode) matchTests(c gocode.MargoCandidate) bool {
	return strings.HasPrefix(c.Name, "Test") ||
		strings.HasPrefix(c.Name, "Benchmark") ||
		strings.HasPrefix(c.Name, "Example")
}

type gocodeCtx struct {
	Gocode
	cn    *CursorNode
	fn    string
	src   []byte
	pos   int
	bctx  *build.Context
	cfg   gocode.MargoConfig
	query struct {
		completions bool
		tooltips    bool
	}
}

func initGocodeReducer(mx *mg.Ctx, g *Gocode) (*mg.State, *gocodeCtx) {
	st := mx.State
	if cfg, ok := st.Config.(sublime.Config); ok {
		cfg = cfg.DisableGsComplete()
		if !g.AllowExplicitCompletions {
			cfg = cfg.InhibitExplicitCompletions()
		}
		if !g.AllowWordCompletions {
			cfg = cfg.InhibitWordCompletions()
		}
		st = st.SetConfig(cfg)
	}

	if !st.View.LangIs("go") {
		return st, nil
	}

	// TODO: use QueryCompletions.Pos when support is added
	_, tooltips := mx.Action.(mg.QueryTooltips)
	_, completions := mx.Action.(mg.QueryCompletions)
	if !completions && !tooltips {
		return st, nil
	}

	bctx := BuildContext(mx.Env)
	src, _ := st.View.ReadAll()
	pos := clampSrcPos(src, st.View.Pos)
	pos = mg.BytePos(src, pos)

	// don't do completion inside comments
	cn := ParseCursorNode(src, pos)
	if cn.Comment != nil {
		return st, nil
	}
	// don't do completion inside strings unless it's an import
	if cn.ImportSpec == nil && cn.BasicLit != nil && cn.BasicLit.Kind == token.STRING {
		return st, nil
	}

	gx := &gocodeCtx{
		cn:   cn,
		fn:   st.View.Filename(),
		pos:  pos,
		src:  src,
		bctx: bctx,
		cfg: gocode.MargoConfig{
			GOROOT:             bctx.GOROOT,
			GOPATHS:            PathList(bctx.GOPATH),
			InstallSuffix:      g.InstallSuffix,
			ProposeBuiltins:    g.ProposeBuiltins,
			Autobuild:          g.Autobuild,
			UnimportedPackages: g.UnimportedPackages,
		},
	}
	gx.query.completions = completions
	gx.query.tooltips = tooltips
	return st, gx
}

func (gx *gocodeCtx) candidates() []gocode.MargoCandidate {
	if len(gx.src) == 0 {
		return nil
	}
	return gocode.Margo.Complete(gx.cfg, gx.src, gx.fn, gx.pos)
}

func clampSrcPos(src []byte, pos int) int {
	if pos < 0 {
		return 0
	}
	if pos > len(src) {
		return len(src) - 1
	}
	return pos
}
<commit_msg>sync deps<commit_after>package golang

import (
	"bytes"
	"disposa.blue/margo/golang/internal/gocode"
	"disposa.blue/margo/mg"
	"disposa.blue/margo/sublime"
	"fmt"
	"go/ast"
	"go/build"
	"go/parser"
	"go/printer"
	"go/token"
	"io"
	"strings"
	"unicode"
)

var (
	gocodeClassTags = map[string]mg.CompletionTag{
		"const":   mg.ConstantTag,
		"func":    mg.FunctionTag,
		"package": mg.PackageTag,
		"import":  mg.PackageTag,
		"type":    mg.TypeTag,
		"var":     mg.VariableTag,
	}
)

type Gocode struct {
	InstallSuffix            string
	ProposeBuiltins          bool
	ProposeTests             bool
	Autobuild                bool
	UnimportedPackages       bool
	AllowExplicitCompletions bool
	AllowWordCompletions     bool
	ShowFuncParams           bool
	ShowFuncResultNames      bool
}

func (g *Gocode) Reduce(mx *mg.Ctx) *mg.State {
	st, gx := initGocodeReducer(mx, g)
	if gx == nil || !gx.query.completions {
		return st
	}

	candidates := gx.candidates()
	completions := make([]mg.Completion, 0, len(candidates))
	for _, v := range candidates {
		if c, ok := g.completion(gx, v); ok {
			completions = append(completions, c)
		}
	}
	return st.AddCompletions(completions...)
}

func (g *Gocode) funcTitle(fx *ast.FuncType, buf *bytes.Buffer, decl string) string {
	// TODO: caching

	buf.Reset()
	fset := token.NewFileSet()

	buf.WriteString("func(")
	if fx.Params != nil {
		switch {
		case g.ShowFuncParams:
			g.printFields(buf, fset, fx.Params.List, true)
		case fx.Params.NumFields() != 0:
			buf.WriteString("…")
		}
	}
	buf.WriteString(")")

	if fl := fx.Results; fl != nil {
		buf.WriteString(" ")
		hasNames := g.ShowFuncResultNames && len(fl.List) != 0 && len(fl.List[0].Names) != 0
		if hasNames {
			buf.WriteString("(")
		}
		g.printFields(buf, fset, fl.List, g.ShowFuncResultNames)
		if hasNames {
			buf.WriteString(")")
		}
	}

	return buf.String()
}

func (g *Gocode) funcSrc(fx *ast.FuncType, buf *bytes.Buffer, v gocode.MargoCandidate, gx *gocodeCtx) string {
	// TODO: caching
	// TODO: only output the name, if we're in a call, assignment, etc. that takes a func

	outputArgs := true
	for _, c := range gx.src[gx.pos:] {
		if c == '(' {
			outputArgs = false
			break
		}
		r := rune(c)
		if !IsLetter(r) && !unicode.IsSpace(r) {
			break
		}
	}

	buf.Reset()
	buf.WriteString(v.Name)
	if outputArgs {
		buf.WriteString("(")
		for i, field := range fx.Params.List {
			if i > 0 {
				fmt.Fprint(buf, ", ")
			}
			for j, name := range field.Names {
				if j > 0 {
					fmt.Fprint(buf, ", ")
				}
				fmt.Fprintf(buf, "${%d:%s}", i+1, name)
			}
		}
		buf.WriteString(")")
	}
	buf.WriteString("${0}")
	return buf.String()
}

func (g *Gocode) printFields(w io.Writer, fset *token.FileSet, list []*ast.Field, printNames bool) {
	for i, field := range list {
		if i > 0 {
			fmt.Fprint(w, ", ")
		}
		if printNames {
			for j, name := range field.Names {
				if j > 0 {
					fmt.Fprint(w, ", ")
				}
				fmt.Fprint(w, name.String())
			}
			if len(field.Names) != 0 {
				fmt.Fprint(w, " ")
			}
		}
		printer.Fprint(w, fset, field.Type)
	}
}

func (g *Gocode) completion(gx *gocodeCtx, v gocode.MargoCandidate) (c mg.Completion, ok bool) {
	buf := bytes.NewBuffer(nil)
	if v.Class.String() == "PANIC" {
		mg.Log.Printf("gocode panicked in '%s' at pos '%d'\n", gx.fn, gx.pos)
		return c, false
	}
	if !g.ProposeTests && g.matchTests(v) {
		return c, false
	}

	var fx *ast.FuncType
	if strings.HasPrefix(v.Type, "func(") {
		x, _ := parser.ParseExpr(v.Type)
		fx, _ = x.(*ast.FuncType)
	}

	c = mg.Completion{
		Query: g.compQuery(v),
		Tag:   g.compTag(v),
		Src:   g.compSrc(fx, buf, v, gx),
		Title: g.compTitle(fx, buf, v),
	}
	return c, true
}

func (g *Gocode) compQuery(v gocode.MargoCandidate) string {
	return v.Name
}

func (g *Gocode) compSrc(fx *ast.FuncType, buf *bytes.Buffer, v gocode.MargoCandidate, gx *gocodeCtx) string {
	if fx == nil {
		return v.Name
	}
	return g.funcSrc(fx, buf, v, gx)
}

func (g *Gocode) compTag(v gocode.MargoCandidate) mg.CompletionTag {
	if tag, ok := gocodeClassTags[v.Class.String()]; ok {
		return tag
	}
	return mg.UnknownTag
}

func (g *Gocode) compTitle(fx *ast.FuncType, buf *bytes.Buffer, v gocode.MargoCandidate) string {
	if fx != nil {
		return g.funcTitle(fx, buf, v.Type)
	}
	if v.Type == "" {
		return v.Class.String()
	}
	return v.Type
}

func (g *Gocode) matchTests(c gocode.MargoCandidate) bool {
	return strings.HasPrefix(c.Name, "Test") ||
		strings.HasPrefix(c.Name, "Benchmark") ||
		strings.HasPrefix(c.Name, "Example")
}

type gocodeCtx struct {
	Gocode
	cn    *CursorNode
	fn    string
	src   []byte
	pos   int
	bctx  *build.Context
	cfg   gocode.MargoConfig
	query struct {
		completions bool
		tooltips    bool
	}
}

func initGocodeReducer(mx *mg.Ctx, g *Gocode) (*mg.State, *gocodeCtx) {
	st := mx.State
	if !st.View.LangIs("go") {
		return st, nil
	}

	if cfg, ok := st.Config.(sublime.Config); ok {
		cfg = cfg.DisableGsComplete()
		if !g.AllowExplicitCompletions {
			cfg = cfg.InhibitExplicitCompletions()
		}
		if !g.AllowWordCompletions {
			cfg = cfg.InhibitWordCompletions()
		}
		st = st.SetConfig(cfg)
	}

	// TODO: use QueryCompletions.Pos when support is added
	_, tooltips := mx.Action.(mg.QueryTooltips)
	_, completions := mx.Action.(mg.QueryCompletions)
	if !completions && !tooltips {
		return st, nil
	}

	bctx := BuildContext(mx.Env)
	src, _ := st.View.ReadAll()
	pos := clampSrcPos(src, st.View.Pos)
	pos = mg.BytePos(src, pos)

	// don't do completion inside comments
	cn := ParseCursorNode(src, pos)
	if cn.Comment != nil {
		return st, nil
	}
	// don't do completion inside strings unless it's an import
	if cn.ImportSpec == nil && cn.BasicLit != nil && cn.BasicLit.Kind == token.STRING {
		return st, nil
	}

	gx := &gocodeCtx{
		cn:   cn,
		fn:   st.View.Filename(),
		pos:  pos,
		src:  src,
		bctx: bctx,
		cfg: gocode.MargoConfig{
			GOROOT:             bctx.GOROOT,
			GOPATHS:            PathList(bctx.GOPATH),
			InstallSuffix:      g.InstallSuffix,
			ProposeBuiltins:    g.ProposeBuiltins,
			Autobuild:          g.Autobuild,
			UnimportedPackages: g.UnimportedPackages,
		},
	}
	gx.query.completions = completions
	gx.query.tooltips = tooltips
	return st, gx
}

func (gx *gocodeCtx) candidates() []gocode.MargoCandidate {
	if len(gx.src) == 0 {
		return nil
	}
	return gocode.Margo.Complete(gx.cfg, gx.src, gx.fn, gx.pos)
}

func clampSrcPos(src []byte, pos int) int {
	if pos < 0 {
		return 0
	}
	if pos > len(src) {
		return len(src) - 1
	}
	return pos
}
<|endoftext|>


<commit_before>package gohttp

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

// // ApiOutput is sturct data need responsed.
// type ApiOutput struct {
// 	Code int         `json:"code"`
// 	Msg  string      `json:"msg"`
// 	Err  error       `json:"err"`
// 	Data interface{} `json:"data"`
// }

// // MarshalJSON rewrite format to json, implement json.Marshaler interface.
// func (api ApiOutput) MarshalJSON() ([]byte, error) {
// 	var buf bytes.Buffer
// 	buf.WriteByte('{')
// 	fmt.Fprintf(&buf, `"code":%d,`, api.Code)
// 	fmt.Fprintf(&buf, `"msg":"%s",`, api.Msg)
// 	fmt.Fprintf(&buf, `"err":"%v",`, strings.Replace(fmt.Sprintf("%v", api.Err), "\"", " ", -1))
// 	fmt.Fprintf(&buf, `"data":`)
// 	b, err := json.Marshal(api.Data)
// 	if err != nil {
// 		return nil, err
// 	}
// 	_, err = buf.Write(b)

// 	buf.WriteByte('}')

// 	bytes := buf.Bytes()
// 	for index, char := range bytes {
// 		if char == '\t' || char == '\n' || char == '\r' {
// 			bytes[index] = ' '
// 		}
// 	}
// 	return bytes, err
// }

// func (api *ApiOutput) Set(code int, msg string, errs ...error) {
// 	api.Code = code
// 	api.Msg = msg
// 	if len(errs) > 0 {
// 		api.Err = errs[0]
// 	}
// }

// func (api *ApiOutput) String() string {
// 	return fmt.Sprintf("%d: %s|%s", api.Code, api.Msg, api.Err.Error())
// }

// func (api *ApiOutput) Detail() string {
// 	return fmt.Sprintf("%d: %s\n%s\n%v", api.Code, api.Msg, api.Err.Error(), api.Data)
// }

// // ApiHandler designed for http api. It can used easily.
// type ApiHandler struct {
// 	ApiOutput
// 	HttpHandler
// }

// func (ctx *ApiHandler) Finish() {
// 	ctx.ResponseWriter.Header().Set("Content-Type", "application/json; charset=utf-8")
// 	ctx.Output(ctx.ApiOutput)
// }

// // RegistErrCode regist api error code and msg.
// func RegistErrCode(code int, msg string) error {
// 	if value, ok := ErrMap[code]; !ok {
// 		return fmt.Errorf("code[%d] is used, msg=%s", code, value)
// 	}
// 	ErrMap[code] = msg
// 	return nil
// }

// // ErrMap
// var ErrMap = map[int]string{
// 	0:    "success",
// 	1000: "success",
// 	1001: "fail",
// 	1002: "",
// }
<commit_msg>upadte api.go<commit_after>package gohttp

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

// APIOutput is sturct data need responsed.
type APIOutput struct {
	Code int         `json:"code"`
	Msg  string      `json:"msg"`
	Err  error       `json:"err"`
	Data interface{} `json:"data"`
}

// MarshalJSON rewrite format to json, implement json.Marshaler interface.
func (api APIOutput) MarshalJSON() ([]byte, error) {
	var buf bytes.Buffer
	buf.WriteByte('{')
	fmt.Fprintf(&buf, `"code":%d,`, api.Code)
	fmt.Fprintf(&buf, `"msg":"%s",`, api.Msg)
	fmt.Fprintf(&buf, `"err":"%v",`, strings.Replace(fmt.Sprintf("%v", api.Err), "\"", " ", -1))
	fmt.Fprintf(&buf, `"data":`)
	b, err := json.Marshal(api.Data)
	if err != nil {
		return nil, err
	}
	_, err = buf.Write(b)

	buf.WriteByte('}')

	bytes := buf.Bytes()
	for index, char := range bytes {
		if char == '\t' || char == '\n' || char == '\r' {
			bytes[index] = ' '
		}
	}
	return bytes, err
}

// Set set msg
func (api *APIOutput) Set(code int, msg string, errs ...error) {
	api.Code = code
	api.Msg = msg
	if len(errs) > 0 {
		api.Err = errs[0]
	}
}

func (api *APIOutput) String() string {
	return fmt.Sprintf("%d: %s|%s", api.Code, api.Msg, api.Err.Error())
}

// Detail detail
func (api *APIOutput) Detail() string {
	return fmt.Sprintf("%d: %s\n%s\n%v", api.Code, api.Msg, api.Err.Error(), api.Data)
}

// APIHandler designed for http api. It can used easily.
type APIHandler struct {
	APIOutput
	BaseHTTPHandler
}

// Finish finish
func (ctx *APIHandler) Finish() {
	ctx.ResponseWriter.Header().Set("Content-Type", "application/json; charset=utf-8")
	ctx.Output(ctx.APIOutput)
}

// RegistErrCode regist api error code and msg.
func RegistErrCode(code int, msg string) error {
	if value, ok := ErrMap[code]; !ok {
		return fmt.Errorf("code[%d] is used, msg=%s", code, value)
	}
	ErrMap[code] = msg
	return nil
}

// ErrMap xx
var ErrMap = map[int]string{
	0:    "success",
	1000: "success",
	1001: "fail",
	1002: "",
}
<|endoftext|>


<commit_before>// Gone Time Tracker -or- Where has my time gone?
package main

import (
	"encoding/gob"
	"flag"
	"fmt"
	"go/build"
	"log"
	"os"
	"path/filepath"
	"time"
)

type Tracks map[Window]Track

type Track struct {
	Seen  time.Time
	Spent time.Duration
	Idle  time.Duration
}

var (
	goneDir      = pkgpath("github.com/dim13/gone")
	dumpFileName = filepath.Join(goneDir, "gone.gob")
	logFileName  = filepath.Join(goneDir, "gone.log")
	tmplFileName = filepath.Join(goneDir, "gone.tmpl")
)

var (
	display = flag.String("display", os.Getenv("DISPLAY"), "X11 display")
	listen  = flag.String("listen", "127.0.0.1:8001", "web reporter")
	timeout = flag.Duration("timeout", time.Minute*5, "idle timeout")
	expire  = flag.Duration("expire", time.Hour*8, "expire timeout")
	refresh = flag.Duration("refresh", time.Minute, "refresh interval")
)

var (
	tracks  Tracks
	current Window
	logger  *log.Logger
	zzz     bool
)

func pkgpath(p string) string {
	pkg, err := build.Import(p, "", build.FindOnly)
	if err != nil {
		log.Fatal(err)
	}
	return pkg.Dir
}

func (t Track) String() string {
	return fmt.Sprintf("%s %s",
		t.Seen.Format("2006/01/02 15:04:05"), t.Spent)
}

func (w Window) String() string {
	return fmt.Sprintf("%s %s", w.Class, w.Name)
}

func (t Tracks) Snooze(idle time.Duration) {
	if !zzz {
		logger.Println("away from keyboard, idle for", idle)
		if c, ok := t[current]; ok {
			c.Idle += idle
			t[current] = c
		}
		zzz = true
	}
}

func (t Tracks) Wakeup() {
	if zzz {
		logger.Println("back to keyboard")
		if c, ok := t[current]; ok {
			c.Seen = time.Now()
			t[current] = c
		}
		zzz = false
	}
}

func (t Tracks) Update(w Window) {
	if !zzz {
		if c, ok := t[current]; ok {
			c.Spent += time.Since(c.Seen)
			t[current] = c
		}
	}

	if _, ok := t[w]; !ok {
		t[w] = Track{}
	}

	s := t[w]
	s.Seen = time.Now()
	t[w] = s

	current = w
}

func (t Tracks) Remove(d time.Duration) {
	for k, v := range t {
		if time.Since(v.Seen) > d || v.Idle > d {
			logger.Println(v, k)
			delete(t, k)
		}
	}
}

func Load(fname string) Tracks {
	t := make(Tracks)
	dump, err := os.Open(fname)
	if err != nil {
		log.Println(err)
		return t
	}
	defer dump.Close()
	dec := gob.NewDecoder(dump)
	err = dec.Decode(&t)
	if err != nil {
		log.Println(err)
	}
	return t
}

func (t Tracks) Store(fname string) {
	tmp := fname + ".tmp"
	dump, err := os.Create(tmp)
	if err != nil {
		log.Println(err)
		return
	}
	defer dump.Close()
	enc := gob.NewEncoder(dump)
	err = enc.Encode(t)
	if err != nil {
		log.Println(err)
		os.Remove(tmp)
		return
	}
	os.Rename(tmp, fname)
}

func (t Tracks) Cleanup() {
	for {
		t.Remove(*expire)
		t.Store(dumpFileName)
		time.Sleep(*refresh)
	}
}

func main() {
	flag.Parse()

	X := Connect(*display)
	defer X.Close()

	logfile, err := os.OpenFile(logFileName,
		os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
	if err != nil {
		log.Fatal(err)
	}
	defer logfile.Close()
	logger = log.New(logfile, "", log.LstdFlags)

	tracks = Load(dumpFileName)
	defer tracks.Store(dumpFileName)

	go X.Collect(tracks, *timeout)
	go tracks.Cleanup()

	webReporter(*listen)
}
<commit_msg>Use ticker<commit_after>// Gone Time Tracker -or- Where has my time gone?
package main

import (
	"encoding/gob"
	"flag"
	"fmt"
	"go/build"
	"log"
	"os"
	"path/filepath"
	"time"
)

type Tracks map[Window]Track

type Track struct {
	Seen  time.Time
	Spent time.Duration
	Idle  time.Duration
}

var (
	goneDir      = pkgpath("github.com/dim13/gone")
	dumpFileName = filepath.Join(goneDir, "gone.gob")
	logFileName  = filepath.Join(goneDir, "gone.log")
	tmplFileName = filepath.Join(goneDir, "gone.tmpl")
)

var (
	display = flag.String("display", os.Getenv("DISPLAY"), "X11 display")
	listen  = flag.String("listen", "127.0.0.1:8001", "web reporter")
	timeout = flag.Duration("timeout", time.Minute*5, "idle timeout")
	expire  = flag.Duration("expire", time.Hour*8, "expire timeout")
	refresh = flag.Duration("refresh", time.Minute, "refresh interval")
)

var (
	tracks  Tracks
	current Window
	logger  *log.Logger
	zzz     bool
)

func pkgpath(p string) string {
	pkg, err := build.Import(p, "", build.FindOnly)
	if err != nil {
		log.Fatal(err)
	}
	return pkg.Dir
}

func (t Track) String() string {
	return fmt.Sprintf("%s %s",
		t.Seen.Format("2006/01/02 15:04:05"), t.Spent)
}

func (w Window) String() string {
	return fmt.Sprintf("%s %s", w.Class, w.Name)
}

func (t Tracks) Snooze(idle time.Duration) {
	if !zzz {
		logger.Println("away from keyboard, idle for", idle)
		if c, ok := t[current]; ok {
			c.Idle += idle
			t[current] = c
		}
		zzz = true
	}
}

func (t Tracks) Wakeup() {
	if zzz {
		logger.Println("back to keyboard")
		if c, ok := t[current]; ok {
			c.Seen = time.Now()
			t[current] = c
		}
		zzz = false
	}
}

func (t Tracks) Update(w Window) {
	if !zzz {
		if c, ok := t[current]; ok {
			c.Spent += time.Since(c.Seen)
			t[current] = c
		}
	}

	if _, ok := t[w]; !ok {
		t[w] = Track{}
	}

	s := t[w]
	s.Seen = time.Now()
	t[w] = s

	current = w
}

func (t Tracks) Remove(d time.Duration) {
	for k, v := range t {
		if time.Since(v.Seen) > d || v.Idle > d {
			logger.Println(v, k)
			delete(t, k)
		}
	}
}

func Load(fname string) Tracks {
	t := make(Tracks)
	dump, err := os.Open(fname)
	if err != nil {
		log.Println(err)
		return t
	}
	defer dump.Close()
	dec := gob.NewDecoder(dump)
	err = dec.Decode(&t)
	if err != nil {
		log.Println(err)
	}
	return t
}

func (t Tracks) Store(fname string) {
	tmp := fname + ".tmp"
	dump, err := os.Create(tmp)
	if err != nil {
		log.Println(err)
		return
	}
	defer dump.Close()
	enc := gob.NewEncoder(dump)
	err = enc.Encode(t)
	if err != nil {
		log.Println(err)
		os.Remove(tmp)
		return
	}
	os.Rename(tmp, fname)
}

func (t Tracks) Cleanup() {
	tick := time.NewTicker(*refresh)
	defer tick.Stop()
	for range tick.C {
		t.Remove(*expire)
		t.Store(dumpFileName)
	}
}

func main() {
	flag.Parse()

	X := Connect(*display)
	defer X.Close()

	logfile, err := os.OpenFile(logFileName,
		os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
	if err != nil {
		log.Fatal(err)
	}
	defer logfile.Close()
	logger = log.New(logfile, "", log.LstdFlags)

	tracks = Load(dumpFileName)
	defer tracks.Store(dumpFileName)

	go X.Collect(tracks, *timeout)
	go tracks.Cleanup()

	webReporter(*listen)
}
<|endoftext|>


<commit_before>// The algorithm implemented here is based on "An O(NP) Sequence Comparison Algorithm"
// by described by Sun Wu, Udi Manber and Gene Myers

package gonp

import (
	"container/list"
	"fmt"
	"math"
	"unicode/utf8"
)

const (
	Delete SesType = iota
	Common
	Add
)

type SesType int

type Point struct {
	x, y, k int
}

type SesElem struct {
	c rune
	t SesType
}

type Diff struct {
	a    []rune
	b    []rune
	m, n int
	ed   int
	ctx  *Ctx
	lcs  *list.List
	ses  *list.List
}

type Ctx struct {
	reverse  bool
	path     []int
	onlyEd   bool
	pathposi map[int]Point
}

func max(x, y int) int {
	return int(math.Max(float64(x), float64(y)))
}

func New(a string, b string) *Diff {
	m, n := utf8.RuneCountInString(a), utf8.RuneCountInString(b)
	diff := new(Diff)
	ctx := new(Ctx)
	if m >= n {
		diff.a, diff.b = []rune(b), []rune(a)
		diff.m, diff.n = n, m
		ctx.reverse = true
	} else {
		diff.a, diff.b = []rune(a), []rune(b)
		diff.m, diff.n = m, n
		ctx.reverse = false
	}
	ctx.onlyEd = false
	diff.ctx = ctx
	return diff
}

func (diff *Diff) OnlyEd() {
	diff.ctx.onlyEd = true
}

func (diff *Diff) Editdistance() int {
	return diff.ed
}

func (diff *Diff) Lcs() string {
	var b = make([]rune, diff.lcs.Len())
	for i, e := 0, diff.lcs.Front(); e != nil; i, e = i+1, e.Next() {
		b[i] = e.Value.(rune)
	}
	return string(b)
}

func (diff *Diff) Ses() []SesElem {
	seq := make([]SesElem, diff.ses.Len())
	for i, e := 0, diff.ses.Front(); e != nil; i, e = i+1, e.Next() {
		seq[i].c = e.Value.(SesElem).c
		seq[i].t = e.Value.(SesElem).t
	}
	return seq
}

func (diff *Diff) PrintSes() {
	for _, e := 0, diff.ses.Front(); e != nil; e = e.Next() {
		ee := e.Value.(SesElem)
		switch ee.t {
		case Delete:
			fmt.Println("- " + string(ee.c))
		case Add:
			fmt.Println("+ " + string(ee.c))
		case Common:
			fmt.Println("  " + string(ee.c))
		}
	}
}

func (diff *Diff) Compose() {
	offset := diff.m + 1
	delta := diff.n - diff.m
	size := diff.m + diff.n + 3
	fp := make([]int, size)
	diff.ctx.path = make([]int, size)
	diff.ctx.pathposi = make(map[int]Point)
	diff.lcs = list.New()
	diff.ses = list.New()

	for i := range fp {
		fp[i] = -1
		diff.ctx.path[i] = -1
	}

	for p := 0; ; p++ {

		for k := -p; k <= delta-1; k++ {
			fp[k+offset] = diff.snake(k, fp[k-1+offset]+1, fp[k+1+offset], offset)
		}

		for k := delta + p; k >= delta+1; k-- {
			fp[k+offset] = diff.snake(k, fp[k-1+offset]+1, fp[k+1+offset], offset)
		}

		fp[delta+offset] = diff.snake(delta, fp[delta-1+offset]+1, fp[delta+1+offset], offset)

		if fp[delta+offset] >= diff.n {
			diff.ed = delta + 2*p
			break
		}
	}

	if diff.ctx.onlyEd {
		return
	}

	r := diff.ctx.path[delta+offset]
	epc := make(map[int]Point)
	for r != -1 {
		epc[len(epc)] = Point{x: diff.ctx.pathposi[r].x, y: diff.ctx.pathposi[r].y, k: -1}
		r = diff.ctx.pathposi[r].k
	}
	diff.recordSeq(epc)
}

func (diff *Diff) recordSeq(epc map[int]Point) {
	x_idx, y_idx := 1, 1
	px_idx, py_idx := 0, 0
	ctx := diff.ctx
	for i := len(epc) - 1; i >= 0; i-- {
		for (px_idx < epc[i].x) || (py_idx < epc[i].y) {
			var t SesType
			if (epc[i].y - epc[i].x) > (py_idx - px_idx) {
				elem := diff.b[py_idx]
				if ctx.reverse {
					t = Delete
				} else {
					t = Add
				}
				diff.ses.PushBack(SesElem{c: elem, t: t})
				y_idx++
				py_idx++
			} else if epc[i].y-epc[i].x < py_idx-px_idx {
				elem := diff.a[px_idx]
				if ctx.reverse {
					t = Add
				} else {
					t = Delete
				}
				diff.ses.PushBack(SesElem{c: elem, t: t})
				x_idx++
				px_idx++
			} else {
				elem := diff.a[px_idx]
				t = Common
				diff.lcs.PushBack(elem)
				diff.ses.PushBack(SesElem{c: elem, t: t})
				x_idx++
				y_idx++
				px_idx++
				py_idx++
			}
		}
	}
}

func (diff *Diff) snake(k, p, pp, offset int) int {
	r := 0
	if p > pp {
		r = diff.ctx.path[k-1+offset]
	} else {
		r = diff.ctx.path[k+1+offset]
	}

	y := max(p, pp)
	x := y - k

	for x < diff.m && y < diff.n && diff.a[x] == diff.b[y] {
		x++
		y++
	}

	if !diff.ctx.onlyEd {
		diff.ctx.path[k+offset] = len(diff.ctx.pathposi)
		diff.ctx.pathposi[len(diff.ctx.pathposi)] = Point{x: x, y: y, k: r}
	}

	return y
}
<commit_msg>restruct diff.<commit_after>// The algorithm implemented here is based on "An O(NP) Sequence Comparison Algorithm"
// by described by Sun Wu, Udi Manber and Gene Myers

package gonp

import (
	"container/list"
	"fmt"
	"math"
	"unicode/utf8"
)

const (
	Delete SesType = iota
	Common
	Add
)

type SesType int

type Point struct {
	x, y, k int
}

type SesElem struct {
	c rune
	t SesType
}

type Diff struct {
	a    []rune
	b    []rune
	m, n int
	ed   int
	ctx  Ctx
	lcs  *list.List
	ses  *list.List
}

type Ctx struct {
	reverse  bool
	path     []int
	onlyEd   bool
	pathposi map[int]Point
}

func max(x, y int) int {
	return int(math.Max(float64(x), float64(y)))
}

func New(a string, b string) *Diff {
	m, n := utf8.RuneCountInString(a), utf8.RuneCountInString(b)
	diff := new(Diff)
	if m >= n {
		diff.a, diff.b = []rune(b), []rune(a)
		diff.m, diff.n = n, m
		diff.ctx.reverse = true
	} else {
		diff.a, diff.b = []rune(a), []rune(b)
		diff.m, diff.n = m, n
		diff.ctx.reverse = false
	}
	diff.ctx.onlyEd = false
	return diff
}

func (diff *Diff) OnlyEd() {
	diff.ctx.onlyEd = true
}

func (diff *Diff) Editdistance() int {
	return diff.ed
}

func (diff *Diff) Lcs() string {
	var b = make([]rune, diff.lcs.Len())
	for i, e := 0, diff.lcs.Front(); e != nil; i, e = i+1, e.Next() {
		b[i] = e.Value.(rune)
	}
	return string(b)
}

func (diff *Diff) Ses() []SesElem {
	seq := make([]SesElem, diff.ses.Len())
	for i, e := 0, diff.ses.Front(); e != nil; i, e = i+1, e.Next() {
		seq[i].c = e.Value.(SesElem).c
		seq[i].t = e.Value.(SesElem).t
	}
	return seq
}

func (diff *Diff) PrintSes() {
	for _, e := 0, diff.ses.Front(); e != nil; e = e.Next() {
		ee := e.Value.(SesElem)
		switch ee.t {
		case Delete:
			fmt.Println("- " + string(ee.c))
		case Add:
			fmt.Println("+ " + string(ee.c))
		case Common:
			fmt.Println("  " + string(ee.c))
		}
	}
}

func (diff *Diff) Compose() {
	offset := diff.m + 1
	delta := diff.n - diff.m
	size := diff.m + diff.n + 3
	fp := make([]int, size)
	diff.ctx.path = make([]int, size)
	diff.ctx.pathposi = make(map[int]Point)
	diff.lcs = list.New()
	diff.ses = list.New()

	for i := range fp {
		fp[i] = -1
		diff.ctx.path[i] = -1
	}

	for p := 0; ; p++ {

		for k := -p; k <= delta-1; k++ {
			fp[k+offset] = diff.snake(k, fp[k-1+offset]+1, fp[k+1+offset], offset)
		}

		for k := delta + p; k >= delta+1; k-- {
			fp[k+offset] = diff.snake(k, fp[k-1+offset]+1, fp[k+1+offset], offset)
		}

		fp[delta+offset] = diff.snake(delta, fp[delta-1+offset]+1, fp[delta+1+offset], offset)

		if fp[delta+offset] >= diff.n {
			diff.ed = delta + 2*p
			break
		}
	}

	if diff.ctx.onlyEd {
		return
	}

	r := diff.ctx.path[delta+offset]
	epc := make(map[int]Point)
	for r != -1 {
		epc[len(epc)] = Point{x: diff.ctx.pathposi[r].x, y: diff.ctx.pathposi[r].y, k: -1}
		r = diff.ctx.pathposi[r].k
	}
	diff.recordSeq(epc)
}

func (diff *Diff) recordSeq(epc map[int]Point) {
	x_idx, y_idx := 1, 1
	px_idx, py_idx := 0, 0
	for i := len(epc) - 1; i >= 0; i-- {
		for (px_idx < epc[i].x) || (py_idx < epc[i].y) {
			var t SesType
			if (epc[i].y - epc[i].x) > (py_idx - px_idx) {
				elem := diff.b[py_idx]
				if diff.ctx.reverse {
					t = Delete
				} else {
					t = Add
				}
				diff.ses.PushBack(SesElem{c: elem, t: t})
				y_idx++
				py_idx++
			} else if epc[i].y-epc[i].x < py_idx-px_idx {
				elem := diff.a[px_idx]
				if diff.ctx.reverse {
					t = Add
				} else {
					t = Delete
				}
				diff.ses.PushBack(SesElem{c: elem, t: t})
				x_idx++
				px_idx++
			} else {
				elem := diff.a[px_idx]
				t = Common
				diff.lcs.PushBack(elem)
				diff.ses.PushBack(SesElem{c: elem, t: t})
				x_idx++
				y_idx++
				px_idx++
				py_idx++
			}
		}
	}
}

func (diff *Diff) snake(k, p, pp, offset int) int {
	r := 0
	if p > pp {
		r = diff.ctx.path[k-1+offset]
	} else {
		r = diff.ctx.path[k+1+offset]
	}

	y := max(p, pp)
	x := y - k

	for x < diff.m && y < diff.n && diff.a[x] == diff.b[y] {
		x++
		y++
	}

	if !diff.ctx.onlyEd {
		diff.ctx.path[k+offset] = len(diff.ctx.pathposi)
		diff.ctx.pathposi[len(diff.ctx.pathposi)] = Point{x: x, y: y, k: r}
	}

	return y
}
<|endoftext|>


<commit_before>package taplink

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha512"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"sync"
	"time"
)

var (
	// ensures the Client implements the API interface
	_ API = (*Client)(nil)

	// DefaultTimeout is the default HTTP request timeout
	DefaultTimeout = 30 * time.Second
	// DefaultKeepAlive is the default HTTP keep-alive duration
	DefaultKeepAlive = 30 * time.Second

	// RetryLimit indicates how many times a connection should be retried before failing
	RetryLimit = 3
	// RetryDelay is the duration to wait between retry attempts
	RetryDelay = 1 * time.Second
)

// API is an interface which exposes TapLink API functionality
type API interface {

	// Config
	Config() Configuration

	// API funcs
	VerifyPassword(hash []byte, expectedHash []byte, versionID int64) (*VerifyPassword, error)
	NewPassword(hash []byte) (*NewPassword, error)

	// Requests returns the total number of HTTP requests made to the TapLink API, including those with errors and those without
	Requests() int64

	// Errors returns the total number of HTTP requests made to the TapLink API which ended in error
	Errors() int64

	// Latency returns the average latency of requests made to the TapLink API
	Latency() time.Duration

	// ErrorPct returns the pct of requests made to the TapLink API which ended in error.
	ErrorPct() int64

	// EnableStats starts the collection of stats regarding HTTP requests made to the TapLink API
	EnableStats()

	// DisableStats starts the collection of stats regarding HTTP requests made to the TapLink API
	DisableStats()
}

// Client is a struct which implements the API interface
type Client struct {
	cfg             Configuration
	reqCt, reqErrCt int64
	reqLatency      []time.Duration
	stats           bool

	sync.RWMutex
}

type saltResponse struct {
	Salt2Hex     string `json:"s2"`
	VersionID    int64  `json:"vid"`
	NewSalt2Hex  string `json:"new_s2"`
	NewVersionID int64  `json:"new_vid"`
}

// Version is a version number for the TapLink API
type Version int64

// String implements fmt.Stringer interface. If the version is empty, the API expects "" so this return it that way
func (v Version) String() string {
	if v == 0 {
		return fmt.Sprintf("")
	}
	return fmt.Sprintf("%d", v)
}

// Salt contains a salt for the current version, and NewSalt if a new version is available
type Salt struct {
	Salt []byte
	// VersionID is the version ID used in the request
	VersionID int64 `json:"-"`
	// NewVersionID is the new version ID to use, if any.
	NewVersionID int64 `json:"vid"`
	// NewSalt is the new salt to use if newer data pool settings are available
	NewSalt []byte `json:"-"`
}

func (s Salt) String() string {
	return hex.EncodeToString(s.Salt)
}

// VerifyPassword provides information about whether a password matched and related hashes
type VerifyPassword struct {
	Matched      bool
	VersionID    int64
	NewVersionID int64
	Hash         []byte
	NewHash      []byte
}

// String returns the hex-encoded value of the password hash
func (v VerifyPassword) String() string {
	return hex.EncodeToString(v.Hash)
}

// NewPassword returns a new password hash and the version it was created with
type NewPassword struct {
	Hash      []byte
	VersionID int64
}

// String returns the hex-encoded value of the password hash
func (p NewPassword) String() string {
	return hex.EncodeToString(p.Hash)
}

// New returns a new TapLink API connection
func New(appID string) API {
	cfg := &Config{
		appID: appID,
		host:  "https://api.taplink.co",
		headers: map[string]string{
			"User-Agent": userAgent,
			"Accept":     "application/json",
		},
	}
	return &Client{cfg: cfg}
}

// Config returns the current client configuration
func (c *Client) Config() Configuration {
	return c.cfg
}

// VerifyPassword verifies a password for an existing user which was stored using blind hashing.
// 'hash'         - hash of the user's password
// 'expected' - expected value of hash2
// 'versionId'        - version identifier for data pool settings to use
// If a new 'versionId' and 'hash2' value are returned, they can either be ignored, or both must be updated in the data store together which
// will cause the latest data pool settings to be used when blind hashing for this user in the future.
// If the versionID is 0, the default version will be used
func (c *Client) VerifyPassword(hash []byte, expected []byte, versionID int64) (*VerifyPassword, error) {
	salt, err := c.getSalt(hash, versionID)
	if err != nil {
		return nil, err
	}
	sum := hmac.New(sha512.New, salt.Salt)
	sum.Write(hash)
	vp := &VerifyPassword{Hash: sum.Sum(nil), NewVersionID: salt.NewVersionID, VersionID: salt.VersionID}
	vp.Matched = bytes.Equal(vp.Hash, expected)
	if vp.Matched && salt.VersionID != salt.NewVersionID && salt.NewSalt != nil {
		sum2 := hmac.New(sha512.New, salt.NewSalt)
		sum2.Write(hash)
		vp.NewHash = sum2.Sum(nil)
	}
	return vp, nil
}

// NewPassword calculates 'salt1' and 'hash2' for a new password, using the latest data pool settings.
// Also returns 'versionId' for the current settings, in case data pool settings are updated in the future
// Inputs:
//   'hash1Hex' - hash of the user's password, as a hex string
//   'callback' - function(err, hash2Hex, versionId)
//       o err       : 'err' from request, or null if request succeeded
//       o hash2Hex  : value of 'hash2' as a hex string
//       o versionId : version id of the current data pool settings used for this request
func (c *Client) NewPassword(hash1 []byte) (*NewPassword, error) {
	salt, err := c.getSalt(hash1, 0)
	if err != nil {
		return nil, err
	}

	// Calculate the hash of the new salt
	sum := hmac.New(sha512.New, salt.Salt)
	sum.Write(hash1)

	return &NewPassword{VersionID: salt.VersionID, Hash: sum.Sum(nil)}, nil
}

// GetSalt retreives a salt value from the data pool, given a 'hash1' value and optionally, a version id
// If requested versionId is undefined or the latest, then only a single 'salt2' value is returned with the same version id as requested
// If the requested versionId is not the latest, also returns an additional 'salt2' value along with the latest version id
// Inputs:
//    'hash1Hex'  - hex string containing value of hash1
//    'versionId' - version identifier for data pool settings to use, or 0/null/undefined to use latest settings
//    'callback'  - function(salt2Hex, versionId, newSalt2Hex, newVersionId)
//       o salt2Hex     : hex string containing value of 'salt2'
//       o versionId    : version id corresponding to the provided 'salt2Hex' value (will always match requested version, if one was specified)
//       o newSalt2Hex  : hex string containing a new value of 'salt2' if newer data pool settings are available, otherwise undefined
//       o newVersionId : a new version id, if newer data pool settings are available, otherwise undefined
func (c *Client) getSalt(hash []byte, versionID int64) (s *Salt, err error) {

	uri := fmt.Sprintf("%s/%s/%s/%s", c.Config().Host(), c.Config().AppID(), hex.EncodeToString(hash), Version(versionID))
	req, err := http.NewRequest("GET", uri, nil)
	if err != nil {
		return
	}

	for k, v := range c.Config().Headers() {
		req.Header.Set(k, v)
	}

	var t time.Time
	var attempts int
	var resp *http.Response

	// Attempt to connect until the attempt limit has been reached.
	// Reset the timer in each loop so the final result will have the proper
	// latency value
	for {
		t = time.Now()
		resp, err = HTTPClient.Do(req)
		if err == nil || attempts > RetryLimit {
			break
		}
		c.incrErrs(0)
		attempts++
		time.Sleep(RetryDelay)
	}

	// If failed to send the request.
	if err != nil {
		return
	}

	latency := time.Since(t)

	// Update stats regardless of what happens from here on out.
	defer func() {
		if err != nil {
			c.incrErrs(latency)
			return
		}
		c.incrSuccess(latency)
	}()

	// If request error, fail now.
	if err != nil {
		return
	}

	defer resp.Body.Close()
	bodyBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}

	// If not a 200 request, return the status text as the error message
	if resp.StatusCode != http.StatusOK {
		err = errors.New(strings.TrimSpace(string(bodyBytes)))
		return
	}

	var sr saltResponse
	err = json.Unmarshal(bodyBytes, &sr)
	if err != nil {
		return
	}

	// Use the values from the request in the return value
	s = &Salt{NewVersionID: sr.NewVersionID, VersionID: sr.VersionID}

	// Hex encoding is used over the wire, so decode here.
	s.Salt, err = hex.DecodeString(sr.Salt2Hex)
	if err != nil {
		return
	}

	if sr.NewSalt2Hex == "" {
		return
	}

	s.NewSalt, err = hex.DecodeString(sr.NewSalt2Hex)
	return
}

func (c *Client) incrErrs(latency time.Duration) {
	if !c.stats {
		return
	}
	c.Lock()
	c.reqErrCt++
	if latency != 0 {
		c.reqLatency = append(c.reqLatency, latency)
	}
	c.Unlock()
}

func (c *Client) incrSuccess(latency time.Duration) {
	if !c.stats {
		return
	}
	c.Lock()
	c.reqCt++
	c.reqLatency = append(c.reqLatency, latency)
	c.Unlock()
}
<commit_msg>Adds panic for unencrypted responses<commit_after>package taplink

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha512"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"sync"
	"time"
)

var (
	// ensures the Client implements the API interface
	_ API = (*Client)(nil)

	// DefaultTimeout is the default HTTP request timeout
	DefaultTimeout = 30 * time.Second
	// DefaultKeepAlive is the default HTTP keep-alive duration
	DefaultKeepAlive = 30 * time.Second

	// RetryLimit indicates how many times a connection should be retried before failing
	RetryLimit = 3
	// RetryDelay is the duration to wait between retry attempts
	RetryDelay = 1 * time.Second
)

// API is an interface which exposes TapLink API functionality
type API interface {

	// Config
	Config() Configuration

	// API funcs
	VerifyPassword(hash []byte, expectedHash []byte, versionID int64) (*VerifyPassword, error)
	NewPassword(hash []byte) (*NewPassword, error)

	// Requests returns the total number of HTTP requests made to the TapLink API, including those with errors and those without
	Requests() int64

	// Errors returns the total number of HTTP requests made to the TapLink API which ended in error
	Errors() int64

	// Latency returns the average latency of requests made to the TapLink API
	Latency() time.Duration

	// ErrorPct returns the pct of requests made to the TapLink API which ended in error.
	ErrorPct() int64

	// EnableStats starts the collection of stats regarding HTTP requests made to the TapLink API
	EnableStats()

	// DisableStats starts the collection of stats regarding HTTP requests made to the TapLink API
	DisableStats()
}

// Client is a struct which implements the API interface
type Client struct {
	cfg             Configuration
	reqCt, reqErrCt int64
	reqLatency      []time.Duration
	stats           bool

	sync.RWMutex
}

type saltResponse struct {
	Salt2Hex     string `json:"s2"`
	VersionID    int64  `json:"vid"`
	NewSalt2Hex  string `json:"new_s2"`
	NewVersionID int64  `json:"new_vid"`
}

// Version is a version number for the TapLink API
type Version int64

// String implements fmt.Stringer interface. If the version is empty, the API expects "" so this return it that way
func (v Version) String() string {
	if v == 0 {
		return fmt.Sprintf("")
	}
	return fmt.Sprintf("%d", v)
}

// Salt contains a salt for the current version, and NewSalt if a new version is available
type Salt struct {
	Salt []byte
	// VersionID is the version ID used in the request
	VersionID int64 `json:"-"`
	// NewVersionID is the new version ID to use, if any.
	NewVersionID int64 `json:"vid"`
	// NewSalt is the new salt to use if newer data pool settings are available
	NewSalt []byte `json:"-"`
}

func (s Salt) String() string {
	return hex.EncodeToString(s.Salt)
}

// VerifyPassword provides information about whether a password matched and related hashes
type VerifyPassword struct {
	Matched      bool
	VersionID    int64
	NewVersionID int64
	Hash         []byte
	NewHash      []byte
}

// String returns the hex-encoded value of the password hash
func (v VerifyPassword) String() string {
	return hex.EncodeToString(v.Hash)
}

// NewPassword returns a new password hash and the version it was created with
type NewPassword struct {
	Hash      []byte
	VersionID int64
}

// String returns the hex-encoded value of the password hash
func (p NewPassword) String() string {
	return hex.EncodeToString(p.Hash)
}

// New returns a new TapLink API connection
func New(appID string) API {
	cfg := &Config{
		appID: appID,
		host:  "https://api.taplink.co",
		headers: map[string]string{
			"User-Agent": userAgent,
			"Accept":     "application/json",
		},
	}
	return &Client{cfg: cfg}
}

// Config returns the current client configuration
func (c *Client) Config() Configuration {
	return c.cfg
}

// VerifyPassword verifies a password for an existing user which was stored using blind hashing.
// 'hash'         - hash of the user's password
// 'expected' - expected value of hash2
// 'versionId'        - version identifier for data pool settings to use
// If a new 'versionId' and 'hash2' value are returned, they can either be ignored, or both must be updated in the data store together which
// will cause the latest data pool settings to be used when blind hashing for this user in the future.
// If the versionID is 0, the default version will be used
func (c *Client) VerifyPassword(hash []byte, expected []byte, versionID int64) (*VerifyPassword, error) {
	salt, err := c.getSalt(hash, versionID)
	if err != nil {
		return nil, err
	}
	sum := hmac.New(sha512.New, salt.Salt)
	sum.Write(hash)
	vp := &VerifyPassword{Hash: sum.Sum(nil), NewVersionID: salt.NewVersionID, VersionID: salt.VersionID}
	vp.Matched = bytes.Equal(vp.Hash, expected)
	if vp.Matched && salt.VersionID != salt.NewVersionID && salt.NewSalt != nil {
		sum2 := hmac.New(sha512.New, salt.NewSalt)
		sum2.Write(hash)
		vp.NewHash = sum2.Sum(nil)
	}
	return vp, nil
}

// NewPassword calculates 'salt1' and 'hash2' for a new password, using the latest data pool settings.
// Also returns 'versionId' for the current settings, in case data pool settings are updated in the future
// Inputs:
//   'hash1Hex' - hash of the user's password, as a hex string
//   'callback' - function(err, hash2Hex, versionId)
//       o err       : 'err' from request, or null if request succeeded
//       o hash2Hex  : value of 'hash2' as a hex string
//       o versionId : version id of the current data pool settings used for this request
func (c *Client) NewPassword(hash1 []byte) (*NewPassword, error) {
	salt, err := c.getSalt(hash1, 0)
	if err != nil {
		return nil, err
	}

	// Calculate the hash of the new salt
	sum := hmac.New(sha512.New, salt.Salt)
	sum.Write(hash1)

	return &NewPassword{VersionID: salt.VersionID, Hash: sum.Sum(nil)}, nil
}

// GetSalt retreives a salt value from the data pool, given a 'hash1' value and optionally, a version id
// If requested versionId is undefined or the latest, then only a single 'salt2' value is returned with the same version id as requested
// If the requested versionId is not the latest, also returns an additional 'salt2' value along with the latest version id
// Inputs:
//    'hash1Hex'  - hex string containing value of hash1
//    'versionId' - version identifier for data pool settings to use, or 0/null/undefined to use latest settings
//    'callback'  - function(salt2Hex, versionId, newSalt2Hex, newVersionId)
//       o salt2Hex     : hex string containing value of 'salt2'
//       o versionId    : version id corresponding to the provided 'salt2Hex' value (will always match requested version, if one was specified)
//       o newSalt2Hex  : hex string containing a new value of 'salt2' if newer data pool settings are available, otherwise undefined
//       o newVersionId : a new version id, if newer data pool settings are available, otherwise undefined
func (c *Client) getSalt(hash []byte, versionID int64) (s *Salt, err error) {

	uri := fmt.Sprintf("%s/%s/%s/%s", c.Config().Host(), c.Config().AppID(), hex.EncodeToString(hash), Version(versionID))
	req, err := http.NewRequest("GET", uri, nil)
	if err != nil {
		return
	}

	for k, v := range c.Config().Headers() {
		req.Header.Set(k, v)
	}

	var t time.Time
	var attempts int
	var resp *http.Response

	// Attempt to connect until the attempt limit has been reached.
	// Reset the timer in each loop so the final result will have the proper
	// latency value
	for {
		t = time.Now()
		resp, err = HTTPClient.Do(req)
		if err == nil || attempts > RetryLimit {
			break
		}
		if resp.TLS == nil {
			panic("Unencrypted response")
		}
		c.incrErrs(0)
		attempts++
		time.Sleep(RetryDelay)
	}

	// If failed to send the request.
	if err != nil {
		return
	}

	latency := time.Since(t)

	// Update stats regardless of what happens from here on out.
	defer func() {
		if err != nil {
			c.incrErrs(latency)
			return
		}
		c.incrSuccess(latency)
	}()

	// If request error, fail now.
	if err != nil {
		return
	}

	defer resp.Body.Close()
	bodyBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return
	}

	// If not a 200 request, return the status text as the error message
	if resp.StatusCode != http.StatusOK {
		err = errors.New(strings.TrimSpace(string(bodyBytes)))
		return
	}

	var sr saltResponse
	err = json.Unmarshal(bodyBytes, &sr)
	if err != nil {
		return
	}

	// Use the values from the request in the return value
	s = &Salt{NewVersionID: sr.NewVersionID, VersionID: sr.VersionID}

	// Hex encoding is used over the wire, so decode here.
	s.Salt, err = hex.DecodeString(sr.Salt2Hex)
	if err != nil {
		return
	}

	if sr.NewSalt2Hex == "" {
		return
	}

	s.NewSalt, err = hex.DecodeString(sr.NewSalt2Hex)
	return
}

func (c *Client) incrErrs(latency time.Duration) {
	if !c.stats {
		return
	}
	c.Lock()
	c.reqErrCt++
	if latency != 0 {
		c.reqLatency = append(c.reqLatency, latency)
	}
	c.Unlock()
}

func (c *Client) incrSuccess(latency time.Duration) {
	if !c.stats {
		return
	}
	c.Lock()
	c.reqCt++
	c.reqLatency = append(c.reqLatency, latency)
	c.Unlock()
}
<|endoftext|>