Compare commits

..

21 Commits

Author SHA1 Message Date
Cory Bennett abc82b909e version bump 2017-09-15 00:33:12 -07:00
Cory Bennett dabf4cf034 Updated Changelog 2017-09-15 00:33:12 -07:00
Cory Bennett 79a6381307 update usage 2017-09-15 00:31:43 -07:00
Cory Bennett 893454fc69 [#87] add various commands for interacting with epics 2017-09-15 00:28:56 -07:00
Cory Bennett d5b9631cf4 make test less error prone 2017-09-13 13:38:55 -07:00
Cory Bennett e841270b83 version bump 2017-09-13 12:31:16 -07:00
Cory Bennett 2ededeeaf7 Updated Changelog 2017-09-13 12:31:16 -07:00
Cory Bennett 00cba793ad tweaks for templates in named queries to work better 2017-09-13 12:17:59 -07:00
Cory Bennett fb43753c31 [#99] add support for named queries to be stored in configs 2017-09-13 11:12:58 -07:00
Cory Bennett 5085a14494 add license badge 2017-09-13 00:15:09 -07:00
Cory Bennett 5da04c1f86 [#98] add --status option for JQL filter on status with list command 2017-09-13 00:12:20 -07:00
Cory Bennett 052e038d73 version bump 2017-09-11 00:16:10 -07:00
Cory Bennett a7f1323f34 Updated Changelog 2017-09-11 00:16:10 -07:00
Cory Bennett 8d27b736ca track *.lock 2017-09-11 00:16:01 -07:00
Cory Bennett c3c008e53d running dep prune 2017-09-11 00:11:56 -07:00
Cory Bennett 608e586d1c use --gjq for GJson Query to filter json response data, remove --jq option 2017-09-10 22:48:40 -07:00
Cory Bennett 2c552ac530 fix field tag syntax 2017-09-09 19:13:58 -07:00
Cory Bennett 1d269183c3 add --jq option to run a json query against Jira service response json 2017-09-09 19:02:23 -07:00
Cory Bennett e0e1e5b941 use {{jira}} in custom-command docs 2017-09-09 17:44:56 -07:00
Cory Bennett 941824d7f8 add '{{jira}}' template macro to refer to path of currently running jira command 2017-09-09 17:40:26 -07:00
Cory Bennett c585244f3e add basic tests for custom-commands 2017-09-09 17:20:57 -07:00
459 changed files with 5939 additions and 69560 deletions
+6
View File
@@ -3,3 +3,9 @@ config:
password-source: pass
endpoint: https://go-jira.atlassian.net
user: admin
queries:
todo: |
resolution = unresolved {{if .project}}AND project = '{{.project}}'{{end}} AND status = 'To Do'
open: |
resolution = unresolved {{if .project}}AND project = '{{.project}}'{{end}} AND status = 'Open'
+16
View File
@@ -1,5 +1,21 @@
# Changelog
## 1.0.7 - 2017-09-15
* [[#87](https://github.com/Netflix-Skunkworks/go-jira/issues/87)] add various commands for interacting with epics [Cory Bennett] [[893454f](https://github.com/Netflix-Skunkworks/go-jira/commit/893454f)]
## 1.0.6 - 2017-09-13
* tweaks for templates in named queries to work better [Cory Bennett] [[00cba79](https://github.com/Netflix-Skunkworks/go-jira/commit/00cba79)]
* [[#99](https://github.com/Netflix-Skunkworks/go-jira/issues/99)] add support for named queries to be stored in configs [Cory Bennett] [[fb43753](https://github.com/Netflix-Skunkworks/go-jira/commit/fb43753)]
* [[#98](https://github.com/Netflix-Skunkworks/go-jira/issues/98)] add `--status` option for JQL filter on status with `list` command [Cory Bennett] [[5da04c1](https://github.com/Netflix-Skunkworks/go-jira/commit/5da04c1)]
## 1.0.5 - 2017-09-11
* use --gjq for GJson Query to filter json response data [Cory Bennett] [[608e586](https://github.com/Netflix-Skunkworks/go-jira/commit/608e586)]
* fix field tag syntax [Cory Bennett] [[2c552ac](https://github.com/Netflix-Skunkworks/go-jira/commit/2c552ac)]
* add '{{jira}}' template macro to refer to path of currently running jira command [Cory Bennett] [[941824d](https://github.com/Netflix-Skunkworks/go-jira/commit/941824d)]
## 1.0.4 - 2017-09-08
* update deps for kingpeon update use os.exec instead of syscall.exec for windows [Cory Bennett] [[86b963b](https://github.com/Netflix-Skunkworks/go-jira/commit/86b963b)]
Generated
+165
View File
@@ -0,0 +1,165 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
name = "github.com/alecthomas/template"
packages = [".","parse"]
revision = "a0175ee3bccc567396460bf5acd36800cb10c49c"
[[projects]]
branch = "master"
name = "github.com/alecthomas/units"
packages = ["."]
revision = "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a"
[[projects]]
branch = "master"
name = "github.com/cheekybits/genny"
packages = ["generic"]
revision = "9127e812e1e9e501ce899a18121d316ecb52e4ba"
[[projects]]
branch = "master"
name = "github.com/coryb/figtree"
packages = ["."]
revision = "c7d8fbf1d7746b5864b8262fabffec813b5a43fa"
[[projects]]
branch = "master"
name = "github.com/coryb/kingpeon"
packages = ["."]
revision = "64b561ae2d0f895b94719c486bed798f4236a4b3"
[[projects]]
branch = "master"
name = "github.com/coryb/oreo"
packages = ["."]
revision = "95687d61c95ee1522c1140e2af59b0c1846abfc1"
[[projects]]
branch = "master"
name = "github.com/fatih/camelcase"
packages = ["."]
revision = "f6a740d52f961c60348ebb109adde9f4635d7540"
[[projects]]
branch = "master"
name = "github.com/guelfey/go.dbus"
packages = ["."]
revision = "f6a3a2366cc39b8479cadc499d3c735fb10fbdda"
[[projects]]
branch = "master"
name = "github.com/jinzhu/copier"
packages = ["."]
revision = "32e0d0db1dcd4373fb9eb0f9d727b1fe1a723e54"
[[projects]]
branch = "master"
name = "github.com/kballard/go-shellquote"
packages = ["."]
revision = "cd60e84ee657ff3dc51de0b4f55dd299a3e136f2"
[[projects]]
branch = "master"
name = "github.com/mattn/go-colorable"
packages = ["."]
revision = "ad5389df28cdac544c99bd7b9161a0b5b6ca9d1b"
[[projects]]
name = "github.com/mattn/go-isatty"
packages = ["."]
revision = "fc9e8d8ef48496124e79ae0df75490096eccf6fe"
version = "v0.0.2"
[[projects]]
branch = "master"
name = "github.com/mgutz/ansi"
packages = ["."]
revision = "9520e82c474b0a04dd04f8a40959027271bab992"
[[projects]]
branch = "master"
name = "github.com/pkg/browser"
packages = ["."]
revision = "c90ca0c84f15f81c982e32665bffd8d7aac8f097"
[[projects]]
name = "github.com/pkg/errors"
packages = ["."]
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
version = "v0.8.0"
[[projects]]
branch = "master"
name = "github.com/sethgrid/pester"
packages = ["."]
revision = "a86a2d88f4dc3c7dbf3a6a6bbbfb095690b834b6"
[[projects]]
branch = "master"
name = "github.com/theckman/go-flock"
packages = ["."]
revision = "6de226b0d5f040ed85b88c82c381709b98277f3d"
[[projects]]
branch = "master"
name = "github.com/tidwall/gjson"
packages = ["."]
revision = "be96719f990978a867f52c48f29d43f6b591da28"
[[projects]]
branch = "master"
name = "github.com/tidwall/match"
packages = ["."]
revision = "173748da739a410c5b0b813b956f89ff94730b4c"
[[projects]]
branch = "master"
name = "github.com/tmc/keyring"
packages = ["."]
revision = "06e6283d50adc5f8fcdb3cdf33ee1244d4400ae1"
[[projects]]
branch = "master"
name = "golang.org/x/crypto"
packages = ["ssh/terminal"]
revision = "9ba3862cf6a5452ae579de98f9364dd2e544844c"
[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = ["unix","windows"]
revision = "a5054c7c1385fd50d9394475365355a87a7873ec"
[[projects]]
name = "gopkg.in/AlecAivazis/survey.v1"
packages = [".","core","terminal"]
revision = "9d910423e24aa6d7c7950160658c295e0734c7e0"
version = "1.3.1"
[[projects]]
name = "gopkg.in/alecthomas/kingpin.v2"
packages = ["."]
revision = "1087e65c9441605df944fb12c33f0fe7072d18ca"
version = "v2.2.5"
[[projects]]
branch = "v2"
name = "gopkg.in/coryb/yaml.v2"
packages = ["."]
revision = "fb7cb9628c6e3bdd76c29fb91798d51a09832470"
[[projects]]
name = "gopkg.in/op/go-logging.v1"
packages = ["."]
revision = "b2cb9fa56473e98db8caba80237377e83fe44db5"
version = "v1"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "1f4b97fcf898a5ef03af5e222686a09328b393e71797d21bf0c37b74d1e74a8e"
solver-name = "gps-cdcl"
solver-version = 1
+75
View File
@@ -0,0 +1,75 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
[[constraint]]
name = "github.com/coryb/figtree"
[[constraint]]
name = "github.com/coryb/kingpeon"
[[constraint]]
name = "github.com/coryb/oreo"
[[constraint]]
name = "github.com/jinzhu/copier"
[[constraint]]
name = "github.com/kballard/go-shellquote"
[[constraint]]
name = "github.com/mgutz/ansi"
[[constraint]]
name = "github.com/pkg/browser"
[[constraint]]
name = "github.com/pkg/errors"
version = "0.8.0"
[[constraint]]
name = "github.com/savaki/jq"
[[constraint]]
name = "github.com/tmc/keyring"
[[constraint]]
name = "golang.org/x/crypto"
[[constraint]]
name = "gopkg.in/AlecAivazis/survey.v1"
version = "1.3.1"
[[constraint]]
name = "gopkg.in/alecthomas/kingpin.v2"
version = "2.2.5"
[[constraint]]
name = "gopkg.in/coryb/yaml.v2"
[[constraint]]
name = "gopkg.in/op/go-logging.v1"
version = "1.0.0"
[[constraint]]
branch = "master"
name = "github.com/tidwall/gjson"
+1 -1
View File
@@ -50,7 +50,7 @@ NEWVER ?= $(shell echo $(CURVER) | awk -F. '{print $$1"."$$2"."$$3+1}')
TODAY := $(shell date +%Y-%m-%d)
changes:
@git log --pretty=format:"* %s [%cn] [%h]" --no-merges ^v$(CURVER) HEAD *.go jiracli/*.go jiradata/*.go jiracmd/*.go cmd/*/*.go glide.* | grep -vE 'gofmt|go fmt|version bump'
@git log --pretty=format:"* %s [%cn] [%h]" --no-merges ^v$(CURVER) HEAD *.go jiracli/*.go jiradata/*.go jiracmd/*.go cmd/*/*.go *.lock | grep -vE 'gofmt|go fmt|version bump'
update-changelog:
@echo "# Changelog" > CHANGELOG.md.new; \
+69 -13
View File
@@ -1,7 +1,7 @@
[![Join the chat at https://gitter.im/go-jira-cli/help](https://badges.gitter.im/go-jira-cli/help.svg)](https://gitter.im/go-jira-cli/help?utm_source=badge&utm_medium=badge&utm_content=badge)
[![Build Status](https://travis-ci.org/Netflix-Skunkworks/go-jira.svg?branch=master)](https://travis-ci.org/Netflix-Skunkworks/go-jira)
[![GoDoc](https://godoc.org/gopkg.in/Netflix-Skunkworks/go-jira.v1?status.png)](https://godoc.org/gopkg.in/Netflix-Skunkworks/go-jira.v1)
[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
# go-jira
simple command line client for Atlassian's Jira service written in Go
@@ -56,7 +56,7 @@ custom-commands:
- name: mine
help: display issues assigned to me
script: |-
jira list --query "resolution = unresolved and assignee=currentuser() ORDER BY created"
{{jira}} list --query "resolution = unresolved and assignee=currentuser() ORDER BY created"
```
Then the next time you run `jira help` you will see your usage:
```
@@ -273,10 +273,10 @@ custom-commands:
script: |-
if [ -n "$JIRA_PROJECT" ]; then
# if `project: ...` configured just list the issues for current project
jira list --template table --query "resolution = unresolved and assignee=currentuser() and project = $JIRA_PROJECT ORDER BY priority asc, created"
{{jira}} list --template table --query "resolution = unresolved and assignee=currentuser() and project = $JIRA_PROJECT ORDER BY priority asc, created"
else
# otherwise list issues for all project
jira list --template table --query "resolution = unresolved and assignee=currentuser() ORDER BY priority asc, created"
{{jira}} list --template table --query "resolution = unresolved and assignee=currentuser() ORDER BY priority asc, created"
fi
```
* `jira sprint` for listing issues in your current sprint
@@ -287,7 +287,7 @@ custom-commands:
script: |-
if [ -n "$JIRA_PROJECT" ]; then
# if `project: ...` configured just list the issues for current project
jira list --template table --query "sprint in openSprints() and type != epic and resolution = unresolved and project=$JIRA_PROJECT ORDER BY rank asc, created"
{{jira}} list --template table --query "sprint in openSprints() and type != epic and resolution = unresolved and project=$JIRA_PROJECT ORDER BY rank asc, created"
else
# otherwise list issues for all project
echo "\"project: ...\" configuration missing from .jira.d/config.yml"
@@ -429,14 +429,17 @@ Commands:
Prints list of issues for given search criteria
-t, --template=TEMPLATE Template to use for output
--gjq=GJQ GJSON Query to filter output, see https://goo.gl/iaYwJ5
-a, --assignee=ASSIGNEE User assigned the issue
-c, --component=COMPONENT Component to search for
-i, --issuetype=ISSUETYPE Issue type to search for
-l, --limit=LIMIT Maximum number of results to return in search
-p, --project=PROJECT Project to search for
-n, --named-query=NAMED-QUERY The name of a query in the `queries` configuration
-q, --query=QUERY Jira Query Language (JQL) expression for the search
-f, --queryfields=QUERYFIELDS Fields that are used in "list" template
-r, --reporter=REPORTER Reporter to search for
-S, --status=STATUS Filter on issue status
-s, --sort=SORT Sort order to return
-w, --watcher=WATCHER Watcher to search for
@@ -445,6 +448,7 @@ Commands:
-b, --browse Open issue(s) in browser after operation
-t, --template=TEMPLATE Template to use for output
--gjq=GJQ GJSON Query to filter output, see https://goo.gl/iaYwJ5
--expand=EXPAND ... field to expand for the issue
--field=FIELD ... field to return for the issue
--property=PROPERTY ... property to return for issue
@@ -465,13 +469,14 @@ Commands:
edit [<flags>] [<ISSUE>]
Edit issue details
-b, --browse Open issue(s) in browser after operation
--editor=EDITOR Editor to use
-t, --template=TEMPLATE Template to use for output
--noedit Disable opening the editor
-q, --query=QUERY Jira Query Language (JQL) expression for the search to edit multiple issues
-m, --comment=COMMENT Comment message for issue
-o, --override=OVERRIDE ... Set issue property
-b, --browse Open issue(s) in browser after operation
--editor=EDITOR Editor to use
-t, --template=TEMPLATE Template to use for output
--noedit Disable opening the editor
-n, --named-query=NAMED-QUERY The name of a query in the `queries` configuration
-q, --query=QUERY Jira Query Language (JQL) expression for the search to edit multiple issues
-m, --comment=COMMENT Comment message for issue
-o, --override=OVERRIDE ... Set issue property
comment [<flags>] [<ISSUE>]
Add comment to issue
@@ -482,11 +487,51 @@ Commands:
--noedit Disable opening the editor
-m, --comment=COMMENT Comment message for issue
epic create [<flags>]
Create Epic
-b, --browse Open issue(s) in browser after operation
--editor=EDITOR Editor to use
-t, --template=TEMPLATE Template to use for output
--noedit Disable opening the editor
-p, --project=PROJECT project to create epic in
-n, --epic-name=EPIC-NAME Epic Name
-m, --comment=COMMENT Comment message for epic
-o, --override=OVERRIDE ... Set epic property
--saveFile=SAVEFILE Write epic as yaml to file
epic list [<flags>] <EPIC>
Prints list of issues for an epic with optional search criteria
-t, --template=TEMPLATE Template to use for output
--gjq=GJQ GJSON Query to filter output, see https://goo.gl/iaYwJ5
-a, --assignee=ASSIGNEE User assigned the issue
-c, --component=COMPONENT Component to search for
-i, --issuetype=ISSUETYPE Issue type to search for
-l, --limit=LIMIT Maximum number of results to return in search
-p, --project=PROJECT Project to search for
-n, --named-query=NAMED-QUERY The name of a query in the `queries` configuration
-q, --query=QUERY Jira Query Language (JQL) expression for the search
-f, --queryfields=QUERYFIELDS Fields that are used in "list" template
-r, --reporter=REPORTER Reporter to search for
-S, --status=STATUS Filter on issue status
-s, --sort=SORT Sort order to return
-w, --watcher=WATCHER Watcher to search for
epic add <EPIC> <ISSUE>...
Add issues to Epic
epic remove <ISSUE>...
Remove issues from Epic
worklog list [<flags>] <ISSUE>
Prints the worklog data for given issue
-b, --browse Open issue(s) in browser after operation
-t, --template=TEMPLATE Template to use for output
--gjq=GJQ GJSON Query to filter output, see https://goo.gl/iaYwJ5
worklog add [<flags>] <ISSUE>
Add a worklog to an issue
@@ -497,16 +542,19 @@ Commands:
--noedit Disable opening the editor
-m, --comment=COMMENT Comment message for worklog
-T, --time-spent=TIME-SPENT Time spent working on issue
-S, --started=STARTED Time you started work
fields [<flags>]
Prints all fields, both System and Custom
-t, --template=TEMPLATE Template to use for output
--gjq=GJQ GJSON Query to filter output, see https://goo.gl/iaYwJ5
createmeta [<flags>]
View 'create' metadata
-t, --template=TEMPLATE Template to use for output
--gjq=GJQ GJSON Query to filter output, see https://goo.gl/iaYwJ5
-p, --project=PROJECT project to fetch create metadata
-i, --issuetype=ISSUETYPE issuetype in project to fetch create metadata
@@ -515,6 +563,7 @@ Commands:
-b, --browse Open issue(s) in browser after operation
-t, --template=TEMPLATE Template to use for output
--gjq=GJQ GJSON Query to filter output, see https://goo.gl/iaYwJ5
subtask [<flags>] [<ISSUE>]
Subtask issue
@@ -555,6 +604,7 @@ Commands:
Show the issue link types
-t, --template=TEMPLATE Template to use for output
--gjq=GJQ GJSON Query to filter output, see https://goo.gl/iaYwJ5
transition [<flags>] <TRANSITION> <ISSUE>
Transition issue to given state
@@ -570,12 +620,14 @@ Commands:
-b, --browse Open issue(s) in browser after operation
-t, --template=TEMPLATE Template to use for output
--gjq=GJQ GJSON Query to filter output, see https://goo.gl/iaYwJ5
transmeta [<flags>] <ISSUE>
List valid issue transitions
-b, --browse Open issue(s) in browser after operation
-t, --template=TEMPLATE Template to use for output
--gjq=GJQ GJSON Query to filter output, see https://goo.gl/iaYwJ5
close [<flags>] <ISSUE>
Transition issue to close state
@@ -732,12 +784,14 @@ Commands:
Show components for a project
-t, --template=TEMPLATE Template to use for output
--gjq=GJQ GJSON Query to filter output, see https://goo.gl/iaYwJ5
-p, --project=PROJECT project to list components
issuetypes [<flags>]
Show issue types for a project
-t, --template=TEMPLATE Template to use for output
--gjq=GJQ GJSON Query to filter output, see https://goo.gl/iaYwJ5
-p, --project=PROJECT project to list issueTypes
export-templates [<flags>]
@@ -759,5 +813,7 @@ Commands:
request [<flags>] <API> [<JSON>]
Open issue in requestr
-M, --method=METHOD HTTP request method to use
-t, --template=TEMPLATE Template to use for output
--gjq=GJQ GJSON Query to filter output, see https://goo.gl/iaYwJ5
-M, --method=METHOD HTTP request method to use
```
+24 -1
View File
@@ -131,6 +131,24 @@ func main() {
Command: "comment",
Entry: jiracmd.CmdCommentRegistry(),
},
jiracli.CommandRegistry{
Command: "epic create",
Entry: jiracmd.CmdEpicCreateRegistry(),
},
jiracli.CommandRegistry{
Command: "epic list",
Entry: jiracmd.CmdEpicListRegistry(),
Aliases: []string{"ls"},
},
jiracli.CommandRegistry{
Command: "epic add",
Entry: jiracmd.CmdEpicAddRegistry(),
},
jiracli.CommandRegistry{
Command: "epic remove",
Entry: jiracmd.CmdEpicRemoveRegistry(),
Aliases: []string{"rm"},
},
jiracli.CommandRegistry{
Command: "worklog list",
Entry: jiracmd.CmdWorklogListRegistry(),
@@ -338,7 +356,12 @@ func main() {
// checking for default usage of `jira ISSUE-123` but need to allow
// for global options first like: `jira --user mothra ISSUE-123`
ctx, _ := app.ParseContext(os.Args[1:])
ctx, err := app.ParseContext(os.Args[1:])
if err != nil && ctx == nil {
// This is an internal kingpin usage error, duplicate options/commands
log.Fatalf("error: %s, ctx: %v", err, ctx)
}
if ctx != nil {
if ctx.SelectedCommand == nil {
next := ctx.Next()
+113
View File
@@ -0,0 +1,113 @@
package jira
import (
"bytes"
"encoding/json"
"fmt"
"net/url"
"strings"
"github.com/coryb/oreo"
"gopkg.in/Netflix-Skunkworks/go-jira.v1/jiradata"
)
// https://docs.atlassian.com/jira-software/REST/latest/#agile/1.0/epic-getIssuesForEpic
func (j *Jira) EpicSearch(epic string, sp SearchProvider) (*jiradata.SearchResults, error) {
return EpicSearch(j.UA, j.Endpoint, epic, sp)
}
func EpicSearch(ua HttpClient, endpoint string, epic string, sp SearchProvider) (*jiradata.SearchResults, error) {
req := sp.ProvideSearchRequest()
// encoded, err := json.Marshal(req)
// if err != nil {
// return nil, err
// }
uri, err := url.Parse(fmt.Sprintf("%s/rest/agile/1.0/epic/%s/issue", endpoint, epic))
if err != nil {
return nil, err
}
params := url.Values{}
if len(req.Fields) > 0 {
params.Add("fields", strings.Join(req.Fields, ","))
}
if req.JQL != "" {
params.Add("jql", req.JQL)
}
if req.MaxResults != 0 {
params.Add("maxResults", fmt.Sprintf("%d", req.MaxResults))
}
if req.StartAt != 0 {
params.Add("startAt", fmt.Sprintf("%d", req.StartAt))
}
if req.ValidateQuery != "" {
params.Add("validateQuery", req.ValidateQuery)
}
uri.RawQuery = params.Encode()
resp, err := ua.Do(oreo.RequestBuilder(uri).WithHeader("Accept", "application/json").Build())
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode == 200 {
results := &jiradata.SearchResults{}
return results, readJSON(resp.Body, results)
}
return nil, responseError(resp)
}
type EpicIssuesProvider interface {
ProvideEpicIssues() *jiradata.EpicIssues
}
// https://docs.atlassian.com/jira-software/REST/latest/#agile/1.0/epic-moveIssuesToEpic
func (j *Jira) EpicAddIssues(epic string, eip EpicIssuesProvider) error {
return EpicAddIssues(j.UA, j.Endpoint, epic, eip)
}
func EpicAddIssues(ua HttpClient, endpoint string, epic string, eip EpicIssuesProvider) error {
req := eip.ProvideEpicIssues()
encoded, err := json.Marshal(req)
if err != nil {
return err
}
uri := fmt.Sprintf("%s/rest/agile/1.0/epic/%s/issue", endpoint, epic)
resp, err := ua.Post(uri, "application/json", bytes.NewBuffer(encoded))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == 204 {
return nil
}
return responseError(resp)
}
// https://docs.atlassian.com/jira-software/REST/latest/#agile/1.0/epic-removeIssuesFromEpic
func (j *Jira) EpicRemoveIssues(eip EpicIssuesProvider) error {
return EpicRemoveIssues(j.UA, j.Endpoint, eip)
}
func EpicRemoveIssues(ua HttpClient, endpoint string, eip EpicIssuesProvider) error {
req := eip.ProvideEpicIssues()
encoded, err := json.Marshal(req)
if err != nil {
return err
}
uri := fmt.Sprintf("%s/rest/agile/1.0/epic/none/issue", endpoint)
resp, err := ua.Post(uri, "application/json", bytes.NewBuffer(encoded))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode == 204 {
return nil
}
return responseError(resp)
}
Generated
-64
View File
@@ -1,64 +0,0 @@
hash: 4c3ae9c9421b17aae9987ea9566cac7d0a789750bb77c8d235b7be163aec8cae
updated: 2017-09-08T18:47:08.390962401-07:00
imports:
- name: github.com/alecthomas/template
version: a0175ee3bccc567396460bf5acd36800cb10c49c
subpackages:
- parse
- name: github.com/alecthomas/units
version: 2efee857e7cfd4f3d0138cc3cbb1b4966962b93a
- name: github.com/cheekybits/genny
version: 9127e812e1e9e501ce899a18121d316ecb52e4ba
subpackages:
- generic
- name: github.com/coryb/figtree
version: 4429db55820d818320f5af8971ef8401baaf3d21
- name: github.com/coryb/kingpeon
version: 64b561ae2d0f895b94719c486bed798f4236a4b3
- name: github.com/coryb/oreo
version: 95687d61c95ee1522c1140e2af59b0c1846abfc1
- name: github.com/fatih/camelcase
version: f6a740d52f961c60348ebb109adde9f4635d7540
- name: github.com/guelfey/go.dbus
version: f6a3a2366cc39b8479cadc499d3c735fb10fbdda
- name: github.com/jinzhu/copier
version: 32e0d0db1dcd4373fb9eb0f9d727b1fe1a723e54
- name: github.com/kballard/go-shellquote
version: cd60e84ee657ff3dc51de0b4f55dd299a3e136f2
- name: github.com/mattn/go-colorable
version: ad5389df28cdac544c99bd7b9161a0b5b6ca9d1b
- name: github.com/mattn/go-isatty
version: fc9e8d8ef48496124e79ae0df75490096eccf6fe
- name: github.com/mgutz/ansi
version: 9520e82c474b0a04dd04f8a40959027271bab992
- name: github.com/pkg/browser
version: c90ca0c84f15f81c982e32665bffd8d7aac8f097
- name: github.com/pkg/errors
version: 645ef00459ed84a119197bfb8d8205042c6df63d
- name: github.com/sethgrid/pester
version: a86a2d88f4dc3c7dbf3a6a6bbbfb095690b834b6
- name: github.com/theckman/go-flock
version: 6de226b0d5f040ed85b88c82c381709b98277f3d
- name: github.com/tmc/keyring
version: 06e6283d50adc5f8fcdb3cdf33ee1244d4400ae1
- name: golang.org/x/crypto
version: 81e90905daefcd6fd217b62423c0908922eadb30
subpackages:
- ssh/terminal
- name: golang.org/x/sys
version: 5513e650ab47a692d3a036d49be8fa52ddd09b65
subpackages:
- unix
- windows
- name: gopkg.in/AlecAivazis/survey.v1
version: 9d910423e24aa6d7c7950160658c295e0734c7e0
subpackages:
- core
- terminal
- name: gopkg.in/alecthomas/kingpin.v2
version: 1087e65c9441605df944fb12c33f0fe7072d18ca
- name: gopkg.in/coryb/yaml.v2
version: fb7cb9628c6e3bdd76c29fb91798d51a09832470
- name: gopkg.in/op/go-logging.v1
version: b2cb9fa56473e98db8caba80237377e83fe44db5
testImports: []
-22
View File
@@ -1,22 +0,0 @@
package: gopkg.in/Netflix-Skunkworks/go-jira.v1
import:
- package: github.com/coryb/figtree
- package: github.com/coryb/kingpeon
- package: github.com/coryb/oreo
- package: github.com/jinzhu/copier
- package: github.com/kballard/go-shellquote
- package: github.com/mgutz/ansi
- package: github.com/pkg/browser
- package: github.com/pkg/errors
version: ^0.8.0
- package: github.com/tmc/keyring
- package: golang.org/x/crypto
subpackages:
- ssh/terminal
- package: gopkg.in/AlecAivazis/survey.v1
version: ^1.3.1
- package: gopkg.in/alecthomas/kingpin.v2
version: ^2.2.5
- package: gopkg.in/coryb/yaml.v2
- package: gopkg.in/op/go-logging.v1
version: ^1.0.0
+1 -1
View File
@@ -7,7 +7,7 @@ import (
var log = logging.MustGetLogger("jira")
const VERSION = "1.0.4"
const VERSION = "1.0.7"
type Jira struct {
Endpoint string `json:"endpoint,omitempty" yaml:"endpoint,omitempty"`
+18
View File
@@ -16,6 +16,7 @@ import (
"github.com/coryb/oreo"
"github.com/jinzhu/copier"
shellquote "github.com/kballard/go-shellquote"
"github.com/tidwall/gjson"
"gopkg.in/AlecAivazis/survey.v1"
kingpin "gopkg.in/alecthomas/kingpin.v2"
yaml "gopkg.in/coryb/yaml.v2"
@@ -40,6 +41,7 @@ type GlobalOptions struct {
type CommonOptions struct {
Browse figtree.BoolOption `yaml:"browse,omitempty" json:"browse,omitempty"`
Editor figtree.StringOption `yaml:"editor,omitempty" json:"editor,omitempty"`
GJsonQuery figtree.StringOption `yaml:"gjq,omitempty" json:"gjq,omitempty"`
SkipEditing figtree.BoolOption `yaml:"noedit,omitempty" json:"noedit,omitempty"`
Template figtree.StringOption `yaml:"template,omitempty" json:"template,omitempty"`
}
@@ -169,6 +171,22 @@ func TemplateUsage(cmd *kingpin.CmdClause, opts *CommonOptions) {
cmd.Flag("template", "Template to use for output").Short('t').SetValue(&opts.Template)
}
func GJsonQueryUsage(cmd *kingpin.CmdClause, opts *CommonOptions) {
cmd.Flag("gjq", "GJSON Query to filter output, see https://goo.gl/iaYwJ5").SetValue(&opts.GJsonQuery)
}
func (o *CommonOptions) PrintTemplate(data interface{}) error {
if o.GJsonQuery.Value != "" {
buf := bytes.NewBufferString("")
RunTemplate("json", data, buf)
results := gjson.GetBytes(buf.Bytes(), o.GJsonQuery.Value)
_, err := os.Stdout.Write([]byte(results.String()))
os.Stdout.Write([]byte{'\n'})
return err
}
return RunTemplate(o.Template.Value, data, nil)
}
func (o *CommonOptions) editFile(fileName string) (changes bool, err error) {
var editor string
for _, ed := range []string{o.Editor.Value, os.Getenv("JIRA_EDITOR"), os.Getenv("EDITOR"), "vim"} {
+95 -33
View File
@@ -13,9 +13,11 @@ import (
"strings"
"text/template"
yaml "gopkg.in/coryb/yaml.v2"
"github.com/coryb/figtree"
"github.com/mgutz/ansi"
"golang.org/x/crypto/ssh/terminal"
yaml "gopkg.in/coryb/yaml.v2"
)
func findTemplate(name string) ([]byte, error) {
@@ -60,6 +62,9 @@ func tmpTemplate(templateName string, data interface{}) (string, error) {
func TemplateProcessor() *template.Template {
funcs := map[string]interface{}{
"jira": func() string {
return os.Args[0]
},
"toJson": func(content interface{}) (string, error) {
bytes, err := json.MarshalIndent(content, "", " ")
if err != nil {
@@ -154,6 +159,48 @@ func TemplateProcessor() *template.Template {
return template.New("gojira").Funcs(funcs)
}
func ConfigTemplate(fig *figtree.FigTree, template, command string, opts interface{}) (string, error) {
tmp, err := translateOptions(opts)
if err != nil {
return "", err
}
fig.LoadAllConfigs(command+".yml", tmp)
fig.LoadAllConfigs("config.yml", tmp)
tmpl, err := TemplateProcessor().Parse(template)
if err != nil {
return "", err
}
buf := bytes.NewBufferString("")
if err := tmpl.Execute(buf, &tmp); err != nil {
return "", err
}
return buf.String(), nil
}
func translateOptions(opts interface{}) (interface{}, error) {
// HACK HACK HACK: convert data formats to json for backwards compatibilty with templates
jsonData, err := json.Marshal(opts)
if err != nil {
return nil, err
}
defer func(mapType, iface reflect.Type) {
yaml.DefaultMapType = mapType
yaml.IfaceType = iface
}(yaml.DefaultMapType, yaml.IfaceType)
yaml.DefaultMapType = reflect.TypeOf(map[string]interface{}{})
yaml.IfaceType = yaml.DefaultMapType.Elem()
var rawData interface{}
if err := yaml.Unmarshal(jsonData, &rawData); err != nil {
return nil, err
}
return &rawData, nil
}
func RunTemplate(templateName string, data interface{}, out io.Writer) error {
templateContent, err := getTemplate(templateName)
@@ -165,22 +212,9 @@ func RunTemplate(templateName string, data interface{}, out io.Writer) error {
out = os.Stdout
}
// HACK HACK HACK: convert data formats to json for backwards compatibilty with templates
var rawData interface{}
if jsonData, err := json.Marshal(data); err != nil {
rawData, err := translateOptions(data)
if err != nil {
return err
} else {
defer func(mapType, iface reflect.Type) {
yaml.DefaultMapType = mapType
yaml.IfaceType = iface
}(yaml.DefaultMapType, yaml.IfaceType)
yaml.DefaultMapType = reflect.TypeOf(map[string]interface{}{})
yaml.IfaceType = yaml.DefaultMapType.Elem()
if err := yaml.Unmarshal(jsonData, &rawData); err != nil {
return err
}
}
tmpl, err := TemplateProcessor().Parse(templateContent)
@@ -194,25 +228,28 @@ func RunTemplate(templateName string, data interface{}, out io.Writer) error {
}
var AllTemplates = map[string]string{
"component-add": defaultComponentAddTemplate,
"debug": defaultDebugTemplate,
"fields": defaultDebugTemplate,
"editmeta": defaultDebugTemplate,
"transmeta": defaultDebugTemplate,
"createmeta": defaultDebugTemplate,
"issuelinktypes": defaultDebugTemplate,
"list": defaultListTemplate,
"table": defaultTableTemplate,
"view": defaultViewTemplate,
"edit": defaultEditTemplate,
"transitions": defaultTransitionsTemplate,
"components": defaultComponentsTemplate,
"issuetypes": defaultIssuetypesTemplate,
"create": defaultCreateTemplate,
"subtask": defaultSubtaskTemplate,
"comment": defaultCommentTemplate,
"transition": defaultTransitionTemplate,
"component-add": defaultComponentAddTemplate,
"components": defaultComponentsTemplate,
"create": defaultCreateTemplate,
"createmeta": defaultDebugTemplate,
"debug": defaultDebugTemplate,
"edit": defaultEditTemplate,
"editmeta": defaultDebugTemplate,
"epic-create": defaultEpicCreateTemplate,
"epic-list": defaultTableTemplate,
"fields": defaultDebugTemplate,
"issuelinktypes": defaultDebugTemplate,
"issuetypes": defaultIssuetypesTemplate,
"json": defaultDebugTemplate,
"list": defaultListTemplate,
"request": defaultDebugTemplate,
"subtask": defaultSubtaskTemplate,
"table": defaultTableTemplate,
"transition": defaultTransitionTemplate,
"transitions": defaultTransitionsTemplate,
"transmeta": defaultDebugTemplate,
"view": defaultViewTemplate,
"worklog": defaultWorklogTemplate,
"worklogs": defaultWorklogsTemplate,
}
@@ -352,6 +389,31 @@ fields:
- name: {{.}}{{end}}
- name:{{end}}`
const defaultEpicCreateTemplate = `{{/* epic create template */ -}}
fields:
project:
key: {{ or .overrides.project "" }}
# Epic Name
customfield_10120: {{ or (index .overrides "epic-name") "" }}
summary: >-
{{ or .overrides.summary "" }}{{if .meta.fields.priority.allowedValues}}
priority: # Values: {{ range .meta.fields.priority.allowedValues }}{{.name}}, {{end}}
name: {{ or .overrides.priority ""}}{{end}}{{if .meta.fields.components.allowedValues}}
components: # Values: {{ range .meta.fields.components.allowedValues }}{{.name}}, {{end}}{{ range split "," (or .overrides.components "")}}
- name: {{ . }}{{end}}{{end}}
description: |~
{{ or .overrides.description "" | indent 4 }}{{if .meta.fields.assignee}}
assignee:
name: {{ or .overrides.assignee "" }}{{end}}{{if .meta.fields.reporter}}
reporter:
name: {{ or .overrides.reporter .overrides.user }}{{end}}{{if .meta.fields.customfield_10110}}
# watchers
customfield_10110: {{ range split "," (or .overrides.watchers "")}}
- name: {{.}}{{end}}
- name:{{end}}
issuetype:
name: Epic`
const defaultSubtaskTemplate = `{{/* create subtask template */ -}}
fields:
project:
+2 -1
View File
@@ -37,6 +37,7 @@ func CmdComponentsRegistry() *jiracli.CommandRegistryEntry {
func CmdComponentsUsage(cmd *kingpin.CmdClause, opts *ComponentsOptions) error {
jiracli.TemplateUsage(cmd, &opts.CommonOptions)
jiracli.GJsonQueryUsage(cmd, &opts.CommonOptions)
cmd.Flag("project", "project to list components").Short('p').StringVar(&opts.Project)
return nil
@@ -51,5 +52,5 @@ func CmdComponents(o *oreo.Client, globals *jiracli.GlobalOptions, opts *Compone
if err != nil {
return err
}
return jiracli.RunTemplate(opts.Template.Value, data, nil)
return opts.PrintTemplate(data)
}
+2 -1
View File
@@ -35,6 +35,7 @@ func CmdCreateMetaRegistry() *jiracli.CommandRegistryEntry {
func CmdCreateMetaUsage(cmd *kingpin.CmdClause, opts *CreateMetaOptions) error {
jiracli.TemplateUsage(cmd, &opts.CommonOptions)
jiracli.GJsonQueryUsage(cmd, &opts.CommonOptions)
cmd.Flag("project", "project to fetch create metadata").Short('p').StringVar(&opts.Project)
cmd.Flag("issuetype", "issuetype in project to fetch create metadata").Short('i').StringVar(&opts.IssueType)
return nil
@@ -49,5 +50,5 @@ func CmdCreateMeta(o *oreo.Client, globals *jiracli.GlobalOptions, opts *CreateM
if err != nil {
return err
}
return jiracli.RunTemplate(opts.Template.Value, createMeta, nil)
return opts.PrintTemplate(createMeta)
}
+12 -2
View File
@@ -18,6 +18,7 @@ type EditOptions struct {
jira.SearchOptions `yaml:",inline" json:",inline" figtree:",inline"`
Overrides map[string]string `yaml:"overrides,omitempty" json:"overrides,omitempty"`
Issue string `yaml:"issue,omitempty" json:"issue,omitempty"`
Queries map[string]string `yaml:"queries,omitempty" json:"queries,omitempty"`
}
func CmdEditRegistry() *jiracli.CommandRegistryEntry {
@@ -32,7 +33,7 @@ func CmdEditRegistry() *jiracli.CommandRegistryEntry {
"Edit issue details",
func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error {
jiracli.LoadConfigs(cmd, fig, &opts)
return CmdEditUsage(cmd, &opts)
return CmdEditUsage(cmd, &opts, fig)
},
func(o *oreo.Client, globals *jiracli.GlobalOptions) error {
return CmdEdit(o, globals, &opts)
@@ -40,11 +41,20 @@ func CmdEditRegistry() *jiracli.CommandRegistryEntry {
}
}
func CmdEditUsage(cmd *kingpin.CmdClause, opts *EditOptions) error {
func CmdEditUsage(cmd *kingpin.CmdClause, opts *EditOptions, fig *figtree.FigTree) error {
jiracli.BrowseUsage(cmd, &opts.CommonOptions)
jiracli.EditorUsage(cmd, &opts.CommonOptions)
jiracli.TemplateUsage(cmd, &opts.CommonOptions)
cmd.Flag("noedit", "Disable opening the editor").SetValue(&opts.SkipEditing)
cmd.Flag("named-query", "The name of a query in the `queries` configuration").Short('n').PreAction(func(ctx *kingpin.ParseContext) error {
name := jiracli.FlagValue(ctx, "named-query")
if query, ok := opts.Queries[name]; ok && query != "" {
var err error
opts.Query, err = jiracli.ConfigTemplate(fig, query, cmd.FullCommand(), opts)
return err
}
return fmt.Errorf("A valid named-query %q not found in `queries` configuration", name)
}).String()
cmd.Flag("query", "Jira Query Language (JQL) expression for the search to edit multiple issues").Short('q').StringVar(&opts.Query)
cmd.Flag("comment", "Comment message for issue").Short('m').PreAction(func(ctx *kingpin.ParseContext) error {
opts.Overrides["comment"] = jiracli.FlagValue(ctx, "comment")
+2 -1
View File
@@ -36,6 +36,7 @@ func CmdEditMetaRegistry() *jiracli.CommandRegistryEntry {
func CmdEditMetaUsage(cmd *kingpin.CmdClause, opts *EditMetaOptions) error {
jiracli.BrowseUsage(cmd, &opts.CommonOptions)
jiracli.TemplateUsage(cmd, &opts.CommonOptions)
jiracli.GJsonQueryUsage(cmd, &opts.CommonOptions)
cmd.Arg("ISSUE", "edit metadata for issue id").Required().StringVar(&opts.Issue)
return nil
}
@@ -46,7 +47,7 @@ func CmdEditMeta(o *oreo.Client, globals *jiracli.GlobalOptions, opts *EditMetaO
if err != nil {
return err
}
if err := jiracli.RunTemplate(opts.Template.Value, editMeta, nil); err != nil {
if err := opts.PrintTemplate(editMeta); err != nil {
return err
}
if opts.Browse.Value {
+54
View File
@@ -0,0 +1,54 @@
package jiracmd
import (
"fmt"
"github.com/coryb/figtree"
"github.com/coryb/oreo"
"gopkg.in/Netflix-Skunkworks/go-jira.v1"
"gopkg.in/Netflix-Skunkworks/go-jira.v1/jiracli"
"gopkg.in/Netflix-Skunkworks/go-jira.v1/jiradata"
kingpin "gopkg.in/alecthomas/kingpin.v2"
)
type EpicAddOptions struct {
jiradata.EpicIssues `yaml:",inline" json:",inline" figtree:",inline"`
Epic string `yaml:"epic,omitempty" json:"epic,omitempty"`
}
func CmdEpicAddRegistry() *jiracli.CommandRegistryEntry {
opts := EpicAddOptions{}
return &jiracli.CommandRegistryEntry{
"Add issues to Epic",
func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error {
jiracli.LoadConfigs(cmd, fig, &opts)
return CmdEpicAddUsage(cmd, &opts)
},
func(o *oreo.Client, globals *jiracli.GlobalOptions) error {
return CmdEpicAdd(o, globals, &opts)
},
}
}
func CmdEpicAddUsage(cmd *kingpin.CmdClause, opts *EpicAddOptions) error {
cmd.Arg("EPIC", "Epic Key or ID to add issues to").Required().StringVar(&opts.Epic)
cmd.Arg("ISSUE", "Issues to add to epic").Required().StringsVar(&opts.Issues)
return nil
}
func CmdEpicAdd(o *oreo.Client, globals *jiracli.GlobalOptions, opts *EpicAddOptions) error {
if err := jira.EpicAddIssues(o, globals.Endpoint.Value, opts.Epic, &opts.EpicIssues); err != nil {
return err
}
if !globals.Quiet.Value {
fmt.Printf("OK %s %s/browse/%s\n", opts.Epic, globals.Endpoint.Value, opts.Epic)
for _, issue := range opts.Issues {
fmt.Printf("OK %s %s/browse/%s\n", issue, globals.Endpoint.Value, issue)
}
}
return nil
}
+48
View File
@@ -0,0 +1,48 @@
package jiracmd
import (
"github.com/coryb/figtree"
"github.com/coryb/oreo"
"gopkg.in/Netflix-Skunkworks/go-jira.v1/jiracli"
kingpin "gopkg.in/alecthomas/kingpin.v2"
)
func CmdEpicCreateRegistry() *jiracli.CommandRegistryEntry {
opts := CreateOptions{
CommonOptions: jiracli.CommonOptions{
Template: figtree.NewStringOption("epic-create"),
},
Overrides: map[string]string{},
}
return &jiracli.CommandRegistryEntry{
"Create Epic",
func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error {
jiracli.LoadConfigs(cmd, fig, &opts)
return CmdEpicCreateUsage(cmd, &opts)
},
func(o *oreo.Client, globals *jiracli.GlobalOptions) error {
return CmdCreate(o, globals, &opts)
},
}
}
func CmdEpicCreateUsage(cmd *kingpin.CmdClause, opts *CreateOptions) error {
jiracli.BrowseUsage(cmd, &opts.CommonOptions)
jiracli.EditorUsage(cmd, &opts.CommonOptions)
jiracli.TemplateUsage(cmd, &opts.CommonOptions)
cmd.Flag("noedit", "Disable opening the editor").SetValue(&opts.SkipEditing)
cmd.Flag("project", "project to create epic in").Short('p').StringVar(&opts.Project)
cmd.Flag("epic-name", "Epic Name").Short('n').PreAction(func(ctx *kingpin.ParseContext) error {
opts.Overrides["epic-name"] = jiracli.FlagValue(ctx, "epic-name")
return nil
}).String()
cmd.Flag("comment", "Comment message for epic").Short('m').PreAction(func(ctx *kingpin.ParseContext) error {
opts.Overrides["comment"] = jiracli.FlagValue(ctx, "comment")
return nil
}).String()
cmd.Flag("override", "Set epic property").Short('o').StringMapVar(&opts.Overrides)
cmd.Flag("saveFile", "Write epic as yaml to file").StringVar(&opts.SaveFile)
return nil
}
+58
View File
@@ -0,0 +1,58 @@
package jiracmd
import (
"github.com/coryb/figtree"
"github.com/coryb/oreo"
"gopkg.in/Netflix-Skunkworks/go-jira.v1"
"gopkg.in/Netflix-Skunkworks/go-jira.v1/jiracli"
kingpin "gopkg.in/alecthomas/kingpin.v2"
)
type EpicListOptions struct {
ListOptions `yaml:",inline" json:",inline" figtree:",inline"`
Epic string `yaml:"epic,omitempty" json:"epic,omitempty"`
}
func CmdEpicListRegistry() *jiracli.CommandRegistryEntry {
opts := EpicListOptions{
ListOptions: ListOptions{
CommonOptions: jiracli.CommonOptions{
Template: figtree.NewStringOption("epic-list"),
},
},
}
return &jiracli.CommandRegistryEntry{
"Prints list of issues for an epic with optional search criteria",
func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error {
jiracli.LoadConfigs(cmd, fig, &opts)
if opts.MaxResults == 0 {
opts.MaxResults = 500
}
if opts.QueryFields == "" {
opts.QueryFields = "assignee,created,priority,reporter,status,summary,updated"
}
if opts.Sort == "" {
opts.Sort = "priority asc, key"
}
return CmdEpicListUsage(cmd, &opts, fig)
},
func(o *oreo.Client, globals *jiracli.GlobalOptions) error {
return CmdEpicList(o, globals, &opts)
},
}
}
func CmdEpicListUsage(cmd *kingpin.CmdClause, opts *EpicListOptions, fig *figtree.FigTree) error {
CmdListUsage(cmd, &opts.ListOptions, fig)
cmd.Arg("EPIC", "Epic Key or ID to list").Required().StringVar(&opts.Epic)
return nil
}
func CmdEpicList(o *oreo.Client, globals *jiracli.GlobalOptions, opts *EpicListOptions) error {
data, err := jira.EpicSearch(o, globals.Endpoint.Value, opts.Epic, opts)
if err != nil {
return err
}
return opts.PrintTemplate(data)
}
+51
View File
@@ -0,0 +1,51 @@
package jiracmd
import (
"fmt"
"github.com/coryb/figtree"
"github.com/coryb/oreo"
"gopkg.in/Netflix-Skunkworks/go-jira.v1"
"gopkg.in/Netflix-Skunkworks/go-jira.v1/jiracli"
"gopkg.in/Netflix-Skunkworks/go-jira.v1/jiradata"
kingpin "gopkg.in/alecthomas/kingpin.v2"
)
type EpicRemoveOptions struct {
jiradata.EpicIssues `yaml:",inline" json:",inline" figtree:",inline"`
}
func CmdEpicRemoveRegistry() *jiracli.CommandRegistryEntry {
opts := EpicRemoveOptions{}
return &jiracli.CommandRegistryEntry{
"Remove issues from Epic",
func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error {
jiracli.LoadConfigs(cmd, fig, &opts)
return CmdEpicRemoveUsage(cmd, &opts)
},
func(o *oreo.Client, globals *jiracli.GlobalOptions) error {
return CmdEpicRemove(o, globals, &opts)
},
}
}
func CmdEpicRemoveUsage(cmd *kingpin.CmdClause, opts *EpicRemoveOptions) error {
cmd.Arg("ISSUE", "Issues to remove from any epic").Required().StringsVar(&opts.Issues)
return nil
}
func CmdEpicRemove(o *oreo.Client, globals *jiracli.GlobalOptions, opts *EpicRemoveOptions) error {
if err := jira.EpicRemoveIssues(o, globals.Endpoint.Value, &opts.EpicIssues); err != nil {
return err
}
if !globals.Quiet.Value {
for _, issue := range opts.Issues {
fmt.Printf("OK %s %s/browse/%s\n", issue, globals.Endpoint.Value, issue)
}
}
return nil
}
+2 -1
View File
@@ -17,6 +17,7 @@ func CmdFieldsRegistry() *jiracli.CommandRegistryEntry {
func(fig *figtree.FigTree, cmd *kingpin.CmdClause) error {
jiracli.LoadConfigs(cmd, fig, &opts)
jiracli.TemplateUsage(cmd, &opts)
jiracli.GJsonQueryUsage(cmd, &opts)
return nil
},
func(o *oreo.Client, globals *jiracli.GlobalOptions) error {
@@ -31,5 +32,5 @@ func CmdFields(o *oreo.Client, globals *jiracli.GlobalOptions, opts *jiracli.Com
if err != nil {
return err
}
return jiracli.RunTemplate(opts.Template.Value, data, nil)
return opts.PrintTemplate(data)
}
+2 -1
View File
@@ -27,6 +27,7 @@ func CmdIssueLinkTypesRegistry() *jiracli.CommandRegistryEntry {
func CmdIssueLinkTypesUsage(cmd *kingpin.CmdClause, opts *jiracli.CommonOptions) error {
jiracli.TemplateUsage(cmd, opts)
jiracli.GJsonQueryUsage(cmd, opts)
return nil
}
@@ -36,5 +37,5 @@ func CmdIssueLinkTypes(o *oreo.Client, globals *jiracli.GlobalOptions, opts *jir
if err != nil {
return err
}
return jiracli.RunTemplate(opts.Template.Value, data, nil)
return opts.PrintTemplate(data)
}
+2 -1
View File
@@ -37,6 +37,7 @@ func CmdIssueTypesRegistry() *jiracli.CommandRegistryEntry {
func CmdIssueTypesUsage(cmd *kingpin.CmdClause, opts *IssueTypesOptions) error {
jiracli.TemplateUsage(cmd, &opts.CommonOptions)
jiracli.GJsonQueryUsage(cmd, &opts.CommonOptions)
cmd.Flag("project", "project to list issueTypes").Short('p').StringVar(&opts.Project)
return nil
@@ -51,5 +52,5 @@ func CmdIssueTypes(o *oreo.Client, globals *jiracli.GlobalOptions, opts *IssueTy
if err != nil {
return err
}
return jiracli.RunTemplate(opts.Template.Value, data, nil)
return opts.PrintTemplate(data)
}
+17 -3
View File
@@ -1,6 +1,8 @@
package jiracmd
import (
"fmt"
"github.com/coryb/figtree"
"github.com/coryb/oreo"
"gopkg.in/Netflix-Skunkworks/go-jira.v1"
@@ -11,6 +13,7 @@ import (
type ListOptions struct {
jiracli.CommonOptions `yaml:",inline" json:",inline" figtree:",inline"`
jira.SearchOptions `yaml:",inline" json:",inline" figtree:",inline"`
Queries map[string]string `yaml:"queries,omitempty" json:"queries,omitempty"`
}
func CmdListRegistry() *jiracli.CommandRegistryEntry {
@@ -33,7 +36,7 @@ func CmdListRegistry() *jiracli.CommandRegistryEntry {
if opts.Sort == "" {
opts.Sort = "priority asc, key"
}
return CmdListUsage(cmd, &opts)
return CmdListUsage(cmd, &opts, fig)
},
func(o *oreo.Client, globals *jiracli.GlobalOptions) error {
return CmdList(o, globals, &opts)
@@ -41,16 +44,27 @@ func CmdListRegistry() *jiracli.CommandRegistryEntry {
}
}
func CmdListUsage(cmd *kingpin.CmdClause, opts *ListOptions) error {
func CmdListUsage(cmd *kingpin.CmdClause, opts *ListOptions, fig *figtree.FigTree) error {
jiracli.TemplateUsage(cmd, &opts.CommonOptions)
jiracli.GJsonQueryUsage(cmd, &opts.CommonOptions)
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').IntVar(&opts.MaxResults)
cmd.Flag("project", "Project to search for").Short('p').StringVar(&opts.Project)
cmd.Flag("named-query", "The name of a query in the `queries` configuration").Short('n').PreAction(func(ctx *kingpin.ParseContext) error {
name := jiracli.FlagValue(ctx, "named-query")
if query, ok := opts.Queries[name]; ok && query != "" {
var err error
opts.Query, err = jiracli.ConfigTemplate(fig, query, cmd.FullCommand(), opts)
return err
}
return fmt.Errorf("A valid named-query %q not found in `queries` configuration", name)
}).String()
cmd.Flag("query", "Jira Query Language (JQL) expression for the search").Short('q').StringVar(&opts.Query)
cmd.Flag("queryfields", "Fields that are used in \"list\" template").Short('f').StringVar(&opts.QueryFields)
cmd.Flag("reporter", "Reporter to search for").Short('r').StringVar(&opts.Reporter)
cmd.Flag("status", "Filter on issue status").Short('S').StringVar(&opts.Status)
cmd.Flag("sort", "Sort order to return").Short('s').StringVar(&opts.Sort)
cmd.Flag("watcher", "Watcher to search for").Short('w').StringVar(&opts.Watcher)
return nil
@@ -62,5 +76,5 @@ func CmdList(o *oreo.Client, globals *jiracli.GlobalOptions, opts *ListOptions)
if err != nil {
return err
}
return jiracli.RunTemplate(opts.Template.Value, data, nil)
return opts.PrintTemplate(data)
}
+3 -1
View File
@@ -35,6 +35,8 @@ func CmdRequestRegistry() *jiracli.CommandRegistryEntry {
if opts.Method == "" {
opts.Method = "GET"
}
jiracli.TemplateUsage(cmd, &opts.CommonOptions)
jiracli.GJsonQueryUsage(cmd, &opts.CommonOptions)
return CmdRequestUsage(cmd, &opts)
},
func(o *oreo.Client, globals *jiracli.GlobalOptions) error {
@@ -89,5 +91,5 @@ func CmdRequest(o *oreo.Client, globals *jiracli.GlobalOptions, opts *RequestOpt
return fmt.Errorf("JSON Parse Error: %s from %q", err, content)
}
return jiracli.RunTemplate(opts.Template.Value, &data, nil)
return opts.PrintTemplate(&data)
}
+2 -1
View File
@@ -35,6 +35,7 @@ func CmdTransitionsRegistry(defaultTemplate string) *jiracli.CommandRegistryEntr
func CmdTransitionsUsage(cmd *kingpin.CmdClause, opts *TransitionsOptions) error {
jiracli.BrowseUsage(cmd, &opts.CommonOptions)
jiracli.TemplateUsage(cmd, &opts.CommonOptions)
jiracli.GJsonQueryUsage(cmd, &opts.CommonOptions)
cmd.Arg("ISSUE", "issue to list valid transitions").Required().StringVar(&opts.Issue)
return nil
}
@@ -45,7 +46,7 @@ func CmdTransitions(o *oreo.Client, globals *jiracli.GlobalOptions, opts *Transi
if err != nil {
return err
}
if err := jiracli.RunTemplate(opts.Template.Value, editMeta, nil); err != nil {
if err := opts.PrintTemplate(editMeta); err != nil {
return err
}
if opts.Browse.Value {
+2 -1
View File
@@ -36,6 +36,7 @@ func CmdViewRegistry() *jiracli.CommandRegistryEntry {
func CmdViewUsage(cmd *kingpin.CmdClause, opts *ViewOptions) error {
jiracli.BrowseUsage(cmd, &opts.CommonOptions)
jiracli.TemplateUsage(cmd, &opts.CommonOptions)
jiracli.GJsonQueryUsage(cmd, &opts.CommonOptions)
cmd.Flag("expand", "field to expand for the issue").StringsVar(&opts.Expand)
cmd.Flag("field", "field to return for the issue").StringsVar(&opts.Fields)
cmd.Flag("property", "property to return for issue").StringsVar(&opts.Properties)
@@ -49,7 +50,7 @@ func CmdView(o *oreo.Client, globals *jiracli.GlobalOptions, opts *ViewOptions)
if err != nil {
return err
}
if err := jiracli.RunTemplate(opts.Template.Value, data, nil); err != nil {
if err := opts.PrintTemplate(data); err != nil {
return err
}
if opts.Browse.Value {
+3 -2
View File
@@ -35,6 +35,7 @@ func CmdWorklogListRegistry() *jiracli.CommandRegistryEntry {
func CmdWorklogListUsage(cmd *kingpin.CmdClause, opts *WorklogListOptions) error {
jiracli.BrowseUsage(cmd, &opts.CommonOptions)
jiracli.TemplateUsage(cmd, &opts.CommonOptions)
jiracli.GJsonQueryUsage(cmd, &opts.CommonOptions)
cmd.Arg("ISSUE", "issue id to fetch worklogs").Required().StringVar(&opts.Issue)
return nil
}
@@ -45,9 +46,9 @@ func CmdWorklogList(o *oreo.Client, globals *jiracli.GlobalOptions, opts *Worklo
if err != nil {
return err
}
if err := jiracli.RunTemplate(opts.Template.Value, struct {
if err := opts.PrintTemplate(struct {
Worklogs *jiradata.Worklogs `json:"worklogs,omitempty" yaml:"worklogs,omitempty"`
}{data}, nil); err != nil {
}{data}); err != nil {
return err
}
if opts.Browse.Value {
+5
View File
@@ -0,0 +1,5 @@
package jiradata
type EpicIssues struct {
Issues []string `json:"issues,omitempty" yaml:"issues,omitempty"`
}
+4
View File
@@ -25,3 +25,7 @@ func (c *Comment) ProvideComment() *Comment {
func (c *Component) ProvideComponent() *Component {
return c
}
func (e *EpicIssues) ProvideEpicIssues() *EpicIssues {
return e
}
+14 -10
View File
@@ -14,16 +14,17 @@ type SearchProvider interface {
}
type SearchOptions struct {
Assignee string
Query string
QueryFields string
Project string
Component string
IssueType string
Watcher string
Reporter string
Sort string
MaxResults int
Assignee string `yaml:"assignee,omitempty" json:"assignee,omitempty"`
Query string `yaml:"query,omitempty" json:"query,omitempty"`
QueryFields string `yaml:"query-fields,omitempty" json:"query-fields,omitempty"`
Project string `yaml:"project,omitempty" json:"project,omitempty"`
Component string `yaml:"component,omitempty" json:"component,omitempty"`
IssueType string `yaml:"issue-type,omitempty" json:"issue-type,omitempty"`
Watcher string `yaml:"watcher,omitempty" json:"watcher,omitempty"`
Reporter string `yaml:"reporter,omitempty" json:"reporter,omitempty"`
Status string `yaml:"status,omitempty" json:"status,omitempty"`
Sort string `yaml:"sort,omitempty" json:"sort,omitempty"`
MaxResults int `yaml:"max-results,omitempty" json:"max-results,omitempty"`
}
func (o *SearchOptions) ProvideSearchRequest() *jiradata.SearchRequest {
@@ -49,6 +50,9 @@ func (o *SearchOptions) ProvideSearchRequest() *jiradata.SearchRequest {
if o.Reporter != "" {
qbuff.WriteString(fmt.Sprintf(" AND reporter = '%s'", o.Reporter))
}
if o.Status != "" {
qbuff.WriteString(fmt.Sprintf(" AND status = '%s'", o.Status))
}
if o.Sort != "" {
qbuff.WriteString(fmt.Sprintf(" ORDER BY %s", o.Sort))
}
+39
View File
@@ -3,3 +3,42 @@ config:
password-source: pass
endpoint: https://go-jira.atlassian.net
user: gojira
project: BASIC
custom-commands:
- name: env
help: print the JIRA environment variables available to custom commands
script: |-
env | sort | grep JIRA
- name: print-project
help: print the name of the configured project
script: "echo $JIRA_PROJECT"
- name: jira-path
help: print the path the jira command that is running this alias
script: |-
echo {{jira}}
- name: mine
help: display issues assigned to me
script: |-
if [ -n "$JIRA_PROJECT" ]; then
# if `project: ...` configured just list the issues for current project
{{jira}} list --template table --query "resolution = unresolved and assignee=currentuser() and project = $JIRA_PROJECT ORDER BY priority asc, created"
else
# otherwise list issues for all project
{{jira}} list --template table --query "resolution = unresolved and assignee=currentuser() ORDER BY priority asc, created"
fi
- name: argtest
help: testing passing args
script: |-
echo {{args.ARG}}
args:
- name: ARG
help: string to echo for testing
- name: opttest
help: testing passing option flags
script: |-
echo {{options.OPT}}
options:
- name: OPT
help: string to echo for testing
+80
View File
@@ -0,0 +1,80 @@
#!/bin/bash
eval "$(curl -q -s https://raw.githubusercontent.com/coryb/osht/master/osht.sh)"
cd $(dirname $0)
jira="../jira"
. env.sh
PLAN 16
# reset login
RUNS $jira logout
RUNS $jira login
# cleanup from previous failed test executions
($jira ls --project BASIC | awk -F: '{print $1}' | while read issue; do ../jira done $issue; done) | sed 's/^/# CLEANUP: /g'
###############################################################################
## Create an issue
###############################################################################
RUNS $jira create --project BASIC -o summary=summary -o description=description --noedit --saveFile issue.props
issue=$(awk '/issue/{print $2}' issue.props)
DIFF <<EOF
OK $issue $ENDPOINT/browse/$issue
EOF
###############################################################################
## Testing the example custom commands, print-project
###############################################################################
RUNS $jira print-project
DIFF <<EOF
BASIC
EOF
###############################################################################
## Testing the example custom commands, jira-path
###############################################################################
RUNS $jira jira-path
DIFF <<EOF
../jira
EOF
###############################################################################
## Testing the example custom commands, env
###############################################################################
RUNS $jira env
GREP ^JIRA_PROJECT=BASIC
###############################################################################
## Testing the example custom commands, argtest
###############################################################################
RUNS $jira argtest TEST
DIFF <<EOF
TEST
EOF
###############################################################################
## Testing the example custom commands, opttest
###############################################################################
RUNS $jira opttest --OPT TEST
DIFF <<EOF
TEST
EOF
###############################################################################
## Use the "mine" alias to list issues assigned to self
###############################################################################
RUNS $jira mine
DIFF <<EOF
+----------------+---------------------------------------------------------+--------------+--------------+------------+--------------+--------------+
| Issue | Summary | Priority | Status | Age | Reporter | Assignee |
+----------------+---------------------------------------------------------+--------------+--------------+------------+--------------+--------------+
| $(printf %-14s $issue) | summary | Medium | To Do | a minute | gojira | gojira |
+----------------+---------------------------------------------------------+--------------+--------------+------------+--------------+--------------+
EOF
+121
View File
@@ -0,0 +1,121 @@
#!/bin/bash
eval "$(curl -q -s https://raw.githubusercontent.com/coryb/osht/master/osht.sh)"
cd $(dirname $0)
jira="../jira"
. env.sh
PLAN 22
# reset login
RUNS $jira logout
RUNS $jira login
# cleanup from previous failed test executions
($jira ls --project BASIC | awk -F: '{print $1}' | while read issue; do ../jira done $issue; done) | sed 's/^/# CLEANUP: /g'
###############################################################################
## Create an epic
###############################################################################
RUNS $jira epic create --project BASIC -o summary="Totally Epic" -o description=description --epic-name "Basic Epic" --noedit --saveFile issue.props
epic=$(awk '/issue/{print $2}' issue.props)
DIFF <<EOF
OK $epic $ENDPOINT/browse/$epic
EOF
###############################################################################
## Create issues we can assign to epic
###############################################################################
RUNS $jira create --project BASIC -o summary="summary" -o description=description --noedit --saveFile issue.props
issue1=$(awk '/issue/{print $2}' issue.props)
DIFF <<EOF
OK $issue1 $ENDPOINT/browse/$issue1
EOF
RUNS $jira create --project BASIC -o summary="summary" -o description=description --noedit --saveFile issue.props
issue2=$(awk '/issue/{print $2}' issue.props)
DIFF <<EOF
OK $issue2 $ENDPOINT/browse/$issue2
EOF
###############################################################################
## List the issues for the epic
###############################################################################
RUNS $jira epic list $epic
DIFF<<EOF
+----------------+---------------------------------------------------------+--------------+--------------+------------+--------------+--------------+
| Issue | Summary | Priority | Status | Age | Reporter | Assignee |
+----------------+---------------------------------------------------------+--------------+--------------+------------+--------------+--------------+
+----------------+---------------------------------------------------------+--------------+--------------+------------+--------------+--------------+
EOF
###############################################################################
## Add issues to an epic
###############################################################################
RUNS $jira epic add $epic $issue1 $issue2
DIFF<<EOF
OK $epic $ENDPOINT/browse/$epic
OK $issue1 $ENDPOINT/browse/$issue1
OK $issue2 $ENDPOINT/browse/$issue2
EOF
###############################################################################
## List the issues for the epic
###############################################################################
RUNS $jira epic list $epic
DIFF<<EOF
+----------------+---------------------------------------------------------+--------------+--------------+------------+--------------+--------------+
| Issue | Summary | Priority | Status | Age | Reporter | Assignee |
+----------------+---------------------------------------------------------+--------------+--------------+------------+--------------+--------------+
| $(printf %-14s $issue1) | summary | Medium | To Do | a minute | gojira | gojira |
| $(printf %-14s $issue2) | summary | Medium | To Do | a minute | gojira | gojira |
+----------------+---------------------------------------------------------+--------------+--------------+------------+--------------+--------------+
EOF
###############################################################################
## Remove an issue from an Epic
###############################################################################
RUNS $jira epic remove $issue1
DIFF<<EOF
OK $issue1 $ENDPOINT/browse/$issue1
EOF
###############################################################################
## List the issues for the epic
###############################################################################
RUNS $jira epic list $epic
DIFF<<EOF
+----------------+---------------------------------------------------------+--------------+--------------+------------+--------------+--------------+
| Issue | Summary | Priority | Status | Age | Reporter | Assignee |
+----------------+---------------------------------------------------------+--------------+--------------+------------+--------------+--------------+
| $(printf %-14s $issue2) | summary | Medium | To Do | a minute | gojira | gojira |
+----------------+---------------------------------------------------------+--------------+--------------+------------+--------------+--------------+
EOF
###############################################################################
## Remove last issue from an Epic
###############################################################################
RUNS $jira epic remove $issue2
DIFF<<EOF
OK $issue2 $ENDPOINT/browse/$issue2
EOF
###############################################################################
## List the issues for the epic
###############################################################################
RUNS $jira epic list $epic
DIFF<<EOF
+----------------+---------------------------------------------------------+--------------+--------------+------------+--------------+--------------+
| Issue | Summary | Priority | Status | Age | Reporter | Assignee |
+----------------+---------------------------------------------------------+--------------+--------------+------------+--------------+--------------+
+----------------+---------------------------------------------------------+--------------+--------------+------------+--------------+--------------+
EOF
-2
View File
@@ -1,2 +0,0 @@
{{define "x"}}TEXT{{end}}
{{define "dotV"}}{{.V}}{{end}}
-2
View File
@@ -1,2 +0,0 @@
{{define "dot"}}{{.}}{{end}}
{{define "nested"}}{{template "dot" .}}{{end}}
-3
View File
@@ -1,3 +0,0 @@
template1
{{define "x"}}x{{end}}
{{template "y"}}
-3
View File
@@ -1,3 +0,0 @@
template2
{{define "y"}}y{{end}}
{{template "x"}}
-12
View File
@@ -1,12 +0,0 @@
package math
import "github.com/cheekybits/genny/generic"
type ThisNumberType generic.Number
func ThisNumberTypeMax(fn func(a, b ThisNumberType) bool, a, b ThisNumberType) ThisNumberType {
if fn(a, b) {
return a
}
return b
}
-3
View File
@@ -1,3 +0,0 @@
#!/bin/bash
cat ./generic_max.go | ../../genny gen "NumberType=NUMBERS" > numbers_max_get.go
cat ./func_thing.go | ../../genny gen "ThisNumberType=NUMBERS" > numbers_func_thing.go
-14
View File
@@ -1,14 +0,0 @@
package math
import "github.com/cheekybits/genny/generic"
type NumberType generic.Number
// NumberTypeMax gets the maximum number from the
// two specified.
func NumberTypeMax(a, b NumberType) NumberType {
if a > b {
return a
}
return b
}
-27
View File
@@ -1,27 +0,0 @@
package math_test
import (
"testing"
"github.com/cheekybits/genny/examples/davechaney"
)
func TestNumberTypeMax(t *testing.T) {
var v math.NumberType
v = math.NumberTypeMax(10, 20)
if v != 20 {
t.Errorf("Max of 10 and 20 is 20")
}
v = math.NumberTypeMax(20, 20)
if v != 20 {
t.Errorf("Max of 20 and 20 is 20")
}
v = math.NumberTypeMax(25, 20)
if v != 25 {
t.Errorf("Max of 25 and 20 is 25")
}
}
@@ -1,89 +0,0 @@
// This file was automatically generated by genny.
// Any changes will be lost if this file is regenerated.
// see https://github.com/cheekybits/genny
package math
func Float32Max(fn func(a, b float32) bool, a, b float32) float32 {
if fn(a, b) {
return a
}
return b
}
func Float64Max(fn func(a, b float64) bool, a, b float64) float64 {
if fn(a, b) {
return a
}
return b
}
func IntMax(fn func(a, b int) bool, a, b int) int {
if fn(a, b) {
return a
}
return b
}
func Int16Max(fn func(a, b int16) bool, a, b int16) int16 {
if fn(a, b) {
return a
}
return b
}
func Int32Max(fn func(a, b int32) bool, a, b int32) int32 {
if fn(a, b) {
return a
}
return b
}
func Int64Max(fn func(a, b int64) bool, a, b int64) int64 {
if fn(a, b) {
return a
}
return b
}
func Int8Max(fn func(a, b int8) bool, a, b int8) int8 {
if fn(a, b) {
return a
}
return b
}
func UintMax(fn func(a, b uint) bool, a, b uint) uint {
if fn(a, b) {
return a
}
return b
}
func Uint16Max(fn func(a, b uint16) bool, a, b uint16) uint16 {
if fn(a, b) {
return a
}
return b
}
func Uint32Max(fn func(a, b uint32) bool, a, b uint32) uint32 {
if fn(a, b) {
return a
}
return b
}
func Uint64Max(fn func(a, b uint64) bool, a, b uint64) uint64 {
if fn(a, b) {
return a
}
return b
}
func Uint8Max(fn func(a, b uint8) bool, a, b uint8) uint8 {
if fn(a, b) {
return a
}
return b
}
@@ -1,29 +0,0 @@
// This file was automatically generated by genny.
// Any changes will be lost if this file is regenerated.
// see https://github.com/cheekybits/genny
package gogenerate
type StringStringMap map[string]string
func NewStringStringMap() map[string]string {
return make(map[string]string)
}
type StringIntMap map[string]int
func NewStringIntMap() map[string]int {
return make(map[string]int)
}
type IntStringMap map[int]string
func NewIntStringMap() map[int]string {
return make(map[int]string)
}
type IntIntMap map[int]int
func NewIntIntMap() map[int]int {
return make(map[int]int)
}
-14
View File
@@ -1,14 +0,0 @@
package gogenerate
import "github.com/cheekybits/genny/generic"
//go:generate genny -in=$GOFILE -out=gen-$GOFILE gen "KeyType=string,int ValueType=string,int"
type KeyType generic.Type
type ValueType generic.Type
type KeyTypeValueTypeMap map[KeyType]ValueType
func NewKeyTypeValueTypeMap() map[KeyType]ValueType {
return make(map[KeyType]ValueType)
}
-2
View File
@@ -1,2 +0,0 @@
#!/bin/bash
cat ./queue_generic.go | ../../genny gen "Generic=string,int" > queue_generic_gen.go
-33
View File
@@ -1,33 +0,0 @@
package example
import "github.com/cheekybits/genny/generic"
type Generic generic.Type
// GenericQueue represents a queue of Generic types.
type GenericQueue struct {
items []Generic
}
// NewGenericQueue makes a new empty Generic queue.
func NewGenericQueue() *GenericQueue {
return &GenericQueue{items: make([]Generic, 0)}
}
// Enq adds an item to the queue.
func (q *GenericQueue) Enq(obj Generic) *GenericQueue {
q.items = append(q.items, obj)
return q
}
// Deq removes and returns the next item in the queue.
func (q *GenericQueue) Deq() Generic {
obj := q.items[0]
q.items = q.items[1:]
return obj
}
// Len gets the current number of Generic items in the queue.
func (q *GenericQueue) Len() int {
return len(q.items)
}
@@ -1,32 +0,0 @@
package example
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestNew(t *testing.T) {
q := NewGenericQueue()
assert.NotNil(t, q)
}
func TestEnqueueAndDequeue(t *testing.T) {
item1 := new(Generic)
item2 := new(Generic)
q := NewGenericQueue()
assert.Equal(t, q, q.Enq(item1), "Enq should return the queue")
assert.Equal(t, 1, q.Len())
assert.Equal(t, q, q.Enq(item2), "Enq should return the queue")
assert.Equal(t, 2, q.Len())
assert.Equal(t, item1, q.Deq())
assert.Equal(t, 1, q.Len())
assert.Equal(t, item2, q.Deq())
assert.Equal(t, 0, q.Len())
}
-41
View File
@@ -1,41 +0,0 @@
package parse
// Builtins contains a slice of all built-in Go types.
var Builtins = []string{
"bool",
"byte",
"complex128",
"complex64",
"error",
"float32",
"float64",
"int",
"int16",
"int32",
"int64",
"int8",
"rune",
"string",
"uint",
"uint16",
"uint32",
"uint64",
"uint8",
"uintptr",
}
// Numbers contains a slice of all built-in number types.
var Numbers = []string{
"float32",
"float64",
"int",
"int16",
"int32",
"int64",
"int8",
"uint",
"uint16",
"uint32",
"uint64",
"uint8",
}
-14
View File
@@ -1,14 +0,0 @@
// Package parse contains the generic code generation capabilities
// that power genny.
//
// genny gen "{types}"
//
// gen - generates type specific code (to stdout) from generic code (via stdin)
//
// {types} - (required) Specific types for each generic type in the source
// {types} format: {generic}={specific}[,another][ {generic2}={specific2}]
// Examples:
// Generic=Specific
// Generic1=Specific1 Generic2=Specific2
// Generic1=Specific1,Specific2 Generic2=Specific3,Specific4
package parse
-47
View File
@@ -1,47 +0,0 @@
package parse
import (
"errors"
)
// errMissingSpecificType represents an error when a generic type is not
// satisfied by a specific type.
type errMissingSpecificType struct {
GenericType string
}
// Error gets a human readable string describing this error.
func (e errMissingSpecificType) Error() string {
return "Missing specific type for '" + e.GenericType + "' generic type"
}
// errImports represents an error from goimports.
type errImports struct {
Err error
}
// Error gets a human readable string describing this error.
func (e errImports) Error() string {
return "Failed to goimports the generated code: " + e.Err.Error()
}
// errSource represents an error with the source file.
type errSource struct {
Err error
}
// Error gets a human readable string describing this error.
func (e errSource) Error() string {
return "Failed to parse source file: " + e.Err.Error()
}
type errBadTypeArgs struct {
Message string
Arg string
}
func (e errBadTypeArgs) Error() string {
return "\"" + e.Arg + "\" is bad: " + e.Message
}
var errMissingTypeInformation = errors.New("No type arguments were specified and no \"// +gogen\" tag was found in the source.")
-271
View File
@@ -1,271 +0,0 @@
package parse
import (
"bufio"
"bytes"
"fmt"
"go/ast"
"go/parser"
"go/token"
"io"
"os"
"strings"
"unicode"
"golang.org/x/tools/imports"
)
type isExported bool
var header = []byte(`
// This file was automatically generated by genny.
// Any changes will be lost if this file is regenerated.
// see https://github.com/cheekybits/genny
`)
var (
packageKeyword = []byte("package")
importKeyword = []byte("import")
openBrace = []byte("(")
closeBrace = []byte(")")
space = " "
genericPackage = "generic"
genericType = "generic.Type"
genericNumber = "generic.Number"
linefeed = "\r\n"
)
var unwantedLinePrefixes = [][]byte{
[]byte("//go:generate genny "),
}
func generateSpecific(filename string, in io.ReadSeeker, typeSet map[string]string) ([]byte, error) {
// ensure we are at the beginning of the file
in.Seek(0, os.SEEK_SET)
// parse the source file
fs := token.NewFileSet()
file, err := parser.ParseFile(fs, filename, in, 0)
if err != nil {
return nil, &errSource{Err: err}
}
// make sure every generic.Type is represented in the types
// argument.
for _, decl := range file.Decls {
switch it := decl.(type) {
case *ast.GenDecl:
for _, spec := range it.Specs {
ts, ok := spec.(*ast.TypeSpec)
if !ok {
continue
}
switch tt := ts.Type.(type) {
case *ast.SelectorExpr:
if name, ok := tt.X.(*ast.Ident); ok {
if name.Name == genericPackage {
if _, ok := typeSet[ts.Name.Name]; !ok {
return nil, &errMissingSpecificType{GenericType: ts.Name.Name}
}
}
}
}
}
}
}
// go back to the start of the file
in.Seek(0, os.SEEK_SET)
var buf bytes.Buffer
comment := ""
scanner := bufio.NewScanner(in)
for scanner.Scan() {
l := scanner.Text()
// does this line contain generic.Type?
if strings.Contains(l, genericType) || strings.Contains(l, genericNumber) {
comment = ""
continue
}
for t, specificType := range typeSet {
// does the line contain our type
if strings.Contains(l, t) {
var newLine string
// check each word
for _, word := range strings.Fields(l) {
i := 0
for {
i = strings.Index(word[i:], t) // find out where
if i > -1 {
// if this isn't an exact match
if i > 0 && isAlphaNumeric(rune(word[i-1])) || i < len(word)-len(t) && isAlphaNumeric(rune(word[i+len(t)])) {
// replace the word with a capitolized version
word = strings.Replace(word, t, wordify(specificType, unicode.IsUpper(rune(strings.TrimLeft(word, "*&")[0]))), 1)
} else {
// replace the word as is
word = strings.Replace(word, t, specificType, 1)
}
} else {
newLine = newLine + word + space
break
}
}
}
l = newLine
}
}
if comment != "" {
buf.WriteString(line(comment))
comment = ""
}
// is this line a comment?
// TODO: should we handle /* */ comments?
if strings.HasPrefix(l, "//") {
// record this line to print later
comment = l
continue
}
// write the line
buf.WriteString(line(l))
}
// write it out
return buf.Bytes(), nil
}
// Generics parses the source file and generates the bytes replacing the
// generic types for the keys map with the specific types (its value).
func Generics(filename, pkgName string, in io.ReadSeeker, typeSets []map[string]string) ([]byte, error) {
totalOutput := header
for _, typeSet := range typeSets {
// generate the specifics
parsed, err := generateSpecific(filename, in, typeSet)
if err != nil {
return nil, err
}
totalOutput = append(totalOutput, parsed...)
}
// clean up the code line by line
packageFound := false
insideImportBlock := false
var cleanOutputLines []string
scanner := bufio.NewScanner(bytes.NewReader(totalOutput))
for scanner.Scan() {
// end of imports block?
if insideImportBlock {
if bytes.HasSuffix(scanner.Bytes(), closeBrace) {
insideImportBlock = false
}
continue
}
if bytes.HasPrefix(scanner.Bytes(), packageKeyword) {
if packageFound {
continue
} else {
packageFound = true
}
} else if bytes.HasPrefix(scanner.Bytes(), importKeyword) {
if bytes.HasSuffix(scanner.Bytes(), openBrace) {
insideImportBlock = true
}
continue
}
// check all unwantedLinePrefixes - and skip them
skipline := false
for _, prefix := range unwantedLinePrefixes {
if bytes.HasPrefix(scanner.Bytes(), prefix) {
skipline = true
continue
}
}
if skipline {
continue
}
cleanOutputLines = append(cleanOutputLines, line(scanner.Text()))
}
cleanOutput := strings.Join(cleanOutputLines, "")
output := []byte(cleanOutput)
var err error
// change package name
if pkgName != "" {
output = changePackage(bytes.NewReader([]byte(output)), pkgName)
}
// fix the imports
output, err = imports.Process(filename, output, nil)
if err != nil {
return nil, &errImports{Err: err}
}
return output, nil
}
func line(s string) string {
return fmt.Sprintln(strings.TrimRight(s, linefeed))
}
// isAlphaNumeric gets whether the rune is alphanumeric or _.
func isAlphaNumeric(r rune) bool {
return r == '_' || unicode.IsLetter(r) || unicode.IsDigit(r)
}
// wordify turns a type into a nice word for function and type
// names etc.
func wordify(s string, exported bool) string {
s = strings.TrimRight(s, "{}")
s = strings.TrimLeft(s, "*&")
s = strings.Replace(s, ".", "", -1)
if !exported {
return s
}
return strings.ToUpper(string(s[0])) + s[1:]
}
func changePackage(r io.Reader, pkgName string) []byte {
var out bytes.Buffer
sc := bufio.NewScanner(r)
done := false
for sc.Scan() {
s := sc.Text()
if !done && strings.HasPrefix(s, "package") {
parts := strings.Split(s, " ")
parts[1] = pkgName
s = strings.Join(parts, " ")
done = true
}
fmt.Fprintln(&out, s)
}
return out.Bytes()
}
-35
View File
@@ -1,35 +0,0 @@
package parse
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestIsAlphaNumeric(t *testing.T) {
for _, r := range []rune{'a', '1', '_', 'A', 'Z'} {
assert.True(t, isAlphaNumeric(r))
}
for _, r := range []rune{' ', '[', ']', '!', '"'} {
assert.False(t, isAlphaNumeric(r))
}
}
func TestWordify(t *testing.T) {
for word, wordified := range map[string]string{
"int": "Int",
"*int": "Int",
"string": "String",
"*MyType": "MyType",
"*myType": "MyType",
"interface{}": "Interface",
"pack.type": "Packtype",
"*pack.type": "Packtype",
} {
assert.Equal(t, wordified, wordify(word, true))
}
}
-126
View File
@@ -1,126 +0,0 @@
package parse_test
import (
"io/ioutil"
"log"
"strings"
"testing"
"github.com/cheekybits/genny/parse"
"github.com/stretchr/testify/assert"
)
var tests = []struct {
// input
filename string
pkgName string
in string
types []map[string]string
// expectations
expectedOut string
expectedErr error
}{
{
filename: "generic_queue.go",
in: `test/queue/generic_queue.go`,
types: []map[string]string{{"Something": "int"}},
expectedOut: `test/queue/int_queue.go`,
},
{
filename: "generic_queue.go",
pkgName: "changed",
in: `test/queue/generic_queue.go`,
types: []map[string]string{{"Something": "int"}},
expectedOut: `test/queue/changed/int_queue.go`,
},
{
filename: "generic_queue.go",
in: `test/queue/generic_queue.go`,
types: []map[string]string{{"Something": "float32"}},
expectedOut: `test/queue/float32_queue.go`,
},
{
filename: "generic_simplemap.go",
in: `test/multipletypes/generic_simplemap.go`,
types: []map[string]string{{"KeyType": "string", "ValueType": "int"}},
expectedOut: `test/multipletypes/string_int_simplemap.go`,
},
{
filename: "generic_simplemap.go",
in: `test/multipletypes/generic_simplemap.go`,
types: []map[string]string{{"KeyType": "interface{}", "ValueType": "int"}},
expectedOut: `test/multipletypes/interface_int_simplemap.go`,
},
{
filename: "generic_simplemap.go",
in: `test/multipletypes/generic_simplemap.go`,
types: []map[string]string{{"KeyType": "*MyType1", "ValueType": "*MyOtherType"}},
expectedOut: `test/multipletypes/custom_types_simplemap.go`,
},
{
filename: "generic_internal.go",
in: `test/unexported/generic_internal.go`,
types: []map[string]string{{"secret": "*myType"}},
expectedOut: `test/unexported/mytype_internal.go`,
},
{
filename: "generic_simplemap.go",
in: `test/multipletypesets/generic_simplemap.go`,
types: []map[string]string{
{"KeyType": "int", "ValueType": "string"},
{"KeyType": "float64", "ValueType": "bool"},
},
expectedOut: `test/multipletypesets/many_simplemaps.go`,
},
{
filename: "generic_number.go",
in: `test/numbers/generic_number.go`,
types: []map[string]string{{"NumberType": "int"}},
expectedOut: `test/numbers/int_number.go`,
},
{
filename: "generic_digraph.go",
in: `test/bugreports/generic_digraph.go`,
types: []map[string]string{{"Node": "int"}},
expectedOut: `test/bugreports/int_digraph.go`,
},
}
func TestParse(t *testing.T) {
for _, test := range tests {
test.in = contents(test.in)
test.expectedOut = contents(test.expectedOut)
bytes, err := parse.Generics(test.filename, test.pkgName, strings.NewReader(test.in), test.types)
// check the error
if test.expectedErr == nil {
assert.NoError(t, err, "(%s) No error was expected but got: %s", test.filename, err)
} else {
assert.NotNil(t, err, "(%s) No error was returned by one was expected: %s", test.filename, test.expectedErr)
assert.IsType(t, test.expectedErr, err, "(%s) Generate should return object of type %v", test.filename, test.expectedErr)
}
// assert the response
if !assert.Equal(t, string(bytes), test.expectedOut, "Parse didn't generate the expected output.") {
log.Println("EXPECTED: " + test.expectedOut)
log.Println("ACTUAL: " + string(bytes))
}
}
}
func contents(s string) string {
if strings.HasSuffix(s, "go") {
file, err := ioutil.ReadFile(s)
if err != nil {
panic(err)
}
return string(file)
}
return s
}
@@ -1,30 +0,0 @@
package bugreports
import "github.com/cheekybits/genny/generic"
type Node generic.Type
type DigraphNode struct {
nodes map[Node][]Node
}
func NewDigraphNode() *DigraphNode {
return &DigraphNode{
nodes: make(map[Node][]Node),
}
}
func (dig *DigraphNode) Add(n Node) {
if _, exists := dig.nodes[n]; exists {
return
}
dig.nodes[n] = nil
}
func (dig *DigraphNode) Connect(a, b Node) {
dig.Add(a)
dig.Add(b)
dig.nodes[a] = append(dig.nodes[a], b)
}
@@ -1,30 +0,0 @@
// This file was automatically generated by genny.
// Any changes will be lost if this file is regenerated.
// see https://github.com/cheekybits/genny
package bugreports
type DigraphInt struct {
nodes map[int][]int
}
func NewDigraphInt() *DigraphInt {
return &DigraphInt{
nodes: make(map[int][]int),
}
}
func (dig *DigraphInt) Add(n int) {
if _, exists := dig.nodes[n]; exists {
return
}
dig.nodes[n] = nil
}
func (dig *DigraphInt) Connect(a, b int) {
dig.Add(a)
dig.Add(b)
dig.nodes[a] = append(dig.nodes[a], b)
}
@@ -1,5 +0,0 @@
package multipletypes
type MyType1 struct{}
type MyOtherType struct{}
@@ -1,21 +0,0 @@
// This file was automatically generated by genny.
// Any changes will be lost if this file is regenerated.
// see https://github.com/cheekybits/genny
package multipletypes
type MyType1MyOtherTypeMap map[*MyType1]*MyOtherType
func (m MyType1MyOtherTypeMap) Has(key *MyType1) bool {
_, ok := m[key]
return ok
}
func (m MyType1MyOtherTypeMap) Get(key *MyType1) *MyOtherType {
return m[key]
}
func (m MyType1MyOtherTypeMap) Set(key *MyType1, value *MyOtherType) MyType1MyOtherTypeMap {
m[key] = value
return m
}
@@ -1,22 +0,0 @@
package multipletypes
import "github.com/cheekybits/genny/generic"
type KeyType generic.Type
type ValueType generic.Type
type KeyTypeValueTypeMap map[KeyType]ValueType
func (m KeyTypeValueTypeMap) Has(key KeyType) bool {
_, ok := m[key]
return ok
}
func (m KeyTypeValueTypeMap) Get(key KeyType) ValueType {
return m[key]
}
func (m KeyTypeValueTypeMap) Set(key KeyType, value ValueType) KeyTypeValueTypeMap {
m[key] = value
return m
}
@@ -1,35 +0,0 @@
package multipletypes
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSimpleMap(t *testing.T) {
key1 := new(KeyType)
key2 := new(KeyType)
value1 := new(ValueType)
m := make(KeyTypeValueTypeMap)
assert.Equal(t, m, m.Set(key1, value1))
assert.True(t, m.Has(key1))
assert.False(t, m.Has(key2))
assert.Equal(t, value1, m.Get(key1))
}
func TestCustomTypesMap(t *testing.T) {
key1 := new(MyType1)
key2 := new(MyType1)
value1 := new(MyOtherType)
m := make(MyType1MyOtherTypeMap)
assert.Equal(t, m, m.Set(key1, value1))
assert.True(t, m.Has(key1))
assert.False(t, m.Has(key2))
assert.Equal(t, value1, m.Get(key1))
}
@@ -1,21 +0,0 @@
// This file was automatically generated by genny.
// Any changes will be lost if this file is regenerated.
// see https://github.com/cheekybits/genny
package multipletypes
type InterfaceIntMap map[interface{}]int
func (m InterfaceIntMap) Has(key interface{}) bool {
_, ok := m[key]
return ok
}
func (m InterfaceIntMap) Get(key interface{}) int {
return m[key]
}
func (m InterfaceIntMap) Set(key interface{}, value int) InterfaceIntMap {
m[key] = value
return m
}
@@ -1,21 +0,0 @@
// This file was automatically generated by genny.
// Any changes will be lost if this file is regenerated.
// see https://github.com/cheekybits/genny
package multipletypes
type StringIntMap map[string]int
func (m StringIntMap) Has(key string) bool {
_, ok := m[key]
return ok
}
func (m StringIntMap) Get(key string) int {
return m[key]
}
func (m StringIntMap) Set(key string, value int) StringIntMap {
m[key] = value
return m
}
@@ -1,27 +0,0 @@
package multipletypesets
import (
"log"
"github.com/cheekybits/genny/generic"
)
type KeyType generic.Type
type ValueType generic.Type
type KeyTypeValueTypeMap map[KeyType]ValueType
func (m KeyTypeValueTypeMap) Has(key KeyType) bool {
_, ok := m[key]
return ok
}
func (m KeyTypeValueTypeMap) Get(key KeyType) ValueType {
return m[key]
}
func (m KeyTypeValueTypeMap) Set(key KeyType, value ValueType) KeyTypeValueTypeMap {
log.Println(value)
m[key] = value
return m
}
@@ -1,21 +0,0 @@
package multipletypesets
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSimpleMap(t *testing.T) {
key1 := new(KeyType)
key2 := new(KeyType)
value1 := new(ValueType)
m := make(KeyTypeValueTypeMap)
assert.Equal(t, m, m.Set(key1, value1))
assert.True(t, m.Has(key1))
assert.False(t, m.Has(key2))
assert.Equal(t, value1, m.Get(key1))
}
@@ -1,41 +0,0 @@
// This file was automatically generated by genny.
// Any changes will be lost if this file is regenerated.
// see https://github.com/cheekybits/genny
package multipletypesets
import "log"
type IntStringMap map[int]string
func (m IntStringMap) Has(key int) bool {
_, ok := m[key]
return ok
}
func (m IntStringMap) Get(key int) string {
return m[key]
}
func (m IntStringMap) Set(key int, value string) IntStringMap {
log.Println(value)
m[key] = value
return m
}
type Float64BoolMap map[float64]bool
func (m Float64BoolMap) Has(key float64) bool {
_, ok := m[key]
return ok
}
func (m Float64BoolMap) Get(key float64) bool {
return m[key]
}
func (m Float64BoolMap) Set(key float64, value bool) Float64BoolMap {
log.Println(value)
m[key] = value
return m
}
@@ -1,12 +0,0 @@
package numbers
import "github.com/cheekybits/genny/generic"
type NumberType generic.Number
func NumberTypeMax(a, b NumberType) NumberType {
if a > b {
return a
}
return b
}
-12
View File
@@ -1,12 +0,0 @@
// This file was automatically generated by genny.
// Any changes will be lost if this file is regenerated.
// see https://github.com/cheekybits/genny
package numbers
func IntMax(a, b int) int {
if a > b {
return a
}
return b
}
@@ -1,22 +0,0 @@
// This file was automatically generated by genny.
// Any changes will be lost if this file is regenerated.
// see https://github.com/cheekybits/genny
package changed
// IntQueue is a queue of Ints.
type IntQueue struct {
items []int
}
func NewIntQueue() *IntQueue {
return &IntQueue{items: make([]int, 0)}
}
func (q *IntQueue) Push(item int) {
q.items = append(q.items, item)
}
func (q *IntQueue) Pop() int {
item := q.items[0]
q.items = q.items[1:]
return item
}
-22
View File
@@ -1,22 +0,0 @@
// This file was automatically generated by genny.
// Any changes will be lost if this file is regenerated.
// see https://github.com/cheekybits/genny
package queue
// Float32Queue is a queue of Float32s.
type Float32Queue struct {
items []float32
}
func NewFloat32Queue() *Float32Queue {
return &Float32Queue{items: make([]float32, 0)}
}
func (q *Float32Queue) Push(item float32) {
q.items = append(q.items, item)
}
func (q *Float32Queue) Pop() float32 {
item := q.items[0]
q.items = q.items[1:]
return item
}
-23
View File
@@ -1,23 +0,0 @@
package queue
import "github.com/cheekybits/genny/generic"
// Something is a type that does something
type Something generic.Type
// SomethingQueue is a queue of Somethings.
type SomethingQueue struct {
items []Something
}
func NewSomethingQueue() *SomethingQueue {
return &SomethingQueue{items: make([]Something, 0)}
}
func (q *SomethingQueue) Push(item Something) {
q.items = append(q.items, item)
}
func (q *SomethingQueue) Pop() Something {
item := q.items[0]
q.items = q.items[1:]
return item
}
@@ -1,87 +0,0 @@
package queue
import "testing"
func TestSomethingQueue(t *testing.T) {
var item1 *Something = new(Something)
var item2 *Something = new(Something)
var item3 *Something = new(Something)
q := NewSomethingQueue()
q.Push(item1)
if len(q.items) != 1 {
t.Error("Push should add the item")
}
q.Push(item2)
if len(q.items) != 2 {
t.Error("Push should add the item")
}
q.Push(item3)
if len(q.items) != 3 {
t.Error("Push should add the item")
}
if q.Pop() != item1 {
t.Error("Pop should return items in the order in which they were added")
}
if len(q.items) != 2 {
t.Error("Pop should remove the item")
}
if q.Pop() != item2 {
t.Error("Pop should return items in the order in which they were added")
}
if len(q.items) != 1 {
t.Error("Pop should remove the item")
}
if q.Pop() != item3 {
t.Error("Pop should return items in the order in which they were added")
}
if len(q.items) != 0 {
t.Error("Pop should remove the item")
}
}
func TestIntQueue(t *testing.T) {
var item1 int = 1
var item2 int = 2
var item3 int = 3
q := NewIntQueue()
q.Push(item1)
if len(q.items) != 1 {
t.Error("Push should add the item")
}
q.Push(item2)
if len(q.items) != 2 {
t.Error("Push should add the item")
}
q.Push(item3)
if len(q.items) != 3 {
t.Error("Push should add the item")
}
if q.Pop() != item1 {
t.Error("Pop should return items in the order in which they were added")
}
if len(q.items) != 2 {
t.Error("Pop should remove the item")
}
if q.Pop() != item2 {
t.Error("Pop should return items in the order in which they were added")
}
if len(q.items) != 1 {
t.Error("Pop should remove the item")
}
if q.Pop() != item3 {
t.Error("Pop should return items in the order in which they were added")
}
if len(q.items) != 0 {
t.Error("Pop should remove the item")
}
}
-22
View File
@@ -1,22 +0,0 @@
// This file was automatically generated by genny.
// Any changes will be lost if this file is regenerated.
// see https://github.com/cheekybits/genny
package queue
// IntQueue is a queue of Ints.
type IntQueue struct {
items []int
}
func NewIntQueue() *IntQueue {
return &IntQueue{items: make([]int, 0)}
}
func (q *IntQueue) Push(item int) {
q.items = append(q.items, item)
}
func (q *IntQueue) Pop() int {
item := q.items[0]
q.items = q.items[1:]
return item
}
-24
View File
@@ -1,24 +0,0 @@
package slice
import (
"log"
"reflect"
"github.com/cheekybits/genny/generic"
)
type MyType generic.Type
func EnsureSlice(objectOrSlice interface{}) []MyType {
log.Printf("%v", reflect.TypeOf(objectOrSlice))
switch obj := objectOrSlice.(type) {
case []MyType:
log.Println(" returning it untouched")
return obj
case MyType, *MyType:
log.Println(" wrapping in slice")
return []MyType{obj}
default:
panic("ensure slice needs MyType or []MyType")
}
}
@@ -1,24 +0,0 @@
package slice
import (
"log"
"testing"
"github.com/stretchr/testify/assert"
)
func TestEnsureSlice(t *testing.T) {
myType := new(MyType)
slice := EnsureSlice(myType)
if assert.NotNil(t, slice) {
assert.Equal(t, slice[0], myType)
}
slice = EnsureSlice(slice)
log.Printf("%#v", slice[0])
if assert.NotNil(t, slice) {
assert.Equal(t, slice[0], myType)
}
}
@@ -1,3 +0,0 @@
package unexported
type myType struct{}
@@ -1,13 +0,0 @@
package unexported
import (
"fmt"
"github.com/cheekybits/genny/generic"
)
type secret generic.Type
func secretInspect(s secret) string {
return fmt.Sprintf("%#v", s)
}
@@ -1,11 +0,0 @@
// This file was automatically generated by genny.
// Any changes will be lost if this file is regenerated.
// see https://github.com/cheekybits/genny
package unexported
import "fmt"
func myTypeInspect(s *myType) string {
return fmt.Sprintf("%#v", s)
}
-89
View File
@@ -1,89 +0,0 @@
package parse
import "strings"
const (
typeSep = " "
keyValueSep = "="
valuesSep = ","
builtins = "BUILTINS"
numbers = "NUMBERS"
)
// TypeSet turns a type string into a []map[string]string
// that can be given to parse.Generics for it to do its magic.
//
// Acceptable args are:
//
// Person=man
// Person=man Animal=dog
// Person=man Animal=dog Animal2=cat
// Person=man,woman Animal=dog,cat
// Person=man,woman,child Animal=dog,cat Place=london,paris
func TypeSet(arg string) ([]map[string]string, error) {
types := make(map[string][]string)
var keys []string
for _, pair := range strings.Split(arg, typeSep) {
segs := strings.Split(pair, keyValueSep)
if len(segs) != 2 {
return nil, &errBadTypeArgs{Arg: arg, Message: "Generic=Specific expected"}
}
key := segs[0]
keys = append(keys, key)
types[key] = make([]string, 0)
for _, t := range strings.Split(segs[1], valuesSep) {
if t == builtins {
types[key] = append(types[key], Builtins...)
} else if t == numbers {
types[key] = append(types[key], Numbers...)
} else {
types[key] = append(types[key], t)
}
}
}
cursors := make(map[string]int)
for _, key := range keys {
cursors[key] = 0
}
outChan := make(chan map[string]string)
go func() {
buildTypeSet(keys, 0, cursors, types, outChan)
close(outChan)
}()
var typeSets []map[string]string
for typeSet := range outChan {
typeSets = append(typeSets, typeSet)
}
return typeSets, nil
}
func buildTypeSet(keys []string, keyI int, cursors map[string]int, types map[string][]string, out chan<- map[string]string) {
key := keys[keyI]
for cursors[key] < len(types[key]) {
if keyI < len(keys)-1 {
buildTypeSet(keys, keyI+1, copycursors(cursors), types, out)
} else {
// build the typeset for this combination
ts := make(map[string]string)
for k, vals := range types {
ts[k] = vals[cursors[k]]
}
out <- ts
}
cursors[key]++
}
}
func copycursors(source map[string]int) map[string]int {
copy := make(map[string]int)
for k, v := range source {
copy[k] = v
}
return copy
}
-74
View File
@@ -1,74 +0,0 @@
package parse_test
import (
"testing"
"github.com/cheekybits/genny/parse"
"github.com/stretchr/testify/assert"
)
func TestArgsToTypeset(t *testing.T) {
args := "Person=man,woman Animal=dog,cat Place=london,paris"
ts, err := parse.TypeSet(args)
if assert.NoError(t, err) {
if assert.Equal(t, 8, len(ts)) {
assert.Equal(t, ts[0]["Person"], "man")
assert.Equal(t, ts[0]["Animal"], "dog")
assert.Equal(t, ts[0]["Place"], "london")
assert.Equal(t, ts[1]["Person"], "man")
assert.Equal(t, ts[1]["Animal"], "dog")
assert.Equal(t, ts[1]["Place"], "paris")
assert.Equal(t, ts[2]["Person"], "man")
assert.Equal(t, ts[2]["Animal"], "cat")
assert.Equal(t, ts[2]["Place"], "london")
assert.Equal(t, ts[3]["Person"], "man")
assert.Equal(t, ts[3]["Animal"], "cat")
assert.Equal(t, ts[3]["Place"], "paris")
assert.Equal(t, ts[4]["Person"], "woman")
assert.Equal(t, ts[4]["Animal"], "dog")
assert.Equal(t, ts[4]["Place"], "london")
assert.Equal(t, ts[5]["Person"], "woman")
assert.Equal(t, ts[5]["Animal"], "dog")
assert.Equal(t, ts[5]["Place"], "paris")
assert.Equal(t, ts[6]["Person"], "woman")
assert.Equal(t, ts[6]["Animal"], "cat")
assert.Equal(t, ts[6]["Place"], "london")
assert.Equal(t, ts[7]["Person"], "woman")
assert.Equal(t, ts[7]["Animal"], "cat")
assert.Equal(t, ts[7]["Place"], "paris")
}
}
ts, err = parse.TypeSet("Person=man Animal=dog Place=london")
if assert.NoError(t, err) {
assert.Equal(t, 1, len(ts))
}
ts, err = parse.TypeSet("Person=1,2,3,4,5 Animal=1,2,3,4,5 Place=1,2,3,4,5")
if assert.NoError(t, err) {
assert.Equal(t, 125, len(ts))
}
ts, err = parse.TypeSet("Person=1 Animal=1,2,3,4,5 Place=1,2")
if assert.NoError(t, err) {
assert.Equal(t, 10, len(ts))
}
ts, err = parse.TypeSet("Person=interface{} Animal=interface{} Place=interface{}")
if assert.NoError(t, err) {
assert.Equal(t, 1, len(ts))
assert.Equal(t, ts[0]["Animal"], "interface{}")
assert.Equal(t, ts[0]["Person"], "interface{}")
assert.Equal(t, ts[0]["Place"], "interface{}")
}
}
+73
View File
@@ -0,0 +1,73 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
name = "github.com/alecthomas/template"
packages = [".","parse"]
revision = "a0175ee3bccc567396460bf5acd36800cb10c49c"
[[projects]]
branch = "master"
name = "github.com/alecthomas/units"
packages = ["."]
revision = "2efee857e7cfd4f3d0138cc3cbb1b4966962b93a"
[[projects]]
branch = "master"
name = "github.com/cheekybits/genny"
packages = ["generic"]
revision = "9127e812e1e9e501ce899a18121d316ecb52e4ba"
[[projects]]
name = "github.com/davecgh/go-spew"
packages = ["spew"]
revision = "6d212800a42e8ab5c146b8ace3490ee17e5225f9"
[[projects]]
branch = "master"
name = "github.com/fatih/camelcase"
packages = ["."]
revision = "f6a740d52f961c60348ebb109adde9f4635d7540"
[[projects]]
name = "github.com/pkg/errors"
packages = ["."]
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
version = "v0.8.0"
[[projects]]
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
revision = "d8ed2627bdf02c080bf22230dbb337003b7aba2d"
[[projects]]
name = "github.com/stretchr/testify"
packages = ["assert"]
revision = "69483b4bd14f5845b5a1e55bca19e954e827f1d0"
version = "v1.1.4"
[[projects]]
name = "gopkg.in/alecthomas/kingpin.v2"
packages = ["."]
revision = "7f0871f2e17818990e4eed73f9b5c2f429501228"
version = "v2.2.4"
[[projects]]
branch = "v2"
name = "gopkg.in/coryb/yaml.v2"
packages = ["."]
revision = "fb7cb9628c6e3bdd76c29fb91798d51a09832470"
[[projects]]
name = "gopkg.in/op/go-logging.v1"
packages = ["."]
revision = "b2cb9fa56473e98db8caba80237377e83fe44db5"
version = "v1"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "1879d7c016fcc8e210ca7fdeed4b099f4e7fc931d34ff47a170222c60eea8aab"
solver-name = "gps-cdcl"
solver-version = 1
+47
View File
@@ -0,0 +1,47 @@
# Gopkg.toml example
#
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
# for detailed Gopkg.toml documentation.
#
# required = ["github.com/user/thing/cmd/thing"]
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
#
# [[constraint]]
# name = "github.com/user/project"
# version = "1.0.0"
#
# [[constraint]]
# name = "github.com/user/project2"
# branch = "dev"
# source = "github.com/myfork/project2"
#
# [[override]]
# name = "github.com/x/y"
# version = "2.4.0"
[[constraint]]
name = "github.com/cheekybits/genny"
[[constraint]]
name = "github.com/fatih/camelcase"
[[constraint]]
name = "github.com/pkg/errors"
version = "0.8.0"
[[constraint]]
name = "github.com/stretchr/testify"
version = "1.1.4"
[[constraint]]
name = "gopkg.in/alecthomas/kingpin.v2"
version = "2.2.4"
[[constraint]]
name = "gopkg.in/coryb/yaml.v2"
[[constraint]]
name = "gopkg.in/op/go-logging.v1"
version = "1.0.0"
-9
View File
@@ -1,9 +0,0 @@
str1:
foo: bar
arr1: strval1
map1:
- abc
- def
int1: true
float1: "strval2"
bool1: 123.45
-13
View File
@@ -1,13 +0,0 @@
#!/bin/sh
cat <<EOM
str1: d3str1val1
arr1:
- d3arr1val1
- d3arr1val2
map1:
key2: d3map1val2
key3: d3map1val3
int1: 333
float1: 3.33
bool1: true
EOM
-12
View File
@@ -1,12 +0,0 @@
str1: d3str1val1
arr1:
- d3arr1val1
- d3arr1val2
- dupval
map1:
key2: d3map1val2
key3: d3map1val3
dup: d3dupval
int1: 333
float1: 3.33
bool1: true
-10
View File
@@ -1,10 +0,0 @@
str1: d3str1val1
arr1:
- d3arr1val1
- d3arr1val2
map1:
key2: d3map1val2
key3: d3map1val3
int1: 333
float1: 3.33
bool1: true
-10
View File
@@ -1,10 +0,0 @@
str1: d3str1val1
arr1:
- d3arr1val1
- d3arr1val2
map1:
key2: d3map1val2
key3: d3map1val3
int1: 333
float1: 3.33
bool1: true
-13
View File
@@ -1,13 +0,0 @@
#!/bin/sh
cat <<EOM
str1: d2str1val1
arr1:
- d2arr1val1
- d2arr1val2
map1:
key1: d2map1val1
key2: d2map1val2
int1: 222
float1: 2.22
bool1: false
EOM
-12
View File
@@ -1,12 +0,0 @@
str1: d2str1val1
arr1:
- d2arr1val1
- d2arr1val2
- dupval
map1:
key1: d2map1val1
key2: d2map1val2
dup: d2dupval
int1: 222
float1: 2.22
bool1: false
-15
View File
@@ -1,15 +0,0 @@
config:
overwrite:
- str1
- arr1
- bool1
str1: d2str1val1
arr1:
- d2arr1val1
- d2arr1val2
map1:
key1: d2map1val1
key2: d2map1val2
int1: 222
float1: 2.22
bool1: false
-12
View File
@@ -1,12 +0,0 @@
config:
stop: true
str1: d2str1val1
arr1:
- d2arr1val1
- d2arr1val2
map1:
key1: d2map1val1
key2: d2map1val2
int1: 222
float1: 2.22
bool1: false
-13
View File
@@ -1,13 +0,0 @@
#!/bin/bash
cat <<EOM
str1: d1str1val1
arr1:
- d1arr1val1
- d1arr1val2
map1:
key0: d1map1val0
key1: d1map1val1
int1: 111
float1: 1.11
bool1: true
EOM
-12
View File
@@ -1,12 +0,0 @@
str1: d1str1val1
arr1:
- d1arr1val1
- d1arr1val2
- dupval
map1:
key0: d1map1val0
key1: d1map1val1
dup: d1dupval
int1: 111
float1: 1.11
bool1: true
-15
View File
@@ -1,15 +0,0 @@
config:
overwrite:
- map1
- int1
- float1
str1: d1str1val1
arr1:
- d1arr1val1
- d1arr1val2
map1:
key0: d1map1val0
key1: d1map1val1
int1: 111
float1: 1.11
bool1: true
-10
View File
@@ -1,10 +0,0 @@
str1: d1str1val1
arr1:
- d1arr1val1
- d1arr1val2
map1:
key0: d1map1val0
key1: d1map1val1
int1: 111
float1: 1.11
bool1: true
+72 -50
View File
@@ -81,6 +81,14 @@ func (f *FigTree) LoadAllConfigs(configFile string, options interface{}) error {
func (f *FigTree) LoadConfigBytes(config []byte, source string, options interface{}) (err error) {
f.populateEnv(options)
defer func(mapType, iface reflect.Type) {
yaml.DefaultMapType = mapType
yaml.IfaceType = iface
}(yaml.DefaultMapType, yaml.IfaceType)
yaml.DefaultMapType = reflect.TypeOf(map[string]interface{}{})
yaml.IfaceType = yaml.DefaultMapType.Elem()
m := &merger{sourceFile: source}
type tmpOpts struct {
Config ConfigOptions
@@ -104,11 +112,11 @@ func (f *FigTree) LoadConfigBytes(config []byte, source string, options interfac
reflect.ValueOf(options),
reflect.ValueOf(tmp),
)
f.populateEnv(options)
if m.Config.Stop {
f.stop = true
return nil
}
f.populateEnv(options)
return nil
}
@@ -350,6 +358,60 @@ Outer:
return ov
}
func (f *FigTree) formatEnvName(name string) string {
name = fmt.Sprintf("%s_%s", f.EnvPrefix, strings.ToUpper(name))
return strings.Map(func(r rune) rune {
if unicode.IsDigit(r) || unicode.IsLetter(r) {
return r
}
return '_'
}, name)
}
func (f *FigTree) formatEnvValue(value reflect.Value) (string, bool) {
switch t := value.Interface().(type) {
case string:
return t, true
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, bool:
return fmt.Sprintf("%v", t), true
default:
switch value.Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
if value.IsNil() {
return "", false
}
}
if t == nil {
return "", false
}
type definable interface {
IsDefined() bool
}
if def, ok := t.(definable); ok {
// skip fields that are not defined
if !def.IsDefined() {
return "", false
}
}
type gettable interface {
GetValue() interface{}
}
if get, ok := t.(gettable); ok {
return fmt.Sprintf("%v", get.GetValue()), true
} else {
if b, err := json.Marshal(t); err == nil {
val := strings.TrimSpace(string(b))
if val == "null" {
return "", true
}
return val, true
}
}
}
return "", false
}
func (f *FigTree) populateEnv(data interface{}) {
options := reflect.ValueOf(data)
if options.Kind() == reflect.Ptr {
@@ -370,8 +432,11 @@ func (f *FigTree) populateEnv(data interface{}) {
}
name := strings.Join(allParts, "_")
envName := fmt.Sprintf("%s_%s", f.EnvPrefix, strings.ToUpper(name))
os.Setenv(envName, fmt.Sprintf("%v", options.MapIndex(key).Interface()))
envName := f.formatEnvName(name)
val, ok := f.formatEnvValue(options.MapIndex(key))
if ok {
os.Setenv(envName, val)
}
}
}
} else if options.Kind() == reflect.Struct {
@@ -400,54 +465,11 @@ func (f *FigTree) populateEnv(data interface{}) {
}
}
envName := fmt.Sprintf("%s_%s", f.EnvPrefix, strings.ToUpper(name))
envName = strings.Map(func(r rune) rune {
if unicode.IsDigit(r) || unicode.IsLetter(r) {
return r
}
return '_'
}, envName)
var val string
switch t := options.Field(i).Interface().(type) {
case string:
val = t
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, float32, float64, bool:
val = fmt.Sprintf("%v", t)
default:
switch options.Field(i).Kind() {
case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
if options.Field(i).IsNil() {
continue
}
}
if t == nil {
continue
}
type definable interface {
IsDefined() bool
}
if def, ok := t.(definable); ok {
// skip fields that are not defined
if !def.IsDefined() {
continue
}
}
type gettable interface {
GetValue() interface{}
}
if get, ok := t.(gettable); ok {
val = fmt.Sprintf("%v", get.GetValue())
} else {
if b, err := json.Marshal(t); err == nil {
val = strings.TrimSpace(string(b))
if val == "null" {
val = ""
}
}
}
envName := f.formatEnvName(name)
val, ok := f.formatEnvValue(options.Field(i))
if ok {
os.Setenv(envName, val)
}
os.Setenv(envName, val)
}
}
}
-36
View File
@@ -1,36 +0,0 @@
hash: 4c141f4247c76717823951388fcd50678e63195fa40b0d5bc7097d0267003052
updated: 2017-07-06T23:09:17.970073816-07:00
imports:
- name: github.com/cheekybits/genny
version: 9127e812e1e9e501ce899a18121d316ecb52e4ba
subpackages:
- generic
- name: github.com/fatih/camelcase
version: f6a740d52f961c60348ebb109adde9f4635d7540
- name: github.com/pkg/errors
version: 645ef00459ed84a119197bfb8d8205042c6df63d
- name: gopkg.in/coryb/yaml.v2
version: f284bc8aa3c31dfdda9cc7917c610398c49b3acd
- name: gopkg.in/op/go-logging.v1
version: b2cb9fa56473e98db8caba80237377e83fe44db5
testImports:
- name: github.com/alecthomas/template
version: a0175ee3bccc567396460bf5acd36800cb10c49c
subpackages:
- parse
- name: github.com/alecthomas/units
version: 2efee857e7cfd4f3d0138cc3cbb1b4966962b93a
- name: github.com/davecgh/go-spew
version: 6d212800a42e8ab5c146b8ace3490ee17e5225f9
subpackages:
- spew
- name: github.com/pmezard/go-difflib
version: d8ed2627bdf02c080bf22230dbb337003b7aba2d
subpackages:
- difflib
- name: github.com/stretchr/testify
version: 69483b4bd14f5845b5a1e55bca19e954e827f1d0
subpackages:
- assert
- name: gopkg.in/alecthomas/kingpin.v2
version: 7f0871f2e17818990e4eed73f9b5c2f429501228
-18
View File
@@ -1,18 +0,0 @@
package: github.com/coryb/figtree
import:
- package: github.com/cheekybits/genny
subpackages:
- generic
- package: github.com/pkg/errors
version: ^0.8.0
- package: gopkg.in/coryb/yaml.v2
- package: gopkg.in/op/go-logging.v1
version: ^1.0.0
- package: github.com/fatih/camelcase
testImport:
- package: github.com/stretchr/testify
version: ^1.1.4
subpackages:
- assert
- package: gopkg.in/alecthomas/kingpin.v2
version: ^2.2.4
-30
View File
@@ -1,30 +0,0 @@
package main
import (
"fmt"
"github.com/guelfey/go.dbus"
"os"
)
func main() {
conn, err := dbus.SessionBus()
if err != nil {
fmt.Fprintln(os.Stderr, "Failed to connect to session bus:", err)
os.Exit(1)
}
for _, v := range []string{"method_call", "method_return", "error", "signal"} {
call := conn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0,
"eavesdrop='true',type='"+v+"'")
if call.Err != nil {
fmt.Fprintln(os.Stderr, "Failed to add match:", call.Err)
os.Exit(1)
}
}
c := make(chan *dbus.Message, 10)
conn.Eavesdrop(c)
fmt.Println("Listening for everything")
for v := range c {
fmt.Println(v)
}
}
-21
View File
@@ -1,21 +0,0 @@
package main
import (
"encoding/json"
"github.com/guelfey/go.dbus"
"github.com/guelfey/go.dbus/introspect"
"os"
)
func main() {
conn, err := dbus.SessionBus()
if err != nil {
panic(err)
}
node, err := introspect.Call(conn.Object("org.freedesktop.DBus", "/org/freedesktop/DBus"))
if err != nil {
panic(err)
}
data, _ := json.MarshalIndent(node, "", " ")
os.Stdout.Write(data)
}

Some files were not shown because too many files have changed in this diff Show More