<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)

var configVars = [...]string{
	"endpoint",
	"auth-endpoint",
	"user",
	"account",
	"token",
	"debug-level",
}

// ConfigVar is a struct which contains a name-value-source triplet
// Source is up to two words separated by a space. The first word is the source type: FLAG, ENV, DIR, CODE.
// The second is the name of the flag/file/environment var used.
type ConfigVar struct {
	Name   string
	Value  string
	Source string
}

// ConfigManager is an interface defining a key->value store that also knows where the values were set from.
type ConfigManager interface {
	Get(string) (string, error)
	GetIgnoreErr(string) string
	GetBool(string) (bool, error)
	GetV(string) (ConfigVar, error)
	GetAll() ([]ConfigVar, error)
	Set(string, string, string)
	SetPersistent(string, string, string) error
	Unset(string) error
	GetDebugLevel() int
	Force() bool
	Silent() bool
	EndpointName() string
	PanelURL() string

	ImportFlags(*flag.FlagSet) []string
}

// Params currently used:
// token - an OAuth 2.0 bearer token to use when authenticating
// username - the default username to use - if not present, $USER
// endpoint - the default endpoint to use - if not present, https://uk0.bigv.io
// auth-endpoint - the default auth API endpoint to use - if not present, https://auth.bytemark.co.uk

// A Config determines the configuration of the bigv client.
// It's responsible for handling things like the credentials to use and what endpoints to talk to.
//
// Each configuration item is read from the following places, falling back to successive places:
//
// Per-command command-line flags, global command-line flags, environment variables, configuration directory, hard-coded defaults
//
//The location of the configuration directory is read from global command-line flags, or is otherwise ~/.go-bigv
//
type Config struct {
	debugLevel  int
	Dir         string
	Memo        map[string]ConfigVar
	Definitions map[string]string
}

type ConfigDirInvalidError struct {
	Path string
}

func (e *ConfigDirInvalidError) Error() string {
	return fmt.Sprintf("The config directory is '%s' but it doesn't seem to be a directory.", e.Path)
}

type CannotLoadDefinitionsError struct {
	Err error
}

func (e *CannotLoadDefinitionsError) Error() string {
	return fmt.Sprintf("Unable to load the definitions file from the BigV API.")
}

type ConfigReadError struct {
	Name string
	Path string
	Err  error
}

func (e *ConfigReadError) Error() string {
	return fmt.Sprintf("Unable to read config for %s from %s.", e.Name, e.Path)
}

type ConfigWriteError struct {
	Name string
	Path string
	Err  error
}

func (e *ConfigWriteError) Error() string {
	return fmt.Sprintf("Unable to write persistent config for %s (%s).", e.Name, e.Path)
}

// Do I really need to have the flags passed in here?
// Yes. Doing commands will be sorted out in a different place, and I don't want to touch it here.

// NewConfig sets up a new config struct. Pass in an empty string to default to ~/.go-bigv
func NewConfig(configDir string, flags *flag.FlagSet) (config *Config, err error) {
	config = new(Config)
	config.Memo = make(map[string]ConfigVar)
	config.Dir = filepath.Join(os.Getenv("HOME"), "/.go-bigv")
	if os.Getenv("BIGV_CONFIG_DIR") != "" {
		config.Dir = os.Getenv("BIGV_CONFIG_DIR")
	}

	if configDir != "" {
		config.Dir = configDir
	}

	err = os.MkdirAll(config.Dir, 0700)
	if err != nil {

		return nil, err
	}

	stat, err := os.Stat(config.Dir)
	if err != nil {
		return nil, err
	}

	if !stat.IsDir() {
		return nil, &ConfigDirInvalidError{config.Dir}
	}

	config.ImportFlags(flags)
	strDL, err := config.Get("debug-level")
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
	} else {
		debugLevel, err := strconv.ParseInt(strDL, 10, 0)
		if err == nil {
			config.debugLevel = int(debugLevel)
		}
	}
	return config, nil
}

func (config *Config) ImportFlags(flags *flag.FlagSet) []string {
	if flags != nil {
		if flags.Parsed() {
			// dump all the flags into the memo
			// should be reet...reet?
			flags.Visit(func(f *flag.Flag) {
				config.Memo[f.Name] = ConfigVar{
					f.Name,
					f.Value.String(),
					"FLAG " + f.Name,
				}
			})
			return flags.Args()
		}
	}
	return nil
}

// GetDebugLevel returns the current debug-level as an integer. This is used throughout the bigv.io/client library to determine verbosity of output.
func (config *Config) GetDebugLevel() int {
	return config.debugLevel
}

// GetPath joins the given string onto the end of the Config.Dir path
func (config *Config) GetPath(name string) string {
	return filepath.Join(config.Dir, name)
}

// LoadDefinitions reads the local copy of the definitions json file, or downloads it from the endpoint if it's too old or nonexistant.
// Eventually this will be used to provide information on various things throughout the application
func (config *Config) LoadDefinitions() error {
	endpoint, err := config.Get("endpoint")
	if err != nil {
		return err
	}
	defPath := config.GetPath("definitions-" + endpoint + ".json")
	stat, err := os.Stat(defPath)

	if err != nil || time.Since(stat.ModTime()) > 24*time.Hour {
		endpoint, err := config.Get("endpoint")
		if err != nil {
			return err
		}
		if !config.Silent() {
			fmt.Fprintf(os.Stderr, "Downloading definitions for %s...\r\n", endpoint)
		}
		c := &http.Client{}
		req, err := http.NewRequest("GET", endpoint+"/definitions.json", nil)
		if err != nil {
			return &CannotLoadDefinitionsError{err}
		}
		req.Header.Add("Accept", "application/json")
		req.Header.Add("Content-Type", "application/json")
		res, err := c.Do(req)
		if err != nil {
			return &CannotLoadDefinitionsError{err}
		}
		if res.StatusCode == 200 {
			responseBody, err := ioutil.ReadAll(res.Body)
			if err != nil {
				return &CannotLoadDefinitionsError{err}
			}
			ioutil.WriteFile(defPath, responseBody, 0660)

			err = json.Unmarshal(responseBody, config.Definitions)
			fmt.Fprintln(os.Stderr)
			return err
		} else {
			// TODO(telyn): Unexpected status code
		}
	} else {
		defs, err := ioutil.ReadFile(defPath)
		if err != nil {
			return &CannotLoadDefinitionsError{err}
		}
		err = json.Unmarshal(defs, config.Definitions)
		return err
	}
	return nil

}

// Get returns the value of a ConfigVar. Used to simplify code when the source is unnecessary.
func (config *Config) Get(name string) (string, error) {
	v, err := config.GetV(name)
	return v.Value, err
}

// GetIgnoreErr returns the value of a ConfigVar or an empty string , if it was unable to read it for whatever reason.
func (config *Config) GetIgnoreErr(name string) string {
	s, _ := config.Get(name)
	return s
}

// GetV returns the ConfigVar for the given key.
func (config *Config) GetV(name string) (ConfigVar, error) {
	// try to read the Memo
	name = strings.ToLower(name)
	if val, ok := config.Memo[name]; ok {
		return val, nil
	}
	return config.read(name)
}

// GetAll returns all of the available ConfigVars in the Config.
func (config *Config) GetAll() (vars []ConfigVar, err error) {
	vars = make([]ConfigVar, len(configVars))
	for i, v := range configVars {
		vars[i], err = config.GetV(v)
		if err != nil {
			return nil, err
		}
	}
	return vars, nil
}

// GetDefault returns the default ConfigVar for the given key.
func (config *Config) GetDefault(name string) ConfigVar {
	// ideally most of these should just be	os.Getenv("BIGV_"+name.Upcase().Replace("-","_"))
	switch name {
	case "user":
		// we don't actually want to default to USER - that will happen during Dispatcher's PromptForCredentials so it can be all "Hey you should bigv config set user <youruser>"
		return ConfigVar{"user", os.Getenv("BIGV_USER"), "ENV BIGV_USER"}
	case "endpoint":
		v := ConfigVar{"endpoint", "https://uk0.bigv.io", "CODE"}

		val := os.Getenv("BIGV_ENDPOINT")
		if val != "" {
			v.Value = val
			v.Source = "ENV BIGV_ENDPOINT"
		}
		return v
	case "auth-endpoint":
		v := ConfigVar{"auth-endpoint", "https://auth.bytemark.co.uk", "CODE"}

		val := os.Getenv("BIGV_AUTH_ENDPOINT")
		if val != "" {
			v.Value = val
			v.Source = "ENV BIGV_AUTH_ENDPOINT"
		}
		return v
	case "account":
		val := os.Getenv("BIGV_ACCOUNT")
		if val != "" {
			return ConfigVar{
				"account",
				val,
				"ENV BIGV_AUTH_ENDPOINT",
			}
		}
		def := config.GetDefault("user")
		def.Name = "account"
		return def
	case "debug-level":
		v := ConfigVar{"debug-level", "0", "CODE"}
		if val := os.Getenv("BIGV_DEBUG_LEVEL"); val != "" {
			v.Value = val
		}
		return v
	case "silent":
		return ConfigVar{"silent", "false", "CODE"}
	case "force":
		return ConfigVar{"force", "false", "CODE"}
	}
	return ConfigVar{"", "", ""}
}

func (config *Config) GetBool(name string) (bool, error) {
	v, err := config.Get(name)
	if err != nil {
		return false, err
	}
	return !(v == "" || v == "false"), nil
}

func (config *Config) read(name string) (ConfigVar, error) {
	path := config.GetPath(name)
	contents, err := ioutil.ReadFile(path)
	if err != nil {
		if os.IsNotExist(err) {
			return config.GetDefault(name), nil
		}

		return config.GetDefault(name), &ConfigReadError{Name: name, Path: path, Err: err}
	}

	return ConfigVar{name, string(contents), "FILE " + path}, nil
}

// Set stores the given key-value pair in config's Memo. This storage does not persist once the program terminates.
func (config *Config) Set(name, value, source string) {
	config.Memo[name] = ConfigVar{name, value, source}
}

// SetPersistent writes a file to the config directory for the given key-value pair.
func (config *Config) SetPersistent(name, value, source string) error {
	path := config.GetPath(name)
	config.Set(name, value, source)
	err := ioutil.WriteFile(path, []byte(value), 0600)
	if err != nil {
		return &ConfigWriteError{Name: name, Path: path, Err: err}
	}
	return nil
}

// Unset removes the named key from both config's Memo and the user's config directory.
func (config *Config) Unset(name string) error {
	delete(config.Memo, name)
	return os.Remove(config.GetPath(name))
}

func (config *Config) Force() bool {
	force, _ := config.GetBool("force")
	return force
}
func (config *Config) Silent() bool {
	silent, _ := config.GetBool("silent")
	return silent
}

func (config *Config) PanelURL() string {
	endpoint := config.EndpointName()
	if strings.EqualFold(endpoint, "uk0.bigv.io") {
		return "https://panel-beta.bytemark.co.uk"
	}
	if strings.EqualFold(endpoint, "int.bigv.io") {
		// worrying leaky code?
		return "https://panel-int.vlan863.bytemark.uk0.bigv.io"
	}
	panel := config.GetIgnoreErr("panel-address")
	if panel == "" {
		panel = "https://your.panel.address"
	}
	return panel
}

func (config *Config) EndpointName() string {
	endpoint := config.GetIgnoreErr("endpoint")
	endpoint = strings.TrimPrefix(endpoint, "https://")
	endpoint = strings.TrimPrefix(endpoint, "http://") // it never hurts to be prepared
	return endpoint
}
<commit_msg>Warn when flags are given after arguments<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"time"
)

var configVars = [...]string{
	"endpoint",
	"auth-endpoint",
	"user",
	"account",
	"token",
	"debug-level",
}

// ConfigVar is a struct which contains a name-value-source triplet
// Source is up to two words separated by a space. The first word is the source type: FLAG, ENV, DIR, CODE.
// The second is the name of the flag/file/environment var used.
type ConfigVar struct {
	Name   string
	Value  string
	Source string
}

// ConfigManager is an interface defining a key->value store that also knows where the values were set from.
type ConfigManager interface {
	Get(string) (string, error)
	GetIgnoreErr(string) string
	GetBool(string) (bool, error)
	GetV(string) (ConfigVar, error)
	GetAll() ([]ConfigVar, error)
	Set(string, string, string)
	SetPersistent(string, string, string) error
	Unset(string) error
	GetDebugLevel() int
	Force() bool
	Silent() bool
	EndpointName() string
	PanelURL() string

	ImportFlags(*flag.FlagSet) []string
}

// Params currently used:
// token - an OAuth 2.0 bearer token to use when authenticating
// username - the default username to use - if not present, $USER
// endpoint - the default endpoint to use - if not present, https://uk0.bigv.io
// auth-endpoint - the default auth API endpoint to use - if not present, https://auth.bytemark.co.uk

// A Config determines the configuration of the bigv client.
// It's responsible for handling things like the credentials to use and what endpoints to talk to.
//
// Each configuration item is read from the following places, falling back to successive places:
//
// Per-command command-line flags, global command-line flags, environment variables, configuration directory, hard-coded defaults
//
//The location of the configuration directory is read from global command-line flags, or is otherwise ~/.go-bigv
//
type Config struct {
	debugLevel  int
	mainFlags   *flag.FlagSet
	Dir         string
	Memo        map[string]ConfigVar
	Definitions map[string]string
}

type ConfigDirInvalidError struct {
	Path string
}

func (e *ConfigDirInvalidError) Error() string {
	return fmt.Sprintf("The config directory is '%s' but it doesn't seem to be a directory.", e.Path)
}

type CannotLoadDefinitionsError struct {
	Err error
}

func (e *CannotLoadDefinitionsError) Error() string {
	return fmt.Sprintf("Unable to load the definitions file from the BigV API.")
}

type ConfigReadError struct {
	Name string
	Path string
	Err  error
}

func (e *ConfigReadError) Error() string {
	return fmt.Sprintf("Unable to read config for %s from %s.", e.Name, e.Path)
}

type ConfigWriteError struct {
	Name string
	Path string
	Err  error
}

func (e *ConfigWriteError) Error() string {
	return fmt.Sprintf("Unable to write persistent config for %s (%s).", e.Name, e.Path)
}

// Do I really need to have the flags passed in here?
// Yes. Doing commands will be sorted out in a different place, and I don't want to touch it here.

// NewConfig sets up a new config struct. Pass in an empty string to default to ~/.go-bigv
func NewConfig(configDir string, flags *flag.FlagSet) (config *Config, err error) {
	config = new(Config)
	config.Memo = make(map[string]ConfigVar)
	config.Dir = filepath.Join(os.Getenv("HOME"), "/.go-bigv")
	config.mainFlags = flags
	if os.Getenv("BIGV_CONFIG_DIR") != "" {
		config.Dir = os.Getenv("BIGV_CONFIG_DIR")
	}

	if configDir != "" {
		config.Dir = configDir
	}

	err = os.MkdirAll(config.Dir, 0700)
	if err != nil {

		return nil, err
	}

	stat, err := os.Stat(config.Dir)
	if err != nil {
		return nil, err
	}

	if !stat.IsDir() {
		return nil, &ConfigDirInvalidError{config.Dir}
	}

	config.ImportFlags(flags)
	strDL, err := config.Get("debug-level")
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
	} else {
		debugLevel, err := strconv.ParseInt(strDL, 10, 0)
		if err == nil {
			config.debugLevel = int(debugLevel)
		}
	}
	return config, nil
}

func (config *Config) ImportFlags(flags *flag.FlagSet) []string {
	if flags != nil {
		if flags.Parsed() {
			// dump all the flags into the memo
			// should be reet...reet?
			flags.Visit(func(f *flag.Flag) {
				config.Memo[f.Name] = ConfigVar{
					f.Name,
					f.Value.String(),
					"FLAG " + f.Name,
				}
			})
			if flags != config.mainFlags {

				args := flags.Args()
				for _, arg := range args {
					if strings.HasPrefix(arg, "-") {
						fmt.Fprintf(os.Stderr, "Flag-like argument '%s' specified after your arguments\r\nBe aware that only flags placed before your arguments are parsed.\r\nSee the help for the command you're calling for invocation examples.\r\n\r\n", arg)
						break
					}
				}
			}
			return flags.Args()
		}
	}
	return nil
}

// GetDebugLevel returns the current debug-level as an integer. This is used throughout the bigv.io/client library to determine verbosity of output.
func (config *Config) GetDebugLevel() int {
	return config.debugLevel
}

// GetPath joins the given string onto the end of the Config.Dir path
func (config *Config) GetPath(name string) string {
	return filepath.Join(config.Dir, name)
}

// LoadDefinitions reads the local copy of the definitions json file, or downloads it from the endpoint if it's too old or nonexistant.
// Eventually this will be used to provide information on various things throughout the application
func (config *Config) LoadDefinitions() error {
	endpoint, err := config.Get("endpoint")
	if err != nil {
		return err
	}
	defPath := config.GetPath("definitions-" + endpoint + ".json")
	stat, err := os.Stat(defPath)

	if err != nil || time.Since(stat.ModTime()) > 24*time.Hour {
		endpoint, err := config.Get("endpoint")
		if err != nil {
			return err
		}
		if !config.Silent() {
			fmt.Fprintf(os.Stderr, "Downloading definitions for %s...\r\n", endpoint)
		}
		c := &http.Client{}
		req, err := http.NewRequest("GET", endpoint+"/definitions.json", nil)
		if err != nil {
			return &CannotLoadDefinitionsError{err}
		}
		req.Header.Add("Accept", "application/json")
		req.Header.Add("Content-Type", "application/json")
		res, err := c.Do(req)
		if err != nil {
			return &CannotLoadDefinitionsError{err}
		}
		if res.StatusCode == 200 {
			responseBody, err := ioutil.ReadAll(res.Body)
			if err != nil {
				return &CannotLoadDefinitionsError{err}
			}
			ioutil.WriteFile(defPath, responseBody, 0660)

			err = json.Unmarshal(responseBody, config.Definitions)
			fmt.Fprintln(os.Stderr)
			return err
		} else {
			// TODO(telyn): Unexpected status code
		}
	} else {
		defs, err := ioutil.ReadFile(defPath)
		if err != nil {
			return &CannotLoadDefinitionsError{err}
		}
		err = json.Unmarshal(defs, config.Definitions)
		return err
	}
	return nil

}

// Get returns the value of a ConfigVar. Used to simplify code when the source is unnecessary.
func (config *Config) Get(name string) (string, error) {
	v, err := config.GetV(name)
	return v.Value, err
}

// GetIgnoreErr returns the value of a ConfigVar or an empty string , if it was unable to read it for whatever reason.
func (config *Config) GetIgnoreErr(name string) string {
	s, _ := config.Get(name)
	return s
}

// GetV returns the ConfigVar for the given key.
func (config *Config) GetV(name string) (ConfigVar, error) {
	// try to read the Memo
	name = strings.ToLower(name)
	if val, ok := config.Memo[name]; ok {
		return val, nil
	}
	return config.read(name)
}

// GetAll returns all of the available ConfigVars in the Config.
func (config *Config) GetAll() (vars []ConfigVar, err error) {
	vars = make([]ConfigVar, len(configVars))
	for i, v := range configVars {
		vars[i], err = config.GetV(v)
		if err != nil {
			return nil, err
		}
	}
	return vars, nil
}

// GetDefault returns the default ConfigVar for the given key.
func (config *Config) GetDefault(name string) ConfigVar {
	// ideally most of these should just be	os.Getenv("BIGV_"+name.Upcase().Replace("-","_"))
	switch name {
	case "user":
		// we don't actually want to default to USER - that will happen during Dispatcher's PromptForCredentials so it can be all "Hey you should bigv config set user <youruser>"
		return ConfigVar{"user", os.Getenv("BIGV_USER"), "ENV BIGV_USER"}
	case "endpoint":
		v := ConfigVar{"endpoint", "https://uk0.bigv.io", "CODE"}

		val := os.Getenv("BIGV_ENDPOINT")
		if val != "" {
			v.Value = val
			v.Source = "ENV BIGV_ENDPOINT"
		}
		return v
	case "auth-endpoint":
		v := ConfigVar{"auth-endpoint", "https://auth.bytemark.co.uk", "CODE"}

		val := os.Getenv("BIGV_AUTH_ENDPOINT")
		if val != "" {
			v.Value = val
			v.Source = "ENV BIGV_AUTH_ENDPOINT"
		}
		return v
	case "account":
		val := os.Getenv("BIGV_ACCOUNT")
		if val != "" {
			return ConfigVar{
				"account",
				val,
				"ENV BIGV_AUTH_ENDPOINT",
			}
		}
		def := config.GetDefault("user")
		def.Name = "account"
		return def
	case "debug-level":
		v := ConfigVar{"debug-level", "0", "CODE"}
		if val := os.Getenv("BIGV_DEBUG_LEVEL"); val != "" {
			v.Value = val
		}
		return v
	case "silent":
		return ConfigVar{"silent", "false", "CODE"}
	case "force":
		return ConfigVar{"force", "false", "CODE"}
	}
	return ConfigVar{"", "", ""}
}

func (config *Config) GetBool(name string) (bool, error) {
	v, err := config.Get(name)
	if err != nil {
		return false, err
	}
	return !(v == "" || v == "false"), nil
}

func (config *Config) read(name string) (ConfigVar, error) {
	path := config.GetPath(name)
	contents, err := ioutil.ReadFile(path)
	if err != nil {
		if os.IsNotExist(err) {
			return config.GetDefault(name), nil
		}

		return config.GetDefault(name), &ConfigReadError{Name: name, Path: path, Err: err}
	}

	return ConfigVar{name, string(contents), "FILE " + path}, nil
}

// Set stores the given key-value pair in config's Memo. This storage does not persist once the program terminates.
func (config *Config) Set(name, value, source string) {
	config.Memo[name] = ConfigVar{name, value, source}
}

// SetPersistent writes a file to the config directory for the given key-value pair.
func (config *Config) SetPersistent(name, value, source string) error {
	path := config.GetPath(name)
	config.Set(name, value, source)
	err := ioutil.WriteFile(path, []byte(value), 0600)
	if err != nil {
		return &ConfigWriteError{Name: name, Path: path, Err: err}
	}
	return nil
}

// Unset removes the named key from both config's Memo and the user's config directory.
func (config *Config) Unset(name string) error {
	delete(config.Memo, name)
	return os.Remove(config.GetPath(name))
}

func (config *Config) Force() bool {
	force, _ := config.GetBool("force")
	return force
}
func (config *Config) Silent() bool {
	silent, _ := config.GetBool("silent")
	return silent
}

func (config *Config) PanelURL() string {
	endpoint := config.EndpointName()
	if strings.EqualFold(endpoint, "uk0.bigv.io") {
		return "https://panel-beta.bytemark.co.uk"
	}
	if strings.EqualFold(endpoint, "int.bigv.io") {
		// worrying leaky code?
		return "https://panel-int.vlan863.bytemark.uk0.bigv.io"
	}
	panel := config.GetIgnoreErr("panel-address")
	if panel == "" {
		panel = "https://your.panel.address"
	}
	return panel
}

func (config *Config) EndpointName() string {
	endpoint := config.GetIgnoreErr("endpoint")
	endpoint = strings.TrimPrefix(endpoint, "https://")
	endpoint = strings.TrimPrefix(endpoint, "http://") // it never hurts to be prepared
	return endpoint
}
<|endoftext|>


<commit_before>package nasa

import (
	"strings"
	"testing"
	"time"
)

func TestNeoFeed(t *testing.T) {
	type neotests struct {
		startText, endText string
		nl                 *NeoList
		err                error
	}
	var tests = []neotests{
		{
			startText: "2017-05-11",
			endText:   "2017-05-12",
			nl:        &NeoList{ElementCount: 17},
		},
		{
			startText: "1000-05-11",
			endText:   "1000-05-12",
			nl:        &NeoList{ElementCount: 0},
		},
	}

	for _, v := range tests {
		start, err := time.Parse("2006-01-02", v.startText)
		if err != nil {
			t.Fatal(err)
		}
		end, err := time.Parse("2006-01-02", v.endText)
		if err != nil {
			t.Fatal(err)
		}
		nl, err := NeoFeed(start, end)
		if err != v.err {
			t.Errorf("NeoFeed returned wrong error got %v, want %v", err, verr)
		}
		if nl == nil {
			if v.nl != nil {
				t.Errorf("NeoFeed returned NeoList when %v was expected", nil)
			}
			continue
		}
		if nl.ElementCount != v.nl.ElementCount {
			t.Errorf("NeoFeed returned wrong element count got %d, want %d", nl.ElementCount, v.nl.ElementCount)
		}
		if !strings.Contains(nl.String(), "Near Earth Objects From:") {
			t.Errorf("NeoFeed return an invalid neolist, not valid NeoList stringer")
		}
	}
}
<commit_msg>Fix broken test<commit_after>package nasa

import (
	"strings"
	"testing"
	"time"
)

func TestNeoFeed(t *testing.T) {
	type neotests struct {
		startText, endText string
		nl                 *NeoList
		err                error
	}
	var tests = []neotests{
		{
			startText: "2017-05-11",
			endText:   "2017-05-12",
			nl:        &NeoList{ElementCount: 17},
		},
		{
			startText: "1000-05-11",
			endText:   "1000-05-12",
			nl:        &NeoList{ElementCount: 0},
		},
	}

	for _, v := range tests {
		start, err := time.Parse("2006-01-02", v.startText)
		if err != nil {
			t.Fatal(err)
		}
		end, err := time.Parse("2006-01-02", v.endText)
		if err != nil {
			t.Fatal(err)
		}
		nl, err := NeoFeed(start, end)
		if err != v.err {
			t.Errorf("NeoFeed returned wrong error got %v, want %v", err, v.err)
		}
		if nl == nil {
			if v.nl != nil {
				t.Errorf("NeoFeed returned NeoList when %v was expected", nil)
			}
			continue
		}
		if nl.ElementCount != v.nl.ElementCount {
			t.Errorf("NeoFeed returned wrong element count got %d, want %d", nl.ElementCount, v.nl.ElementCount)
		}
		if !strings.Contains(nl.String(), "Near Earth Objects From:") {
			t.Errorf("NeoFeed return an invalid neolist, not valid NeoList stringer")
		}
	}
}
<|endoftext|>


<commit_before>// 15 july 2014

package ui

import (
	"unsafe"
)

// #include "winapi_windows.h"
import "C"

type button struct {
	*controlbase
	clicked		*event
}

var buttonclass = toUTF16("BUTTON")

func startNewButton(text string, style C.DWORD) *button {
	c := newControl(buttonclass,
		style | C.WS_TABSTOP,
		0)
	C.setWindowText(c.hwnd, toUTF16(text))
	C.controlSetControlFont(c.hwnd)
	b := &button{
		controlbase:	c,
		clicked:		newEvent(),
	}
	return b
}

func newButton(text string) *button {
	b := startNewButton(text, C.BS_PUSHBUTTON)
	C.setButtonSubclass(b.hwnd, unsafe.Pointer(b))
	return b
}

func (b *button) OnClicked(e func()) {
	b.clicked.set(e)
}

func (b *button) Text() string {
	return b.text()
}

func (b *button) SetText(text string) {
	b.setText(text)
}

//export buttonClicked
func buttonClicked(data unsafe.Pointer) {
	b := (*button)(data)
	b.clicked.fire()
	println("button clicked")
}

// TODO button preferredSize

type checkbox struct {
	*button
}

func newCheckbox(text string) *checkbox {
	c := &checkbox{
		// don't use BS_AUTOCHECKBOX here because it creates problems when refocusing (see http://blogs.msdn.com/b/oldnewthing/archive/2014/05/22/10527522.aspx)
		// we'll handle actually toggling the check state ourselves (see controls_windows.c)
		button:	startNewButton(text, C.BS_CHECKBOX),
	}
	C.setCheckboxSubclass(c.hwnd, unsafe.Pointer(c))
	return c
}

func (c *checkbox) Checked() bool {
	if C.checkboxChecked(c.hwnd) == C.FALSE {
		return false
	}
	return true
}

func (c *checkbox) SetChecked(checked bool) {
	if checked {
		C.checkboxSetChecked(c.hwnd, C.TRUE)
		return
	}
	C.checkboxSetChecked(c.hwnd, C.FALSE)
}

//export checkboxToggled
func checkboxToggled(data unsafe.Pointer) {
	c := (*checkbox)(data)
	c.clicked.fire()
	println("checkbox toggled")
}

type textField struct {
	*controlbase
}

var editclass = toUTF16("EDIT")

func startNewTextField(style C.DWORD) *textField {
	c := newControl(editclass,
		style | C.ES_AUTOHSCROLL | C.ES_LEFT | C.ES_NOHIDESEL | C.WS_TABSTOP,
		C.WS_EX_CLIENTEDGE)		// WS_EX_CLIENTEDGE without WS_BORDER will show the canonical visual styles border (thanks to MindChild in irc.efnet.net/#winprog)
	C.controlSetControlFont(c.hwnd)
	return &textField{
		controlbase:	c,
	}
}

func newTextField() *textField {
	return startNewTextField(0)
}

func newPasswordField() *textField {
	return startNewTextField(C.ES_PASSWORD)
}

func (t *textField) Text() string {
	return t.text()
}

func (t *textField) SetText(text string) {
	t.setText(text)
}

type label struct {
	*controlbase
	standalone			bool
	supercommitResize		func(c *allocation, d *sizing)
}

var labelclass = toUTF16("STATIC")

func finishNewLabel(text string, standalone bool) *label {
	c := newControl(labelclass,
		// SS_NOPREFIX avoids accelerator translation; SS_LEFTNOWORDWRAP clips text past the end
		// controls are vertically aligned to the top by default (thanks Xeek in irc.freenode.net/#winapi)
		C.SS_NOPREFIX | C.SS_LEFTNOWORDWRAP,
		0)
	C.setWindowText(c.hwnd, toUTF16(text))
	C.controlSetControlFont(c.hwnd)
	l := &label{
		controlbase:	c,
		standalone:	standalone,
	}
	l.supercommitResize = l.fcommitResize
	l.fcommitResize = l.labelcommitResize
	return l
}

func newLabel(text string) Label {
	return finishNewLabel(text, false)
}

func newStandaloneLabel(text string) Label {
	return finishNewLabel(text, true)
}

func (l *label) Text() string {
	return l.text()
}

func (l *label) SetText(text string) {
	l.setText(text)
}

const (
	// via http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing
	labelYOffset = 3
	// TODO the label is offset slightly by default...
)

func (l *label) labelcommitResize(c *allocation, d *sizing) {
	if !l.standalone {
		yoff := fromdlgunitsY(labelYOffset, d)
		c.y += yoff
		c.height -= yoff
	}
	l.supercommitResize(c, d)
}
<commit_msg>Converted direct usages of C.setWindowText() in the Windows Control code into calls to the equivalent Control methods so the text length can be properly stored.<commit_after>// 15 july 2014

package ui

import (
	"unsafe"
)

// #include "winapi_windows.h"
import "C"

type button struct {
	*controlbase
	clicked		*event
}

var buttonclass = toUTF16("BUTTON")

func startNewButton(text string, style C.DWORD) *button {
	c := newControl(buttonclass,
		style | C.WS_TABSTOP,
		0)
	c.setText(text)
	C.controlSetControlFont(c.hwnd)
	b := &button{
		controlbase:	c,
		clicked:		newEvent(),
	}
	return b
}

func newButton(text string) *button {
	b := startNewButton(text, C.BS_PUSHBUTTON)
	C.setButtonSubclass(b.hwnd, unsafe.Pointer(b))
	return b
}

func (b *button) OnClicked(e func()) {
	b.clicked.set(e)
}

func (b *button) Text() string {
	return b.text()
}

func (b *button) SetText(text string) {
	b.setText(text)
}

//export buttonClicked
func buttonClicked(data unsafe.Pointer) {
	b := (*button)(data)
	b.clicked.fire()
	println("button clicked")
}

// TODO button preferredSize

type checkbox struct {
	*button
}

func newCheckbox(text string) *checkbox {
	c := &checkbox{
		// don't use BS_AUTOCHECKBOX here because it creates problems when refocusing (see http://blogs.msdn.com/b/oldnewthing/archive/2014/05/22/10527522.aspx)
		// we'll handle actually toggling the check state ourselves (see controls_windows.c)
		button:	startNewButton(text, C.BS_CHECKBOX),
	}
	C.setCheckboxSubclass(c.hwnd, unsafe.Pointer(c))
	return c
}

func (c *checkbox) Checked() bool {
	if C.checkboxChecked(c.hwnd) == C.FALSE {
		return false
	}
	return true
}

func (c *checkbox) SetChecked(checked bool) {
	if checked {
		C.checkboxSetChecked(c.hwnd, C.TRUE)
		return
	}
	C.checkboxSetChecked(c.hwnd, C.FALSE)
}

//export checkboxToggled
func checkboxToggled(data unsafe.Pointer) {
	c := (*checkbox)(data)
	c.clicked.fire()
	println("checkbox toggled")
}

type textField struct {
	*controlbase
}

var editclass = toUTF16("EDIT")

func startNewTextField(style C.DWORD) *textField {
	c := newControl(editclass,
		style | C.ES_AUTOHSCROLL | C.ES_LEFT | C.ES_NOHIDESEL | C.WS_TABSTOP,
		C.WS_EX_CLIENTEDGE)		// WS_EX_CLIENTEDGE without WS_BORDER will show the canonical visual styles border (thanks to MindChild in irc.efnet.net/#winprog)
	C.controlSetControlFont(c.hwnd)
	return &textField{
		controlbase:	c,
	}
}

func newTextField() *textField {
	return startNewTextField(0)
}

func newPasswordField() *textField {
	return startNewTextField(C.ES_PASSWORD)
}

func (t *textField) Text() string {
	return t.text()
}

func (t *textField) SetText(text string) {
	t.setText(text)
}

type label struct {
	*controlbase
	standalone			bool
	supercommitResize		func(c *allocation, d *sizing)
}

var labelclass = toUTF16("STATIC")

func finishNewLabel(text string, standalone bool) *label {
	c := newControl(labelclass,
		// SS_NOPREFIX avoids accelerator translation; SS_LEFTNOWORDWRAP clips text past the end
		// controls are vertically aligned to the top by default (thanks Xeek in irc.freenode.net/#winapi)
		C.SS_NOPREFIX | C.SS_LEFTNOWORDWRAP,
		0)
	c.setText(text)
	C.controlSetControlFont(c.hwnd)
	l := &label{
		controlbase:	c,
		standalone:	standalone,
	}
	l.supercommitResize = l.fcommitResize
	l.fcommitResize = l.labelcommitResize
	return l
}

func newLabel(text string) Label {
	return finishNewLabel(text, false)
}

func newStandaloneLabel(text string) Label {
	return finishNewLabel(text, true)
}

func (l *label) Text() string {
	return l.text()
}

func (l *label) SetText(text string) {
	l.setText(text)
}

const (
	// via http://msdn.microsoft.com/en-us/library/windows/desktop/dn742486.aspx#sizingandspacing
	labelYOffset = 3
	// TODO the label is offset slightly by default...
)

func (l *label) labelcommitResize(c *allocation, d *sizing) {
	if !l.standalone {
		yoff := fromdlgunitsY(labelYOffset, d)
		c.y += yoff
		c.height -= yoff
	}
	l.supercommitResize(c, d)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"net/http"
	"os/exec"
	"strings"
)

