Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DOCS: TOC builder (for functions and providers) #2197

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Next Next commit
Code to auto build TOC entries based on folder structure
systemcrash committed Apr 3, 2023
commit 757a1fab37a1dcf84808a790d0844f7a215ae8dd
178 changes: 178 additions & 0 deletions build/generate/docuTOC.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package main

import (
"fmt"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"os"
"path/filepath"
"sort"
"strings"
)

func generateDocuTOC(folderPath string, targetFile string, onlyThisPath string, startMarker string, endMarker string) error {
if folderPath == "" {
return fmt.Errorf("empty docutoc path")
}

var exclusivePath string
if onlyThisPath != "" {
// if onlyThisPath is provided, build a list of files exclusively in this (sub)folder
exclusivePath = string(filepath.Separator) + onlyThisPath
}
// Find all the markdown files in the specified folder and its subfolders.
markdownFiles, err := findMarkdownFiles(folderPath + exclusivePath)
if err != nil {
return err
}

//First sort by folders, then by filename.
sort.SliceStable(markdownFiles, func(i, j int) bool {
if filepath.Dir(markdownFiles[i]) == filepath.Dir(markdownFiles[j]) {
return strings.ToLower(markdownFiles[i]) < strings.ToLower(markdownFiles[j])
} else {
return filepath.Dir(strings.ToLower(markdownFiles[i])) < filepath.Dir(strings.ToLower(markdownFiles[j]))
}
})

// Create the table of contents.
toc := generateTableOfContents(folderPath, markdownFiles)

err = replaceTextBetweenMarkers(filepath.Join(folderPath, targetFile), startMarker, endMarker, toc)
if err != nil {
return err
}

return nil
}

// func stringInSlice(a string, list []string) bool {
// for _, b := range list {
// if b == a {
// return true
// }
// }
// return false
// }

func generateTableOfContents(folderPath string, markdownFiles []string) string {
var toc strings.Builder
currentFolder := ""

// skip over these root entries (dont print these "#" headings)
rootFolderExceptions := []string{
"documentation",
}

// dont print these folder names as bullets (which lack a link)
folderExceptions := []string{
"providers",
}
// dont print these file names as bullets
fileExceptions := []string{
"index.md",
"summary.md",
"ignore-me.md",
}

caser := cases.Title(language.Und, cases.NoLower)

for _, file := range markdownFiles {
//depthCount is folder depth for toc indentation, minus one for docu folder
depthCount := strings.Count(file, string(filepath.Separator)) - 1
filename := filepath.Base(file)

fileFolder := filepath.Dir(file)
if fileFolder != currentFolder {
// we are in a new folder

// hop over these entries altogether
if stringInSlice(strings.ToLower(fileFolder), rootFolderExceptions) {
continue
}
currentFolder = fileFolder
folderName := filepath.Base(currentFolder)

// if we're in an "exception" folder, deeper than a heading "#", skip printing it
// this has the effect of putting subentries under an entry that is already a link,
// without printing an entry to represent the folder name that is not a link
// e.g. provider md files are all links, under the provider.md file which is a link
if stringInSlice(strings.ToLower(folderName), folderExceptions) && depthCount > 1 {
continue
} else {
if depthCount > 1 {
// if we're deeper in heirarchy, print an indented bullet "*" to add to bullet heirarchy
toc.WriteString(strings.Repeat(" ", depthCount-1) + "* ")
} else {
// If we're in folder root, just print an unindented heading "#"
toc.WriteString("\n## ")
}
// Captalize folder names, replace underscores with spaces for # headings
toc.WriteString(strings.TrimSpace(caser.String(strings.ReplaceAll(folderName, "_", " "))) + "\n")
}
}
//if the file is an exception listed above, skip it.
if stringInSlice(strings.ToLower(filename), fileExceptions) {
continue
}

// naming exceptions - function names shall retain "_"
displayfilename := strings.TrimSuffix(filename, filepath.Ext(filename))
if !strings.Contains(file, "functions") {
displayfilename = strings.TrimSpace(caser.String(strings.ReplaceAll(displayfilename, "_", " ")))
}

// print the filename as a bullet, and as a [link](hyperlink)
toc.WriteString(strings.Repeat(" ", depthCount))
toc.WriteString("* [" + displayfilename + "](" + filepath.Join(".", strings.ReplaceAll(file, folderPath, "")) + ")\n")
}
return toc.String()
}

