<commit_before>package poeditor

import (
	"encoding/json"
	"io"
	"net/http"
	"strconv"
	"time"
)

// Project represents a POEditor project
type Project struct {
	POEditor          *POEditor
	ID                int
	Name              string
	Description       string
	Public            int
	Open              int
	ReferenceLanguage string
	Terms             int
	Created           time.Time
}

// ListProjects lists all the projects that are accessable by the used APIKey
func (poe *POEditor) ListProjects() ([]Project, error) {
	res := projectsResult{}
	err := poe.post("/projects/list", nil, nil, &res)
	if err != nil {
		return []Project{}, err
	}
	ps := make([]Project, len(res.Projects))
	for i, p := range res.Projects {
		ps[i] = Project{POEditor: poe, ID: p.ID}
	}
	return ps, nil
}

// ViewProject returns project with given ID
func (poe *POEditor) ViewProject(id int) (*Project, error) {
	res := projectResult{}
	err := poe.post("/projects/view", map[string]string{"id": strconv.Itoa(id)}, nil, &res)
	if err != nil {
		return nil, err
	}
	return res.Project.toProject(poe), nil
}

// AddProject creates a new project with the given name and description
func (poe *POEditor) AddProject(name, description string) (*Project, error) {
	res := projectResult{}
	err := poe.post("/projects/add", map[string]string{
		"name":        name,
		"description": description,
	}, nil, &res)
	if err != nil {
		return nil, err
	}
	return res.Project.toProject(poe), nil
}

/*
Update updates the project according to the map[string]string.

		...
		p, err := p.Update(map[string]string{
			"name": "a project name",
			"description": "a project description"
			"reference_language": "a reference language code"
		})

Omitted key value pairs are not updated. Only `name`, `description` and
`reference_language` can be updated.
*/
func (p *Project) Update(props map[string]string) (*Project, error) {
	res := projectResult{}
	fields := make(map[string]string)
	for k, v := range props {
		if k == "name" || k == "description" || k == "reference_language" {
			fields[k] = v
		} else {
			return nil, ErrorUpdateFields
		}
	}
	err := p.post("/projects/update", fields, nil, &res)
	if err != nil {
		return nil, err
	}
	return res.Project.toProject(p.POEditor), nil
}

// Delete does its thing
func (p *Project) Delete() error {
	return p.post("/projects/delete", nil, nil, nil)
}

// Upload uploads terms
func (p *Project) Upload(reader io.Reader, options UploadOptions) (UploadResult, error) {
	var (
		res    UploadResult
		fields map[string]string
	)
	var validUpdating = func() bool {
		u := options.Updating
		return u == UploadTerms || u == UploadTranslations || u == UploadTermsTranslations
	}
	if !validUpdating() {
		return UploadResult{}, ErrorUploadUpdating
	}
	if options.Updating != UploadTerms && options.Language.Code == "" {
		return UploadResult{}, ErrorUploadLanguage
	}
	fields["language"] = options.Language.Code
	if options.Overwrite {
		fields["overwrite"] = "1"
	}
	if options.SyncTerms {
		fields["sync_terms"] = "1"
	}
	if len(options.Tags) > 0 {
		jsonTags, err := json.Marshal(options.Tags)
		if err != nil {
			return UploadResult{}, err
		}
		fields["tags"] = string(jsonTags)
	}
	if options.ReadFromSource {
		fields["read_from_source"] = "1"
	}
	if options.FuzzyTrigger {
		fields["fuzzy_trigger"] = "1"
	}
	err := p.post("/projects/upload", fields, map[string]io.Reader{"file": reader}, &res)
	if err != nil {
		return UploadResult{}, err
	}
	return res, nil
}

// Sync syncs project terms with the given list of terms.
//
// CAUTION: this is a destructive operation. Any term not found in the input
// array will be deleted from the project.
func (p *Project) Sync(terms []Term) (CountResult, error) {
	jsonTerms, err := json.Marshal(terms)
	if err != nil {
		return CountResult{}, err
	}
	var res termsCountResult
	err = p.post("/projects/sync", map[string]string{"data": string(jsonTerms)}, nil, &res)
	if err != nil {
		return CountResult{}, err
	}
	return res.Terms, nil
}

// Export extracts the language in the given fileformat. For available file
// formats, see the FileFormat constants. Terms can be filtered using the
// Filter constants. Terms can also be filtered by tags.
func (l Language) Export(fileFormat string, filters []string, tags []string, dest io.Writer) error {
	fields := map[string]string{"type": fileFormat}
	if len(filters) > 0 {
		jsonFilters, err := json.Marshal(filters)
		if err != nil {
			return err
		}
		fields["filters"] = string(jsonFilters)
	}
	if len(tags) > 0 {
		jsonTags, err := json.Marshal(tags)
		if err != nil {
			return err
		}
		fields["tags"] = string(jsonTags)
	}
	exportRes := exportResult{}
	err := l.post("/projects/export", fields, nil, &exportRes)
	if err != nil {
		return err
	}
	export, err := http.Get(exportRes.URL)
	if err != nil {
		return err
	}
	_, err = io.Copy(dest, export.Body)
	return err
}

const (
	// UploadTerms is a valid value of UploadOptions.Updating
	UploadTerms = "terms"
	// UploadTermsTranslations is a valid value of UploadOptions.Updating
	UploadTermsTranslations = "terms_translations"
	// UploadTranslations is a valid value of UploadOptions.Updating
	UploadTranslations = "translations"
)

// UploadOptions specifies options for upload of a file
type UploadOptions struct {
	Updating       string
	Language       Language
	Overwrite      bool
	SyncTerms      bool
	Tags           []string
	ReadFromSource bool
	FuzzyTrigger   bool
}

// CountResult is a part of UploadResult and returned directly from
// Project.Sync. It shows counts for uploaded/synced terms and translations
type CountResult struct {
	Parsed           int `json:"parsed"`
	Added            int `json:"added"`
	Deleted          int `json:"deleted"`
	WithAddedComment int `json:"with_added_comment"`
}

// UploadResult is returned when uploading a file
type UploadResult struct {
	Terms        CountResult `json:"terms"`
	Translations CountResult `json:"translations"`
}

const (
	// FileFormatPO specifies a .po file
	FileFormatPO = "po"
	// FileFormatPOT specifies a .pot file
	FileFormatPOT = "pot"
	// FileFormatMO specifies a .mo file
	FileFormatMO = "mo"
	// FileFormatXLS specifies an .xls file
	FileFormatXLS = "xls"
	// FileFormatCSV specifies a .csv file
	FileFormatCSV = "csv"
	// FileFormatRESW specifies an .resw file
	FileFormatRESW = "resw"
	// FileFormatRESX specifies an .resx file
	FileFormatRESX = "resx"
	// FileFormatAndroidStrings specifies strings should be in android format
	FileFormatAndroidStrings = "android_strings"
	// FileFormatAppleStrings specifies strings should be in apple format
	FileFormatAppleStrings = "apple_strings"
	// FileFormatXLIFF specifies an .xliff file
	FileFormatXLIFF = "xliff"
	// FileFormatProperties specifies a .propterties file
	FileFormatProperties = "properties"
	// FileFormatKeyValueJSON specifies a .json file in key value format
	FileFormatKeyValueJSON = "key_value_json"
	// FileFormatJSON specifies a .json file
	FileFormatJSON = "json"
	// FileFormatXMB specifies an .xmb file
	FileFormatXMB = "xmb"
	// FileFormatXTB specifies an .xtb file
	FileFormatXTB = "xtb"
)

const (
	// FilterTranslated filters terms in translated state
	FilterTranslated = "translated"
	// FilterUntranslated filters terms in untranslated state
	FilterUntranslated = "untranslated"
	// FilterFuzzy filters terms in fuzzy state
	FilterFuzzy = "fuzzy"
	// FilterNotFuzzy filters terms in not fuzzy state
	FilterNotFuzzy = "not_fuzzy"
	// FilterAutomatic filters terms in automatic state
	FilterAutomatic = "automatic"
	// FilterNotAutomatic filters terms in not automatic state
	FilterNotAutomatic = "not_automatic"
	// FilterProofread filters terms in proofread state
	FilterProofread = "proofread"
	// FilterNotProofread filters terms in not proofread state
	FilterNotProofread = "not_proofread"
)

func (p *Project) post(endpoint string, fields map[string]string, files map[string]io.Reader, res interface{}) error {
	if fields == nil {
		fields = make(map[string]string)
	}
	fields["id"] = strconv.Itoa(p.ID)
	return p.POEditor.post(endpoint, fields, files, res)
}

type projectsResult struct {
	Projects []project `json:"projects"`
}

type projectResult struct {
	Project project `json:"project"`
}

type project struct {
	ID                int          `json:"id"`
	Name              string       `json:"name"`
	Description       string       `json:"description"`
	Public            int          `json:"public"`
	Open              int          `json:"open"`
	ReferenceLanguage string       `json:"reference_language"`
	Terms             int          `json:"terms"`
	Created           poEditorTime `json:"created"`
}

func (p project) toProject(poe *POEditor) *Project {
	return &Project{
		POEditor:          poe,
		ID:                p.ID,
		Name:              p.Name,
		Description:       p.Description,
		Public:            p.Public,
		Open:              p.Open,
		ReferenceLanguage: p.ReferenceLanguage,
		Terms:             p.Terms,
		Created:           p.Created.Time,
	}
}

type termsCountResult struct {
	Terms CountResult `json:"terms"`
}

type exportResult struct {
	URL string `json:"url"`
}
<commit_msg>Add helper which returns the tags of a project<commit_after>package poeditor

import (
	"encoding/json"
	"io"
	"net/http"
	"strconv"
	"time"
)

// Project represents a POEditor project
type Project struct {
	POEditor          *POEditor
	ID                int
	Name              string
	Description       string
	Public            int
	Open              int
	ReferenceLanguage string
	Terms             int
	Created           time.Time
}

// ListProjects lists all the projects that are accessable by the used APIKey
func (poe *POEditor) ListProjects() ([]Project, error) {
	res := projectsResult{}
	err := poe.post("/projects/list", nil, nil, &res)
	if err != nil {
		return []Project{}, err
	}
	ps := make([]Project, len(res.Projects))
	for i, p := range res.Projects {
		ps[i] = Project{POEditor: poe, ID: p.ID}
	}
	return ps, nil
}

// ViewProject returns project with given ID
func (poe *POEditor) ViewProject(id int) (*Project, error) {
	res := projectResult{}
	err := poe.post("/projects/view", map[string]string{"id": strconv.Itoa(id)}, nil, &res)
	if err != nil {
		return nil, err
	}
	return res.Project.toProject(poe), nil
}

// AddProject creates a new project with the given name and description
func (poe *POEditor) AddProject(name, description string) (*Project, error) {
	res := projectResult{}
	err := poe.post("/projects/add", map[string]string{
		"name":        name,
		"description": description,
	}, nil, &res)
	if err != nil {
		return nil, err
	}
	return res.Project.toProject(poe), nil
}

/*
Update updates the project according to the map[string]string.

		...
		p, err := p.Update(map[string]string{
			"name": "a project name",
			"description": "a project description"
			"reference_language": "a reference language code"
		})

Omitted key value pairs are not updated. Only `name`, `description` and
`reference_language` can be updated.
*/
func (p *Project) Update(props map[string]string) (*Project, error) {
	res := projectResult{}
	fields := make(map[string]string)
	for k, v := range props {
		if k == "name" || k == "description" || k == "reference_language" {
			fields[k] = v
		} else {
			return nil, ErrorUpdateFields
		}
	}
	err := p.post("/projects/update", fields, nil, &res)
	if err != nil {
		return nil, err
	}
	return res.Project.toProject(p.POEditor), nil
}

// Delete does its thing
func (p *Project) Delete() error {
	return p.post("/projects/delete", nil, nil, nil)
}

// Upload uploads terms
func (p *Project) Upload(reader io.Reader, options UploadOptions) (UploadResult, error) {
	var (
		res    UploadResult
		fields map[string]string
	)
	var validUpdating = func() bool {
		u := options.Updating
		return u == UploadTerms || u == UploadTranslations || u == UploadTermsTranslations
	}
	if !validUpdating() {
		return UploadResult{}, ErrorUploadUpdating
	}
	if options.Updating != UploadTerms && options.Language.Code == "" {
		return UploadResult{}, ErrorUploadLanguage
	}
	fields["language"] = options.Language.Code
	if options.Overwrite {
		fields["overwrite"] = "1"
	}
	if options.SyncTerms {
		fields["sync_terms"] = "1"
	}
	if len(options.Tags) > 0 {
		jsonTags, err := json.Marshal(options.Tags)
		if err != nil {
			return UploadResult{}, err
		}
		fields["tags"] = string(jsonTags)
	}
	if options.ReadFromSource {
		fields["read_from_source"] = "1"
	}
	if options.FuzzyTrigger {
		fields["fuzzy_trigger"] = "1"
	}
	err := p.post("/projects/upload", fields, map[string]io.Reader{"file": reader}, &res)
	if err != nil {
		return UploadResult{}, err
	}
	return res, nil
}

// Sync syncs project terms with the given list of terms.
//
// CAUTION: this is a destructive operation. Any term not found in the input
// array will be deleted from the project.
func (p *Project) Sync(terms []Term) (CountResult, error) {
	jsonTerms, err := json.Marshal(terms)
	if err != nil {
		return CountResult{}, err
	}
	var res termsCountResult
	err = p.post("/projects/sync", map[string]string{"data": string(jsonTerms)}, nil, &res)
	if err != nil {
		return CountResult{}, err
	}
	return res.Terms, nil
}

// Export extracts the language in the given fileformat. For available file
// formats, see the FileFormat constants. Terms can be filtered using the
// Filter constants. Terms can also be filtered by tags.
func (l Language) Export(fileFormat string, filters []string, tags []string, dest io.Writer) error {
	fields := map[string]string{"type": fileFormat}
	if len(filters) > 0 {
		jsonFilters, err := json.Marshal(filters)
		if err != nil {
			return err
		}
		fields["filters"] = string(jsonFilters)
	}
	if len(tags) > 0 {
		jsonTags, err := json.Marshal(tags)
		if err != nil {
			return err
		}
		fields["tags"] = string(jsonTags)
	}
	exportRes := exportResult{}
	err := l.post("/projects/export", fields, nil, &exportRes)
	if err != nil {
		return err
	}
	export, err := http.Get(exportRes.URL)
	if err != nil {
		return err
	}
	_, err = io.Copy(dest, export.Body)
	return err
}

// ListTags returns a list of tags found on the project. This is not a standard
// API endpoint, but a useful helper never the less.
func (p *Project) ListTags() ([]string, error) {
	terms, err := p.ListTerms()
	if err != nil {
		return []string{}, err
	}
	tagsM := make(map[string]bool)
	for _, term := range terms {
		for _, tag := range term.Tags {
			tagsM[tag] = true
		}
	}
	tags := make([]string, len(tagsM))
	i := 0
	for tag := range tagsM {
		tags[i] = tag
	}
	return tags, nil
}

const (
	// UploadTerms is a valid value of UploadOptions.Updating
	UploadTerms = "terms"
	// UploadTermsTranslations is a valid value of UploadOptions.Updating
	UploadTermsTranslations = "terms_translations"
	// UploadTranslations is a valid value of UploadOptions.Updating
	UploadTranslations = "translations"
)

// UploadOptions specifies options for upload of a file
type UploadOptions struct {
	Updating       string
	Language       Language
	Overwrite      bool
	SyncTerms      bool
	Tags           []string
	ReadFromSource bool
	FuzzyTrigger   bool
}

// CountResult is a part of UploadResult and returned directly from
// Project.Sync. It shows counts for uploaded/synced terms and translations
type CountResult struct {
	Parsed           int `json:"parsed"`
	Added            int `json:"added"`
	Deleted          int `json:"deleted"`
	WithAddedComment int `json:"with_added_comment"`
}

// UploadResult is returned when uploading a file
type UploadResult struct {
	Terms        CountResult `json:"terms"`
	Translations CountResult `json:"translations"`
}

const (
	// FileFormatPO specifies a .po file
	FileFormatPO = "po"
	// FileFormatPOT specifies a .pot file
	FileFormatPOT = "pot"
	// FileFormatMO specifies a .mo file
	FileFormatMO = "mo"
	// FileFormatXLS specifies an .xls file
	FileFormatXLS = "xls"
	// FileFormatCSV specifies a .csv file
	FileFormatCSV = "csv"
	// FileFormatRESW specifies an .resw file
	FileFormatRESW = "resw"
	// FileFormatRESX specifies an .resx file
	FileFormatRESX = "resx"
	// FileFormatAndroidStrings specifies strings should be in android format
	FileFormatAndroidStrings = "android_strings"
	// FileFormatAppleStrings specifies strings should be in apple format
	FileFormatAppleStrings = "apple_strings"
	// FileFormatXLIFF specifies an .xliff file
	FileFormatXLIFF = "xliff"
	// FileFormatProperties specifies a .propterties file
	FileFormatProperties = "properties"
	// FileFormatKeyValueJSON specifies a .json file in key value format
	FileFormatKeyValueJSON = "key_value_json"
	// FileFormatJSON specifies a .json file
	FileFormatJSON = "json"
	// FileFormatXMB specifies an .xmb file
	FileFormatXMB = "xmb"
	// FileFormatXTB specifies an .xtb file
	FileFormatXTB = "xtb"
)

const (
	// FilterTranslated filters terms in translated state
	FilterTranslated = "translated"
	// FilterUntranslated filters terms in untranslated state
	FilterUntranslated = "untranslated"
	// FilterFuzzy filters terms in fuzzy state
	FilterFuzzy = "fuzzy"
	// FilterNotFuzzy filters terms in not fuzzy state
	FilterNotFuzzy = "not_fuzzy"
	// FilterAutomatic filters terms in automatic state
	FilterAutomatic = "automatic"
	// FilterNotAutomatic filters terms in not automatic state
	FilterNotAutomatic = "not_automatic"
	// FilterProofread filters terms in proofread state
	FilterProofread = "proofread"
	// FilterNotProofread filters terms in not proofread state
	FilterNotProofread = "not_proofread"
)

func (p *Project) post(endpoint string, fields map[string]string, files map[string]io.Reader, res interface{}) error {
	if fields == nil {
		fields = make(map[string]string)
	}
	fields["id"] = strconv.Itoa(p.ID)
	return p.POEditor.post(endpoint, fields, files, res)
}

type projectsResult struct {
	Projects []project `json:"projects"`
}

type projectResult struct {
	Project project `json:"project"`
}

type project struct {
	ID                int          `json:"id"`
	Name              string       `json:"name"`
	Description       string       `json:"description"`
	Public            int          `json:"public"`
	Open              int          `json:"open"`
	ReferenceLanguage string       `json:"reference_language"`
	Terms             int          `json:"terms"`
	Created           poEditorTime `json:"created"`
}

func (p project) toProject(poe *POEditor) *Project {
	return &Project{
		POEditor:          poe,
		ID:                p.ID,
		Name:              p.Name,
		Description:       p.Description,
		Public:            p.Public,
		Open:              p.Open,
		ReferenceLanguage: p.ReferenceLanguage,
		Terms:             p.Terms,
		Created:           p.Created.Time,
	}
}

type termsCountResult struct {
	Terms CountResult `json:"terms"`
}

type exportResult struct {
	URL string `json:"url"`
}
<|endoftext|>


<commit_before>package client

import (
	"github.com/haukurk/latency-microservice-go/api"
)

type LatencyClient struct {
	Host       string
	RemoteHost string
}

func (tc *LatencyClient) PingHost() (api.Latency, error) {
	var respLatency api.Latency

	url := tc.Host + "/latency/" + tc.RemoteHost
	r, err := makeRequest("GET", url, nil)
	if err != nil {
		return respLatency, err
	}
	err = processResponseEntity(r, &respLatency, 201)
	return respLatency, err
}
<commit_msg>Client fixes<commit_after>package client

import (
	"github.com/haukurk/latency-microservice-go/api"
)

type LatencyClient struct {
	Host       string
	RemoteHost string
}

func (tc *LatencyClient) PingHost() (api.Latency, error) {
	var respLatency api.Latency

	url := tc.Host + "/latency/" + tc.RemoteHost
	r, err := makeRequest("GET", url, nil)
	if err != nil {
		return respLatency, err
	}
	err = processResponseEntity(r, &respLatency, 200)
	return respLatency, err
}
<|endoftext|>


<commit_before>package client

import (
	"errors"
	"regexp"
	"strings"
	"time"
)

type Line struct {
	nick, ident, host, src string
	cmd, raw               string
	args                   []string
	time                   time.Time

	context string
	output  string
}

// Output returns the formatted string to be outputted by the client
func (l *Line) Output() string {
	return "[" + l.time.Format("15:04:05") + "] " + l.output
}

// lexServerMsg scans a IRC message and outputs its tokens in a Line struct
func lexServerMsg(message string) (l *Line, err error) {

	// grab prefix if present
	var prefix string
	prefixEnd := -1
	if strings.HasPrefix(message, ":") {
		prefixEnd = strings.Index(message, " ")
		if prefixEnd == -1 {
			err = errors.New("Message with only a prefix")
			return
		}
		prefix = message[1:prefixEnd]
	}

	// grab trailing param if present
	var trailing string
	trailingStart := strings.Index(message, " :")
	if trailingStart >= 0 {
		trailing = message[trailingStart+2:]
	} else {
		trailingStart = len(message)
	}

	tmp := message[prefixEnd+1 : trailingStart]
	cmdAndParams := strings.Fields(tmp)
	if len(cmdAndParams) < 1 {
		err = errors.New("Cannot lex command")
		return
	}

	command := cmdAndParams[0]
	params := cmdAndParams[1:]
	if trailing != "" {
		params = append(params, trailing)
	}

	nick, ident, host, src, err := resolvePrefix(prefix)
	if err != nil {
		return
	}

	l = &Line{
		nick: nick, ident: ident, host: host, src: src,
		cmd: command, raw: message,
		args: params,
	}

	return

}

// ParseServerMsg parses an IRC message from an IRC server and outputs
// a string ready to be printed out from the client.
func ParseServerMsg(message string) (l *Line, err error) {
	l, err = lexServerMsg(message)
	l.time = time.Now()
	if err != nil {
		return
	}
	var output string
	var context string
	switch l.cmd {
	case "NOTICE":
		output, context = notice(l.nick, l.args)
	case "NICK":
		output, context = nick(l.nick, l.args)
	case "MODE":
		output, context = mode(l.nick, l.args)
	case "PRIVMSG":
		output, context = privMsg(l.nick, l.args)
	case "PART":
		output, context = part(l.nick, l.args)
	case "JOIN":
		output, context = join(l.nick, l.args)
	case "QUIT":
		output, context = quit(l.nick, l.args)
	default:
		// check for numeric commands
		r := regexp.MustCompile("^\\d+$")
		if r.MatchString(l.cmd) {
			l.output, l.context = numeric(l.nick, l.args)
			return
		}
		err = errors.New("Unknown command.")
		return
	}

	l.output = output
	l.context = context
	return
}

func join(nick string, params []string) (output, context string) {
	channel := params[0]
	output = nick + " has joined " + channel
	context = channel
	return
}

func quit(nick string, params []string) (output, context string) {
	output = nick + " has quit"
	if len(params) != 0 {
		output += " (" + params[0] + ")"
	}
	return
}

func notice(nick string, params []string) (output, context string) {
	return privMsg(nick, params)
}

func mode(nick string, params []string) (output, context string) {
	context = params[0]
	output = nick + " changed mode"
	for i := 1; i < len(params); i++ {
		output += " " + params[i]
	}
	output += " for " + context
	return
}

func privMsg(nick string, params []string) (output, context string) {
	output = nick + ": " + params[len(params)-1]
	context = params[0]
	return
}

func part(nick string, params []string) (output, context string) {
	output = nick + " has left " + params[0]
	context = params[0]
	return
}

func nick(nick string, params []string) (output, context string) {
	output = nick + " changed nick to " + params[0]
	return
}

func numeric(nick string, params []string) (output, context string) {
	context = params[0]
	output = params[len(params)-1]
	return
}

// resolvePrefix returns the token of the IRC message prefix
func resolvePrefix(prefix string) (nick, ident, host, src string, err error) {
	src = prefix
	if prefix == "" {
		nick = "<Server>"
		return
	}

	nickEnd := strings.Index(prefix, "!")
	userEnd := strings.Index(prefix, "@")
	if nickEnd != -1 && userEnd != -1 {
		nick = prefix[0:nickEnd]
		ident = prefix[nickEnd+1 : userEnd]
		host = prefix[userEnd+1:]
	} else {
		nick = src
	}

	return
}
<commit_msg>Context thing<commit_after>package client

import (
	"errors"
	"regexp"
	"strings"
	"time"
)

type Line struct {
	nick, ident, host, src string
	cmd, raw               string
	args                   []string
	time                   time.Time

	context string
	output  string
}

// Output returns the formatted string to be outputted by the client
func (l *Line) Output() string {
	return "[" + l.time.Format("15:04:05") + "] " + l.output
}

// Context returns the context string of the line. Empty context equals
// server and/or client context
func (l *Line) Context() string {
	return l.context
}

// lexServerMsg scans a IRC message and outputs its tokens in a Line struct
func lexServerMsg(message string) (l *Line, err error) {

	// grab prefix if present
	var prefix string
	prefixEnd := -1
	if strings.HasPrefix(message, ":") {
		prefixEnd = strings.Index(message, " ")
		if prefixEnd == -1 {
			err = errors.New("Message with only a prefix")
			return
		}
		prefix = message[1:prefixEnd]
	}

	// grab trailing param if present
	var trailing string
	trailingStart := strings.Index(message, " :")
	if trailingStart >= 0 {
		trailing = message[trailingStart+2:]
	} else {
		trailingStart = len(message)
	}

	tmp := message[prefixEnd+1 : trailingStart]
	cmdAndParams := strings.Fields(tmp)
	if len(cmdAndParams) < 1 {
		err = errors.New("Cannot lex command")
		return
	}

	command := cmdAndParams[0]
	params := cmdAndParams[1:]
	if trailing != "" {
		params = append(params, trailing)
	}

	nick, ident, host, src, err := resolvePrefix(prefix)
	if err != nil {
		return
	}

	l = &Line{
		nick: nick, ident: ident, host: host, src: src,
		cmd: command, raw: message,
		args: params,
	}

	return

}

// ParseServerMsg parses an IRC message from an IRC server and outputs
// a string ready to be printed out from the client.
func ParseServerMsg(message string) (l *Line, err error) {
	l, err = lexServerMsg(message)
	l.time = time.Now()
	if err != nil {
		return
	}
	var output string
	var context string
	switch l.cmd {
	case "NOTICE":
		output, context = notice(l.nick, l.args)
	case "NICK":
		output, context = nick(l.nick, l.args)
	case "MODE":
		output, context = mode(l.nick, l.args)
	case "PRIVMSG":
		output, context = privMsg(l.nick, l.args)
	case "PART":
		output, context = part(l.nick, l.args)
	case "JOIN":
		output, context = join(l.nick, l.args)
	case "QUIT":
		output, context = quit(l.nick, l.args)
	default:
		// check for numeric commands
		r := regexp.MustCompile("^\\d+$")
		if r.MatchString(l.cmd) {
			l.output, l.context = numeric(l.nick, l.args)
			return
		}
		err = errors.New("Unknown command.")
		return
	}

	l.output = output
	l.context = context
	return
}

func join(nick string, params []string) (output, context string) {
	channel := params[0]
	output = nick + " has joined " + channel
	context = channel
	return
}

func quit(nick string, params []string) (output, context string) {
	output = nick + " has quit"
	if len(params) != 0 {
		output += " (" + params[0] + ")"
	}
	return
}

func notice(nick string, params []string) (output, context string) {
	return privMsg(nick, params)
}

func mode(nick string, params []string) (output, context string) {
	context = params[0]
	output = nick + " changed mode"
	for i := 1; i < len(params); i++ {
		output += " " + params[i]
	}
	output += " for " + context
	return
}

func privMsg(nick string, params []string) (output, context string) {
	output = nick + ": " + params[len(params)-1]
	context = params[0]
	return
}

func part(nick string, params []string) (output, context string) {
	output = nick + " has left " + params[0]
	context = params[0]
	return
}

func nick(nick string, params []string) (output, context string) {
	output = nick + " changed nick to " + params[0]
	return
}

func numeric(nick string, params []string) (output, context string) {
	context = params[0]
	output = params[len(params)-1]
	return
}

// resolvePrefix returns the token of the IRC message prefix
func resolvePrefix(prefix string) (nick, ident, host, src string, err error) {
	src = prefix
	if prefix == "" {
		nick = "<Server>"
		return
	}

	nickEnd := strings.Index(prefix, "!")
	userEnd := strings.Index(prefix, "@")
	if nickEnd != -1 && userEnd != -1 {
		nick = prefix[0:nickEnd]
		ident = prefix[nickEnd+1 : userEnd]
		host = prefix[userEnd+1:]
	} else {
		nick = src
	}

	return
}
<|endoftext|>


<commit_before>package libbuildpack

import (
	"fmt"
	"sort"

	semver2 "github.com/Masterminds/semver"
	semver1 "github.com/blang/semver"
)

type versionWithOriginal struct {
	original string
	version  semver1.Version
}
type versionsWithOriginal []versionWithOriginal

func (v versionsWithOriginal) Len() int           { return len(v) }
func (v versionsWithOriginal) Swap(i, j int)      { v[i], v[j] = v[j], v[i] }
func (v versionsWithOriginal) Less(i, j int) bool { return v[i].version.LT(v[j].version) }

func FindMatchingVersion(constraint string, versions []string) (string, error) {
	version, err := matchBlang(constraint, versions)
	if err == nil {
		return version, nil
	}

	return matchMasterminds(constraint, versions)
}

func matchBlang(constraint string, versions []string) (string, error) {
	var depVersions versionsWithOriginal
	versionConstraint, err := semver1.ParseRange(constraint)
	if err != nil {
		return "", err
	}

	for _, ver := range versions {
		depVersion, err := semver1.Parse(ver)
		if err != nil {
			return "", err
		}
		versionWithOriginal := versionWithOriginal{
			original: ver,
			version:  depVersion,
		}

		if versionConstraint(depVersion) {
			depVersions = append(depVersions, versionWithOriginal)
		}
	}

	if len(depVersions) != 0 {
		sort.Sort(depVersions)
		return depVersions[len(depVersions)-1].original, nil
	}

	return "", fmt.Errorf("no match found for %s in %v", constraint, versions)
}

func matchMasterminds(constraint string, versions []string) (string, error) {
	var depVersions []*semver2.Version
	versionConstraint, err := semver2.NewConstraint(constraint)
	if err != nil {
		return "", err
	}

	for _, ver := range versions {
		depVersion, err := semver2.NewVersion(ver)
		if err != nil {
			return "", err
		}

		if versionConstraint.Check(depVersion) {
			depVersions = append(depVersions, depVersion)
		}
	}

	if len(depVersions) != 0 {
		sort.Sort(semver2.Collection(depVersions))
		return depVersions[len(depVersions)-1].Original(), nil
	}

	return "", fmt.Errorf("no match found for %s in %v", constraint, versions)
}
<commit_msg>Rename private methods to make them more readable<commit_after>package libbuildpack

import (
	"fmt"
	"sort"

	semver2 "github.com/Masterminds/semver"
	semver1 "github.com/blang/semver"
)

type versionWithOriginal struct {
	original string
	version  semver1.Version
}
type versionsWithOriginal []versionWithOriginal

func (v versionsWithOriginal) Len() int           { return len(v) }
func (v versionsWithOriginal) Swap(i, j int)      { v[i], v[j] = v[j], v[i] }
func (v versionsWithOriginal) Less(i, j int) bool { return v[i].version.LT(v[j].version) }

func FindMatchingVersion(constraint string, versions []string) (string, error) {
	version, err := matchSemver1(constraint, versions)
	if err == nil {
		return version, nil
	}

	return matchSemver2(constraint, versions)
}

func matchSemver1(constraint string, versions []string) (string, error) {
	var depVersions versionsWithOriginal
	versionConstraint, err := semver1.ParseRange(constraint)
	if err != nil {
		return "", err
	}

	for _, ver := range versions {
		depVersion, err := semver1.Parse(ver)
		if err != nil {
			return "", err
		}
		versionWithOriginal := versionWithOriginal{
			original: ver,
			version:  depVersion,
		}

		if versionConstraint(depVersion) {
			depVersions = append(depVersions, versionWithOriginal)
		}
	}

	if len(depVersions) != 0 {
		sort.Sort(depVersions)
		return depVersions[len(depVersions)-1].original, nil
	}

	return "", fmt.Errorf("no match found for %s in %v", constraint, versions)
}

func matchSemver2(constraint string, versions []string) (string, error) {
	var depVersions []*semver2.Version
	versionConstraint, err := semver2.NewConstraint(constraint)
	if err != nil {
		return "", err
	}

	for _, ver := range versions {
		depVersion, err := semver2.NewVersion(ver)
		if err != nil {
			return "", err
		}

		if versionConstraint.Check(depVersion) {
			depVersions = append(depVersions, depVersion)
		}
	}

	if len(depVersions) != 0 {
		sort.Sort(semver2.Collection(depVersions))
		return depVersions[len(depVersions)-1].Original(), nil
	}

	return "", fmt.Errorf("no match found for %s in %v", constraint, versions)
}
<|endoftext|>


<commit_before>package agent

import (
    "time"
    "fmt"
    "github.com/crowdmob/goamz/aws"
    "github.com/crowdmob/goamz/cloudwatch"
)

const (
    SCHEDULED_LOOP = 6
)

var cw *cloudwatch.CloudWatch

func init() {
    region := aws.Regions["eu-west-1"]
    auth, err := aws.EnvAuth()

    if err != nil {
        L.Err("Unable to send data to CloudWatch")

        panic("Unable to send data to CloudWatch")
    }

        cw,_ = cloudwatch.NewCloudWatch(auth, region.CloudWatchServicepoint)
}

func SendCollectedData() {
    doEvery(SCHEDULED_LOOP * time.Second, func(time time.Time) {
        Database.Lock()

        for key, point := range Database.metrics {
            metric := new(cloudwatch.MetricDatum)

            metric.MetricName = point.Metric
            metric.Timestamp = time
            metric.Unit = ""
            metric.Value = point.Value

            metrics := []cloudwatch.MetricDatum{*metric}

            if _, err := cw.PutMetricDataNamespace(metrics, point.Namespace); err != nil {
                L.Err(fmt.Sprintf("%v", err))
            } else {
                L.Info("Metric with key: \"" + key + "\" sent to cloud correcly")
            }

            delete(Database.metrics, key)
        }
        Database.Unlock()
    })
}

func doEvery(d time.Duration, f func(time.Time)) {
    for {
        time.Sleep(d)
        f(time.Now())
    }
}

<commit_msg>Send to cloud every 60 seconds<commit_after>package agent

import (
    "time"
    "fmt"
    "github.com/crowdmob/goamz/aws"
    "github.com/crowdmob/goamz/cloudwatch"
)

const (
    SCHEDULED_LOOP = 60
)

var cw *cloudwatch.CloudWatch

func init() {
    region := aws.Regions["eu-west-1"]
    auth, err := aws.EnvAuth()

    if err != nil {
        L.Err("Unable to send data to CloudWatch")

        panic("Unable to send data to CloudWatch")
    }

        cw,_ = cloudwatch.NewCloudWatch(auth, region.CloudWatchServicepoint)
}

func SendCollectedData() {
    doEvery(SCHEDULED_LOOP * time.Second, func(time time.Time) {
        Database.Lock()

        for key, point := range Database.metrics {
            metric := new(cloudwatch.MetricDatum)

            metric.MetricName = point.Metric
            metric.Timestamp = time
            metric.Unit = ""
            metric.Value = point.Value

            metrics := []cloudwatch.MetricDatum{*metric}

            if _, err := cw.PutMetricDataNamespace(metrics, point.Namespace); err != nil {
                L.Err(fmt.Sprintf("%v", err))
            } else {
                L.Info("Metric with key: \"" + key + "\" sent to cloud correcly")
            }

            delete(Database.metrics, key)
        }
        Database.Unlock()
    })
}

func doEvery(d time.Duration, f func(time.Time)) {
    for {
        time.Sleep(d)
        f(time.Now())
    }
}

<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"html"
	"log"
	"net/http"

	"github.com/gorilla/mux"
)

func main() {

	router := mux.NewRouter().StrictSlash(true)
	router.HandleFunc("/", index)

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
	})

	log.Fatal(http.ListenAndServe(":8080", router))
}

func index(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
}
<commit_msg>routes<commit_after>package main

import (
	"fmt"
	"html"
	"log"
	"net/http"

	"github.com/gorilla/mux"
)

func main() {
	router := mux.NewRouter().StrictSlash(true)
	router.HandleFunc("/packages", postPackage).Methods("POST")
	router.HandleFunc("/packages", getPackages).Methods("GET").Queries("lookupTerm", "{lookupTerm}")
	router.HandleFunc("/packages/{packageName}", getPackagesByPackageName).Methods("GET")
	router.HandleFunc("/packages/{packageName}/{version}", getPackage).Methods("GET")

	log.Fatal(http.ListenAndServe(":8080", router))
}

func getPackages(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	lookupTerm := vars["lookupTerm"]
	fmt.Fprintf(w, "Hello from getPackages. path: %q, lookupTerm: %q", html.EscapeString(r.URL.Path), lookupTerm)
}

func getPackagesByPackageName(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	packageName := vars["packageName"]
	fmt.Fprintf(w, "Hello from getPackagesByPackageName. path: %q, packageName: %q", html.EscapeString(r.URL.Path), packageName)
}

func getPackage(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	packageName := vars["packageName"]
	version := vars["version"]
	fmt.Fprintf(w, "Hello from getPackage. path: %q, packageName: %q, version: %q", html.EscapeString(r.URL.Path), packageName, version)
}

func postPackage(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello from postPackage, %q", html.EscapeString(r.URL.Path))
}
<|endoftext|>


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

package lease

import (
	"fmt"
	"io"
	"log"
	"sync"
)

// Create a ReadLease that never expires, unless voluntarily revoked or
// upgraded.
//
// The supplied function will be used to obtain the read lease contents, the
// first time and whenever the supplied file leaser decides to expire the
// temporary copy thus obtained. It must return the same contents every time,
// and the contents must be of the given size.
//
// This magic is not preserved after the lease is upgraded.
func NewAutoRefreshingReadLease(
	fl FileLeaser,
	size int64,
	f func() (io.ReadCloser, error)) (rl ReadLease) {
	rl = &autoRefreshingReadLease{
		leaser: fl,
		size:   size,
		f:      f,
	}

	return
}

type autoRefreshingReadLease struct {
	mu sync.Mutex

	/////////////////////////
	// Constant data
	/////////////////////////

	size int64

	/////////////////////////
	// Dependencies
	/////////////////////////

	leaser FileLeaser
	f      func() (io.ReadCloser, error)

	/////////////////////////
	// Mutable state
	/////////////////////////

	// Set to true when we've been revoked for good.
	//
	// GUARDED_BY(mu)
	revoked bool

	// The current wrapped lease, or nil if one has never been issued.
	//
	// GUARDED_BY(mu)
	wrapped ReadLease
}

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

// Attempt to clean up after the supplied read/write lease.
func destroyReadWriteLease(rwl ReadWriteLease) {
	var err error
	defer func() {
		if err != nil {
			log.Printf("Error destroying read/write lease: %v", err)
		}
	}()

	// Downgrade to a read lease.
	rl, err := rwl.Downgrade()
	if err != nil {
		err = fmt.Errorf("Downgrade: %v", err)
		return
	}

	// Revoke the read lease.
	rl.Revoke()
}

func isRevokedErr(err error) bool {
	_, ok := err.(*RevokedError)
	return ok
}

// Set up a read/write lease and fill in our contents.
//
// REQUIRES: The caller has observed that rl.lease has expired.
//
// LOCKS_REQUIRED(rl.mu)
func (rl *autoRefreshingReadLease) getContents() (
	rwl ReadWriteLease, err error) {
	// Obtain some space to write the contents.
	rwl, err = rl.leaser.NewFile()
	if err != nil {
		err = fmt.Errorf("NewFile: %v", err)
		return
	}

	// Attempt to clean up if we exit early.
	defer func() {
		if err != nil {
			destroyReadWriteLease(rwl)
		}
	}()

	// Obtain the reader for our contents.
	rc, err := rl.f()
	if err != nil {
		err = fmt.Errorf("User function: %v", err)
		return
	}

	defer func() {
		closeErr := rc.Close()
		if closeErr != nil && err == nil {
			err = fmt.Errorf("Close: %v", closeErr)
		}
	}()

	// Copy into the read/write lease.
	copied, err := io.Copy(rwl, rc)
	if err != nil {
		err = fmt.Errorf("Copy: %v", err)
		return
	}

	// Did the user lie about the size?
	if copied != rl.Size() {
		err = fmt.Errorf("Copied %v bytes; expected %v", copied, rl.Size())
		return
	}

	return
}

// Downgrade and save the supplied read/write lease obtained with getContents
// for later use.
//
// LOCKS_REQUIRED(rl.mu)
func (rl *autoRefreshingReadLease) saveContents(rwl ReadWriteLease) {
	downgraded, err := rwl.Downgrade()
	if err != nil {
		log.Printf("Failed to downgrade write lease (%q); abandoning.", err.Error())
		return
	}

	rl.wrapped = downgraded
}

////////////////////////////////////////////////////////////////////////
// Public interface
////////////////////////////////////////////////////////////////////////

func (rl *autoRefreshingReadLease) Read(p []byte) (n int, err error) {
	rl.mu.Lock()
	defer rl.mu.Unlock()

	// Special case: have we been permanently revoked?
	if rl.revoked {
		err = &RevokedError{}
		return
	}

	// Common case: is the existing lease still valid?
	if rl.wrapped != nil {
		n, err = rl.wrapped.Read(p)
		if !isRevokedErr(err) {
			panic("TODO")
		}

		// Clear the revoked error.
		err = nil
	}

	// Get hold of a read/write lease containing our contents.
	rwl, err := rl.getContents()
	if err != nil {
		err = fmt.Errorf("getContents: %v", err)
		return
	}

	defer rl.saveContents(rwl)

	// Serve from the read/write lease.
	n, err = rwl.Read(p)

	return
}

func (rl *autoRefreshingReadLease) Seek(
	offset int64,
	whence int) (off int64, err error) {
	rl.mu.Lock()
	defer rl.mu.Unlock()

	// Special case: have we been permanently revoked?
	if rl.revoked {
		err = &RevokedError{}
		return
	}

	// Common case: is the existing lease still valid?
	if rl.wrapped != nil {
		panic("TODO")
	}

	// Get hold of a read/write lease containing our contents.
	rwl, err := rl.getContents()
	if err != nil {
		err = fmt.Errorf("getContents: %v", err)
		return
	}

	defer rl.saveContents(rwl)

	// Serve from the read/write lease.
	off, err = rwl.Seek(offset, whence)

	return
}

func (rl *autoRefreshingReadLease) ReadAt(
	p []byte,
	off int64) (n int, err error) {
	rl.mu.Lock()
	defer rl.mu.Unlock()

	// Special case: have we been permanently revoked?
	if rl.revoked {
		err = &RevokedError{}
		return
	}

	// Common case: is the existing lease still valid?
	if rl.wrapped != nil {
		panic("TODO")
	}

	// Get hold of a read/write lease containing our contents.
	rwl, err := rl.getContents()
	if err != nil {
		err = fmt.Errorf("getContents: %v", err)
		return
	}

	defer rl.saveContents(rwl)

	// Serve from the read/write lease.
	n, err = rwl.ReadAt(p, off)

	return
}

func (rl *autoRefreshingReadLease) Size() (size int64) {
	size = rl.size
	return
}

func (rl *autoRefreshingReadLease) Revoked() (revoked bool) {
	rl.mu.Lock()
	defer rl.mu.Unlock()

	revoked = rl.revoked
	return
}

func (rl *autoRefreshingReadLease) Upgrade() (rwl ReadWriteLease, err error) {
	rl.mu.Lock()
	defer rl.mu.Unlock()

	// Special case: have we been permanently revoked?
	if rl.revoked {
		err = &RevokedError{}
		return
	}

	// Common case: is the existing lease still valid?
	if rl.wrapped != nil {
		panic("TODO")
	}

	// Build the read/write lease anew.
	rwl, err = rl.getContents()
	if err != nil {
		err = fmt.Errorf("getContents: %v", err)
		return
	}

	// We are now revoked.
	rl.revoked = true

	return
}

func (rl *autoRefreshingReadLease) Revoke() {
	rl.mu.Lock()
	defer rl.mu.Unlock()

	rl.revoked = true
	if rl.wrapped != nil {
		rl.wrapped.Revoke()
	}
}
<commit_msg>The other part of the logic.<commit_after>// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package lease

import (
	"fmt"
	"io"
	"log"
	"sync"
)

// Create a ReadLease that never expires, unless voluntarily revoked or
// upgraded.
//
// The supplied function will be used to obtain the read lease contents, the
// first time and whenever the supplied file leaser decides to expire the
// temporary copy thus obtained. It must return the same contents every time,
// and the contents must be of the given size.
//
// This magic is not preserved after the lease is upgraded.
func NewAutoRefreshingReadLease(
	fl FileLeaser,
	size int64,
	f func() (io.ReadCloser, error)) (rl ReadLease) {
	rl = &autoRefreshingReadLease{
		leaser: fl,
		size:   size,
		f:      f,
	}

	return
}

type autoRefreshingReadLease struct {
	mu sync.Mutex

	/////////////////////////
	// Constant data
	/////////////////////////

	size int64

	/////////////////////////
	// Dependencies
	/////////////////////////

	leaser FileLeaser
	f      func() (io.ReadCloser, error)

	/////////////////////////
	// Mutable state
	/////////////////////////

	// Set to true when we've been revoked for good.
	//
	// GUARDED_BY(mu)
	revoked bool

	// The current wrapped lease, or nil if one has never been issued.
	//
	// GUARDED_BY(mu)
	wrapped ReadLease
}

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

// Attempt to clean up after the supplied read/write lease.
func destroyReadWriteLease(rwl ReadWriteLease) {
	var err error
	defer func() {
		if err != nil {
			log.Printf("Error destroying read/write lease: %v", err)
		}
	}()

	// Downgrade to a read lease.
	rl, err := rwl.Downgrade()
	if err != nil {
		err = fmt.Errorf("Downgrade: %v", err)
		return
	}

	// Revoke the read lease.
	rl.Revoke()
}

func isRevokedErr(err error) bool {
	_, ok := err.(*RevokedError)
	return ok
}

// Set up a read/write lease and fill in our contents.
//
// REQUIRES: The caller has observed that rl.lease has expired.
//
// LOCKS_REQUIRED(rl.mu)
func (rl *autoRefreshingReadLease) getContents() (
	rwl ReadWriteLease, err error) {
	// Obtain some space to write the contents.
	rwl, err = rl.leaser.NewFile()
	if err != nil {
		err = fmt.Errorf("NewFile: %v", err)
		return
	}

	// Attempt to clean up if we exit early.
	defer func() {
		if err != nil {
			destroyReadWriteLease(rwl)
		}
	}()

	// Obtain the reader for our contents.
	rc, err := rl.f()
	if err != nil {
		err = fmt.Errorf("User function: %v", err)
		return
	}

	defer func() {
		closeErr := rc.Close()
		if closeErr != nil && err == nil {
			err = fmt.Errorf("Close: %v", closeErr)
		}
	}()

	// Copy into the read/write lease.
	copied, err := io.Copy(rwl, rc)
	if err != nil {
		err = fmt.Errorf("Copy: %v", err)
		return
	}

	// Did the user lie about the size?
	if copied != rl.Size() {
		err = fmt.Errorf("Copied %v bytes; expected %v", copied, rl.Size())
		return
	}

	return
}

// Downgrade and save the supplied read/write lease obtained with getContents
// for later use.
//
// LOCKS_REQUIRED(rl.mu)
func (rl *autoRefreshingReadLease) saveContents(rwl ReadWriteLease) {
	downgraded, err := rwl.Downgrade()
	if err != nil {
		log.Printf("Failed to downgrade write lease (%q); abandoning.", err.Error())
		return
	}

	rl.wrapped = downgraded
}

////////////////////////////////////////////////////////////////////////
// Public interface
////////////////////////////////////////////////////////////////////////

func (rl *autoRefreshingReadLease) Read(p []byte) (n int, err error) {
	rl.mu.Lock()
	defer rl.mu.Unlock()

	// Special case: have we been permanently revoked?
	if rl.revoked {
		err = &RevokedError{}
		return
	}

	// Common case: is the existing lease still valid?
	if rl.wrapped != nil {
		n, err = rl.wrapped.Read(p)
		if !isRevokedErr(err) {
			return
		}

		// Clear the revoked error.
		err = nil
	}

	// Get hold of a read/write lease containing our contents.
	rwl, err := rl.getContents()
	if err != nil {
		err = fmt.Errorf("getContents: %v", err)
		return
	}

	defer rl.saveContents(rwl)

	// Serve from the read/write lease.
	n, err = rwl.Read(p)

	return
}

func (rl *autoRefreshingReadLease) Seek(
	offset int64,
	whence int) (off int64, err error) {
	rl.mu.Lock()
	defer rl.mu.Unlock()

	// Special case: have we been permanently revoked?
	if rl.revoked {
		err = &RevokedError{}
		return
	}

	// Common case: is the existing lease still valid?
	if rl.wrapped != nil {
		panic("TODO")
	}

	// Get hold of a read/write lease containing our contents.
	rwl, err := rl.getContents()
	if err != nil {
		err = fmt.Errorf("getContents: %v", err)
		return
	}

	defer rl.saveContents(rwl)

	// Serve from the read/write lease.
	off, err = rwl.Seek(offset, whence)

	return
}

func (rl *autoRefreshingReadLease) ReadAt(
	p []byte,
	off int64) (n int, err error) {
	rl.mu.Lock()
	defer rl.mu.Unlock()

	// Special case: have we been permanently revoked?
	if rl.revoked {
		err = &RevokedError{}
		return
	}

	// Common case: is the existing lease still valid?
	if rl.wrapped != nil {
		panic("TODO")
	}

	// Get hold of a read/write lease containing our contents.
	rwl, err := rl.getContents()
	if err != nil {
		err = fmt.Errorf("getContents: %v", err)
		return
	}

	defer rl.saveContents(rwl)

	// Serve from the read/write lease.
	n, err = rwl.ReadAt(p, off)

	return
}

func (rl *autoRefreshingReadLease) Size() (size int64) {
	size = rl.size
	return
}

func (rl *autoRefreshingReadLease) Revoked() (revoked bool) {
	rl.mu.Lock()
	defer rl.mu.Unlock()

	revoked = rl.revoked
	return
}

func (rl *autoRefreshingReadLease) Upgrade() (rwl ReadWriteLease, err error) {
	rl.mu.Lock()
	defer rl.mu.Unlock()

	// Special case: have we been permanently revoked?
	if rl.revoked {
		err = &RevokedError{}
		return
	}

	// Common case: is the existing lease still valid?
	if rl.wrapped != nil {
		panic("TODO")
	}

	// Build the read/write lease anew.
	rwl, err = rl.getContents()
	if err != nil {
		err = fmt.Errorf("getContents: %v", err)
		return
	}

	// We are now revoked.
	rl.revoked = true

	return
}

func (rl *autoRefreshingReadLease) Revoke() {
	rl.mu.Lock()
	defer rl.mu.Unlock()

	rl.revoked = true
	if rl.wrapped != nil {
		rl.wrapped.Revoke()
	}
}
<|endoftext|>


<commit_before>/*
 * Provide the ability to query DuckDuckGo from IRC.
 */

package duckduckgo

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strings"
	"time"

	"golang.org/x/net/html"
	"summercat.com/irc"
	"summercat.com/irc/client"
)

// SearchResult holds a single parsed out search result.
type SearchResult struct {
	URL  string
	Text string
}

// RelatedTopic holds an instant answer API related topic answer.
type RelatedTopic struct {
	Text string
}

// Answer holds an instant answer API result.
type Answer struct {
	Heading       string
	AbstractText  string
	Type          string
	Answer        string
	AnswerType    string
	Redirect      string
	RelatedTopics []RelatedTopic

	// APIURL is not part of the response but it's nice to have on hand.
	APIURL string
}

// Regular expressions to match on !triggers
var ddgTriggerRe = regexp.MustCompile("(?i)^\\s*[!.](?:ddg|d|g|google)(\\s+.*|$)")
var ddg1TriggerRe = regexp.MustCompile("(?i)^\\s*[!.](?:ddg1|d1|g1)(\\s+.*|$)")
var duckTriggerRe = regexp.MustCompile("(?i)^\\s*[!.](?:duck)(\\s+.*|$)")

// The user agent to send.
var userAgent = "Lynx/2.8.8dev.2 libwww-FM/2.14 SSL-MM/1.4.1"

// Timeout on HTTP requests.
var timeout = 15 * time.Second

// Debug mode toggle. If enabled we will save responses to debugFile
// and if that file exists use its content instead of making additional
// HTTP requests.
var debug = false
var debugFile = "/tmp/ddg.out"

// init registers us to receive IRC messages.
func init() {
	client.Hooks = append(client.Hooks, Hook)
}

// Hook fires when an IRC message of some kind occurs.
// This can let us know whether to do anything or not.
func Hook(conn *client.Conn, message irc.Message) {
	if message.Command != "PRIVMSG" {
		return
	}

	matches := ddgTriggerRe.FindStringSubmatch(message.Params[1])
	if matches != nil {
		hookDDG(conn, message.Params[0], matches[1])
		return
	}

	matches = ddg1TriggerRe.FindStringSubmatch(message.Params[1])
	if matches != nil {
		hookDDG1(conn, message.Params[0], matches[1])
		return
	}

	matches = duckTriggerRe.FindStringSubmatch(message.Params[1])
	if matches != nil {
		hookDuck(conn, message.Params[0], matches[1])
	}
}

// hookDDG handles !ddg
func hookDDG(conn *client.Conn, target string, args string) {
	query := strings.TrimSpace(args)
	if len(query) == 0 {
		conn.Message(target, "Usage: !ddg <query>")
		return
	}

	search(conn, target, query, 4)
}

// hookDDG handles !ddg1
func hookDDG1(conn *client.Conn, target string, args string) {
	query := strings.TrimSpace(args)
	if len(query) == 0 {
		conn.Message(target, "Usage: !ddg1 <query>")
		return
	}

	search(conn, target, query, 1)
}

// hookDuck handles !duck
//
// We look up an instant answer and respond to the target.
func hookDuck(conn *client.Conn, target string, args string) {
	query := strings.TrimSpace(args)
	if len(query) == 0 {
		conn.Message(target, "Usage: !duck <query>")
		return
	}

	answer, err := getInstantAnswer(query)
	if err != nil {
		conn.Message(target, fmt.Sprintf("Failure: %s", err))
		return
	}

	// Topic summary (type A)
	if answer.Type == "A" {
		if len(answer.AbstractText) == 0 {
			conn.Message(target, fmt.Sprintf("Missing summary! (%s)", answer.APIURL))
			return
		}

		conn.Message(target, answer.AbstractText)
		return
	}

	// Disambiguation (type D)
	if answer.Type == "D" {
		if len(answer.RelatedTopics) > 0 && len(answer.RelatedTopics[0].Text) > 0 {
			conn.Message(target, fmt.Sprintf("Did you mean: %s",
				answer.RelatedTopics[0].Text))
			return
		}

		conn.Message(target, fmt.Sprintf("No exact result found. (%s).",
			answer.APIURL))
		return
	}

	// Category (Type C). Lists related. e.g. list of Simpsons characters.
	if answer.Type == "C" {
		if len(answer.RelatedTopics) > 0 && len(answer.RelatedTopics[0].Text) > 0 {
			conn.Message(target, fmt.Sprintf("First result: %s",
				answer.RelatedTopics[0].Text))
			return
		}
		conn.Message(target, fmt.Sprintf("No category found (%s).", answer.APIURL))
		return

	}

	// Exclusive (Type E). Exclusive. e.g., !bang
	if answer.Type == "E" {
		if len(answer.Redirect) > 0 {
			conn.Message(target, fmt.Sprintf("Found: %s", answer.Redirect))
			return
		}

		if len(answer.Answer) > 0 {
			conn.Message(target, fmt.Sprintf("Answer: %s", answer.Answer))
			return
		}

		conn.Message(target, fmt.Sprintf("Exclusive match, but no redirect or answer. (%s)",
			answer.APIURL))
		return
	}

	// Name (type N). Name.
	if answer.Type == "N" {
		conn.Message(target,
			fmt.Sprintf("Name result found but not supported (%s)", answer.APIURL))
		return
	}

	if answer.Type == "" {
		conn.Message(target, fmt.Sprintf("No results. (%s)", answer.APIURL))
		return
	}

	conn.Message(target, fmt.Sprintf("Unknown answer type (%s). (%s)",
		answer.Type, answer.APIURL))
}

// getInstantAnswer queries the DuckDuckGo instant answer API.
// See https://duckduckgo.com/api
//
// Interesting keys:
//
// For topic summaries:
//
// AbstractText: Topic summary with no HTML
// Heading:

// For instant answers:
//
// Answer
// AnswerType