func mainHandler(w http.ResponseWriter, r *http.Request) {

	path := strings.TrimSpace(r.URL.Path)

	//Cut off the leading and trailing forward slashes, if they exist.
	//This cuts off the leading forward slash.
	if strings.HasPrefix(path, "/") {
		path = path[1:]
	}
	//This cuts off the trailing forward slash.
	if strings.HasSuffix(path, "/") {
		cut_off_last_char_len := len(path) - 1
		path = path[:cut_off_last_char_len]
	}

	if len(path) == 0 {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	// We need to isolate the individual components of the path.
	components := strings.Split(path, "/")

	salt_node := fmt.Sprintf("G@node_type:%s*", components[0])

	cmdName := "sudo"
	cmdArgs := []string{"salt", "-C", salt_node, "state.highstate"}

	out, err := exec.Command(cmdName, cmdArgs...).Output()
	if err != nil {
		fmt.Fprintf(w, "There was an error running command: %s %s %s", cmdName, cmdArgs, err)
		return
	}

	fmt.Fprintf(w, string(out))
	return
}
<commit_msg>	modified:   mainHandler.go<commit_after>package main

import (
	"fmt"
	"net/http"
	"os/exec"
	"strings"
)

func mainHandler(w http.ResponseWriter, r *http.Request) {

	path := strings.TrimSpace(r.URL.Path)

	//Cut off the leading and trailing forward slashes, if they exist.
	//This cuts off the leading forward slash.
	if strings.HasPrefix(path, "/") {
		path = path[1:]
	}
	//This cuts off the trailing forward slash.
	if strings.HasSuffix(path, "/") {
		cut_off_last_char_len := len(path) - 1
		path = path[:cut_off_last_char_len]
	}

	if len(path) == 0 {
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	// We need to isolate the individual components of the path.
	components := strings.Split(path, "/")

	// added an * to match project_staging/production
	salt_node := fmt.Sprintf("G@node_type:%s*", components[0])

	cmdName := "sudo"
	cmdArgs := []string{"salt", "-C", salt_node, "state.highstate"}

	out, err := exec.Command(cmdName, cmdArgs...).Output()
	if err != nil {
		fmt.Fprintf(w, "There was an error running command: %s %s %s", cmdName, cmdArgs, err)
		return
	}

	fmt.Fprintf(w, string(out))
	return
}
<|endoftext|>


<commit_before>package grayt

import (
	"fmt"
	"image/png"
	"log"
	"math/rand"
	"os"
	"time"
)

func init() {
	rand.Seed(time.Now().UnixNano())
}

// Runner is a convenience struct to help run grayt from a main() function.
type Runner struct {
	PxWide, PxHigh int
	BaseName       string
	Quality        int
}

func NewRunner() *Runner {
	return &Runner{
		PxWide:   640,
		PxHigh:   480,
		BaseName: "default",
		Quality:  10,
	}
}

func (r *Runner) Run(s Scene) {
	strat := strategy{}
	img := strat.traceImage(r.PxWide, r.PxHigh, s, r.Quality)
	f, err := os.Create(r.outputFilename())
	r.checkErr(err)
	defer f.Close()
	err = png.Encode(f, img)
	r.checkErr(err)
}

func (r *Runner) checkErr(err error) {
	if err != nil {
		log.Fatal("Fatal: ", err)
	}
}

func (r *Runner) outputFilename() string {
	return fmt.Sprintf("%s_%dx%d_Q%d.png",
		r.BaseName, r.PxWide, r.PxHigh, r.Quality)
}
<commit_msg>Show output filename<commit_after>package grayt

import (
	"fmt"
	"image/png"
	"log"
	"math/rand"
	"os"
	"time"
)

func init() {
	rand.Seed(time.Now().UnixNano())
}

// Runner is a convenience struct to help run grayt from a main() function.
type Runner struct {
	PxWide, PxHigh int
	BaseName       string
	Quality        int
}

func NewRunner() *Runner {
	return &Runner{
		PxWide:   640,
		PxHigh:   480,
		BaseName: "default",
		Quality:  10,
	}
}

func (r *Runner) Run(s Scene) {
	fmt.Println(r.outputFilename())
	strat := strategy{}
	img := strat.traceImage(r.PxWide, r.PxHigh, s, r.Quality)
	f, err := os.Create(r.outputFilename())
	r.checkErr(err)
	defer f.Close()
	err = png.Encode(f, img)
	r.checkErr(err)
}

func (r *Runner) checkErr(err error) {
	if err != nil {
		log.Fatal("Fatal: ", err)
	}
}

func (r *Runner) outputFilename() string {
	return fmt.Sprintf("%s_%dx%d_Q%d.png",
		r.BaseName, r.PxWide, r.PxHigh, r.Quality)
}
<|endoftext|>


<commit_before>package http

import (
	"errors"
	"io"
	"net/http"

	cmds "github.com/jbenet/go-ipfs/commands"
)

type Handler struct {
	Ctx  cmds.Context
	Root *cmds.Command
}

var ErrNotFound = errors.New("404 page not found")

var mimeTypes = map[string]string{
	cmds.JSON: "application/json",
	cmds.XML:  "application/xml",
	cmds.Text: "text/plain",
}

func (i Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	req, err := Parse(r, i.Root)
	if err != nil {
		if err == ErrNotFound {
			w.WriteHeader(http.StatusNotFound)
		} else {
			w.WriteHeader(http.StatusBadRequest)
		}
		w.Write([]byte(err.Error()))
		return
	}
	req.SetContext(i.Ctx)

	// call the command
	res := i.Root.Call(req)

	// set the Content-Type based on res output
	if _, ok := res.Output().(io.Reader); ok {
		// TODO: set based on actual Content-Type of file
		w.Header().Set("Content-Type", "application/octet-stream")
	} else {
		enc, _ := req.Option(cmds.EncShort)
		encStr, ok := enc.(string)
		if !ok {
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		mime := mimeTypes[encStr]
		w.Header().Set("Content-Type", mime)
	}

	// if response contains an error, write an HTTP error status code
	if e := res.Error(); e != nil {
		if e.Code == cmds.ErrClient {
			w.WriteHeader(http.StatusBadRequest)
		} else {
			w.WriteHeader(http.StatusInternalServerError)
		}
	}

	out, err := res.Reader()
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Header().Set("Content-Type", "text/plain")
		w.Write([]byte(err.Error()))
		return
	}

	io.Copy(w, out)
}
<commit_msg>commands/http: Unexported Handler fields and created constructor<commit_after>package http

import (
	"errors"
	"io"
	"net/http"

	cmds "github.com/jbenet/go-ipfs/commands"
)

type Handler struct {
	ctx  cmds.Context
	root *cmds.Command
}

var ErrNotFound = errors.New("404 page not found")

var mimeTypes = map[string]string{
	cmds.JSON: "application/json",
	cmds.XML:  "application/xml",
	cmds.Text: "text/plain",
}

func NewHandler(ctx cmds.Context, root *cmds.Command) *Handler {
	return &Handler{ctx, root}
}

func (i Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	req, err := Parse(r, i.root)
	if err != nil {
		if err == ErrNotFound {
			w.WriteHeader(http.StatusNotFound)
		} else {
			w.WriteHeader(http.StatusBadRequest)
		}
		w.Write([]byte(err.Error()))
		return
	}
	req.SetContext(i.ctx)

	// call the command
	res := i.root.Call(req)

	// set the Content-Type based on res output
	if _, ok := res.Output().(io.Reader); ok {
		// TODO: set based on actual Content-Type of file
		w.Header().Set("Content-Type", "application/octet-stream")
	} else {
		enc, _ := req.Option(cmds.EncShort)
		encStr, ok := enc.(string)
		if !ok {
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		mime := mimeTypes[encStr]
		w.Header().Set("Content-Type", mime)
	}

	// if response contains an error, write an HTTP error status code
	if e := res.Error(); e != nil {
		if e.Code == cmds.ErrClient {
			w.WriteHeader(http.StatusBadRequest)
		} else {
			w.WriteHeader(http.StatusInternalServerError)
		}
	}

	out, err := res.Reader()
	if err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		w.Header().Set("Content-Type", "text/plain")
		w.Write([]byte(err.Error()))
		return
	}

	io.Copy(w, out)
}
<|endoftext|>


<commit_before>package commands

import (
	"bufio"
	"fmt"
	"github.com/jingweno/gh/cmd"
	"github.com/jingweno/gh/git"
	"github.com/jingweno/gh/github"
	"github.com/jingweno/gh/utils"
	"io/ioutil"
	"os"
	"reflect"
	"regexp"
	"strings"
)

var cmdPullRequest = &Command{
	Run:   pullRequest,
	Usage: "pull-request [-f] [-i ISSUE] [-b BASE] [-h HEAD] [-m MESSAGE] [TITLE]",
	Short: "Open a pull request on GitHub",
	Long: `Opens a pull request on GitHub for the project that the "origin" remote
points to. The default head of the pull request is the current branch.
Both base and head of the pull request can be explicitly given in one of
the following formats: "branch", "owner:branch", "owner/repo:branch".
This command will abort operation if it detects that the current topic
branch has local commits that are not yet pushed to its upstream branch
on the remote. To skip this check, use -f.

If TITLE is omitted, a text editor will open in which title and body of
the pull request can be entered in the same manner as git commit message.

If instead of normal TITLE an issue number is given with -i, the pull
request will be attached to an existing GitHub issue. Alternatively, instead
of title you can paste a full URL to an issue on GitHub.
`,
}

var (
	flagPullRequestBase,
	flagPullRequestHead,
	flagPullRequestIssue,
	flagPullRequestMessage,
	flagPullRequestFile string
	flagPullRequestForce bool
)

func init() {
	cmdPullRequest.Flag.StringVar(&flagPullRequestBase, "b", "", "BASE")
	cmdPullRequest.Flag.StringVar(&flagPullRequestHead, "h", "", "HEAD")
	cmdPullRequest.Flag.StringVar(&flagPullRequestIssue, "i", "", "ISSUE")
	cmdPullRequest.Flag.StringVar(&flagPullRequestMessage, "m", "", "MESSAGE")
	cmdPullRequest.Flag.BoolVar(&flagPullRequestForce, "f", false, "FORCE")
	cmdPullRequest.Flag.StringVar(&flagPullRequestFile, "F", "", "FILE")
	cmdPullRequest.Flag.StringVar(&flagPullRequestFile, "file", "", "FILE")
}

/*
  # while on a topic branch called "feature":
  $ gh pull-request
  [ opens text editor to edit title & body for the request ]
  [ opened pull request on GitHub for "YOUR_USER:feature" ]

  # explicit pull base & head:
  $ gh pull-request -b jingweno:master -h jingweno:feature

  $ gh pull-request -m "title\n\nbody"
  [ create pull request with title & body  ]

  $ gh pull-request -i 123
  [ attached pull request to issue #123 ]

  $ gh pull-request https://github.com/jingweno/gh/pull/123
  [ attached pull request to issue #123 ]
*/
func pullRequest(cmd *Command, args *Args) {
	localRepo := github.LocalRepo()

	currentBranch, err := localRepo.CurrentBranch()
	utils.Check(err)

	baseProject, err := localRepo.MainProject()
	utils.Check(err)

	headProject, err := localRepo.CurrentProject()
	utils.Check(err)

	gh := github.NewWithoutProject()
	gh.Project = baseProject

	var (
		base, head           string
		force, explicitOwner bool
	)
	force = flagPullRequestForce
	if flagPullRequestBase != "" {
		if strings.Contains(flagPullRequestBase, ":") {
			split := strings.SplitN(flagPullRequestBase, ":", 2)
			base = split[1]
			baseProject = github.NewProjectFromString(split[0])
		} else {
			base = flagPullRequestBase
		}
	}

	if flagPullRequestHead != "" {
		if strings.Contains(flagPullRequestHead, ":") {
			split := strings.SplitN(flagPullRequestHead, ":", 2)
			head = split[1]
			headProject = github.NewProjectFromString(split[0])
			explicitOwner = true
		} else {
			head = flagPullRequestHead
		}
	}

	if args.ParamsSize() == 1 {
		arg := args.RemoveParam(0)
		u, e := github.ParseURL(arg)
		r := regexp.MustCompile(`^issues\/(\d+)`)
		p := u.ProjectPath()
		if e == nil && r.MatchString(p) {
			flagPullRequestIssue = r.FindStringSubmatch(p)[1]
		}
	}

	if base == "" {
		masterBranch, err := localRepo.MasterBranch()
		utils.Check(err)
		base = masterBranch.ShortName()
	}

	trackedBranch, tberr := currentBranch.Upstream()
	if head == "" {
		if err == nil {
			if trackedBranch.IsRemote() {
				if reflect.DeepEqual(baseProject, headProject) && base == trackedBranch.ShortName() {
					e := fmt.Errorf(`Aborted: head branch is the same as base ("%s")`, base)
					e = fmt.Errorf("%s\n(use `-h <branch>` to specify an explicit pull request head)", e)
					utils.Check(e)
				}
			} else {
				// the current branch tracking another branch
				// pretend there's no upstream at all
				tberr = fmt.Errorf("No upstream found for current branch")
			}
		}

		if tberr == nil {
			head = trackedBranch.ShortName()
		} else {
			head = currentBranch.ShortName()
		}
	}

	// when no tracking, assume remote branch is published under active user's fork
	if tberr != nil && !explicitOwner && gh.Config.User != headProject.Owner {
		headProject = github.NewProjectFromString(headProject.Name)
	}

	var title, body string
	if flagPullRequestMessage != "" {
		title, body = readMsg(flagPullRequestMessage)
	}

	if flagPullRequestFile != "" {
		var (
			content []byte
			err     error
		)
		if flagPullRequestFile == "-" {
			content, err = ioutil.ReadAll(os.Stdin)
		} else {
			content, err = ioutil.ReadFile(flagPullRequestFile)
		}
		utils.Check(err)
		title, body = readMsg(string(content))
	}

	fullBase := fmt.Sprintf("%s:%s", baseProject.Owner, base)
	fullHead := fmt.Sprintf("%s:%s", headProject.Owner, head)

	commits, _ := git.RefList(base, head)
	if !force && tberr == nil && len(commits) > 0 {
		err = fmt.Errorf("Aborted: %d commits are not yet pushed to %s", len(commits), trackedBranch.LongName())
		err = fmt.Errorf("%s\n(use `-f` to force submit a pull request anyway)", err)
		utils.Check(err)
	}

	if title == "" && flagPullRequestIssue == "" {
		t, b, err := writePullRequestTitleAndBody(base, head, fullBase, fullHead, commits)
		utils.Check(err)
		title = t
		body = b
	}

	if title == "" && flagPullRequestIssue == "" {
		utils.Check(fmt.Errorf("Aborting due to empty pull request title"))
	}

	var pullRequestURL string
	if args.Noop {
		args.Before(fmt.Sprintf("Would request a pull request to %s from %s", fullBase, fullHead), "")
		pullRequestURL = "PULL_REQUEST_URL"
	} else {
		if title != "" {
			pr, err := gh.CreatePullRequest(base, fullHead, title, body)
			utils.Check(err)
			pullRequestURL = pr.HTMLURL
		}

		if flagPullRequestIssue != "" {
			pr, err := gh.CreatePullRequestForIssue(base, fullHead, flagPullRequestIssue)
			utils.Check(err)
			pullRequestURL = pr.HTMLURL
		}
	}

	args.Replace("echo", "", pullRequestURL)
}

func writePullRequestTitleAndBody(base, head, fullBase, fullHead string, commits []string) (title, body string, err error) {
	messageFile, err := git.PullReqMsgFile()
	if err != nil {
		return
	}
	defer os.Remove(messageFile)

	err = writePullRequestChanges(base, head, fullBase, fullHead, commits, messageFile)
	if err != nil {
		return
	}

	editor, err := git.Editor()
	if err != nil {
		return
	}

	err = editTitleAndBody(editor, messageFile)
	if err != nil {
		err = fmt.Errorf("error using text editor for pull request message")
		return
	}

	title, body, err = readTitleAndBody(messageFile)
	if err != nil {
		return
	}

	return
}

func writePullRequestChanges(base, head, fullBase, fullHead string, commits []string, messageFile string) error {
	var defaultMsg, commitSummary string
	if len(commits) == 1 {
		defaultMsg, err := git.Show(commits[0])
		if err != nil {
			return err
		}
		defaultMsg = fmt.Sprintf("%s\n", defaultMsg)
	} else if len(commits) > 1 {
		commitLogs, err := git.Log(base, head)
		if err != nil {
			return err
		}

		if len(commitLogs) > 0 {
			startRegexp := regexp.MustCompilePOSIX("^")
			endRegexp := regexp.MustCompilePOSIX(" +$")

			commitLogs = strings.TrimSpace(commitLogs)
			commitLogs = startRegexp.ReplaceAllString(commitLogs, "# ")
			commitLogs = endRegexp.ReplaceAllString(commitLogs, "")
			commitSummary = `
#
# Changes:
#
%s`
			commitSummary = fmt.Sprintf(commitSummary, commitLogs)
		}
	}

	message := `%s
# Requesting a pull to %s from %s
#
# Write a message for this pull request. The first block
# of the text is the title and the rest is description.%s
`
	message = fmt.Sprintf(message, defaultMsg, fullBase, fullHead, commitSummary)

	return ioutil.WriteFile(messageFile, []byte(message), 0644)
}

func editTitleAndBody(editor, messageFile string) error {
	editCmd := cmd.New(editor)
	r := regexp.MustCompile("[mg]?vi[m]$")
	if r.MatchString(editor) {
		editCmd.WithArg("-c")
		editCmd.WithArg("set ft=gitcommit tw=0 wrap lbr")
	}
	editCmd.WithArg(messageFile)

	return editCmd.Exec()
}

func readTitleAndBody(messageFile string) (title, body string, err error) {
	f, err := os.Open(messageFile)
	defer f.Close()
	if err != nil {
		return "", "", err
	}

	reader := bufio.NewReader(f)

	return readTitleAndBodyFrom(reader)
}

func readTitleAndBodyFrom(reader *bufio.Reader) (title, body string, err error) {
	r := regexp.MustCompile("\\S")
	var titleParts, bodyParts []string

	line, err := readLine(reader)
	for err == nil {
		if strings.HasPrefix(line, "#") {
			break
		}

		if len(bodyParts) == 0 && r.MatchString(line) {
			titleParts = append(titleParts, line)
		} else {
			bodyParts = append(bodyParts, line)
		}

		line, err = readLine(reader)
	}

	title = strings.Join(titleParts, " ")
	title = strings.TrimSpace(title)

	body = strings.Join(bodyParts, "\n")
	body = strings.TrimSpace(body)

	return title, body, nil
}

func readLine(r *bufio.Reader) (string, error) {
	var (
		isPrefix = true
		err      error
		line, ln []byte
	)

	for isPrefix && err == nil {
		line, isPrefix, err = r.ReadLine()
		ln = append(ln, line...)
	}

	return string(ln), err
}

func readMsg(msg string) (title, body string) {
	split := strings.SplitN(msg, "\n\n", 2)
	title = strings.TrimSpace(split[0])
	if len(split) > 1 {
		body = strings.TrimSpace(split[1])
	}

	return
}
<commit_msg>Change owner only<commit_after>package commands

import (
	"bufio"
	"fmt"
	"github.com/jingweno/gh/cmd"
	"github.com/jingweno/gh/git"
	"github.com/jingweno/gh/github"
	"github.com/jingweno/gh/utils"
	"io/ioutil"
	"os"
	"reflect"
	"regexp"
	"strings"
)

var cmdPullRequest = &Command{
	Run:   pullRequest,
	Usage: "pull-request [-f] [-i ISSUE] [-b BASE] [-h HEAD] [-m MESSAGE] [TITLE]",
	Short: "Open a pull request on GitHub",
	Long: `Opens a pull request on GitHub for the project that the "origin" remote
points to. The default head of the pull request is the current branch.
Both base and head of the pull request can be explicitly given in one of
the following formats: "branch", "owner:branch", "owner/repo:branch".
This command will abort operation if it detects that the current topic
branch has local commits that are not yet pushed to its upstream branch
on the remote. To skip this check, use -f.

If TITLE is omitted, a text editor will open in which title and body of
the pull request can be entered in the same manner as git commit message.

If instead of normal TITLE an issue number is given with -i, the pull
request will be attached to an existing GitHub issue. Alternatively, instead
of title you can paste a full URL to an issue on GitHub.
`,
}

var (
	flagPullRequestBase,
	flagPullRequestHead,
	flagPullRequestIssue,
	flagPullRequestMessage,
	flagPullRequestFile string
	flagPullRequestForce bool
)

func init() {
	cmdPullRequest.Flag.StringVar(&flagPullRequestBase, "b", "", "BASE")
	cmdPullRequest.Flag.StringVar(&flagPullRequestHead, "h", "", "HEAD")
	cmdPullRequest.Flag.StringVar(&flagPullRequestIssue, "i", "", "ISSUE")
	cmdPullRequest.Flag.StringVar(&flagPullRequestMessage, "m", "", "MESSAGE")
	cmdPullRequest.Flag.BoolVar(&flagPullRequestForce, "f", false, "FORCE")
	cmdPullRequest.Flag.StringVar(&flagPullRequestFile, "F", "", "FILE")
	cmdPullRequest.Flag.StringVar(&flagPullRequestFile, "file", "", "FILE")
}

/*
  # while on a topic branch called "feature":
  $ gh pull-request
  [ opens text editor to edit title & body for the request ]
  [ opened pull request on GitHub for "YOUR_USER:feature" ]

  # explicit pull base & head:
  $ gh pull-request -b jingweno:master -h jingweno:feature

  $ gh pull-request -m "title\n\nbody"
  [ create pull request with title & body  ]

  $ gh pull-request -i 123
  [ attached pull request to issue #123 ]

  $ gh pull-request https://github.com/jingweno/gh/pull/123
  [ attached pull request to issue #123 ]
*/
func pullRequest(cmd *Command, args *Args) {
	localRepo := github.LocalRepo()

	currentBranch, err := localRepo.CurrentBranch()
	utils.Check(err)

	baseProject, err := localRepo.MainProject()
	utils.Check(err)

	headProject, err := localRepo.CurrentProject()
	utils.Check(err)

	gh := github.NewWithoutProject()
	gh.Project = baseProject

	var (
		base, head           string
		force, explicitOwner bool
	)
	force = flagPullRequestForce
	if flagPullRequestBase != "" {
		if strings.Contains(flagPullRequestBase, ":") {
			split := strings.SplitN(flagPullRequestBase, ":", 2)
			base = split[1]
			baseProject.Owner = split[0]
		} else {
			base = flagPullRequestBase
		}
	}

	if flagPullRequestHead != "" {
		if strings.Contains(flagPullRequestHead, ":") {
			split := strings.SplitN(flagPullRequestHead, ":", 2)
			head = split[1]
			headProject.Owner = split[0]
			explicitOwner = true
		} else {
			head = flagPullRequestHead
		}
	}

	if args.ParamsSize() == 1 {
		arg := args.RemoveParam(0)
		u, e := github.ParseURL(arg)
		r := regexp.MustCompile(`^issues\/(\d+)`)
		p := u.ProjectPath()
		if e == nil && r.MatchString(p) {
			flagPullRequestIssue = r.FindStringSubmatch(p)[1]
		}
	}

	if base == "" {
		masterBranch, err := localRepo.MasterBranch()
		utils.Check(err)
		base = masterBranch.ShortName()
	}

	trackedBranch, tberr := currentBranch.Upstream()
	if head == "" {
		if err == nil {
			if trackedBranch.IsRemote() {
				if reflect.DeepEqual(baseProject, headProject) && base == trackedBranch.ShortName() {
					e := fmt.Errorf(`Aborted: head branch is the same as base ("%s")`, base)
					e = fmt.Errorf("%s\n(use `-h <branch>` to specify an explicit pull request head)", e)
					utils.Check(e)
				}
			} else {
				// the current branch tracking another branch
				// pretend there's no upstream at all
				tberr = fmt.Errorf("No upstream found for current branch")
			}
		}

		if tberr == nil {
			head = trackedBranch.ShortName()
		} else {
			head = currentBranch.ShortName()
		}
	}

	// when no tracking, assume remote branch is published under active user's fork
	if tberr != nil && !explicitOwner && gh.Config.User != headProject.Owner {
		headProject = github.NewProjectFromString(headProject.Name)
	}

	var title, body string
	if flagPullRequestMessage != "" {
		title, body = readMsg(flagPullRequestMessage)
	}

	if flagPullRequestFile != "" {
		var (
			content []byte
			err     error
		)
		if flagPullRequestFile == "-" {
			content, err = ioutil.ReadAll(os.Stdin)
		} else {
			content, err = ioutil.ReadFile(flagPullRequestFile)
		}
		utils.Check(err)
		title, body = readMsg(string(content))
	}

	fullBase := fmt.Sprintf("%s:%s", baseProject.Owner, base)
	fullHead := fmt.Sprintf("%s:%s", headProject.Owner, head)

	commits, _ := git.RefList(base, head)
	if !force && tberr == nil && len(commits) > 0 {
		err = fmt.Errorf("Aborted: %d commits are not yet pushed to %s", len(commits), trackedBranch.LongName())
		err = fmt.Errorf("%s\n(use `-f` to force submit a pull request anyway)", err)
		utils.Check(err)
	}

	if title == "" && flagPullRequestIssue == "" {
		t, b, err := writePullRequestTitleAndBody(base, head, fullBase, fullHead, commits)
		utils.Check(err)
		title = t
		body = b
	}

	if title == "" && flagPullRequestIssue == "" {
		utils.Check(fmt.Errorf("Aborting due to empty pull request title"))
	}

	var pullRequestURL string
	if args.Noop {
		args.Before(fmt.Sprintf("Would request a pull request to %s from %s", fullBase, fullHead), "")
		pullRequestURL = "PULL_REQUEST_URL"
	} else {
		if title != "" {
			pr, err := gh.CreatePullRequest(base, fullHead, title, body)
			utils.Check(err)
			pullRequestURL = pr.HTMLURL
		}

		if flagPullRequestIssue != "" {
			pr, err := gh.CreatePullRequestForIssue(base, fullHead, flagPullRequestIssue)
			utils.Check(err)
			pullRequestURL = pr.HTMLURL
		}
	}

	args.Replace("echo", "", pullRequestURL)
}

func writePullRequestTitleAndBody(base, head, fullBase, fullHead string, commits []string) (title, body string, err error) {
	messageFile, err := git.PullReqMsgFile()
	if err != nil {
		return
	}
	defer os.Remove(messageFile)

	err = writePullRequestChanges(base, head, fullBase, fullHead, commits, messageFile)
	if err != nil {
		return
	}

	editor, err := git.Editor()
	if err != nil {
		return
	}

	err = editTitleAndBody(editor, messageFile)
	if err != nil {
		err = fmt.Errorf("error using text editor for pull request message")
		return
	}

	title, body, err = readTitleAndBody(messageFile)
	if err != nil {
		return
	}

	return
}

func writePullRequestChanges(base, head, fullBase, fullHead string, commits []string, messageFile string) error {
	var defaultMsg, commitSummary string
	if len(commits) == 1 {
		defaultMsg, err := git.Show(commits[0])
		if err != nil {
			return err
		}
		defaultMsg = fmt.Sprintf("%s\n", defaultMsg)
	} else if len(commits) > 1 {
		commitLogs, err := git.Log(base, head)
		if err != nil {
			return err
		}

		if len(commitLogs) > 0 {
			startRegexp := regexp.MustCompilePOSIX("^")
			endRegexp := regexp.MustCompilePOSIX(" +$")

			commitLogs = strings.TrimSpace(commitLogs)
			commitLogs = startRegexp.ReplaceAllString(commitLogs, "# ")
			commitLogs = endRegexp.ReplaceAllString(commitLogs, "")
			commitSummary = `
#
# Changes:
#
%s`
			commitSummary = fmt.Sprintf(commitSummary, commitLogs)
		}
	}

	message := `%s
# Requesting a pull to %s from %s
#
# Write a message for this pull request. The first block
# of the text is the title and the rest is description.%s
`
	message = fmt.Sprintf(message, defaultMsg, fullBase, fullHead, commitSummary)

	return ioutil.WriteFile(messageFile, []byte(message), 0644)
}

func editTitleAndBody(editor, messageFile string) error {
	editCmd := cmd.New(editor)
	r := regexp.MustCompile("[mg]?vi[m]$")
	if r.MatchString(editor) {
		editCmd.WithArg("-c")
		editCmd.WithArg("set ft=gitcommit tw=0 wrap lbr")
	}
	editCmd.WithArg(messageFile)

	return editCmd.Exec()
}

func readTitleAndBody(messageFile string) (title, body string, err error) {
	f, err := os.Open(messageFile)
	defer f.Close()
	if err != nil {
		return "", "", err
	}

	reader := bufio.NewReader(f)

	return readTitleAndBodyFrom(reader)
}

func readTitleAndBodyFrom(reader *bufio.Reader) (title, body string, err error) {
	r := regexp.MustCompile("\\S")
	var titleParts, bodyParts []string

	line, err := readLine(reader)
	for err == nil {
		if strings.HasPrefix(line, "#") {
			break
		}

		if len(bodyParts) == 0 && r.MatchString(line) {
			titleParts = append(titleParts, line)
		} else {
			bodyParts = append(bodyParts, line)
		}

		line, err = readLine(reader)
	}

	title = strings.Join(titleParts, " ")
	title = strings.TrimSpace(title)

	body = strings.Join(bodyParts, "\n")
	body = strings.TrimSpace(body)

	return title, body, nil
}

func readLine(r *bufio.Reader) (string, error) {
	var (
		isPrefix = true
		err      error
		line, ln []byte
	)

	for isPrefix && err == nil {
		line, isPrefix, err = r.ReadLine()
		ln = append(ln, line...)
	}

	return string(ln), err
}

func readMsg(msg string) (title, body string) {
	split := strings.SplitN(msg, "\n\n", 2)
	title = strings.TrimSpace(split[0])
	if len(split) > 1 {
		body = strings.TrimSpace(split[1])
	}

	return
}
<|endoftext|>


<commit_before>package openx

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"path"
	"strconv"
	"strings"

	"github.com/mrjones/oauth"
	log "github.com/sirupsen/logrus"
)

// oauth global consumer
var consumer *oauth.Consumer

// At the moment i'm only supporting the APIPath2 and 4.0
const (
	version          = "0.0.1"
	requestTokenURL  = "https://sso.openx.com/api/index/initiate"
	accessTokenURL   = "https://sso.openx.com/api/index/token"
	authorizationURL = "https://sso.openx.com/login/process"
	apiPath          = "/ox/4.0/"
	callBack         = "oob"
)

// Client holds all the user information, all of it is private however
// as a result only the defined methods below are used to interact with the data
// Go doesn't provide predifined PUT, PATCH, or DELETE requests, but they can be
// defined a number of ways.
type Client struct {
	domain          string
	realm           string
	scheme          string
	consumerKey     string
	consumerSecrect string
	email           string
	password        string
	apiPath         string
	timeOut         int
	session         *http.Client
}

// Get ...
func (c *Client) Get(url string, urlParms map[string]interface{}) (res *http.Response, err error) {
	url = c.resolveURL(url)
	if urlParms != nil {
		p := "?"
		for key, value := range urlParms {
			var v string
			switch value.(type) {
			case string:
				v = value.(string)
			case int:
				v = strconv.Itoa(value.(int))
			case float64:
				v = strconv.FormatFloat(value.(float64), 'f', -1, 64)
			case bool:
				v = strconv.FormatBool(value.(bool))
			default:
				log.Fatalln("The value entered %v must be of type string, int, float64, or bool")
			}
			p += key + "=" + v + "&"
		}
		url += p[:len(p)-1]
	}
	res, err = c.session.Get(url)
	return
}

// Delete ...
func (c *Client) Delete(url string, data io.Reader) (res *http.Response, err error) {
	request, err := http.NewRequest("DELETE", c.resolveURL(url), data)
	if err != nil {
		log.Fatalf("Could not create DELETE requests: %v", err)
	}
	res, err = c.session.Do(request)
	return
}

// Options ...
func (c *Client) Options(url string) (res *http.Response, err error) {
	if !strings.Contains(url, "/options") {
		url = path.Join("/options", url)
	}
	res, err = c.session.Get(c.resolveURL(url))
	return
}

// Put ...
func (c *Client) Put(url string, data io.Reader) (res *http.Response, err error) {
	request, err := http.NewRequest("PUT", c.resolveURL(url), data)
	if err != nil {
		log.Fatalf("Could not create PUT requests: %v", err)
	}
	res, err = c.session.Do(request)
	return
}

// Post ...
func (c *Client) Post(url string, contentType string, data io.Reader) (res *http.Response, err error) {
	res, err = c.session.Post(c.resolveURL(url), contentType, data)

	return
}

// PostForm ...
func (c *Client) PostForm(url string, data url.Values) (res *http.Response, err error) {
	res, err = c.session.PostForm(c.resolveURL(url), data)
	return
}

// LogOff ...
func (c *Client) LogOff() (res *http.Response, err error) {
	// set the session to an empty struct to clear auth information
	c.session = &http.Client{}
	return
}

func (c *Client) resolveURL(endpoint string) (u string) {
	rawURL, err := url.Parse(endpoint)
	if err != nil {
		log.Fatalln("Could not parse endpoint: %v", err)
	}
	if rawURL.Scheme == "" {
		u = fmt.Sprintf("%s://", c.scheme) + path.Join(c.domain, c.apiPath, rawURL.Path)
	}
	return
}

func (c *Client) getAccessToken(debug bool) *oauth.AccessToken {
	requestToken, requestURL, err := consumer.GetRequestTokenAndUrl(callBack)
	if err != nil {
		log.Fatalf("Requests token could not be generated %v", err)
	}
	if debug {
		log.Info("Requests Token generated")
	}

	// auth into openx
	request := http.Client{}
	urlData := url.Values{}
	urlData.Set("email", c.email)
	urlData.Set("password", c.password)
	urlData.Set("oauth_token", requestToken.Token)
	resp, err := request.PostForm(requestURL, urlData)
	if err != nil {
		log.Fatalf("Could not get authorization token: %v", err)
	}
	if debug {
		log.Info("Getting auth token")
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		log.Fatalf("Could not get authorization status returned: %d", resp.StatusCode)
	}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatalf("Could not read body: %v", err)
	}
	// parse the response, should contain oauth_verifier
	raw, err := url.Parse(string(body))
	if err != nil {
		log.Fatalf("Could not parse url: %v", err)
	}
	authInfo := raw.Query()
	var oauthVerifier string
	if val, ok := authInfo["oauth_verifier"]; !ok {
		log.Fatalln("oauth_verifier key not in response", err)
	} else {
		oauthVerifier = val[0]
	}
	// use oauth_verifier to get access_token
	accessToken, err := consumer.AuthorizeToken(requestToken, oauthVerifier)
	if err != nil {
		log.Fatalf("Access token could not be generated %v", err)
	}
	if debug {
		log.Info("Access Token generated")
	}
	return accessToken
}

