Skip to content

Commit d04a3b6

Browse files
authored
pkg/config/configtest: move package cfgtest from chainlink/v2 (#1103)
1 parent f505552 commit d04a3b6

File tree

8 files changed

+522
-4
lines changed

8 files changed

+522
-4
lines changed

go.mod

+2-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ require (
2626
github.com/jmoiron/sqlx v1.4.0
2727
github.com/jonboulle/clockwork v0.4.0
2828
github.com/jpillora/backoff v1.0.0
29+
github.com/kylelemons/godebug v1.1.0
2930
github.com/lib/pq v1.10.9
3031
github.com/marcboeker/go-duckdb v1.8.3
3132
github.com/pelletier/go-toml/v2 v2.2.3
@@ -53,7 +54,6 @@ require (
5354
go.opentelemetry.io/otel/sdk/log v0.6.0
5455
go.opentelemetry.io/otel/sdk/metric v1.35.0
5556
go.opentelemetry.io/otel/trace v1.35.0
56-
go.uber.org/multierr v1.11.0
5757
go.uber.org/zap v1.27.0
5858
golang.org/x/crypto v0.36.0
5959
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0
@@ -121,6 +121,7 @@ require (
121121
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
122122
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect
123123
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
124+
go.uber.org/multierr v1.11.0 // indirect
124125
golang.org/x/mod v0.24.0 // indirect
125126
golang.org/x/net v0.38.0 // indirect
126127
golang.org/x/sync v0.12.0 // indirect

pkg/config/configdoc/configdoc.go

+217
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
package configdoc
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"strings"
7+
8+
"github.com/smartcontractkit/chainlink-common/pkg/config"
9+
)
10+
11+
const (
12+
FieldDefault = "# Default"
13+
FieldExample = "# Example"
14+
15+
TokenAdvanced = "**ADVANCED**"
16+
)
17+
18+
// Generate returns MarkDown documentation generated from the TOML string.
19+
// - Each field but include a trailing comment of either FieldDefault or FieldExample.
20+
// - If a description begins with TokenAdvanced, then a warning will be included.
21+
// - The markdown wil begin with the header, followed by the example
22+
// - Extended descriptions can be applied to top level tables
23+
func Generate(toml, header, example string, extendedDescriptions map[string]string) (string, error) {
24+
items, err := parseTOMLDocs(toml, extendedDescriptions)
25+
var sb strings.Builder
26+
27+
sb.WriteString(header)
28+
sb.WriteString(`
29+
## Example
30+
31+
`)
32+
sb.WriteString("```toml\n")
33+
sb.WriteString(example)
34+
sb.WriteString("\n```\n\n")
35+
36+
for _, item := range items {
37+
sb.WriteString(item.String())
38+
sb.WriteString("\n\n")
39+
}
40+
41+
return sb.String(), err
42+
}
43+
44+
func advancedWarning(msg string) string {
45+
return fmt.Sprintf(":warning: **_ADVANCED_**: _%s_\n", msg)
46+
}
47+
48+
// lines holds a set of contiguous lines
49+
type lines []string
50+
51+
func (d lines) String() string {
52+
return strings.Join(d, "\n")
53+
}
54+
55+
type table struct {
56+
name string
57+
codes lines
58+
adv bool
59+
desc lines
60+
extended string
61+
}
62+
63+
func newTable(line string, desc lines, extendedDescriptions map[string]string) *table {
64+
t := &table{
65+
name: strings.Trim(line, "[]"),
66+
codes: []string{line},
67+
desc: desc,
68+
}
69+
if extended, ok := extendedDescriptions[t.name]; ok {
70+
t.extended = extended
71+
}
72+
if len(desc) > 0 {
73+
if strings.HasPrefix(strings.TrimSpace(desc[0]), TokenAdvanced) {
74+
t.adv = true
75+
t.desc = t.desc[1:]
76+
}
77+
}
78+
return t
79+
}
80+
81+
func newArrayOfTables(line string, desc lines, extendedDescriptions map[string]string) *table {
82+
t := &table{
83+
name: strings.Trim(strings.Trim(line, FieldExample), "[]"),
84+
codes: []string{line},
85+
desc: desc,
86+
}
87+
if extended, ok := extendedDescriptions[t.name]; ok {
88+
t.extended = extended
89+
}
90+
if len(desc) > 0 {
91+
if strings.HasPrefix(strings.TrimSpace(desc[0]), TokenAdvanced) {
92+
t.adv = true
93+
t.desc = t.desc[1:]
94+
}
95+
}
96+
return t
97+
}
98+
99+
func (t table) advanced() string {
100+
if t.adv {
101+
return advancedWarning("Do not change these settings unless you know what you are doing.")
102+
}
103+
return ""
104+
}
105+
106+
func (t table) code() string {
107+
if t.extended == "" {
108+
return fmt.Sprint("```toml\n", t.codes, "\n```\n")
109+
}
110+
return ""
111+
}
112+
113+
// String prints a table as an H2, followed by a code block and description.
114+
func (t *table) String() string {
115+
return fmt.Sprint("## ", t.name, "\n",
116+
t.advanced(),
117+
t.code(),
118+
t.desc,
119+
t.extended)
120+
}
121+
122+
type keyval struct {
123+
name string
124+
code string
125+
adv bool
126+
desc lines
127+
}
128+
129+
func newKeyval(line string, desc lines) keyval {
130+
line = strings.TrimSpace(line)
131+
kv := keyval{
132+
name: line[:strings.Index(line, " ")],
133+
code: line,
134+
desc: desc,
135+
}
136+
if len(desc) > 0 && strings.HasPrefix(strings.TrimSpace(desc[0]), TokenAdvanced) {
137+
kv.adv = true
138+
kv.desc = kv.desc[1:]
139+
}
140+
return kv
141+
}
142+
143+
func (k keyval) advanced() string {
144+
if k.adv {
145+
return advancedWarning("Do not change this setting unless you know what you are doing.")
146+
}
147+
return ""
148+
}
149+
150+
// String prints a keyval as an H3, followed by a code block and description.
151+
func (k keyval) String() string {
152+
name := k.name
153+
if i := strings.LastIndex(name, "."); i > -1 {
154+
name = name[i+1:]
155+
}
156+
return fmt.Sprint("### ", name, "\n",
157+
k.advanced(),
158+
"```toml\n",
159+
k.code,
160+
"\n```\n",
161+
k.desc)
162+
}
163+
164+
func parseTOMLDocs(s string, extendedDescriptions map[string]string) (items []fmt.Stringer, err error) {
165+
defer func() { _, err = config.MultiErrorList(err) }()
166+
globalTable := table{name: "Global"}
167+
currentTable := &globalTable
168+
items = append(items, currentTable)
169+
var desc lines
170+
for _, line := range strings.Split(s, "\n") {
171+
if strings.HasPrefix(line, "#") {
172+
// comment
173+
desc = append(desc, strings.TrimSpace(line[1:]))
174+
} else if strings.TrimSpace(line) == "" {
175+
// empty
176+
if len(desc) > 0 {
177+
items = append(items, desc)
178+
desc = nil
179+
}
180+
} else if strings.HasPrefix(line, "[[") {
181+
currentTable = newArrayOfTables(line, desc, extendedDescriptions)
182+
items = append(items, currentTable)
183+
desc = nil
184+
} else if strings.HasPrefix(line, "[") {
185+
currentTable = newTable(line, desc, extendedDescriptions)
186+
items = append(items, currentTable)
187+
desc = nil
188+
} else {
189+
kv := newKeyval(line, desc)
190+
shortName := kv.name
191+
if currentTable != &globalTable {
192+
// update to full name
193+
kv.name = currentTable.name + "." + kv.name
194+
}
195+
if len(kv.desc) == 0 {
196+
err = errors.Join(err, fmt.Errorf("%s: missing description", kv.name))
197+
} else if !strings.HasPrefix(kv.desc[0], shortName) {
198+
err = errors.Join(err, fmt.Errorf("%s: description does not begin with %q", kv.name, shortName))
199+
}
200+
if !strings.HasSuffix(line, FieldDefault) && !strings.HasSuffix(line, FieldExample) {
201+
err = errors.Join(err, fmt.Errorf(`%s: is not one of %v`, kv.name, []string{FieldDefault, FieldExample}))
202+
}
203+
204+
items = append(items, kv)
205+
currentTable.codes = append(currentTable.codes, kv.code)
206+
desc = nil
207+
}
208+
}
209+
if len(globalTable.codes) == 0 {
210+
// drop it
211+
items = items[1:]
212+
}
213+
if len(desc) > 0 {
214+
items = append(items, desc)
215+
}
216+
return
217+
}
+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package configdoc
2+
3+
import (
4+
_ "embed"
5+
"testing"
6+
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
//go:embed testdata/gen_exp.md
11+
var exp string
12+
13+
func TestGenerate(t *testing.T) {
14+
def := `
15+
# Foo is a boolean field.
16+
Foo = false # Default
17+
# Bar is a number.
18+
Bar = 42 # Example
19+
[Baz]
20+
# Test holds a string.
21+
Test = "test" # Example`
22+
23+
header := `# Example docs
24+
This is the header. It has a list:
25+
- first
26+
- second`
27+
28+
example := `Bar = 10
29+
Baz.Test = "asdf"`
30+
31+
s, err := Generate(def, header, example, map[string]string{
32+
"Baz": "Baz has an extended description",
33+
})
34+
require.NoError(t, err)
35+
36+
require.Equal(t, exp, s)
37+
}
+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Example docs
2+
This is the header. It has a list:
3+
- first
4+
- second
5+
## Example
6+
7+
```toml
8+
Bar = 10
9+
Baz.Test = "asdf"
10+
```
11+
12+
## Global
13+
```toml
14+
Foo = false # Default
15+
Bar = 42 # Example
16+
```
17+
18+
19+
### Foo
20+
```toml
21+
Foo = false # Default
22+
```
23+
Foo is a boolean field.
24+
25+
### Bar
26+
```toml
27+
Bar = 42 # Example
28+
```
29+
Bar is a number.
30+
31+
## Baz
32+
Baz has an extended description
33+
34+
### Test
35+
```toml
36+
Test = "test" # Example
37+
```
38+
Test holds a string.
39+

0 commit comments

Comments
 (0)