Add support for silent mode.
Fixes #13 partially. Only missing STDIN reading as a special file.
This commit is contained in:
parent
edc5120921
commit
7f8947b970
9 changed files with 188 additions and 125 deletions
|
|
@ -4,19 +4,20 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"fmt"
|
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"otremblay.com/jkl"
|
"otremblay.com/jkl"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CreateCmd struct {
|
type CreateCmd struct {
|
||||||
args []string
|
args []string
|
||||||
project string
|
project string
|
||||||
file string
|
file string
|
||||||
issuetype string
|
issuetype string
|
||||||
|
silent bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCreateCmd(args []string) (*CreateCmd, error) {
|
func NewCreateCmd(args []string) (*CreateCmd, error) {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"flag"
|
"flag"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
@ -21,9 +22,9 @@ func NewEditCmd(args []string) (*EditCmd, error) {
|
||||||
ccmd := &EditCmd{project: os.Getenv("JIRA_PROJECT")}
|
ccmd := &EditCmd{project: os.Getenv("JIRA_PROJECT")}
|
||||||
f := flag.NewFlagSet("x", flag.ExitOnError)
|
f := flag.NewFlagSet("x", flag.ExitOnError)
|
||||||
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", "", "File to get issue description from")
|
||||||
f.Parse(args)
|
f.Parse(args)
|
||||||
ccmd.taskKey = flag.Arg(0)
|
ccmd.taskKey = f.Arg(0)
|
||||||
return ccmd, nil
|
return ccmd, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,7 +32,7 @@ func (ecmd *EditCmd) Edit() error {
|
||||||
b := bytes.NewBuffer(nil)
|
b := bytes.NewBuffer(nil)
|
||||||
iss, err := jkl.GetIssue(ecmd.taskKey)
|
iss, err := jkl.GetIssue(ecmd.taskKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("Edit failed: %v", err)
|
||||||
}
|
}
|
||||||
err = editTmpl.Execute(b, iss)
|
err = editTmpl.Execute(b, iss)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -42,12 +43,12 @@ func (ecmd *EditCmd) Edit() error {
|
||||||
iss, err = GetIssueFromFile(ecmd.file, b, iss.EditMeta)
|
iss, err = GetIssueFromFile(ecmd.file, b, iss.EditMeta)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("Error getting issue from file: %v", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
iss, err = GetIssueFromTmpFile(b, iss.EditMeta)
|
iss, err = GetIssueFromTmpFile(b, iss.EditMeta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("Error getting issue from temp file: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
|
@ -17,6 +18,7 @@ import (
|
||||||
|
|
||||||
"otremblay.com/jkl"
|
"otremblay.com/jkl"
|
||||||
)
|
)
|
||||||
|
|
||||||
// def get_editor do
|
// def get_editor do
|
||||||
// [System.get_env("EDITOR"), "nano", "vim", "vi"]
|
// [System.get_env("EDITOR"), "nano", "vim", "vi"]
|
||||||
// |> Enum.find(nil, fn (ed) -> System.find_executable(ed) != nil end)
|
// |> Enum.find(nil, fn (ed) -> System.find_executable(ed) != nil end)
|
||||||
|
|
@ -42,12 +44,12 @@ func copyInitial(dst io.WriteSeeker, initial io.Reader) {
|
||||||
func GetIssueFromTmpFile(initial io.Reader, editMeta *jkl.EditMeta) (*jkl.JiraIssue, error) {
|
func GetIssueFromTmpFile(initial io.Reader, editMeta *jkl.EditMeta) (*jkl.JiraIssue, error) {
|
||||||
f, err := ioutil.TempFile(os.TempDir(), "jkl")
|
f, err := ioutil.TempFile(os.TempDir(), "jkl")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("Error opening tempfile: %v", err)
|
||||||
}
|
}
|
||||||
copyInitial(f, initial)
|
copyInitial(f, initial)
|
||||||
f2, err := GetTextFromFile(f)
|
f2, err := GetTextFromFile(f)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("Error reading tempfile: %v", err)
|
||||||
}
|
}
|
||||||
return IssueFromReader(f2, editMeta), nil
|
return IssueFromReader(f2, editMeta), nil
|
||||||
}
|
}
|
||||||
|
|
@ -73,16 +75,19 @@ func GetTextFromSpecifiedFile(filename string, initial io.Reader) (io.Reader, er
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTextFromFile(file *os.File) (io.Reader, error) {
|
func GetTextFromFile(file *os.File) (io.Reader, error) {
|
||||||
cmd := exec.Command(GetEditor(), file.Name())
|
var err error
|
||||||
cmd.Stdin = os.Stdin
|
if !*SilentMode {
|
||||||
cmd.Stdout = os.Stdout
|
cmd := exec.Command(GetEditor(), file.Name())
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stdin = os.Stdin
|
||||||
err := cmd.Run()
|
cmd.Stdout = os.Stdout
|
||||||
if err != nil {
|
cmd.Stderr = os.Stderr
|
||||||
fmt.Println(err)
|
err := cmd.Run()
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
_, err = file.Seek(0, 0)
|
||||||
}
|
}
|
||||||
_, err = file.Seek(0, 0)
|
|
||||||
return file, err
|
return file, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -108,6 +113,7 @@ func IssueFromReader(f io.Reader, editMeta *jkl.EditMeta) *jkl.JiraIssue {
|
||||||
riss := reflect.ValueOf(iss).Elem()
|
riss := reflect.ValueOf(iss).Elem()
|
||||||
fieldsField := riss.FieldByName("Fields").Elem()
|
fieldsField := riss.FieldByName("Fields").Elem()
|
||||||
currentField := reflect.Value{}
|
currentField := reflect.Value{}
|
||||||
|
currFieldName := ""
|
||||||
brd := bufio.NewReader(f)
|
brd := bufio.NewReader(f)
|
||||||
for {
|
for {
|
||||||
b, _, err := brd.ReadLine()
|
b, _, err := brd.ReadLine()
|
||||||
|
|
@ -131,6 +137,7 @@ func IssueFromReader(f io.Reader, editMeta *jkl.EditMeta) *jkl.JiraIssue {
|
||||||
if len(parts) > 0 {
|
if len(parts) > 0 {
|
||||||
iss.Fields.IssueType = &jkl.IssueType{}
|
iss.Fields.IssueType = &jkl.IssueType{}
|
||||||
currentField = reflect.Value{}
|
currentField = reflect.Value{}
|
||||||
|
currFieldName = potentialField
|
||||||
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, ":")))
|
||||||
|
|
@ -139,6 +146,7 @@ func IssueFromReader(f io.Reader, editMeta *jkl.EditMeta) *jkl.JiraIssue {
|
||||||
if len(parts) > 0 {
|
if len(parts) > 0 {
|
||||||
iss.Fields.Project = &jkl.Project{}
|
iss.Fields.Project = &jkl.Project{}
|
||||||
currentField = reflect.Value{}
|
currentField = reflect.Value{}
|
||||||
|
currFieldName = potentialField
|
||||||
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, ":")))
|
||||||
|
|
@ -147,11 +155,13 @@ func IssueFromReader(f io.Reader, editMeta *jkl.EditMeta) *jkl.JiraIssue {
|
||||||
if len(parts) > 0 {
|
if len(parts) > 0 {
|
||||||
iss.Fields.Parent = &jkl.JiraIssue{}
|
iss.Fields.Parent = &jkl.JiraIssue{}
|
||||||
currentField = reflect.Value{}
|
currentField = reflect.Value{}
|
||||||
|
currFieldName = potentialField
|
||||||
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 {
|
} else {
|
||||||
|
currFieldName = potentialField
|
||||||
currentField = newfield
|
currentField = newfield
|
||||||
}
|
}
|
||||||
} else if editMeta != nil {
|
} else if editMeta != nil {
|
||||||
|
|
@ -159,7 +169,13 @@ func IssueFromReader(f io.Reader, editMeta *jkl.EditMeta) *jkl.JiraIssue {
|
||||||
|
|
||||||
}
|
}
|
||||||
if currentField.IsValid() {
|
if currentField.IsValid() {
|
||||||
currentField.SetString(strings.TrimSpace(currentField.String() + "\n" + strings.Join(parts, ":")))
|
newString := currentField.String() + "\n" + strings.Join(parts, ":")
|
||||||
|
if currFieldName != "Description" {
|
||||||
|
newString = strings.TrimSpace(newString)
|
||||||
|
} else if currentField.String() == "" {
|
||||||
|
newString = strings.TrimLeftFunc(newString, unicode.IsSpace)
|
||||||
|
}
|
||||||
|
currentField.SetString(newString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return iss
|
return iss
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@ hell
|
||||||
Issue Type: Sometype
|
Issue Type: Sometype
|
||||||
this is ignored
|
this is ignored
|
||||||
Summary: Dookienator
|
Summary: Dookienator
|
||||||
also ignored`, "\n"))
|
also ignored`, "\n"), nil)
|
||||||
|
|
||||||
AssertEqual(t, `Cowboys
|
AssertEqual(t, `Cowboys
|
||||||
from
|
from
|
||||||
hell`, iss.Fields.Description)
|
hell`, iss.Fields.Description)
|
||||||
|
|
@ -34,3 +35,13 @@ Actual: %v`, expected, actual)
|
||||||
func Assert(t *testing.T, fn func() bool) {
|
func Assert(t *testing.T, fn func() bool) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIssueFromFile(t *testing.T) {
|
||||||
|
*SilentMode = true
|
||||||
|
issue, err := GetIssueFromFile("editor_test_file.issue", nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Apparently we effed up some! --> %v", err)
|
||||||
|
}
|
||||||
|
AssertEqual(t, "Dookie", issue.Fields.Summary)
|
||||||
|
AssertEqual(t, "McDoogal", issue.Fields.Description)
|
||||||
|
}
|
||||||
|
|
|
||||||
2
cmd/jkl/editor_test_file.issue
Normal file
2
cmd/jkl/editor_test_file.issue
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
Summary: Dookie
|
||||||
|
Description: McDoogal
|
||||||
|
|
@ -14,6 +14,7 @@ import (
|
||||||
|
|
||||||
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")
|
var help = flag.Bool("h", false, "Outputs usage information message")
|
||||||
|
var SilentMode = flag.Bool("s", false, "Silent execution.")
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
jkl.FindRCFile()
|
jkl.FindRCFile()
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,9 @@ func (l *listissue) Color() string {
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
func (l *listissue) EFByName(name string) string {
|
||||||
|
return (*jkl.JiraIssue)(l).EFByName(name)
|
||||||
|
}
|
||||||
|
|
||||||
type ListCmd struct {
|
type ListCmd struct {
|
||||||
args []string
|
args []string
|
||||||
|
|
@ -76,7 +79,7 @@ func (l *ListCmd) List() error {
|
||||||
if issues, err := jkl.List(strings.Join(l.args, " ")); err != nil {
|
if issues, err := jkl.List(strings.Join(l.args, " ")); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
for _, issue := range issues {
|
for issue := range issues {
|
||||||
var li listissue
|
var li listissue
|
||||||
li = listissue(*issue)
|
li = listissue(*issue)
|
||||||
err := l.tmpl.Execute(os.Stdout, &li)
|
err := l.tmpl.Execute(os.Stdout, &li)
|
||||||
|
|
|
||||||
221
issue.go
221
issue.go
|
|
@ -15,13 +15,15 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Search struct {
|
type Search struct {
|
||||||
Issues []*JiraIssue `json:"issues"`
|
Issues []*JiraIssue `json:"issues"`
|
||||||
|
Total int
|
||||||
|
MaxResults int
|
||||||
}
|
}
|
||||||
|
|
||||||
type IssueType struct {
|
type IssueType struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
IconURL string `json:",omitempty"`
|
IconURL string `json:",omitempty"`
|
||||||
Fields map[string]*FieldSpec
|
Fields map[string]*FieldSpec
|
||||||
}
|
}
|
||||||
|
|
||||||
func (it *IssueType) RangeFieldSpecs() string {
|
func (it *IssueType) RangeFieldSpecs() string {
|
||||||
|
|
@ -38,12 +40,12 @@ func (it *IssueType) RangeFieldSpecs() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
type AllowedValue struct {
|
type AllowedValue struct {
|
||||||
Id string
|
Id string
|
||||||
Self string
|
Self string
|
||||||
Value string
|
Value string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AllowedValue) String() string{
|
func (a *AllowedValue) String() string {
|
||||||
return a.Value
|
return a.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,24 +53,22 @@ type FieldSpec struct {
|
||||||
Name string
|
Name string
|
||||||
Required bool
|
Required bool
|
||||||
Schema struct {
|
Schema struct {
|
||||||
Type string
|
Type string
|
||||||
Custom string
|
Custom string
|
||||||
CustomId int
|
CustomId int
|
||||||
Items string
|
Items string
|
||||||
}
|
}
|
||||||
Operations []string
|
Operations []string
|
||||||
AllowedValues []*AllowedValue
|
AllowedValues []*AllowedValue
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type CreateMeta struct {
|
type CreateMeta struct {
|
||||||
Projects []*Project
|
Projects []*Project
|
||||||
}
|
}
|
||||||
|
|
||||||
type Project struct {
|
type Project struct {
|
||||||
Key string `json:"key,omitempty"`
|
Key string `json:"key,omitempty"`
|
||||||
Name string
|
Name string
|
||||||
IssueTypes []*IssueType
|
IssueTypes []*IssueType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,9 +82,9 @@ func (a *Author) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Comment struct {
|
type Comment struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
Author *Author `json:"author"`
|
Author *Author `json:"author"`
|
||||||
Body string `json:"body"`
|
Body string `json:"body"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CommentColl struct {
|
type CommentColl struct {
|
||||||
|
|
@ -92,7 +92,7 @@ type CommentColl struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Status struct {
|
type Status struct {
|
||||||
Name string
|
Name string
|
||||||
IconURL string `json:",omitempty"`
|
IconURL string `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,14 +107,14 @@ func (a *Attachment) String() string {
|
||||||
|
|
||||||
type Attachment struct {
|
type Attachment struct {
|
||||||
Filename string
|
Filename string
|
||||||
Author *Author
|
Author *Author
|
||||||
Content string
|
Content string
|
||||||
}
|
}
|
||||||
|
|
||||||
type LinkType struct {
|
type LinkType struct {
|
||||||
Id string
|
Id string
|
||||||
Name string
|
Name string
|
||||||
Inward string
|
Inward string
|
||||||
Outward string
|
Outward string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -129,8 +129,8 @@ func (i *IssueLink) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
type IssueLink struct {
|
type IssueLink struct {
|
||||||
LinkType *LinkType `json:"type"`
|
LinkType *LinkType `json:"type"`
|
||||||
InwardIssue *JiraIssue
|
InwardIssue *JiraIssue
|
||||||
OutwardIssue *JiraIssue
|
OutwardIssue *JiraIssue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,18 +139,18 @@ func (w *Worklog) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Worklog struct {
|
type Worklog struct {
|
||||||
Author *Author
|
Author *Author
|
||||||
Comment string
|
Comment string
|
||||||
TimeSpent string
|
TimeSpent string
|
||||||
TimeSpentSeconds int
|
TimeSpentSeconds int
|
||||||
Started string
|
Started string
|
||||||
}
|
}
|
||||||
|
|
||||||
type Worklogs struct {
|
type Worklogs struct {
|
||||||
Worklogs []*Worklog `json:",omitempty"`
|
Worklogs []*Worklog `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fields) UnmarshalJSON(b []byte) error{
|
func (f *Fields) UnmarshalJSON(b []byte) error {
|
||||||
err := json.Unmarshal(b, &f.rawFields)
|
err := json.Unmarshal(b, &f.rawFields)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("splosion")
|
fmt.Println("splosion")
|
||||||
|
|
@ -159,16 +159,18 @@ func (f *Fields) UnmarshalJSON(b []byte) error{
|
||||||
f.rawExtraFields = map[string]json.RawMessage{}
|
f.rawExtraFields = map[string]json.RawMessage{}
|
||||||
vf := reflect.ValueOf(f).Elem()
|
vf := reflect.ValueOf(f).Elem()
|
||||||
for key, mess := range f.rawFields {
|
for key, mess := range f.rawFields {
|
||||||
field := vf.FieldByNameFunc(func(s string)bool{return strings.ToLower(key) == strings.ToLower(s)})
|
field := vf.FieldByNameFunc(func(s string) bool { return strings.ToLower(key) == strings.ToLower(s) })
|
||||||
if field.IsValid() {
|
if field.IsValid() {
|
||||||
objType := field.Type()
|
objType := field.Type()
|
||||||
obj := reflect.New(objType).Interface()
|
obj := reflect.New(objType).Interface()
|
||||||
err := json.Unmarshal(mess, &obj)
|
err := json.Unmarshal(mess, &obj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, objType, obj, string(mess))
|
fmt.Fprintln(os.Stderr, objType, obj, string(mess))
|
||||||
fmt.Fprintln(os.Stderr, errors.New(fmt.Sprintf("%s [%s]: %s","Error allocating field",key, err)))
|
fmt.Fprintln(os.Stderr, errors.New(fmt.Sprintf("%s [%s]: %s", "Error allocating field", key, err)))
|
||||||
|
}
|
||||||
|
if obj != nil && !(reflect.ValueOf(obj) == reflect.Zero(reflect.TypeOf(obj))) {
|
||||||
|
field.Set(reflect.ValueOf(obj).Elem())
|
||||||
}
|
}
|
||||||
field.Set(reflect.ValueOf(obj).Elem())
|
|
||||||
} else {
|
} else {
|
||||||
f.rawExtraFields[key] = mess
|
f.rawExtraFields[key] = mess
|
||||||
}
|
}
|
||||||
|
|
@ -182,22 +184,22 @@ type Priority struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Fields struct {
|
type Fields struct {
|
||||||
*IssueType `json:"issuetype,omitempty"`
|
*IssueType `json:"issuetype,omitempty"`
|
||||||
Assignee *Author `json:",omitempty"`
|
Assignee *Author `json:",omitempty"`
|
||||||
Project *Project `json:"project,omitempty"`
|
Project *Project `json:"project,omitempty"`
|
||||||
Summary string `json:"summary,omitempty"`
|
Summary string `json:"summary,omitempty"`
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty"`
|
||||||
Comment *CommentColl `json:"comment,omitempty"`
|
Comment *CommentColl `json:"comment,omitempty"`
|
||||||
Parent *JiraIssue `json:",omitempty"`
|
Parent *JiraIssue `json:",omitempty"`
|
||||||
Status *Status `json:",omitempty"`
|
Status *Status `json:",omitempty"`
|
||||||
TimeTracking *TimeTracking `json:"timetracking,omitempty"`
|
TimeTracking *TimeTracking `json:"timetracking,omitempty"`
|
||||||
Attachment []*Attachment `json:"attachment,omitempty"`
|
Attachment []*Attachment `json:"attachment,omitempty"`
|
||||||
IssueLinks []*IssueLink `json:"issueLinks,omitempty"`
|
IssueLinks []*IssueLink `json:"issueLinks,omitempty"`
|
||||||
Priority *Priority `json:",omitempty"`
|
Priority *Priority `json:",omitempty"`
|
||||||
Worklog *Worklogs `json:"worklog,omitempty"`
|
Worklog *Worklogs `json:"worklog,omitempty"`
|
||||||
rawFields map[string]json.RawMessage
|
rawFields map[string]json.RawMessage
|
||||||
rawExtraFields map[string]json.RawMessage
|
rawExtraFields map[string]json.RawMessage
|
||||||
ExtraFields map[string]interface{} `json:"-"`
|
ExtraFields map[string]interface{} `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Fields) PrettyRemaining() string {
|
func (f *Fields) PrettyRemaining() string {
|
||||||
|
|
@ -218,41 +220,37 @@ func PrettySeconds(seconds int) string {
|
||||||
return fmt.Sprintf("%dd %2dh %2dm %2ds", days, hours, minutes, seconds)
|
return fmt.Sprintf("%dd %2dh %2dm %2ds", days, hours, minutes, seconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Transition struct{
|
type Transition struct {
|
||||||
Id string `json:"id"`
|
Id string `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type Schema struct {
|
type Schema struct {
|
||||||
System string
|
System string
|
||||||
Custom string
|
Custom string
|
||||||
CustomId int
|
CustomId int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
type EditMeta struct {
|
type EditMeta struct {
|
||||||
Fields map[string]*FieldSpec
|
Fields map[string]*FieldSpec
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
type JiraIssue struct {
|
type JiraIssue struct {
|
||||||
Key string `json:"key,omitempty"`
|
Key string `json:"key,omitempty"`
|
||||||
Fields *Fields `json:"fields,omitempty"`
|
Fields *Fields `json:"fields,omitempty"`
|
||||||
Transitions []*Transition `json:"transitions,omitempty"`
|
Transitions []*Transition `json:"transitions,omitempty"`
|
||||||
EditMeta *EditMeta `json:"editmeta,omitempty"`
|
EditMeta *EditMeta `json:"editmeta,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var sprintRegexp = regexp.MustCompile(`name=([^,]+),`)
|
||||||
|
|
||||||
var sprintRegexp = regexp.MustCompile(`name=([^,]+),`)
|
|
||||||
func (i *JiraIssue) UnmarshalJSON(b []byte) error {
|
func (i *JiraIssue) UnmarshalJSON(b []byte) error {
|
||||||
tmp := map[string]json.RawMessage{}
|
tmp := map[string]json.RawMessage{}
|
||||||
if len(b) == 0 {
|
if len(b) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
i.Fields = &Fields{}
|
i.Fields = &Fields{}
|
||||||
i.EditMeta = &EditMeta{Fields:map[string]*FieldSpec{}}
|
i.EditMeta = &EditMeta{Fields: map[string]*FieldSpec{}}
|
||||||
err := json.Unmarshal(b, &tmp)
|
err := json.Unmarshal(b, &tmp)
|
||||||
if err != nil && *Verbose {
|
if err != nil && *Verbose {
|
||||||
fmt.Fprintln(os.Stderr, errors.New(fmt.Sprintf("%s: %s", "Error unpacking raw json", err)))
|
fmt.Fprintln(os.Stderr, errors.New(fmt.Sprintf("%s: %s", "Error unpacking raw json", err)))
|
||||||
|
|
@ -263,10 +261,10 @@ func (i *JiraIssue) UnmarshalJSON(b []byte) error {
|
||||||
}
|
}
|
||||||
err = json.Unmarshal(tmp["transitions"], &i.Transitions)
|
err = json.Unmarshal(tmp["transitions"], &i.Transitions)
|
||||||
if err != nil && *Verbose {
|
if err != nil && *Verbose {
|
||||||
fmt.Fprintln(os.Stderr, errors.New(fmt.Sprintf("%s: %s", "Error unpacking transitions", err)))
|
fmt.Fprintln(os.Stderr, errors.New(fmt.Sprintf("%s: %s", "Error unpacking transitions", err)))
|
||||||
}
|
}
|
||||||
err = json.Unmarshal(tmp["editmeta"], &i.EditMeta)
|
err = json.Unmarshal(tmp["editmeta"], &i.EditMeta)
|
||||||
if err != nil && *Verbose{
|
if err != nil && *Verbose {
|
||||||
fmt.Fprintln(os.Stderr, errors.New(fmt.Sprintf("%s: %s", "Error unpacking EditMeta", err)))
|
fmt.Fprintln(os.Stderr, errors.New(fmt.Sprintf("%s: %s", "Error unpacking EditMeta", err)))
|
||||||
}
|
}
|
||||||
err = json.Unmarshal(tmp["key"], &i.Key)
|
err = json.Unmarshal(tmp["key"], &i.Key)
|
||||||
|
|
@ -282,31 +280,36 @@ func (i *JiraIssue) UnmarshalJSON(b []byte) error {
|
||||||
if len(results) == 2 {
|
if len(results) == 2 {
|
||||||
i.Fields.ExtraFields[k] = results[1]
|
i.Fields.ExtraFields[k] = results[1]
|
||||||
}
|
}
|
||||||
} else {switch f.Schema.Type {
|
} else {
|
||||||
case "user":
|
switch f.Schema.Type {
|
||||||
a := &Author{}
|
case "user":
|
||||||
json.Unmarshal(v, &a)
|
a := &Author{}
|
||||||
i.Fields.ExtraFields[k] = a
|
json.Unmarshal(v, &a)
|
||||||
case "option":
|
i.Fields.ExtraFields[k] = a
|
||||||
val := &AllowedValue{}
|
case "option":
|
||||||
err = json.Unmarshal(v, &val)
|
val := &AllowedValue{}
|
||||||
if err != nil {panic(err)}
|
err = json.Unmarshal(v, &val)
|
||||||
i.Fields.ExtraFields[k] = val
|
if err != nil {
|
||||||
case "array":
|
panic(err)
|
||||||
if f.Schema.Items == "option" {
|
}
|
||||||
val := []*AllowedValue{}
|
i.Fields.ExtraFields[k] = val
|
||||||
err = json.Unmarshal(v, &val)
|
case "array":
|
||||||
if err != nil {panic(err)}
|
if f.Schema.Items == "option" {
|
||||||
i.Fields.ExtraFields[k] = val
|
val := []*AllowedValue{}
|
||||||
continue
|
err = json.Unmarshal(v, &val)
|
||||||
}
|
if err != nil {
|
||||||
fallthrough
|
panic(err)
|
||||||
default:
|
}
|
||||||
if string(v) != "null" {
|
i.Fields.ExtraFields[k] = val
|
||||||
i.Fields.ExtraFields[k] = string(v)
|
continue
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
default:
|
||||||
|
if string(v) != "null" {
|
||||||
|
i.Fields.ExtraFields[k] = string(v)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -331,7 +334,21 @@ func (i *JiraIssue) String() string {
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *JiraIssue) PrintExtraFields() string{
|
func (i *JiraIssue) EFByName(name string) string {
|
||||||
|
for k, f := range i.EditMeta.Fields {
|
||||||
|
if f.Name == name {
|
||||||
|
return fmt.Sprint(i.Fields.ExtraFields[k])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *JiraIssue) StoryPoints() string {
|
||||||
|
return i.EFByName("Story Points")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *JiraIssue) PrintExtraFields() string {
|
||||||
sorter := map[string]string{}
|
sorter := map[string]string{}
|
||||||
b := bytes.NewBuffer(nil)
|
b := bytes.NewBuffer(nil)
|
||||||
for k, v := range i.Fields.ExtraFields {
|
for k, v := range i.Fields.ExtraFields {
|
||||||
|
|
@ -350,7 +367,7 @@ func (i *JiraIssue) PrintExtraFields() string{
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
var commentTemplate = `{{if .Fields.Comment }}{{$k := .Key}}{{range .Fields.Comment.Comments}}{{.Author.DisplayName}} [~{{.Author.Name}}] ({{$k}}`+CommentIdSeparator+`{{.Id}}):
|
var commentTemplate = `{{if .Fields.Comment }}{{$k := .Key}}{{range .Fields.Comment.Comments}}{{.Author.DisplayName}} [~{{.Author.Name}}] ({{$k}}` + CommentIdSeparator + `{{.Id}}):
|
||||||
-----------------
|
-----------------
|
||||||
{{.Body}}
|
{{.Body}}
|
||||||
-----------------
|
-----------------
|
||||||
|
|
@ -358,30 +375,40 @@ var commentTemplate = `{{if .Fields.Comment }}{{$k := .Key}}{{range .Fields.Comm
|
||||||
{{end}}{{end}}`
|
{{end}}{{end}}`
|
||||||
|
|
||||||
var issueTmplTxt = "\x1b[1m{{.Key}}\x1b[0m\t{{if .Fields.IssueType}}[{{.Fields.IssueType.Name}}]{{end}}\t{{.Fields.Summary}}\n\n" +
|
var issueTmplTxt = "\x1b[1m{{.Key}}\x1b[0m\t{{if .Fields.IssueType}}[{{.Fields.IssueType.Name}}]{{end}}\t{{.Fields.Summary}}\n\n" +
|
||||||
"\x1b[1mURL\x1b[0m: {{.URL}}\n\n" +
|
"\x1b[1mURL\x1b[0m: {{.URL}}\n\n" +
|
||||||
"{{if .Fields.Status}}\x1b[1mStatus\x1b[0m:\t {{.Fields.Status.Name}}\n{{end}}" +
|
"{{if .Fields.Status}}\x1b[1mStatus\x1b[0m:\t {{.Fields.Status.Name}}\n{{end}}" +
|
||||||
"{{if .Fields.Priority}}\x1b[1mStatus\x1b[0m:\t {{.Fields.Priority.Name}}\n{{end}}" +
|
"{{if .Fields.Priority}}\x1b[1mStatus\x1b[0m:\t {{.Fields.Priority.Name}}\n{{end}}" +
|
||||||
"\x1b[1mTransitions\x1b[0m: {{range .Transitions}}[{{.Name}}] {{end}}\n"+
|
"\x1b[1mTransitions\x1b[0m: {{range .Transitions}}[{{.Name}}] {{end}}\n" +
|
||||||
"{{if .Fields.Assignee}}\x1b[1mAssignee:\x1b[0m\t{{.Fields.Assignee.Name}}\n{{end}}\n" +
|
"{{if .Fields.Assignee}}\x1b[1mAssignee:\x1b[0m\t{{.Fields.Assignee.Name}}\n{{end}}\n" +
|
||||||
"\x1b[1mTime Remaining/Original Estimate:\x1b[0m\t{{.Fields.PrettyRemaining}} / {{.Fields.PrettyOriginalEstimate}}\n\n" +
|
"\x1b[1mTime Remaining/Original Estimate:\x1b[0m\t{{.Fields.PrettyRemaining}} / {{.Fields.PrettyOriginalEstimate}}\n\n" +
|
||||||
"{{$i := .}}{{range $k, $v := .Fields.ExtraFields}}{{with index $i.EditMeta.Fields $k}}\x1b[1m{{.Name}}\x1b[0m{{end}}: {{$v}}\n{{end}}\n\n"+
|
"{{$i := .}}{{range $k, $v := .Fields.ExtraFields}}{{with index $i.EditMeta.Fields $k}}\x1b[1m{{.Name}}\x1b[0m{{end}}: {{$v}}\n{{end}}\n\n" +
|
||||||
"\x1b[1mDescription:\x1b[0m {{.Fields.Description}} \n\n" +
|
"\x1b[1mDescription:\x1b[0m {{.Fields.Description}} \n\n" +
|
||||||
"\x1b[1mIssue Links\x1b[0m: \n{{range .Fields.IssueLinks}}\t{{.}}\n{{end}}\n\n" +
|
"\x1b[1mIssue Links\x1b[0m: \n{{range .Fields.IssueLinks}}\t{{.}}\n{{end}}\n\n" +
|
||||||
"\x1b[1mComments:\x1b[0m\n\n" + commentTemplate +
|
"\x1b[1mComments:\x1b[0m\n\n" + commentTemplate +
|
||||||
"Worklog:\n{{range .Fields.Worklog.Worklogs}}\t{{.}}\n{{end}}"
|
"Worklog:\n{{range .Fields.Worklog.Worklogs}}\t{{.}}\n{{end}}"
|
||||||
|
|
||||||
var issueTmplNoColorTxt = "{{.Key}}\t{{if .Fields.IssueType}}[{{.Fields.IssueType.Name}}]{{end}}\t{{.Fields.Summary}}\n\n" +
|
var issueTmplNoColorTxt = "{{.Key}}\t{{if .Fields.IssueType}}[{{.Fields.IssueType.Name}}]{{end}}\t{{.Fields.Summary}}\n\n" +
|
||||||
"URL: {{.URL}}\n\n" +
|
"URL: {{.URL}}\n\n" +
|
||||||
"{{if .Fields.Status}}Status:\t {{.Fields.Status.Name}}\n{{end}}" +
|
"{{if .Fields.Status}}Status:\t {{.Fields.Status.Name}}\n{{end}}" +
|
||||||
"Transitions: {{range .Transitions}}[{{.Name}}] {{end}}\n"+
|
"Transitions: {{range .Transitions}}[{{.Name}}] {{end}}\n" +
|
||||||
"{{if .Fields.Assignee}}Assignee:\t{{.Fields.Assignee.Name}}\n{{end}}\n" +
|
"{{if .Fields.Assignee}}Assignee:\t{{.Fields.Assignee.Name}}\n{{end}}\n" +
|
||||||
"Time Remaining/Original Estimate:\t{{.Fields.PrettyRemaining}} / {{.Fields.PrettyOriginalEstimate}}\n\n" +
|
"Time Remaining/Original Estimate:\t{{.Fields.PrettyRemaining}} / {{.Fields.PrettyOriginalEstimate}}\n\n" +
|
||||||
"{{.PrintExtraFields}}\n\n"+
|
"{{.PrintExtraFields}}\n\n" +
|
||||||
"Description: {{.Fields.Description}} \n\n" +
|
"Description: {{.Fields.Description}} \n\n" +
|
||||||
"Issue Links: \n{{range .Fields.IssueLinks}}\t{{.}}\n{{end}}\n\n" +
|
"Issue Links: \n{{range .Fields.IssueLinks}}\t{{.}}\n{{end}}\n\n" +
|
||||||
"Comments:\n\n" + commentTemplate+
|
"Comments:\n\n" + commentTemplate +
|
||||||
"Worklog:\n{{range .Fields.Worklog.Worklogs}}\t{{.}}\n{{end}}"
|
"Worklog:\n{{range .Fields.Worklog.Worklogs}}\t{{.}}\n{{end}}"
|
||||||
|
|
||||||
var CommentIdSeparator = "~"
|
var CommentIdSeparator = "~"
|
||||||
var issueTmpl = template.Must(template.New("issueTmpl").Parse(issueTmplTxt))
|
var issueTmpl, issueTmplNoColor *template.Template
|
||||||
var issueTmplNoColor = template.Must(template.New("issueTmplNoColor").Parse(issueTmplNoColorTxt))
|
|
||||||
|
func SetupTmpl() {
|
||||||
|
isstmpl := os.Getenv("JKL_ISSUE_TMPL")
|
||||||
|
if isstmpl != "" {
|
||||||
|
issueTmpl = template.Must(template.New("issueTmpl").Parse(isstmpl))
|
||||||
|
issueTmplNoColor = issueTmpl
|
||||||
|
} else {
|
||||||
|
issueTmpl = template.Must(template.New("issueTmpl").Parse(issueTmplTxt))
|
||||||
|
issueTmplNoColor = template.Must(template.New("issueTmplNoColor").Parse(issueTmplNoColorTxt))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
1
jkl.go
1
jkl.go
|
|
@ -239,6 +239,7 @@ func DoTransition(taskKey string, transitionName string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func LogWork(taskKey string, workAmount string) error {
|
func LogWork(taskKey string, workAmount string) error {
|
||||||
|
bootHttpClient()
|
||||||
payload, err := serializePayload(map[string]interface{}{"timeSpent": workAmount})
|
payload, err := serializePayload(map[string]interface{}{"timeSpent": workAmount})
|
||||||
resp, err := httpClient.Post("api/2/issue/"+taskKey+"/worklog", payload)
|
resp, err := httpClient.Post("api/2/issue/"+taskKey+"/worklog", payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue