Have Exporter.Write automatically call ExportData on given data structure
This commit is contained in:
parent
026b07d1cf
commit
5f0301c990
10 changed files with 97 additions and 61 deletions
|
|
@ -6,6 +6,7 @@ import (
|
|||
)
|
||||
|
||||
func (issue *Issue) ExportData(fields []string) *map[string]interface{} {
|
||||
v := reflect.ValueOf(issue).Elem()
|
||||
data := map[string]interface{}{}
|
||||
|
||||
for _, f := range fields {
|
||||
|
|
@ -25,7 +26,6 @@ func (issue *Issue) ExportData(fields []string) *map[string]interface{} {
|
|||
case "projectCards":
|
||||
data[f] = issue.ProjectCards.Nodes
|
||||
default:
|
||||
v := reflect.ValueOf(issue).Elem()
|
||||
sf := fieldByName(v, f)
|
||||
data[f] = sf.Interface()
|
||||
}
|
||||
|
|
@ -35,6 +35,7 @@ func (issue *Issue) ExportData(fields []string) *map[string]interface{} {
|
|||
}
|
||||
|
||||
func (pr *PullRequest) ExportData(fields []string) *map[string]interface{} {
|
||||
v := reflect.ValueOf(pr).Elem()
|
||||
data := map[string]interface{}{}
|
||||
|
||||
for _, f := range fields {
|
||||
|
|
@ -75,7 +76,6 @@ func (pr *PullRequest) ExportData(fields []string) *map[string]interface{} {
|
|||
}
|
||||
data[f] = &requests
|
||||
default:
|
||||
v := reflect.ValueOf(pr).Elem()
|
||||
sf := fieldByName(v, f)
|
||||
data[f] = sf.Interface()
|
||||
}
|
||||
|
|
@ -84,22 +84,6 @@ func (pr *PullRequest) ExportData(fields []string) *map[string]interface{} {
|
|||
return &data
|
||||
}
|
||||
|
||||
func ExportIssues(issues []Issue, fields []string) *[]interface{} {
|
||||
data := make([]interface{}, len(issues))
|
||||
for i := range issues {
|
||||
data[i] = issues[i].ExportData(fields)
|
||||
}
|
||||
return &data
|
||||
}
|
||||
|
||||
func ExportPRs(prs []PullRequest, fields []string) *[]interface{} {
|
||||
data := make([]interface{}, len(prs))
|
||||
for i := range prs {
|
||||
data[i] = prs[i].ExportData(fields)
|
||||
}
|
||||
return &data
|
||||
}
|
||||
|
||||
func fieldByName(v reflect.Value, field string) reflect.Value {
|
||||
return v.FieldByNameFunc(func(s string) bool {
|
||||
return strings.EqualFold(field, s)
|
||||
|
|
|
|||
|
|
@ -90,31 +90,6 @@ func TestIssue_ExportData(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestExportIssues(t *testing.T) {
|
||||
issues := []Issue{
|
||||
{Milestone: Milestone{Title: "hi"}},
|
||||
{},
|
||||
}
|
||||
exported := ExportIssues(issues, []string{"milestone"})
|
||||
|
||||
buf := bytes.Buffer{}
|
||||
enc := json.NewEncoder(&buf)
|
||||
enc.SetIndent("", "\t")
|
||||
require.NoError(t, enc.Encode(exported))
|
||||
assert.Equal(t, heredoc.Doc(`
|
||||
[
|
||||
{
|
||||
"milestone": {
|
||||
"title": "hi"
|
||||
}
|
||||
},
|
||||
{
|
||||
"milestone": null
|
||||
}
|
||||
]
|
||||
`), buf.String())
|
||||
}
|
||||
|
||||
func TestPullRequest_ExportData(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
|
|||
|
|
@ -155,8 +155,7 @@ func listRun(opts *ListOptions) error {
|
|||
defer opts.IO.StopPager()
|
||||
|
||||
if opts.Exporter != nil {
|
||||
data := api.ExportIssues(listResult.Issues, opts.Exporter.Fields())
|
||||
return opts.Exporter.Write(opts.IO.Out, data, opts.IO.ColorEnabled())
|
||||
return opts.Exporter.Write(opts.IO.Out, listResult.Issues, opts.IO.ColorEnabled())
|
||||
}
|
||||
|
||||
if isTerminal {
|
||||
|
|
|
|||
|
|
@ -96,11 +96,11 @@ func statusRun(opts *StatusOptions) error {
|
|||
|
||||
if opts.Exporter != nil {
|
||||
data := map[string]interface{}{
|
||||
"createdBy": api.ExportIssues(issuePayload.Authored.Issues, opts.Exporter.Fields()),
|
||||
"assigned": api.ExportIssues(issuePayload.Assigned.Issues, opts.Exporter.Fields()),
|
||||
"mentioned": api.ExportIssues(issuePayload.Mentioned.Issues, opts.Exporter.Fields()),
|
||||
"createdBy": issuePayload.Authored.Issues,
|
||||
"assigned": issuePayload.Assigned.Issues,
|
||||
"mentioned": issuePayload.Mentioned.Issues,
|
||||
}
|
||||
return opts.Exporter.Write(opts.IO.Out, &data, opts.IO.ColorEnabled())
|
||||
return opts.Exporter.Write(opts.IO.Out, data, opts.IO.ColorEnabled())
|
||||
}
|
||||
|
||||
out := opts.IO.Out
|
||||
|
|
|
|||
|
|
@ -116,8 +116,7 @@ func viewRun(opts *ViewOptions) error {
|
|||
defer opts.IO.StopPager()
|
||||
|
||||
if opts.Exporter != nil {
|
||||
exportIssue := issue.ExportData(opts.Exporter.Fields())
|
||||
return opts.Exporter.Write(opts.IO.Out, exportIssue, opts.IO.ColorEnabled())
|
||||
return opts.Exporter.Write(opts.IO.Out, issue, opts.IO.ColorEnabled())
|
||||
}
|
||||
|
||||
if opts.IO.IsStdoutTTY() {
|
||||
|
|
|
|||
|
|
@ -155,8 +155,7 @@ func listRun(opts *ListOptions) error {
|
|||
defer opts.IO.StopPager()
|
||||
|
||||
if opts.Exporter != nil {
|
||||
data := api.ExportPRs(listResult.PullRequests, opts.Exporter.Fields())
|
||||
return opts.Exporter.Write(opts.IO.Out, data, opts.IO.ColorEnabled())
|
||||
return opts.Exporter.Write(opts.IO.Out, listResult.PullRequests, opts.IO.ColorEnabled())
|
||||
}
|
||||
|
||||
if opts.IO.IsStdoutTTY() {
|
||||
|
|
|
|||
|
|
@ -113,13 +113,13 @@ func statusRun(opts *StatusOptions) error {
|
|||
if opts.Exporter != nil {
|
||||
data := map[string]interface{}{
|
||||
"currentBranch": nil,
|
||||
"createdBy": api.ExportPRs(prPayload.ViewerCreated.PullRequests, opts.Exporter.Fields()),
|
||||
"needsReview": api.ExportPRs(prPayload.ReviewRequested.PullRequests, opts.Exporter.Fields()),
|
||||
"createdBy": prPayload.ViewerCreated.PullRequests,
|
||||
"needsReview": prPayload.ReviewRequested.PullRequests,
|
||||
}
|
||||
if prPayload.CurrentPR != nil {
|
||||
data["currentBranch"] = prPayload.CurrentPR.ExportData(opts.Exporter.Fields())
|
||||
data["currentBranch"] = prPayload.CurrentPR
|
||||
}
|
||||
return opts.Exporter.Write(opts.IO.Out, &data, opts.IO.ColorEnabled())
|
||||
return opts.Exporter.Write(opts.IO.Out, data, opts.IO.ColorEnabled())
|
||||
}
|
||||
|
||||
out := opts.IO.Out
|
||||
|
|
|
|||
|
|
@ -117,8 +117,7 @@ func viewRun(opts *ViewOptions) error {
|
|||
defer opts.IO.StopPager()
|
||||
|
||||
if opts.Exporter != nil {
|
||||
exportPR := pr.ExportData(opts.Exporter.Fields())
|
||||
return opts.Exporter.Write(opts.IO.Out, exportPR, opts.IO.ColorEnabled())
|
||||
return opts.Exporter.Write(opts.IO.Out, pr, opts.IO.ColorEnabled())
|
||||
}
|
||||
|
||||
if connectedToTerminal {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
|
|
@ -102,11 +103,14 @@ func (e *exportFormat) Fields() []string {
|
|||
return e.fields
|
||||
}
|
||||
|
||||
// Write serializes data into JSON output written to w. If the object passed as data implements exportable,
|
||||
// or if data is a map or slice of exportable object, ExportData() will be called on each object to obtain
|
||||
// raw data for serialization.
|
||||
func (e *exportFormat) Write(w io.Writer, data interface{}, colorEnabled bool) error {
|
||||
buf := bytes.Buffer{}
|
||||
encoder := json.NewEncoder(&buf)
|
||||
encoder.SetEscapeHTML(false)
|
||||
if err := encoder.Encode(data); err != nil {
|
||||
if err := encoder.Encode(e.exportData(reflect.ValueOf(data))); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -121,3 +125,44 @@ func (e *exportFormat) Write(w io.Writer, data interface{}, colorEnabled bool) e
|
|||
_, err := io.Copy(w, &buf)
|
||||
return err
|
||||
}
|
||||
|
||||
func (e *exportFormat) exportData(v reflect.Value) interface{} {
|
||||
switch v.Kind() {
|
||||
case reflect.Ptr, reflect.Interface:
|
||||
if !v.IsNil() {
|
||||
return e.exportData(v.Elem())
|
||||
}
|
||||
case reflect.Slice:
|
||||
a := make([]interface{}, v.Len())
|
||||
for i := 0; i < v.Len(); i++ {
|
||||
a[i] = e.exportData(v.Index(i))
|
||||
}
|
||||
return a
|
||||
case reflect.Map:
|
||||
t := reflect.MapOf(v.Type().Key(), emptyInterfaceType)
|
||||
m := reflect.MakeMapWithSize(t, v.Len())
|
||||
iter := v.MapRange()
|
||||
for iter.Next() {
|
||||
ve := reflect.ValueOf(e.exportData(iter.Value()))
|
||||
m.SetMapIndex(iter.Key(), ve)
|
||||
}
|
||||
return m.Interface()
|
||||
case reflect.Struct:
|
||||
if v.CanAddr() && reflect.PtrTo(v.Type()).Implements(exportableType) {
|
||||
ve := v.Addr().Interface().(exportable)
|
||||
return ve.ExportData(e.fields)
|
||||
} else if v.Type().Implements(exportableType) {
|
||||
ve := v.Interface().(exportable)
|
||||
return ve.ExportData(e.fields)
|
||||
}
|
||||
}
|
||||
return v.Interface()
|
||||
}
|
||||
|
||||
type exportable interface {
|
||||
ExportData([]string) *map[string]interface{}
|
||||
}
|
||||
|
||||
var exportableType = reflect.TypeOf((*exportable)(nil)).Elem()
|
||||
var sliceOfEmptyInterface []interface{}
|
||||
var emptyInterfaceType = reflect.TypeOf(sliceOfEmptyInterface).Elem()
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package cmdutil
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
|
|
@ -137,6 +138,29 @@ func Test_exportFormat_Write(t *testing.T) {
|
|||
wantW: "{\"name\":\"hubot\"}\n",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "call ExportData",
|
||||
exporter: exportFormat{fields: []string{"field1", "field2"}},
|
||||
args: args{
|
||||
data: &exportableItem{"item1"},
|
||||
colorEnabled: false,
|
||||
},
|
||||
wantW: "{\"field1\":\"item1:field1\",\"field2\":\"item1:field2\"}\n",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "recursively call ExportData",
|
||||
exporter: exportFormat{fields: []string{"f1", "f2"}},
|
||||
args: args{
|
||||
data: map[string]interface{}{
|
||||
"s1": []exportableItem{{"i1"}, {"i2"}},
|
||||
"s2": []exportableItem{{"i3"}},
|
||||
},
|
||||
colorEnabled: false,
|
||||
},
|
||||
wantW: "{\"s1\":[{\"f1\":\"i1:f1\",\"f2\":\"i1:f2\"},{\"f1\":\"i2:f1\",\"f2\":\"i2:f2\"}],\"s2\":[{\"f1\":\"i3:f1\",\"f2\":\"i3:f2\"}]}\n",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "with jq filter",
|
||||
exporter: exportFormat{filter: ".name"},
|
||||
|
|
@ -166,8 +190,20 @@ func Test_exportFormat_Write(t *testing.T) {
|
|||
return
|
||||
}
|
||||
if gotW := w.String(); gotW != tt.wantW {
|
||||
t.Errorf("exportFormat.Write() = %v, want %v", gotW, tt.wantW)
|
||||
t.Errorf("exportFormat.Write() = %q, want %q", gotW, tt.wantW)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type exportableItem struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func (e *exportableItem) ExportData(fields []string) *map[string]interface{} {
|
||||
m := map[string]interface{}{}
|
||||
for _, f := range fields {
|
||||
m[f] = fmt.Sprintf("%s:%s", e.Name, f)
|
||||
}
|
||||
return &m
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue