diff --git a/cmd/jira/main.go b/cmd/jira/main.go index af0029d6..16241e44 100644 --- a/cmd/jira/main.go +++ b/cmd/jira/main.go @@ -254,6 +254,11 @@ func main() { Entry: cli.CmdBrowseRegistry(), Aliases: []string{"b"}, }, + jiracli.CommandRegistry{ + Command: "request", + Entry: cli.CmdRequestRegistry(), + Aliases: []string{"req"}, + }, } cli.Register(app, registry) @@ -270,285 +275,4 @@ func main() { if err != nil { log.Fatalf("%s", err) } - - // Usage: - // jira (b|browse) ISSUE - // jira request [-M METHOD] URI [DATA] - // jira ISSUE - - // General Options: - // -b --browse Open your browser to the Jira issue - // -e --endpoint=URI URI to use for jira - // -k --insecure disable TLS certificate verification - // -h --help Show this usage - // -t --template=FILE Template file to use for output/editing - // -u --user=USER Username to use for authenticaion (default: %s) - // -v --verbose Increase output logging - // --unixproxy=PATH Path for a unix-socket proxy (eg., --unixproxy /tmp/proxy.sock) - // --version Print version - - // Query Options: - // -a --assignee=USER Username assigned the issue - // -c --component=COMPONENT Component to Search for - // -f --queryfields=FIELDS Fields that are used in "list" template: (default: %s) - // -i --issuetype=ISSUETYPE The Issue Type - // -l --limit=VAL Maximum number of results to return in query (default: %d) - // --start=START Start parameter for pagination - // -p --project=PROJECT Project to Search for - // -q --query=JQL Jira Query Language expression for the search - // -r --reporter=USER Reporter to search for - // -s --sort=ORDER For list operations, sort issues (default: %s) - - // Edit Options: - // -m --comment=COMMENT Comment message for transition - // -o --override=KEY=VAL Set custom key/value pairs - - // Create Options: - // -i --issuetype=ISSUETYPE Jira Issue Type (default: Bug) - // -m --comment=COMMENT Comment message for transition - // -o --override=KEY=VAL Set custom key/value pairs - - // Worklog Options: - // -T --time-spent=TIMESPENT Time spent working on issue - // -m --comment=COMMENT Comment message for worklog - - // Command Options: - // -d --directory=DIR Directory to export templates to (default: %s) - // `, user, defaultQueryFields, defaultMaxResults, defaultSort, user, fmt.Sprintf("%s/.jira.d/templates", home)) - // printer(output) - // } - - // jiraCommands := map[string]string{ - // "browse": "browse", - // "req": "request", - // "request": "request", - // } - - // defaults := map[string]interface{}{ - // "user": user, - // "queryfields": defaultQueryFields, - // "directory": fmt.Sprintf("%s/.jira.d/templates", home), - // "sort": defaultSort, - // "max_results": defaultMaxResults, - // "method": "GET", - // "quiet": false, - // } - // opts := make(map[string]interface{}) - - // setopt := func(name string, value interface{}) { - // opts[name] = value - // } - - // op := optigo.NewDirectAssignParser(map[string]interface{}{ - // "h|help": usage, - // "version": func() { - // fmt.Println(fmt.Sprintf("version: %s", jira.VERSION)) - // os.Exit(0) - // }, - // "v|verbose+": func() { - // logging.SetLevel(logging.GetLevel("")+1, "") - // }, - // "dryrun": setopt, - // "b|browse": setopt, - // "editor=s": setopt, - // "u|user=s": setopt, - // "endpoint=s": setopt, - // "k|insecure": setopt, - // "t|template=s": setopt, - // "q|query=s": setopt, - // "p|project=s": setopt, - // "c|component=s": setopt, - // "a|assignee=s": setopt, - // "i|issuetype=s": setopt, - // "remove": setopt, - // "r|reporter=s": setopt, - // "f|queryfields=s": setopt, - // "x|expand=s": setopt, - // "s|sort=s": setopt, - // "l|limit|max_results=i": setopt, - // "start|start_at=i": setopt, - // "o|override=s%": &opts, - // "noedit": setopt, - // "edit": setopt, - // "m|comment=s": setopt, - // "d|dir|directory=s": setopt, - // "M|method=s": setopt, - // "S|saveFile=s": setopt, - // "T|time-spent=s": setopt, - // "Q|quiet": setopt, - // "unixproxy": setopt, - // "down": setopt, - // "default": setopt, - // }) - - // if err := op.ProcessAll(os.Args[1:]); err != nil { - // log.Errorf("%s", err) - // usage(false) - // } - // args := op.Args - - // var command string - // if len(args) > 0 { - // if alias, ok := jiraCommands[args[0]]; ok { - // command = alias - // args = args[1:] - // } else if len(args) > 1 { - // // look at second arg for "dups" and "blocks" commands - // // also for 'set/add/remove' actions like 'labels' - // if alias, ok := jiraCommands[args[1]]; ok { - // command = alias - // args = append(args[:1], args[2:]...) - // } - // } - // } - - // if command == "" && len(args) > 0 { - // command = args[0] - // args = args[1:] - // } - - // os.Setenv("JIRA_OPERATION", command) - // loadConfigs(opts) - - // // check to see if it was set in the configs: - // if value, ok := opts["command"].(string); ok { - // command = value - // } else if _, ok := jiraCommands[command]; !ok || command == "" { - // if command != "" { - // args = append([]string{command}, args...) - // } - // command = "view" - // } - - // // apply defaults - // for k, v := range defaults { - // if _, ok := opts[k]; !ok { - // log.Debugf("Setting %q to %#v from defaults", k, v) - // opts[k] = v - // } - // } - - // log.Debugf("opts: %v", opts) - // log.Debugf("args: %v", args) - - // if _, ok := opts["endpoint"]; !ok { - // log.Errorf("endpoint option required. Either use --endpoint or set a endpoint option in your ~/.jira.d/config.yml file") - // os.Exit(1) - // } - - // c := jira.New(opts) - - // log.Debugf("opts: %s", opts) - - // setEditing := func(dflt bool) { - // log.Debugf("Default Editing: %t", dflt) - // if dflt { - // if val, ok := opts["noedit"].(bool); ok && val { - // log.Debugf("Setting edit = false") - // opts["edit"] = false - // } else { - // log.Debugf("Setting edit = true") - // opts["edit"] = true - // } - // } else { - // if _, ok := opts["edit"].(bool); !ok { - // log.Debugf("Setting edit = %t", dflt) - // opts["edit"] = dflt - // } - // } - // } - - // requireArgs := func(count int) { - // if len(args) < count { - // log.Errorf("Not enough arguments. %d required, %d provided", count, len(args)) - // usage(false) - // } - // } - - // var err error - // switch command { - // case "request": - // requireArgs(1) - // data := "" - // if len(args) > 1 { - // data = args[1] - // } - // err = c.CmdRequest(args[0], data) - // default: - // log.Errorf("Unknown command %s", command) - // os.Exit(1) - // } - - // if err != nil { - // log.Errorf("%s", err) - // os.Exit(1) - // } - // os.Exit(0) } - -// func parseYaml(file string, opts map[string]interface{}) { -// if fh, err := ioutil.ReadFile(file); err == nil { -// log.Debugf("Found Config file: %s", file) -// if err := yaml.Unmarshal(fh, &opts); err != nil { -// log.Errorf("Unable to parse %s: %s", file, err) -// } -// } -// } - -// func populateEnv(opts map[string]interface{}) { -// for k, v := range opts { -// envName := fmt.Sprintf("JIRA_%s", strings.ToUpper(k)) -// var val string -// switch t := v.(type) { -// case string: -// val = t -// case int, int8, int16, int32, int64: -// val = fmt.Sprintf("%d", t) -// case float32, float64: -// val = fmt.Sprintf("%f", t) -// case bool: -// val = fmt.Sprintf("%t", t) -// default: -// val = fmt.Sprintf("%v", t) -// } -// os.Setenv(envName, val) -// } -// } - -// func loadConfigs(opts map[string]interface{}) { -// populateEnv(opts) -// paths := jira.FindParentPaths(".jira.d/config.yml") -// // prepend -// paths = append(paths, "/etc/go-jira.yml") - -// // iterate paths in reverse -// for i := 0; i < len(paths); i++ { -// file := paths[i] -// if stat, err := os.Stat(file); err == nil { -// tmp := make(map[string]interface{}) -// // check to see if config file is exectuable -// if stat.Mode()&0111 == 0 { -// parseYaml(file, tmp) -// } else { -// log.Debugf("Found Executable Config file: %s", file) -// // it is executable, so run it and try to parse the output -// cmd := exec.Command(file) -// stdout := bytes.NewBufferString("") -// cmd.Stdout = stdout -// cmd.Stderr = bytes.NewBufferString("") -// if err := cmd.Run(); err != nil { -// log.Errorf("%s is exectuable, but it failed to execute: %s\n%s", file, err, cmd.Stderr) -// os.Exit(1) -// } -// yaml.Unmarshal(stdout.Bytes(), &tmp) -// } -// for k, v := range tmp { -// if _, ok := opts[k]; !ok { -// log.Debugf("Setting %q to %#v from %s", k, v, file) -// opts[k] = v -// } -// } -// populateEnv(opts) -// } -// } -// } diff --git a/jiracli/cli.go b/jiracli/cli.go index 1be00bfc..3e6499b8 100644 --- a/jiracli/cli.go +++ b/jiracli/cli.go @@ -31,6 +31,38 @@ type JiraCli struct { oreoAgent *oreo.Client } +type Exit struct { + Code int +} + +type GlobalOptions struct { + Browse bool `json:"browse,omitempty" yaml:"browse,omitempty"` + Editor string `json:"editor,omitempty" yaml:"editor,omitempty"` + SkipEditing bool `json:"noedit,omitempty" yaml:"noedit,omitempty"` + PasswordSource string `json:"password-source,omitempty" yaml:"password-source,omitempty"` + Template string `json:"template,omitempty" yaml:"template,omitempty"` + User string `json:"user,omitempty", yaml:"user,omitempty"` +} + +type CommandRegistryEntry struct { + Help string + ExecuteFunc func() error + UsageFunc func(*kingpin.CmdClause) error +} + +type CommandRegistry struct { + Command string + Aliases []string + Entry *CommandRegistryEntry + Default bool +} + +// either kingpin.Application or kingpin.CmdClause fit this interface +type kingpinAppOrCommand interface { + Command(string, string) *kingpin.CmdClause + GetCommand(string) *kingpin.CmdClause +} + func New(configDir string) *JiraCli { agent := oreo.New().WithCookieFile(filepath.Join(homedir(), configDir, "cookies.js")) return &JiraCli{ @@ -42,17 +74,38 @@ func New(configDir string) *JiraCli { } } -type Exit struct { - Code int -} +func (jc *JiraCli) Register(app *kingpin.Application, reg []CommandRegistry) { + for _, command := range reg { + copy := command + commandFields := strings.Fields(copy.Command) + var appOrCmd kingpinAppOrCommand = app + if len(commandFields) > 1 { + for _, name := range commandFields[0 : len(commandFields)-1] { + tmp := appOrCmd.GetCommand(name) + if tmp == nil { + tmp = appOrCmd.Command(name, "") + } + appOrCmd = tmp + } + } -type GlobalOptions struct { - Browse bool `json:"browse,omitempty" yaml:"browse,omitempty"` - Editor string `json:"editor,omitempty" yaml:"editor,omitempty"` - SkipEditing bool `json:"noedit,omitempty" yaml:"noedit,omitempty"` - PasswordSource string `json:"password-source,omitempty" yaml:"password-source,omitempty"` - Template string `json:"template,omitempty" yaml:"template,omitempty"` - User string `json:"user,omitempty", yaml:"user,omitempty"` + cmd := appOrCmd.Command(commandFields[len(commandFields)-1], copy.Entry.Help) + for _, alias := range copy.Aliases { + cmd = cmd.Alias(alias) + } + if copy.Default { + cmd = cmd.Default() + } + if copy.Entry.UsageFunc != nil { + copy.Entry.UsageFunc(cmd) + } + + cmd.Action( + func(_ *kingpin.ParseContext) error { + return copy.Entry.ExecuteFunc() + }, + ) + } } func (jc *JiraCli) GlobalUsage(cmd *kingpin.CmdClause, opts *GlobalOptions) error { @@ -265,315 +318,3 @@ func (jc *JiraCli) editLoop(opts *GlobalOptions, input interface{}, output inter } return nil } - -// // New creates go-jira client object -// func New(opts map[string]interface{}) *Cli { -// homedir := homedir() -// cookieJar, _ := cookiejar.New(nil) -// endpoint, _ := opts["endpoint"].(string) -// url, _ := url.Parse(strings.TrimRight(endpoint, "/")) - -// if project, ok := opts["project"].(string); ok { -// opts["project"] = strings.ToUpper(project) -// } - -// var ua *http.Client -// if unixProxyPath, ok := opts["unixproxy"].(string); ok { -// ua = &http.Client{ -// Jar: cookieJar, -// Transport: UnixProxy(unixProxyPath), -// } -// } else { -// transport := &http.Transport{ -// Proxy: http.ProxyFromEnvironment, -// TLSClientConfig: &tls.Config{}, -// } -// if insecureSkipVerify, ok := opts["insecure"].(bool); ok { -// transport.TLSClientConfig.InsecureSkipVerify = insecureSkipVerify -// } - -// ua = &http.Client{ -// Jar: cookieJar, -// Transport: transport, -// } -// } - -// cli := &Cli{ -// endpoint: url, -// opts: opts, -// cookieFile: filepath.Join(homedir, ".jira.d", "cookies.js"), -// ua: ua, -// } - -// cli.ua.Jar.SetCookies(url, cli.loadCookies()) - -// return cli -// } - -// // NoChangesFound is an error returned from when editing templates -// // and no modifications were made while editing -// type NoChangesFound struct{} - -// func (f NoChangesFound) Error() string { -// return "No changes found, aborting" -// } - -// func (c *Cli) editTemplate(template string, tmpFilePrefix string, templateData map[string]interface{}, templateProcessor func(string) error) error { - -// tmpdir := filepath.Join(homedir(), ".jira.d", "tmp") -// if err := mkdir(tmpdir); err != nil { -// return err -// } - -// fh, err := ioutil.TempFile(tmpdir, tmpFilePrefix) -// if err != nil { -// log.Errorf("Failed to make temp file in %s: %s", tmpdir, err) -// return err -// } - -// oldFileName := fh.Name() -// tmpFileName := fmt.Sprintf("%s.yml", oldFileName) - -// // close tmpfile so we can rename on windows -// fh.Close() - -// if err := os.Rename(oldFileName, tmpFileName); err != nil { -// log.Errorf("Failed to rename %s to %s: %s", oldFileName, tmpFileName, err) -// return err -// } - -// fh, err = os.OpenFile(tmpFileName, os.O_RDWR|os.O_EXCL, 0600) -// if err != nil { -// log.Errorf("Failed to reopen temp file file in %s: %s", tmpFileName, err) -// return err -// } - -// defer fh.Close() -// defer func() { -// os.Remove(tmpFileName) -// }() - -// err = runTemplate(template, templateData, fh) -// if err != nil { -// return err -// } - -// fh.Close() - -// editor, ok := c.opts["editor"].(string) -// if !ok { -// editor = os.Getenv("JIRA_EDITOR") -// if editor == "" { -// editor = os.Getenv("EDITOR") -// if editor == "" { -// editor = "vim" -// } -// } -// } - -// editing := c.getOptBool("edit", true) - -// tmpFileNameOrig := fmt.Sprintf("%s.orig", tmpFileName) -// copyFile(tmpFileName, tmpFileNameOrig) -// defer func() { -// os.Remove(tmpFileNameOrig) -// }() - -// for true { -// if editing { -// shell, _ := shellquote.Split(editor) -// shell = append(shell, tmpFileName) -// log.Debugf("Running: %#v", shell) -// cmd := exec.Command(shell[0], shell[1:]...) -// cmd.Stdout, cmd.Stderr, cmd.Stdin = os.Stdout, os.Stderr, os.Stdin -// if err := cmd.Run(); err != nil { -// log.Errorf("Failed to edit template with %s: %s", editor, err) -// if promptYN("edit again?", true) { -// continue -// } -// return err -// } - -// diff := exec.Command("diff", "-q", tmpFileNameOrig, tmpFileName) -// // if err == nil then diff found no changes -// if err := diff.Run(); err == nil { -// return NoChangesFound{} -// } -// } - -// edited := make(map[string]interface{}) -// var data []byte -// if data, err = ioutil.ReadFile(tmpFileName); err != nil { -// log.Errorf("Failed to read tmpfile %s: %s", tmpFileName, err) -// if editing && promptYN("edit again?", true) { -// continue -// } -// return err -// } -// if err := yaml.Unmarshal(data, &edited); err != nil { -// log.Errorf("Failed to parse YAML: %s", err) -// if editing && promptYN("edit again?", true) { -// continue -// } -// return err -// } - -// var fixed interface{} -// if fixed, err = yamlFixup(edited); err != nil { -// return err -// } -// edited = fixed.(map[string]interface{}) - -// // if you want to abort editing a jira issue then -// // you can add the "abort: true" flag to the document -// // and we will abort now -// if val, ok := edited["abort"].(bool); ok && val { -// log.Infof("abort flag found in template, quiting") -// return fmt.Errorf("abort flag found in template, quiting") -// } - -// if _, ok := templateData["meta"]; ok { -// mf := templateData["meta"].(map[string]interface{})["fields"] -// if f, ok := edited["fields"].(map[string]interface{}); ok { -// for k := range f { -// if _, ok := mf.(map[string]interface{})[k]; !ok { -// err := fmt.Errorf("Field %s is not editable", k) -// log.Errorf("%s", err) -// if editing && promptYN("edit again?", true) { -// continue -// } -// return err -// } -// } -// } -// } - -// json, err := jsonEncode(edited) -// if err != nil { -// return err -// } - -// if err := templateProcessor(json); err != nil { -// log.Errorf("%s", err) -// if editing && promptYN("edit again?", true) { -// continue -// } -// } -// return nil -// } -// return nil -// } - -// // SaveData will write out the yaml formated --saveFile file with provided data -// func (c *Cli) SaveData(data interface{}) error { -// if val, ok := c.opts["saveFile"].(string); ok && val != "" { -// yamlWrite(val, data) -// } -// return nil -// } - -// // FindIssues will return a list of issues that match the given options. -// // If the "query" option is undefined it will generate a JQL query -// // using any/all of the provide options: project, component, assignee, -// // issuetype, watcher, reporter, sort -// // Further it will restrict the fields being extracted from the jira -// // response with the 'queryfields' option -// func (c *Cli) FindIssues() (interface{}, error) { -// var query string -// var ok bool -// // project = BAKERY and status not in (Resolved, Closed) -// if query, ok = c.opts["query"].(string); !ok { -// qbuff := bytes.NewBufferString("resolution = unresolved") -// var project string -// if project, ok = c.opts["project"].(string); !ok { -// err := fmt.Errorf("Missing required arguments, either 'query' or 'project' are required") -// log.Errorf("%s", err) -// return nil, err -// } -// qbuff.WriteString(fmt.Sprintf(" AND project = '%s'", project)) - -// if component, ok := c.opts["component"]; ok { -// qbuff.WriteString(fmt.Sprintf(" AND component = '%s'", component)) -// } - -// if assignee, ok := c.opts["assignee"]; ok { -// qbuff.WriteString(fmt.Sprintf(" AND assignee = '%s'", assignee)) -// } - -// if issuetype, ok := c.opts["issuetype"]; ok { -// qbuff.WriteString(fmt.Sprintf(" AND issuetype = '%s'", issuetype)) -// } - -// if watcher, ok := c.opts["watcher"]; ok { -// qbuff.WriteString(fmt.Sprintf(" AND watcher = '%s'", watcher)) -// } - -// if reporter, ok := c.opts["reporter"]; ok { -// qbuff.WriteString(fmt.Sprintf(" AND reporter = '%s'", reporter)) -// } - -// if sort, ok := c.opts["sort"]; ok && sort != "" { -// qbuff.WriteString(fmt.Sprintf(" ORDER BY %s", sort)) -// } - -// query = qbuff.String() -// } - -// fields := []string{"summary"} -// if qf, ok := c.opts["queryfields"].(string); ok { -// fields = strings.Split(qf, ",") -// } - -// json, err := jsonEncode(map[string]interface{}{ -// "jql": query, -// "startAt": c.opts["start_at"], -// "maxResults": c.opts["max_results"], -// "fields": fields, -// "expand": c.expansions(), -// }) -// if err != nil { -// return nil, err -// } - -// uri := fmt.Sprintf("%s/rest/api/2/search", c.endpoint) -// var data interface{} -// if data, err = responseToJSON(c.post(uri, json)); err != nil { -// return nil, err -// } -// return data, nil -// } - -// // GetOptString will extract the string from the Cli object options -// // otherwise return the provided default -// func (c *Cli) GetOptString(optName string, dflt string) string { -// return c.getOptString(optName, dflt) -// } - -// func (c *Cli) getOptString(optName string, dflt string) string { -// if val, ok := c.opts[optName].(string); ok { -// return val -// } -// return dflt -// } - -// // GetOptBool will extract the boolean value from the Client object options -// // otherwise return the provided default\ -// func (c *Cli) GetOptBool(optName string, dflt bool) bool { -// return c.getOptBool(optName, dflt) -// } - -// func (c *Cli) getOptBool(optName string, dflt bool) bool { -// if val, ok := c.opts[optName].(bool); ok { -// return val -// } -// return dflt -// } - -// // expansions returns a comma-separated list of values for field expansion -// func (c *Cli) expansions() []string { -// var expansions []string -// if x, ok := c.opts["expand"].(string); ok { -// expansions = strings.Split(x, ",") -// } -// return expansions -// } diff --git a/jiracli/commands.go b/jiracli/commands.go index 17588f5f..f615f78d 100644 --- a/jiracli/commands.go +++ b/jiracli/commands.go @@ -1,83 +1 @@ package jiracli - -import ( - "strings" - - kingpin "gopkg.in/alecthomas/kingpin.v2" -) - -type CommandRegistryEntry struct { - Help string - ExecuteFunc func() error - UsageFunc func(*kingpin.CmdClause) error -} - -type CommandRegistry struct { - Command string - Aliases []string - Entry *CommandRegistryEntry - Default bool -} - -// either kingpin.Application or kingpin.CmdClause fit this interface -type kingpinAppOrCommand interface { - Command(string, string) *kingpin.CmdClause - GetCommand(string) *kingpin.CmdClause -} - -func (jc *JiraCli) Register(app *kingpin.Application, reg []CommandRegistry) { - for _, command := range reg { - copy := command - commandFields := strings.Fields(copy.Command) - var appOrCmd kingpinAppOrCommand = app - if len(commandFields) > 1 { - for _, name := range commandFields[0 : len(commandFields)-1] { - tmp := appOrCmd.GetCommand(name) - if tmp == nil { - tmp = appOrCmd.Command(name, "") - } - appOrCmd = tmp - } - } - - cmd := appOrCmd.Command(commandFields[len(commandFields)-1], copy.Entry.Help) - for _, alias := range copy.Aliases { - cmd = cmd.Alias(alias) - } - if copy.Default { - cmd = cmd.Default() - } - if copy.Entry.UsageFunc != nil { - copy.Entry.UsageFunc(cmd) - } - - cmd.Action( - func(_ *kingpin.ParseContext) error { - return copy.Entry.ExecuteFunc() - }, - ) - } -} - -// // CmdRequest will use the given uri to make a request and potentially send provided content. -// func (c *Cli) CmdRequest(uri, content string) (err error) { -// log.Debugf("request called") - -// if !strings.HasPrefix(uri, "http") { -// uri = fmt.Sprintf("%s%s", c.endpoint, uri) -// } - -// method := strings.ToUpper(c.opts["method"].(string)) -// var data interface{} -// if method == "GET" { -// data, err = responseToJSON(c.get(uri)) -// } else if method == "POST" { -// data, err = responseToJSON(c.post(uri, content)) -// } else if method == "PUT" { -// data, err = responseToJSON(c.put(uri, content)) -// } -// if err != nil { -// return err -// } -// return runTemplate(c.getTemplate("request"), data, nil) -// } diff --git a/jiracli/edit.go b/jiracli/edit.go index c45c56bc..eab9c179 100644 --- a/jiracli/edit.go +++ b/jiracli/edit.go @@ -43,15 +43,7 @@ func (jc *JiraCli) CmdEditUsage(cmd *kingpin.CmdClause, opts *EditOptions) error jc.EditorUsage(cmd, &opts.GlobalOptions) jc.TemplateUsage(cmd, &opts.GlobalOptions) cmd.Flag("noedit", "Disable opening the editor").BoolVar(&opts.SkipEditing) - // cmd.Flag("assignee", "User assigned the issue").Short('a').StringVar(&opts.Assignee) - // cmd.Flag("component", "Component to search for").Short('c').StringVar(&opts.Component) - // cmd.Flag("issuetype", "Issue type to search for").Short('i').StringVar(&opts.IssueType) - // cmd.Flag("limit", "Maximum number of results to return in search").Short('l').Default("500").IntVar(&opts.MaxResults) - // cmd.Flag("project", "Project to search for").Short('p').StringVar(&opts.Project) cmd.Flag("query", "Jira Query Language (JQL) expression for the search to edit multiple issues").Short('q').StringVar(&opts.Query) - // cmd.Flag("reporter", "Reporter to search for").Short('r').StringVar(&opts.Reporter) - // cmd.Flag("sort", "Sort order to return").Short('s').Default("priority asc, key").StringVar(&opts.Sort) - // cmd.Flag("watcher", "Watcher to search for").Short('w').StringVar(&opts.Watcher) cmd.Flag("comment", "Comment message for issue").Short('m').PreAction(func(ctx *kingpin.ParseContext) error { opts.Overrides["comment"] = flagValue(ctx, "comment") return nil diff --git a/jiracli/request.go b/jiracli/request.go new file mode 100644 index 00000000..f6a9ab7e --- /dev/null +++ b/jiracli/request.go @@ -0,0 +1,89 @@ +package jiracli + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/url" + "strings" + + "github.com/coryb/oreo" + + kingpin "gopkg.in/alecthomas/kingpin.v2" +) + +type RequestOptions struct { + GlobalOptions + Method string + URI string + Data string +} + +func (jc *JiraCli) CmdRequestRegistry() *CommandRegistryEntry { + opts := RequestOptions{ + GlobalOptions: GlobalOptions{ + Template: "request", + }, + Method: "GET", + } + + return &CommandRegistryEntry{ + "Open issue in requestr", + func() error { + return jc.CmdRequest(&opts) + }, + func(cmd *kingpin.CmdClause) error { + return jc.CmdRequestUsage(cmd, &opts) + }, + } +} + +func (jc *JiraCli) CmdRequestUsage(cmd *kingpin.CmdClause, opts *RequestOptions) error { + if err := jc.GlobalUsage(cmd, &opts.GlobalOptions); err != nil { + return err + } + cmd.Flag("method", "HTTP request method to use").Short('m').EnumVar(&opts.Method, "GET", "PUT", "POST", "DELETE") + cmd.Arg("API", "Path to Jira API (ie: /rest/api/2/issue)").Required().StringVar(&opts.URI) + cmd.Arg("JSON", "JSON Content to send to API").Required().StringVar(&opts.Data) + + return nil +} + +// CmdRequest open the default system requestr to the provided issue +func (jc *JiraCli) CmdRequest(opts *RequestOptions) error { + uri := opts.URI + if !strings.HasPrefix(uri, "http") { + uri = jc.Endpoint + uri + } + + parsedURI, err := url.Parse(uri) + if err != nil { + return err + } + builder := oreo.RequestBuilder(parsedURI).WithMethod(opts.Method) + if opts.Data != "" { + builder = builder.WithJSON(opts.Data) + } + + resp, err := jc.UA.Do(builder.Build()) + if err != nil { + return err + } + defer resp.Body.Close() + + content, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } + if len(content) == 0 { + fmt.Println("No Content") + return nil + } + var data interface{} + err = json.Unmarshal(content, &data) + if err != nil { + return fmt.Errorf("JSON Parse Error: %s from %q", err, content) + } + + return jc.runTemplate(opts.Template, &data, nil) +} diff --git a/jiracli/util.go b/jiracli/util.go index 6fc1eacd..7e866c45 100644 --- a/jiracli/util.go +++ b/jiracli/util.go @@ -67,17 +67,6 @@ func flagValue(ctx *kingpin.ParseContext, name string) string { return "" } -// func readFile(file string) string { -// var bytes []byte -// var err error -// log.Debugf("readFile: reading %q", file) -// if bytes, err = ioutil.ReadFile(file); err != nil { -// log.Errorf("Failed to read file %s: %s", file, err) -// os.Exit(1) -// } -// return string(bytes) -// } - func copyFile(src, dst string) (err error) { var s, d *os.File if s, err = os.Open(src); err == nil { @@ -121,98 +110,6 @@ func dateFormat(format string, content string) (string, error) { return t.Format(format), nil } -// // RunTemplate will run the give templateContent as a golang text/template -// // and pass the provided data to the template execution. It will write -// // the output to the provided "out" writer. -// func RunTemplate(templateContent string, data interface{}, out io.Writer) error { -// return runTemplate(templateContent, data, out) -// } - -// func responseToJSON(resp *http.Response, err error) (interface{}, error) { -// if err != nil { -// return nil, err -// } - -// data := jsonDecode(resp.Body) -// if resp.StatusCode == 400 { -// if val, ok := data.(map[string]interface{})["errorMessages"]; ok { -// for _, errMsg := range val.([]interface{}) { -// log.Errorf("%s", errMsg) -// } -// } -// } - -// return data, nil -// } - -// func jsonDecode(io io.Reader) interface{} { -// content, err := ioutil.ReadAll(io) -// var data interface{} -// err = json.Unmarshal(content, &data) -// if err != nil { -// log.Errorf("JSON Parse Error: %s from %s", err, content) -// } -// return data -// } - -// func jsonEncode(data interface{}) (string, error) { -// buffer := bytes.NewBuffer(make([]byte, 0)) -// enc := json.NewEncoder(buffer) - -// err := enc.Encode(data) -// if err != nil { -// log.Errorf("Failed to encode data %s: %s", data, err) -// return "", err -// } -// return buffer.String(), nil -// } - -// func jsonWrite(file string, data interface{}) { -// fh, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) -// defer fh.Close() -// if err != nil { -// log.Errorf("Failed to open %s: %s", file, err) -// os.Exit(1) -// } -// enc := json.NewEncoder(fh) -// enc.Encode(data) -// } - -// func yamlWrite(file string, data interface{}) { -// fh, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) -// defer fh.Close() -// if err != nil { -// log.Errorf("Failed to open %s: %s", file, err) -// os.Exit(1) -// } -// if out, err := yaml.Marshal(data); err != nil { -// log.Errorf("Failed to marshal yaml %v: %s", data, err) -// os.Exit(1) -// } else { -// fh.Write(out) -// } -// } - -// func promptYN(prompt string, yes bool) bool { -// reader := bufio.NewReader(os.Stdin) -// if !yes { -// prompt = fmt.Sprintf("%s [y/N]: ", prompt) -// } else { -// prompt = fmt.Sprintf("%s [Y/n]: ", prompt) -// } - -// fmt.Printf("%s", prompt) -// text, _ := reader.ReadString('\n') -// ans := strings.ToLower(strings.TrimRight(text, "\n")) -// if ans == "" { -// return yes -// } -// if strings.HasPrefix(ans, "y") { -// return true -// } -// return false -// } - // this is a HACK to make yaml parsed documents to be serializable // to json, so prevent this: // json: unsupported type: map[interface {}]interface {} @@ -283,21 +180,3 @@ func yamlFixup(data interface{}) (interface{}, error) { return d, nil } } - -// func mkdir(dir string) error { -// if stat, err := os.Stat(dir); err != nil && !os.IsNotExist(err) { -// log.Errorf("Failed to stat %s: %s", dir, err) -// return err -// } else if err == nil && !stat.IsDir() { -// err := fmt.Errorf("%s exists and is not a directory", dir) -// log.Errorf("%s", err) -// return err -// } else { -// // dir does not exist, so try to create it -// if err := os.MkdirAll(dir, 0755); err != nil { -// log.Errorf("Failed to mkdir -p %s: %s", dir, err) -// return err -// } -// } -// return nil -// }