<commit_before>package main

import  (
        "fmt"
        "os"
        "../go-todotxt"
        "github.com/spf13/cobra"
        "os/user"
        "strings"
        "strconv"
        "github.com/rakyll/globalconf"
        "flag"
        "regexp"
        "os/exec"
        "io/ioutil"
)

func extendedLoader(filename string) (todotxt.TaskList, error) {
        usr, err := user.Current()
        if err != nil {
                return nil, err
        }

        filename = strings.Replace(filename, "~", usr.HomeDir, -1)
        tasks := todotxt.LoadTaskList(filename)

        return tasks, nil
}

type flagValue struct {
        str string
}

func (f *flagValue) String() string {
        return f.str
}

func (f *flagValue) Set(value string) error {
        f.str = value
        return nil
}

func newFlagValue(val string) *flagValue {
        return &flagValue{str: val}
}

func main() {

        conf, _ := globalconf.New("gotodo")

        var numtasks bool
        var sortby string
        var finished bool
        var prettyformat string
        var filename string

        var flagFilename = flag.String("file", "", "Location of the todo.txt file.")

        var flags = make(map[string]string)

        var cmdConfig = &cobra.Command{
            Use:   "config [key] [value]",
            Short: "Show and sets config values",
            Long:  `Config can be used to see and also set configuration variables.`,
            Run: func(cmd *cobra.Command, args []string) {
                    if len(args) == 0{
                            fmt.Printf("Available config variables:\n\n")
                            for k := range flags {
                                    fmt.Printf("%s\n", k)
                            }
                    }
                    if len(args) == 1 {
                            val, exists := flags[args[0]]
                            if exists {
                                    fmt.Printf("%s\n", val)
                            } else {
                                    // otherwise exit with non-zero status
                                    os.Exit(1)
                            }
                    }
                    if len(args) == 2 {
                            _, exists := flags[args[0]]
                            if exists {
                                    f := &flag.Flag{Name: args[0], Value: newFlagValue(args[1])}
                                    conf.Set("", f)
                            } else {
                                    os.Exit(1)
                            }
                    }
            },
        }

        var cmdList = &cobra.Command{
            Use:   "list [keyword]",
            Short: "Lists tasks that contain keyword, if any",
            Long:  `List is the most basic command that is used for listing tasks.
                    You can specify a keyword as well as other options.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                if numtasks {
                    fmt.Println(tasks.Len())
                } else {
                    tasks.Sort(sortby)

                    var filteredTasks todotxt.TaskList
                    for _, task := range tasks {
                        if (!task.Finished() && !finished) ||
                           (task.Finished() && finished) {
                           if (len(args) > 0) {
                                   match, _ := regexp.MatchString(args[0], task.Text())
                                   if match {
                                           filteredTasks = append(filteredTasks, task)
                                   }
                           } else {
                                   filteredTasks = append(filteredTasks, task)
                           }
                        }
                    }

                    for _, task := range filteredTasks {
                            task.SetIdPaddingBy(tasks)
                            fmt.Println(task.PrettyPrint(prettyformat))
                    }
                }
            },
        }
        cmdList.Flags().BoolVarP(&numtasks, "num-tasks", "n", false,
                                 "Show the number of tasks")
        cmdList.Flags().BoolVarP(&finished, "finished", "f", false,
                                 "Show finished tasks")
        cmdList.Flags().StringVarP(&sortby, "sort", "s", "prio",
                                   "Sort tasks by parameter (prio|date|len|prio-rev|date-rev|len-rev|id|rand)")
        cmdList.Flags().StringVarP(&prettyformat, "pretty", "", "%c %i %p %t",
                                   "Pretty print tasks")

        var cmdAdd = &cobra.Command{
            Use:   "add [task]",
            Short: "Adds a task to the todo list.",
            Long:  `Adds a task to the todo list.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                task := strings.Join(args, " ")
                tasks.Add(task)

                tasks.Save(filename)
            },
        }

        var nofinishdate bool
        var cmdDone = &cobra.Command{
            Use:   "done [taskid]",
            Short: "Marks task as done.",
            Long:  `Marks task as done.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                if len(args) < 1 {
                        fmt.Println("So what needs to be done?")
                        return
                }

                taskid, err := strconv.Atoi(args[0])
                if err != nil {
                        fmt.Printf("Do you really consider that a number? %v\n", err)
                        return
                }

                err = tasks.Done(taskid, !nofinishdate)
                if err != nil {
                        fmt.Printf("There was an error %v\n", err)
                }

                tasks.Save(filename)
            },
        }
        cmdDone.Flags().BoolVarP(&nofinishdate, "no-finish-date", "D", false,
                                        "Do not mark finished tasks with date.")

        var cmdArchive = &cobra.Command{
            Use:   "archive [taskid]",
            Short: "Archives task.",
            Long:  `Archives task.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                if len(args) < 1 {
                        fmt.Println("So what needs to be done?")
                        return
                }

                taskid, err := strconv.Atoi(args[0])
                if err != nil {
                        fmt.Printf("Do you really consider that a number? %v\n", err)
                        return
                }

                fmt.Printf("Archiving task %v\n", taskid)

                tasks.Save(filename)
            },
        }

        var setprio string
        var settodo string
        var cmdSet = &cobra.Command{
            Use:   "set [taskid]",
            Short: "Sets given task's parameters.",
            Long:  `Sets given task's parameters.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                if len(args) < 1 {
                        fmt.Println("So what do you want to edit?")
                        return
                }

                taskid, err := strconv.Atoi(args[0])
                if err != nil {
                        fmt.Printf("Do you really consider that a number? %v\n", err)
                        return
                }

                if len(setprio) > 0 {
                        tasks[taskid].SetPriority(setprio[0])
                        tasks[taskid].RebuildRawTodo()
                }

                if len(settodo) > 0 {
                        tasks[taskid].SetTodo(settodo)
                        tasks[taskid].RebuildRawTodo()
                }

                tasks.Save(filename)
            },
        }

        var cmdEdit = &cobra.Command{
            Use:   "edit [taskid]",
            Short: "Edit given task",
            Long:  `Edit given task with your prefered editor.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                if len(args) < 1 {
                        fmt.Println("So what do you want to edit?")
                        return
                }

                taskid, err := strconv.Atoi(args[0])
                if err != nil {
                        fmt.Printf("Do you really consider that a number? %v\n", err)
                        return
                }

                text := tasks[taskid].RawText()

                file, err := ioutil.TempFile(os.TempDir(), "gotodo")
                defer os.Remove(file.Name())

                if err != nil {
                        panic(err)
                }

                e := ioutil.WriteFile(file.Name(), []byte(text), 0644)
                if e != nil {
                        panic(e)
                }

                editor := os.Getenv("EDITOR")
                if len(editor) == 0 {
                        editor = "nano" //FIXME: saner default?
                }

                c := exec.Command(editor, file.Name())

                // nasty hack, see http://stackoverflow.com/a/12089980
                c.Stdin = os.Stdin
                c.Stdout = os.Stdout
                c.Stderr = os.Stderr

                er := c.Run()

                if er != nil {
                        fmt.Println(er.Error())
                        panic(er)
                }

                dat, err := ioutil.ReadFile(file.Name())
                if err != nil {
                        panic(err)
                }

                lines := strings.Split(string(dat), "\n")
                if len (lines) > 0 {
                        if tasks[taskid].RawText() != lines[0] {
                                tasks[taskid] = todotxt.ParseTask(lines[0], taskid)
                                fmt.Printf("Task %d updated to:\n%s\n", taskid, lines[0])
                        }
                }

                tasks.Save(filename)
            },
        }

        cmdSet.PersistentFlags().StringVarP(&setprio, "priority", "p", "",
                                     "Sets task's priority.")
        cmdSet.PersistentFlags().StringVarP(&settodo, "todo", "t", "",
                                     "Sets task's todo.")

        var GotodoCmd = &cobra.Command{
            Use:   "gotodo",
            Short: "Gotodo is a go implementation of todo.txt.",
            Long: `A small, fast and fun implementation of todo.txt`,
            Run: func(cmd *cobra.Command, args []string) {
                cmdList.Run(cmd, nil)
            },
        }

        GotodoCmd.PersistentFlags().StringVarP(&filename, "filename", "", "",
                                     "Load tasks from this file.")

        conf.ParseAll()

        // values here
        flags["file"] = *flagFilename

        // sadly, this is the best we can do right now
        if filename == "" {
                if *flagFilename == "" {
                        filename = "todo.txt"
                } else {
                        filename = *flagFilename
                }
        }

        GotodoCmd.AddCommand(cmdList)
        GotodoCmd.AddCommand(cmdAdd)
        GotodoCmd.AddCommand(cmdDone)
        GotodoCmd.AddCommand(cmdArchive)
        GotodoCmd.AddCommand(cmdSet)
        GotodoCmd.AddCommand(cmdEdit)
        GotodoCmd.AddCommand(cmdConfig)
        GotodoCmd.Execute()
}
<commit_msg>Even better default<commit_after>package main

import  (
        "fmt"
        "os"
        "../go-todotxt"
        "github.com/spf13/cobra"
        "os/user"
        "strings"
        "strconv"
        "github.com/rakyll/globalconf"
        "flag"
        "regexp"
        "os/exec"
        "io/ioutil"
)

func extendedLoader(filename string) (todotxt.TaskList, error) {
        usr, err := user.Current()
        if err != nil {
                return nil, err
        }

        filename = strings.Replace(filename, "~", usr.HomeDir, -1)
        tasks := todotxt.LoadTaskList(filename)

        return tasks, nil
}

type flagValue struct {
        str string
}

func (f *flagValue) String() string {
        return f.str
}

func (f *flagValue) Set(value string) error {
        f.str = value
        return nil
}

func newFlagValue(val string) *flagValue {
        return &flagValue{str: val}
}

func main() {

        conf, _ := globalconf.New("gotodo")

        var numtasks bool
        var sortby string
        var finished bool
        var prettyformat string
        var filename string

        var flagFilename = flag.String("file", "", "Location of the todo.txt file.")

        var flags = make(map[string]string)

        var cmdConfig = &cobra.Command{
            Use:   "config [key] [value]",
            Short: "Show and sets config values",
            Long:  `Config can be used to see and also set configuration variables.`,
            Run: func(cmd *cobra.Command, args []string) {
                    if len(args) == 0{
                            fmt.Printf("Available config variables:\n\n")
                            for k := range flags {
                                    fmt.Printf("%s\n", k)
                            }
                    }
                    if len(args) == 1 {
                            val, exists := flags[args[0]]
                            if exists {
                                    fmt.Printf("%s\n", val)
                            } else {
                                    // otherwise exit with non-zero status
                                    os.Exit(1)
                            }
                    }
                    if len(args) == 2 {
                            _, exists := flags[args[0]]
                            if exists {
                                    f := &flag.Flag{Name: args[0], Value: newFlagValue(args[1])}
                                    conf.Set("", f)
                            } else {
                                    os.Exit(1)
                            }
                    }
            },
        }

        var cmdList = &cobra.Command{
            Use:   "list [keyword]",
            Short: "Lists tasks that contain keyword, if any",
            Long:  `List is the most basic command that is used for listing tasks.
                    You can specify a keyword as well as other options.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                if numtasks {
                    fmt.Println(tasks.Len())
                } else {
                    tasks.Sort(sortby)

                    var filteredTasks todotxt.TaskList
                    for _, task := range tasks {
                        if (!task.Finished() && !finished) ||
                           (task.Finished() && finished) {
                           if (len(args) > 0) {
                                   match, _ := regexp.MatchString(args[0], task.Text())
                                   if match {
                                           filteredTasks = append(filteredTasks, task)
                                   }
                           } else {
                                   filteredTasks = append(filteredTasks, task)
                           }
                        }
                    }

                    for _, task := range filteredTasks {
                            task.SetIdPaddingBy(tasks)
                            fmt.Println(task.PrettyPrint(prettyformat))
                    }
                }
            },
        }
        cmdList.Flags().BoolVarP(&numtasks, "num-tasks", "n", false,
                                 "Show the number of tasks")
        cmdList.Flags().BoolVarP(&finished, "finished", "f", false,
                                 "Show finished tasks")
        cmdList.Flags().StringVarP(&sortby, "sort", "s", "prio",
                                   "Sort tasks by parameter (prio|date|len|prio-rev|date-rev|len-rev|id|rand)")
        cmdList.Flags().StringVarP(&prettyformat, "pretty", "", "%i %c %p %t%r",
                                   "Pretty print tasks")

        var cmdAdd = &cobra.Command{
            Use:   "add [task]",
            Short: "Adds a task to the todo list.",
            Long:  `Adds a task to the todo list.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                task := strings.Join(args, " ")
                tasks.Add(task)

                tasks.Save(filename)
            },
        }

        var nofinishdate bool
        var cmdDone = &cobra.Command{
            Use:   "done [taskid]",
            Short: "Marks task as done.",
            Long:  `Marks task as done.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                if len(args) < 1 {
                        fmt.Println("So what needs to be done?")
                        return
                }

                taskid, err := strconv.Atoi(args[0])
                if err != nil {
                        fmt.Printf("Do you really consider that a number? %v\n", err)
                        return
                }

                err = tasks.Done(taskid, !nofinishdate)
                if err != nil {
                        fmt.Printf("There was an error %v\n", err)
                }

                tasks.Save(filename)
            },
        }
        cmdDone.Flags().BoolVarP(&nofinishdate, "no-finish-date", "D", false,
                                        "Do not mark finished tasks with date.")

        var cmdArchive = &cobra.Command{
            Use:   "archive [taskid]",
            Short: "Archives task.",
            Long:  `Archives task.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                if len(args) < 1 {
                        fmt.Println("So what needs to be done?")
                        return
                }

                taskid, err := strconv.Atoi(args[0])
                if err != nil {
                        fmt.Printf("Do you really consider that a number? %v\n", err)
                        return
                }

                fmt.Printf("Archiving task %v\n", taskid)

                tasks.Save(filename)
            },
        }

        var setprio string
        var settodo string
        var cmdSet = &cobra.Command{
            Use:   "set [taskid]",
            Short: "Sets given task's parameters.",
            Long:  `Sets given task's parameters.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                if len(args) < 1 {
                        fmt.Println("So what do you want to edit?")
                        return
                }

                taskid, err := strconv.Atoi(args[0])
                if err != nil {
                        fmt.Printf("Do you really consider that a number? %v\n", err)
                        return
                }

                if len(setprio) > 0 {
                        tasks[taskid].SetPriority(setprio[0])
                        tasks[taskid].RebuildRawTodo()
                }

                if len(settodo) > 0 {
                        tasks[taskid].SetTodo(settodo)
                        tasks[taskid].RebuildRawTodo()
                }

                tasks.Save(filename)
            },
        }

        var cmdEdit = &cobra.Command{
            Use:   "edit [taskid]",
            Short: "Edit given task",
            Long:  `Edit given task with your prefered editor.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                if len(args) < 1 {
                        fmt.Println("So what do you want to edit?")
                        return
                }

                taskid, err := strconv.Atoi(args[0])
                if err != nil {
                        fmt.Printf("Do you really consider that a number? %v\n", err)
                        return
                }

                text := tasks[taskid].RawText()

                file, err := ioutil.TempFile(os.TempDir(), "gotodo")
                defer os.Remove(file.Name())

                if err != nil {
                        panic(err)
                }

                e := ioutil.WriteFile(file.Name(), []byte(text), 0644)
                if e != nil {
                        panic(e)
                }

                editor := os.Getenv("EDITOR")
                if len(editor) == 0 {
                        editor = "nano" //FIXME: saner default?
                }

                c := exec.Command(editor, file.Name())

                // nasty hack, see http://stackoverflow.com/a/12089980
                c.Stdin = os.Stdin
                c.Stdout = os.Stdout
                c.Stderr = os.Stderr

                er := c.Run()

                if er != nil {
                        fmt.Println(er.Error())
                        panic(er)
                }

                dat, err := ioutil.ReadFile(file.Name())
                if err != nil {
                        panic(err)
                }

                lines := strings.Split(string(dat), "\n")
                if len (lines) > 0 {
                        if tasks[taskid].RawText() != lines[0] {
                                tasks[taskid] = todotxt.ParseTask(lines[0], taskid)
                                fmt.Printf("Task %d updated to:\n%s\n", taskid, lines[0])
                        }
                }

                tasks.Save(filename)
            },
        }

        cmdSet.PersistentFlags().StringVarP(&setprio, "priority", "p", "",
                                     "Sets task's priority.")
        cmdSet.PersistentFlags().StringVarP(&settodo, "todo", "t", "",
                                     "Sets task's todo.")

        var GotodoCmd = &cobra.Command{
            Use:   "gotodo",
            Short: "Gotodo is a go implementation of todo.txt.",
            Long: `A small, fast and fun implementation of todo.txt`,
            Run: func(cmd *cobra.Command, args []string) {
                cmdList.Run(cmd, nil)
            },
        }

        GotodoCmd.PersistentFlags().StringVarP(&filename, "filename", "", "",
                                     "Load tasks from this file.")

        conf.ParseAll()

        // values here
        flags["file"] = *flagFilename

        // sadly, this is the best we can do right now
        if filename == "" {
                if *flagFilename == "" {
                        filename = "todo.txt"
                } else {
                        filename = *flagFilename
                }
        }

        GotodoCmd.AddCommand(cmdList)
        GotodoCmd.AddCommand(cmdAdd)
        GotodoCmd.AddCommand(cmdDone)
        GotodoCmd.AddCommand(cmdArchive)
        GotodoCmd.AddCommand(cmdSet)
        GotodoCmd.AddCommand(cmdEdit)
        GotodoCmd.AddCommand(cmdConfig)
        GotodoCmd.Execute()
}
<|endoftext|>


<commit_before>package main

import  (
        "fmt"
        "os"
        "../go-todotxt"
        "github.com/spf13/cobra"
        "os/user"
        "strings"
        "strconv"
        "github.com/rakyll/globalconf"
        "flag"
        "regexp"
        //"os/exec"
)

func extendedLoader(filename string) (todotxt.TaskList, error) {
        usr, err := user.Current()
        if err != nil {
                return nil, err
        }

        filename = strings.Replace(filename, "~", usr.HomeDir, -1)
        tasks := todotxt.LoadTaskList(filename)

        return tasks, nil
}

type flagValue struct {
        str string
}

func (f *flagValue) String() string {
        return f.str
}

func (f *flagValue) Set(value string) error {
        f.str = value
        return nil
}

func newFlagValue(val string) *flagValue {
        return &flagValue{str: val}
}

func main() {

        conf, _ := globalconf.New("gotodo")

        var numtasks bool
        var sortby string
        var finished bool
        var prettyformat string
        var filename string

        var flagFilename = flag.String("file", "", "Location of the todo.txt file.")

        var flags = make(map[string]string)

        var cmdConfig = &cobra.Command{
            Use:   "config [key] [value]",
            Short: "Show and sets config values",
            Long:  `Config can be used to see and also set configuration variables.`,
            Run: func(cmd *cobra.Command, args []string) {
                    if len(args) == 0{
                            fmt.Printf("Available config variables:\n\n")
                            for k := range flags {
                                    fmt.Printf("%s\n", k)
                            }
                    }
                    if len(args) == 1 {
                            val, exists := flags[args[0]]
                            if exists {
                                    fmt.Printf("%s\n", val)
                            } else {
                                    // otherwise exit with non-zero status
                                    os.Exit(1)
                            }
                    }
                    if len(args) == 2 {
                            _, exists := flags[args[0]]
                            if exists {
                                    f := &flag.Flag{Name: args[0], Value: newFlagValue(args[1])}
                                    conf.Set("", f)
                            } else {
                                    os.Exit(1)
                            }
                    }
            },
        }

        var cmdList = &cobra.Command{
            Use:   "list [keyword]",
            Short: "Lists tasks that contain keyword, if any",
            Long:  `List is the most basic command that is used for listing tasks.
                    You can specify a keyword as well as other options.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                if numtasks {
                    fmt.Println(tasks.Len())
                } else {
                    tasks.Sort(sortby)

                    var filteredTasks todotxt.TaskList
                    for _, task := range tasks {
                        if (!task.Finished() && !finished) ||
                           (task.Finished() && finished) {
                           if (len(args) > 0) {
                                   match, _ := regexp.MatchString(args[0], task.Text())
                                   if match {
                                           filteredTasks = append(filteredTasks, task)
                                   }
                           } else {
                                   filteredTasks = append(filteredTasks, task)
                           }
                        }
                    }

                    for _, task := range filteredTasks {
                            task.SetIdPaddingBy(tasks)
                            fmt.Println(task.PrettyPrint(prettyformat))
                    }
                }
            },
        }
        cmdList.Flags().BoolVarP(&numtasks, "num-tasks", "n", false,
                                 "Show the number of tasks")
        cmdList.Flags().BoolVarP(&finished, "finished", "f", false,
                                 "Show finished tasks")
        cmdList.Flags().StringVarP(&sortby, "sort", "s", "prio",
                                   "Sort tasks by parameter (prio|date|len|prio-rev|date-rev|len-rev|id|rand)")
        cmdList.Flags().StringVarP(&prettyformat, "pretty", "", "%i %p %t",
                                   "Pretty print tasks")

        var cmdAdd = &cobra.Command{
            Use:   "add [task]",
            Short: "Adds a task to the todo list.",
            Long:  `Adds a task to the todo list.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                task := strings.Join(args, " ")
                tasks.Add(task)

                tasks.Save(filename)
            },
        }

        var nofinishdate bool
        var cmdDone = &cobra.Command{
            Use:   "done [taskid]",
            Short: "Marks task as done.",
            Long:  `Marks task as done.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                if len(args) < 1 {
                        fmt.Println("So what needs to be done?")
                        return
                }

                taskid, err := strconv.Atoi(args[0])
                if err != nil {
                        fmt.Printf("Do you really consider that a number? %v\n", err)
                        return
                }

                err = tasks.Done(taskid, !nofinishdate)
                if err != nil {
                        fmt.Printf("There was an error %v\n", err)
                }

                tasks.Save(filename)
            },
        }
        cmdDone.Flags().BoolVarP(&nofinishdate, "no-finish-date", "D", false,
                                        "Do not mark finished tasks with date.")

        var cmdArchive = &cobra.Command{
            Use:   "archive [taskid]",
            Short: "Archives task.",
            Long:  `Archives task.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                if len(args) < 1 {
                        fmt.Println("So what needs to be done?")
                        return
                }

                taskid, err := strconv.Atoi(args[0])
                if err != nil {
                        fmt.Printf("Do you really consider that a number? %v\n", err)
                        return
                }

                fmt.Printf("Archiving task %v\n", taskid)

                tasks.Save(filename)
            },
        }

        var setprio string
        var settodo string
        var cmdSet = &cobra.Command{
            Use:   "set [taskid]",
            Short: "Sets given task's parameters.",
            Long:  `Sets given task's parameters.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                if len(args) < 1 {
                        fmt.Println("So what do you want to edit?")
                        return
                }

                taskid, err := strconv.Atoi(args[0])
                if err != nil {
                        fmt.Printf("Do you really consider that a number? %v\n", err)
                        return
                }

                if len(setprio) > 0 {
                        tasks[taskid].SetPriority(setprio[0])
                        tasks[taskid].RebuildRawTodo()
                }

                if len(settodo) > 0 {
                        tasks[taskid].SetTodo(settodo)
                        tasks[taskid].RebuildRawTodo()
                }

              //if len(edittodo) == 0 && len(editprio) == 0 {
              //        // FIXME: nost just vim
              //        out, err := exec.Command("vim", filename).Output()
              //}

                tasks.Save(filename)
            },
        }

        cmdSet.PersistentFlags().StringVarP(&setprio, "priority", "p", "",
                                     "Sets task's priority.")
        cmdSet.PersistentFlags().StringVarP(&settodo, "todo", "t", "",
                                     "Sets task's todo.")

        var GotodoCmd = &cobra.Command{
            Use:   "gotodo",
            Short: "Gotodo is a go implementation of todo.txt.",
            Long: `A small, fast and fun implementation of todo.txt`,
            Run: func(cmd *cobra.Command, args []string) {
                cmdList.Run(cmd, nil)
            },
        }

        GotodoCmd.PersistentFlags().StringVarP(&filename, "filename", "", "",
                                     "Load tasks from this file.")

        conf.ParseAll()

        // values here
        flags["file"] = *flagFilename

        // sadly, this is the best we can do right now
        if filename == "" {
                if *flagFilename == "" {
                        filename = "todo.txt"
                } else {
                        filename = *flagFilename
                }
        }

        GotodoCmd.AddCommand(cmdList)
        GotodoCmd.AddCommand(cmdAdd)
        GotodoCmd.AddCommand(cmdDone)
        GotodoCmd.AddCommand(cmdArchive)
        GotodoCmd.AddCommand(cmdSet)
        GotodoCmd.AddCommand(cmdConfig)
        GotodoCmd.Execute()
}
<commit_msg>Edit command<commit_after>package main

import  (
        "fmt"
        "os"
        "../go-todotxt"
        "github.com/spf13/cobra"
        "os/user"
        "strings"
        "strconv"
        "github.com/rakyll/globalconf"
        "flag"
        "regexp"
        "os/exec"
        "io/ioutil"
)

func extendedLoader(filename string) (todotxt.TaskList, error) {
        usr, err := user.Current()
        if err != nil {
                return nil, err
        }

        filename = strings.Replace(filename, "~", usr.HomeDir, -1)
        tasks := todotxt.LoadTaskList(filename)

        return tasks, nil
}

type flagValue struct {
        str string
}

func (f *flagValue) String() string {
        return f.str
}

func (f *flagValue) Set(value string) error {
        f.str = value
        return nil
}

func newFlagValue(val string) *flagValue {
        return &flagValue{str: val}
}

func main() {

        conf, _ := globalconf.New("gotodo")

        var numtasks bool
        var sortby string
        var finished bool
        var prettyformat string
        var filename string

        var flagFilename = flag.String("file", "", "Location of the todo.txt file.")

        var flags = make(map[string]string)

        var cmdConfig = &cobra.Command{
            Use:   "config [key] [value]",
            Short: "Show and sets config values",
            Long:  `Config can be used to see and also set configuration variables.`,
            Run: func(cmd *cobra.Command, args []string) {
                    if len(args) == 0{
                            fmt.Printf("Available config variables:\n\n")
                            for k := range flags {
                                    fmt.Printf("%s\n", k)
                            }
                    }
                    if len(args) == 1 {
                            val, exists := flags[args[0]]
                            if exists {
                                    fmt.Printf("%s\n", val)
                            } else {
                                    // otherwise exit with non-zero status
                                    os.Exit(1)
                            }
                    }
                    if len(args) == 2 {
                            _, exists := flags[args[0]]
                            if exists {
                                    f := &flag.Flag{Name: args[0], Value: newFlagValue(args[1])}
                                    conf.Set("", f)
                            } else {
                                    os.Exit(1)
                            }
                    }
            },
        }

        var cmdList = &cobra.Command{
            Use:   "list [keyword]",
            Short: "Lists tasks that contain keyword, if any",
            Long:  `List is the most basic command that is used for listing tasks.
                    You can specify a keyword as well as other options.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                if numtasks {
                    fmt.Println(tasks.Len())
                } else {
                    tasks.Sort(sortby)

                    var filteredTasks todotxt.TaskList
                    for _, task := range tasks {
                        if (!task.Finished() && !finished) ||
                           (task.Finished() && finished) {
                           if (len(args) > 0) {
                                   match, _ := regexp.MatchString(args[0], task.Text())
                                   if match {
                                           filteredTasks = append(filteredTasks, task)
                                   }
                           } else {
                                   filteredTasks = append(filteredTasks, task)
                           }
                        }
                    }

                    for _, task := range filteredTasks {
                            task.SetIdPaddingBy(tasks)
                            fmt.Println(task.PrettyPrint(prettyformat))
                    }
                }
            },
        }
        cmdList.Flags().BoolVarP(&numtasks, "num-tasks", "n", false,
                                 "Show the number of tasks")
        cmdList.Flags().BoolVarP(&finished, "finished", "f", false,
                                 "Show finished tasks")
        cmdList.Flags().StringVarP(&sortby, "sort", "s", "prio",
                                   "Sort tasks by parameter (prio|date|len|prio-rev|date-rev|len-rev|id|rand)")
        cmdList.Flags().StringVarP(&prettyformat, "pretty", "", "%i %p %t",
                                   "Pretty print tasks")

        var cmdAdd = &cobra.Command{
            Use:   "add [task]",
            Short: "Adds a task to the todo list.",
            Long:  `Adds a task to the todo list.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                task := strings.Join(args, " ")
                tasks.Add(task)

                tasks.Save(filename)
            },
        }

        var nofinishdate bool
        var cmdDone = &cobra.Command{
            Use:   "done [taskid]",
            Short: "Marks task as done.",
            Long:  `Marks task as done.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                if len(args) < 1 {
                        fmt.Println("So what needs to be done?")
                        return
                }

                taskid, err := strconv.Atoi(args[0])
                if err != nil {
                        fmt.Printf("Do you really consider that a number? %v\n", err)
                        return
                }

                err = tasks.Done(taskid, !nofinishdate)
                if err != nil {
                        fmt.Printf("There was an error %v\n", err)
                }

                tasks.Save(filename)
            },
        }
        cmdDone.Flags().BoolVarP(&nofinishdate, "no-finish-date", "D", false,
                                        "Do not mark finished tasks with date.")

        var cmdArchive = &cobra.Command{
            Use:   "archive [taskid]",
            Short: "Archives task.",
            Long:  `Archives task.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                if len(args) < 1 {
                        fmt.Println("So what needs to be done?")
                        return
                }

                taskid, err := strconv.Atoi(args[0])
                if err != nil {
                        fmt.Printf("Do you really consider that a number? %v\n", err)
                        return
                }

                fmt.Printf("Archiving task %v\n", taskid)

                tasks.Save(filename)
            },
        }

        var setprio string
        var settodo string
        var cmdSet = &cobra.Command{
            Use:   "set [taskid]",
            Short: "Sets given task's parameters.",
            Long:  `Sets given task's parameters.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                if len(args) < 1 {
                        fmt.Println("So what do you want to edit?")
                        return
                }

                taskid, err := strconv.Atoi(args[0])
                if err != nil {
                        fmt.Printf("Do you really consider that a number? %v\n", err)
                        return
                }

                if len(setprio) > 0 {
                        tasks[taskid].SetPriority(setprio[0])
                        tasks[taskid].RebuildRawTodo()
                }

                if len(settodo) > 0 {
                        tasks[taskid].SetTodo(settodo)
                        tasks[taskid].RebuildRawTodo()
                }

              //if len(edittodo) == 0 && len(editprio) == 0 {
              //        // FIXME: nost just vim
              //        out, err := exec.Command("vim", filename).Output()
              //}

                tasks.Save(filename)
            },
        }

        var cmdEdit = &cobra.Command{
            Use:   "edit [taskid]",
            Short: "Edit given task",
            Long:  `Edit given task with your prefered editor.`,
            Run: func(cmd *cobra.Command, args []string) {
                tasks, err := extendedLoader(filename)
                if err != nil {
                        fmt.Println(err)
                        return
                }

                if len(args) < 1 {
                        fmt.Println("So what do you want to edit?")
                        return
                }

                taskid, err := strconv.Atoi(args[0])
                if err != nil {
                        fmt.Printf("Do you really consider that a number? %v\n", err)
                        return
                }

                text := tasks[taskid].RawText()

                file, err := ioutil.TempFile(os.TempDir(), "gotodo")
                defer os.Remove(file.Name())

                if err != nil {
                        panic(err)
                }

                e := ioutil.WriteFile(file.Name(), []byte(text), 0644)
                if e != nil {
                        panic(e)
                }

                exec.Command("vim", file.Name()).Output()

                dat, err := ioutil.ReadFile(file.Name())
                if err != nil {
                        panic(err)
                }

                tasks[taskid].SetTodo(string(dat))
                tasks[taskid].RebuildRawTodo()

                tasks.Save(filename)
            },
        }

        cmdSet.PersistentFlags().StringVarP(&setprio, "priority", "p", "",
                                     "Sets task's priority.")
        cmdSet.PersistentFlags().StringVarP(&settodo, "todo", "t", "",
                                     "Sets task's todo.")

        var GotodoCmd = &cobra.Command{
            Use:   "gotodo",
            Short: "Gotodo is a go implementation of todo.txt.",
            Long: `A small, fast and fun implementation of todo.txt`,
            Run: func(cmd *cobra.Command, args []string) {
                cmdList.Run(cmd, nil)
            },
        }

        GotodoCmd.PersistentFlags().StringVarP(&filename, "filename", "", "",
                                     "Load tasks from this file.")

        conf.ParseAll()

        // values here
        flags["file"] = *flagFilename

        // sadly, this is the best we can do right now
        if filename == "" {
                if *flagFilename == "" {
                        filename = "todo.txt"
                } else {
                        filename = *flagFilename
                }
        }

        GotodoCmd.AddCommand(cmdList)
        GotodoCmd.AddCommand(cmdAdd)
        GotodoCmd.AddCommand(cmdDone)
        GotodoCmd.AddCommand(cmdArchive)
        GotodoCmd.AddCommand(cmdSet)
        GotodoCmd.AddCommand(cmdEdit)
        GotodoCmd.AddCommand(cmdConfig)
        GotodoCmd.Execute()
}
<|endoftext|>


<commit_before>// This file contains about everything related to users aka users. At the top you will find routes
// and at the bottom you can find CRUD options. Some functions in this file are analogous
// to the ones in posts.go.
package gorm

import (
	"errors"
	"log"
	"time"
	"strconv"

	. "github.com/9uuso/vertigo/crypto"
	. "vertigo/email"

	"code.google.com/p/go-uuid/uuid"
	"github.com/jinzhu/gorm"
	"github.com/martini-contrib/sessions"
)

// User struct holds all relevant data for representing user accounts on Vertigo.
// A complete User struct also includes Posts field (type []Post) which includes
// all posts made by the user.
type User struct {
	ID       int64  `json:"id" gorm:"primary_key:yes"`
	Name     string `json:"name" form:"name"`
	Password string `json:"password,omitempty" form:"password" sql:"-"`
	Recovery string `json:"-"`
	Digest   []byte `json:"-"`
	Email    string `json:"email,omitempty" form:"email" binding:"required" sql:"unique"`
	Posts    []Post `json:"posts"`
}

// Login or user.Login is a function which retrieves user according to given .Email field.
// The function then compares the retrieved object's .Digest field with given .Password field.
// If the .Password and .Digest match, the function returns the requested User struct, but with
// the .Password and .Digest omitted.
func (user User) Login() (User, error) {
	password := user.Password
	user, err := user.GetByEmail()
	if err != nil {
		return user, err
	}
	if !CompareHash(user.Digest, password) {
		return user, errors.New("wrong username or password")
	}
	return user, nil
}

// Update or user.Update updates data of "entry" parameter with the data received from "user".
// Can only used to update Name and Digest fields because of how user.Get works.
// Currently not used elsewhere than in password Recovery, that's why the Digest generation.
func (user User) Update(entry User) (User, error) {
	query := connection.Gorm.Where(&User{ID: user.ID}).Find(&user).Updates(entry)
	if query.Error != nil {
		if query.Error == gorm.RecordNotFound {
			return user, errors.New("not found")
		}
		return user, query.Error
	}
	return user, nil
}

// Recover or user.Recover is used to recover User's password according to user.Email
// The function will insert user.Recovery field with generated UUID string and dispatch an email
// to the corresponding user.Email address. It will also add TTL to Recovery field.
func (user User) Recover() error {

	user, err := user.GetByEmail()
	if err != nil {
		return err
	}

	var entry User
	entry.Recovery = uuid.New()
	user, err = user.Update(entry)
	if err != nil {
		return err
	}

	id := strconv.Itoa(int(user.ID))
	err = SendRecoveryEmail(id, user.Email, user.Recovery)
	if err != nil {
		return err
	}

	go user.ExpireRecovery(180 * time.Minute)

	return nil
}

func (user User) PasswordReset(entry User) (User, error) {
	digest, err := GenerateHash(entry.Password)
	if err != nil {
		return entry, err
	}
	entry.Digest = digest
	entry.Recovery = " " // gorm can't save an empty string
	_, err = user.Update(entry)
	if err != nil {
		return entry, err
	}
	return entry, nil
}

// ExpireRecovery or user.ExpireRecovery sets a TTL according to t to a recovery hash.
// This function is supposed to be run as goroutine to avoid blocking exection for t.
func (user User) ExpireRecovery(t time.Duration) {
	time.Sleep(t)

	var entry User
	entry.Recovery = " "
	_, err := user.Update(entry)
	if err != nil {
		log.Println(err)
	}
	return
}

// Get or user.Get returns User object according to given .ID
// with post information merged.
func (user User) Get() (User, error) {
	var posts []Post
	query := connection.Gorm.Where(&User{ID: user.ID}).First(&user)
	if query.Error != nil {
		if query.Error == gorm.RecordNotFound {
			return user, errors.New("not found")
		}
		return user, query.Error
	}
	query = connection.Gorm.Order("created desc").Where(&Post{Author: user.ID}).Find(&posts)
	if query.Error != nil {
		if query.Error == gorm.RecordNotFound {
			user.Posts = make([]Post, 0)
			return user, nil
		}
		return user, query.Error
	}
	user.Posts = posts
	return user, nil
}

// GetByEmail or user.GetByEmail returns User object according to given .Email
// with post information merged.
func (user User) GetByEmail() (User, error) {
	var posts []Post
	query := connection.Gorm.Where(&User{Email: user.Email}).First(&user)
	if query.Error != nil {
		if query.Error == gorm.RecordNotFound {
			return user, errors.New("not found")
		}
		return user, query.Error
	}
	query = connection.Gorm.Where(&Post{Author: user.ID}).Find(&posts)
	if query.Error != nil {
		if query.Error == gorm.RecordNotFound {
			user.Posts = make([]Post, 0)
			return user, nil
		}
		return user, query.Error
	}
	user.Posts = posts
	return user, nil
}

// Session or user.Session returns user.ID from client session cookie.
// The returned object has post data merged.
func (user User) Session(s sessions.Session) (User, error) {
	data := s.Get("user")
	id, exists := data.(int64)
	if exists {
		var user User
		user.ID = id
		user, err := user.Get()
		if err != nil {
			return user, err
		}
		return user, nil
	}
	return user, errors.New("unauthorized")
}

// Delete or user.Delete deletes the user with given ID from the database.
// func (user User) Delete(s sessions.Session) error {
// 	user, err := user.Session(db, s)
// 	if err != nil {
// 		return err
// 	}
// 	query := db.Delete(&user)
// 	if query.Error != nil {
// 		if query.Error == gorm.RecordNotFound {
// 			return errors.New("not found")
// 		}
// 		return query.Error
// 	}
// 	return nil
// }

// Insert or user.Insert inserts a new User struct into the database.
// The function creates .Digest hash from .Password.
func (user User) Insert() (User, error) {
	digest, err := GenerateHash(user.Password)
	if err != nil {
		return user, err
	}
	user.Digest = digest
	user.Posts = make([]Post, 0)
	query := connection.Gorm.Create(&user)
	if connection.Gorm.NewRecord(user) == true {
		return user, errors.New("user email exists")
	}
	if query.Error != nil {
		return user, query.Error
	}
	return user, nil
}

// GetAll or user.GetAll fetches all users with post data merged from the database.
func (user User) GetAll() ([]User, error) {
	var users []User
	query := connection.Gorm.Find(&users)
	if query.Error != nil {
		if query.Error == gorm.RecordNotFound {
			users = make([]User, 0)
			return users, nil
		}
		return users, query.Error
	}
	for index, user := range users {
		user, err := user.Get()
		if err != nil {
			return users, err
		}
		users[index] = user
	}
	return users, nil
}
<commit_msg>add forgotten absolute import path<commit_after>// This file contains about everything related to users aka users. At the top you will find routes
// and at the bottom you can find CRUD options. Some functions in this file are analogous
// to the ones in posts.go.
package gorm

import (
	"errors"
	"log"
	"time"
	"strconv"

	. "github.com/9uuso/vertigo/crypto"
	. "github.com/9uuso/vertigo/email"

	"code.google.com/p/go-uuid/uuid"
	"github.com/jinzhu/gorm"
	"github.com/martini-contrib/sessions"
)

// User struct holds all relevant data for representing user accounts on Vertigo.
// A complete User struct also includes Posts field (type []Post) which includes
// all posts made by the user.
type User struct {
	ID       int64  `json:"id" gorm:"primary_key:yes"`
	Name     string `json:"name" form:"name"`
	Password string `json:"password,omitempty" form:"password" sql:"-"`
	Recovery string `json:"-"`
	Digest   []byte `json:"-"`
	Email    string `json:"email,omitempty" form:"email" binding:"required" sql:"unique"`
	Posts    []Post `json:"posts"`
}

// Login or user.Login is a function which retrieves user according to given .Email field.
// The function then compares the retrieved object's .Digest field with given .Password field.
// If the .Password and .Digest match, the function returns the requested User struct, but with
// the .Password and .Digest omitted.
func (user User) Login() (User, error) {
	password := user.Password
	user, err := user.GetByEmail()
	if err != nil {
		return user, err
	}
	if !CompareHash(user.Digest, password) {
		return user, errors.New("wrong username or password")
	}
	return user, nil
}

// Update or user.Update updates data of "entry" parameter with the data received from "user".
// Can only used to update Name and Digest fields because of how user.Get works.
// Currently not used elsewhere than in password Recovery, that's why the Digest generation.
func (user User) Update(entry User) (User, error) {
	query := connection.Gorm.Where(&User{ID: user.ID}).Find(&user).Updates(entry)
	if query.Error != nil {
		if query.Error == gorm.RecordNotFound {
			return user, errors.New("not found")
		}
		return user, query.Error
	}
	return user, nil
}

// Recover or user.Recover is used to recover User's password according to user.Email
// The function will insert user.Recovery field with generated UUID string and dispatch an email
// to the corresponding user.Email address. It will also add TTL to Recovery field.
func (user User) Recover() error {

	user, err := user.GetByEmail()
	if err != nil {
		return err
	}

	var entry User
	entry.Recovery = uuid.New()
	user, err = user.Update(entry)
	if err != nil {
		return err
	}

	id := strconv.Itoa(int(user.ID))
	err = SendRecoveryEmail(id, user.Email, user.Recovery)
	if err != nil {
		return err
	}

	go user.ExpireRecovery(180 * time.Minute)

	return nil
}

func (user User) PasswordReset(entry User) (User, error) {
	digest, err := GenerateHash(entry.Password)
	if err != nil {
		return entry, err
	}
	entry.Digest = digest
	entry.Recovery = " " // gorm can't save an empty string
	_, err = user.Update(entry)
	if err != nil {
		return entry, err
	}
	return entry, nil
}

// ExpireRecovery or user.ExpireRecovery sets a TTL according to t to a recovery hash.
// This function is supposed to be run as goroutine to avoid blocking exection for t.
func (user User) ExpireRecovery(t time.Duration) {
	time.Sleep(t)

	var entry User
	entry.Recovery = " "
	_, err := user.Update(entry)
	if err != nil {
		log.Println(err)
	}
	return
}