// For definitions
//
// Definition
func getInstantAnswer(query string) (Answer, error) {
	// I want to set headers, so I need to build and make the request this
	// way.

	values := url.Values{}
	values.Set("q", query)
	values.Set("format", "json")
	values.Set("pretty", "1")
	values.Set("no_redirect", "1")
	values.Set("no_html", "1")
	values.Set("t", "github.com/horgh/irc/duckduckgo")

	apiURL := "https://api.duckduckgo.com/?" + values.Encode()

	request, err := http.NewRequest("GET", apiURL, nil)
	if err != nil {
		return Answer{}, fmt.Errorf("Preparing request: %s", err)
	}

	request.Header.Set("User-Agent", userAgent)

	client := http.Client{Timeout: timeout}

	log.Printf("Making request... [%s]", query)

	resp, err := client.Do(request)
	if err != nil {
		return Answer{}, fmt.Errorf("HTTP failure: %s", err)
	}

	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return Answer{}, fmt.Errorf("Read failure: %s", err)
	}

	answer := Answer{}
	err = json.Unmarshal(body, &answer)
	if err != nil {
		return Answer{}, fmt.Errorf("Unable to decode: %s", err)
	}

	answer.APIURL = apiURL

	return answer, nil
}

// search looks up search results and outputs them to the target.
func search(conn *client.Conn, target string, query string, result int) {
	body, err := getRawSearchResults(query)
	if err != nil {
		conn.Message(target, fmt.Sprintf("Query failure: %s", err))
		return
	}

	results, err := parseSearchResults(body)
	if err != nil {
		conn.Message(target, fmt.Sprintf("Failure parsing results: %s", err))
		return
	}

	if len(results) == 0 {
		conn.Message(target, "No results.")
		return
	}

	for i := 0; i < result && i < len(results); i++ {
		conn.Message(target, fmt.Sprintf("%s - %s", results[i].URL,
			results[i].Text))
	}
}

// getRawSearchResults retrieves the results as an HTML document.
//
// We make an HTTP request (unless in debug mode, and then we may not).
func getRawSearchResults(query string) ([]byte, error) {
	// In debug mode we use the saved response if it is present rather than
	// making a new HTTP request.
	if debug {
		_, err := os.Lstat(debugFile)
		if err == nil {
			body, err := ioutil.ReadFile(debugFile)
			if err != nil {
				return nil, fmt.Errorf("Debug file exists but could not read: %s", err)
			}
			log.Printf("Debug mode. Read %s", debugFile)
			return body, nil
		}
	}

	// I want to set headers, so I need to build and make the request this
	// way.

	values := url.Values{}
	values.Set("q", query)
	values.Set("kl", "us-en")

	request, err := http.NewRequest("POST", "https://duckduckgo.com/lite/",
		strings.NewReader(values.Encode()))
	if err != nil {
		return nil, fmt.Errorf("Preparing request: %s", err)
	}

	request.Header.Set("User-Agent", userAgent)
	request.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	client := http.Client{Timeout: timeout}

	log.Printf("Making request... [%s]", query)

	resp, err := client.Do(request)
	if err != nil {
		return nil, fmt.Errorf("HTTP failure: %s", err)
	}

	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("Read failure: %s", err)
	}

	// In debug mode we save the response to disk.
	// This is because I don't want to repeatedly hit the site when debugging
	// if I can help it.
	if debug {
		err = ioutil.WriteFile(debugFile, body, 0777)
		if err != nil {
			return nil, fmt.Errorf("Write failure: %s", err)
		}
	}

	return body, nil
}

// parseSearchResults parses out research results.
func parseSearchResults(body []byte) ([]*SearchResult, error) {
	doc, err := html.Parse(bytes.NewReader(body))
	if err != nil {
		return nil, fmt.Errorf("Document parsing error: %s", err)
	}

	results := traverseForSearchResults(doc)
	return results, nil
}

// traverseForSearchResults looks through the document for search results.
//
// At each node in the document, we check to see if we have a search
// result. We recursively descend, depth first.
func traverseForSearchResults(node *html.Node) []*SearchResult {
	results := []*SearchResult{}

	result := grabSearchResult(node)
	if result != nil {
		results = append(results, result)
	}

	for child := node.FirstChild; child != nil; child = child.NextSibling {
		childResults := traverseForSearchResults(child)
		results = append(results, childResults...)
	}

	return results
}

// grabSearchResult checks if there is a search result starting at the
// current node in the tree.
//
// The HTML of the tree we are looking for looks like this:
//
//

//   3.
//
//     Platform to Create Organizational Testing and Certifications
//
//

func grabSearchResult(node *html.Node) *SearchResult {
	//

	if node.Type != html.ElementNode || node.Data != "tr" {
		return nil
	}

	if len(node.Attr) != 0 {
		return nil
	}

	//   3.

	td1 := node.FirstChild

	// In several cases we have "hidden" text nodes as we traverse. Skip
	// them.
	if td1.Type == html.TextNode {
		td1 = td1.NextSibling
	}

	if td1 == nil || td1.Type != html.ElementNode || td1.Data != "td" {
		return nil
	}

	if len(td1.Attr) != 1 || td1.Attr[0].Key != "valign" ||
		td1.Attr[0].Val != "top" {
		return nil
	}

	//

	td2 := td1.NextSibling
	if td2.Type == html.TextNode {
		td2 = td2.NextSibling
	}

	if td2 == nil || td2.Type != html.ElementNode || td2.Data != "td" {
		return nil
	}

	if len(td2.Attr) != 0 {
		return nil
	}

	//     Platform to Create Organizational Testing and Certifications

	anchor := td2.FirstChild
	if anchor.Type == html.TextNode {
		anchor = anchor.NextSibling
	}

	if anchor == nil || anchor.Type != html.ElementNode || anchor.Data != "a" {
		return nil
	}

	if len(anchor.Attr) != 3 {
		return nil
	}

	if anchor.Attr[0].Key != "rel" || anchor.Attr[0].Val != "nofollow" {
		return nil
	}

	if anchor.Attr[1].Key != "href" {
		return nil
	}
	href := anchor.Attr[1].Val

	if anchor.Attr[2].Key != "class" || anchor.Attr[2].Val != "result-link" {
		return nil
	}

	// Text in the

	text := grabText(anchor)

	re := regexp.MustCompile("\\s+")
	text = re.ReplaceAllString(text, " ")

	return &SearchResult{URL: href, Text: text}
}

// grabText recursively descends and takes the text from all text nodes
// starting from the given node.
func grabText(node *html.Node) string {
	if node == nil {
		return ""
	}

	if node.Type == html.TextNode {
		return node.Data
	}

	if node.Type != html.ElementNode {
		return ""
	}

	text := ""
	for child := node.FirstChild; child != nil; child = child.NextSibling {
		text += grabText(child)
	}

	return text
}
<commit_msg>duckduckgo: Don't set useragent on Instant Answer<commit_after>/*
 * Provide the ability to query DuckDuckGo from IRC.
 */

package duckduckgo

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strings"
	"time"

	"golang.org/x/net/html"
	"summercat.com/irc"
	"summercat.com/irc/client"
)

// SearchResult holds a single parsed out search result.
type SearchResult struct {
	URL  string
	Text string
}

// RelatedTopic holds an instant answer API related topic answer.
type RelatedTopic struct {
	Text string
}

// Answer holds an instant answer API result.
type Answer struct {
	Heading       string
	AbstractText  string
	Type          string
	Answer        string
	AnswerType    string
	Redirect      string
	RelatedTopics []RelatedTopic

	// APIURL is not part of the response but it's nice to have on hand.
	APIURL string
}

// Regular expressions to match on !triggers
var ddgTriggerRe = regexp.MustCompile("(?i)^\\s*[!.](?:ddg|d|g|google)(\\s+.*|$)")
var ddg1TriggerRe = regexp.MustCompile("(?i)^\\s*[!.](?:ddg1|d1|g1)(\\s+.*|$)")
var duckTriggerRe = regexp.MustCompile("(?i)^\\s*[!.](?:duck)(\\s+.*|$)")

// Timeout on HTTP requests.
var timeout = 15 * time.Second

// Debug mode toggle. If enabled we will save responses to debugFile
// and if that file exists use its content instead of making additional
// HTTP requests.
var debug = false
var debugFile = "/tmp/ddg.out"

// init registers us to receive IRC messages.
func init() {
	client.Hooks = append(client.Hooks, Hook)
}

// Hook fires when an IRC message of some kind occurs.
// This can let us know whether to do anything or not.
func Hook(conn *client.Conn, message irc.Message) {
	if message.Command != "PRIVMSG" {
		return
	}

	matches := ddgTriggerRe.FindStringSubmatch(message.Params[1])
	if matches != nil {
		hookDDG(conn, message.Params[0], matches[1])
		return
	}

	matches = ddg1TriggerRe.FindStringSubmatch(message.Params[1])
	if matches != nil {
		hookDDG1(conn, message.Params[0], matches[1])
		return
	}

	matches = duckTriggerRe.FindStringSubmatch(message.Params[1])
	if matches != nil {
		hookDuck(conn, message.Params[0], matches[1])
	}
}

// hookDDG handles !ddg
func hookDDG(conn *client.Conn, target string, args string) {
	query := strings.TrimSpace(args)
	if len(query) == 0 {
		conn.Message(target, "Usage: !ddg <query>")
		return
	}

	search(conn, target, query, 4)
}

// hookDDG handles !ddg1
func hookDDG1(conn *client.Conn, target string, args string) {
	query := strings.TrimSpace(args)
	if len(query) == 0 {
		conn.Message(target, "Usage: !ddg1 <query>")
		return
	}

	search(conn, target, query, 1)
}

// hookDuck handles !duck
//
// We look up an instant answer and respond to the target.
func hookDuck(conn *client.Conn, target string, args string) {
	query := strings.TrimSpace(args)
	if len(query) == 0 {
		conn.Message(target, "Usage: !duck <query>")
		return
	}

	answer, err := getInstantAnswer(query)
	if err != nil {
		conn.Message(target, fmt.Sprintf("Failure: %s", err))
		return
	}

	// Topic summary (type A)
	if answer.Type == "A" {
		if len(answer.AbstractText) == 0 {
			conn.Message(target, fmt.Sprintf("Missing summary! (%s)", answer.APIURL))
			return
		}

		conn.Message(target, answer.AbstractText)
		return
	}

	// Disambiguation (type D)
	if answer.Type == "D" {
		if len(answer.RelatedTopics) > 0 && len(answer.RelatedTopics[0].Text) > 0 {
			conn.Message(target, fmt.Sprintf("Did you mean: %s",
				answer.RelatedTopics[0].Text))
			return
		}

		conn.Message(target, fmt.Sprintf("No exact result found. (%s).",
			answer.APIURL))
		return
	}

	// Category (Type C). Lists related. e.g. list of Simpsons characters.
	if answer.Type == "C" {
		if len(answer.RelatedTopics) > 0 && len(answer.RelatedTopics[0].Text) > 0 {
			conn.Message(target, fmt.Sprintf("First result: %s",
				answer.RelatedTopics[0].Text))
			return
		}
		conn.Message(target, fmt.Sprintf("No category found (%s).", answer.APIURL))
		return

	}

	// Exclusive (Type E). Exclusive. e.g., !bang
	if answer.Type == "E" {
		if len(answer.Redirect) > 0 {
			conn.Message(target, fmt.Sprintf("Found: %s", answer.Redirect))
			return
		}

		if len(answer.Answer) > 0 {
			conn.Message(target, fmt.Sprintf("Answer: %s", answer.Answer))
			return
		}

		conn.Message(target, fmt.Sprintf("Exclusive match, but no redirect or answer. (%s)",
			answer.APIURL))
		return
	}

	// Name (type N). Name.
	if answer.Type == "N" {
		conn.Message(target,
			fmt.Sprintf("Name result found but not supported (%s)", answer.APIURL))
		return
	}

	if answer.Type == "" {
		conn.Message(target, fmt.Sprintf("No results. (%s)", answer.APIURL))
		return
	}

	conn.Message(target, fmt.Sprintf("Unknown answer type (%s). (%s)",
		answer.Type, answer.APIURL))
}

// getInstantAnswer queries the DuckDuckGo instant answer API.
// See https://duckduckgo.com/api
//
// Interesting keys:
//
// For topic summaries:
//
// AbstractText: Topic summary with no HTML
// Heading:

// For instant answers:
//
// Answer
// AnswerType

// For definitions
//
// Definition
func getInstantAnswer(query string) (Answer, error) {
	// I want to set headers, so I need to build and make the request this way.

	values := url.Values{}
	values.Set("q", query)
	values.Set("format", "json")
	values.Set("pretty", "1")
	values.Set("no_redirect", "1")
	values.Set("no_html", "1")
	values.Set("t", "github.com/horgh/irc/duckduckgo")

	apiURL := "https://api.duckduckgo.com/?" + values.Encode()

	request, err := http.NewRequest("GET", apiURL, nil)
	if err != nil {
		return Answer{}, fmt.Errorf("Preparing request: %s", err)
	}

	client := http.Client{Timeout: timeout}

	log.Printf("Making request... [%s] (URL %s)", query, apiURL)

	resp, err := client.Do(request)
	if err != nil {
		return Answer{}, fmt.Errorf("HTTP failure: %s", err)
	}

	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return Answer{}, fmt.Errorf("Read failure: %s", err)
	}

	answer := Answer{}
	err = json.Unmarshal(body, &answer)
	if err != nil {
		log.Printf("Body: %s", body)
		return Answer{}, fmt.Errorf("Unable to decode: %s", err)
	}

	answer.APIURL = apiURL

	return answer, nil
}

// search looks up search results and outputs them to the target.
func search(conn *client.Conn, target string, query string, result int) {
	body, err := getRawSearchResults(query)
	if err != nil {
		conn.Message(target, fmt.Sprintf("Query failure: %s", err))
		return
	}

	results, err := parseSearchResults(body)
	if err != nil {
		conn.Message(target, fmt.Sprintf("Failure parsing results: %s", err))
		return
	}

	if len(results) == 0 {
		conn.Message(target, "No results.")
		return
	}

	for i := 0; i < result && i < len(results); i++ {
		conn.Message(target, fmt.Sprintf("%s - %s", results[i].URL,
			results[i].Text))
	}
}

// getRawSearchResults retrieves the results as an HTML document.
//
// We make an HTTP request (unless in debug mode, and then we may not).
func getRawSearchResults(query string) ([]byte, error) {
	// In debug mode we use the saved response if it is present rather than
	// making a new HTTP request.
	if debug {
		_, err := os.Lstat(debugFile)
		if err == nil {
			body, err := ioutil.ReadFile(debugFile)
			if err != nil {
				return nil, fmt.Errorf("Debug file exists but could not read: %s", err)
			}
			log.Printf("Debug mode. Read %s", debugFile)
			return body, nil
		}
	}

	// I want to set headers, so I need to build and make the request this way.

	values := url.Values{}
	values.Set("q", query)
	values.Set("kl", "us-en")

	request, err := http.NewRequest("POST", "https://duckduckgo.com/lite/",
		strings.NewReader(values.Encode()))
	if err != nil {
		return nil, fmt.Errorf("Preparing request: %s", err)
	}

	userAgent := "Lynx/2.8.8dev.2 libwww-FM/2.14 SSL-MM/1.4.1"

	request.Header.Set("User-Agent", userAgent)
	request.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	client := http.Client{Timeout: timeout}

	log.Printf("Making request... [%s]", query)

	resp, err := client.Do(request)
	if err != nil {
		return nil, fmt.Errorf("HTTP failure: %s", err)
	}

	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("Read failure: %s", err)
	}

	// In debug mode we save the response to disk.
	// This is because I don't want to repeatedly hit the site when debugging
	// if I can help it.
	if debug {
		err = ioutil.WriteFile(debugFile, body, 0777)
		if err != nil {
			return nil, fmt.Errorf("Write failure: %s", err)
		}
	}

	return body, nil
}

// parseSearchResults parses out research results.
func parseSearchResults(body []byte) ([]*SearchResult, error) {
	doc, err := html.Parse(bytes.NewReader(body))
	if err != nil {
		return nil, fmt.Errorf("Document parsing error: %s", err)
	}

	results := traverseForSearchResults(doc)
	return results, nil
}

// traverseForSearchResults looks through the document for search results.
//
// At each node in the document, we check to see if we have a search
// result. We recursively descend, depth first.
func traverseForSearchResults(node *html.Node) []*SearchResult {
	results := []*SearchResult{}

	result := grabSearchResult(node)
	if result != nil {
		results = append(results, result)
	}

	for child := node.FirstChild; child != nil; child = child.NextSibling {
		childResults := traverseForSearchResults(child)
		results = append(results, childResults...)
	}

	return results
}

// grabSearchResult checks if there is a search result starting at the
// current node in the tree.
//
// The HTML of the tree we are looking for looks like this:
//
//

//   3.
//
//     Platform to Create Organizational Testing and Certifications
//
//

func grabSearchResult(node *html.Node) *SearchResult {
	//

	if node.Type != html.ElementNode || node.Data != "tr" {
		return nil
	}

	if len(node.Attr) != 0 {
		return nil
	}

	//   3.

	td1 := node.FirstChild

	// In several cases we have "hidden" text nodes as we traverse. Skip
	// them.
	if td1.Type == html.TextNode {
		td1 = td1.NextSibling
	}

	if td1 == nil || td1.Type != html.ElementNode || td1.Data != "td" {
		return nil
	}

	if len(td1.Attr) != 1 || td1.Attr[0].Key != "valign" ||
		td1.Attr[0].Val != "top" {
		return nil
	}

	//

	td2 := td1.NextSibling
	if td2.Type == html.TextNode {
		td2 = td2.NextSibling
	}

	if td2 == nil || td2.Type != html.ElementNode || td2.Data != "td" {
		return nil
	}

	if len(td2.Attr) != 0 {
		return nil
	}

	//     Platform to Create Organizational Testing and Certifications

	anchor := td2.FirstChild
	if anchor.Type == html.TextNode {
		anchor = anchor.NextSibling
	}

	if anchor == nil || anchor.Type != html.ElementNode || anchor.Data != "a" {
		return nil
	}

	if len(anchor.Attr) != 3 {
		return nil
	}

	if anchor.Attr[0].Key != "rel" || anchor.Attr[0].Val != "nofollow" {
		return nil
	}

	if anchor.Attr[1].Key != "href" {
		return nil
	}
	href := anchor.Attr[1].Val

	if anchor.Attr[2].Key != "class" || anchor.Attr[2].Val != "result-link" {
		return nil
	}

	// Text in the

	text := grabText(anchor)

	re := regexp.MustCompile("\\s+")
	text = re.ReplaceAllString(text, " ")

	return &SearchResult{URL: href, Text: text}
}

// grabText recursively descends and takes the text from all text nodes
// starting from the given node.
func grabText(node *html.Node) string {
	if node == nil {
		return ""
	}

	if node.Type == html.TextNode {
		return node.Data
	}

	if node.Type != html.ElementNode {
		return ""
	}

	text := ""
	for child := node.FirstChild; child != nil; child = child.NextSibling {
		text += grabText(child)
	}

	return text
}
<|endoftext|>


<commit_before>package api

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"os"
	"testing"
	"time"
)

func handle(pattern string, handler http.HandlerFunc) func() {
	mux := http.NewServeMux()
	mux.HandleFunc(pattern, handler)

	server := httptest.NewServer(mux)
	baseURL = server.URL

	return server.Close
}

func TestLogin(t *testing.T) {
	close := handle("/webapps/services/auth/login", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, `{"userId":"1519071252","accessToken":"token","Cookie":"cookie"}`)
	})

	defer close()
	session, err := Login(context.Background(), "email", "password")

	if err != nil {
		t.Fatal(err)
	}

	expected := UserID("1519071252")

	if session.UserID != expected {
		t.Fatalf("Expected %s, got %s", expected, session.UserID)
	}
}

func TestGetActivityIDs(t *testing.T) {
	close := handle("/webapps/services/runsessions/v3/sync", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, `{"sessions":[{"id":"3031240871","gpsTraceAvailable":"true"}]}`)
	})

	defer close()
	ids, err := GetActivityIDs(context.Background(), new(Session))

	if err != nil {
		t.Fatal(err)
	}

	if len(ids) != 1 {
		t.Fatalf("Expected single activity, got %d", len(ids))
	}

	expected := ActivityID("3031240871")

	if ids[0] != expected {
		t.Fatalf("Expected %s, got %s", expected, ids[0])
	}
}

func TestGetActivity(t *testing.T) {
	id := ActivityID("1481996726")

	close := handle("/webapps/services/runsessions/v2/1481996726/details", func(w http.ResponseWriter, r *http.Request) {
		file, err := os.Open("activity.json")

		if err != nil {
			t.Fatal(err)
		}

		io.Copy(w, file)
	})

	defer close()
	activity, err := GetActivity(context.Background(), new(Session), id)

	if err != nil {
		t.Fatal(err)
	}

	expected := Activity{
		ID:        id,
		StartTime: time.Unix(1480085018, 0).UTC(),
		EndTime:   time.Unix(1480085041, 0).UTC(),
	}

	if activity.StartTime != expected.StartTime {
		t.Fatalf("Expected %v, got %v", expected.StartTime, activity.StartTime)
	}

	if activity.EndTime != expected.EndTime {
		t.Fatalf("Expected %v, got %v", expected.EndTime, activity.EndTime)
	}
}
<commit_msg>Finalize tests<commit_after>package api

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"os"
	"reflect"
	"testing"
	"time"
)

func handle(pattern string, handler http.HandlerFunc) func() {
	mux := http.NewServeMux()
	mux.HandleFunc(pattern, handler)

	server := httptest.NewServer(mux)
	baseURL = server.URL

	return server.Close
}

func mustParse(value string) time.Time {
	t, err := time.Parse(time.RFC3339, value)

	if err != nil {
		panic(err)
	}

	return t
}

func TestLogin(t *testing.T) {
	close := handle("/webapps/services/auth/login", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, `{"userId":"1519071252","accessToken":"token","Cookie":"cookie"}`)
	})

	defer close()
	session, err := Login(context.Background(), "email", "password")

	if err != nil {
		t.Fatal(err)
	}

	expected := UserID("1519071252")

	if session.UserID != expected {
		t.Fatalf("Expected %s, got %s", expected, session.UserID)
	}
}

func TestGetActivityIDs(t *testing.T) {
	close := handle("/webapps/services/runsessions/v3/sync", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, `{"sessions":[{"id":"3031240871","gpsTraceAvailable":"true"}]}`)
	})

	defer close()
	ids, err := GetActivityIDs(context.Background(), new(Session))

	if err != nil {
		t.Fatal(err)
	}

	if len(ids) != 1 {
		t.Fatalf("Expected single activity, got %d", len(ids))
	}

	expected := ActivityID("3031240871")

	if ids[0] != expected {
		t.Fatalf("Expected %s, got %s", expected, ids[0])
	}
}