// replaceTextBetweenMarkers inserts the generated table of contents between the two markers in the specified markdown file.
func replaceTextBetweenMarkers(targetFile, startMarker, endMarker, newcontent string) error {
// Read the contents of the markdown file into memory.
input, err := os.ReadFile(targetFile)
if err != nil {
return err
}

// Find the starting and ending positions of the table of contents markers.
startPos := strings.Index(string(input), startMarker)
if startPos == -1 {
return fmt.Errorf("could not find start marker %q in file %q", startMarker, targetFile)
}
endPos := strings.Index(string(input), endMarker)
if endPos == -1 {
return fmt.Errorf("could not find end marker %q in file %q", endMarker, targetFile)
}

// Construct the new contents of the markdown file with the updated table of contents.
output := string(input[:startPos+len(startMarker)]) + newcontent + string(input[endPos:])

// Write the updated contents to the markdown file.
err = os.WriteFile(targetFile, []byte(output), 0644)
if err != nil {
return err
}

return nil
}

// findMarkdownFiles returns a list of all the markdown files in the specified folder and its subfolders.
func findMarkdownFiles(folderPath string) ([]string, error) {
markdownFiles := make([]string, 0)
err := filepath.Walk(folderPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !info.IsDir() && filepath.Ext(path) == ".md" {
markdownFiles = append(markdownFiles, path)
}
return nil
})
if err != nil {
return nil, err
}
return markdownFiles, err
}
24 changes: 12 additions & 12 deletions build/generate/featureMatrix.go
Original file line number Diff line number Diff line change
@@ -76,20 +76,20 @@ func matrixData() *FeatureMatrix {
OfficialSupport = "Official Support" // vs. community supported
ProviderDNSProvider = "DNS Provider"
ProviderRegistrar = "Registrar"
DomainModifierAlias = "[`ALIAS`](functions/domain/ALIAS.md)"
DomainModifierCaa = "[`CAA`](functions/domain/CAA.md)"
DomainModifierDnssec = "[`AUTODNSSEC`](functions/domain/AUTODNSSEC_ON.md)"
DomainModifierLoc = "[`LOC`](functions/domain/LOC.md)"
DomainModifierNaptr = "[`NAPTR`](functions/domain/NAPTR.md)"
DomainModifierPtr = "[`PTR`](functions/domain/PTR.md)"
DomainModifierSoa = "[`SOA`](functions/domain/SOA.md)"
DomainModifierSrv = "[`SRV`](functions/domain/SRV.md)"
DomainModifierSshfp = "[`SSHFP`](functions/domain/SSHFP.md)"
DomainModifierTlsa = "[`TLSA`](functions/domain/TLSA.md)"
DomainModifierDs = "[`DS`](functions/domain/DS.md)"
DomainModifierAlias = "[`ALIAS`](../language_reference/domain_modifier_functions/ALIAS.md)"
DomainModifierCaa = "[`CAA`](../language_reference/domain_modifier_functions/CAA.md)"
DomainModifierDnssec = "[`AUTODNSSEC`](../language_reference/domain_modifier_functions/AUTODNSSEC_ON.md)"
DomainModifierLoc = "[`LOC`](../language_reference/domain_modifier_functions/LOC.md)"
DomainModifierNaptr = "[`NAPTR`](../language_reference/domain_modifier_functions/NAPTR.md)"
DomainModifierPtr = "[`PTR`](../language_reference/domain_modifier_functions/PTR.md)"
DomainModifierSoa = "[`SOA`](../language_reference/domain_modifier_functions/SOA.md)"
DomainModifierSrv = "[`SRV`](../language_reference/domain_modifier_functions/SRV.md)"
DomainModifierSshfp = "[`SSHFP`](../language_reference/domain_modifier_functions/SSHFP.md)"
DomainModifierTlsa = "[`TLSA`](../language_reference/domain_modifier_functions/TLSA.md)"
DomainModifierDs = "[`DS`](../language_reference/domain_modifier_functions/DS.md)"
DualHost = "dual host"
CreateDomains = "create-domains"
NoPurge = "[`NO_PURGE`](functions/domain/NO_PURGE.md)"
NoPurge = "[`NO_PURGE`](../language_reference/domain_modifier_functions/NO_PURGE.md)"
GetZones = "get-zones"
)

