diff --git a/changelog/unreleased/automate-md-creation.md b/changelog/unreleased/automate-md-creation.md new file mode 100644 index 0000000000..9631a2d0cf --- /dev/null +++ b/changelog/unreleased/automate-md-creation.md @@ -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 diff --git a/docs/helpers/index.tmpl b/docs/helpers/index.tmpl new file mode 100644 index 0000000000..0ea33427be --- /dev/null +++ b/docs/helpers/index.tmpl @@ -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 }} diff --git a/docs/helpers/main.go b/docs/helpers/main.go index 86076042c0..2f150567fb 100644 --- a/docs/helpers/main.go +++ b/docs/helpers/main.go @@ -4,4 +4,5 @@ func main() { RenderTemplates() GetRogueEnvs() RenderGlobalVarsTemplate() + GenerateServiceIndexMarkdowns() } diff --git a/docs/helpers/markdowncreation.go b/docs/helpers/markdowncreation.go new file mode 100644 index 0000000000..33dbf5ad00 --- /dev/null +++ b/docs/helpers/markdowncreation.go @@ -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) +} diff --git a/ocis-pkg/markdown/markdown.go b/ocis-pkg/markdown/markdown.go new file mode 100644 index 0000000000..c95391ce7c --- /dev/null +++ b/ocis-pkg/markdown/markdown.go @@ -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, " "), + } +} diff --git a/ocis-pkg/markdown/markdown_suite_test.go b/ocis-pkg/markdown/markdown_suite_test.go new file mode 100644 index 0000000000..2c925074a9 --- /dev/null +++ b/ocis-pkg/markdown/markdown_suite_test.go @@ -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") +} diff --git a/ocis-pkg/markdown/markdown_test.go b/ocis-pkg/markdown/markdown_test.go new file mode 100644 index 0000000000..615bb34412 --- /dev/null +++ b/ocis-pkg/markdown/markdown_test.go @@ -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), + ) +}) diff --git a/services/notifications/README.md b/services/notifications/README.md index 439247e162..507d7afab3 100644 --- a/services/notifications/README.md +++ b/services/notifications/README.md @@ -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.