// NewClient ...
func NewClient(domain, realm, consumerKey, consumerSecrect, email, password string, debug bool) (*Client, error) {
	if strings.TrimSpace(domain) == "" {
		return &Client{}, fmt.Errorf("Domain cannot be emtpy")
	}
	if strings.TrimSpace(realm) == "" {
		return &Client{}, fmt.Errorf("Realm cannot be emtpy")
	}
	if strings.TrimSpace(consumerKey) == "" {
		return &Client{}, fmt.Errorf("Consumer key cannot be emtpy")
	}
	if strings.TrimSpace(consumerSecrect) == "" {
		return &Client{}, fmt.Errorf("Consumer secrect cannot be emtpy")
	}
	if strings.TrimSpace(email) == "" {
		return &Client{}, fmt.Errorf("email cannot be emtpy")
	}
	if strings.TrimSpace(password) == "" {
		return &Client{}, fmt.Errorf("password cannot be emtpy")
	}

	// create base client default to http
	c := &Client{
		domain:          domain,
		realm:           realm,
		consumerKey:     consumerKey,
		consumerSecrect: consumerSecrect,
		apiPath:         apiPath,
		email:           email,
		password:        password,
		scheme:          "http",
	}

	// create oauth consumer
	consumer = oauth.NewConsumer(c.consumerKey, c.consumerSecrect, oauth.ServiceProvider{
		RequestTokenUrl:   requestTokenURL,
		AuthorizeTokenUrl: authorizationURL,
		AccessTokenUrl:    accessTokenURL,
		HttpMethod:        "POST",
	})
	consumer.Debug(debug)

	accessToken := c.getAccessToken(debug)

	// create a cookie jar to add the access token to
	cj, err := cookiejar.New(nil)
	if err != nil {
		log.Fatalf("Cookiejar could not be created %v", err)
	}

	// clean up entered domain just incase user passes in a domain in a way I'm not ready for
	r := strings.NewReplacer(
		"www.", "",
		"http://", "",
		"https://", "",
		"//", "",
		"/", "",
	)

	c.domain = r.Replace(c.domain)
	// format the domain
	base, err := url.Parse(fmt.Sprintf("%s://www.%s", c.scheme, c.domain))
	if err != nil {
		log.Fatalf("Domain could not be parsed to type url %v", err)
	}

	// create auth cookie
	var cookies []*http.Cookie
	cookie := &http.Cookie{
		Name:   "openx3_access_token",
		Value:  accessToken.Token,
		Path:   "/",
		Domain: c.domain,
		Secure: false,
		// HttpOnly: false,
	}
	cookies = append(cookies, cookie)
	cj.SetCookies(base, cookies)

	// create authenticated session
	session, err := consumer.MakeHttpClient(accessToken)
	if err != nil {
		log.Fatalf("Could not create client %v", err)
	}
	session.Jar = cj
	c.session = session
	return c, nil
}
<commit_msg>adding some documentation<commit_after>package openx

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/cookiejar"
	"net/url"
	"path"
	"strconv"
	"strings"

	"github.com/mrjones/oauth"
	log "github.com/sirupsen/logrus"
)

// oauth global consumer
var consumer *oauth.Consumer

// At the moment i'm only supporting the APIPath2 and 4.0
const (
	version          = "0.0.1"
	requestTokenURL  = "https://sso.openx.com/api/index/initiate"
	accessTokenURL   = "https://sso.openx.com/api/index/token"
	authorizationURL = "https://sso.openx.com/login/process"
	apiPath          = "/ox/4.0/"
	callBack         = "oob"
)

// Client holds all the user information, all of it is private however and
// as a result only the defined methods below are used to interact with the data
type Client struct {
	domain          string
	realm           string
	scheme          string
	consumerKey     string
	consumerSecrect string
	email           string
	password        string
	apiPath         string
	timeOut         int
	session         *http.Client
}

// Get is simailiar to the normal Go *http.Client.Get
// except string parameters can be pass in in the url or the as a map[string]interface{}
func (c *Client) Get(url string, urlParms map[string]interface{}) (res *http.Response, err error) {
	url = c.resolveURL(url)
	if urlParms != nil {
		p := "?"
		for key, value := range urlParms {
			var v string
			switch value.(type) {
			case string:
				v = value.(string)
			case int:
				v = strconv.Itoa(value.(int))
			case float64:
				v = strconv.FormatFloat(value.(float64), 'f', -1, 64)
			case bool:
				v = strconv.FormatBool(value.(bool))
			default:
				log.Fatalln("The value entered %v must be of type string, int, float64, or bool")
			}
			p += key + "=" + v + "&"
		}
		url += p[:len(p)-1]
	}
	res, err = c.session.Get(url)
	return
}

