Skip to content

Commit

Permalink
automate docs TOC
Browse files Browse the repository at this point in the history
  • Loading branch information
xrstf committed Nov 25, 2023
1 parent 54cf7fe commit e25eb29
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 70 deletions.
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,16 @@ GO_BUILD_FLAGS ?= -v -ldflags '$(GO_LDFLAGS)'
GO_TEST_FLAGS ?=

.PHONY: all
all: clean generate build test
all: clean generate build docs test

.PHONY: generate
generate:
pigeon pkg/lang/grammar/rudi.peg > pkg/lang/parser/generated.go

.PHONY: docs
docs:
go run hack/docs-toc/main.go

.PHONY: clean
clean:
rm -rf _build
Expand Down
77 changes: 14 additions & 63 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,70 +2,21 @@

Welcome to the Rudi documentation :smile:

<!-- BEGIN_TOC -->
## General

* [Language Spec](language.md)
* [language](language.md) – A short introduction to the Rudi language

## Function Library
## Core Functions

* core
* [`default`](functions/core-default.md)
* [`delete`](functions/core-delete.md)
* [`do`](functions/core-do.md)
* [`has?`](functions/core-has.md)
* [`if`](functions/core-if.md)
* [`empty?`](functions/core-isEmpty.md)
* [`set`](functions/core-set.md)
* [`try`](functions/core-try.md)
* logic
* [`and`](functions/logic-and.md)
* [`or`](functions/logic-or.md)
* [`not`](functions/logic-not.md)
* math
* [`+`](functions/math-sum.md) (aliases: `add`)
* [`-`](functions/math-sub.md) (aliases: `sub`)
* [`*`](functions/math-multiply.md) (aliases: `mult`)
* [`/`](functions/math-divide.md) (aliases: `div`)
* strings
* [`concat`](functions/strings-concat.md)
* [`has-prefix?`](functions/strings-has-prefix.md)
* [`has-suffix?`](functions/strings-has-suffix.md)
* [`len`](functions/lists-len.md)
* [`reverse`](functions/lists-reverse.md)
* [`split`](functions/strings-split.md)
* [`to-lower`](functions/strings-to-lower.md)
* [`to-upper`](functions/strings-to-upper.md)
* [`trim-prefix`](functions/strings-trim-prefix.md)
* [`trim-suffix`](functions/strings-trim-suffix.md)
* [`trim`](functions/strings-trim.md)
* lists
* [`append`](functions/lists-append.md)
* [`contains?`](functions/lists-contains.md)
* [`filter`](functions/lists-filter.md)
* [`len`](functions/lists-len.md)
* [`map`](functions/lists-map.md)
* [`prepend`](functions/lists-prepend.md)
* [`range`](functions/lists-range.md)
* [`reverse`](functions/lists-reverse.md)
* comparisons
* [`eq?`](functions/comparisons-eq.md)
* [`like?`](functions/comparisons-like.md)
* [`lt?`](functions/comparisons-lt.md)
* [`lte?`](functions/comparisons-lte.md)
* [`gt?`](functions/comparisons-gt.md)
* [`gte?`](functions/comparisons-gte.md)
* types
* [`type-of`](functions/types-type-of.md)
* [`to-string`](functions/types-to-string.md)
* [`to-int`](functions/types-to-int.md)
* [`to-float`](functions/types-to-float.md)
* [`to-bool`](functions/types-to-bool.md)
* hashes
* [`sha1`](functions/hashes-sha1.md)
* [`sha256`](functions/hashes-sha256.md)
* [`sha512`](functions/hashes-sha512.md)
* encoding
* [`to-base64`](functions/encodingto-base64.md)
* [`from-base64`](functions/encodingfrom-base64.md)
* dates & time
* [`now`](functions/dates-now.md)
* [`default`](functions/core-default.md) – returns the default value if the first argument is empty
* [`do`](functions/core-do.md) – eval a sequence of statements where only one expression is valid
* [`empty?`](functions/core-empty.md) – returns true when the given value is empty-ish (0, false, null, "", ...)
* [`has?`](functions/core-has.md) – returns true if the given symbol's path expression points to an existing value
* [`if`](functions/core-if.md) – evaluate one of two expressions based on a condition
* [`try`](functions/core-try.md) – returns the fallback if the first expression errors out

## Math Functions

* [`+`](functions/math-sum.md) – returns the sum of all of its arguments
<!-- END_TOC -->
23 changes: 18 additions & 5 deletions docs/embed.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ type Topic struct {
CliNames []string
Group string
Description string
filename string
IsFunction bool
Filename string
}

func (t *Topic) Content() ([]byte, error) {
return embeddedFS.ReadFile(t.filename)
return embeddedFS.ReadFile(t.Filename)
}

func Topics() []Topic {
Expand All @@ -29,7 +30,7 @@ func Topics() []Topic {
CliNames: []string{"language", "lang", "rudi"},
Group: "General",
Description: "A short introduction to the Rudi language",
filename: "language.md",
Filename: "language.md",
},
}

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

topics = append(topics, Topic{
CliNames: []string{funcName, sanitized},
Group: group + " Functions",
Group: ucFirst(group) + " Functions",
Description: function.Description(),
filename: filename,
Filename: filename,
IsFunction: true,
})
}

return topics
}

func ucFirst(s string) string {
if len(s) < 2 {
return strings.ToUpper(s)
}

first := string(s[0])
tail := string(s[1:])

return strings.ToUpper(first) + tail
}

// Function names are global in Rudi; however the docs is logically split
// into groups like "core" or "math", which also make sense in the documentation
// (hence why names are like "core-if.md").
Expand Down
2 changes: 1 addition & 1 deletion docs/functions/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Rudi Functions
# Rudi Standard Library

Rudi ships with a set of built-in functions. When embedding Rudi, this set can
be extended or overwritten as desired to inject custom functions. It is not
Expand Down
3 changes: 3 additions & 0 deletions docs/language.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ Keys and values are separated by whitespace (not with a `:` like in JSON). Likew
are separated from each other by whitespace. In effect, each object declaration needs to have an
even number of expression in it.

As a convenience feature, identifiers are also allowed as object keys and will, in this special
instance, be converted to strings, so `{foo "bar"}` is equivalent to `{"foo" "bar"}`.

Empty objects are permitted (`{}`).

Note that objects are internally unordered and functions like [map](functions/lists-map.md) or
Expand Down
132 changes: 132 additions & 0 deletions hack/docs-toc/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// SPDX-FileCopyrightText: 2023 Christoph Mewes
// SPDX-License-Identifier: MIT

package main

import (
"fmt"
html "html/template"
"log"
"os"
"regexp"
"sort"
"strings"

"go.xrstf.de/rudi/docs"
)

const (
filename = "docs/README.md"
beginMarker = `<!-- BEGIN_TOC -->`
endMarker = `<!-- END_TOC -->`
)

func main() {
topics := docs.Topics()
groups := getGroups(topics)

rendered := renderTopics(topics, groups)
rendered = fmt.Sprintf("%s\n%s\n%s", beginMarker, rendered, endMarker)

content, err := os.ReadFile(filename)
if err != nil {
log.Fatalf("Failed to read %s: %v", filename, err)
}

regex := regexp.MustCompile(`(?s)` + beginMarker + `.+` + endMarker)
output := regex.ReplaceAllString(string(content), rendered)

os.WriteFile(filename, []byte(output), 0644)
}

func strSliceHas(haystack []string, needle string) bool {
for _, val := range haystack {
if val == needle {
return true
}
}

return false
}

func getGroups(topics []docs.Topic) []string {
// determine a sorted list of functions, with some groups
// hardcoded to be at the top, regardless of their name
prioritizedGroups := []string{
"General",
"Core Functions",
}

remainingGroups := []string{}

for _, topic := range topics {
if !strSliceHas(prioritizedGroups, topic.Group) {
if !strSliceHas(remainingGroups, topic.Group) {
remainingGroups = append(remainingGroups, topic.Group)
}
}
}

sort.Strings(remainingGroups)

return append(prioritizedGroups, remainingGroups...)
}

func renderTopics(topics []docs.Topic, groups []string) string {
var out strings.Builder

for _, group := range groups {
out.WriteString(fmt.Sprintf("## %s\n", group))
out.WriteString("\n")

topicNames := getTopicNames(topics, group)
for _, topicName := range topicNames {
topic := getTopic(topics, topicName)
linkTitle := topicName

if topic.IsFunction {
linkTitle = fmt.Sprintf("`%s`", linkTitle)
}

out.WriteString(fmt.Sprintf("* [%s](%s) – %s\n", linkTitle, topic.Filename, topic.Description))
}

out.WriteString("\n")
}

return strings.TrimSpace(out.String())
}

func htmlencode(s string) string {
return html.HTMLEscapeString(s)
}

func getTopicNames(topics []docs.Topic, group string) []string {
names := []string{}

for _, topic := range topics {
if topic.Group != group {
continue
}

primaryName := topic.CliNames[0]

if !strSliceHas(names, primaryName) {
names = append(names, primaryName)
}
}

sort.Strings(names)

return names
}

func getTopic(topics []docs.Topic, name string) docs.Topic {
for _, topic := range topics {
if topic.CliNames[0] == name {
return topic
}
}

panic("this should never happen")
}

0 comments on commit e25eb29

Please sign in to comment.