204 lines
4.5 KiB
Go
204 lines
4.5 KiB
Go
package export
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
"text/template"
|
|
"time"
|
|
|
|
"github.com/cli/cli/v2/pkg/iostreams"
|
|
"github.com/cli/cli/v2/pkg/text"
|
|
"github.com/cli/cli/v2/utils"
|
|
"github.com/mgutz/ansi"
|
|
)
|
|
|
|
type Template struct {
|
|
io *iostreams.IOStreams
|
|
tablePrinter utils.TablePrinter
|
|
template *template.Template
|
|
templateStr string
|
|
}
|
|
|
|
func NewTemplate(io *iostreams.IOStreams, template string) Template {
|
|
return Template{
|
|
io: io,
|
|
templateStr: template,
|
|
}
|
|
}
|
|
|
|
func (t *Template) parseTemplate(tpl string) (*template.Template, error) {
|
|
now := time.Now()
|
|
|
|
templateFuncs := map[string]interface{}{
|
|
"color": t.color,
|
|
"autocolor": t.color,
|
|
|
|
"timefmt": func(format, input string) (string, error) {
|
|
t, err := time.Parse(time.RFC3339, input)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return t.Format(format), nil
|
|
},
|
|
"timeago": func(input string) (string, error) {
|
|
t, err := time.Parse(time.RFC3339, input)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return timeAgo(now.Sub(t)), nil
|
|
},
|
|
|
|
"pluck": templatePluck,
|
|
"join": templateJoin,
|
|
"tablerow": t.tableRow,
|
|
"tablerender": t.tableRender,
|
|
"truncate": text.Truncate,
|
|
}
|
|
|
|
if !t.io.ColorEnabled() {
|
|
templateFuncs["autocolor"] = func(colorName string, input interface{}) (string, error) {
|
|
return jsonScalarToString(input)
|
|
}
|
|
}
|
|
|
|
return template.New("").Funcs(templateFuncs).Parse(tpl)
|
|
}
|
|
|
|
func (t *Template) Execute(input io.Reader) error {
|
|
w := t.io.Out
|
|
|
|
if t.template == nil {
|
|
template, err := t.parseTemplate(t.templateStr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
t.template = template
|
|
}
|
|
|
|
jsonData, err := ioutil.ReadAll(input)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var data interface{}
|
|
if err := json.Unmarshal(jsonData, &data); err != nil {
|
|
return err
|
|
}
|
|
|
|
return t.template.Execute(w, data)
|
|
}
|
|
|
|
func ExecuteTemplate(io *iostreams.IOStreams, input io.Reader, template string) error {
|
|
t := NewTemplate(io, template)
|
|
if err := t.Execute(input); err != nil {
|
|
return err
|
|
}
|
|
return t.End()
|
|
}
|
|
|
|
func jsonScalarToString(input interface{}) (string, error) {
|
|
switch tt := input.(type) {
|
|
case string:
|
|
return tt, nil
|
|
case float64:
|
|
if math.Trunc(tt) == tt {
|
|
return strconv.FormatFloat(tt, 'f', 0, 64), nil
|
|
} else {
|
|
return strconv.FormatFloat(tt, 'f', 2, 64), nil
|
|
}
|
|
case nil:
|
|
return "", nil
|
|
case bool:
|
|
return fmt.Sprintf("%v", tt), nil
|
|
default:
|
|
return "", fmt.Errorf("cannot convert type to string: %v", tt)
|
|
}
|
|
}
|
|
|
|
func (t *Template) color(colorName string, input interface{}) (string, error) {
|
|
text, err := jsonScalarToString(input)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return ansi.Color(text, colorName), nil
|
|
}
|
|
|
|
func templatePluck(field string, input []interface{}) []interface{} {
|
|
var results []interface{}
|
|
for _, item := range input {
|
|
obj := item.(map[string]interface{})
|
|
results = append(results, obj[field])
|
|
}
|
|
return results
|
|
}
|
|
|
|
func templateJoin(sep string, input []interface{}) (string, error) {
|
|
var results []string
|
|
for _, item := range input {
|
|
text, err := jsonScalarToString(item)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
results = append(results, text)
|
|
}
|
|
return strings.Join(results, sep), nil
|
|
}
|
|
|
|
func (t *Template) tableRow(fields ...interface{}) (string, error) {
|
|
if t.tablePrinter == nil {
|
|
t.tablePrinter = utils.NewTablePrinterWithOptions(t.io, utils.TablePrinterOptions{IsTTY: true})
|
|
}
|
|
for _, e := range fields {
|
|
s, err := jsonScalarToString(e)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to write table row: %v", err)
|
|
}
|
|
t.tablePrinter.AddField(s, text.TruncateColumn, nil)
|
|
}
|
|
t.tablePrinter.EndRow()
|
|
return "", nil
|
|
}
|
|
|
|
func (t *Template) tableRender() (string, error) {
|
|
if t.tablePrinter != nil {
|
|
err := t.tablePrinter.Render()
|
|
t.tablePrinter = nil
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to render table: %v", err)
|
|
}
|
|
}
|
|
return "", nil
|
|
}
|
|
|
|
func (t *Template) End() error {
|
|
// Finalize any template actions.
|
|
if _, err := t.tableRender(); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func timeAgo(ago time.Duration) string {
|
|
if ago < time.Minute {
|
|
return "just now"
|
|
}
|
|
if ago < time.Hour {
|
|
return utils.Pluralize(int(ago.Minutes()), "minute") + " ago"
|
|
}
|
|
if ago < 24*time.Hour {
|
|
return utils.Pluralize(int(ago.Hours()), "hour") + " ago"
|
|
}
|
|
if ago < 30*24*time.Hour {
|
|
return utils.Pluralize(int(ago.Hours())/24, "day") + " ago"
|
|
}
|
|
if ago < 365*24*time.Hour {
|
|
return utils.Pluralize(int(ago.Hours())/24/30, "month") + " ago"
|
|
}
|
|
return utils.Pluralize(int(ago.Hours()/24/365), "year") + " ago"
|
|
}
|