From 7f8171d8041f425b7f8d6a43eb33c53378c861fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Mon, 9 Nov 2020 14:50:06 +0100 Subject: [PATCH 01/18] Add skeleton code+test for markdown help --- markdown.go | 55 ++++++++++++++++++++++++++++++++++++++++++++++++ markdown_test.go | 33 +++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 markdown.go create mode 100644 markdown_test.go diff --git a/markdown.go b/markdown.go new file mode 100644 index 00000000..42c67181 --- /dev/null +++ b/markdown.go @@ -0,0 +1,55 @@ +package baker + +import ( + "fmt" + "io" + "reflect" +) + +// GenerateMarkdownHelp generates markdown-formatted textual help for a Baker +// component from its description structure. Markdown is written into w. +func GenerateMarkdownHelp(w io.Writer, desc interface{}) error { + if desc == nil { + return fmt.Errorf("can't generate markdown help for a nil interface") + } + + if reflect.TypeOf(desc).Kind() == reflect.Ptr { + // dereference pointer + desc = reflect.ValueOf(desc).Elem().Interface() + } + + switch d := desc.(type) { + case InputDesc: + return genInputMarkdown(w, d) + case FilterDesc: + return genFilterMarkdown(w, d) + case OutputDesc: + return genOutputMarkdown(w, d) + case UploadDesc: + return genUploadMarkdown(w, d) + case MetricsDesc: + return genMetricsMarkdown(w, d) + } + + return fmt.Errorf("can't generate markdown, unsupported type %T", desc) +} + +func genInputMarkdown(w io.Writer, desc InputDesc) error { + return nil +} + +func genFilterMarkdown(w io.Writer, desc FilterDesc) error { + return nil +} + +func genOutputMarkdown(w io.Writer, desc OutputDesc) error { + return nil +} + +func genUploadMarkdown(w io.Writer, desc UploadDesc) error { + return nil +} + +func genMetricsMarkdown(w io.Writer, desc MetricsDesc) error { + return nil +} diff --git a/markdown_test.go b/markdown_test.go new file mode 100644 index 00000000..9f0b9f2e --- /dev/null +++ b/markdown_test.go @@ -0,0 +1,33 @@ +package baker + +import ( + "bytes" + "testing" +) + +func TestGenerateMarkdownHelp(t *testing.T) { + tests := []struct { + name string + desc interface{} + want string + wantErr bool + }{ + { + name: "nil", + desc: nil, + wantErr: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + w := &bytes.Buffer{} + if err := GenerateMarkdownHelp(w, tt.desc); (err != nil) != tt.wantErr { + t.Errorf("GenerateMarkdownHelp() error = %v, wantErr %v", err, tt.wantErr) + return + } + if gotW := w.String(); gotW != tt.want { + t.Errorf("GenerateMarkdownHelp() = %v, want %v", gotW, tt.want) + } + }) + } +} From 0ab222b0c4bbc2c6bdea64379632abcae6238a82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Mon, 9 Nov 2020 14:51:18 +0100 Subject: [PATCH 02/18] markdown help: handle unsupported types --- markdown_test.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/markdown_test.go b/markdown_test.go index 9f0b9f2e..beb2c287 100644 --- a/markdown_test.go +++ b/markdown_test.go @@ -17,6 +17,11 @@ func TestGenerateMarkdownHelp(t *testing.T) { desc: nil, wantErr: true, }, + { + name: "unsupported type", + desc: 23, + wantErr: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { From d2e2dfcf4a614f53950504b7e3a6927f4f2d26c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Mon, 9 Nov 2020 19:17:18 +0100 Subject: [PATCH 03/18] Add doc structures for generation and fill them --- help.go | 162 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) diff --git a/help.go b/help.go index e87b2e24..a7a3f3ea 100644 --- a/help.go +++ b/help.go @@ -129,6 +129,168 @@ func PrintHelp(w io.Writer, name string, comp Components) { } } +type baseDoc struct { + name string // component name + help string // general help string + keys []helpConfigKey // configuration keys +} + +type inputDoc struct{ baseDoc } +type filterDoc struct{ baseDoc } +type uploadDoc struct{ baseDoc } + +type outputDoc struct { + baseDoc + raw bool // raw output? +} + +func newInputDoc(desc InputDesc) (inputDoc, error) { + doc := inputDoc{ + baseDoc{ + name: desc.Name, + help: desc.Help, + }, + } + + var err error + + doc.keys, err = configKeysFromStruct(desc.Config) + if err != nil { + return doc, fmt.Errorf("input %q: %v", desc.Name, err) + } + + return doc, nil +} + +func newFilterDoc(desc FilterDesc) (filterDoc, error) { + doc := filterDoc{ + baseDoc{ + name: desc.Name, + help: desc.Help, + }, + } + + var err error + + doc.keys, err = configKeysFromStruct(desc.Config) + if err != nil { + return doc, fmt.Errorf("filter %q: %v", desc.Name, err) + } + + return doc, nil +} + +func newOutputDoc(desc OutputDesc) (outputDoc, error) { + doc := outputDoc{ + raw: desc.Raw, + baseDoc: baseDoc{ + name: desc.Name, + help: desc.Help, + }, + } + + var err error + + doc.keys, err = configKeysFromStruct(desc.Config) + if err != nil { + return doc, fmt.Errorf("output %q: %v", desc.Name, err) + } + + return doc, nil +} + +func newUploadDoc(desc UploadDesc) (uploadDoc, error) { + doc := uploadDoc{ + baseDoc{ + name: desc.Name, + help: desc.Help, + }, + } + + var err error + + doc.keys, err = configKeysFromStruct(desc.Config) + if err != nil { + return doc, fmt.Errorf("upload %q: %v", desc.Name, err) + } + + return doc, nil +} + +type helpConfigKey struct { + name string // config key name + typ string // config key type + def string // default value + required bool + desc string +} + +func configKeysFromStruct(cfg interface{}) ([]helpConfigKey, error) { + var keys []helpConfigKey + + tf := reflect.TypeOf(cfg).Elem() + for i := 0; i < tf.NumField(); i++ { + f := tf.Field(i) + + // skip unexported fields + if f.PkgPath != "" && !f.Anonymous { + continue + } + + key, err := newHelpConfigKeyFromField(f) + if err != nil { + return nil, fmt.Errorf("error at exported key %d: %v", i, err) + } + keys = append(keys, key) + } + + return keys, nil +} + +func newHelpConfigKeyFromField(f reflect.StructField) (helpConfigKey, error) { + h := helpConfigKey{ + name: f.Name, + desc: f.Tag.Get("help"), + def: f.Tag.Get("default"), + required: f.Tag.Get("required") == "true", + } + + if err := h.fillType(f); err != nil { + return h, err + } + + return h, nil +} + +func (h helpConfigKey) fillType(f reflect.StructField) error { + switch f.Type.Kind() { + case reflect.Int: + h.typ = "int" + case reflect.String: + h.typ = "string" + case reflect.Slice: + switch f.Type.Elem().Kind() { + case reflect.String: + h.typ = "array of strings" + case reflect.Int: + h.typ = "array of ints" + default: + return fmt.Errorf("config key %q: unsupported type array of %s", f.Type.Name(), f.Type.Elem()) + } + case reflect.Int64: + if f.Type.Name() == "Duration" { + h.typ = "duration" + } else { + h.typ = "int" + } + case reflect.Bool: + h.typ = "bool" + default: + return fmt.Errorf("config key %q: unsupported type", f.Type.Name()) + } + + return nil +} func dumpConfigHelp(w io.Writer, cfg interface{}) { const sfmt = "%-18s | %-18s | %-18s | %-8s | " const sep = "----------------------------------------------------------------------------------------------------" From e2546d0ecbf31ce551c472ccd3ac0dc34880e3e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Mon, 9 Nov 2020 19:18:21 +0100 Subject: [PATCH 04/18] Refactor help rendering and support markdown --- baker_cli.go | 3 +- go.mod | 2 +- go.sum | 40 +++++++++ help.go | 227 +++++++++++++++++++++++++++++++++-------------- markdown.go | 55 ------------ markdown_help.go | 150 +++++++++++++++++++++++++++++++ text_help.go | 180 +++++++++++++++++++++++++++++++++++++ 7 files changed, 532 insertions(+), 125 deletions(-) delete mode 100644 markdown.go create mode 100644 markdown_help.go create mode 100644 text_help.go diff --git a/baker_cli.go b/baker_cli.go index 857b5c1c..e837ea51 100644 --- a/baker_cli.go +++ b/baker_cli.go @@ -47,8 +47,7 @@ func MainCLI(components Components) error { flag.Parse() if *flagHelpConfig != "" { - PrintHelp(os.Stderr, *flagHelpConfig, components) - return nil + return PrintHelp(os.Stderr, *flagHelpConfig, components, HelpFormatMarkdown) } if *flagPProf != "" { diff --git a/go.mod b/go.mod index 8b70287a..10f0a7d9 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/DataDog/datadog-go v3.2.0+incompatible github.com/aws/aws-sdk-go v1.25.6-0.20191003182926-f28610301a1e github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b + github.com/charmbracelet/glamour v0.2.0 github.com/google/uuid v1.1.2-0.20190416172445-c2e93f3ae59f github.com/jpillora/backoff v0.0.0-20160301230512-f85df8d02bdf github.com/juju/ratelimit v0.0.0-20151125201925-77ed1c8a0121 @@ -20,5 +21,4 @@ require ( github.com/valyala/gozstd v1.7.0 github.com/vmware/vmware-go-kcl v0.0.0-20200605022506-bd2980e951b3 golang.org/x/net v0.0.0-20190522155817-f3200d17e092 - golang.org/x/sys v0.0.0-20191003212358-c178f38b412c // indirect ) diff --git a/go.sum b/go.sum index 6a095a69..3aadba96 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,12 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= +github.com/alecthomas/chroma v0.7.3 h1:NfdAERMy+esYQs8OXk0I868/qDxxCEo7FMz1WIqMAeI= +github.com/alecthomas/chroma v0.7.3/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM= +github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= +github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE= +github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/aws/aws-sdk-go v1.19.38/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= @@ -15,10 +21,16 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b h1:AP/Y7sqYicnjGDfD5VcY4CIfh1hRXBUavxrvELjTiOE= github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b/go.mod h1:ac9efd0D1fsDb3EJvhqgXRbFx7bs2wqZ10HQPeU8U/Q= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/charmbracelet/glamour v0.2.0 h1:mTgaiNiumpqTZp3qVM6DH9UB0NlbY17wejoMf1kM8Pg= +github.com/charmbracelet/glamour v0.2.0/go.mod h1:UA27Kwj3QHialP74iU6C+Gpc8Y7IOAKupeKMLLBURWM= +github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= +github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk= +github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/frankban/quicktest v1.4.0 h1:rCSCih1FnSWJEel/eub9wclBSqpF2F/PuvxUWGWnbO8= github.com/frankban/quicktest v1.4.0/go.mod h1:36zfPVQyHxymz4cH7wlDmVwDrJuljRB60qkgn7rorfQ= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -32,6 +44,8 @@ github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f h1:5CjVwnuUcp5adK4gmY6i72gpVFVnZDP2h5TmPScB6u4= +github.com/google/goterm v0.0.0-20190703233501-fc88cf888a3f/go.mod h1:nOFQdrUlIlx6M6ODdSpBj1NVA+VgLC6kmw60mkw34H4= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2-0.20190416172445-c2e93f3ae59f h1:XXzyYlFbxK3kWfcmu3Wc+Tv8/QQl/VqwsWuSYF1Rj0s= github.com/google/uuid v1.1.2-0.20190416172445-c2e93f3ae59f/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -58,15 +72,31 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac= +github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.14.3 h1:j7a/xn1U6TKA/PHHxqZuzh64CdtRc7rU9M+AvkOl5bA= github.com/mattn/go-sqlite3 v1.14.3/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s= +github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw= +github.com/muesli/reflow v0.1.0 h1:oQdpLfO56lr5pgLvqD0TcjW85rDjSYSBVdiG1Ch1ddM= +github.com/muesli/reflow v0.1.0/go.mod h1:I9bWAt7QTg/que/qmUCJBGlj7wEq8OAFBjPNjc6xK4I= +github.com/muesli/termenv v0.6.0 h1:zxvzTBmo4ZcxhNGGWeMz+Tttm51eF5bmPjfy4MCRYlk= +github.com/muesli/termenv v0.6.0/go.mod h1:SohX91w6swWA4AYU+QmPx+aSgXhWO0juiyID9UZmbpA= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/nsf/sexp v0.0.0-20130620094510-d3d2f2591f1d h1:WpcnBFcUqwixsy144U4Z1OwsVhNfsCUgwDhFC9GQ6Lg= github.com/nsf/sexp v0.0.0-20130620094510-d3d2f2591f1d/go.mod h1:j03Rsepo03350Rt/HWJob95loRlZfQoivqII1FvfP+I= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/olekukonko/tablewriter v0.0.4 h1:vHD/YYe1Wolo78koG299f7V/VAS08c6IpCLn+Ejf/w8= +github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= @@ -75,6 +105,8 @@ github.com/pierrec/cmdflag v0.0.2/go.mod h1:a3zKGZ3cdQUfxjd0RGMLZr8xI3nvpJOB+m6o github.com/pierrec/lz4/v3 v3.3.2 h1:QTUOCbMNDbK4PYtkuHyOBd28C0UhPBw3T4OH4WpFDik= github.com/pierrec/lz4/v3 v3.3.2/go.mod h1:280XNCGS8jAcG++AHdd6SeWnzyJ1w9oow2vbORyey8Q= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -95,6 +127,7 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T github.com/rasky/toml v0.1.1-0.20160309013025-90bcb678a72a h1:Rbac1N2pVUVpc/StliFKe/Ze677rTJXah54a24EYggY= github.com/rasky/toml v0.1.1-0.20160309013025-90bcb678a72a/go.mod h1:8gCi4R7MCILewoZRV5Zw1SP1JnlKl+rLSM35uMF//VQ= github.com/schollz/progressbar/v2 v2.13.2/go.mod h1:6YZjqdthH6SCZKv2rqGryrxPtfmRB/DWZxSMfCXPyD8= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= @@ -109,6 +142,8 @@ github.com/valyala/gozstd v1.7.0 h1:Ljh5c9zboqLhwTI33al32R72iCZfn0mCbVGcFWbGwRQ= github.com/valyala/gozstd v1.7.0/go.mod h1:y5Ew47GLlP37EkTB+B4s7r6A5rdaeB7ftbl9zoYiIPQ= github.com/vmware/vmware-go-kcl v0.0.0-20200605022506-bd2980e951b3 h1:IxhYHqOckMG6XSxCMr1yS0NUvQIU6QLcEODmuw2caSA= github.com/vmware/vmware-go-kcl v0.0.0-20200605022506-bd2980e951b3/go.mod h1:JFn5wAwfmRZgv/VScA9aUc51zOVL5395yPKGxPi3eNo= +github.com/yuin/goldmark v1.2.0 h1:WOOcyaJPlzb8fZ8TloxFe8QZkhOOJx87leDa9MIT9dc= +github.com/yuin/goldmark v1.2.0/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.2.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.11.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= @@ -116,6 +151,7 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -130,6 +166,10 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190528012530-adf421d2caf4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191003212358-c178f38b412c h1:6Zx7DRlKXf79yfxuQ/7GqV3w2y7aDsk6bGg0MzF5RVU= golang.org/x/sys v0.0.0-20191003212358-c178f38b412c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY= +golang.org/x/sys v0.0.0-20200413165638-669c56c373c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= diff --git a/help.go b/help.go index a7a3f3ea..24db1d47 100644 --- a/help.go +++ b/help.go @@ -4,10 +4,22 @@ import ( "bytes" "fmt" "io" - "os" "reflect" "strings" "unicode" + + "github.com/charmbracelet/glamour" +) + +// HelpFormat represents the possible formats for baker help. +type HelpFormat int + +const ( + // HelpFormatRaw is for raw-formatted help. + HelpFormatRaw HelpFormat = iota + + // HelpFormatMarkdown is for markdown formatted help. + HelpFormatMarkdown ) // PrintHelp prints the help message for the given component, identified by its name. @@ -41,92 +53,89 @@ import ( // ---------------------------------------------------------------------------------------------------- // Listener | string | | Host:Port to bind to // ---------------------------------------------------------------------------------------------------- -func PrintHelp(w io.Writer, name string, comp Components) { +func PrintHelp(w io.Writer, name string, comp Components, format HelpFormat) error { dumpall := name == "*" + ww := w // wraps w + + generateHelp := GenerateTextHelp + if format == HelpFormatMarkdown { + generateHelp = GenerateMarkdownHelp + + buf := &bytes.Buffer{} + ww = buf + defer func() { + + // TODO(arl) this is temporary + r, _ := glamour.NewTermRenderer( + // detect background color and pick either the default dark or light theme + glamour.WithAutoStyle(), + // wrap output at specific width + glamour.WithWordWrap(180), + ) + + out, err := r.Render(buf.String()) + if err != nil { + panic(err) + } + // Should copy to w (original writer) and not to os.Stdout + fmt.Print(out) + }() + } + for _, inp := range comp.Inputs { if strings.EqualFold(inp.Name, name) || dumpall { - fmt.Fprintf(w, "=============================================\n") - fmt.Fprintf(w, "Input: %s\n", inp.Name) - fmt.Fprintf(w, "=============================================\n") - fmt.Fprintf(w, inp.Help) - if hasConfig(inp.Config) { - fmt.Fprintf(w, "\nKeys available in the [input.config] section:\n\n") - dumpConfigHelp(w, inp.Config) - } else { - fmt.Fprintf(w, "\n(no configuration available)\n\n") + if err := generateHelp(ww, inp); err != nil { + return fmt.Errorf("can't print help for %q input: %v", inp.Name, err) } - fmt.Fprintln(w) - fmt.Fprintln(w) if !dumpall { - return + return nil } } } for _, fil := range comp.Filters { if strings.EqualFold(fil.Name, name) || dumpall { - fmt.Fprintf(w, "=============================================\n") - fmt.Fprintf(w, "Filter: %s\n", fil.Name) - fmt.Fprintf(w, "=============================================\n") - fmt.Fprintf(w, fil.Help) - if hasConfig(fil.Config) { - fmt.Fprintf(w, "\nKeys available in the [filter.config] section:\n\n") - dumpConfigHelp(w, fil.Config) - } else { - fmt.Fprintf(w, "\n(no configuration available)\n\n") + if err := generateHelp(ww, fil); err != nil { + return fmt.Errorf("can't print help for %q filter: %v", fil.Name, err) } - fmt.Fprintln(w) - fmt.Fprintln(w) if !dumpall { - return + return nil } } } for _, out := range comp.Outputs { if strings.EqualFold(out.Name, name) || dumpall { - fmt.Fprintf(w, "=============================================\n") - fmt.Fprintf(w, "Output: %s\n", out.Name) - fmt.Fprintf(w, "=============================================\n") - fmt.Fprintf(w, out.Help) - if hasConfig(out.Config) { - fmt.Fprintf(w, "\nKeys available in the [output.config] section:\n\n") - dumpConfigHelp(w, out.Config) - } else { - fmt.Fprintf(w, "\n(no configuration available)\n\n") - } - fmt.Fprintln(w) - fmt.Fprintln(w) - if !dumpall { - return + if strings.EqualFold(out.Name, name) || dumpall { + if err := generateHelp(ww, out); err != nil { + return fmt.Errorf("can't print help for %q output: %v", out.Name, err) + } + if !dumpall { + return nil + } } } } for _, upl := range comp.Uploads { if strings.EqualFold(upl.Name, name) || dumpall { - fmt.Fprintf(w, "=============================================\n") - fmt.Fprintf(w, "Upload: %s\n", upl.Name) - fmt.Fprintf(w, "=============================================\n") - fmt.Fprintf(w, upl.Help) - if hasConfig(upl.Config) { - fmt.Fprintf(w, "\nKeys available in the [upload.config] section:\n\n") - dumpConfigHelp(w, upl.Config) - } else { - fmt.Fprintf(w, "\n(no configuration available)\n\n") - } - fmt.Fprintln(w) - fmt.Fprintln(w) - if !dumpall { - return + if strings.EqualFold(upl.Name, name) || dumpall { + if err := generateHelp(ww, upl); err != nil { + return fmt.Errorf("can't print help for %q upload: %v", upl.Name, err) + } + if !dumpall { + return nil + } } } } if !dumpall { - fmt.Fprintf(os.Stderr, "Component not found: %s\n", name) + return fmt.Errorf("component not found: %s", name) } + + return nil } type baseDoc struct { @@ -144,6 +153,11 @@ type outputDoc struct { raw bool // raw output? } +type metricsDoc struct { + name string // component name + keys []helpConfigKey // configuration keys +} + func newInputDoc(desc InputDesc) (inputDoc, error) { doc := inputDoc{ baseDoc{ @@ -217,6 +231,21 @@ func newUploadDoc(desc UploadDesc) (uploadDoc, error) { return doc, nil } +func newMetricsDoc(desc MetricsDesc) (metricsDoc, error) { + doc := metricsDoc{ + name: desc.Name, + } + + var err error + + doc.keys, err = configKeysFromStruct(desc.Config) + if err != nil { + return doc, fmt.Errorf("metrics %q: %v", desc.Name, err) + } + + return doc, nil +} + type helpConfigKey struct { name string // config key name typ string // config key type @@ -255,19 +284,12 @@ func newHelpConfigKeyFromField(f reflect.StructField) (helpConfigKey, error) { required: f.Tag.Get("required") == "true", } - if err := h.fillType(f); err != nil { - return h, err - } - - return h, nil -} - -func (h helpConfigKey) fillType(f reflect.StructField) error { switch f.Type.Kind() { case reflect.Int: h.typ = "int" case reflect.String: h.typ = "string" + h.def = `"` + h.def + `"` case reflect.Slice: switch f.Type.Elem().Kind() { case reflect.String: @@ -275,7 +297,7 @@ func (h helpConfigKey) fillType(f reflect.StructField) error { case reflect.Int: h.typ = "array of ints" default: - return fmt.Errorf("config key %q: unsupported type array of %s", f.Type.Name(), f.Type.Elem()) + return h, fmt.Errorf("config key %q: unsupported type array of %s", f.Type.Name(), f.Type.Elem()) } case reflect.Int64: if f.Type.Name() == "Duration" { @@ -286,13 +308,83 @@ func (h helpConfigKey) fillType(f reflect.StructField) error { case reflect.Bool: h.typ = "bool" default: - return fmt.Errorf("config key %q: unsupported type", f.Type.Name()) + return h, fmt.Errorf("config key %q: unsupported type", f.Type.Name()) } - return nil + return h, nil +} + +func dumpConfigHelp(w io.Writer, cfg interface{}) { + const sfmt = "%-18s | %-18s | %-18s | %-8s | |" + const sep = "----------------------------------------------------------------------------------------------------" + + hpad := fmt.Sprintf(sfmt, "", "", "", "") + fmt.Fprintf(w, sfmt, "Name", "Type", "Default", "Required") + fmt.Fprintf(w, "Help\n%s\n", sep) + + tf := reflect.TypeOf(cfg).Elem() + for i := 0; i < tf.NumField(); i++ { + field := tf.Field(i) + + // skip unexported fields + if field.PkgPath != "" && !field.Anonymous { + continue + } + + var typ string + switch field.Type.Kind() { + case reflect.Int: + typ = "int" + case reflect.String: + typ = "string" + case reflect.Slice: + switch field.Type.Elem().Kind() { + case reflect.String: + typ = "array of strings" + case reflect.Int: + typ = "array of ints" + default: + panic(field.Type.Elem()) + } + case reflect.Int64: + if field.Type.Name() == "Duration" { + typ = "duration" + } else { + typ = "int" + } + case reflect.Bool: + typ = "bool" + default: + panic(field.Type.Name()) + } + + help := field.Tag.Get("help") + def := field.Tag.Get("default") + req := field.Tag.Get("required") + if req == "true" { + req = "yes" + } else { + req = "no" + } + + fmt.Fprintf(w, sfmt, field.Name, typ, def, req) + helpLines := strings.Split(wrapString(help, 60), "\n") + if len(helpLines) > 0 { + fmt.Fprint(w, helpLines[0], "\n") + for _, h := range helpLines[1:] { + fmt.Fprint(w, hpad, " ", h, "\n") + } + } else { + fmt.Fprint(w, "\n") + } + } + + fmt.Fprint(w, sep, "\n") } + +/* func dumpConfigHelp(w io.Writer, cfg interface{}) { - const sfmt = "%-18s | %-18s | %-18s | %-8s | " + const sfmt = "%-18s | %-18s | %-18s | %-8s | |" const sep = "----------------------------------------------------------------------------------------------------" hpad := fmt.Sprintf(sfmt, "", "", "", "") @@ -358,6 +450,7 @@ func dumpConfigHelp(w io.Writer, cfg interface{}) { fmt.Fprint(w, sep, "\n") } +*/ // wrapString wraps the given string within lim width in characters. // diff --git a/markdown.go b/markdown.go deleted file mode 100644 index 42c67181..00000000 --- a/markdown.go +++ /dev/null @@ -1,55 +0,0 @@ -package baker - -import ( - "fmt" - "io" - "reflect" -) - -// GenerateMarkdownHelp generates markdown-formatted textual help for a Baker -// component from its description structure. Markdown is written into w. -func GenerateMarkdownHelp(w io.Writer, desc interface{}) error { - if desc == nil { - return fmt.Errorf("can't generate markdown help for a nil interface") - } - - if reflect.TypeOf(desc).Kind() == reflect.Ptr { - // dereference pointer - desc = reflect.ValueOf(desc).Elem().Interface() - } - - switch d := desc.(type) { - case InputDesc: - return genInputMarkdown(w, d) - case FilterDesc: - return genFilterMarkdown(w, d) - case OutputDesc: - return genOutputMarkdown(w, d) - case UploadDesc: - return genUploadMarkdown(w, d) - case MetricsDesc: - return genMetricsMarkdown(w, d) - } - - return fmt.Errorf("can't generate markdown, unsupported type %T", desc) -} - -func genInputMarkdown(w io.Writer, desc InputDesc) error { - return nil -} - -func genFilterMarkdown(w io.Writer, desc FilterDesc) error { - return nil -} - -func genOutputMarkdown(w io.Writer, desc OutputDesc) error { - return nil -} - -func genUploadMarkdown(w io.Writer, desc UploadDesc) error { - return nil -} - -func genMetricsMarkdown(w io.Writer, desc MetricsDesc) error { - return nil -} diff --git a/markdown_help.go b/markdown_help.go new file mode 100644 index 00000000..01310ad9 --- /dev/null +++ b/markdown_help.go @@ -0,0 +1,150 @@ +package baker + +import ( + "fmt" + "io" + "reflect" +) + +// GenerateMarkdownHelp generates markdown-formatted textual help for a Baker +// component from its description structure. Markdown is written into w. +func GenerateMarkdownHelp(w io.Writer, desc interface{}) error { + if desc == nil { + return fmt.Errorf("can't generate markdown help for a nil interface") + } + + if reflect.TypeOf(desc).Kind() == reflect.Ptr { + // dereference pointer + desc = reflect.ValueOf(desc).Elem().Interface() + } + + switch d := desc.(type) { + case InputDesc: + doc, err := newInputDoc(d) + if err != nil { + return err + } + genInputMarkdown(w, doc) + case FilterDesc: + doc, err := newFilterDoc(d) + if err != nil { + return err + } + genFilterMarkdown(w, doc) + case OutputDesc: + doc, err := newOutputDoc(d) + if err != nil { + return err + } + genOutputMarkdown(w, doc) + case UploadDesc: + doc, err := newUploadDoc(d) + if err != nil { + return err + } + genUploadMarkdown(w, doc) + case MetricsDesc: + doc, err := newMetricsDoc(d) + if err != nil { + return err + } + genMetricsMarkdown(w, doc) + default: + return fmt.Errorf("can't generate markdown help, unsupported type %T", desc) + } + + return nil +} + +func genInputMarkdown(w io.Writer, doc inputDoc) { + fmt.Fprintf(w, "## Input *%s*\n", doc.name) + fmt.Fprintln(w) + fmt.Fprintln(w, "### Overview") + fmt.Fprintln(w, doc.help) + fmt.Fprintln(w) + fmt.Fprintln(w, "### Configuration") + if len(doc.keys) == 0 { + fmt.Fprintf(w, "No configuration available") + } else { + fmt.Fprintf(w, "\nKeys available in the `[input.config]` section:\n\n") + genConfigKeysMarkdown(w, doc.keys) + } +} + +func genFilterMarkdown(w io.Writer, doc filterDoc) { + fmt.Fprintf(w, "## Filter *%s*\n", doc.name) + fmt.Fprintln(w) + fmt.Fprintln(w, "### Overview") + fmt.Fprintln(w, doc.help) + fmt.Fprintln(w) + fmt.Fprintln(w, "### Configuration") + if len(doc.keys) == 0 { + fmt.Fprintf(w, "No configuration available") + } else { + fmt.Fprintf(w, "\nKeys available in the `[filter.config]` section:\n\n") + genConfigKeysMarkdown(w, doc.keys) + } +} + +func genOutputMarkdown(w io.Writer, doc outputDoc) { + const ( + rawString = "This is a *raw* output, for each record it receives a buffer containing the serialized record, plus a list holding a set of fields (`output.fields` in TOML)." + nonRawString = "This is a *non-raw* output, it doesn't receive whole records. Instead it receives a list of fields for each record (`output.fields` in TOML)." + ) + + fmt.Fprintf(w, "## Output *%s*\n", doc.name) + fmt.Fprintln(w) + fmt.Fprintln(w, "### Overview") + if doc.raw { + fmt.Fprintln(w, rawString) + } else { + fmt.Fprintln(w, nonRawString) + } + fmt.Fprintln(w) + fmt.Fprintln(w) + fmt.Fprintln(w, doc.help) + fmt.Fprintln(w) + fmt.Fprintln(w, "### Configuration") + if len(doc.keys) == 0 { + fmt.Fprintf(w, "No configuration available") + } else { + fmt.Fprintf(w, "\nKeys available in the `[output.config]` section:\n\n") + genConfigKeysMarkdown(w, doc.keys) + } +} + +func genUploadMarkdown(w io.Writer, doc uploadDoc) { + fmt.Fprintf(w, "## Upload *%s*\n", doc.name) + fmt.Fprintln(w) + fmt.Fprintln(w, "### Overview") + fmt.Fprintln(w, doc.help) + fmt.Fprintln(w) + fmt.Fprintln(w, "### Configuration") + if len(doc.keys) == 0 { + fmt.Fprintf(w, "No configuration available") + } else { + fmt.Fprintf(w, "\nKeys available in the `[upload.config]` section:\n\n") + genConfigKeysMarkdown(w, doc.keys) + } +} + +func genMetricsMarkdown(w io.Writer, doc metricsDoc) { + fmt.Fprintf(w, "## Metrics *%s*\n", doc.name) + fmt.Fprintln(w) + fmt.Fprintln(w, "### Configuration") + if len(doc.keys) == 0 { + fmt.Fprintf(w, "No configuration available") + } else { + fmt.Fprintf(w, "\nKeys available in the `[metrics.config]` section:\n\n") + genConfigKeysMarkdown(w, doc.keys) + } +} + +func genConfigKeysMarkdown(w io.Writer, keys []helpConfigKey) { + fmt.Fprintln(w, "|Name|Type|Default|Required|Description|") + fmt.Fprintln(w, "|:--:|:--:|:-----:|:------:|:---------:|") + for _, k := range keys { + fmt.Fprintf(w, "| %v| %v| %v| %t| %v|\n", k.name, k.typ, k.def, k.required, k.desc) + } + fmt.Fprintln(w) +} diff --git a/text_help.go b/text_help.go new file mode 100644 index 00000000..e7c228c0 --- /dev/null +++ b/text_help.go @@ -0,0 +1,180 @@ +package baker + +import ( + "fmt" + "io" + "reflect" + "strings" +) + +// GenerateTextHelp generates non-formatted textual help for a Baker +// component from its description structure, into w. +func GenerateTextHelp(w io.Writer, desc interface{}) error { + if desc == nil { + return fmt.Errorf("can't generate text help for a nil interface") + } + + if reflect.TypeOf(desc).Kind() == reflect.Ptr { + // dereference pointer + desc = reflect.ValueOf(desc).Elem().Interface() + } + + switch d := desc.(type) { + case InputDesc: + doc, err := newInputDoc(d) + if err != nil { + return err + } + genInputText(w, doc) + case FilterDesc: + doc, err := newFilterDoc(d) + if err != nil { + return err + } + genFilterText(w, doc) + case OutputDesc: + doc, err := newOutputDoc(d) + if err != nil { + return err + } + genOutputText(w, doc) + case UploadDesc: + doc, err := newUploadDoc(d) + if err != nil { + return err + } + genUploadText(w, doc) + case MetricsDesc: + doc, err := newMetricsDoc(d) + if err != nil { + return err + } + genMetricsText(w, doc) + default: + return fmt.Errorf("can't generate help, unsupported type %T", desc) + } + + return nil +} + +const ( + helpTextHdrSfmt = "%-18s | %-18s | %-18s | %-8s | |" + helpTextSep = "----------------------------------------------------------------------------------------------------" +) + +func genInputText(w io.Writer, doc inputDoc) { + fmt.Fprintf(w, "=============================================\n") + fmt.Fprintf(w, "Input: %s\n", doc.name) + fmt.Fprintf(w, "=============================================\n") + fmt.Fprintf(w, doc.help) + + if len(doc.keys) == 0 { + fmt.Fprintf(w, "\n(no configuration available)\n\n") + } else { + fmt.Fprintf(w, "\nKeys available in the [input.config] section:\n\n") + genConfigKeysText(w, doc.keys) + } + + fmt.Fprintln(w) + fmt.Fprintln(w) +} + +func genFilterText(w io.Writer, doc filterDoc) { + fmt.Fprintf(w, "=============================================\n") + fmt.Fprintf(w, "Filter: %s\n", doc.name) + fmt.Fprintf(w, "=============================================\n") + fmt.Fprintf(w, doc.help) + + if len(doc.keys) == 0 { + fmt.Fprintf(w, "\n(no configuration available)\n\n") + } else { + fmt.Fprintf(w, "\nKeys available in the [filter.config] section:\n\n") + genConfigKeysText(w, doc.keys) + } + + fmt.Fprintln(w) + fmt.Fprintln(w) +} + +func genOutputText(w io.Writer, doc outputDoc) { + const ( + rawString = "This is a raw output, for each record it receives a buffer containing the serialized record, plus a list holding a set of fields ('output.fields' in TOML)." + nonRawString = "This is a non-raw output, it doesn't receive whole records. Instead it receives a list of fields for each record ('output.fields' in TOML)." + ) + + fmt.Fprintf(w, "=============================================\n") + fmt.Fprintf(w, "Output: %s\n", doc.name) + fmt.Fprintf(w, "=============================================\n") + fmt.Fprintf(w, doc.help) + + if doc.raw { + fmt.Fprintln(w, rawString) + } else { + fmt.Fprintln(w, nonRawString) + } + + if len(doc.keys) == 0 { + fmt.Fprintf(w, "\n(no configuration available)\n\n") + } else { + fmt.Fprintf(w, "\nKeys available in the [output.config] section:\n\n") + genConfigKeysText(w, doc.keys) + } + + fmt.Fprintln(w) + fmt.Fprintln(w) +} + +func genUploadText(w io.Writer, doc uploadDoc) { + fmt.Fprintf(w, "=============================================\n") + fmt.Fprintf(w, "Upload: %s\n", doc.name) + fmt.Fprintf(w, "=============================================\n") + fmt.Fprintf(w, doc.help) + + if len(doc.keys) == 0 { + fmt.Fprintf(w, "\n(no configuration available)\n\n") + } else { + fmt.Fprintf(w, "\nKeys available in the [upload.config] section:\n\n") + genConfigKeysText(w, doc.keys) + } + + fmt.Fprintln(w) + fmt.Fprintln(w) +} + +func genMetricsText(w io.Writer, doc metricsDoc) { + fmt.Fprintf(w, "=============================================\n") + fmt.Fprintf(w, "Metrics: %s\n", doc.name) + fmt.Fprintf(w, "=============================================\n") + + if len(doc.keys) == 0 { + fmt.Fprintf(w, "\n(no configuration available)\n\n") + } else { + fmt.Fprintf(w, "\nKeys available in the [metrics.config] section:\n\n") + genConfigKeysText(w, doc.keys) + } + + fmt.Fprintln(w) + fmt.Fprintln(w) +} + +func genConfigKeysText(w io.Writer, keys []helpConfigKey) { + hpad := fmt.Sprintf(helpTextHdrSfmt, "", "", "", "") + + fmt.Fprintf(w, helpTextHdrSfmt, "Name", "Type", "Default", "Required") + fmt.Fprintf(w, "Help\n%s\n", helpTextSep) + + for _, k := range keys { + fmt.Fprintf(w, helpTextHdrSfmt, k.name, k.typ, k.def, k.required) + helpLines := strings.Split(wrapString(k.desc, 60), "\n") + if len(helpLines) > 0 { + fmt.Fprint(w, helpLines[0], "\n") + for _, h := range helpLines[1:] { + fmt.Fprint(w, hpad, " ", h, "\n") + } + } else { + fmt.Fprint(w, "\n") + } + } + + fmt.Fprint(w, helpTextSep, "\n") +} From 069f144212882044415575968eac0ea68872109a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Wed, 11 Nov 2020 13:09:50 +0100 Subject: [PATCH 05/18] Add line break after end of lines for markdown help. --- markdown_help.go | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/markdown_help.go b/markdown_help.go index 01310ad9..6b23af1c 100644 --- a/markdown_help.go +++ b/markdown_help.go @@ -4,6 +4,7 @@ import ( "fmt" "io" "reflect" + "strings" ) // GenerateMarkdownHelp generates markdown-formatted textual help for a Baker @@ -56,11 +57,21 @@ func GenerateMarkdownHelp(w io.Writer, desc interface{}) error { return nil } +func breakAfterDots(s string) string { + r := strings.NewReplacer( + ".", ".\n", + "!", "!\n", + "?", "?\n", + ":", ":\n", + ) + return r.Replace(s) +} + func genInputMarkdown(w io.Writer, doc inputDoc) { fmt.Fprintf(w, "## Input *%s*\n", doc.name) fmt.Fprintln(w) fmt.Fprintln(w, "### Overview") - fmt.Fprintln(w, doc.help) + fmt.Fprintln(w, breakAfterDots(doc.help)) fmt.Fprintln(w) fmt.Fprintln(w, "### Configuration") if len(doc.keys) == 0 { @@ -75,7 +86,7 @@ func genFilterMarkdown(w io.Writer, doc filterDoc) { fmt.Fprintf(w, "## Filter *%s*\n", doc.name) fmt.Fprintln(w) fmt.Fprintln(w, "### Overview") - fmt.Fprintln(w, doc.help) + fmt.Fprintln(w, breakAfterDots(doc.help)) fmt.Fprintln(w) fmt.Fprintln(w, "### Configuration") if len(doc.keys) == 0 { @@ -102,7 +113,7 @@ func genOutputMarkdown(w io.Writer, doc outputDoc) { } fmt.Fprintln(w) fmt.Fprintln(w) - fmt.Fprintln(w, doc.help) + fmt.Fprintln(w, breakAfterDots(doc.help)) fmt.Fprintln(w) fmt.Fprintln(w, "### Configuration") if len(doc.keys) == 0 { @@ -117,7 +128,7 @@ func genUploadMarkdown(w io.Writer, doc uploadDoc) { fmt.Fprintf(w, "## Upload *%s*\n", doc.name) fmt.Fprintln(w) fmt.Fprintln(w, "### Overview") - fmt.Fprintln(w, doc.help) + fmt.Fprintln(w, breakAfterDots(doc.help)) fmt.Fprintln(w) fmt.Fprintln(w, "### Configuration") if len(doc.keys) == 0 { From 945dd3e31b089dc0d9ed08a325532f63e18a7115 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Wed, 11 Nov 2020 13:13:51 +0100 Subject: [PATCH 06/18] Use terminal width for better markdown rendering --- help.go | 79 ++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 58 insertions(+), 21 deletions(-) diff --git a/help.go b/help.go index 24db1d47..5b5b7e54 100644 --- a/help.go +++ b/help.go @@ -5,8 +5,11 @@ import ( "fmt" "io" "reflect" + "runtime" "strings" + "syscall" "unicode" + "unsafe" "github.com/charmbracelet/glamour" ) @@ -56,36 +59,32 @@ const ( func PrintHelp(w io.Writer, name string, comp Components, format HelpFormat) error { dumpall := name == "*" - ww := w // wraps w - generateHelp := GenerateTextHelp if format == HelpFormatMarkdown { generateHelp = GenerateMarkdownHelp - buf := &bytes.Buffer{} - ww = buf - defer func() { - - // TODO(arl) this is temporary - r, _ := glamour.NewTermRenderer( - // detect background color and pick either the default dark or light theme - glamour.WithAutoStyle(), - // wrap output at specific width - glamour.WithWordWrap(180), - ) + r, _ := glamour.NewTermRenderer( + // detect background color and pick either the default dark or light theme + glamour.WithAutoStyle(), + // wrap output at specific width + glamour.WithWordWrap(int(terminalWidth())), + ) + wprev := w + w = r - out, err := r.Render(buf.String()) - if err != nil { + defer func() { + if err := r.Close(); err != nil { + panic(err) + } + if _, err := io.Copy(wprev, r); err != nil { panic(err) } - // Should copy to w (original writer) and not to os.Stdout - fmt.Print(out) }() } for _, inp := range comp.Inputs { if strings.EqualFold(inp.Name, name) || dumpall { - if err := generateHelp(ww, inp); err != nil { + if err := generateHelp(w, inp); err != nil { return fmt.Errorf("can't print help for %q input: %v", inp.Name, err) } if !dumpall { @@ -96,7 +95,7 @@ func PrintHelp(w io.Writer, name string, comp Components, format HelpFormat) err for _, fil := range comp.Filters { if strings.EqualFold(fil.Name, name) || dumpall { - if err := generateHelp(ww, fil); err != nil { + if err := generateHelp(w, fil); err != nil { return fmt.Errorf("can't print help for %q filter: %v", fil.Name, err) } if !dumpall { @@ -108,7 +107,7 @@ func PrintHelp(w io.Writer, name string, comp Components, format HelpFormat) err for _, out := range comp.Outputs { if strings.EqualFold(out.Name, name) || dumpall { if strings.EqualFold(out.Name, name) || dumpall { - if err := generateHelp(ww, out); err != nil { + if err := generateHelp(w, out); err != nil { return fmt.Errorf("can't print help for %q output: %v", out.Name, err) } if !dumpall { @@ -121,7 +120,7 @@ func PrintHelp(w io.Writer, name string, comp Components, format HelpFormat) err for _, upl := range comp.Uploads { if strings.EqualFold(upl.Name, name) || dumpall { if strings.EqualFold(upl.Name, name) || dumpall { - if err := generateHelp(ww, upl); err != nil { + if err := generateHelp(w, upl); err != nil { return fmt.Errorf("can't print help for %q upload: %v", upl.Name, err) } if !dumpall { @@ -138,6 +137,44 @@ func PrintHelp(w io.Writer, name string, comp Components, format HelpFormat) err return nil } +func terminalWidth() uint { + const ( + maxWidth = 140 // don't go over 140 chars anyway + defaultWidth = 110 // in case we can't get the terminal width + ) + + var w uint + + defer func() { + if err := recover(); err != nil { + w = defaultWidth + } + }() + + if runtime.GOOS == "windows" { + // On windows assume 120 character wide terminal since the subsequent + // method only works on nix systems. + return 120 + } + + ws := &struct{ Row, Col, Xpixel, Ypixel uint16 }{} + retCode, _, errno := syscall.Syscall(syscall.SYS_IOCTL, + uintptr(syscall.Stdout), + uintptr(syscall.TIOCGWINSZ), + uintptr(unsafe.Pointer(ws))) + + if int(retCode) == -1 { + panic(errno) + } + + if ws.Col > maxWidth { + return maxWidth + } + + w = uint(ws.Col) + return w +} + type baseDoc struct { name string // component name help string // general help string From 77685e1e217dcc416bb1ff0d9216ead31b86ceeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Wed, 11 Nov 2020 19:39:25 +0100 Subject: [PATCH 07/18] Fix textual help rendering --- text_help.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/text_help.go b/text_help.go index e7c228c0..e8b27db6 100644 --- a/text_help.go +++ b/text_help.go @@ -164,7 +164,7 @@ func genConfigKeysText(w io.Writer, keys []helpConfigKey) { fmt.Fprintf(w, "Help\n%s\n", helpTextSep) for _, k := range keys { - fmt.Fprintf(w, helpTextHdrSfmt, k.name, k.typ, k.def, k.required) + fmt.Fprintf(w, helpTextHdrSfmt, k.name, k.typ, k.def, fmt.Sprintf("%t", k.required)) helpLines := strings.Split(wrapString(k.desc, 60), "\n") if len(helpLines) > 0 { fmt.Fprint(w, helpLines[0], "\n") From 18ab2c4433185ab587b80917ee6abee347e3f174 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Wed, 11 Nov 2020 19:46:58 +0100 Subject: [PATCH 08/18] Add RenderHelpMarkdown public API --- help.go | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/help.go b/help.go index 5b5b7e54..5c1d6e07 100644 --- a/help.go +++ b/help.go @@ -62,24 +62,6 @@ func PrintHelp(w io.Writer, name string, comp Components, format HelpFormat) err generateHelp := GenerateTextHelp if format == HelpFormatMarkdown { generateHelp = GenerateMarkdownHelp - - r, _ := glamour.NewTermRenderer( - // detect background color and pick either the default dark or light theme - glamour.WithAutoStyle(), - // wrap output at specific width - glamour.WithWordWrap(int(terminalWidth())), - ) - wprev := w - w = r - - defer func() { - if err := r.Close(); err != nil { - panic(err) - } - if _, err := io.Copy(wprev, r); err != nil { - panic(err) - } - }() } for _, inp := range comp.Inputs { @@ -137,6 +119,25 @@ func PrintHelp(w io.Writer, name string, comp Components, format HelpFormat) err return nil } +// RenderHelpMarkdown calls PrintHelp withy format markdown but render the +// markdown for terminal consulation. +func RenderHelpMarkdown(w io.Writer, name string, comp Components) error { + r, _ := glamour.NewTermRenderer( + // detect background color and pick either the default dark or light theme + glamour.WithAutoStyle(), + // wrap output at specific width + glamour.WithWordWrap(int(terminalWidth())), + ) + + if err := PrintHelp(r, name, comp, HelpFormatMarkdown); err != nil { + return err + } + + r.Close() + _, err := io.Copy(w, r) + return err +} + func terminalWidth() uint { const ( maxWidth = 140 // don't go over 140 chars anyway From 5e3266aa9a0c7a049279170a657249727a474bd8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Wed, 11 Nov 2020 19:32:20 +0100 Subject: [PATCH 09/18] examples/help: print help in markdown format --- examples/help/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/help/main.go b/examples/help/main.go index d2dbe599..2bb09974 100644 --- a/examples/help/main.go +++ b/examples/help/main.go @@ -8,9 +8,9 @@ package main import ( "flag" "fmt" - "html/template" "os" "strings" + "text/template" "github.com/AdRoll/baker" "github.com/AdRoll/baker/filter" @@ -26,7 +26,7 @@ func main() { flag.Parse() if *flagHelpConfig != "" { - baker.PrintHelp(os.Stderr, *flagHelpConfig, components) + baker.RenderHelpMarkdown(os.Stderr, *flagHelpConfig, components) return } From e2a3e469a63711ca02e6cc58c57050c5ba562340 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Wed, 11 Nov 2020 19:49:27 +0100 Subject: [PATCH 10/18] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 09a99998..a303f012 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - README: document KCL input [#59](https://github.com/AdRoll/baker/pull/59) - Document how to specialize baker.LogLine [#63](https://github.com/AdRoll/baker/pull/63) - Add `baker.MainCLI` [#73](https://github.com/AdRoll/baker/pull/73) +- Implement markdown rendering of component help/configuration [#80](https://github.com/AdRoll/baker/pull/80) ### Changed From cca76c296f9753b4e96b157773a1383c3b545012 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Thu, 12 Nov 2020 12:14:21 +0100 Subject: [PATCH 11/18] Rename files dealing with help generation --- markdown_help.go => help_markdown.go | 0 text_help.go => help_text.go | 0 markdown_test.go | 38 ---------------------------- 3 files changed, 38 deletions(-) rename markdown_help.go => help_markdown.go (100%) rename text_help.go => help_text.go (100%) delete mode 100644 markdown_test.go diff --git a/markdown_help.go b/help_markdown.go similarity index 100% rename from markdown_help.go rename to help_markdown.go diff --git a/text_help.go b/help_text.go similarity index 100% rename from text_help.go rename to help_text.go diff --git a/markdown_test.go b/markdown_test.go deleted file mode 100644 index beb2c287..00000000 --- a/markdown_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package baker - -import ( - "bytes" - "testing" -) - -func TestGenerateMarkdownHelp(t *testing.T) { - tests := []struct { - name string - desc interface{} - want string - wantErr bool - }{ - { - name: "nil", - desc: nil, - wantErr: true, - }, - { - name: "unsupported type", - desc: 23, - wantErr: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - w := &bytes.Buffer{} - if err := GenerateMarkdownHelp(w, tt.desc); (err != nil) != tt.wantErr { - t.Errorf("GenerateMarkdownHelp() error = %v, wantErr %v", err, tt.wantErr) - return - } - if gotW := w.String(); gotW != tt.want { - t.Errorf("GenerateMarkdownHelp() = %v, want %v", gotW, tt.want) - } - }) - } -} From 693622cc640cc0b417ce4d4e0b230c3c6fd039fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Thu, 12 Nov 2020 12:22:09 +0100 Subject: [PATCH 12/18] Text upload help and rename test functions --- help_test.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/help_test.go b/help_test.go index 528be48e..c650ac68 100644 --- a/help_test.go +++ b/help_test.go @@ -7,6 +7,7 @@ import ( "github.com/AdRoll/baker/filter" "github.com/AdRoll/baker/input" "github.com/AdRoll/baker/output" + "github.com/AdRoll/baker/upload" ) func assertValidConfigHelp(t *testing.T, name string, cfg interface{}) { @@ -30,24 +31,27 @@ func assertValidConfigHelp(t *testing.T, name string, cfg interface{}) { } } -func TestAllInputsHasConfigHelp(t *testing.T) { +func TestAllInputsHaveConfigHelp(t *testing.T) { for _, input := range input.All { assertValidConfigHelp(t, input.Name, input.Config) } } -func TestAllFiltersHasConfigHelp(t *testing.T) { +func TestAllFiltersHaveConfigHelp(t *testing.T) { for _, filter := range filter.All { assertValidConfigHelp(t, filter.Name, filter.Config) } } -func TestAllOutputsHasConfigHelp(t *testing.T) { +func TestAllOutputsHaveConfigHelp(t *testing.T) { for _, output := range output.All { assertValidConfigHelp(t, output.Name, output.Config) } } -func TestAllUploadsHasConfigHelp(t *testing.T) { - // nothing to do: baker.Upload don't have any config at the moment +func TestAllUploadsHaveConfigHelp(t *testing.T) { + for _, upload := range upload.All { + assertValidConfigHelp(t, upload.Name, upload.Config) + } +} } From 1429cf9566252a765abb033c26c6f5e2bb66135a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Thu, 12 Nov 2020 12:25:00 +0100 Subject: [PATCH 13/18] Move RenderHelpMarkdown in help_markdown.go --- help.go | 62 ----------------------------------------------- help_markdown.go | 63 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 62 deletions(-) diff --git a/help.go b/help.go index 5c1d6e07..8a45556e 100644 --- a/help.go +++ b/help.go @@ -5,13 +5,8 @@ import ( "fmt" "io" "reflect" - "runtime" "strings" - "syscall" "unicode" - "unsafe" - - "github.com/charmbracelet/glamour" ) // HelpFormat represents the possible formats for baker help. @@ -119,63 +114,6 @@ func PrintHelp(w io.Writer, name string, comp Components, format HelpFormat) err return nil } -// RenderHelpMarkdown calls PrintHelp withy format markdown but render the -// markdown for terminal consulation. -func RenderHelpMarkdown(w io.Writer, name string, comp Components) error { - r, _ := glamour.NewTermRenderer( - // detect background color and pick either the default dark or light theme - glamour.WithAutoStyle(), - // wrap output at specific width - glamour.WithWordWrap(int(terminalWidth())), - ) - - if err := PrintHelp(r, name, comp, HelpFormatMarkdown); err != nil { - return err - } - - r.Close() - _, err := io.Copy(w, r) - return err -} - -func terminalWidth() uint { - const ( - maxWidth = 140 // don't go over 140 chars anyway - defaultWidth = 110 // in case we can't get the terminal width - ) - - var w uint - - defer func() { - if err := recover(); err != nil { - w = defaultWidth - } - }() - - if runtime.GOOS == "windows" { - // On windows assume 120 character wide terminal since the subsequent - // method only works on nix systems. - return 120 - } - - ws := &struct{ Row, Col, Xpixel, Ypixel uint16 }{} - retCode, _, errno := syscall.Syscall(syscall.SYS_IOCTL, - uintptr(syscall.Stdout), - uintptr(syscall.TIOCGWINSZ), - uintptr(unsafe.Pointer(ws))) - - if int(retCode) == -1 { - panic(errno) - } - - if ws.Col > maxWidth { - return maxWidth - } - - w = uint(ws.Col) - return w -} - type baseDoc struct { name string // component name help string // general help string diff --git a/help_markdown.go b/help_markdown.go index 6b23af1c..c5c3e9b8 100644 --- a/help_markdown.go +++ b/help_markdown.go @@ -4,9 +4,72 @@ import ( "fmt" "io" "reflect" + "runtime" "strings" + "syscall" + "unsafe" + + "github.com/charmbracelet/glamour" ) +// RenderHelpMarkdown prints markdown formatted help for a single component or +// for all of them (with name = '*'), and renders it so that it can be +// printed on a terminal. +func RenderHelpMarkdown(w io.Writer, name string, comp Components) error { + r, _ := glamour.NewTermRenderer( + // detect background color and pick either the default dark or light theme + glamour.WithAutoStyle(), + // wrap output at specific width + glamour.WithWordWrap(int(terminalWidth())), + ) + + if err := PrintHelp(r, name, comp, HelpFormatMarkdown); err != nil { + return err + } + + r.Close() + _, err := io.Copy(w, r) + return err +} + +func terminalWidth() uint { + const ( + maxWidth = 140 // don't go over 140 chars anyway + defaultWidth = 110 // in case we can't get the terminal width + ) + + var w uint + + defer func() { + if err := recover(); err != nil { + w = defaultWidth + } + }() + + if runtime.GOOS == "windows" { + // On windows assume 120 character wide terminal since the subsequent + // method only works on nix systems. + return 120 + } + + ws := &struct{ Row, Col, Xpixel, Ypixel uint16 }{} + retCode, _, errno := syscall.Syscall(syscall.SYS_IOCTL, + uintptr(syscall.Stdout), + uintptr(syscall.TIOCGWINSZ), + uintptr(unsafe.Pointer(ws))) + + if int(retCode) == -1 { + panic(errno) + } + + if ws.Col > maxWidth { + return maxWidth + } + + w = uint(ws.Col) + return w +} + // GenerateMarkdownHelp generates markdown-formatted textual help for a Baker // component from its description structure. Markdown is written into w. func GenerateMarkdownHelp(w io.Writer, desc interface{}) error { From 6a14fd36e4475bfc0c3a01469254856f2a070344 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Thu, 12 Nov 2020 12:27:13 +0100 Subject: [PATCH 14/18] Move wrapString in help_text.go --- help.go | 208 --------------------------------------------------- help_text.go | 70 +++++++++++++++++ 2 files changed, 70 insertions(+), 208 deletions(-) diff --git a/help.go b/help.go index 8a45556e..a40cf284 100644 --- a/help.go +++ b/help.go @@ -1,12 +1,10 @@ package baker import ( - "bytes" "fmt" "io" "reflect" "strings" - "unicode" ) // HelpFormat represents the possible formats for baker help. @@ -289,209 +287,3 @@ func newHelpConfigKeyFromField(f reflect.StructField) (helpConfigKey, error) { return h, nil } - -func dumpConfigHelp(w io.Writer, cfg interface{}) { - const sfmt = "%-18s | %-18s | %-18s | %-8s | |" - const sep = "----------------------------------------------------------------------------------------------------" - - hpad := fmt.Sprintf(sfmt, "", "", "", "") - fmt.Fprintf(w, sfmt, "Name", "Type", "Default", "Required") - fmt.Fprintf(w, "Help\n%s\n", sep) - - tf := reflect.TypeOf(cfg).Elem() - for i := 0; i < tf.NumField(); i++ { - field := tf.Field(i) - - // skip unexported fields - if field.PkgPath != "" && !field.Anonymous { - continue - } - - var typ string - switch field.Type.Kind() { - case reflect.Int: - typ = "int" - case reflect.String: - typ = "string" - case reflect.Slice: - switch field.Type.Elem().Kind() { - case reflect.String: - typ = "array of strings" - case reflect.Int: - typ = "array of ints" - default: - panic(field.Type.Elem()) - } - case reflect.Int64: - if field.Type.Name() == "Duration" { - typ = "duration" - } else { - typ = "int" - } - case reflect.Bool: - typ = "bool" - default: - panic(field.Type.Name()) - } - - help := field.Tag.Get("help") - def := field.Tag.Get("default") - req := field.Tag.Get("required") - if req == "true" { - req = "yes" - } else { - req = "no" - } - - fmt.Fprintf(w, sfmt, field.Name, typ, def, req) - helpLines := strings.Split(wrapString(help, 60), "\n") - if len(helpLines) > 0 { - fmt.Fprint(w, helpLines[0], "\n") - for _, h := range helpLines[1:] { - fmt.Fprint(w, hpad, " ", h, "\n") - } - } else { - fmt.Fprint(w, "\n") - } - } - - fmt.Fprint(w, sep, "\n") -} - -/* -func dumpConfigHelp(w io.Writer, cfg interface{}) { - const sfmt = "%-18s | %-18s | %-18s | %-8s | |" - const sep = "----------------------------------------------------------------------------------------------------" - - hpad := fmt.Sprintf(sfmt, "", "", "", "") - fmt.Fprintf(w, sfmt, "Name", "Type", "Default", "Required") - fmt.Fprintf(w, "Help\n%s\n", sep) - - tf := reflect.TypeOf(cfg).Elem() - for i := 0; i < tf.NumField(); i++ { - field := tf.Field(i) - - // skip unexported fields - if field.PkgPath != "" && !field.Anonymous { - continue - } - - var typ string - switch field.Type.Kind() { - case reflect.Int: - typ = "int" - case reflect.String: - typ = "string" - case reflect.Slice: - switch field.Type.Elem().Kind() { - case reflect.String: - typ = "array of strings" - case reflect.Int: - typ = "array of ints" - default: - panic(field.Type.Elem()) - } - case reflect.Int64: - if field.Type.Name() == "Duration" { - typ = "duration" - } else { - typ = "int" - } - case reflect.Bool: - typ = "bool" - default: - panic(field.Type.Name()) - } - - help := field.Tag.Get("help") - def := field.Tag.Get("default") - req := field.Tag.Get("required") - if req == "true" { - req = "yes" - } else { - req = "no" - } - - fmt.Fprintf(w, sfmt, field.Name, typ, def, req) - helpLines := strings.Split(wrapString(help, 60), "\n") - if len(helpLines) > 0 { - fmt.Fprint(w, helpLines[0], "\n") - for _, h := range helpLines[1:] { - fmt.Fprint(w, hpad, " ", h, "\n") - } - } else { - fmt.Fprint(w, "\n") - } - } - - fmt.Fprint(w, sep, "\n") -} -*/ - -// wrapString wraps the given string within lim width in characters. -// -// Source: https://github.com/mitchellh/go-wordwrap -// Wrapping is currently naive and only happens at white-space. A future -// version of the library will implement smarter wrapping. This means that -// pathological cases can dramatically reach past the limit, such as a very -// long word. -func wrapString(s string, lim uint) string { - // Initialize a buffer with a slightly larger size to account for breaks - init := make([]byte, 0, len(s)) - buf := bytes.NewBuffer(init) - - var current uint - var wordBuf, spaceBuf bytes.Buffer - - for _, char := range s { - if char == '\n' { - if wordBuf.Len() == 0 { - if current+uint(spaceBuf.Len()) > lim { - current = 0 - } else { - current += uint(spaceBuf.Len()) - spaceBuf.WriteTo(buf) - } - spaceBuf.Reset() - } else { - current += uint(spaceBuf.Len() + wordBuf.Len()) - spaceBuf.WriteTo(buf) - spaceBuf.Reset() - wordBuf.WriteTo(buf) - wordBuf.Reset() - } - buf.WriteRune(char) - current = 0 - } else if unicode.IsSpace(char) { - if spaceBuf.Len() == 0 || wordBuf.Len() > 0 { - current += uint(spaceBuf.Len() + wordBuf.Len()) - spaceBuf.WriteTo(buf) - spaceBuf.Reset() - wordBuf.WriteTo(buf) - wordBuf.Reset() - } - - spaceBuf.WriteRune(char) - } else { - - wordBuf.WriteRune(char) - - if current+uint(spaceBuf.Len()+wordBuf.Len()) > lim && uint(wordBuf.Len()) < lim { - buf.WriteRune('\n') - current = 0 - spaceBuf.Reset() - } - } - } - - if wordBuf.Len() == 0 { - if current+uint(spaceBuf.Len()) <= lim { - spaceBuf.WriteTo(buf) - } - } else { - spaceBuf.WriteTo(buf) - wordBuf.WriteTo(buf) - } - - return buf.String() -} diff --git a/help_text.go b/help_text.go index e8b27db6..905a6710 100644 --- a/help_text.go +++ b/help_text.go @@ -1,10 +1,12 @@ package baker import ( + "bytes" "fmt" "io" "reflect" "strings" + "unicode" ) // GenerateTextHelp generates non-formatted textual help for a Baker @@ -178,3 +180,71 @@ func genConfigKeysText(w io.Writer, keys []helpConfigKey) { fmt.Fprint(w, helpTextSep, "\n") } + +// wrapString wraps the given string within lim width in characters. +// +// Source: https://github.com/mitchellh/go-wordwrap +// Wrapping is currently naive and only happens at white-space. A future +// version of the library will implement smarter wrapping. This means that +// pathological cases can dramatically reach past the limit, such as a very +// long word. +func wrapString(s string, lim uint) string { + // Initialize a buffer with a slightly larger size to account for breaks + init := make([]byte, 0, len(s)) + buf := bytes.NewBuffer(init) + + var current uint + var wordBuf, spaceBuf bytes.Buffer + + for _, char := range s { + if char == '\n' { + if wordBuf.Len() == 0 { + if current+uint(spaceBuf.Len()) > lim { + current = 0 + } else { + current += uint(spaceBuf.Len()) + spaceBuf.WriteTo(buf) + } + spaceBuf.Reset() + } else { + current += uint(spaceBuf.Len() + wordBuf.Len()) + spaceBuf.WriteTo(buf) + spaceBuf.Reset() + wordBuf.WriteTo(buf) + wordBuf.Reset() + } + buf.WriteRune(char) + current = 0 + } else if unicode.IsSpace(char) { + if spaceBuf.Len() == 0 || wordBuf.Len() > 0 { + current += uint(spaceBuf.Len() + wordBuf.Len()) + spaceBuf.WriteTo(buf) + spaceBuf.Reset() + wordBuf.WriteTo(buf) + wordBuf.Reset() + } + + spaceBuf.WriteRune(char) + } else { + + wordBuf.WriteRune(char) + + if current+uint(spaceBuf.Len()+wordBuf.Len()) > lim && uint(wordBuf.Len()) < lim { + buf.WriteRune('\n') + current = 0 + spaceBuf.Reset() + } + } + } + + if wordBuf.Len() == 0 { + if current+uint(spaceBuf.Len()) <= lim { + spaceBuf.WriteTo(buf) + } + } else { + spaceBuf.WriteTo(buf) + wordBuf.WriteTo(buf) + } + + return buf.String() +} From 9ea69a14319bdbf65cd4bf8c96068830ff6c3cc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Thu, 12 Nov 2020 12:28:36 +0100 Subject: [PATCH 15/18] Create help_config.go --- help.go | 177 ----------------------------------------------- help_config.go | 182 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 182 insertions(+), 177 deletions(-) create mode 100644 help_config.go diff --git a/help.go b/help.go index a40cf284..a5726f32 100644 --- a/help.go +++ b/help.go @@ -3,7 +3,6 @@ package baker import ( "fmt" "io" - "reflect" "strings" ) @@ -111,179 +110,3 @@ func PrintHelp(w io.Writer, name string, comp Components, format HelpFormat) err return nil } - -type baseDoc struct { - name string // component name - help string // general help string - keys []helpConfigKey // configuration keys -} - -type inputDoc struct{ baseDoc } -type filterDoc struct{ baseDoc } -type uploadDoc struct{ baseDoc } - -type outputDoc struct { - baseDoc - raw bool // raw output? -} - -type metricsDoc struct { - name string // component name - keys []helpConfigKey // configuration keys -} - -func newInputDoc(desc InputDesc) (inputDoc, error) { - doc := inputDoc{ - baseDoc{ - name: desc.Name, - help: desc.Help, - }, - } - - var err error - - doc.keys, err = configKeysFromStruct(desc.Config) - if err != nil { - return doc, fmt.Errorf("input %q: %v", desc.Name, err) - } - - return doc, nil -} - -func newFilterDoc(desc FilterDesc) (filterDoc, error) { - doc := filterDoc{ - baseDoc{ - name: desc.Name, - help: desc.Help, - }, - } - - var err error - - doc.keys, err = configKeysFromStruct(desc.Config) - if err != nil { - return doc, fmt.Errorf("filter %q: %v", desc.Name, err) - } - - return doc, nil -} - -func newOutputDoc(desc OutputDesc) (outputDoc, error) { - doc := outputDoc{ - raw: desc.Raw, - baseDoc: baseDoc{ - name: desc.Name, - help: desc.Help, - }, - } - - var err error - - doc.keys, err = configKeysFromStruct(desc.Config) - if err != nil { - return doc, fmt.Errorf("output %q: %v", desc.Name, err) - } - - return doc, nil -} - -func newUploadDoc(desc UploadDesc) (uploadDoc, error) { - doc := uploadDoc{ - baseDoc{ - name: desc.Name, - help: desc.Help, - }, - } - - var err error - - doc.keys, err = configKeysFromStruct(desc.Config) - if err != nil { - return doc, fmt.Errorf("upload %q: %v", desc.Name, err) - } - - return doc, nil -} - -func newMetricsDoc(desc MetricsDesc) (metricsDoc, error) { - doc := metricsDoc{ - name: desc.Name, - } - - var err error - - doc.keys, err = configKeysFromStruct(desc.Config) - if err != nil { - return doc, fmt.Errorf("metrics %q: %v", desc.Name, err) - } - - return doc, nil -} - -type helpConfigKey struct { - name string // config key name - typ string // config key type - def string // default value - required bool - desc string -} - -func configKeysFromStruct(cfg interface{}) ([]helpConfigKey, error) { - var keys []helpConfigKey - - tf := reflect.TypeOf(cfg).Elem() - for i := 0; i < tf.NumField(); i++ { - f := tf.Field(i) - - // skip unexported fields - if f.PkgPath != "" && !f.Anonymous { - continue - } - - key, err := newHelpConfigKeyFromField(f) - if err != nil { - return nil, fmt.Errorf("error at exported key %d: %v", i, err) - } - keys = append(keys, key) - } - - return keys, nil -} - -func newHelpConfigKeyFromField(f reflect.StructField) (helpConfigKey, error) { - h := helpConfigKey{ - name: f.Name, - desc: f.Tag.Get("help"), - def: f.Tag.Get("default"), - required: f.Tag.Get("required") == "true", - } - - switch f.Type.Kind() { - case reflect.Int: - h.typ = "int" - case reflect.String: - h.typ = "string" - h.def = `"` + h.def + `"` - case reflect.Slice: - switch f.Type.Elem().Kind() { - case reflect.String: - h.typ = "array of strings" - case reflect.Int: - h.typ = "array of ints" - default: - return h, fmt.Errorf("config key %q: unsupported type array of %s", f.Type.Name(), f.Type.Elem()) - } - case reflect.Int64: - if f.Type.Name() == "Duration" { - h.typ = "duration" - } else { - h.typ = "int" - } - case reflect.Bool: - h.typ = "bool" - default: - return h, fmt.Errorf("config key %q: unsupported type", f.Type.Name()) - } - - return h, nil -} diff --git a/help_config.go b/help_config.go new file mode 100644 index 00000000..fe0c3ff1 --- /dev/null +++ b/help_config.go @@ -0,0 +1,182 @@ +package baker + +import ( + "fmt" + "reflect" +) + +type baseDoc struct { + name string // component name + help string // general help string + keys []helpConfigKey // configuration keys +} + +type inputDoc struct{ baseDoc } +type filterDoc struct{ baseDoc } +type uploadDoc struct{ baseDoc } + +type outputDoc struct { + baseDoc + raw bool // raw output? +} + +type metricsDoc struct { + name string // component name + keys []helpConfigKey // configuration keys +} + +func newInputDoc(desc InputDesc) (inputDoc, error) { + doc := inputDoc{ + baseDoc{ + name: desc.Name, + help: desc.Help, + }, + } + + var err error + + doc.keys, err = configKeysFromStruct(desc.Config) + if err != nil { + return doc, fmt.Errorf("input %q: %v", desc.Name, err) + } + + return doc, nil +} + +func newFilterDoc(desc FilterDesc) (filterDoc, error) { + doc := filterDoc{ + baseDoc{ + name: desc.Name, + help: desc.Help, + }, + } + + var err error + + doc.keys, err = configKeysFromStruct(desc.Config) + if err != nil { + return doc, fmt.Errorf("filter %q: %v", desc.Name, err) + } + + return doc, nil +} + +func newOutputDoc(desc OutputDesc) (outputDoc, error) { + doc := outputDoc{ + raw: desc.Raw, + baseDoc: baseDoc{ + name: desc.Name, + help: desc.Help, + }, + } + + var err error + + doc.keys, err = configKeysFromStruct(desc.Config) + if err != nil { + return doc, fmt.Errorf("output %q: %v", desc.Name, err) + } + + return doc, nil +} + +func newUploadDoc(desc UploadDesc) (uploadDoc, error) { + doc := uploadDoc{ + baseDoc{ + name: desc.Name, + help: desc.Help, + }, + } + + var err error + + doc.keys, err = configKeysFromStruct(desc.Config) + if err != nil { + return doc, fmt.Errorf("upload %q: %v", desc.Name, err) + } + + return doc, nil +} + +func newMetricsDoc(desc MetricsDesc) (metricsDoc, error) { + doc := metricsDoc{ + name: desc.Name, + } + + var err error + + doc.keys, err = configKeysFromStruct(desc.Config) + if err != nil { + return doc, fmt.Errorf("metrics %q: %v", desc.Name, err) + } + + return doc, nil +} + +type helpConfigKey struct { + name string // config key name + typ string // config key type + def string // default value + required bool + desc string +} + +func configKeysFromStruct(cfg interface{}) ([]helpConfigKey, error) { + var keys []helpConfigKey + + tf := reflect.TypeOf(cfg).Elem() + for i := 0; i < tf.NumField(); i++ { + f := tf.Field(i) + + // skip unexported fields + if f.PkgPath != "" && !f.Anonymous { + continue + } + + key, err := newHelpConfigKeyFromField(f) + if err != nil { + return nil, fmt.Errorf("error at exported key %d: %v", i, err) + } + keys = append(keys, key) + } + + return keys, nil +} + +func newHelpConfigKeyFromField(f reflect.StructField) (helpConfigKey, error) { + h := helpConfigKey{ + name: f.Name, + desc: f.Tag.Get("help"), + def: f.Tag.Get("default"), + required: f.Tag.Get("required") == "true", + } + + switch f.Type.Kind() { + case reflect.Int: + h.typ = "int" + case reflect.String: + h.typ = "string" + h.def = `"` + h.def + `"` + case reflect.Slice: + switch f.Type.Elem().Kind() { + case reflect.String: + h.typ = "array of strings" + case reflect.Int: + h.typ = "array of ints" + default: + return h, fmt.Errorf("config key %q: unsupported type array of %s", f.Type.Name(), f.Type.Elem()) + } + case reflect.Int64: + if f.Type.Name() == "Duration" { + h.typ = "duration" + } else { + h.typ = "int" + } + case reflect.Bool: + h.typ = "bool" + default: + return h, fmt.Errorf("config key %q: unsupported type", f.Type.Name()) + } + + return h, nil +} From f4080723b1c4c91bef76d07e834c1233ff6af20e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Thu, 12 Nov 2020 12:41:03 +0100 Subject: [PATCH 16/18] Add case to TestGenerateHelp --- help_config_test.go | 70 +++++++++++++++++++++++++++++++++++++++++++++ help_test.go | 1 - 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 help_config_test.go diff --git a/help_config_test.go b/help_config_test.go new file mode 100644 index 00000000..109beb21 --- /dev/null +++ b/help_config_test.go @@ -0,0 +1,70 @@ +package baker + +import ( + "bytes" + "testing" + "time" +) + +type dummyConfig struct { + IntField int `help:"int field" required:"true" default:"0"` + Int64Field int64 `help:"int64 field" required:"false" default:"1"` + DurationField time.Duration `help:"duration field" required:"true" default:"2s"` + StringField string `help:"string field" required:"true" default:"4"` + BoolField bool `help:"bool field" required:"true" default:"true"` + SliceOfStringsField []string `help:"strings field" required:"true" default:"[\"a\", \"b\", \"c\"]"` + SliceOfIntsField []int `help:"ints field" required:"true" default:"[0, 1, 2, 3]"` +} + +func TestGenerateHelp(t *testing.T) { + tests := []struct { + name string + desc interface{} + wantErr bool + }{ + { + name: "nil", + desc: nil, + wantErr: true, + }, + { + name: "unsupported type", + desc: 23, + wantErr: true, + }, + { + name: "supported configuration", + desc: InputDesc{Name: "name", Config: &dummyConfig{ + IntField: 1, + Int64Field: 2, + DurationField: 3, + StringField: "5", + BoolField: false, + SliceOfStringsField: []string{"foo", "bar"}, + SliceOfIntsField: []int{0, 1, 2, 3, 4, 5}, + }}, + }, + } + for _, tt := range tests { + t.Run(tt.name+"/text", func(t *testing.T) { + w := &bytes.Buffer{} + if err := GenerateTextHelp(w, tt.desc); (err != nil) != tt.wantErr { + t.Errorf("GenerateTextHelp() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr && w.String() == "" { + t.Errorf(`GenerateTextHelp() = "", shouldn't be empty`) + } + }) + t.Run(tt.name+"/markdown", func(t *testing.T) { + w := &bytes.Buffer{} + if err := GenerateMarkdownHelp(w, tt.desc); (err != nil) != tt.wantErr { + t.Errorf("GenerateMarkdownHelp() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !tt.wantErr && w.String() == "" { + t.Errorf(`GenerateMarkdownHelp() = "", shouldn't be empty`) + } + }) + } +} diff --git a/help_test.go b/help_test.go index c650ac68..91f9cecb 100644 --- a/help_test.go +++ b/help_test.go @@ -54,4 +54,3 @@ func TestAllUploadsHaveConfigHelp(t *testing.T) { assertValidConfigHelp(t, upload.Name, upload.Config) } } -} From 11adecb28586b93c296f2b4860bd7b27d9497f23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Thu, 12 Nov 2020 14:44:31 +0100 Subject: [PATCH 17/18] Add tests for help doc structs --- help_config_test.go | 196 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 196 insertions(+) diff --git a/help_config_test.go b/help_config_test.go index 109beb21..86cd08e5 100644 --- a/help_config_test.go +++ b/help_config_test.go @@ -2,6 +2,7 @@ package baker import ( "bytes" + "reflect" "testing" "time" ) @@ -16,6 +17,58 @@ type dummyConfig struct { SliceOfIntsField []int `help:"ints field" required:"true" default:"[0, 1, 2, 3]"` } +var dummyKeys = []helpConfigKey{ + { + name: "IntField", + typ: "int", + def: "0", + required: true, + desc: "int field", + }, + { + name: "Int64Field", + typ: "int", + def: "1", + required: false, + desc: "int64 field", + }, + { + name: "DurationField", + typ: "duration", + def: "2s", + required: true, + desc: "duration field", + }, + { + name: "StringField", + typ: "string", + def: `"4"`, + required: true, + desc: "string field", + }, + { + name: "BoolField", + typ: "bool", + def: "true", + required: true, + desc: "bool field", + }, + { + name: "SliceOfStringsField", + typ: "array of strings", + def: `["a", "b", "c"]`, + required: true, + desc: "strings field", + }, + { + name: "SliceOfIntsField", + typ: "array of ints", + def: `[0, 1, 2, 3]`, + required: true, + desc: "ints field", + }, +} + func TestGenerateHelp(t *testing.T) { tests := []struct { name string @@ -68,3 +121,146 @@ func TestGenerateHelp(t *testing.T) { }) } } + +func Test_newInputDoc(t *testing.T) { + const ( + name = "dummy" + help = "This is the high-level doc of the dummy input." + ) + + desc := InputDesc{ + Name: name, + Config: &dummyConfig{}, + Help: help, + } + want := inputDoc{ + baseDoc: baseDoc{ + name: name, + help: help, + keys: dummyKeys, + }, + } + + got, err := newInputDoc(desc) + if err != nil { + t.Errorf("newInputDoc() error = %v", err) + return + } + if !reflect.DeepEqual(got, want) { + t.Errorf("newInputDoc():\ngot:\n%+v\nwant:\n%+v", got, want) + } +} + +func Test_newFilterDoc(t *testing.T) { + const ( + name = "dummy" + help = "This is the high-level doc of the dummy filter." + ) + + desc := FilterDesc{ + Name: name, + Config: &dummyConfig{}, + Help: help, + } + want := filterDoc{ + baseDoc: baseDoc{ + name: name, + help: help, + keys: dummyKeys, + }, + } + + got, err := newFilterDoc(desc) + if err != nil { + t.Errorf("newFilterDoc() error = %v", err) + return + } + if !reflect.DeepEqual(got, want) { + t.Errorf("newFilterDoc():\ngot:\n%+v\nwant:\n%+v", got, want) + } +} + +func Test_newOuputDoc(t *testing.T) { + const ( + name = "dummy" + help = "This is the high-level doc of the dummy output." + ) + + desc := OutputDesc{ + Name: name, + Config: &dummyConfig{}, + Help: help, + Raw: true, + } + want := outputDoc{ + raw: true, + baseDoc: baseDoc{ + name: name, + help: help, + keys: dummyKeys, + }, + } + + got, err := newOutputDoc(desc) + if err != nil { + t.Errorf("newOutputDoc() error = %v", err) + return + } + if !reflect.DeepEqual(got, want) { + t.Errorf("newOutputDoc():\ngot:\n%+v\nwant:\n%+v", got, want) + } +} + +func Test_newUploadDoc(t *testing.T) { + const ( + name = "dummy" + help = "This is the high-level doc of the dummy upload." + ) + + desc := UploadDesc{ + Name: name, + Config: &dummyConfig{}, + Help: help, + } + want := uploadDoc{ + baseDoc: baseDoc{ + name: name, + help: help, + keys: dummyKeys, + }, + } + + got, err := newUploadDoc(desc) + if err != nil { + t.Errorf("newUploadDoc() error = %v", err) + return + } + if !reflect.DeepEqual(got, want) { + t.Errorf("newUploadDoc():\ngot:\n%+v\nwant:\n%+v", got, want) + } +} + +func Test_newMetricsDoc(t *testing.T) { + const ( + name = "dummy" + help = "This is the high-level doc of the dummy metrics." + ) + + desc := MetricsDesc{ + Name: name, + Config: &dummyConfig{}, + } + want := metricsDoc{ + name: name, + keys: dummyKeys, + } + + got, err := newMetricsDoc(desc) + if err != nil { + t.Errorf("newMetricsDoc() error = %v", err) + return + } + if !reflect.DeepEqual(got, want) { + t.Errorf("newMetricsDoc():\ngot:\n%+v\nwant:\n%+v", got, want) + } +} From e1f31c64b0e07b1672ebb9fa5bd0d8a351ffbe0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Rainone?= Date: Thu, 12 Nov 2020 15:20:52 +0100 Subject: [PATCH 18/18] go mod tidy --- go.sum | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/go.sum b/go.sum index 3aadba96..9eb24a02 100644 --- a/go.sum +++ b/go.sum @@ -4,11 +4,14 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U= github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= github.com/alecthomas/chroma v0.7.3 h1:NfdAERMy+esYQs8OXk0I868/qDxxCEo7FMz1WIqMAeI= github.com/alecthomas/chroma v0.7.3/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM= +github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo= github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= github.com/alecthomas/kong v0.2.4/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE= +github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY= github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -127,6 +130,7 @@ github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40T github.com/rasky/toml v0.1.1-0.20160309013025-90bcb678a72a h1:Rbac1N2pVUVpc/StliFKe/Ze677rTJXah54a24EYggY= github.com/rasky/toml v0.1.1-0.20160309013025-90bcb678a72a/go.mod h1:8gCi4R7MCILewoZRV5Zw1SP1JnlKl+rLSM35uMF//VQ= github.com/schollz/progressbar/v2 v2.13.2/go.mod h1:6YZjqdthH6SCZKv2rqGryrxPtfmRB/DWZxSMfCXPyD8= +github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= @@ -164,8 +168,6 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190528012530-adf421d2caf4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191003212358-c178f38b412c h1:6Zx7DRlKXf79yfxuQ/7GqV3w2y7aDsk6bGg0MzF5RVU= -golang.org/x/sys v0.0.0-20191003212358-c178f38b412c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200413165638-669c56c373c4 h1:opSr2sbRXk5X5/givKrrKj9HXxFpW2sdCiP8MJSKLQY=