diff --git a/cmd/jkl/comment.go b/cmd/jkl/comment.go index bfb313f..fe1e27d 100644 --- a/cmd/jkl/comment.go +++ b/cmd/jkl/comment.go @@ -50,3 +50,7 @@ func (ccmd *CommentCmd) Comment() error { return jkl.AddComment(ccmd.issueKey, b.String()) } + +func (ccmd *CommentCmd) Run() error { + return ccmd.Comment() +} diff --git a/cmd/jkl/create.go b/cmd/jkl/create.go index 0df577a..46c0287 100644 --- a/cmd/jkl/create.go +++ b/cmd/jkl/create.go @@ -4,6 +4,7 @@ import ( "bytes" "errors" "flag" + "io" "os" "otremblay.com/jkl" @@ -27,7 +28,20 @@ func NewCreateCmd(args []string) (*CreateCmd, error) { var ErrCcmdJiraProjectRequired = errors.New("Jira project needs to be set") func (ccmd *CreateCmd) Create() error { - var b = bytes.NewBufferString(CREATE_TEMPLATE) + var b = bytes.NewBuffer([]byte{}) + var readfile bool + if fp := os.Getenv("JIRA_ISSUE_TEMPLATE"); fp != "" { + if f, err := os.Open(fp); err == nil { + _, err := io.Copy(b, f) + if err == nil { + readfile = true + } + + } + } + if !readfile { + b.WriteString(CREATE_TEMPLATE) + } var iss *jkl.JiraIssue var err error if ccmd.file != "" { @@ -49,6 +63,10 @@ func (ccmd *CreateCmd) Create() error { return jkl.Create(iss) } +func (ccmd *CreateCmd) Run() error { + return ccmd.Create() +} + const CREATE_TEMPLATE = `Issue Type: Summary: Description:` diff --git a/cmd/jkl/edit.go b/cmd/jkl/edit.go index 87aef2d..01a3a99 100644 --- a/cmd/jkl/edit.go +++ b/cmd/jkl/edit.go @@ -14,6 +14,7 @@ type EditCmd struct { args []string project string file string + taskKey string } func NewEditCmd(args []string) (*EditCmd, error) { @@ -22,12 +23,13 @@ func NewEditCmd(args []string) (*EditCmd, error) { f.StringVar(&ccmd.project, "p", "", "Jira project key") f.StringVar(&ccmd.file, "f", "filename", "File to get issue description from") f.Parse(args) + ccmd.taskKey = flag.Arg(0) return ccmd, nil } -func (ecmd *EditCmd) Edit(taskKey string) error { +func (ecmd *EditCmd) Edit() error { b := bytes.NewBuffer(nil) - iss, err := jkl.GetIssue(taskKey) + iss, err := jkl.GetIssue(ecmd.taskKey) if err != nil { return err } @@ -49,10 +51,14 @@ func (ecmd *EditCmd) Edit(taskKey string) error { } } - iss.Key = taskKey + iss.Key = ecmd.taskKey return jkl.Edit(iss) } +func (ecmd *EditCmd) Run() error { + return ecmd.Edit() +} + const EDIT_TEMPLATE = `Summary: {{.Fields.Summary}} Description: {{.Fields.Description}} ` diff --git a/cmd/jkl/editor.go b/cmd/jkl/editor.go index 149df27..6a3ead3 100644 --- a/cmd/jkl/editor.go +++ b/cmd/jkl/editor.go @@ -50,7 +50,7 @@ func GetIssueFromTmpFile(initial io.Reader) (*jkl.JiraIssue, error) { if err != nil { return nil, err } - return IssueFromFile(f2), nil + return IssueFromReader(f2), nil } func GetTextFromTmpFile(initial io.Reader) (io.Reader, error) { @@ -99,12 +99,12 @@ func GetIssueFromFile(filename string, initial io.Reader) (*jkl.JiraIssue, error if err != nil { return nil, err } - return IssueFromFile(f2), nil + return IssueFromReader(f2), nil } var spacex = regexp.MustCompile(`\s`) -func IssueFromFile(f io.Reader) *jkl.JiraIssue { +func IssueFromReader(f io.Reader) *jkl.JiraIssue { iss := &jkl.JiraIssue{Fields: &jkl.Fields{}} riss := reflect.ValueOf(iss).Elem() fieldsField := riss.FieldByName("Fields").Elem() @@ -118,20 +118,40 @@ func IssueFromFile(f io.Reader) *jkl.JiraIssue { parts := strings.Split(string(b), ":") potentialField := spacex.ReplaceAllString(parts[0], "") + // Is the current line a field in an issue directly? + // Also special cases: Objects that have a deeper depth + // have specific fields "flattened" for ease of use. + // I think this loop could be made more general, to account + // for deeper objects. Then again, there's not that many fields + // I actually care about yet. + // Custom fields are gonna be hell. + if newfield := fieldsField.FieldByName(potentialField); newfield.IsValid() { parts = parts[1:len(parts)] if potentialField == "IssueType" { - iss.Fields.IssueType = &jkl.IssueType{} - currentField = reflect.Value{} - f2 := newfield.Elem() - f3 := f2.FieldByName("Name") - f3.SetString(strings.TrimSpace(strings.Join(parts, ":"))) + if len(parts) > 0 { + iss.Fields.IssueType = &jkl.IssueType{} + currentField = reflect.Value{} + f2 := newfield.Elem() + f3 := f2.FieldByName("Name") + f3.SetString(strings.TrimSpace(strings.Join(parts, ":"))) + } } else if potentialField == "Project" { - iss.Fields.Project = &jkl.Project{} - currentField = reflect.Value{} - f2 := newfield.Elem() - f3 := f2.FieldByName("Key") - f3.SetString(strings.TrimSpace(strings.Join(parts, ":"))) + if len(parts) > 0 { + iss.Fields.Project = &jkl.Project{} + currentField = reflect.Value{} + f2 := newfield.Elem() + f3 := f2.FieldByName("Key") + f3.SetString(strings.TrimSpace(strings.Join(parts, ":"))) + } + } else if potentialField == "Parent" { + if len(parts) > 0 { + iss.Fields.Parent = &jkl.JiraIssue{} + currentField = reflect.Value{} + f2 := newfield.Elem() + f3 := f2.FieldByName("Key") + f3.SetString(strings.TrimSpace(strings.Join(parts, ":"))) + } } else { currentField = newfield } @@ -144,5 +164,5 @@ func IssueFromFile(f io.Reader) *jkl.JiraIssue { } func IssueFromList(list []string) *jkl.JiraIssue { - return IssueFromFile(bytes.NewBufferString(strings.Join(list, "\n"))) + return IssueFromReader(bytes.NewBufferString(strings.Join(list, "\n"))) } diff --git a/cmd/jkl/jkl.go b/cmd/jkl/jkl.go index 553ef6f..5136e6a 100644 --- a/cmd/jkl/jkl.go +++ b/cmd/jkl/jkl.go @@ -4,57 +4,81 @@ import ( "flag" "fmt" "log" + "os" + + "strings" "otremblay.com/jkl" ) var verbose = flag.Bool("v", false, "Output debug information about jkl") +var help = flag.Bool("h", false, "Outputs usage information message") func main() { jkl.FindRCFile() flag.Parse() jkl.Verbose = verbose - if len(flag.Args()) == 0 { - fmt.Print(usage) - return - } if err := runcmd(flag.Args()); err != nil { log.Println(err) } } func runcmd(args []string) error { + if len(args) == 0 { + if *help { + fmt.Fprintln(os.Stderr, usage) + flag.PrintDefaults() + return nil + } + args = append(args, "list") + } + if strings.Contains(args[0], "~") { + args = append([]string{"edit-comment"}, args...) + } + cmd, err := getCmd(args, 0) + if err != nil { + return err + } + return cmd.Run() +} + +func getCmd(args []string, depth int) (Runner, error) { switch args[0] { case "list": - if *verbose { - fmt.Println("Running List command") - } - lcmd, err := NewListCmd(flag.Args()[1:]) - if err != nil { - return err - } - return lcmd.List() + return NewListCmd(args[1:]) case "create": - ccmd, err := NewCreateCmd(flag.Args()[1:]) - if err != nil { - return err - } - return ccmd.Create() + return NewCreateCmd(args[1:]) case "task": - tcmd := &TaskCmd{} - return tcmd.Handle(flag.Args()[1:]) + tcmd := &TaskCmd{args: args[1:]} + return tcmd, nil case "edit": - ecmd := &EditCmd{} - return ecmd.Edit(flag.Arg(1)) + return NewEditCmd(args[1:]) case "comment": - ccmd, err := NewCommentCmd(flag.Args()[1:]) - if err != nil { - return err + return NewCommentCmd(args[1:]) + case "edit-comment": + return NewEditCommentCmd(args[1:]) + default: + // Think about this real hard. + // I want `jkl JIRA-1234 done` to move it to done. + // I want `jkl JIRA-1234` to print out info + // I want `jkl JIRA-1234 edit` to run the edit command. + // I want `jkl JIRA-1234 comment` to run the comment command. + // I want `jkl JIRA-1234 attach ` to run the attach command. + + if depth == 0 { + // Assume args[0] is a task key + args[0], args[1] = args[1], args[0] + return getCmd(args, depth+1) + } else { + // Swapping the first two args didn't help; + // this means it's a transition. + + // tcmd, err := NewTransitionCommand(args) + // if err != nil {return nil, err} + // return tcmd, nil } - return ccmd.Comment() } - fmt.Println(usage) - return nil + return nil, ErrTaskSubCommandNotFound } const usage = `Usage: @@ -65,4 +89,6 @@ Available commands: list create edit +comment + ` diff --git a/cmd/jkl/list.go b/cmd/jkl/list.go index 5c5ce46..a4c3f58 100644 --- a/cmd/jkl/list.go +++ b/cmd/jkl/list.go @@ -49,6 +49,9 @@ type ListCmd struct { func NewListCmd(args []string) (*ListCmd, error) { ccmd := &ListCmd{} f := flag.NewFlagSet("x", flag.ExitOnError) + if *verbose { + fmt.Println(&ccmd.tmplstr) + } f.StringVar(&ccmd.tmplstr, "listTemplate", "{{.Color}}{{.Key}}{{if .Color}}\x1b[39m{{end}}\t({{.Fields.IssueType.Name}}{{if .Fields.Parent}} of {{.Fields.Parent.Key}}{{end}})\t{{.Fields.Summary}}\t{{if .Fields.Assignee}}[{{.Fields.Assignee.Name}}]{{end}}\n", "Go template used in list command") f.Parse(args) ccmd.args = f.Args() @@ -58,6 +61,12 @@ func NewListCmd(args []string) (*ListCmd, error) { proj = fmt.Sprintf(" and project = %s ", proj) } ccmd.args = []string{fmt.Sprintf("sprint in openSprints() %s order by rank", proj)} + if *verbose { + fmt.Println("No arguments, running default command") + } + } + if *verbose { + fmt.Println(ccmd.args) } ccmd.tmpl = template.Must(template.New("listTemplate").Parse(ccmd.tmplstr)) return ccmd, nil @@ -78,3 +87,7 @@ func (l *ListCmd) List() error { } return nil } + +func (l *ListCmd) Run() error { + return l.List() +} diff --git a/cmd/jkl/task.go b/cmd/jkl/task.go index 81279b5..cfe90d6 100644 --- a/cmd/jkl/task.go +++ b/cmd/jkl/task.go @@ -7,15 +7,18 @@ import ( "otremblay.com/jkl" ) -type TaskCmd struct{} +type TaskCmd struct { + args []string +} -func (t *TaskCmd) Handle(args []string) error { - c := len(args) +// TODO: split in individual commands. +func (t *TaskCmd) Handle() error { + c := len(t.args) if c == 1 { - return t.Get(args[0]) + return t.Get(t.args[0]) } if c == 2 { - return t.Transition(args[0], args[1]) + return t.Transition(t.args[0], t.args[1]) } return ErrTaskSubCommandNotFound } @@ -34,3 +37,7 @@ func (t *TaskCmd) Get(taskKey string) error { func (t *TaskCmd) Transition(taskKey, transition string) error { return nil } + +func (t *TaskCmd) Run() error { + return t.Handle() +} diff --git a/issue.go b/issue.go index e30ec4d..4dc06ce 100644 --- a/issue.go +++ b/issue.go @@ -95,7 +95,11 @@ func (i *JiraIssue) URL() string { func (i *JiraIssue) String() string { var b = bytes.NewBuffer(nil) - err := issueTmpl.Execute(b, i) + var tmpl *template.Template = issueTmpl + if os.Getenv("JKLNOCOLOR") == "true" { + tmpl = issueTmplNoColor + } + err := tmpl.Execute(b, i) if err != nil { log.Fatalln(err) } @@ -117,4 +121,12 @@ var issueTmplTxt = "\x1b[1m{{.Key}}\x1b[0m\t{{if .Fields.IssueType}}[{{.Fields.I "\x1b[1mDescription:\x1b[0m {{.Fields.Description}} \n\n" + "\x1b[1mComments:\x1b[0m\n\n" + commentTemplate +var issueTmplNoColorTxt = "{{.Key}\t{{if .Fields.IssueType}}[{{.Fields.IssueType.Name}}]{{end}}\t{{.Fields.Summary}}\n\n" + + "{{if .Fields.Status}}Status:\t {{.Fields.Status.Name}}\n{{end}}" + + "{{if .Fields.Assignee}}Assignee:\t{{.Fields.Assignee.Name}}\n{{end}}\n" + + "Time Remaining/Original Estimate:\t{{.Fields.PrettyRemaining}} / {{.Fields.PrettyOriginalEstimate}}\n\n" + + "Description: {{.Fields.Description}} \n\n" + + "Comments:\n\n" + commentTemplate + var issueTmpl = template.Must(template.New("issueTmpl").Parse(issueTmplTxt)) +var issueTmplNoColor = template.Must(template.New("issueTmpl").Parse(issueTmplNoColorTxt)) \ No newline at end of file