// Get or user.Get returns User object according to given .ID
// with post information merged.
func (user User) Get() (User, error) {
	var posts []Post
	query := connection.Gorm.Where(&User{ID: user.ID}).First(&user)
	if query.Error != nil {
		if query.Error == gorm.RecordNotFound {
			return user, errors.New("not found")
		}
		return user, query.Error
	}
	query = connection.Gorm.Order("created desc").Where(&Post{Author: user.ID}).Find(&posts)
	if query.Error != nil {
		if query.Error == gorm.RecordNotFound {
			user.Posts = make([]Post, 0)
			return user, nil
		}
		return user, query.Error
	}
	user.Posts = posts
	return user, nil
}

// GetByEmail or user.GetByEmail returns User object according to given .Email
// with post information merged.
func (user User) GetByEmail() (User, error) {
	var posts []Post
	query := connection.Gorm.Where(&User{Email: user.Email}).First(&user)
	if query.Error != nil {
		if query.Error == gorm.RecordNotFound {
			return user, errors.New("not found")
		}
		return user, query.Error
	}
	query = connection.Gorm.Where(&Post{Author: user.ID}).Find(&posts)
	if query.Error != nil {
		if query.Error == gorm.RecordNotFound {
			user.Posts = make([]Post, 0)
			return user, nil
		}
		return user, query.Error
	}
	user.Posts = posts
	return user, nil
}

// Session or user.Session returns user.ID from client session cookie.
// The returned object has post data merged.
func (user User) Session(s sessions.Session) (User, error) {
	data := s.Get("user")
	id, exists := data.(int64)
	if exists {
		var user User
		user.ID = id
		user, err := user.Get()
		if err != nil {
			return user, err
		}
		return user, nil
	}
	return user, errors.New("unauthorized")
}

// Delete or user.Delete deletes the user with given ID from the database.
// func (user User) Delete(s sessions.Session) error {
// 	user, err := user.Session(db, s)
// 	if err != nil {
// 		return err
// 	}
// 	query := db.Delete(&user)
// 	if query.Error != nil {
// 		if query.Error == gorm.RecordNotFound {
// 			return errors.New("not found")
// 		}
// 		return query.Error
// 	}
// 	return nil
// }

// Insert or user.Insert inserts a new User struct into the database.
// The function creates .Digest hash from .Password.
func (user User) Insert() (User, error) {
	digest, err := GenerateHash(user.Password)
	if err != nil {
		return user, err
	}
	user.Digest = digest
	user.Posts = make([]Post, 0)
	query := connection.Gorm.Create(&user)
	if connection.Gorm.NewRecord(user) == true {
		return user, errors.New("user email exists")
	}
	if query.Error != nil {
		return user, query.Error
	}
	return user, nil
}

// GetAll or user.GetAll fetches all users with post data merged from the database.
func (user User) GetAll() ([]User, error) {
	var users []User
	query := connection.Gorm.Find(&users)
	if query.Error != nil {
		if query.Error == gorm.RecordNotFound {
			users = make([]User, 0)
			return users, nil
		}
		return users, query.Error
	}
	for index, user := range users {
		user, err := user.Get()
		if err != nil {
			return users, err
		}
		users[index] = user
	}
	return users, nil
}
<|endoftext|>


<commit_before>package gobucket

import (
	"net/http"
	"testing"
	"fmt"
	"reflect"
)

func TestPullRequestsService_GetAll(t *testing.T) {
	setUp()
	defer tearDown()

	mux.HandleFunc("/2.0/repositories/batman/batcave/pullrequests/", func(w http.ResponseWriter, r *http.Request) {
			if m := "GET"; m != r.Method {
				t.Errorf("Request method = %v, expected %v", r.Method, m)
			}
			if r.URL.Query().Get("page") == "1" {
				fmt.Fprint(w, `{
  "pagelen": 10,
  "next": "https://bitbucket.org/api/2.0/repositories/batman/?page=2",
  "values": [
    {
      "title": "Recognition / GenericBundle"
    },
    {
      "title": "Recognition / ResellerBundle"
    },
    {
      "title": "Recognition / CatalogueBundle"
    },
    {
      "title": "Recognition / Recognition Service"
    },
    {
      "title": "Recognition / BatchBundle"
    },
    {
      "title": "Recognition / Image Service"
    },
    {
      "title": "Recognition / Super User"
    },
    {
      "title": "Utility / Phingistrano"
    },
    {
      "title": "Internal / GIT Hooks"
    },
    {
      "title": "Internal / Bare Project Template"
    }
  ],
  "page": 1,
  "size": 11
}`)
			} else {
				fmt.Fprint(w, `{
  "pagelen": 1,
  "values": [
    {
      "title": "Recognition / ClientBundle"
    }
  ],
  "page": 2,
  "size": 11
}`)
			}
		})

	resp, err := client.PullRequests.GetAll("batman", "batcave")

	if err != nil {
		t.Errorf("Expected no err, got %v", err)
	}

	respLen := len(resp);

	if respLen != 11 {
		t.Errorf("Response length = %v, expected %v", respLen, 11)
	}

	expected := []*PullRequest{
		&PullRequest{Title: "Recognition / GenericBundle"},
		&PullRequest{Title: "Recognition / ResellerBundle"},
		&PullRequest{Title: "Recognition / CatalogueBundle"},
		&PullRequest{Title: "Recognition / Recognition Service"},
		&PullRequest{Title: "Recognition / BatchBundle"},
		&PullRequest{Title: "Recognition / Image Service"},
		&PullRequest{Title: "Recognition / Super User"},
		&PullRequest{Title: "Utility / Phingistrano"},
		&PullRequest{Title: "Internal / GIT Hooks"},
		&PullRequest{Title: "Internal / Bare Project Template"},
		&PullRequest{Title: "Recognition / ClientBundle"},
	}

	if !reflect.DeepEqual(resp, expected) {
		t.Errorf("Response body = %v, expected %v", resp, expected)
	}
}

func TestPullRequestsService_GetBranch(t *testing.T) {
	setUp()
	defer tearDown()

	mux.HandleFunc("/2.0/repositories/batman/batcave/pullrequests/", func(w http.ResponseWriter, r *http.Request) {
			if m := "GET"; m != r.Method {
				t.Errorf("Request method = %v, expected %v", r.Method, m)
			}
			if r.URL.Query().Get("page") == "1" {
				fmt.Fprint(w, `{
  "pagelen": 10,
  "next": "https://bitbucket.org/api/2.0/repositories/batman/?page=2",
  "values": [
    {
      "title": "Recognition / GenericBundle",
      "branch": {"Name":"test"}
    },
    {
      "title": "Recognition / ResellerBundle",
      "branch": {"Name":"test"}
    },
    {
      "title": "Recognition / CatalogueBundle",
      "branch": {"Name":"test"}
    },
    {
      "title": "Recognition / Recognition Service",
      "branch": {"Name":"test"}
    },
    {
      "title": "Recognition / BatchBundle",
      "Source": {"branch": {"name":"thisone"}}
    },
    {
      "title": "Recognition / Image Service",
      "branch": {"name":"test"}
    },
    {
      "title": "Recognition / Super User",
      "branch": {"name":"test"}
    },
    {
      "title": "Utility / Phingistrano",
      "source": {"branch": {"name":"test"}}
    },
    {
      "title": "Internal / GIT Hooks",
      "source": {"branch": {"name":"test"}}
    },
    {
      "title": "Internal / Bare Project Template",
      "source": {"branch": {"name":"test"}}
    }
  ],
  "page": 1,
  "size": 11
}`)
			} else {
				fmt.Fprint(w, `{
  "pagelen": 1,
  "values": [
    {
      "title": "Recognition / ClientBundle",
      "source": {"branch": {"name":"test"}}
    }
  ],
  "page": 2,
  "size": 11
}`)
			}
		})

	resp, err := client.PullRequests.GetBranch("batman", "batcave", "thisone")

	if err != nil {
		t.Errorf("Expected no err, got %v", err)
	}

	expected := 		&PullRequest{
		Title: "Recognition / BatchBundle",
		Source: PlaceInfo{
			Branch: BranchName{
				Name:"thisone",
			},
		},
	}
	if !reflect.DeepEqual(resp, expected) {
		t.Errorf("Response body = %v, expected %v", resp, expected)
	}
}

func TestPullRequestsService_Approve(t *testing.T) {
	setUp()
	defer tearDown()

	apiHit := false

	mux.HandleFunc("/2.0/repositories/batman/batcave/pullrequests/123/approve", func(w http.ResponseWriter, r *http.Request) {
			if m := "POST"; m != r.Method {
				t.Errorf("Request method = %v, expected %v", r.Method, m)
			}
			apiHit = true
			fmt.Fprint(w, `{"status": "success"}`)
		})

	err := client.PullRequests.Approve("batman", "batcave", 123)

	if err != nil {
		t.Errorf("Expected no err, got %v", err)
	}

	if apiHit != true {
		t.Errorf("Expected to hit api but didn't")
	}
}

func TestPullRequestsService_Approve_LowerCase(t *testing.T) {
	setUp()
	defer tearDown()

	apiHit := false

	mux.HandleFunc("/2.0/repositories/batman/batcave/pullrequests/123/approve", func(w http.ResponseWriter, r *http.Request) {
			if m := "POST"; m != r.Method {
				t.Errorf("Request method = %v, expected %v", r.Method, m)
			}
			apiHit = true
			fmt.Fprint(w, `{"status": "success"}`)
		})

	err := client.PullRequests.Approve("Batman", "batCave", 123)

	if err != nil {
		t.Errorf("Expected no err, got %v", err)
	}

	if apiHit != true {
		t.Errorf("Expected to hit api but didn't")
	}
}

func TestPullRequestsService_Merge(t *testing.T) {
	setUp()
	defer tearDown()

	apiHit := false

	mux.HandleFunc("/2.0/repositories/batman/batcave/pullrequests/123/merge", func(w http.ResponseWriter, r *http.Request) {
			if m := "POST"; m != r.Method {
				t.Errorf("Request method = %v, expected %v", r.Method, m)
			}
			apiHit = true
			fmt.Fprint(w, `{"status": "success"}`)
		})

	err := client.PullRequests.Merge("Batman", "batCave", 123)

	if err != nil {
		t.Errorf("Expected no err, got %v", err)
	}

	if apiHit != true {
		t.Errorf("Expected to hit api but didn't")
	}
}

func TestPullRequestsService_Unapprove(t *testing.T) {
	setUp()
	defer tearDown()

	apiHit := false

	mux.HandleFunc("/2.0/repositories/batman/batcave/pullrequests/123/approve", func(w http.ResponseWriter, r *http.Request) {
			if m := "DELETE"; m != r.Method {
				t.Errorf("Request method = %v, expected %v", r.Method, m)
			}
			apiHit = true
			fmt.Fprint(w, `{"status": "success"}`)
		})

	err := client.PullRequests.Unapprove("batman", "batcave", 123)

	if err != nil {
		t.Errorf("Expected no err, got %v", err)
	}

	if apiHit != true {
		t.Errorf("Expected to hit api but didn't")
	}
}

