Compare commits
17 commits
silentmode
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
493275c780 | ||
|
|
f5b3436243 | ||
|
|
2a6d302067 | ||
|
|
7526ce33cc | ||
|
|
672d42341c | ||
|
|
0bc3cce7d7 | ||
|
|
1215ca9b05 | ||
|
|
2f862fa7d6 | ||
|
|
4e2338e694 | ||
|
|
197e28024b | ||
|
|
96cadaf4df | ||
|
|
22a1ba56e5 | ||
|
|
9aa31ec22c | ||
|
|
1c44b282a8 | ||
|
|
70716e847e | ||
|
|
a36986db32 | ||
|
|
1458b63287 |
21 changed files with 656 additions and 189 deletions
12
Godeps/Godeps.json
generated
12
Godeps/Godeps.json
generated
|
|
@ -1,12 +0,0 @@
|
||||||
{
|
|
||||||
"ImportPath": "github.com/avanier/jkl",
|
|
||||||
"GoVersion": "go1.8",
|
|
||||||
"GodepVersion": "v79",
|
|
||||||
"Deps": [
|
|
||||||
{
|
|
||||||
"ImportPath": "github.com/joho/godotenv",
|
|
||||||
"Comment": "v1.1-23-gc9360df",
|
|
||||||
"Rev": "c9360df4d16dc0e391ea2f28da2d31a9ede2e26f"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
5
Godeps/Readme
generated
5
Godeps/Readme
generated
|
|
@ -1,5 +0,0 @@
|
||||||
This directory tree is generated automatically by godep.
|
|
||||||
|
|
||||||
Please do not edit.
|
|
||||||
|
|
||||||
See https://github.com/tools/godep for more information.
|
|
||||||
28
README.md
28
README.md
|
|
@ -1,14 +1,34 @@
|
||||||
# jkl
|
# jkl
|
||||||
|
jkl is a library for programmatically interacting with a JIRA installation. It
|
||||||
TODO: Write a project description
|
comes with a command line program (also called `jkl`) which allows you to
|
||||||
|
interact with JIRA via the command line.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
To use the library, simply import it into your application:
|
||||||
|
`import "github.com/otremblay/jkl"`
|
||||||
|
|
||||||
TODO: Describe the installation process
|
To install the command line application:
|
||||||
|
First, make sure you have a working go environment:
|
||||||
|
https://golang.org/doc/install
|
||||||
|
|
||||||
|
Then, execute the following command from your shell:
|
||||||
|
|
||||||
|
`$ go get github.com/otremblay/jkl/cmd/jkl`
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
TODO: Write usage instructions
|
Make sure you create a `~/.jklrc` file in your home directory, it should contain
|
||||||
|
at a minimum:
|
||||||
|
|
||||||
|
```
|
||||||
|
JIRA_ROOT="https://jira.example.com/"
|
||||||
|
JIRA_USER="myusername"
|
||||||
|
JIRA_PASSWORD="mypassword"
|
||||||
|
JIRA_PROJECT="DPK"
|
||||||
|
```
|
||||||
|
Those values are for example only, your setup will be different.
|
||||||
|
|
||||||
|
TODO: Finish writing usage instructions
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|
|
||||||
36
cmd/jkl/assign.go
Normal file
36
cmd/jkl/assign.go
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
|
||||||
|
"otremblay.com/jkl"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AssignCmd struct {
|
||||||
|
args []string
|
||||||
|
assignee string
|
||||||
|
issueKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAssignCmd(args []string) (*AssignCmd, error) {
|
||||||
|
ccmd := &AssignCmd{}
|
||||||
|
f := flag.NewFlagSet("assign", flag.ExitOnError)
|
||||||
|
f.Parse(args)
|
||||||
|
if len(f.Args()) < 2 {
|
||||||
|
return nil, ErrAssignNotEnoughArgs
|
||||||
|
}
|
||||||
|
ccmd.issueKey = f.Arg(0)
|
||||||
|
ccmd.assignee = f.Arg(1)
|
||||||
|
return ccmd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrAssignNotEnoughArgs = errors.New("Not enough arguments, need issue key + assignee")
|
||||||
|
|
||||||
|
func (ccmd *AssignCmd) Assign() error {
|
||||||
|
return jkl.Assign(ccmd.issueKey, ccmd.assignee)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ccmd *AssignCmd) Run() error {
|
||||||
|
return ccmd.Assign()
|
||||||
|
}
|
||||||
33
cmd/jkl/attach.go
Normal file
33
cmd/jkl/attach.go
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
|
||||||
|
"otremblay.com/jkl"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AttachCmd struct {
|
||||||
|
args []string
|
||||||
|
file string
|
||||||
|
taskKey string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAttachCmd(args []string) (*AttachCmd, error) {
|
||||||
|
ccmd := &AttachCmd{}
|
||||||
|
f := flag.NewFlagSet("x", flag.ExitOnError)
|
||||||
|
f.Parse(args)
|
||||||
|
if len(f.Args()) < 2 {
|
||||||
|
return nil, ErrNotEnoughArgs
|
||||||
|
}
|
||||||
|
ccmd.taskKey = f.Arg(0)
|
||||||
|
ccmd.file = f.Arg(1)
|
||||||
|
return ccmd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ecmd *AttachCmd) Attach() error {
|
||||||
|
return jkl.Attach(ecmd.taskKey, ecmd.file)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ecmd *AttachCmd) Run() error {
|
||||||
|
return ecmd.Attach()
|
||||||
|
}
|
||||||
|
|
@ -4,18 +4,19 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"flag"
|
"flag"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"fmt"
|
"strings"
|
||||||
"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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -43,7 +44,7 @@ func (ccmd *CreateCmd) Create() error {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ccmd.project == "" {
|
if ccmd.project == "" {
|
||||||
return ErrCcmdJiraProjectRequired
|
return ErrCcmdJiraProjectRequired
|
||||||
}
|
}
|
||||||
|
|
@ -55,13 +56,22 @@ func (ccmd *CreateCmd) Create() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, fmt.Sprintf("Error getting the CreateMeta for project [%s] and issue types [%s]", ccmd.project, isstype), err)
|
fmt.Fprintln(os.Stderr, fmt.Sprintf("Error getting the CreateMeta for project [%s] and issue types [%s]", ccmd.project, isstype), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !readfile {
|
if !readfile {
|
||||||
createTemplate.Execute(b, cm)
|
createTemplate.Execute(b, cm)
|
||||||
}
|
}
|
||||||
var iss *jkl.JiraIssue
|
var iss *jkl.JiraIssue
|
||||||
// TODO: Evil badbad don't do this.
|
// TODO: Evil badbad don't do this.
|
||||||
em := &jkl.EditMeta{Fields: cm.Projects[0].IssueTypes[0].Fields}
|
var isst = cm.Projects[0].IssueTypes[0].Fields
|
||||||
|
for _, v := range cm.Projects[0].IssueTypes {
|
||||||
|
if strings.ToLower(isstype) == strings.ToLower(v.Name) {
|
||||||
|
isst = v.Fields
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
em := &jkl.EditMeta{Fields: isst}
|
||||||
|
|
||||||
if ccmd.file != "" {
|
if ccmd.file != "" {
|
||||||
iss, err = GetIssueFromFile(ccmd.file, b, em)
|
iss, err = GetIssueFromFile(ccmd.file, b, em)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -21,9 +21,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
@ -17,20 +16,26 @@ 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)
|
||||||
// end
|
// end
|
||||||
var editors = []string{os.Getenv("EDITOR"), "nano", "vim", "vi"}
|
var editors = []string{"nano", "vim", "vi"}
|
||||||
|
|
||||||
// GetEditor returns the path to an editor, taking $EDITOR in account
|
// GetEditor returns the path to an editor, taking $EDITOR in account
|
||||||
func GetEditor() string {
|
func GetEditor() string {
|
||||||
|
if ed := os.Getenv("EDITOR"); ed != "" {
|
||||||
|
return ed
|
||||||
|
}
|
||||||
|
if ed := os.Getenv("VISUAL"); ed != "" {
|
||||||
|
return ed
|
||||||
|
}
|
||||||
for _, ed := range editors {
|
for _, ed := range editors {
|
||||||
if p, err := exec.LookPath(ed); err == nil {
|
if p, err := exec.LookPath(ed); err == nil {
|
||||||
return p
|
return p
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Fatal("No editor available; use flags.")
|
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,7 +109,7 @@ func GetIssueFromFile(filename string, initial io.Reader, editMeta *jkl.EditMeta
|
||||||
var spacex = regexp.MustCompile(`\s`)
|
var spacex = regexp.MustCompile(`\s`)
|
||||||
|
|
||||||
func IssueFromReader(f io.Reader, editMeta *jkl.EditMeta) *jkl.JiraIssue {
|
func IssueFromReader(f io.Reader, editMeta *jkl.EditMeta) *jkl.JiraIssue {
|
||||||
iss := &jkl.JiraIssue{Fields: &jkl.Fields{}}
|
iss := &jkl.JiraIssue{Fields: &jkl.Fields{ExtraFields: map[string]interface{}{}}}
|
||||||
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{}
|
||||||
|
|
@ -154,14 +159,40 @@ func IssueFromReader(f io.Reader, editMeta *jkl.EditMeta) *jkl.JiraIssue {
|
||||||
} else {
|
} else {
|
||||||
currentField = newfield
|
currentField = newfield
|
||||||
}
|
}
|
||||||
} else if editMeta != nil {
|
} else if editMeta != nil {
|
||||||
// If it's not valid, throw it at the createmeta. It will probably end up in ExtraFields.
|
// If it's not valid, throw it at the createmeta. It will probably end up in ExtraFields.
|
||||||
|
val := strings.TrimSpace(strings.Join(parts[1:], ":"))
|
||||||
|
for fieldname, m := range editMeta.Fields {
|
||||||
|
var something interface{} = val
|
||||||
|
if strings.ToLower(m.Name) == strings.ToLower(potentialField) {
|
||||||
|
name := fieldname
|
||||||
|
for _, av := range m.AllowedValues {
|
||||||
|
if strings.ToLower(av.Name) == strings.ToLower(val) {
|
||||||
|
something = av
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if m.Schema.CustomId > 0 {
|
||||||
|
name = fmt.Sprintf("custom_%d", m.Schema.CustomId)
|
||||||
|
}
|
||||||
|
iss.Fields.ExtraFields[name] = something
|
||||||
|
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
if currentField.IsValid() {
|
if currentField.IsValid() {
|
||||||
currentField.SetString(strings.TrimSpace(currentField.String() + "\n" + strings.Join(parts, ":")))
|
newpart := strings.Join(parts, ":")
|
||||||
|
newvalue := currentField.String() + "\n" + newpart
|
||||||
|
if strings.TrimSpace(newpart) != "" {
|
||||||
|
newvalue = strings.TrimSpace(newvalue)
|
||||||
|
}
|
||||||
|
currentField.SetString(newvalue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
iss.EditMeta = editMeta
|
||||||
return iss
|
return iss
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ 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
cmd/jkl/flag.go
Normal file
34
cmd/jkl/flag.go
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
|
||||||
|
"otremblay.com/jkl"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FlagCmd struct {
|
||||||
|
args []string
|
||||||
|
flg bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFlagCmd(args []string, flg bool) (*FlagCmd, error) {
|
||||||
|
ccmd := &FlagCmd{flg: flg}
|
||||||
|
f := flag.NewFlagSet("flag", flag.ExitOnError)
|
||||||
|
f.Parse(args)
|
||||||
|
if len(f.Args()) < 1 {
|
||||||
|
return nil, ErrFlagNotEnoughArgs
|
||||||
|
}
|
||||||
|
ccmd.args = f.Args()
|
||||||
|
return ccmd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrFlagNotEnoughArgs = errors.New("Not enough arguments, need at least one issue key")
|
||||||
|
|
||||||
|
func (ccmd *FlagCmd) Flag() error {
|
||||||
|
return jkl.FlagIssue(ccmd.args, ccmd.flg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ccmd *FlagCmd) Run() error {
|
||||||
|
return ccmd.Flag()
|
||||||
|
}
|
||||||
|
|
@ -55,12 +55,22 @@ func getCmd(args []string, depth int) (Runner, error) {
|
||||||
case "edit":
|
case "edit":
|
||||||
return NewEditCmd(args[1:])
|
return NewEditCmd(args[1:])
|
||||||
case "comment":
|
case "comment":
|
||||||
if strings.Contains(strings.Join(args,""),jkl.CommentIdSeparator){
|
if strings.Contains(strings.Join(args, ""), jkl.CommentIdSeparator) {
|
||||||
return NewEditCommentCmd(args[1:])
|
return NewEditCommentCmd(args[1:])
|
||||||
}
|
}
|
||||||
return NewCommentCmd(args[1:])
|
return NewCommentCmd(args[1:])
|
||||||
case "edit-comment":
|
case "edit-comment":
|
||||||
return NewEditCommentCmd(args[1:])
|
return NewEditCommentCmd(args[1:])
|
||||||
|
case "assign":
|
||||||
|
return NewAssignCmd(args[1:])
|
||||||
|
case "flag":
|
||||||
|
return NewFlagCmd(args[1:], true)
|
||||||
|
case "unflag":
|
||||||
|
return NewFlagCmd(args[1:], false)
|
||||||
|
case "link":
|
||||||
|
return NewLinkCmd(args[1:])
|
||||||
|
case "attach":
|
||||||
|
return NewAttachCmd(args[1:])
|
||||||
default:
|
default:
|
||||||
// Think about this real hard.
|
// Think about this real hard.
|
||||||
// I want `jkl JIRA-1234 done` to move it to done.
|
// I want `jkl JIRA-1234 done` to move it to done.
|
||||||
|
|
@ -92,9 +102,10 @@ func getCmd(args []string, depth int) (Runner, error) {
|
||||||
return nil, ErrTaskSubCommandNotFound
|
return nil, ErrTaskSubCommandNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
var verbs = []string{"list", "create", "task", "edit", "comment","edit-comment"}
|
var verbs = []string{"list", "create", "task", "edit", "comment", "edit-comment", "attach"}
|
||||||
func init(){
|
|
||||||
sort.Strings(verbs)
|
func init() {
|
||||||
|
sort.Strings(verbs)
|
||||||
}
|
}
|
||||||
|
|
||||||
const usage = `Usage:
|
const usage = `Usage:
|
||||||
|
|
|
||||||
30
cmd/jkl/link.go
Normal file
30
cmd/jkl/link.go
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
|
||||||
|
"otremblay.com/jkl"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LinkCmd struct {
|
||||||
|
args []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLinkCmd(args []string) (*LinkCmd, error) {
|
||||||
|
ccmd := &LinkCmd{}
|
||||||
|
f := flag.NewFlagSet("Link", flag.ExitOnError)
|
||||||
|
f.Parse(args)
|
||||||
|
ccmd.args = f.Args()
|
||||||
|
return ccmd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var ErrLinkNotEnoughArgs = errors.New("Not enough arguments, need at least two issue keys and a reason")
|
||||||
|
|
||||||
|
func (ccmd *LinkCmd) Link() error {
|
||||||
|
return jkl.LinkIssue(ccmd.args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ccmd *LinkCmd) Run() error {
|
||||||
|
return ccmd.Link()
|
||||||
|
}
|
||||||
|
|
@ -21,6 +21,9 @@ func (l *listissue) URL() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *listissue) Color() string {
|
func (l *listissue) Color() string {
|
||||||
|
if l.Fields == nil || l.Fields.Status == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
if os.Getenv("JKLNOCOLOR") == "true" || !terminal.IsTerminal(int(os.Stdout.Fd())) {
|
if os.Getenv("JKLNOCOLOR") == "true" || !terminal.IsTerminal(int(os.Stdout.Fd())) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
@ -52,7 +55,7 @@ func NewListCmd(args []string) (*ListCmd, error) {
|
||||||
if *verbose {
|
if *verbose {
|
||||||
fmt.Println(&ccmd.tmplstr)
|
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({{if .Fields.IssueType}}{{.Fields.IssueType.Name}}{{end}}{{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()
|
||||||
if len(ccmd.args) == 0 {
|
if len(ccmd.args) == 0 {
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,11 @@ func (t *TaskCmd) Handle() error {
|
||||||
return t.Get(t.args[0])
|
return t.Get(t.args[0])
|
||||||
}
|
}
|
||||||
if c == 2 {
|
if c == 2 {
|
||||||
fmt.Println(t.args)
|
// fmt.Println(t.args)
|
||||||
err := t.Transition(t.args[0], t.args[1])
|
err := t.Transition(t.args[0], t.args[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
//fmt.Println(err)
|
||||||
return t.Log(t.args[0], strings.Join(t.args[1:]," "))
|
return t.Log(t.args[0], strings.Join(t.args[1:], " "))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ErrTaskSubCommandNotFound
|
return ErrTaskSubCommandNotFound
|
||||||
|
|
@ -45,7 +45,7 @@ func (t *TaskCmd) Transition(taskKey, transition string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TaskCmd) Log(taskKey, time string) error {
|
func (t *TaskCmd) Log(taskKey, time string) error {
|
||||||
return jkl.LogWork(taskKey, time)
|
return jkl.LogWork(taskKey, time)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TaskCmd) Run() error {
|
func (t *TaskCmd) Run() error {
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ func (f *jklfile) String() string {
|
||||||
func (f *jklfile) Write(data []byte, off int64) (uint32, fuse.Status) {
|
func (f *jklfile) Write(data []byte, off int64) (uint32, fuse.Status) {
|
||||||
n, err := f.File.WriteAt(data, off)
|
n, err := f.File.WriteAt(data, off)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return uint32(0),fuse.EACCES
|
return uint32(0), fuse.EACCES
|
||||||
}
|
}
|
||||||
return uint32(n), fuse.OK
|
return uint32(n), fuse.OK
|
||||||
}
|
}
|
||||||
|
|
@ -76,3 +76,7 @@ func (f *jklfile) SetInode(i *nodefs.Inode) {}
|
||||||
func (f *jklfile) Utimens(atime *time.Time, mtime *time.Time) fuse.Status {
|
func (f *jklfile) Utimens(atime *time.Time, mtime *time.Time) fuse.Status {
|
||||||
return fuse.EPERM
|
return fuse.EPERM
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *jklfile) Flock(flags int) fuse.Status {
|
||||||
|
return fuse.ENOSYS
|
||||||
|
}
|
||||||
|
|
|
||||||
10
go.mod
Normal file
10
go.mod
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
module github.com/otremblay/jkl
|
||||||
|
|
||||||
|
go 1.13
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/hanwen/go-fuse v0.0.0-20170609101909-5690be47d614
|
||||||
|
github.com/joho/godotenv v1.2.0
|
||||||
|
golang.org/x/crypto v0.0.0-20171019172325-541b9d50ad47
|
||||||
|
golang.org/x/sys v0.0.0-20171017063910-8dbc5d05d6ed
|
||||||
|
)
|
||||||
4
go.sum
Normal file
4
go.sum
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
github.com/hanwen/go-fuse v0.0.0-20170609101909-5690be47d614/go.mod h1:4ZJ05v9yt5k/mcFkGvSPKJB5T8G/6nuumL63ZqlrPvI=
|
||||||
|
github.com/joho/godotenv v1.2.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||||
|
golang.org/x/crypto v0.0.0-20171019172325-541b9d50ad47/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
golang.org/x/sys v0.0.0-20171017063910-8dbc5d05d6ed/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
306
issue.go
306
issue.go
|
|
@ -19,9 +19,9 @@ type Search struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
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 +38,13 @@ func (it *IssueType) RangeFieldSpecs() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
type AllowedValue struct {
|
type AllowedValue struct {
|
||||||
Id string
|
Id string `json:"id"`
|
||||||
Self string
|
Self string `json:"self"`
|
||||||
Value string
|
Value string `json:"value"`
|
||||||
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *AllowedValue) String() string{
|
func (a *AllowedValue) String() string {
|
||||||
return a.Value
|
return a.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -51,24 +52,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 +81,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 +91,7 @@ type CommentColl struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type Status struct {
|
type Status struct {
|
||||||
Name string
|
Name string
|
||||||
IconURL string `json:",omitempty"`
|
IconURL string `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -107,14 +106,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 +128,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,36 +138,48 @@ 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")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
f.rawExtraFields = map[string]json.RawMessage{}
|
f.rawExtraFields = map[string]json.RawMessage{}
|
||||||
vf := reflect.ValueOf(f).Elem()
|
if reflect.ValueOf(f) == reflect.Zero(reflect.TypeOf(f)) {
|
||||||
|
fmt.Println("wtf")
|
||||||
|
}
|
||||||
|
v := reflect.ValueOf(f)
|
||||||
|
if !v.IsValid() {
|
||||||
|
fmt.Println("What all the fucks")
|
||||||
|
}
|
||||||
|
vf := v.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)))
|
||||||
|
}
|
||||||
|
val := reflect.ValueOf(obj)
|
||||||
|
if val == reflect.Zero(reflect.TypeOf(val)) || !val.IsValid() {
|
||||||
|
field.Set(reflect.Zero(objType))
|
||||||
|
} else {
|
||||||
|
field.Set(val.Elem())
|
||||||
}
|
}
|
||||||
field.Set(reflect.ValueOf(obj).Elem())
|
|
||||||
} else {
|
} else {
|
||||||
f.rawExtraFields[key] = mess
|
f.rawExtraFields[key] = mess
|
||||||
}
|
}
|
||||||
|
|
@ -182,22 +193,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,55 +229,142 @@ 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=([^,]+),`)
|
||||||
|
|
||||||
|
func isEmptyValue(v reflect.Value) bool {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
|
||||||
|
return v.Len() == 0
|
||||||
|
case reflect.Bool:
|
||||||
|
return !v.Bool()
|
||||||
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
|
return v.Int() == 0
|
||||||
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||||
|
return v.Uint() == 0
|
||||||
|
case reflect.Float32, reflect.Float64:
|
||||||
|
return v.Float() == 0
|
||||||
|
case reflect.Interface, reflect.Ptr:
|
||||||
|
return v.IsNil() || isEmptyValue(reflect.Indirect(v))
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *JiraIssue) MarshalJSON() ([]byte, error) {
|
||||||
|
fields := map[string]interface{}{}
|
||||||
|
vf := reflect.ValueOf(*(i.Fields))
|
||||||
|
if i.EditMeta != nil && i.EditMeta.Fields != nil {
|
||||||
|
for k, f := range i.EditMeta.Fields {
|
||||||
|
name := k
|
||||||
|
if f.Schema.CustomId > 0 {
|
||||||
|
name = fmt.Sprintf("custom_%d", f.Schema.CustomId)
|
||||||
|
}
|
||||||
|
if val, ok := i.Fields.ExtraFields[name]; ok && val != nil {
|
||||||
|
if f.Schema.Type == "array" {
|
||||||
|
fields[name] = []interface{}{val}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fields[name] = val
|
||||||
|
} else if val == nil {
|
||||||
|
delete(fields, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < vf.NumField(); i++ {
|
||||||
|
ft := vf.Type().Field(i)
|
||||||
|
fv := vf.Field(i)
|
||||||
|
|
||||||
|
if ft.Name != "ExtraFields" && (fv.CanSet() || fv.CanInterface() || fv.CanAddr()) && fv.IsValid() {
|
||||||
|
name := strings.ToLower(ft.Name)
|
||||||
|
if alias, ok := ft.Tag.Lookup("json"); ok {
|
||||||
|
if alias != "" {
|
||||||
|
name = strings.Split(alias, ",")[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value := fv.Interface()
|
||||||
|
if value != nil {
|
||||||
|
fields[name] = value
|
||||||
|
|
||||||
|
} else {
|
||||||
|
delete(fields, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Println(fields)
|
||||||
|
for k, v := range fields {
|
||||||
|
v2 := reflect.ValueOf(v)
|
||||||
|
for {
|
||||||
|
if v2.Kind() != reflect.Ptr && v2.Kind() != reflect.Interface {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
v2 = v2.Elem()
|
||||||
|
}
|
||||||
|
if !v2.IsValid() || (v2.Kind() == reflect.Slice && v2.Len() == 0) {
|
||||||
|
delete(fields, k)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
em := i.EditMeta
|
||||||
|
i.EditMeta = nil
|
||||||
|
defer func() { i.EditMeta = em }()
|
||||||
|
type Alias JiraIssue
|
||||||
|
return json.Marshal(&struct {
|
||||||
|
*Alias
|
||||||
|
Fields map[string]interface{} `json:"fields,omitempty"`
|
||||||
|
}{
|
||||||
|
Fields: fields,
|
||||||
|
Alias: (*Alias)(i),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
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)))
|
||||||
}
|
}
|
||||||
|
if _, ok := tmp["fields"]; !ok {
|
||||||
|
fmt.Fprintln(os.Stderr, "Received no fields? wtf?")
|
||||||
|
fmt.Fprintln(os.Stderr, string(b))
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
err = json.Unmarshal(tmp["fields"], &i.Fields)
|
err = json.Unmarshal(tmp["fields"], &i.Fields)
|
||||||
if err != nil && *Verbose {
|
if err != nil && *Verbose {
|
||||||
|
fmt.Println(string(tmp["fields"]))
|
||||||
fmt.Fprintln(os.Stderr, errors.New(fmt.Sprintf("%s: %s", "Error unpacking fields", err)))
|
fmt.Fprintln(os.Stderr, errors.New(fmt.Sprintf("%s: %s", "Error unpacking fields", err)))
|
||||||
}
|
}
|
||||||
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,34 +380,39 @@ 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] = strings.Replace(string(v), "\\r\\n", "\n", -1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -323,6 +426,9 @@ func (i *JiraIssue) String() string {
|
||||||
if os.Getenv("JKLNOCOLOR") == "true" {
|
if os.Getenv("JKLNOCOLOR") == "true" {
|
||||||
tmpl = issueTmplNoColor
|
tmpl = issueTmplNoColor
|
||||||
}
|
}
|
||||||
|
if customTmpl := os.Getenv("JKL_ISSUE_TMPL"); customTmpl != "" {
|
||||||
|
tmpl = template.Must(template.New("customIssueTmpl").Parse(customTmpl))
|
||||||
|
}
|
||||||
err := tmpl.Execute(b, i)
|
err := tmpl.Execute(b, i)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln(err)
|
log.Fatalln(err)
|
||||||
|
|
@ -331,7 +437,7 @@ func (i *JiraIssue) String() string {
|
||||||
return b.String()
|
return b.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *JiraIssue) PrintExtraFields() string{
|
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 +456,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 +464,30 @@ 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 = template.Must(template.New("issueTmpl").Parse(issueTmplTxt))
|
||||||
var issueTmplNoColor = template.Must(template.New("issueTmplNoColor").Parse(issueTmplNoColorTxt))
|
var issueTmplNoColor = template.Must(template.New("issueTmplNoColor").Parse(issueTmplNoColorTxt))
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package jkl
|
package jkl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
@ -9,6 +11,8 @@ import (
|
||||||
"net/http/cookiejar"
|
"net/http/cookiejar"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var j, _ = cookiejar.New(nil)
|
var j, _ = cookiejar.New(nil)
|
||||||
|
|
@ -20,39 +24,84 @@ type JiraClient struct {
|
||||||
jiraRoot string
|
jiraRoot string
|
||||||
}
|
}
|
||||||
|
|
||||||
func init(){
|
func init() {
|
||||||
x := false
|
x := false
|
||||||
Verbose = &x
|
Verbose = &x
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewJiraClient(jiraRoot string) *JiraClient {
|
func NewJiraClient(jiraRoot string) *JiraClient {
|
||||||
j := &JiraClient{
|
jc := &JiraClient{
|
||||||
&http.Client{
|
&http.Client{
|
||||||
Jar: j,
|
Jar: j,
|
||||||
},
|
},
|
||||||
jiraRoot,
|
jiraRoot,
|
||||||
}
|
}
|
||||||
if j.jiraRoot == "" {
|
if jc.jiraRoot == "" {
|
||||||
j.jiraRoot = os.Getenv("JIRA_ROOT")
|
jc.jiraRoot = os.Getenv("JIRA_ROOT")
|
||||||
}
|
}
|
||||||
|
if cookiefile := os.Getenv("JIRA_COOKIEFILE"); cookiefile != "" {
|
||||||
|
makeNewFile := false
|
||||||
|
f, err := os.Open(cookiefile)
|
||||||
|
server := jc.jiraRoot + "rest/gadget/1.0/login"
|
||||||
|
u, _ := url.Parse(server)
|
||||||
|
if err != nil {
|
||||||
|
makeNewFile = true
|
||||||
|
} else {
|
||||||
|
if stat, err := f.Stat(); err == nil {
|
||||||
|
if time.Now().Sub(stat.ModTime()).Minutes() > 60 {
|
||||||
|
makeNewFile = true
|
||||||
|
} else {
|
||||||
|
var cookies []*http.Cookie
|
||||||
|
dec := json.NewDecoder(f)
|
||||||
|
dec.Decode(&cookies)
|
||||||
|
u, _ = url.Parse(jc.jiraRoot)
|
||||||
|
jc.Jar.SetCookies(u, cookies)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
if makeNewFile {
|
||||||
|
f, err = os.Create(cookiefile)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
http.DefaultClient.Jar = j
|
||||||
|
form := url.Values{}
|
||||||
|
form.Add("os_username", os.Getenv("JIRA_USER"))
|
||||||
|
form.Add("os_password", os.Getenv("JIRA_PASSWORD"))
|
||||||
|
req, _ := http.NewRequest("POST", server, strings.NewReader(form.Encode()))
|
||||||
|
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil || resp.StatusCode >= 400 {
|
||||||
|
fmt.Println(resp.Header)
|
||||||
|
fmt.Println(resp.Status)
|
||||||
|
fmt.Println(err)
|
||||||
|
}
|
||||||
|
b := bytes.NewBuffer(nil)
|
||||||
|
enc := json.NewEncoder(b)
|
||||||
|
enc.Encode(j.Cookies(u))
|
||||||
|
io.Copy(f, b)
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if *Verbose {
|
if *Verbose {
|
||||||
fmt.Println("Jira root:", j.jiraRoot)
|
fmt.Println("Jira root:", jc.jiraRoot)
|
||||||
}
|
}
|
||||||
return j
|
return jc
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *JiraClient) Do(req *http.Request) (*http.Response, error) {
|
func (j *JiraClient) DoLess(req *http.Request) (*http.Response, error) {
|
||||||
var err error
|
|
||||||
req.SetBasicAuth(os.Getenv("JIRA_USER"), os.Getenv("JIRA_PASSWORD"))
|
|
||||||
if *Verbose {
|
|
||||||
fmt.Println("Jira User: ", os.Getenv("JIRA_USER"))
|
|
||||||
fmt.Println("Jira Password: ", os.Getenv("JIRA_PASSWORD"))
|
|
||||||
}
|
|
||||||
req.Header.Add("Content-Type", "application/json")
|
req.Header.Add("Content-Type", "application/json")
|
||||||
req.Header.Add("Accept", "application/json, text/plain, text/html")
|
req.Header.Add("Accept", "application/json, text/plain, text/html")
|
||||||
req.URL, err = url.Parse(j.jiraRoot + "rest/" + req.URL.RequestURI())
|
return j.DoEvenLess(req)
|
||||||
if err != nil {
|
}
|
||||||
return nil, err
|
|
||||||
|
func (j *JiraClient) DoEvenLess(req *http.Request) (*http.Response, error) {
|
||||||
|
var err error
|
||||||
|
if os.Getenv("JIRA_COOKIEFILE") == "" {
|
||||||
|
req.SetBasicAuth(os.Getenv("JIRA_USER"), os.Getenv("JIRA_PASSWORD"))
|
||||||
}
|
}
|
||||||
resp, err := j.Client.Do(req)
|
resp, err := j.Client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -75,6 +124,15 @@ func (j *JiraClient) Do(req *http.Request) (*http.Response, error) {
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (j *JiraClient) Do(req *http.Request) (*http.Response, error) {
|
||||||
|
var err error
|
||||||
|
req.URL, err = url.Parse(j.jiraRoot + "rest/" + req.URL.RequestURI())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return j.DoLess(req)
|
||||||
|
}
|
||||||
|
|
||||||
func (j *JiraClient) Put(path string, payload io.Reader) (*http.Response, error) {
|
func (j *JiraClient) Put(path string, payload io.Reader) (*http.Response, error) {
|
||||||
req, err := http.NewRequest("PUT", path, payload)
|
req, err := http.NewRequest("PUT", path, payload)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
121
jkl.go
121
jkl.go
|
|
@ -8,6 +8,8 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
"log"
|
||||||
|
"mime/multipart"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -32,6 +34,7 @@ func bootHttpClient() {
|
||||||
func Create(issue *JiraIssue) (*JiraIssue, error) {
|
func Create(issue *JiraIssue) (*JiraIssue, error) {
|
||||||
bootHttpClient()
|
bootHttpClient()
|
||||||
payload, err := formatPayload(issue)
|
payload, err := formatPayload(issue)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -216,7 +219,7 @@ func DoTransition(taskKey string, transitionName string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var t *Transition
|
var t *Transition
|
||||||
fmt.Println(iss.Transitions)
|
//fmt.Println(iss.Transitions)
|
||||||
for _, transition := range iss.Transitions {
|
for _, transition := range iss.Transitions {
|
||||||
if strings.ToLower(transition.Name) == strings.ToLower(transitionName) {
|
if strings.ToLower(transition.Name) == strings.ToLower(transitionName) {
|
||||||
t = transition
|
t = transition
|
||||||
|
|
@ -251,6 +254,120 @@ func LogWork(taskKey string, workAmount string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Assign(taskKey string, user string) error {
|
||||||
|
bootHttpClient()
|
||||||
|
payload, err := serializePayload(map[string]interface{}{"name": user})
|
||||||
|
resp, err := httpClient.Put("api/2/issue/"+taskKey+"/assignee", payload)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(resp.StatusCode)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if resp.StatusCode >= 400 {
|
||||||
|
io.Copy(os.Stderr, resp.Body)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func FlagIssue(taskKeys []string, flg bool) error {
|
||||||
|
bootHttpClient()
|
||||||
|
payload, err := serializePayload(map[string]interface{}{"issueKeys": taskKeys, "flag": flg})
|
||||||
|
req, err := http.NewRequest("POST", "", payload)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.URL, err = url.Parse(httpClient.jiraRoot + "rest/" + "greenhopper/1.0/xboard/issue/flag/flag.json")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
resp, err := httpClient.DoLess(req)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(resp.StatusCode)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if resp.StatusCode >= 400 {
|
||||||
|
io.Copy(os.Stderr, resp.Body)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type msi map[string]interface{}
|
||||||
|
|
||||||
|
func LinkIssue(params []string) error {
|
||||||
|
bootHttpClient()
|
||||||
|
if len(params) == 0 {
|
||||||
|
resp, err := httpClient.Get("api/2/issueLinkType")
|
||||||
|
if err != nil {
|
||||||
|
if resp != nil {
|
||||||
|
fmt.Println(resp.StatusCode)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
io.Copy(os.Stdout, resp.Body)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
payload, err := serializePayload(msi{
|
||||||
|
"type": msi{"name": strings.Join(params[1:len(params)-1], " ")},
|
||||||
|
"inwardIssue": msi{"key": params[len(params)-1]},
|
||||||
|
"outwardIssue": msi{"key": params[0]},
|
||||||
|
})
|
||||||
|
resp, err := httpClient.Post("api/2/issueLink", payload)
|
||||||
|
if err != nil {
|
||||||
|
if resp != nil {
|
||||||
|
fmt.Println(resp.StatusCode)
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if resp.StatusCode >= 400 {
|
||||||
|
io.Copy(os.Stderr, resp.Body)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Attach(issueKey string, filename string) error {
|
||||||
|
bootHttpClient()
|
||||||
|
|
||||||
|
// Prepare a form that you will submit to that URL.
|
||||||
|
var b bytes.Buffer
|
||||||
|
w := multipart.NewWriter(&b)
|
||||||
|
// Add your image file
|
||||||
|
f, err := os.Open(filename)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fi, err := os.Lstat(filename)
|
||||||
|
fw, err := w.CreateFormFile("file", fi.Name())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err = io.Copy(fw, f); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Don't forget to close the multipart writer.
|
||||||
|
// If you don't close it, your request will be missing the terminating boundary.
|
||||||
|
w.Close()
|
||||||
|
|
||||||
|
req, err := http.NewRequest("POST", "", &b)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.URL, err = url.Parse(httpClient.jiraRoot + "rest/" + fmt.Sprintf("api/2/issue/%s/attachments", issueKey))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.Header.Add("X-Atlassian-Token", "no-check")
|
||||||
|
req.Header.Add("Content-Type", w.FormDataContentType())
|
||||||
|
res, err := httpClient.DoEvenLess(req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s, _ := ioutil.ReadAll(res.Body)
|
||||||
|
fmt.Println(string(s))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func formatPayload(issue *JiraIssue) (io.Reader, error) {
|
func formatPayload(issue *JiraIssue) (io.Reader, error) {
|
||||||
if issue.Fields != nil &&
|
if issue.Fields != nil &&
|
||||||
issue.Fields.Project != nil &&
|
issue.Fields.Project != nil &&
|
||||||
|
|
@ -265,7 +382,7 @@ func serializePayload(i interface{}) (io.Reader, error) {
|
||||||
payload := bytes.NewBuffer(b)
|
payload := bytes.NewBuffer(b)
|
||||||
enc := json.NewEncoder(payload)
|
enc := json.NewEncoder(payload)
|
||||||
err := enc.Encode(i)
|
err := enc.Encode(i)
|
||||||
fmt.Println(payload.String())
|
//fmt.Println("payload: ", payload.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
23
jkl_test.go
23
jkl_test.go
|
|
@ -1,34 +1,11 @@
|
||||||
package jkl
|
package jkl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
"text/template"
|
"text/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestUnmarshalProjects(t *testing.T) {
|
|
||||||
f, err := os.Open("projects.json")
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
dec := json.NewDecoder(f)
|
|
||||||
x := struct{ Projects []Project }{}
|
|
||||||
|
|
||||||
err = dec.Decode(&x)
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
for _, p := range x.Projects {
|
|
||||||
for _, it := range p.IssueTypes {
|
|
||||||
for sn, f := range it.Fields {
|
|
||||||
fmt.Println(it.Name, sn, f.Name, f.Required, f.Schema.Type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestType struct {
|
type TestType struct {
|
||||||
Field string
|
Field string
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue