Merge pull request #5903 from kobergj/AutomateMDCreation

Automate Creation of `_index.md` Files
This commit is contained in:
kobergj
2023-03-24 13:00:10 +01:00
committed by GitHub
8 changed files with 293 additions and 1 deletions

View File

@@ -0,0 +1,5 @@
Enhancement: Automate md creation
Automatically create `_index.md` files from the services `README.md`
https://github.com/owncloud/ocis/pull/5901

17
docs/helpers/index.tmpl Normal file
View File

@@ -0,0 +1,17 @@
---
title: {{ .ServiceName }}
date: {{ .CreationTime }}
weight: 20
geekdocRepo: https://github.com/owncloud/ocis
geekdocEditPath: edit/master/docs/services/{{ .service }}
geekdocFilePath: _index.md
geekdocCollapseSection: true
---
## Abstract
{{ .Abstract }}
## Table of Contents
{{ .TocTree }}
{{ .Content }}

View File

@@ -4,4 +4,5 @@ func main() {
RenderTemplates()
GetRogueEnvs()
RenderGlobalVarsTemplate()
GenerateServiceIndexMarkdowns()
}

View File

@@ -0,0 +1,71 @@
package main
import (
"bytes"
"errors"
"fmt"
"log"
"os"
"path/filepath"
"text/template"
"time"
"github.com/owncloud/ocis/v2/ocis-pkg/markdown"
)
var _configMarkdown = `{{< include file="services/_includes/%s-config-example.yaml" language="yaml" >}}
{{< include file="services/_includes/%s_configvars.md" >}}
`
// GenerateServiceIndexMarkdowns generates the _index.md files for the dev docu
func GenerateServiceIndexMarkdowns() {
paths, err := filepath.Glob("../../services/*/README.md")
if err != nil {
log.Fatal(err)
}
for _, p := range paths {
service := filepath.Base(filepath.Dir(p))
if err := generateMarkdown(p, service); err != nil {
fmt.Printf("error generating markdown for %s: %s\n", service, err)
}
}
}
func generateMarkdown(filepath string, servicename string) error {
f, err := os.ReadFile(filepath)
if err != nil {
return err
}
md := markdown.NewMD(f)
if len(md.Headings) == 0 || md.Headings[0].Level != 1 {
return errors.New("readme has invalid format")
}
// we don't need the main title, we add in our template
head := md.Headings[0]
md.Headings = md.Headings[1:]
md.Headings = append(md.Headings, markdown.Heading{
Level: 2,
Header: "Example Yaml Config",
Content: fmt.Sprintf(_configMarkdown, servicename, servicename),
})
tpl := template.Must(template.ParseFiles("index.tmpl"))
b := bytes.NewBuffer(nil)
if err := tpl.Execute(b, map[string]interface{}{
"ServiceName": head.Header,
"CreationTime": time.Now().Format(time.RFC3339Nano),
"service": servicename,
"Abstract": head.Content,
"TocTree": md.TocString(),
"Content": md.String(),
}); err != nil {
return err
}
targetFile := fmt.Sprintf("../../docs/services/%s/_index.md", servicename)
return os.WriteFile(targetFile, b.Bytes(), os.ModePerm)
}

View File

@@ -0,0 +1,137 @@
// Package markdown allows reading and editing Markdown files
package markdown
import (
"bytes"
"fmt"
"io"
"strings"
)
// Heading represents a markdown Heading
type Heading struct {
Level int // the level of the heading. 1 means it's the H1
Content string // the text of the heading
Header string // the heading itself
}
// MD represents a markdown file
type MD struct {
Headings []Heading
}
// Bytes returns the markdown as []bytes, ignoring errors
func (md MD) Bytes() []byte {
var b bytes.Buffer
_, _ = md.WriteContent(&b)
return b.Bytes()
}
// String returns the markdown as string, ignoring errors
func (md MD) String() string {
var b strings.Builder
_, _ = md.WriteContent(&b)
return b.String()
}
// TocBytes returns the table of contents as []byte, ignoring errors
func (md MD) TocBytes() []byte {
var b bytes.Buffer
_, _ = md.WriteToc(&b)
return b.Bytes()
}
// TocString returns the table of contents as string, ignoring errors
func (md MD) TocString() string {
var b strings.Builder
_, _ = md.WriteToc(&b)
return b.String()
}
// WriteContent writes the MDs content to the given writer
func (md MD) WriteContent(w io.Writer) (int64, error) {
max, written := len(md.Headings), int64(0)
write := func(s string) error {
n, err := w.Write([]byte(s))
written += int64(n)
return err
}
for i, h := range md.Headings {
if err := write(strings.Repeat("#", h.Level) + " " + h.Header + "\n"); err != nil {
return written, err
}
if err := write("\n"); err != nil {
return written, err
}
if len(h.Content) > 0 {
if err := write(h.Content); err != nil {
return written, err
}
if i < max-1 {
if err := write("\n"); err != nil {
return written, err
}
}
}
}
return written, nil
}
// WriteToc writes the table of contents to the given writer
func (md MD) WriteToc(w io.Writer) (int64, error) {
var written int64
for _, h := range md.Headings {
if h.Level == 1 {
// main title not in toc
continue
}
link := fmt.Sprintf("#%s", strings.ToLower(strings.Replace(h.Header, " ", "-", -1)))
s := fmt.Sprintf("%s* [%s](%s)\n", strings.Repeat(" ", h.Level-2), h.Header, link)
n, err := w.Write([]byte(s))
if err != nil {
return written, err
}
written += int64(n)
}
return written, nil
}
// NewMD parses a new Markdown
func NewMD(b []byte) MD {
var (
md MD
heading Heading
content strings.Builder
)
sendHeading := func() {
if heading.Header != "" {
heading.Content = content.String()
md.Headings = append(md.Headings, heading)
content = strings.Builder{}
}
}
parts := strings.Split(string(b), "\n")
for _, p := range parts {
if p == "" {
continue
}
if p[:1] == "#" { // this is a header
sendHeading()
heading = headingFromString(p)
} else {
// readd lost "\n"
_, _ = content.WriteString(p + "\n")
}
}
sendHeading()
return md
}
func headingFromString(s string) Heading {
i := strings.LastIndex(s, "#")
levs, con := s[:i+1], s[i+1:]
return Heading{
Level: len(levs),
Header: strings.TrimPrefix(con, " "),
}
}

View File

@@ -0,0 +1,13 @@
package markdown
import (
"testing"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
func TestSearch(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Markdown Suite")
}

View File

@@ -0,0 +1,48 @@
package markdown
import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var (
SmallMarkdown = `# Title
some abstract description
## SubTitle 1
subtitle one description
## SubTitle 2
subtitle two description
### Subpoint to SubTitle 2
description to subpoint
`
SmallMD = MD{
Headings: []Heading{
{Level: 1, Header: "Title", Content: "some abstract description\n"},
{Level: 2, Header: "SubTitle 1", Content: "subtitle one description\n"},
{Level: 2, Header: "SubTitle 2", Content: "subtitle two description\n"},
{Level: 3, Header: "Subpoint to SubTitle 2", Content: "description to subpoint\n"},
},
}
)
var _ = Describe("TestMarkdown", func() {
DescribeTable("Conversion works both ways",
func(mdfile string, expectedMD MD) {
md := NewMD([]byte(mdfile))
Expect(len(md.Headings)).To(Equal(len(expectedMD.Headings)))
for i, h := range md.Headings {
Expect(h).To(Equal(expectedMD.Headings[i]))
}
Expect(md.String()).To(Equal(mdfile))
},
Entry("converts a small markdown", SmallMarkdown, SmallMD),
)
})

View File

@@ -1,4 +1,4 @@
#### Notification service
# Notification service
The notification service is responsible for sending emails to users informing them about events that happened. To do this it hooks into the event system and listens for certain events that the users need to be informed about.