author | Mikael Berthe <mikael@lilotux.net> |
Tue, 23 Aug 2022 22:33:28 +0200 | |
changeset 259 | db4911b0c721 |
parent 239 | 605a00e9d1ab |
child 265 | 05c40b36d3b2 |
permissions | -rw-r--r-- |
0 | 1 |
// Copyright © 2017 Mikael Berthe <mikael@lilotux.net> |
2 |
// |
|
3 |
// Licensed under the MIT license. |
|
4 |
// Please see the LICENSE file is this directory. |
|
5 |
||
6 |
package printer |
|
7 |
||
8 |
import ( |
|
9 |
"encoding/json" |
|
10 |
"fmt" |
|
11 |
"io" |
|
12 |
"os" |
|
13 |
"reflect" |
|
93
1cef5da83488
Add template functions trim and wrap
Mikael Berthe <mikael@lilotux.net>
parents:
90
diff
changeset
|
14 |
"strings" |
0 | 15 |
"text/template" |
120
54b6f2a4098b
Add template command tolocal; update templates and documentation
Mikael Berthe <mikael@lilotux.net>
parents:
93
diff
changeset
|
16 |
"time" |
0 | 17 |
|
124 | 18 |
"github.com/kr/text" |
50
c6adf9d9e996
Disable ANSI sequences when stdout is not a TTY
Mikael Berthe <mikael@lilotux.net>
parents:
49
diff
changeset
|
19 |
"github.com/mattn/go-isatty" |
40
ad148f60dc87
TemplatePrinter: Handle InstanceStatus lists
Mikael Berthe <mikael@lilotux.net>
parents:
14
diff
changeset
|
20 |
|
239
605a00e9d1ab
Switch to Go modules (and bump Go version requirement)
Mikael Berthe <mikael@lilotux.net>
parents:
194
diff
changeset
|
21 |
"github.com/McKael/madon/v2" |
49
d6b4e3b7c6c6
Add ANSI color support to templates (function "color")
Mikael Berthe <mikael@lilotux.net>
parents:
41
diff
changeset
|
22 |
"github.com/McKael/madonctl/printer/colors" |
0 | 23 |
) |
24 |
||
51
300ac09051a7
Add ability to force colors: option --color=auto|on|off
Mikael Berthe <mikael@lilotux.net>
parents:
50
diff
changeset
|
25 |
// disableColors can be set to true to disable the color template function |
300ac09051a7
Add ability to force colors: option --color=auto|on|off
Mikael Berthe <mikael@lilotux.net>
parents:
50
diff
changeset
|
26 |
var disableColors bool |
50
c6adf9d9e996
Disable ANSI sequences when stdout is not a TTY
Mikael Berthe <mikael@lilotux.net>
parents:
49
diff
changeset
|
27 |
|
0 | 28 |
// TemplatePrinter represents a Template printer |
29 |
type TemplatePrinter struct { |
|
30 |
rawTemplate string |
|
31 |
template *template.Template |
|
32 |
} |
|
33 |
||
34 |
// NewPrinterTemplate returns a Template ResourcePrinter |
|
83
57afac822019
Use a map for ResourcePrinter options
Mikael Berthe <mikael@lilotux.net>
parents:
51
diff
changeset
|
35 |
// For TemplatePrinter, the options parameter contains the template string. |
89
3758bb5f0979
ResourcePrinter (templates): Use options to pass color_mode
Mikael Berthe <mikael@lilotux.net>
parents:
85
diff
changeset
|
36 |
// The "color_mode" option defines the color behaviour: it can be |
3758bb5f0979
ResourcePrinter (templates): Use options to pass color_mode
Mikael Berthe <mikael@lilotux.net>
parents:
85
diff
changeset
|
37 |
// "auto" (default), "on" (forced), "off" (disabled). |
83
57afac822019
Use a map for ResourcePrinter options
Mikael Berthe <mikael@lilotux.net>
parents:
51
diff
changeset
|
38 |
func NewPrinterTemplate(options Options) (*TemplatePrinter, error) { |
57afac822019
Use a map for ResourcePrinter options
Mikael Berthe <mikael@lilotux.net>
parents:
51
diff
changeset
|
39 |
tmpl := options["template"] |
85 | 40 |
if tmpl == "" { |
41 |
return nil, fmt.Errorf("empty template") |
|
42 |
} |
|
41
909c3ddd83f6
Add fromunix filter to templates (converts date from UNIX timestamp)
Mikael Berthe <mikael@lilotux.net>
parents:
40
diff
changeset
|
43 |
t, err := template.New("output").Funcs(template.FuncMap{ |
909c3ddd83f6
Add fromunix filter to templates (converts date from UNIX timestamp)
Mikael Berthe <mikael@lilotux.net>
parents:
40
diff
changeset
|
44 |
"fromhtml": html2string, |
120
54b6f2a4098b
Add template command tolocal; update templates and documentation
Mikael Berthe <mikael@lilotux.net>
parents:
93
diff
changeset
|
45 |
"fromunix": unix2time, |
54b6f2a4098b
Add template command tolocal; update templates and documentation
Mikael Berthe <mikael@lilotux.net>
parents:
93
diff
changeset
|
46 |
"tolocal": dateToLocal, |
50
c6adf9d9e996
Disable ANSI sequences when stdout is not a TTY
Mikael Berthe <mikael@lilotux.net>
parents:
49
diff
changeset
|
47 |
"color": ansiColor, |
93
1cef5da83488
Add template functions trim and wrap
Mikael Berthe <mikael@lilotux.net>
parents:
90
diff
changeset
|
48 |
"trim": strings.TrimSpace, |
1cef5da83488
Add template functions trim and wrap
Mikael Berthe <mikael@lilotux.net>
parents:
90
diff
changeset
|
49 |
"wrap": wrap, |
41
909c3ddd83f6
Add fromunix filter to templates (converts date from UNIX timestamp)
Mikael Berthe <mikael@lilotux.net>
parents:
40
diff
changeset
|
50 |
}).Parse(tmpl) |
0 | 51 |
if err != nil { |
52 |
return nil, err |
|
53 |
} |
|
50
c6adf9d9e996
Disable ANSI sequences when stdout is not a TTY
Mikael Berthe <mikael@lilotux.net>
parents:
49
diff
changeset
|
54 |
|
51
300ac09051a7
Add ability to force colors: option --color=auto|on|off
Mikael Berthe <mikael@lilotux.net>
parents:
50
diff
changeset
|
55 |
// Update disableColors. |
300ac09051a7
Add ability to force colors: option --color=auto|on|off
Mikael Berthe <mikael@lilotux.net>
parents:
50
diff
changeset
|
56 |
// In auto-mode, check if stdout is a TTY. |
89
3758bb5f0979
ResourcePrinter (templates): Use options to pass color_mode
Mikael Berthe <mikael@lilotux.net>
parents:
85
diff
changeset
|
57 |
colorMode := options["color_mode"] |
3758bb5f0979
ResourcePrinter (templates): Use options to pass color_mode
Mikael Berthe <mikael@lilotux.net>
parents:
85
diff
changeset
|
58 |
if colorMode == "off" || (colorMode != "on" && !isatty.IsTerminal(os.Stdout.Fd())) { |
51
300ac09051a7
Add ability to force colors: option --color=auto|on|off
Mikael Berthe <mikael@lilotux.net>
parents:
50
diff
changeset
|
59 |
disableColors = true |
50
c6adf9d9e996
Disable ANSI sequences when stdout is not a TTY
Mikael Berthe <mikael@lilotux.net>
parents:
49
diff
changeset
|
60 |
} |
c6adf9d9e996
Disable ANSI sequences when stdout is not a TTY
Mikael Berthe <mikael@lilotux.net>
parents:
49
diff
changeset
|
61 |
|
0 | 62 |
return &TemplatePrinter{ |
63 |
rawTemplate: tmpl, |
|
64 |
template: t, |
|
65 |
}, nil |
|
66 |
} |
|
67 |
||
68 |
// PrintObj sends the object as text to the writer |
|
69 |
// If the writer w is nil, standard output will be used. |
|
70 |
func (p *TemplatePrinter) PrintObj(obj interface{}, w io.Writer, tmpl string) error { |
|
71 |
if w == nil { |
|
72 |
w = os.Stdout |
|
73 |
} |
|
74 |
||
75 |
if p.template == nil { |
|
76 |
return fmt.Errorf("template not built") |
|
77 |
} |
|
78 |
||
79 |
switch ot := obj.(type) { // I wish I knew a better way... |
|
193
4a1e3b57fd0f
Themes & Templates: Add new types
Mikael Berthe <mikael@lilotux.net>
parents:
124
diff
changeset
|
80 |
case []madon.Account, []madon.Application, []madon.Attachment, |
4a1e3b57fd0f
Themes & Templates: Add new types
Mikael Berthe <mikael@lilotux.net>
parents:
124
diff
changeset
|
81 |
[]madon.Card, []madon.Client, []madon.Context, []madon.Emoji, |
4a1e3b57fd0f
Themes & Templates: Add new types
Mikael Berthe <mikael@lilotux.net>
parents:
124
diff
changeset
|
82 |
[]madon.Instance, []madon.List, []madon.Mention, |
0 | 83 |
[]madon.Notification, []madon.Relationship, []madon.Report, |
193
4a1e3b57fd0f
Themes & Templates: Add new types
Mikael Berthe <mikael@lilotux.net>
parents:
124
diff
changeset
|
84 |
[]madon.Results, []madon.Status, []madon.StreamEvent, |
194
660233815ca8
Printers: Remove previous instance statistics
Mikael Berthe <mikael@lilotux.net>
parents:
193
diff
changeset
|
85 |
[]madon.Tag, []string: |
0 | 86 |
return p.templateForeach(ot, w) |
87 |
} |
|
88 |
||
89 |
return p.templatePrintSingleObj(obj, w) |
|
90 |
} |
|
91 |
||
92 |
func (p *TemplatePrinter) templatePrintSingleObj(obj interface{}, w io.Writer) error { |
|
90
b1da6dc9f689
TemplatePrinter: Add support for string objects
Mikael Berthe <mikael@lilotux.net>
parents:
89
diff
changeset
|
93 |
if s, ok := obj.(string); ok { |
b1da6dc9f689
TemplatePrinter: Add support for string objects
Mikael Berthe <mikael@lilotux.net>
parents:
89
diff
changeset
|
94 |
// obj is a simple string |
b1da6dc9f689
TemplatePrinter: Add support for string objects
Mikael Berthe <mikael@lilotux.net>
parents:
89
diff
changeset
|
95 |
if err := p.safeExecute(w, s); err != nil { |
b1da6dc9f689
TemplatePrinter: Add support for string objects
Mikael Berthe <mikael@lilotux.net>
parents:
89
diff
changeset
|
96 |
return fmt.Errorf("error executing template %q: %v", p.rawTemplate, err) |
b1da6dc9f689
TemplatePrinter: Add support for string objects
Mikael Berthe <mikael@lilotux.net>
parents:
89
diff
changeset
|
97 |
} |
b1da6dc9f689
TemplatePrinter: Add support for string objects
Mikael Berthe <mikael@lilotux.net>
parents:
89
diff
changeset
|
98 |
return nil |
b1da6dc9f689
TemplatePrinter: Add support for string objects
Mikael Berthe <mikael@lilotux.net>
parents:
89
diff
changeset
|
99 |
} |
b1da6dc9f689
TemplatePrinter: Add support for string objects
Mikael Berthe <mikael@lilotux.net>
parents:
89
diff
changeset
|
100 |
|
0 | 101 |
// This code comes from Kubernetes. |
102 |
data, err := json.Marshal(obj) |
|
103 |
if err != nil { |
|
104 |
return err |
|
105 |
} |
|
106 |
out := map[string]interface{}{} |
|
107 |
if err := json.Unmarshal(data, &out); err != nil { |
|
108 |
return err |
|
109 |
} |
|
110 |
if err = p.safeExecute(w, out); err != nil { |
|
111 |
return fmt.Errorf("error executing template %q: %v", p.rawTemplate, err) |
|
112 |
} |
|
113 |
return nil |
|
114 |
} |
|
115 |
||
116 |
// safeExecute tries to execute the template, but catches panics and returns an error |
|
117 |
// should the template engine panic. |
|
118 |
// This code comes from Kubernetes. |
|
119 |
func (p *TemplatePrinter) safeExecute(w io.Writer, obj interface{}) error { |
|
120 |
var panicErr error |
|
121 |
// Sorry for the double anonymous function. There's probably a clever way |
|
122 |
// to do this that has the defer'd func setting the value to be returned, but |
|
123 |
// that would be even less obvious. |
|
124 |
retErr := func() error { |
|
125 |
defer func() { |
|
126 |
if x := recover(); x != nil { |
|
127 |
panicErr = fmt.Errorf("caught panic: %+v", x) |
|
128 |
} |
|
129 |
}() |
|
130 |
return p.template.Execute(w, obj) |
|
131 |
}() |
|
132 |
if panicErr != nil { |
|
133 |
return panicErr |
|
134 |
} |
|
135 |
return retErr |
|
136 |
} |
|
137 |
||
138 |
func (p *TemplatePrinter) templateForeach(ol interface{}, w io.Writer) error { |
|
139 |
switch reflect.TypeOf(ol).Kind() { |
|
140 |
case reflect.Slice: |
|
141 |
s := reflect.ValueOf(ol) |
|
142 |
||
143 |
for i := 0; i < s.Len(); i++ { |
|
144 |
o := s.Index(i).Interface() |
|
145 |
if err := p.templatePrintSingleObj(o, w); err != nil { |
|
146 |
return err |
|
147 |
} |
|
148 |
} |
|
149 |
} |
|
150 |
return nil |
|
151 |
} |
|
50
c6adf9d9e996
Disable ANSI sequences when stdout is not a TTY
Mikael Berthe <mikael@lilotux.net>
parents:
49
diff
changeset
|
152 |
|
c6adf9d9e996
Disable ANSI sequences when stdout is not a TTY
Mikael Berthe <mikael@lilotux.net>
parents:
49
diff
changeset
|
153 |
func ansiColor(desc string) (string, error) { |
51
300ac09051a7
Add ability to force colors: option --color=auto|on|off
Mikael Berthe <mikael@lilotux.net>
parents:
50
diff
changeset
|
154 |
if disableColors { |
50
c6adf9d9e996
Disable ANSI sequences when stdout is not a TTY
Mikael Berthe <mikael@lilotux.net>
parents:
49
diff
changeset
|
155 |
return "", nil |
c6adf9d9e996
Disable ANSI sequences when stdout is not a TTY
Mikael Berthe <mikael@lilotux.net>
parents:
49
diff
changeset
|
156 |
} |
c6adf9d9e996
Disable ANSI sequences when stdout is not a TTY
Mikael Berthe <mikael@lilotux.net>
parents:
49
diff
changeset
|
157 |
return colors.ANSICodeString(desc) |
c6adf9d9e996
Disable ANSI sequences when stdout is not a TTY
Mikael Berthe <mikael@lilotux.net>
parents:
49
diff
changeset
|
158 |
} |
93
1cef5da83488
Add template functions trim and wrap
Mikael Berthe <mikael@lilotux.net>
parents:
90
diff
changeset
|
159 |
|
120
54b6f2a4098b
Add template command tolocal; update templates and documentation
Mikael Berthe <mikael@lilotux.net>
parents:
93
diff
changeset
|
160 |
// Parse datetime string from RFC3339 (default format in templates because |
54b6f2a4098b
Add template command tolocal; update templates and documentation
Mikael Berthe <mikael@lilotux.net>
parents:
93
diff
changeset
|
161 |
// of implicit conversion to string) and return a local time. |
54b6f2a4098b
Add template command tolocal; update templates and documentation
Mikael Berthe <mikael@lilotux.net>
parents:
93
diff
changeset
|
162 |
func dateToLocal(s string) (time.Time, error) { |
54b6f2a4098b
Add template command tolocal; update templates and documentation
Mikael Berthe <mikael@lilotux.net>
parents:
93
diff
changeset
|
163 |
t, err := time.Parse(time.RFC3339, s) |
54b6f2a4098b
Add template command tolocal; update templates and documentation
Mikael Berthe <mikael@lilotux.net>
parents:
93
diff
changeset
|
164 |
if err != nil { |
54b6f2a4098b
Add template command tolocal; update templates and documentation
Mikael Berthe <mikael@lilotux.net>
parents:
93
diff
changeset
|
165 |
return t, err |
54b6f2a4098b
Add template command tolocal; update templates and documentation
Mikael Berthe <mikael@lilotux.net>
parents:
93
diff
changeset
|
166 |
} |
54b6f2a4098b
Add template command tolocal; update templates and documentation
Mikael Berthe <mikael@lilotux.net>
parents:
93
diff
changeset
|
167 |
return t.Local(), err |
54b6f2a4098b
Add template command tolocal; update templates and documentation
Mikael Berthe <mikael@lilotux.net>
parents:
93
diff
changeset
|
168 |
} |
124 | 169 |
|
170 |
// Wrap text with indent prefix |
|
171 |
func wrap(indent string, lineLength int, txt string) string { |
|
172 |
width := lineLength - len(indent) |
|
173 |
if width < 10 { |
|
174 |
width = 10 |
|
175 |
} |
|
176 |
||
177 |
lines := strings.SplitAfter(txt, "\n") |
|
178 |
var out []string |
|
179 |
for _, l := range lines { |
|
180 |
l = strings.TrimLeft(l, " ") |
|
181 |
out = append(out, text.Indent(text.Wrap(l, width), indent)) |
|
182 |
} |
|
183 |
return strings.Join(out, "\n") |
|
184 |
} |