func TestGetActivity(t *testing.T) {
	id := ActivityID("1481996726")

	close := handle("/webapps/services/runsessions/v2/1481996726/details", func(w http.ResponseWriter, r *http.Request) {
		file, err := os.Open("activity.json")

		if err != nil {
			t.Fatal(err)
		}

		io.Copy(w, file)
	})

	defer close()
	activity, err := GetActivity(context.Background(), new(Session), id)

	if err != nil {
		t.Fatal(err)
	}

	expected := Activity{
		ID:        id,
		StartTime: time.Unix(1480085018, 0).UTC(),
		EndTime:   time.Unix(1480085041, 0).UTC(),
		Data: []DataPoint{
			{Longitude: 20.470512, Latitude: 44.80998, Elevation: 129.74628, Time: mustParse("2016-11-25T14:43:38Z")},
			{Longitude: 20.47056, Latitude: 44.809906, Elevation: 129.63553, Time: mustParse("2016-11-25T14:43:40Z")},
			{Longitude: 20.470585, Latitude: 44.809803, Elevation: 129.62706, Time: mustParse("2016-11-25T14:43:43Z")},
			{Longitude: 20.470596, Latitude: 44.809734, Elevation: 129.65015, Time: mustParse("2016-11-25T14:43:45Z")},
			{Longitude: 20.470652, Latitude: 44.809635, Elevation: 129.66025, Time: mustParse("2016-11-25T14:43:48Z")},
			{Longitude: 20.470716, Latitude: 44.809586, Elevation: 129.61984, Time: mustParse("2016-11-25T14:43:50Z")},
			{Longitude: 20.47078, Latitude: 44.809513, Elevation: 129.62643, Time: mustParse("2016-11-25T14:43:53Z")},
			{Longitude: 20.47083, Latitude: 44.809456, Elevation: 129.6278, Time: mustParse("2016-11-25T14:43:55Z")},
			{Longitude: 20.470943, Latitude: 44.80939, Elevation: 129.58714, Time: mustParse("2016-11-25T14:43:58Z")},
			{Longitude: 20.47103, Latitude: 44.809315, Elevation: 129.57872, Time: mustParse("2016-11-25T14:44:01Z")},
		},
	}

	if activity.StartTime != expected.StartTime {
		t.Fatalf("Expected %v, got %v", expected.StartTime, activity.StartTime)
	}

	if activity.EndTime != expected.EndTime {
		t.Fatalf("Expected %v, got %v", expected.EndTime, activity.EndTime)
	}

	if !reflect.DeepEqual(activity.Data, expected.Data) {
		t.Fatalf("Expected %v, got %v", expected.Data, activity.Data)
	}
}
<|endoftext|>


<commit_before>package api_test

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"testing"
	"time"

	"github.com/spf13/viper"

	"github.com/nanopack/pulse/api"
	"github.com/nanopack/pulse/kapacitor"
)

var apiAddr = "0.0.0.0:9898"

func TestMain(m *testing.M) {
	viper.SetDefault("http-listen-address", apiAddr)
	// viper.SetDefault("influx-address", "http://192.168.0.50:8086")
	// viper.SetDefault("kapacitor-address", "http://192.168.0.50:9092")
	viper.SetDefault("kapacitor-address", "http://127.0.0.1:9092")
	viper.SetDefault("influx-address", "http://127.0.0.1:8086")
	viper.SetDefault("insecure", true)
	viper.SetDefault("log-level", "trace")

	// start api
	go api.Start()
	kapacitor.Init()

	time.Sleep(time.Second)

	rtn := m.Run()

	os.Exit(rtn)
}

// test ping
func TestPing(t *testing.T) {
	resp, err := rest("GET", "/ping", "")
	if err != nil {
		t.Error(err)
	}

	if string(resp) != "pong" {
		t.Errorf("%s doesn't match expected out", resp)
	}
}

func TestGetKeys(t *testing.T) {
	resp, err := rest("GET", "/keys", "")
	if err != nil {
		t.Error(err)
	}

	if string(resp) != "null\n" {
		t.Errorf("%s doesn't match expected out", resp)
	}
}

func TestGetTags(t *testing.T) {
	resp, err := rest("GET", "/tags", "")
	if err != nil {
		t.Error(err)
	}

	if string(resp) != "null\n" {
		t.Errorf("%s doesn't match expected out", resp)
	}
}

func TestGetLatest(t *testing.T) {
	resp, err := rest("GET", "/latest/test-stat", "")
	if err != nil {
		t.Error(err)
	}

	if string(resp) != "{\"error\":\"Not Found\"}\n" {
		t.Errorf("%s doesn't match expected out", resp)
	}
}

func TestGetHourly(t *testing.T) {
	resp, err := rest("GET", "/hourly/test-stat", "")
	if err != nil {
		t.Error(err)
	}

	if string(resp) != "{\"error\":\"Not Found\"}\n" {
		t.Errorf("%s doesn't match expected out", resp)
	}
}

func TestGetDaily(t *testing.T) {
	resp, err := rest("GET", "/daily/test-stat", "")
	if err != nil {
		t.Error(err)
	}

	if string(resp) != "{\"error\":\"Not Found\"}\n" {
		t.Errorf("%s doesn't match expected out", resp)
	}

	resp, err = rest("GET", "/daily_peak/test-stat", "")
	if err != nil {
		t.Error(err)
	}

	if string(resp) != "404 page not found\n" {
		t.Errorf("%s doesn't match expected out", resp)
	}
}

func TestAddAlert(t *testing.T) {
	alert := `{
	  "tags": {"host":"abcd"},
	  "metric": "cpu_used",
	  "level": "crit",
	  "threshold": "80",
	  "duration": "30s",
	  "post": "http://127.0.0.1/alert"
	}`
	resp, err := rest("POST", "/alerts", alert)
	if err != nil {
		t.Error(err)
	}

	var newAlert kapacitor.Alert
	err = json.Unmarshal(resp, &newAlert)
	if err != nil {
		t.Errorf("Failed to POST alert - %s", err)
		t.FailNow()
	}

	if newAlert.Metric != "cpu_used" {
		t.Errorf("'%s' doesn't match expected out", newAlert.Metric)
	}

	resp, err = rest("DELETE", "/alerts/"+newAlert.Id, "")
	if err != nil {
		t.Error(err)
	}

	resp, err = rest("PUT", "/alerts", alert)
	if err != nil {
		t.Error(err)
	}

	err = json.Unmarshal(resp, &newAlert)
	if err != nil {
		t.Errorf("Failed to POST alert - %s", err)
		t.FailNow()
	}

	if newAlert.Metric != "cpu_used" {
		t.Errorf("'%s' doesn't match expected out", newAlert.Metric)
	}

	resp, err = rest("DELETE", "/alerts/"+newAlert.Id, "")
	if err != nil {
		t.Error(err)
	}
}

// hit api and return response body
func rest(method, route, data string) ([]byte, error) {
	body := bytes.NewBuffer([]byte(data))

	req, _ := http.NewRequest(method, fmt.Sprintf("http://%s%s", apiAddr, route), body)
	req.Header.Add("X-AUTH-TOKEN", "")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("Unable to %s %s - %s", method, route, err)
	}
	defer res.Body.Close()

	b, _ := ioutil.ReadAll(res.Body)

	return b, nil
}
<commit_msg>Skip kapacitor tests if no kapacitor found<commit_after>package api_test

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"testing"
	"time"

	"github.com/spf13/viper"

	"github.com/nanopack/pulse/api"
	"github.com/nanopack/pulse/kapacitor"
)

var apiAddr = "0.0.0.0:9898"
var kap = true

func TestMain(m *testing.M) {
	viper.SetDefault("http-listen-address", apiAddr)
	// viper.SetDefault("influx-address", "http://192.168.0.50:8086")
	// viper.SetDefault("kapacitor-address", "http://192.168.0.50:9092")
	viper.SetDefault("kapacitor-address", "http://127.0.0.1:9092")
	viper.SetDefault("influx-address", "http://127.0.0.1:8086")
	viper.SetDefault("insecure", true)
	viper.SetDefault("log-level", "trace")

	// start api
	go api.Start()
	err := kapacitor.Init()
	if err != nil {
		fmt.Printf("Failed to init kapacitor - '%s' skipping related tests\n", err)
		kap = false
	}

	time.Sleep(time.Second)

	rtn := m.Run()

	os.Exit(rtn)
}

// test ping
func TestPing(t *testing.T) {
	resp, err := rest("GET", "/ping", "")
	if err != nil {
		t.Error(err)
	}

	if string(resp) != "pong" {
		t.Errorf("%s doesn't match expected out", resp)
	}
}

func TestGetKeys(t *testing.T) {
	resp, err := rest("GET", "/keys", "")
	if err != nil {
		t.Error(err)
	}

	if string(resp) != "null\n" {
		t.Errorf("%s doesn't match expected out", resp)
	}
}

func TestGetTags(t *testing.T) {
	resp, err := rest("GET", "/tags", "")
	if err != nil {
		t.Error(err)
	}

	if string(resp) != "null\n" {
		t.Errorf("%s doesn't match expected out", resp)
	}
}

func TestGetLatest(t *testing.T) {
	resp, err := rest("GET", "/latest/test-stat", "")
	if err != nil {
		t.Error(err)
	}

	if string(resp) != "{\"error\":\"Not Found\"}\n" {
		t.Errorf("%s doesn't match expected out", resp)
	}
}

func TestGetHourly(t *testing.T) {
	resp, err := rest("GET", "/hourly/test-stat", "")
	if err != nil {
		t.Error(err)
	}

	if string(resp) != "{\"error\":\"Not Found\"}\n" {
		t.Errorf("%s doesn't match expected out", resp)
	}
}

func TestGetDaily(t *testing.T) {
	resp, err := rest("GET", "/daily/test-stat", "")
	if err != nil {
		t.Error(err)
	}

	if string(resp) != "{\"error\":\"Not Found\"}\n" {
		t.Errorf("%s doesn't match expected out", resp)
	}

	resp, err = rest("GET", "/daily_peak/test-stat", "")
	if err != nil {
		t.Error(err)
	}

	if string(resp) != "404 page not found\n" {
		t.Errorf("%s doesn't match expected out", resp)
	}
}

func TestAddAlert(t *testing.T) {
	if !kap {
		t.SkipNow()
	}

	alert := `{
	  "tags": {"host":"abcd"},
	  "metric": "cpu_used",
	  "level": "crit",
	  "threshold": "80",
	  "duration": "30s",
	  "post": "http://127.0.0.1/alert"
	}`
	resp, err := rest("POST", "/alerts", alert)
	if err != nil {
		t.Error(err)
	}

	var newAlert kapacitor.Alert
	err = json.Unmarshal(resp, &newAlert)
	if err != nil {
		t.Errorf("Failed to POST alert - %s", err)
		t.FailNow()
	}

	if newAlert.Metric != "cpu_used" {
		t.Errorf("'%s' doesn't match expected out", newAlert.Metric)
	}

	resp, err = rest("DELETE", "/alerts/"+newAlert.Id, "")
	if err != nil {
		t.Error(err)
	}

	resp, err = rest("PUT", "/alerts", alert)
	if err != nil {
		t.Error(err)
	}

	err = json.Unmarshal(resp, &newAlert)
	if err != nil {
		t.Errorf("Failed to POST alert - %s", err)
		t.FailNow()
	}

	if newAlert.Metric != "cpu_used" {
		t.Errorf("'%s' doesn't match expected out", newAlert.Metric)
	}

	resp, err = rest("DELETE", "/alerts/"+newAlert.Id, "")
	if err != nil {
		t.Error(err)
	}
}

// hit api and return response body
func rest(method, route, data string) ([]byte, error) {
	body := bytes.NewBuffer([]byte(data))

	req, _ := http.NewRequest(method, fmt.Sprintf("http://%s%s", apiAddr, route), body)
	req.Header.Add("X-AUTH-TOKEN", "")

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("Unable to %s %s - %s", method, route, err)
	}
	defer res.Body.Close()

	b, _ := ioutil.ReadAll(res.Body)

	return b, nil
}
<|endoftext|>