37 changes: 32 additions & 5 deletions build/generate/functionTypes.go
Original file line number Diff line number Diff line change
@@ -49,20 +49,43 @@ func parseFrontMatter(content string) (map[string]interface{}, string, error) {
}

var returnTypes = map[string]string{
"domain": "DomainModifier",
"global": "void",
"record": "RecordModifier",
"domain_modifier_functions": "DomainModifier",
"top_level_functions": "void",
"record_modifier_functions": "RecordModifier",
}

func stringInSlice(a string, list []string) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}

func generateFunctionTypes() (string, error) {
funcs := []Function{}
folderExceptions := []string{
".DS_Store",
"js.md",
}
fileExceptions := []string{
".DS_Store",
"JavaScript_DSL.md",
}

srcRoot := join("documentation", "functions")
srcRoot := join("documentation", "language_reference")
types, err := os.ReadDir(srcRoot)
if err != nil {
return "", err
}
for _, t := range types {
if stringInSlice(t.Name(), folderExceptions) {
continue
}
if stringInSlice(t.Name(), fileExceptions) {
continue
}
if !t.IsDir() {
return "", errors.New("not a directory: " + join(srcRoot, t.Name()))
}
@@ -73,9 +96,13 @@ func generateFunctionTypes() (string, error) {
}

for _, f := range funcNames {
if stringInSlice(f.Name(), fileExceptions) {
continue
}
fPath := join(tPath, f.Name())
if f.IsDir() {
return "", errors.New("not a file: " + fPath)
// return "", errors.New("not a file: " + fPath)
continue
}
// println("Processing", fPath)
content, err := os.ReadFile(fPath)
13 changes: 13 additions & 0 deletions build/generate/generate.go
Original file line number Diff line number Diff line change
@@ -2,6 +2,13 @@ package main

import "log"

const (
langRefStartMarker = "<!-- LANG_REF start -->\n"
langRefEndMarker = "<!-- LANG_REF end -->"
providerStartMarker = "<!-- PROVIDER start -->\n"
providerEndMarker = "<!-- PROVIDER end -->"
)

func main() {
if err := generateFeatureMatrix(); err != nil {
log.Fatal(err)
@@ -13,4 +20,10 @@ func main() {
if err := generateDTSFile(funcs); err != nil {
log.Fatal(err)
}
if err := generateDocuTOC("documentation", "SUMMARY.md", "language_reference", langRefStartMarker, langRefEndMarker); err != nil {
log.Print(err)
}
if err := generateDocuTOC("documentation", "SUMMARY.md", "service_providers", providerStartMarker, providerEndMarker); err != nil {
log.Print(err)
}
}
7 changes: 4 additions & 3 deletions documentation/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@
* [Examples](examples.md)
* [Migrating zones to DNSControl](migrating.md)
* [TypeScript autocomplete and type checking](typescript.md)

<!-- LANG_REF start -->
## Language Reference

* [JavaScript DSL](js.md)
@@ -87,8 +87,9 @@
* Service Provider specific
* Amazon Route 53
* [R53_ZONE](functions/record/R53_ZONE.md)
<!-- LANG_REF end -->
* [Why CNAME/MX/NS targets require a "dot"](why-the-dot.md)

<!-- PROVIDER start -->
## Service Providers

* [Providers](providers.md)
@@ -135,7 +136,7 @@
* [SoftLayer DNS](providers/softlayer.md)
* [TransIP](providers/transip.md)
* [Vultr](providers/vultr.md)

<!-- PROVIDER end -->
## Commands

* [creds.json](creds-json.md)
2 changes: 1 addition & 1 deletion documentation/getting-started.md
Original file line number Diff line number Diff line change
@@ -99,7 +99,7 @@ D('example.com', REG_NONE, DnsProvider(DNS_BIND),
```
{% endcode %}

Modify this file to match your particular providers and domains. See [the DNSConfig docs](js.md) and [the provider docs](providers.md) for more details.
Modify this file to match your particular providers and domains. See [the DNSConfig docs](language_reference/JavaScript_DSL.md) and [the provider docs](providers.md) for more details.

Create a file called `creds.json` for storing provider configurations (API tokens and other account information).
For example, to use both name.com and Cloudflare, you would have:
2 changes: 1 addition & 1 deletion documentation/index.md
Original file line number Diff line number Diff line change
@@ -4,7 +4,7 @@

# Try It

Want to jump right in? Follow our [quick start tutorial](getting-started.md) on a new domain or [migrate](migrating.md) an existing one. Read the [language spec](js.md) for more info.
Want to jump right in? Follow our [quick start tutorial](getting-started.md) on a new domain or [migrate](migrating.md) an existing one. Read the [language spec](02_language_reference/JavaScript_DSL.md) for more info.

# Use It

File renamed without changes.
Original file line number Diff line number Diff line change
@@ -12,7 +12,7 @@ parameter_types:

A adds an A record To a domain. The name should be the relative label for the record. Use `@` for the domain apex.

The address should be an ip address, either a string, or a numeric value obtained via [IP](../global/IP.md).
The address should be an ip address, either a string, or a numeric value obtained via [IP](../top_level_functions/IP.md).

Modifiers can be any number of [record modifiers](https://docs.dnscontrol.org/language-reference/record-modifiers) or JSON objects, which will be merged into the record's metadata.

File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -38,4 +38,4 @@ D("example.com", REGISTRAR, DnsProvider("GCLOUD"),
```
{% endcode %}

DNSControl contains a [`CAA_BUILDER`](../record/CAA_BUILDER.md) which can be used to simply create `CAA()` records for your domains. Instead of creating each CAA record individually, you can simply configure your report mail address, the authorized certificate authorities and the builder cares about the rest.
DNSControl contains a [`CAA_BUILDER`](../record_modifier_functions/CAA_BUILDER.md) which can be used to simply create `CAA()` records for your domains. Instead of creating each CAA record individually, you can simply configure your report mail address, the authorized certificate authorities and the builder cares about the rest.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@ parameter_types:
---

DnsProvider indicates that the specified provider should be used to manage
records for this domain. The name must match the name used with [NewDnsProvider](../global/NewDnsProvider.md).
records for this domain. The name must match the name used with [NewDnsProvider](../top_level_functions/NewDnsProvider.md).

The nsCount parameter determines how the nameservers will be managed from this provider.

File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -86,10 +86,10 @@ the LOC record type will supply defaults where values were absent on DNS import.
One must supply the `LOC()` js helper all parameters. If that seems like too
much work, see also helper functions:

* [`LOC_BUILDER_DD({})`](../record/LOC_BUILDER_DD.md) - build a `LOC` by supplying only **d**ecimal **d**egrees.
* [`LOC_BUILDER_DMS_STR({})`](../record/LOC_BUILDER_DMS_STR.md) - accepts DMS 33°51′31″S 151°12′51″E
* [`LOC_BUILDER_DMM_STR({})`](../record/LOC_BUILDER_DMM_STR.md) - accepts DMM 25.24°S 153.15°E
* [`LOC_BUILDER_STR({})`](../record/LOC_BUILDER_STR.md) - tries the cooordinate string in all `LOC_BUILDER_DM*_STR()` functions until one works
* [`LOC_BUILDER_DD({})`](../record_modifier_functions/LOC_BUILDER_DD.md) - build a `LOC` by supplying only **d**ecimal **d**egrees.
* [`LOC_BUILDER_DMS_STR({})`](../record_modifier_functions/LOC_BUILDER_DMS_STR.md) - accepts DMS 33°51′31″S 151°12′51″E
* [`LOC_BUILDER_DMM_STR({})`](../record_modifier_functions/LOC_BUILDER_DMM_STR.md) - accepts DMM 25.24°S 153.15°E
* [`LOC_BUILDER_STR({})`](../record_modifier_functions/LOC_BUILDER_STR.md) - tries the cooordinate string in all `LOC_BUILDER_DM*_STR()` functions until one works

## Format ##

File renamed without changes.
Loading