func TestPullRequest_GetById(t *testing.T) {
	setUp()
	defer tearDown()

	apiHit := false

	mux.HandleFunc("/2.0/repositories/batman/batcave/pullrequests/123", func(w http.ResponseWriter, r *http.Request) {
			if m := "GET"; m != r.Method {
				t.Errorf("Request method = %v, expected %v", r.Method, m)
			}
			apiHit = true
			fmt.Fprint(w, `{"title": "Recognition / GenericBundle"}`)
		})
	resp, err := client.PullRequests.GetById("batman", "batcave", 123)

	if err != nil {
		t.Errorf("Expected no err, got %v", err)
	}

	expected := 		&PullRequest{
		Title: "Recognition / GenericBundle",
	}
	if !reflect.DeepEqual(resp, expected) {
		t.Errorf("Response body = %v, expected %v", resp, expected)
	}
}

func TestPullRequest_GetApprovals(t *testing.T) {
	setUp()
	defer tearDown()

	expectedUser :=  User{DisplayName: "Iain Cambridge"}

	pr := PullRequest{}
	pr.Participants = []Participant{
		Participant{Role: "REVIEWER", User:expectedUser, Approved: true},
		Participant{Role: "REVIEWER", User:User{DisplayName: "Johnny"}, Approved: false},
	}

	found := pr.GetApprovals()
	expected := []User{expectedUser}
	if !reflect.DeepEqual(found, expected) {
		t.Errorf("Approvals = %v, expected %v", found, expected)
	}
}

<commit_msg>Add forgotten tests to get owner and get repo<commit_after>package gobucket

import (
	"net/http"
	"testing"
	"fmt"
	"reflect"
)

func TestPullRequestsService_GetAll(t *testing.T) {
	setUp()
	defer tearDown()

	mux.HandleFunc("/2.0/repositories/batman/batcave/pullrequests/", func(w http.ResponseWriter, r *http.Request) {
			if m := "GET"; m != r.Method {
				t.Errorf("Request method = %v, expected %v", r.Method, m)
			}
			if r.URL.Query().Get("page") == "1" {
				fmt.Fprint(w, `{
  "pagelen": 10,
  "next": "https://bitbucket.org/api/2.0/repositories/batman/?page=2",
  "values": [
    {
      "title": "Recognition / GenericBundle"
    },
    {
      "title": "Recognition / ResellerBundle"
    },
    {
      "title": "Recognition / CatalogueBundle"
    },
    {
      "title": "Recognition / Recognition Service"
    },
    {
      "title": "Recognition / BatchBundle"
    },
    {
      "title": "Recognition / Image Service"
    },
    {
      "title": "Recognition / Super User"
    },
    {
      "title": "Utility / Phingistrano"
    },
    {
      "title": "Internal / GIT Hooks"
    },
    {
      "title": "Internal / Bare Project Template"
    }
  ],
  "page": 1,
  "size": 11
}`)
			} else {
				fmt.Fprint(w, `{
  "pagelen": 1,
  "values": [
    {
      "title": "Recognition / ClientBundle"
    }
  ],
  "page": 2,
  "size": 11
}`)
			}
		})

	resp, err := client.PullRequests.GetAll("batman", "batcave")

	if err != nil {
		t.Errorf("Expected no err, got %v", err)
	}

	respLen := len(resp);

	if respLen != 11 {
		t.Errorf("Response length = %v, expected %v", respLen, 11)
	}

	expected := []*PullRequest{
		&PullRequest{Title: "Recognition / GenericBundle"},
		&PullRequest{Title: "Recognition / ResellerBundle"},
		&PullRequest{Title: "Recognition / CatalogueBundle"},
		&PullRequest{Title: "Recognition / Recognition Service"},
		&PullRequest{Title: "Recognition / BatchBundle"},
		&PullRequest{Title: "Recognition / Image Service"},
		&PullRequest{Title: "Recognition / Super User"},
		&PullRequest{Title: "Utility / Phingistrano"},
		&PullRequest{Title: "Internal / GIT Hooks"},
		&PullRequest{Title: "Internal / Bare Project Template"},
		&PullRequest{Title: "Recognition / ClientBundle"},
	}

	if !reflect.DeepEqual(resp, expected) {
		t.Errorf("Response body = %v, expected %v", resp, expected)
	}
}

func TestPullRequestsService_GetBranch(t *testing.T) {
	setUp()
	defer tearDown()

	mux.HandleFunc("/2.0/repositories/batman/batcave/pullrequests/", func(w http.ResponseWriter, r *http.Request) {
			if m := "GET"; m != r.Method {
				t.Errorf("Request method = %v, expected %v", r.Method, m)
			}
			if r.URL.Query().Get("page") == "1" {
				fmt.Fprint(w, `{
  "pagelen": 10,
  "next": "https://bitbucket.org/api/2.0/repositories/batman/?page=2",
  "values": [
    {
      "title": "Recognition / GenericBundle",
      "branch": {"Name":"test"}
    },
    {
      "title": "Recognition / ResellerBundle",
      "branch": {"Name":"test"}
    },
    {
      "title": "Recognition / CatalogueBundle",
      "branch": {"Name":"test"}
    },
    {
      "title": "Recognition / Recognition Service",
      "branch": {"Name":"test"}
    },
    {
      "title": "Recognition / BatchBundle",
      "Source": {"branch": {"name":"thisone"}}
    },
    {
      "title": "Recognition / Image Service",
      "branch": {"name":"test"}
    },
    {
      "title": "Recognition / Super User",
      "branch": {"name":"test"}
    },
    {
      "title": "Utility / Phingistrano",
      "source": {"branch": {"name":"test"}}
    },
    {
      "title": "Internal / GIT Hooks",
      "source": {"branch": {"name":"test"}}
    },
    {
      "title": "Internal / Bare Project Template",
      "source": {"branch": {"name":"test"}}
    }
  ],
  "page": 1,
  "size": 11
}`)
			} else {
				fmt.Fprint(w, `{
  "pagelen": 1,
  "values": [
    {
      "title": "Recognition / ClientBundle",
      "source": {"branch": {"name":"test"}}
    }
  ],
  "page": 2,
  "size": 11
}`)
			}
		})

	resp, err := client.PullRequests.GetBranch("batman", "batcave", "thisone")

	if err != nil {
		t.Errorf("Expected no err, got %v", err)
	}

	expected := 		&PullRequest{
		Title: "Recognition / BatchBundle",
		Source: PlaceInfo{
			Branch: BranchName{
				Name:"thisone",
			},
		},
	}
	if !reflect.DeepEqual(resp, expected) {
		t.Errorf("Response body = %v, expected %v", resp, expected)
	}
}

func TestPullRequestsService_Approve(t *testing.T) {
	setUp()
	defer tearDown()

	apiHit := false

	mux.HandleFunc("/2.0/repositories/batman/batcave/pullrequests/123/approve", func(w http.ResponseWriter, r *http.Request) {
			if m := "POST"; m != r.Method {
				t.Errorf("Request method = %v, expected %v", r.Method, m)
			}
			apiHit = true
			fmt.Fprint(w, `{"status": "success"}`)
		})

	err := client.PullRequests.Approve("batman", "batcave", 123)

	if err != nil {
		t.Errorf("Expected no err, got %v", err)
	}

	if apiHit != true {
		t.Errorf("Expected to hit api but didn't")
	}
}

func TestPullRequestsService_Approve_LowerCase(t *testing.T) {
	setUp()
	defer tearDown()

	apiHit := false

	mux.HandleFunc("/2.0/repositories/batman/batcave/pullrequests/123/approve", func(w http.ResponseWriter, r *http.Request) {
			if m := "POST"; m != r.Method {
				t.Errorf("Request method = %v, expected %v", r.Method, m)
			}
			apiHit = true
			fmt.Fprint(w, `{"status": "success"}`)
		})

	err := client.PullRequests.Approve("Batman", "batCave", 123)

	if err != nil {
		t.Errorf("Expected no err, got %v", err)
	}

	if apiHit != true {
		t.Errorf("Expected to hit api but didn't")
	}
}

func TestPullRequestsService_Merge(t *testing.T) {
	setUp()
	defer tearDown()

	apiHit := false

	mux.HandleFunc("/2.0/repositories/batman/batcave/pullrequests/123/merge", func(w http.ResponseWriter, r *http.Request) {
			if m := "POST"; m != r.Method {
				t.Errorf("Request method = %v, expected %v", r.Method, m)
			}
			apiHit = true
			fmt.Fprint(w, `{"status": "success"}`)
		})

	err := client.PullRequests.Merge("Batman", "batCave", 123)

	if err != nil {
		t.Errorf("Expected no err, got %v", err)
	}

	if apiHit != true {
		t.Errorf("Expected to hit api but didn't")
	}
}

func TestPullRequestsService_Unapprove(t *testing.T) {
	setUp()
	defer tearDown()

	apiHit := false

	mux.HandleFunc("/2.0/repositories/batman/batcave/pullrequests/123/approve", func(w http.ResponseWriter, r *http.Request) {
			if m := "DELETE"; m != r.Method {
				t.Errorf("Request method = %v, expected %v", r.Method, m)
			}
			apiHit = true
			fmt.Fprint(w, `{"status": "success"}`)
		})

	err := client.PullRequests.Unapprove("batman", "batcave", 123)

	if err != nil {
		t.Errorf("Expected no err, got %v", err)
	}

	if apiHit != true {
		t.Errorf("Expected to hit api but didn't")
	}
}

func TestPullRequest_GetById(t *testing.T) {
	setUp()
	defer tearDown()

	apiHit := false

	mux.HandleFunc("/2.0/repositories/batman/batcave/pullrequests/123", func(w http.ResponseWriter, r *http.Request) {
			if m := "GET"; m != r.Method {
				t.Errorf("Request method = %v, expected %v", r.Method, m)
			}
			apiHit = true
			fmt.Fprint(w, `{"title": "Recognition / GenericBundle"}`)
		})
	resp, err := client.PullRequests.GetById("batman", "batcave", 123)

	if err != nil {
		t.Errorf("Expected no err, got %v", err)
	}

	expected := 		&PullRequest{
		Title: "Recognition / GenericBundle",
	}
	if !reflect.DeepEqual(resp, expected) {
		t.Errorf("Response body = %v, expected %v", resp, expected)
	}
}

func TestPullRequest_GetApprovals(t *testing.T) {
	setUp()
	defer tearDown()

	expectedUser :=  User{DisplayName: "Iain Cambridge"}

	pr := PullRequest{}
	pr.Participants = []Participant{
		Participant{Role: "REVIEWER", User:expectedUser, Approved: true},
		Participant{Role: "REVIEWER", User:User{DisplayName: "Johnny"}, Approved: false},
	}

	found := pr.GetApprovals()
	expected := []User{expectedUser}
	if !reflect.DeepEqual(found, expected) {
		t.Errorf("Approvals = %v, expected %v", found, expected)
	}
}

func TestPullRequest_GetOwner(t *testing.T) {
	setUp()
	defer tearDown()

	pr := PullRequest{}
	pr.Destination.Repository.FullName = "batman/cave-system"
	owner := pr.GetOwner()
	expected := "batman"
	if owner != expected {
		t.Errorf("Expected %v, got %v", expected, owner)
	}
}

func TestPullRequest_GetRepo(t *testing.T) {
	setUp()
	defer tearDown()

	pr := PullRequest{}
	pr.Destination.Repository.FullName = "batman/cave-system"
	owner := pr.GetRepoName()
	expected := "cave-system"
	if owner != expected {
		t.Errorf("Expected %v, got %v", expected, owner)
	}
}

<|endoftext|>


<commit_before>package node

import (
	"errors"
	"fmt"
	"net"
	"net/http"
	"time"
)

// HTTPStream implements a raft stream for use with Golang's net/http.
type HTTPStream struct {
	closed   bool
	addr     net.Addr
	accepted chan net.Conn
}

// NewHTTPStream will instantiate a new HTTPStream.
func NewHTTPStream(addr string) (*HTTPStream, error) {
	a, err := net.ResolveTCPAddr("tcp", addr)
	if err != nil {
		return nil, err
	}

	h := &HTTPStream{
		addr:     a,
		accepted: make(chan net.Conn),
	}

	return h, nil
}

// Dial will dial a remote http endpoint (and implement raft.StreamLayer).
func (h *HTTPStream) Dial(address string, timeout time.Duration) (net.Conn, error) {
	conn, err := net.DialTimeout("tcp", address, timeout)
	if err != nil {
		return nil, err
	}

	open := fmt.Sprintf("GET /raft HTTP/1.1\nHost: %s\nUpgrade: raft-0\n\n", h.addr.String())

	_, err = conn.Write([]byte(open))
	if err != nil {
		conn.Close()

		return nil, err
	}

	return conn, nil
}

// Accept waits for and returns the next connection to the listener.
func (h *HTTPStream) Accept() (net.Conn, error) {
	if h.closed {
		return nil, errors.New("Server is shutting down")
	}

	return <-h.accepted, nil
}

// Close closes the listener.
// Any blocked Accept operations will be unblocked and return errors.
func (h *HTTPStream) Close() error {
	h.closed = true

	return nil
}

// Addr returns the listener's network address.
func (h *HTTPStream) Addr() net.Addr {
	return h.addr
}

// ServeHTTP implements the http.Handler interface.
func (h *HTTPStream) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if h.closed {
		http.Error(w, "Server is shutting down", http.StatusServiceUnavailable)
		return
	}

	upgrade := r.Header.Get("Upgrade")
	if upgrade != "raft-0" {
		http.Error(w, "This endpoint is for streaming raft", http.StatusBadRequest)
		return
	}

	hj, ok := w.(http.Hijacker)
	if !ok {
		http.Error(w, "webserver doesn't support hijacking", http.StatusInternalServerError)
		return
	}

	// We hijack the connection.
	conn, _, err := hj.Hijack()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	// Undo deadlines set by webserver.
	conn.SetDeadline(time.Time{})
	conn.SetWriteDeadline(time.Time{})

	h.accepted <- conn
}
<commit_msg>Allow HTTPStream to bind to correct IP when connecting.<commit_after>package node

import (
	"errors"
	"fmt"
	"net"
	"net/http"
	"time"
)

// HTTPStream implements a raft stream for use with Golang's net/http.
type HTTPStream struct {
	closed   bool
	addr     net.Addr
	accepted chan net.Conn
	dial     net.Dialer
}

// NewHTTPStream will instantiate a new HTTPStream.
func NewHTTPStream(addr string) (*HTTPStream, error) {
	a, err := net.ResolveTCPAddr("tcp", addr)
	if err != nil {
		return nil, err
	}

	// We try to derive the local address too. This doesn't make much sense
	// in the real world, but it makes debugging networking issues with
	// multiple nodes on the same host much easier.
	// This should never fail as long as the last call to ResolveTCPAddr()
	// with the same input went well.
	local, _ := net.ResolveTCPAddr("tcp", addr)
	local.Port = 0

	// Set up our own dialer.
	dial := net.Dialer{
		LocalAddr: local,

		// Some crappy NAT devices will close a connection after 30 seconds of
		// inactivity. We try to keep alive every 25th second to counter this.
		KeepAlive: time.Second * 25,
	}

	h := &HTTPStream{
		addr:     a,
		accepted: make(chan net.Conn),
		dial:     dial,
	}

	return h, nil
}

// Dial will dial a remote http endpoint (and implement raft.StreamLayer).
func (h *HTTPStream) Dial(address string, timeout time.Duration) (net.Conn, error) {
	// Make a copy of our dialer to allow custom timeout.
	dial := h.dial
	dial.Timeout = timeout

	conn, err := dial.Dial("tcp", address)
	if err != nil {
		return nil, err
	}

	open := fmt.Sprintf("GET /raft HTTP/1.1\nHost: %s\nUpgrade: raft-0\n\n", h.addr.String())

	_, err = conn.Write([]byte(open))
	if err != nil {
		conn.Close()

		return nil, err
	}

	return conn, nil
}

// Accept waits for and returns the next connection to the listener.
func (h *HTTPStream) Accept() (net.Conn, error) {
	if h.closed {
		return nil, errors.New("Server is shutting down")
	}

	return <-h.accepted, nil
}

// Close closes the listener.
// Any blocked Accept operations will be unblocked and return errors.
func (h *HTTPStream) Close() error {
	h.closed = true

	return nil
}

// Addr returns the listener's network address.
func (h *HTTPStream) Addr() net.Addr {
	return h.addr
}

// ServeHTTP implements the http.Handler interface.
func (h *HTTPStream) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if h.closed {
		http.Error(w, "Server is shutting down", http.StatusServiceUnavailable)
		return
	}

	upgrade := r.Header.Get("Upgrade")
	if upgrade != "raft-0" {
		http.Error(w, "This endpoint is for streaming raft", http.StatusBadRequest)
		return
	}

	hj, ok := w.(http.Hijacker)
	if !ok {
		http.Error(w, "webserver doesn't support hijacking", http.StatusInternalServerError)
		return
	}

	// We hijack the connection.
	conn, _, err := hj.Hijack()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	// Undo deadlines set by webserver.
	conn.SetDeadline(time.Time{})
	conn.SetWriteDeadline(time.Time{})

	h.accepted <- conn
}
<|endoftext|>


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

// maintserve is a program that serves Go issues over HTTP, so they
// can be viewed in a browser. It uses x/build/maintner/godata as
// its backing source of data.
//
// It statically embeds all the resources it uses, so it's possible to use
// it when offline. During that time, the corpus will not be able to update,
// and GitHub user profile pictures won't load.
package main

import (
	"context"
	"flag"
	"fmt"
	"html/template"
	"log"
	"mime"
	"net/http"
	"net/url"
	"sort"
	"strings"
	"time"

	"github.com/shurcooL/gofontwoff"
	"github.com/shurcooL/httpgzip"
	"github.com/shurcooL/issues"
	maintnerissues "github.com/shurcooL/issues/maintner"
	"github.com/shurcooL/issuesapp"
	"golang.org/x/build/maintner"
	"golang.org/x/build/maintner/godata"
)

var httpFlag = flag.String("http", ":8080", "Listen for HTTP connections on this address.")

func main() {
	flag.Parse()

	err := run()
	if err != nil {
		log.Fatalln(err)
	}
}

func run() error {
	if err := mime.AddExtensionType(".woff2", "application/font-woff"); err != nil {
		return err
	}

	corpus, err := godata.Get(context.Background())
	if err != nil {
		return err
	}
	issuesService := maintnerissues.NewService(corpus)
	issuesApp := issuesapp.New(issuesService, nil, issuesapp.Options{
		HeadPre: `<meta name="viewport" content="width=device-width">
<link href="/assets/fonts/fonts.css" rel="stylesheet" type="text/css">
<link href="/assets/style.css" rel="stylesheet" type="text/css">`,
		HeadPost: ``,
		BodyPre: `

{{/* Override new comment component to link to original issue for leaving comments. */}}
{{define "new-comment"}}

	View original issue to comment.

{{end}}`,
		DisableReactions: true,
	})

	// TODO: Implement background updates for corpus while the appliation is running.
	//       Right now, it only updates at startup.
	//       It's likely just a matter of calling RLock/RUnlock before all read operations,
	//       and launching a background goroutine that occasionally calls corpus.Update()
	//       or corpus.Sync() or something.

	printServingAt(*httpFlag)
	err = http.ListenAndServe(*httpFlag, &handler{
		c:             corpus,
		fontsHandler:  httpgzip.FileServer(gofontwoff.Assets, httpgzip.FileServerOptions{}),
		issuesHandler: issuesApp,
	})
	return err
}

func printServingAt(addr string) {
	hostPort := addr
	if strings.HasPrefix(hostPort, ":") {
		hostPort = "localhost" + hostPort
	}
	fmt.Printf("serving at http://%s/\n", hostPort)
}

// handler handles all requests to maintserve. It acts like a request multiplexer,
// choosing from various endpoints and parsing the repository ID from URL.
type handler struct {
	c             *maintner.Corpus
	fontsHandler  http.Handler
	issuesHandler http.Handler
}

func (h *handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	// Handle "/".
	if req.URL.Path == "/" {
		h.serveIndex(w, req)
		return
	}

	// Handle "/assets/fonts/...".
	if strings.HasPrefix(req.URL.Path, "/assets/fonts") {
		req = stripPrefix(req, len("/assets/fonts"))
		h.fontsHandler.ServeHTTP(w, req)
		return
	}

	// Handle "/assets/style.css".
	if req.URL.Path == "/assets/style.css" {
		http.ServeContent(w, req, "style.css", time.Time{}, strings.NewReader(styleCSS))
		return
	}

	// Handle "/owner/repo/..." URLs.
	elems := strings.SplitN(req.URL.Path[1:], "/", 3)
	if len(elems) < 2 {
		http.Error(w, "404 Not Found", http.StatusNotFound)
		return
	}
	owner, repo := elems[0], elems[1]
	baseURLLen := 1 + len(owner) + 1 + len(repo) // Base URL is "/owner/repo".
	if baseURL := req.URL.Path[:baseURLLen]; req.URL.Path == baseURL+"/" {
		// Redirect "/owner/repo/" to "/owner/repo".
		if req.URL.RawQuery != "" {
			baseURL = "?" + req.URL.RawQuery
		}
		http.Redirect(w, req, baseURL, http.StatusFound)
		return
	}
	req = stripPrefix(req, baseURLLen)
	h.serveIssues(w, req, maintner.GithubRepoID{Owner: owner, Repo: repo})
}

var indexHTML = template.Must(template.New("").Parse(`

		<title>maintserve</title>
		<meta name="viewport" content="width=device-width">
		<link href="/assets/fonts/fonts.css" rel="stylesheet" type="text/css">
		<link href="/assets/style.css" rel="stylesheet" type="text/css">

maintserve

Repos

{{range .}}

{{.RepoID}} ({{.Count}} issues)

				{{- end}}

`))

// serveIndex serves the index page, which lists all available repositories.
func (h *handler) serveIndex(w http.ResponseWriter, req *http.Request) {
	type repo struct {
		RepoID maintner.GithubRepoID
		Count  uint64 // Issues count.
	}
	var repos []repo
	err := h.c.GitHub().ForeachRepo(func(r *maintner.GitHubRepo) error {
		issues, err := countIssues(r)
		if err != nil {
			return err
		}
		repos = append(repos, repo{
			RepoID: r.ID(),
			Count:  issues,
		})
		return nil
	})
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	sort.Slice(repos, func(i, j int) bool {
		return repos[i].RepoID.String() < repos[j].RepoID.String()
	})

	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	err = indexHTML.Execute(w, repos)
	if err != nil {
		log.Println(err)
	}
}

// countIssues reports the number of issues in a GitHubRepo r.
func countIssues(r *maintner.GitHubRepo) (uint64, error) {
	var issues uint64
	err := r.ForeachIssue(func(i *maintner.GitHubIssue) error {
		if i.NotExist {
			return nil
		}
		issues++
		return nil
	})
	return issues, err
}

// serveIssues serves issues for repository id.
func (h *handler) serveIssues(w http.ResponseWriter, req *http.Request, id maintner.GithubRepoID) {
	if h.c.GitHub().Repo(id.Owner, id.Repo) == nil {
		http.Error(w, fmt.Sprintf("404 Not Found\n\nrepository %q not found", id), http.StatusNotFound)
		return
	}

	req = req.WithContext(context.WithValue(req.Context(),
		issuesapp.RepoSpecContextKey, issues.RepoSpec{URI: fmt.Sprintf("%s/%s", id.Owner, id.Repo)}))
	req = req.WithContext(context.WithValue(req.Context(),
		issuesapp.BaseURIContextKey, fmt.Sprintf("/%s/%s", id.Owner, id.Repo)))
	h.issuesHandler.ServeHTTP(w, req)
}

// stripPrefix returns request r with prefix of length prefixLen stripped from r.URL.Path.
// prefixLen must not be longer than len(r.URL.Path), otherwise stripPrefix panics.
// If r.URL.Path is empty after the prefix is stripped, the path is changed to "/".
func stripPrefix(r *http.Request, prefixLen int) *http.Request {
	r2 := new(http.Request)
	*r2 = *r
	r2.URL = new(url.URL)
	*r2.URL = *r.URL
	r2.URL.Path = r.URL.Path[prefixLen:]
	if r2.URL.Path == "" {
		r2.URL.Path = "/"
	}
	return r2
}

const styleCSS = `body {
	margin: 20px;
	font-family: Go;
	font-size: 14px;
	line-height: initial;
	color: #373a3c;
}
a {
	color: #0275d8;
	text-decoration: none;
}
a:focus, a:hover {
	color: #014c8c;
	text-decoration: underline;
}
.btn {
	font-family: inherit;
	font-size: 11px;
	line-height: 11px;
	height: 18px;
	border-radius: 4px;
	border: solid #d2d2d2 1px;
	background-color: #fff;
	box-shadow: 0 1px 1px rgba(0, 0, 0, .05);
}`
<commit_msg>maintner/cmd/maintserve: fix redirect logic<commit_after>// Copyright 2017 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// maintserve is a program that serves Go issues over HTTP, so they
// can be viewed in a browser. It uses x/build/maintner/godata as
// its backing source of data.
//
// It statically embeds all the resources it uses, so it's possible to use
// it when offline. During that time, the corpus will not be able to update,
// and GitHub user profile pictures won't load.
package main

import (
	"context"
	"flag"
	"fmt"
	"html/template"
	"log"
	"mime"
	"net/http"
	"net/url"
	"sort"
	"strings"
	"time"

	"github.com/shurcooL/gofontwoff"
	"github.com/shurcooL/httpgzip"
	"github.com/shurcooL/issues"
	maintnerissues "github.com/shurcooL/issues/maintner"
	"github.com/shurcooL/issuesapp"
	"golang.org/x/build/maintner"
	"golang.org/x/build/maintner/godata"
)

var httpFlag = flag.String("http", ":8080", "Listen for HTTP connections on this address.")

func main() {
	flag.Parse()

	err := run()
	if err != nil {
		log.Fatalln(err)
	}
}

func run() error {
	if err := mime.AddExtensionType(".woff2", "application/font-woff"); err != nil {
		return err
	}

	corpus, err := godata.Get(context.Background())
	if err != nil {
		return err
	}
	issuesService := maintnerissues.NewService(corpus)
	issuesApp := issuesapp.New(issuesService, nil, issuesapp.Options{
		HeadPre: `<meta name="viewport" content="width=device-width">
<link href="/assets/fonts/fonts.css" rel="stylesheet" type="text/css">
<link href="/assets/style.css" rel="stylesheet" type="text/css">`,
		HeadPost: ``,
		BodyPre: `

{{/* Override new comment component to link to original issue for leaving comments. */}}
{{define "new-comment"}}

	View original issue to comment.

{{end}}`,
		DisableReactions: true,
	})

	// TODO: Implement background updates for corpus while the appliation is running.
	//       Right now, it only updates at startup.
	//       It's likely just a matter of calling RLock/RUnlock before all read operations,
	//       and launching a background goroutine that occasionally calls corpus.Update()
	//       or corpus.Sync() or something.

	printServingAt(*httpFlag)
	err = http.ListenAndServe(*httpFlag, &handler{
		c:             corpus,
		fontsHandler:  httpgzip.FileServer(gofontwoff.Assets, httpgzip.FileServerOptions{}),
		issuesHandler: issuesApp,
	})
	return err
}

func printServingAt(addr string) {
	hostPort := addr
	if strings.HasPrefix(hostPort, ":") {
		hostPort = "localhost" + hostPort
	}
	fmt.Printf("serving at http://%s/\n", hostPort)
}

// handler handles all requests to maintserve. It acts like a request multiplexer,
// choosing from various endpoints and parsing the repository ID from URL.
type handler struct {
	c             *maintner.Corpus
	fontsHandler  http.Handler
	issuesHandler http.Handler
}

func (h *handler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	// Handle "/".
	if req.URL.Path == "/" {
		h.serveIndex(w, req)
		return
	}

	// Handle "/assets/fonts/...".
	if strings.HasPrefix(req.URL.Path, "/assets/fonts") {
		req = stripPrefix(req, len("/assets/fonts"))
		h.fontsHandler.ServeHTTP(w, req)
		return
	}

	// Handle "/assets/style.css".
	if req.URL.Path == "/assets/style.css" {
		http.ServeContent(w, req, "style.css", time.Time{}, strings.NewReader(styleCSS))
		return
	}

	// Handle "/owner/repo/..." URLs.
	elems := strings.SplitN(req.URL.Path[1:], "/", 3)
	if len(elems) < 2 {
		http.Error(w, "404 Not Found", http.StatusNotFound)
		return
	}
	owner, repo := elems[0], elems[1]
	baseURLLen := 1 + len(owner) + 1 + len(repo) // Base URL is "/owner/repo".
	if baseURL := req.URL.Path[:baseURLLen]; req.URL.Path == baseURL+"/" {
		// Redirect "/owner/repo/" to "/owner/repo".
		if req.URL.RawQuery != "" {
			baseURL += "?" + req.URL.RawQuery
		}
		http.Redirect(w, req, baseURL, http.StatusFound)
		return
	}
	req = stripPrefix(req, baseURLLen)
	h.serveIssues(w, req, maintner.GithubRepoID{Owner: owner, Repo: repo})
}

var indexHTML = template.Must(template.New("").Parse(`

		<title>maintserve</title>
		<meta name="viewport" content="width=device-width">
		<link href="/assets/fonts/fonts.css" rel="stylesheet" type="text/css">
		<link href="/assets/style.css" rel="stylesheet" type="text/css">

maintserve

Repos

{{range .}}

{{.RepoID}} ({{.Count}} issues)

				{{- end}}

`))

// serveIndex serves the index page, which lists all available repositories.
func (h *handler) serveIndex(w http.ResponseWriter, req *http.Request) {
	type repo struct {
		RepoID maintner.GithubRepoID
		Count  uint64 // Issues count.
	}
	var repos []repo
	err := h.c.GitHub().ForeachRepo(func(r *maintner.GitHubRepo) error {
		issues, err := countIssues(r)
		if err != nil {
			return err
		}
		repos = append(repos, repo{
			RepoID: r.ID(),
			Count:  issues,
		})
		return nil
	})
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	sort.Slice(repos, func(i, j int) bool {
		return repos[i].RepoID.String() < repos[j].RepoID.String()
	})

	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	err = indexHTML.Execute(w, repos)
	if err != nil {
		log.Println(err)
	}
}

// countIssues reports the number of issues in a GitHubRepo r.
func countIssues(r *maintner.GitHubRepo) (uint64, error) {
	var issues uint64
	err := r.ForeachIssue(func(i *maintner.GitHubIssue) error {
		if i.NotExist {
			return nil
		}
		issues++
		return nil
	})
	return issues, err
}

// serveIssues serves issues for repository id.
func (h *handler) serveIssues(w http.ResponseWriter, req *http.Request, id maintner.GithubRepoID) {
	if h.c.GitHub().Repo(id.Owner, id.Repo) == nil {
		http.Error(w, fmt.Sprintf("404 Not Found\n\nrepository %q not found", id), http.StatusNotFound)
		return
	}

	req = req.WithContext(context.WithValue(req.Context(),
		issuesapp.RepoSpecContextKey, issues.RepoSpec{URI: fmt.Sprintf("%s/%s", id.Owner, id.Repo)}))
	req = req.WithContext(context.WithValue(req.Context(),
		issuesapp.BaseURIContextKey, fmt.Sprintf("/%s/%s", id.Owner, id.Repo)))
	h.issuesHandler.ServeHTTP(w, req)
}

// stripPrefix returns request r with prefix of length prefixLen stripped from r.URL.Path.
// prefixLen must not be longer than len(r.URL.Path), otherwise stripPrefix panics.
// If r.URL.Path is empty after the prefix is stripped, the path is changed to "/".
func stripPrefix(r *http.Request, prefixLen int) *http.Request {
	r2 := new(http.Request)
	*r2 = *r
	r2.URL = new(url.URL)
	*r2.URL = *r.URL
	r2.URL.Path = r.URL.Path[prefixLen:]
	if r2.URL.Path == "" {
		r2.URL.Path = "/"
	}
	return r2
}

const styleCSS = `body {
	margin: 20px;
	font-family: Go;
	font-size: 14px;
	line-height: initial;
	color: #373a3c;
}
a {
	color: #0275d8;
	text-decoration: none;
}
a:focus, a:hover {
	color: #014c8c;
	text-decoration: underline;
}
.btn {
	font-family: inherit;
	font-size: 11px;
	line-height: 11px;
	height: 18px;
	border-radius: 4px;
	border: solid #d2d2d2 1px;
	background-color: #fff;
	box-shadow: 0 1px 1px rgba(0, 0, 0, .05);
}`
<|endoftext|>