WHAT DID I DO
This commit is contained in:
parent
020ac7a6fb
commit
0cc82e4f87
8 changed files with 157 additions and 51 deletions
|
|
@ -50,3 +50,7 @@ func (ccmd *CommentCmd) Comment() error {
|
||||||
|
|
||||||
return jkl.AddComment(ccmd.issueKey, b.String())
|
return jkl.AddComment(ccmd.issueKey, b.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ccmd *CommentCmd) Run() error {
|
||||||
|
return ccmd.Comment()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"otremblay.com/jkl"
|
"otremblay.com/jkl"
|
||||||
|
|
@ -27,7 +28,20 @@ func NewCreateCmd(args []string) (*CreateCmd, error) {
|
||||||
var ErrCcmdJiraProjectRequired = errors.New("Jira project needs to be set")
|
var ErrCcmdJiraProjectRequired = errors.New("Jira project needs to be set")
|
||||||
|
|
||||||
func (ccmd *CreateCmd) Create() error {
|
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 iss *jkl.JiraIssue
|
||||||
var err error
|
var err error
|
||||||
if ccmd.file != "" {
|
if ccmd.file != "" {
|
||||||
|
|
@ -49,6 +63,10 @@ func (ccmd *CreateCmd) Create() error {
|
||||||
return jkl.Create(iss)
|
return jkl.Create(iss)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ccmd *CreateCmd) Run() error {
|
||||||
|
return ccmd.Create()
|
||||||
|
}
|
||||||
|
|
||||||
const CREATE_TEMPLATE = `Issue Type:
|
const CREATE_TEMPLATE = `Issue Type:
|
||||||
Summary:
|
Summary:
|
||||||
Description:`
|
Description:`
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ type EditCmd struct {
|
||||||
args []string
|
args []string
|
||||||
project string
|
project string
|
||||||
file string
|
file string
|
||||||
|
taskKey string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewEditCmd(args []string) (*EditCmd, error) {
|
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.project, "p", "", "Jira project key")
|
||||||
f.StringVar(&ccmd.file, "f", "filename", "File to get issue description from")
|
f.StringVar(&ccmd.file, "f", "filename", "File to get issue description from")
|
||||||
f.Parse(args)
|
f.Parse(args)
|
||||||
|
ccmd.taskKey = flag.Arg(0)
|
||||||
return ccmd, nil
|
return ccmd, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ecmd *EditCmd) Edit(taskKey string) error {
|
func (ecmd *EditCmd) Edit() error {
|
||||||
b := bytes.NewBuffer(nil)
|
b := bytes.NewBuffer(nil)
|
||||||
iss, err := jkl.GetIssue(taskKey)
|
iss, err := jkl.GetIssue(ecmd.taskKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -49,10 +51,14 @@ func (ecmd *EditCmd) Edit(taskKey string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
iss.Key = taskKey
|
iss.Key = ecmd.taskKey
|
||||||
return jkl.Edit(iss)
|
return jkl.Edit(iss)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ecmd *EditCmd) Run() error {
|
||||||
|
return ecmd.Edit()
|
||||||
|
}
|
||||||
|
|
||||||
const EDIT_TEMPLATE = `Summary: {{.Fields.Summary}}
|
const EDIT_TEMPLATE = `Summary: {{.Fields.Summary}}
|
||||||
Description: {{.Fields.Description}}
|
Description: {{.Fields.Description}}
|
||||||
`
|
`
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ func GetIssueFromTmpFile(initial io.Reader) (*jkl.JiraIssue, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return IssueFromFile(f2), nil
|
return IssueFromReader(f2), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTextFromTmpFile(initial io.Reader) (io.Reader, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return IssueFromFile(f2), nil
|
return IssueFromReader(f2), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var spacex = regexp.MustCompile(`\s`)
|
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{}}
|
iss := &jkl.JiraIssue{Fields: &jkl.Fields{}}
|
||||||
riss := reflect.ValueOf(iss).Elem()
|
riss := reflect.ValueOf(iss).Elem()
|
||||||
fieldsField := riss.FieldByName("Fields").Elem()
|
fieldsField := riss.FieldByName("Fields").Elem()
|
||||||
|
|
@ -118,20 +118,40 @@ func IssueFromFile(f io.Reader) *jkl.JiraIssue {
|
||||||
parts := strings.Split(string(b), ":")
|
parts := strings.Split(string(b), ":")
|
||||||
potentialField := spacex.ReplaceAllString(parts[0], "")
|
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() {
|
if newfield := fieldsField.FieldByName(potentialField); newfield.IsValid() {
|
||||||
parts = parts[1:len(parts)]
|
parts = parts[1:len(parts)]
|
||||||
if potentialField == "IssueType" {
|
if potentialField == "IssueType" {
|
||||||
|
if len(parts) > 0 {
|
||||||
iss.Fields.IssueType = &jkl.IssueType{}
|
iss.Fields.IssueType = &jkl.IssueType{}
|
||||||
currentField = reflect.Value{}
|
currentField = reflect.Value{}
|
||||||
f2 := newfield.Elem()
|
f2 := newfield.Elem()
|
||||||
f3 := f2.FieldByName("Name")
|
f3 := f2.FieldByName("Name")
|
||||||
f3.SetString(strings.TrimSpace(strings.Join(parts, ":")))
|
f3.SetString(strings.TrimSpace(strings.Join(parts, ":")))
|
||||||
|
}
|
||||||
} else if potentialField == "Project" {
|
} else if potentialField == "Project" {
|
||||||
|
if len(parts) > 0 {
|
||||||
iss.Fields.Project = &jkl.Project{}
|
iss.Fields.Project = &jkl.Project{}
|
||||||
currentField = reflect.Value{}
|
currentField = reflect.Value{}
|
||||||
f2 := newfield.Elem()
|
f2 := newfield.Elem()
|
||||||
f3 := f2.FieldByName("Key")
|
f3 := f2.FieldByName("Key")
|
||||||
f3.SetString(strings.TrimSpace(strings.Join(parts, ":")))
|
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 {
|
} else {
|
||||||
currentField = newfield
|
currentField = newfield
|
||||||
}
|
}
|
||||||
|
|
@ -144,5 +164,5 @@ func IssueFromFile(f io.Reader) *jkl.JiraIssue {
|
||||||
}
|
}
|
||||||
|
|
||||||
func IssueFromList(list []string) *jkl.JiraIssue {
|
func IssueFromList(list []string) *jkl.JiraIssue {
|
||||||
return IssueFromFile(bytes.NewBufferString(strings.Join(list, "\n")))
|
return IssueFromReader(bytes.NewBufferString(strings.Join(list, "\n")))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,57 +4,81 @@ import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"strings"
|
||||||
|
|
||||||
"otremblay.com/jkl"
|
"otremblay.com/jkl"
|
||||||
)
|
)
|
||||||
|
|
||||||
var verbose = flag.Bool("v", false, "Output debug information about jkl")
|
var verbose = flag.Bool("v", false, "Output debug information about jkl")
|
||||||
|
var help = flag.Bool("h", false, "Outputs usage information message")
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
jkl.FindRCFile()
|
jkl.FindRCFile()
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
jkl.Verbose = verbose
|
jkl.Verbose = verbose
|
||||||
if len(flag.Args()) == 0 {
|
|
||||||
fmt.Print(usage)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := runcmd(flag.Args()); err != nil {
|
if err := runcmd(flag.Args()); err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runcmd(args []string) error {
|
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] {
|
switch args[0] {
|
||||||
case "list":
|
case "list":
|
||||||
if *verbose {
|
return NewListCmd(args[1:])
|
||||||
fmt.Println("Running List command")
|
|
||||||
}
|
|
||||||
lcmd, err := NewListCmd(flag.Args()[1:])
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return lcmd.List()
|
|
||||||
case "create":
|
case "create":
|
||||||
ccmd, err := NewCreateCmd(flag.Args()[1:])
|
return NewCreateCmd(args[1:])
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return ccmd.Create()
|
|
||||||
case "task":
|
case "task":
|
||||||
tcmd := &TaskCmd{}
|
tcmd := &TaskCmd{args: args[1:]}
|
||||||
return tcmd.Handle(flag.Args()[1:])
|
return tcmd, nil
|
||||||
case "edit":
|
case "edit":
|
||||||
ecmd := &EditCmd{}
|
return NewEditCmd(args[1:])
|
||||||
return ecmd.Edit(flag.Arg(1))
|
|
||||||
case "comment":
|
case "comment":
|
||||||
ccmd, err := NewCommentCmd(flag.Args()[1:])
|
return NewCommentCmd(args[1:])
|
||||||
if err != nil {
|
case "edit-comment":
|
||||||
return err
|
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 <filename>` 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, ErrTaskSubCommandNotFound
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const usage = `Usage:
|
const usage = `Usage:
|
||||||
|
|
@ -65,4 +89,6 @@ Available commands:
|
||||||
list
|
list
|
||||||
create
|
create
|
||||||
edit
|
edit
|
||||||
|
comment
|
||||||
|
|
||||||
`
|
`
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,9 @@ type ListCmd struct {
|
||||||
func NewListCmd(args []string) (*ListCmd, error) {
|
func NewListCmd(args []string) (*ListCmd, error) {
|
||||||
ccmd := &ListCmd{}
|
ccmd := &ListCmd{}
|
||||||
f := flag.NewFlagSet("x", flag.ExitOnError)
|
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.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)
|
f.Parse(args)
|
||||||
ccmd.args = f.Args()
|
ccmd.args = f.Args()
|
||||||
|
|
@ -58,6 +61,12 @@ func NewListCmd(args []string) (*ListCmd, error) {
|
||||||
proj = fmt.Sprintf(" and project = %s ", proj)
|
proj = fmt.Sprintf(" and project = %s ", proj)
|
||||||
}
|
}
|
||||||
ccmd.args = []string{fmt.Sprintf("sprint in openSprints() %s order by rank", 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))
|
ccmd.tmpl = template.Must(template.New("listTemplate").Parse(ccmd.tmplstr))
|
||||||
return ccmd, nil
|
return ccmd, nil
|
||||||
|
|
@ -78,3 +87,7 @@ func (l *ListCmd) List() error {
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *ListCmd) Run() error {
|
||||||
|
return l.List()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,15 +7,18 @@ import (
|
||||||
"otremblay.com/jkl"
|
"otremblay.com/jkl"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TaskCmd struct{}
|
type TaskCmd struct {
|
||||||
|
args []string
|
||||||
|
}
|
||||||
|
|
||||||
func (t *TaskCmd) Handle(args []string) error {
|
// TODO: split in individual commands.
|
||||||
c := len(args)
|
func (t *TaskCmd) Handle() error {
|
||||||
|
c := len(t.args)
|
||||||
if c == 1 {
|
if c == 1 {
|
||||||
return t.Get(args[0])
|
return t.Get(t.args[0])
|
||||||
}
|
}
|
||||||
if c == 2 {
|
if c == 2 {
|
||||||
return t.Transition(args[0], args[1])
|
return t.Transition(t.args[0], t.args[1])
|
||||||
}
|
}
|
||||||
return ErrTaskSubCommandNotFound
|
return ErrTaskSubCommandNotFound
|
||||||
}
|
}
|
||||||
|
|
@ -34,3 +37,7 @@ func (t *TaskCmd) Get(taskKey string) error {
|
||||||
func (t *TaskCmd) Transition(taskKey, transition string) error {
|
func (t *TaskCmd) Transition(taskKey, transition string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *TaskCmd) Run() error {
|
||||||
|
return t.Handle()
|
||||||
|
}
|
||||||
|
|
|
||||||
14
issue.go
14
issue.go
|
|
@ -95,7 +95,11 @@ func (i *JiraIssue) URL() string {
|
||||||
|
|
||||||
func (i *JiraIssue) String() string {
|
func (i *JiraIssue) String() string {
|
||||||
var b = bytes.NewBuffer(nil)
|
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 {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
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[1mDescription:\x1b[0m {{.Fields.Description}} \n\n" +
|
||||||
"\x1b[1mComments:\x1b[0m\n\n" + commentTemplate
|
"\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 issueTmpl = template.Must(template.New("issueTmpl").Parse(issueTmplTxt))
|
||||||
|
var issueTmplNoColor = template.Must(template.New("issueTmpl").Parse(issueTmplNoColorTxt))
|
||||||
Loading…
Add table
Add a link
Reference in a new issue