// Delete creates a delete request which the Openx3 API uses, but that is not defined by Go
func (c *Client) Delete(url string, data io.Reader) (res *http.Response, err error) {
	request, err := http.NewRequest("DELETE", c.resolveURL(url), data)
	if err != nil {
		log.Fatalf("Could not create DELETE requests: %v", err)
	}
	res, err = c.session.Do(request)
	return
}

// Options is a wrapper for a GET request that has the /options endpoint already passed in
func (c *Client) Options(url string) (res *http.Response, err error) {
	if !strings.Contains(url, "/options") {
		url = path.Join("/options", url)
	}
	res, err = c.session.Get(c.resolveURL(url))
	return
}

// Put creates a put request which the Openx3 API uses, but that is not defined by Go
func (c *Client) Put(url string, data io.Reader) (res *http.Response, err error) {
	request, err := http.NewRequest("PUT", c.resolveURL(url), data)
	if err != nil {
		log.Fatalf("Could not create PUT requests: %v", err)
	}
	res, err = c.session.Do(request)
	return
}

// Post is a wrapper for the basic Go *http.Client.Post
func (c *Client) Post(url string, contentType string, data io.Reader) (res *http.Response, err error) {
	res, err = c.session.Post(c.resolveURL(url), contentType, data)

	return
}

// PostForm is a wrapper for the basic Go *http.Client.PostForm
func (c *Client) PostForm(url string, data url.Values) (res *http.Response, err error) {
	res, err = c.session.PostForm(c.resolveURL(url), data)
	return
}

// LogOff sets the created session to an empty http.Client, destorying the stored cookie
func (c *Client) LogOff() (res *http.Response, err error) {
	// set the session to an empty struct to clear auth information
	c.session = &http.Client{}
	return
}

func (c *Client) resolveURL(endpoint string) (u string) {
	rawURL, err := url.Parse(endpoint)
	if err != nil {
		log.Fatalln("Could not parse endpoint: %v", err)
	}
	if rawURL.Scheme == "" {
		u = fmt.Sprintf("%s://", c.scheme) + path.Join(c.domain, c.apiPath, rawURL.Path)
	}
	return
}

func (c *Client) getAccessToken(debug bool) *oauth.AccessToken {
	requestToken, requestURL, err := consumer.GetRequestTokenAndUrl(callBack)
	if err != nil {
		log.Fatalf("Requests token could not be generated %v", err)
	}
	if debug {
		log.Info("Requests Token generated")
	}

	// auth into openx
	request := http.Client{}
	urlData := url.Values{}
	urlData.Set("email", c.email)
	urlData.Set("password", c.password)
	urlData.Set("oauth_token", requestToken.Token)
	resp, err := request.PostForm(requestURL, urlData)
	if err != nil {
		log.Fatalf("Could not get authorization token: %v", err)
	}
	if debug {
		log.Info("Getting auth token")
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		log.Fatalf("Could not get authorization status returned: %d", resp.StatusCode)
	}
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatalf("Could not read body: %v", err)
	}
	// parse the response, should contain oauth_verifier
	raw, err := url.Parse(string(body))
	if err != nil {
		log.Fatalf("Could not parse url: %v", err)
	}
	authInfo := raw.Query()
	var oauthVerifier string
	if val, ok := authInfo["oauth_verifier"]; !ok {
		log.Fatalln("oauth_verifier key not in response", err)
	} else {
		oauthVerifier = val[0]
	}
	// use oauth_verifier to get access_token
	accessToken, err := consumer.AuthorizeToken(requestToken, oauthVerifier)
	if err != nil {
		log.Fatalf("Access token could not be generated %v", err)
	}
	if debug {
		log.Info("Access Token generated")
	}
	return accessToken
}

// NewClient creates the basic Openx3 *Client via oauth1
func NewClient(domain, realm, consumerKey, consumerSecrect, email, password string, debug bool) (*Client, error) {
	if strings.TrimSpace(domain) == "" {
		return &Client{}, fmt.Errorf("Domain cannot be emtpy")
	}
	if strings.TrimSpace(realm) == "" {
		return &Client{}, fmt.Errorf("Realm cannot be emtpy")
	}
	if strings.TrimSpace(consumerKey) == "" {
		return &Client{}, fmt.Errorf("Consumer key cannot be emtpy")
	}
	if strings.TrimSpace(consumerSecrect) == "" {
		return &Client{}, fmt.Errorf("Consumer secrect cannot be emtpy")
	}
	if strings.TrimSpace(email) == "" {
		return &Client{}, fmt.Errorf("email cannot be emtpy")
	}
	if strings.TrimSpace(password) == "" {
		return &Client{}, fmt.Errorf("password cannot be emtpy")
	}

	// create base client default to http
	c := &Client{
		domain:          domain,
		realm:           realm,
		consumerKey:     consumerKey,
		consumerSecrect: consumerSecrect,
		apiPath:         apiPath,
		email:           email,
		password:        password,
		scheme:          "http",
	}

	// create oauth consumer
	consumer = oauth.NewConsumer(c.consumerKey, c.consumerSecrect, oauth.ServiceProvider{
		RequestTokenUrl:   requestTokenURL,
		AuthorizeTokenUrl: authorizationURL,
		AccessTokenUrl:    accessTokenURL,
		HttpMethod:        "POST",
	})
	consumer.Debug(debug)

	accessToken := c.getAccessToken(debug)

	// create a cookie jar to add the access token to
	cj, err := cookiejar.New(nil)
	if err != nil {
		log.Fatalf("Cookiejar could not be created %v", err)
	}

	// clean up entered domain just incase user passes in a domain in a way I'm not ready for
	r := strings.NewReplacer(
		"www.", "",
		"http://", "",
		"https://", "",
		"//", "",
		"/", "",
	)

	c.domain = r.Replace(c.domain)
	// format the domain
	base, err := url.Parse(fmt.Sprintf("%s://www.%s", c.scheme, c.domain))
	if err != nil {
		log.Fatalf("Domain could not be parsed to type url %v", err)
	}

	// create auth cookie
	var cookies []*http.Cookie
	cookie := &http.Cookie{
		Name:   "openx3_access_token",
		Value:  accessToken.Token,
		Path:   "/",
		Domain: c.domain,
		Secure: false,
		// HttpOnly: false,
	}
	cookies = append(cookies, cookie)
	cj.SetCookies(base, cookies)

	// create authenticated session
	session, err := consumer.MakeHttpClient(accessToken)
	if err != nil {
		log.Fatalf("Could not create client %v", err)
	}
	session.Jar = cj
	c.session = session
	return c, nil
}
<|endoftext|>


<commit_before>// Copyright 2015, David Howden
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package index

import (
	"crypto/sha1"
	"fmt"
	"sort"
	"strconv"

	"tchaik.com/index/attr"
)

// Tracker is an interface which defines the Tracks method which returns a list
// of Tracks.
type Tracker interface {
	Tracks() []Track
}

// Group is an interface which represents a named group of Tracks.
type Group interface {
	Tracker

	// Name returns the name of the group.
	Name() string

	// Field returns the value of a field.
	Field(string) interface{}
}

// Collection is an interface which represents an ordered series of Groups.
type Collection interface {
	Group

	// Keys returns a slice of Keys which give an ordering for Groups in the Collection.
	Keys() []Key

	// Get returns the Group corresponding to the given Key.
	Get(Key) Group
}

// Collect applies the Collector to the Tracker and returns the resulting Collection.
func Collect(t Tracker, c Collector) Collection {
	return c.Collect(t)
}

// CollectionPaths creates a slice of Paths which contains the path for each immediate Group
// in the Ccollection.
func CollectionPaths(c Collection, root Path) []Path {
	keys := c.Keys()
	paths := make([]Path, len(keys))
	for _, k := range keys {
		p := make(Path, len(root)+1)
		copy(p, root)
		p[len(root)] = k

		paths = append(paths, p)
	}
	return paths
}

// GroupFromPath returns the Group which represents the given Path.
func GroupFromPath(g Group, p Path) (Group, error) {
	if c, ok := g.(Collection); ok {
		ng := c.Get(p[0])
		if ng == nil {
			return nil, fmt.Errorf("invalid key: '%v'", p[0])
		}
		if len(p) > 1 {
			return GroupFromPath(ng, p[1:])
		}
		return ng, nil
	}
	if len(p) > 1 {
		return nil, fmt.Errorf("invalid path: reached leaf group and remaining path: %v", p)
	}
	return g, nil
}

// NewCollection creates a new collection from a source collection `c` which will have the groups
// represented by the given list of paths.
func NewPathsCollection(src Collection, paths []Path) Collection {
	done := make(map[Key]bool)
	keys := make([]Key, 0, len(paths))
	for _, path := range paths {
		k := path[1]
		if !done[k] {
			keys = append(keys, path[1])
			done[k] = true
		}
	}

	return pathsCollection{
		Collection: src,
		name:       "paths collection",
		keys:       keys,
	}
}

type pathsCollection struct {
	Collection
	name string
	keys []Key
}

// Keys implements Collection.
func (c pathsCollection) Keys() []Key { return c.keys }

// Name implements Group.
func (c pathsCollection) Name() string { return c.name }

// Field implements Group.
func (c pathsCollection) Field(string) interface{} { return nil }

func (c pathsCollection) Tracks() []Track {
	// TODO: Do something better here - this method shouldn't really get called
	return nil
}

// col is a basic implementation of Collection. It assumes that all Groups have unique names
// and so uses Group names for the keys.
type col struct {
	keys []Key
	name string
	grps map[Key]group
	flds map[string]interface{}
}

func newCol(name string) col {
	return col{
		name: name,
		grps: make(map[Key]group),
		flds: make(map[string]interface{}),
	}
}

func (c col) Keys() []Key     { return c.keys }
func (c col) Name() string    { return c.name }
func (c col) Get(k Key) Group { return c.grps[k] }

func (c col) Field(field string) interface{} { return c.flds[field] }

func (c col) Tracks() []Track {
	return collectionTracks(c)
}

// addTrack adds the track t to the collection by adding it to a group with key k.  If no such
// group exists in the collection, then a new group is created with name n.
func (c *col) addTrack(n string, k Key, t Track) {
	if g, ok := c.grps[k]; ok {
		g.tracks = append(g.tracks, t)
		c.grps[k] = g
		return
	}
	g := group{
		name:   n,
		tracks: make([]Track, 1),
	}
	g.tracks[0] = t
	c.grps[k] = g
	c.keys = append(c.keys, k)
}

// add adds the track t to the collection, using the name n as the key.
func (c *col) add(n string, t Track) {
	k := Key(fmt.Sprintf("%x", sha1.Sum([]byte(n)))[:6])
	c.addTrack(n, k, t)
}

// collectionTracks iterates over all the tracks in all the groups of the collection to construct a
// slice of Tracks.
func collectionTracks(c Collection) []Track {
	keys := c.Keys()
	var tracks []Track
	for _, k := range keys {
		tracks = append(tracks, c.Get(k).Tracks()...)
	}
	return tracks
}

// Collector is an interface which defines the Collect method.
type Collector interface {
	Collect(Tracker) Collection
}

// group is a basic implementation of Group
type group struct {
	name   string
	tracks []Track
	fields map[string]interface{}
}

// Name implements Group.
func (g group) Name() string { return g.name }

// Tracks implements Tracker.
func (g group) Tracks() []Track { return g.tracks }

// Field implements Group.
func (g group) Field(field string) interface{} { return g.fields[field] }

// subCol is a wrapper around an existing collection which overrides the Get
// method.
type subCol struct {
	Collection
	grps map[Key]Group
	flds map[string]interface{}
}

// Get implements Collection.
func (sg subCol) Get(k Key) Group {
	return sg.grps[k]
}

// Field implements Group.
func (sg subCol) Field(f string) interface{} {
	if x, ok := sg.flds[f]; ok {
		return x
	}
	return sg.Collection.Field(f)
}

// SubCollect applies the given Collector to each of the "leaf" Groups
// in the Collection.
func SubCollect(c Collection, r Collector) Collection {
	keys := c.Keys()
	nc := subCol{
		Collection: c,
		grps:       make(map[Key]Group, len(keys)),
		flds:       make(map[string]interface{}),
	}
	for _, k := range keys {
		g := c.Get(k)
		if gc, ok := g.(Collection); ok {
			nc.grps[k] = SubCollect(gc, r)
			continue
		}
		nc.grps[k] = r.Collect(g)
	}
	return nc
}

// WalkFn is the type of the function called for each Track visited by Walk.  Return
// non-nil error from Walk to stop the trasversal, and return the error from Walk.
type WalkFn func(Track, Path) error

func walkCollection(c Collection, p Path, f WalkFn) error {
	for _, k := range c.Keys() {
		g := c.Get(k)
		np := make(Path, len(p)+1)
		copy(np, p)
		np[len(p)] = k
		err := Walk(g, np, f)
		if err != nil {
			return err
		}
	}
	return nil
}

// Walk transverses the Group and calls the WalkFn on each Track.  The Path is assumed to be
// the path of the Group.
func Walk(g Group, p Path, f WalkFn) error {
	if gc, ok := g.(Collection); ok {
		return walkCollection(gc, p, f)
	}
	for i, t := range g.Tracks() {
		np := make(Path, len(p)+1)
		copy(np, p)
		np[len(p)] = Key(strconv.Itoa(i))
		err := f(t, np)
		if err != nil {
			return err
		}
	}
	return nil
}

type trackPath struct {
	t Track
	p Path
}

type trackPathSorter struct {
	tp []trackPath
	fn LessFn
}

func (tps trackPathSorter) Len() int           { return len(tps.tp) }
func (tps trackPathSorter) Swap(i, j int)      { tps.tp[i], tps.tp[j] = tps.tp[j], tps.tp[i] }
func (tps trackPathSorter) Less(i, j int) bool { return tps.fn(tps.tp[i].t, tps.tp[j].t) }

func Recent(c Collection, n int) []Path {
	var trackPaths []trackPath
	walkfn := func(t Track, p Path) error {
		trackPaths = append(trackPaths, trackPath{t, p[:2]})
		return nil
	}
	Walk(c, Path([]Key{"Root"}), walkfn)

	sort.Sort(sort.Reverse(trackPathSorter{trackPaths, SortByTime("DateAdded")}))

	dedup := make(map[string]bool)
	result := make([]Path, 0, n)
	for _, tp := range trackPaths {
		e := tp.p.Encode()
		if !dedup[e] {
			dedup[e] = true
			result = append(result, tp.p)
		}
		if len(result) == n {
			break
		}
	}
	return result
}

// By is a function which returns a Collector to group a collection using the given
// attribute.
func By(a attr.Interface) Collector {
	return groupBy{a}
}

type groupBy struct {
	attr.Interface
}

// Collect implements Collector
func (a groupBy) Collect(tracker Tracker) Collection {
	name := "by " + a.Name()
	if tg, ok := tracker.(Group); ok {
		name = tg.Name()
	}
	gg := newCol(name)
	for _, t := range tracker.Tracks() {
		gg.add(fmt.Sprintf("%v", a.Value(t)), t)
	}
	return gg
}
<commit_msg>Fix doc typo.<commit_after>// Copyright 2015, David Howden
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package index

import (
	"crypto/sha1"
	"fmt"
	"sort"
	"strconv"

	"tchaik.com/index/attr"
)

// Tracker is an interface which defines the Tracks method which returns a list
// of Tracks.
type Tracker interface {
	Tracks() []Track
}

// Group is an interface which represents a named group of Tracks.
type Group interface {
	Tracker

	// Name returns the name of the group.
	Name() string

	// Field returns the value of a field.
	Field(string) interface{}
}

// Collection is an interface which represents an ordered series of Groups.
type Collection interface {
	Group

	// Keys returns a slice of Keys which give an ordering for Groups in the Collection.
	Keys() []Key

	// Get returns the Group corresponding to the given Key.
	Get(Key) Group
}

// Collect applies the Collector to the Tracker and returns the resulting Collection.
func Collect(t Tracker, c Collector) Collection {
	return c.Collect(t)
}

// CollectionPaths creates a slice of Paths which contains the path for each immediate Group
// in the Collection.
func CollectionPaths(c Collection, root Path) []Path {
	keys := c.Keys()
	paths := make([]Path, len(keys))
	for _, k := range keys {
		p := make(Path, len(root)+1)
		copy(p, root)
		p[len(root)] = k

		paths = append(paths, p)
	}
	return paths
}

// GroupFromPath returns the Group which represents the given Path.
func GroupFromPath(g Group, p Path) (Group, error) {
	if c, ok := g.(Collection); ok {
		ng := c.Get(p[0])
		if ng == nil {
			return nil, fmt.Errorf("invalid key: '%v'", p[0])
		}
		if len(p) > 1 {
			return GroupFromPath(ng, p[1:])
		}
		return ng, nil
	}
	if len(p) > 1 {
		return nil, fmt.Errorf("invalid path: reached leaf group and remaining path: %v", p)
	}
	return g, nil
}

// NewCollection creates a new collection from a source collection `c` which will have the groups
// represented by the given list of paths.
func NewPathsCollection(src Collection, paths []Path) Collection {
	done := make(map[Key]bool)
	keys := make([]Key, 0, len(paths))
	for _, path := range paths {
		k := path[1]
		if !done[k] {
			keys = append(keys, path[1])
			done[k] = true
		}
	}

	return pathsCollection{
		Collection: src,
		name:       "paths collection",
		keys:       keys,
	}
}

type pathsCollection struct {
	Collection
	name string
	keys []Key
}

// Keys implements Collection.
func (c pathsCollection) Keys() []Key { return c.keys }

// Name implements Group.
func (c pathsCollection) Name() string { return c.name }

// Field implements Group.
func (c pathsCollection) Field(string) interface{} { return nil }

func (c pathsCollection) Tracks() []Track {
	// TODO: Do something better here - this method shouldn't really get called
	return nil
}

// col is a basic implementation of Collection. It assumes that all Groups have unique names
// and so uses Group names for the keys.
type col struct {
	keys []Key
	name string
	grps map[Key]group
	flds map[string]interface{}
}

func newCol(name string) col {
	return col{
		name: name,
		grps: make(map[Key]group),
		flds: make(map[string]interface{}),
	}
}

func (c col) Keys() []Key     { return c.keys }
func (c col) Name() string    { return c.name }
func (c col) Get(k Key) Group { return c.grps[k] }

func (c col) Field(field string) interface{} { return c.flds[field] }

func (c col) Tracks() []Track {
	return collectionTracks(c)
}

// addTrack adds the track t to the collection by adding it to a group with key k.  If no such
// group exists in the collection, then a new group is created with name n.
func (c *col) addTrack(n string, k Key, t Track) {
	if g, ok := c.grps[k]; ok {
		g.tracks = append(g.tracks, t)
		c.grps[k] = g
		return
	}
	g := group{
		name:   n,
		tracks: make([]Track, 1),
	}
	g.tracks[0] = t
	c.grps[k] = g
	c.keys = append(c.keys, k)
}

// add adds the track t to the collection, using the name n as the key.
func (c *col) add(n string, t Track) {
	k := Key(fmt.Sprintf("%x", sha1.Sum([]byte(n)))[:6])
	c.addTrack(n, k, t)
}

// collectionTracks iterates over all the tracks in all the groups of the collection to construct a
// slice of Tracks.
func collectionTracks(c Collection) []Track {
	keys := c.Keys()
	var tracks []Track
	for _, k := range keys {
		tracks = append(tracks, c.Get(k).Tracks()...)
	}
	return tracks
}

// Collector is an interface which defines the Collect method.
type Collector interface {
	Collect(Tracker) Collection
}

// group is a basic implementation of Group
type group struct {
	name   string
	tracks []Track
	fields map[string]interface{}
}

// Name implements Group.
func (g group) Name() string { return g.name }

// Tracks implements Tracker.
func (g group) Tracks() []Track { return g.tracks }

// Field implements Group.
func (g group) Field(field string) interface{} { return g.fields[field] }

// subCol is a wrapper around an existing collection which overrides the Get
// method.
type subCol struct {
	Collection
	grps map[Key]Group
	flds map[string]interface{}
}

// Get implements Collection.
func (sg subCol) Get(k Key) Group {
	return sg.grps[k]
}

// Field implements Group.
func (sg subCol) Field(f string) interface{} {
	if x, ok := sg.flds[f]; ok {
		return x
	}
	return sg.Collection.Field(f)
}

// SubCollect applies the given Collector to each of the "leaf" Groups
// in the Collection.
func SubCollect(c Collection, r Collector) Collection {
	keys := c.Keys()
	nc := subCol{
		Collection: c,
		grps:       make(map[Key]Group, len(keys)),
		flds:       make(map[string]interface{}),
	}
	for _, k := range keys {
		g := c.Get(k)
		if gc, ok := g.(Collection); ok {
			nc.grps[k] = SubCollect(gc, r)
			continue
		}
		nc.grps[k] = r.Collect(g)
	}
	return nc
}

// WalkFn is the type of the function called for each Track visited by Walk.  Return
// non-nil error from Walk to stop the trasversal, and return the error from Walk.
type WalkFn func(Track, Path) error

func walkCollection(c Collection, p Path, f WalkFn) error {
	for _, k := range c.Keys() {
		g := c.Get(k)
		np := make(Path, len(p)+1)
		copy(np, p)
		np[len(p)] = k
		err := Walk(g, np, f)
		if err != nil {
			return err
		}
	}
	return nil
}

// Walk transverses the Group and calls the WalkFn on each Track.  The Path is assumed to be
// the path of the Group.
func Walk(g Group, p Path, f WalkFn) error {
	if gc, ok := g.(Collection); ok {
		return walkCollection(gc, p, f)
	}
	for i, t := range g.Tracks() {
		np := make(Path, len(p)+1)
		copy(np, p)
		np[len(p)] = Key(strconv.Itoa(i))
		err := f(t, np)
		if err != nil {
			return err
		}
	}
	return nil
}

type trackPath struct {
	t Track
	p Path
}

type trackPathSorter struct {
	tp []trackPath
	fn LessFn
}

func (tps trackPathSorter) Len() int           { return len(tps.tp) }
func (tps trackPathSorter) Swap(i, j int)      { tps.tp[i], tps.tp[j] = tps.tp[j], tps.tp[i] }
func (tps trackPathSorter) Less(i, j int) bool { return tps.fn(tps.tp[i].t, tps.tp[j].t) }

func Recent(c Collection, n int) []Path {
	var trackPaths []trackPath
	walkfn := func(t Track, p Path) error {
		trackPaths = append(trackPaths, trackPath{t, p[:2]})
		return nil
	}
	Walk(c, Path([]Key{"Root"}), walkfn)

	sort.Sort(sort.Reverse(trackPathSorter{trackPaths, SortByTime("DateAdded")}))

	dedup := make(map[string]bool)
	result := make([]Path, 0, n)
	for _, tp := range trackPaths {
		e := tp.p.Encode()
		if !dedup[e] {
			dedup[e] = true
			result = append(result, tp.p)
		}
		if len(result) == n {
			break
		}
	}
	return result
}

// By is a function which returns a Collector to group a collection using the given
// attribute.
func By(a attr.Interface) Collector {
	return groupBy{a}
}

type groupBy struct {
	attr.Interface
}

// Collect implements Collector
func (a groupBy) Collect(tracker Tracker) Collection {
	name := "by " + a.Name()
	if tg, ok := tracker.(Group); ok {
		name = tg.Name()
	}
	gg := newCol(name)
	for _, t := range tracker.Tracks() {
		gg.add(fmt.Sprintf("%v", a.Value(t)), t)
	}
	return gg
}
<|endoftext|>