Skip to content

Commit 1306561

Browse files
committed
pkg/config/configtest: move package cfgtest from chainlink/v2
1 parent d9eabb4 commit 1306561

File tree

8 files changed

+522
-4
lines changed

8 files changed

+522
-4
lines changed

go.mod

Lines changed: 2 additions & 1 deletion
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

Lines changed: 215 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
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 len(desc) > 0 {
70+
if strings.HasPrefix(strings.TrimSpace(desc[0]), TokenAdvanced) {
71+
t.adv = true
72+
t.desc = t.desc[1:]
73+
} else if extended, ok := extendedDescriptions[t.name]; ok {
74+
t.extended = extended
75+
}
76+
}
77+
return t
78+
}
79+
80+
func newArrayOfTables(line string, desc lines, extendedDescriptions map[string]string) *table {
81+
t := &table{
82+
name: strings.Trim(strings.Trim(line, FieldExample), "[]"),
83+
codes: []string{line},
84+
desc: desc,
85+
}
86+
if len(desc) > 0 {
87+
if strings.HasPrefix(strings.TrimSpace(desc[0]), TokenAdvanced) {
88+
t.adv = true
89+
t.desc = t.desc[1:]
90+
} else if extended, ok := extendedDescriptions[t.name]; ok {
91+
t.extended = extended
92+
}
93+
}
94+
return t
95+
}
96+
97+
func (t table) advanced() string {
98+
if t.adv {
99+
return advancedWarning("Do not change these settings unless you know what you are doing.")
100+
}
101+
return ""
102+
}
103+
104+
func (t table) code() string {
105+
if t.extended == "" {
106+
return fmt.Sprint("```toml\n", t.codes, "\n```\n")
107+
}
108+
return ""
109+
}
110+
111+
// String prints a table as an H2, followed by a code block and description.
112+
func (t *table) String() string {
113+
return fmt.Sprint("## ", t.name, "\n",
114+
t.advanced(),
115+
t.code(),
116+
t.desc,
117+
t.extended)
118+
}
119+
120+
type keyval struct {
121+
name string
122+
code string
123+
adv bool
124+
desc lines
125+
}
126+
127+
func newKeyval(line string, desc lines) keyval {
128+
line = strings.TrimSpace(line)
129+
kv := keyval{
130+
name: line[:strings.Index(line, " ")],
131+
code: line,
132+
desc: desc,
133+
}
134+
if len(desc) > 0 && strings.HasPrefix(strings.TrimSpace(desc[0]), TokenAdvanced) {
135+
kv.adv = true
136+
kv.desc = kv.desc[1:]
137+
}
138+
return kv
139+
}
140+
141+
func (k keyval) advanced() string {
142+
if k.adv {
143+
return advancedWarning("Do not change this setting unless you know what you are doing.")
144+
}
145+
return ""
146+
}
147+
148+
// String prints a keyval as an H3, followed by a code block and description.
149+
func (k keyval) String() string {
150+
name := k.name
151+
if i := strings.LastIndex(name, "."); i > -1 {
152+
name = name[i+1:]
153+
}
154+
return fmt.Sprint("### ", name, "\n",
155+
k.advanced(),
156+
"```toml\n",
157+
k.code,
158+
"\n```\n",
159+
k.desc)
160+
}
161+
162+
func parseTOMLDocs(s string, extendedDescriptions map[string]string) (items []fmt.Stringer, err error) {
163+
defer func() { _, err = config.MultiErrorList(err) }()
164+
globalTable := table{name: "Global"}
165+
currentTable := &globalTable
166+
items = append(items, currentTable)
167+
var desc lines
168+
for _, line := range strings.Split(s, "\n") {
169+
if strings.HasPrefix(line, "#") {
170+
// comment
171+
desc = append(desc, strings.TrimSpace(line[1:]))
172+
} else if strings.TrimSpace(line) == "" {
173+
// empty
174+
if len(desc) > 0 {
175+
items = append(items, desc)
176+
desc = nil
177+
}
178+
} else if strings.HasPrefix(line, "[[") {
179+
currentTable = newArrayOfTables(line, desc, extendedDescriptions)
180+
items = append(items, currentTable)
181+
desc = nil
182+
} else if strings.HasPrefix(line, "[") {
183+
currentTable = newTable(line, desc, extendedDescriptions)
184+
items = append(items, currentTable)
185+
desc = nil
186+
} else {
187+
kv := newKeyval(line, desc)
188+
shortName := kv.name
189+
if currentTable != &globalTable {
190+
// update to full name
191+
kv.name = currentTable.name + "." + kv.name
192+
}
193+
if len(kv.desc) == 0 {
194+
err = errors.Join(err, fmt.Errorf("%s: missing description", kv.name))
195+
} else if !strings.HasPrefix(kv.desc[0], shortName) {
196+
err = errors.Join(err, fmt.Errorf("%s: description does not begin with %q", kv.name, shortName))
197+
}
198+
if !strings.HasSuffix(line, FieldDefault) && !strings.HasSuffix(line, FieldExample) {
199+
err = errors.Join(err, fmt.Errorf(`%s: is not one of %v`, kv.name, []string{FieldDefault, FieldExample}))
200+
}
201+
202+
items = append(items, kv)
203+
currentTable.codes = append(currentTable.codes, kv.code)
204+
desc = nil
205+
}
206+
}
207+
if len(globalTable.codes) == 0 {
208+
// drop it
209+
items = items[1:]
210+
}
211+
if len(desc) > 0 {
212+
items = append(items, desc)
213+
}
214+
return
215+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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 := `This is the example.`
29+
30+
s, err := Generate(def, header, example, map[string]string{
31+
"Baz": "Baz has an extended description",
32+
})
33+
require.NoError(t, err)
34+
35+
require.Equal(t, exp, s)
36+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Example docs
2+
This is the header. It has a list:
3+
- first
4+
- second
5+
## Example
6+
7+
```toml
8+
This is the example.
9+
```
10+
11+
## Global
12+
```toml
13+
Foo = false # Default
14+
Bar = 42 # Example
15+
```
16+
17+
18+
### Foo
19+
```toml
20+
Foo = false # Default
21+
```
22+
Foo is a boolean field.
23+
24+
### Bar
25+
```toml
26+
Bar = 42 # Example
27+
```
28+
Bar is a number.
29+
30+
## Baz
31+
```toml
32+
[Baz]
33+
Test = "test" # Example
34+
```
35+
36+
37+
### Test
38+
```toml
39+
Test = "test" # Example
40+
```
41+
Test holds a string.
42+

0 commit comments

Comments
 (0)