Skip to content
This repository was archived by the owner on Mar 16, 2025. It is now read-only.

Commit e25eb29

Browse files
committed
automate docs TOC
1 parent 54cf7fe commit e25eb29

6 files changed

Lines changed: 173 additions & 70 deletions

File tree

Makefile

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,16 @@ GO_BUILD_FLAGS ?= -v -ldflags '$(GO_LDFLAGS)'
1414
GO_TEST_FLAGS ?=
1515

1616
.PHONY: all
17-
all: clean generate build test
17+
all: clean generate build docs test
1818

1919
.PHONY: generate
2020
generate:
2121
pigeon pkg/lang/grammar/rudi.peg > pkg/lang/parser/generated.go
2222

23+
.PHONY: docs
24+
docs:
25+
go run hack/docs-toc/main.go
26+
2327
.PHONY: clean
2428
clean:
2529
rm -rf _build

docs/README.md

Lines changed: 14 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -2,70 +2,21 @@
22

33
Welcome to the Rudi documentation :smile:
44

5+
<!-- BEGIN_TOC -->
56
## General
67

7-
* [Language Spec](language.md)
8+
* [language](language.md) – A short introduction to the Rudi language
89

9-
## Function Library
10+
## Core Functions
1011

11-
* core
12-
* [`default`](functions/core-default.md)
13-
* [`delete`](functions/core-delete.md)
14-
* [`do`](functions/core-do.md)
15-
* [`has?`](functions/core-has.md)
16-
* [`if`](functions/core-if.md)
17-
* [`empty?`](functions/core-isEmpty.md)
18-
* [`set`](functions/core-set.md)
19-
* [`try`](functions/core-try.md)
20-
* logic
21-
* [`and`](functions/logic-and.md)
22-
* [`or`](functions/logic-or.md)
23-
* [`not`](functions/logic-not.md)
24-
* math
25-
* [`+`](functions/math-sum.md) (aliases: `add`)
26-
* [`-`](functions/math-sub.md) (aliases: `sub`)
27-
* [`*`](functions/math-multiply.md) (aliases: `mult`)
28-
* [`/`](functions/math-divide.md) (aliases: `div`)
29-
* strings
30-
* [`concat`](functions/strings-concat.md)
31-
* [`has-prefix?`](functions/strings-has-prefix.md)
32-
* [`has-suffix?`](functions/strings-has-suffix.md)
33-
* [`len`](functions/lists-len.md)
34-
* [`reverse`](functions/lists-reverse.md)
35-
* [`split`](functions/strings-split.md)
36-
* [`to-lower`](functions/strings-to-lower.md)
37-
* [`to-upper`](functions/strings-to-upper.md)
38-
* [`trim-prefix`](functions/strings-trim-prefix.md)
39-
* [`trim-suffix`](functions/strings-trim-suffix.md)
40-
* [`trim`](functions/strings-trim.md)
41-
* lists
42-
* [`append`](functions/lists-append.md)
43-
* [`contains?`](functions/lists-contains.md)
44-
* [`filter`](functions/lists-filter.md)
45-
* [`len`](functions/lists-len.md)
46-
* [`map`](functions/lists-map.md)
47-
* [`prepend`](functions/lists-prepend.md)
48-
* [`range`](functions/lists-range.md)
49-
* [`reverse`](functions/lists-reverse.md)
50-
* comparisons
51-
* [`eq?`](functions/comparisons-eq.md)
52-
* [`like?`](functions/comparisons-like.md)
53-
* [`lt?`](functions/comparisons-lt.md)
54-
* [`lte?`](functions/comparisons-lte.md)
55-
* [`gt?`](functions/comparisons-gt.md)
56-
* [`gte?`](functions/comparisons-gte.md)
57-
* types
58-
* [`type-of`](functions/types-type-of.md)
59-
* [`to-string`](functions/types-to-string.md)
60-
* [`to-int`](functions/types-to-int.md)
61-
* [`to-float`](functions/types-to-float.md)
62-
* [`to-bool`](functions/types-to-bool.md)
63-
* hashes
64-
* [`sha1`](functions/hashes-sha1.md)
65-
* [`sha256`](functions/hashes-sha256.md)
66-
* [`sha512`](functions/hashes-sha512.md)
67-
* encoding
68-
* [`to-base64`](functions/encodingto-base64.md)
69-
* [`from-base64`](functions/encodingfrom-base64.md)
70-
* dates & time
71-
* [`now`](functions/dates-now.md)
12+
* [`default`](functions/core-default.md) – returns the default value if the first argument is empty
13+
* [`do`](functions/core-do.md) – eval a sequence of statements where only one expression is valid
14+
* [`empty?`](functions/core-empty.md) – returns true when the given value is empty-ish (0, false, null, "", ...)
15+
* [`has?`](functions/core-has.md) – returns true if the given symbol's path expression points to an existing value
16+
* [`if`](functions/core-if.md) – evaluate one of two expressions based on a condition
17+
* [`try`](functions/core-try.md) – returns the fallback if the first expression errors out
18+
19+
## Math Functions
20+
21+
* [`+`](functions/math-sum.md) – returns the sum of all of its arguments
22+
<!-- END_TOC -->

docs/embed.go

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ type Topic struct {
1616
CliNames []string
1717
Group string
1818
Description string
19-
filename string
19+
IsFunction bool
20+
Filename string
2021
}
2122

2223
func (t *Topic) Content() ([]byte, error) {
23-
return embeddedFS.ReadFile(t.filename)
24+
return embeddedFS.ReadFile(t.Filename)
2425
}
2526

2627
func Topics() []Topic {
@@ -29,7 +30,7 @@ func Topics() []Topic {
2930
CliNames: []string{"language", "lang", "rudi"},
3031
Group: "General",
3132
Description: "A short introduction to the Rudi language",
32-
filename: "language.md",
33+
Filename: "language.md",
3334
},
3435
}
3536

@@ -56,15 +57,27 @@ func Topics() []Topic {
5657

5758
topics = append(topics, Topic{
5859
CliNames: []string{funcName, sanitized},
59-
Group: group + " Functions",
60+
Group: ucFirst(group) + " Functions",
6061
Description: function.Description(),
61-
filename: filename,
62+
Filename: filename,
63+
IsFunction: true,
6264
})
6365
}
6466

6567
return topics
6668
}
6769

70+
func ucFirst(s string) string {
71+
if len(s) < 2 {
72+
return strings.ToUpper(s)
73+
}
74+
75+
first := string(s[0])
76+
tail := string(s[1:])
77+
78+
return strings.ToUpper(first) + tail
79+
}
80+
6881
// Function names are global in Rudi; however the docs is logically split
6982
// into groups like "core" or "math", which also make sense in the documentation
7083
// (hence why names are like "core-if.md").

docs/functions/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Rudi Functions
1+
# Rudi Standard Library
22

33
Rudi ships with a set of built-in functions. When embedding Rudi, this set can
44
be extended or overwritten as desired to inject custom functions. It is not

docs/language.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ Keys and values are separated by whitespace (not with a `:` like in JSON). Likew
9292
are separated from each other by whitespace. In effect, each object declaration needs to have an
9393
even number of expression in it.
9494

95+
As a convenience feature, identifiers are also allowed as object keys and will, in this special
96+
instance, be converted to strings, so `{foo "bar"}` is equivalent to `{"foo" "bar"}`.
97+
9598
Empty objects are permitted (`{}`).
9699

97100
Note that objects are internally unordered and functions like [map](functions/lists-map.md) or

hack/docs-toc/main.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// SPDX-FileCopyrightText: 2023 Christoph Mewes
2+
// SPDX-License-Identifier: MIT
3+
4+
package main
5+
6+
import (
7+
"fmt"
8+
html "html/template"
9+
"log"
10+
"os"
11+
"regexp"
12+
"sort"
13+
"strings"
14+
15+
"go.xrstf.de/rudi/docs"
16+
)
17+
18+
const (
19+
filename = "docs/README.md"
20+
beginMarker = `<!-- BEGIN_TOC -->`
21+
endMarker = `<!-- END_TOC -->`
22+
)
23+
24+
func main() {
25+
topics := docs.Topics()
26+
groups := getGroups(topics)
27+
28+
rendered := renderTopics(topics, groups)
29+
rendered = fmt.Sprintf("%s\n%s\n%s", beginMarker, rendered, endMarker)
30+
31+
content, err := os.ReadFile(filename)
32+
if err != nil {
33+
log.Fatalf("Failed to read %s: %v", filename, err)
34+
}
35+
36+
regex := regexp.MustCompile(`(?s)` + beginMarker + `.+` + endMarker)
37+
output := regex.ReplaceAllString(string(content), rendered)
38+
39+
os.WriteFile(filename, []byte(output), 0644)
40+
}
41+
42+
func strSliceHas(haystack []string, needle string) bool {
43+
for _, val := range haystack {
44+
if val == needle {
45+
return true
46+
}
47+
}
48+
49+
return false
50+
}
51+
52+
func getGroups(topics []docs.Topic) []string {
53+
// determine a sorted list of functions, with some groups
54+
// hardcoded to be at the top, regardless of their name
55+
prioritizedGroups := []string{
56+
"General",
57+
"Core Functions",
58+
}
59+
60+
remainingGroups := []string{}
61+
62+
for _, topic := range topics {
63+
if !strSliceHas(prioritizedGroups, topic.Group) {
64+
if !strSliceHas(remainingGroups, topic.Group) {
65+
remainingGroups = append(remainingGroups, topic.Group)
66+
}
67+
}
68+
}
69+
70+
sort.Strings(remainingGroups)
71+
72+
return append(prioritizedGroups, remainingGroups...)
73+
}
74+
75+
func renderTopics(topics []docs.Topic, groups []string) string {
76+
var out strings.Builder
77+
78+
for _, group := range groups {
79+
out.WriteString(fmt.Sprintf("## %s\n", group))
80+
out.WriteString("\n")
81+
82+
topicNames := getTopicNames(topics, group)
83+
for _, topicName := range topicNames {
84+
topic := getTopic(topics, topicName)
85+
linkTitle := topicName
86+
87+
if topic.IsFunction {
88+
linkTitle = fmt.Sprintf("`%s`", linkTitle)
89+
}
90+
91+
out.WriteString(fmt.Sprintf("* [%s](%s) – %s\n", linkTitle, topic.Filename, topic.Description))
92+
}
93+
94+
out.WriteString("\n")
95+
}
96+
97+
return strings.TrimSpace(out.String())
98+
}
99+
100+
func htmlencode(s string) string {
101+
return html.HTMLEscapeString(s)
102+
}
103+
104+
func getTopicNames(topics []docs.Topic, group string) []string {
105+
names := []string{}
106+
107+
for _, topic := range topics {
108+
if topic.Group != group {
109+
continue
110+
}
111+
112+
primaryName := topic.CliNames[0]
113+
114+
if !strSliceHas(names, primaryName) {
115+
names = append(names, primaryName)
116+
}
117+
}
118+
119+
sort.Strings(names)
120+
121+
return names
122+
}
123+
124+
func getTopic(topics []docs.Topic, name string) docs.Topic {
125+
for _, topic := range topics {
126+
if topic.CliNames[0] == name {
127+
return topic
128+
}
129+
}
130+
131+
panic("this should never happen")
132+
}

0 commit comments

Comments
 (0)