Skip to content
This repository has been archived by the owner on Mar 14, 2024. It is now read-only.

Generate CLI Documentation from Go code #294

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ generate: ## Generate Go code for apis and fakes
go generate ./...
go run sigs.k8s.io/controller-tools/cmd/controller-gen object paths=./pkg/apis/...
scripts/generateSchema.sh api/upstream-ca-validation.json > internal/nginx-meshctl/upstreamauthority/schema.go
go run -ldflags '-X main.genDocs=true' cmd/nginx-meshctl/main.go > /dev/null
$(MAKE) format

.PHONY: output-dir
Expand Down
154 changes: 154 additions & 0 deletions cmd/nginx-meshctl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,176 @@
package main

import (
"bufio"
"io"
"log"
"os"
"path/filepath"
"regexp"
"runtime"
"strings"

"github.com/nginxinc/nginx-service-mesh/internal/nginx-meshctl/commands"

"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)

var (
pkgName = "nginx-meshctl"
version = "0.0.0"
commit = "local"
genDocs = ""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than a variable built in, how about a flag that a user specifies, such as --verbose-usage or something along those lines?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do 👍

)

func main() {
cmd := commands.Setup(pkgName, version, commit)
cmd.SilenceUsage = true

if genDocs != "" {
if err := generateMarkdown(cmd); err != nil {
log.Fatal(err)
}
}

if cmd.Execute() != nil {
os.Exit(1)
}
}

func generateCompletion(cmd *cobra.Command, docsPath string) error {
cmd.CompletionOptions.HiddenDefaultCmd = false
cmd.InitDefaultCompletionCmd()
completionCmd, _, err := cmd.Find([]string{"completion"})
if err != nil {
return err
}
mdFileFullPath := filepath.Join(docsPath, "nginx-meshctl_completion.md")

mdFile, err := os.Create(mdFileFullPath)
if err != nil {
return err
}
defer mdFile.Close()

caser := cases.Title(language.English)

scanner := bufio.NewScanner(strings.NewReader(completionCmd.UsageString()))
if _, err = mdFile.WriteString("## " + caser.String(completionCmd.Name()) + "\n"); err != nil {
return err
}

for scanner.Scan() {
line := strings.ReplaceAll(scanner.Text(), "ke.agarwal", "<user>")
if strings.Contains(line, "Usage:") {
_, err = mdFile.WriteString("\n```\n")
} else if strings.Contains(line, "Available Commands:") {
_, err = mdFile.WriteString("```\n\n### Available Commands\n\n```\n")
} else if strings.Contains(line, "Global Flags:") {
_, err = mdFile.WriteString("```\n\n### Options\n\n```\n")
} else if strings.Contains(line, "for more information about") {
_, err = mdFile.WriteString("```\n")
} else if len(line) > 0 {
_, err = mdFile.WriteString(line + "\n")
}
if err != nil {
return err
}
}
return nil
}

func generateMarkdown(cmd *cobra.Command) error {
cmd.DisableAutoGenTag = true

// generates a markdown file for each CLI command
_, b, _, _ := runtime.Caller(0)
docsPath := filepath.Join(filepath.Dir(b), "../../docs/content/reference")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would rather have the CLI spit out all of this to stdout, and then the caller of the generator can pipe that output into our docs file. I don't want our CLI itself to write the file.

if err := doc.GenMarkdownTree(cmd, docsPath); err != nil {
return err
}

// combine all files in a buffer
buf := new(strings.Builder)
if err := generateCompletion(cmd, docsPath); err != nil {
log.Fatal(err)
}

docsFiles, err := os.ReadDir(docsPath)
if err != nil {
return err
}

for _, file := range docsFiles {
if file.IsDir() || !strings.HasPrefix(file.Name(), "nginx-meshctl") {
continue
}

mdFileFullPath := filepath.Join(docsPath, file.Name())

var mdFile *os.File
mdFile, err = os.Open(mdFileFullPath)
if err != nil {
return err
}
defer mdFile.Close()

_, err = io.Copy(buf, mdFile)
if err != nil {
return err
}
if _, err = buf.WriteString("\n"); err != nil {
return err
}

if err = os.Remove(mdFileFullPath); err != nil {
return err
}
}

finalName := filepath.Join(docsPath, "nginx-meshctl.md")
finalFile, err := os.Create(finalName)
if err != nil {
return err
}
defer finalFile.Close()

if _, err := finalFile.WriteString(`---
title: CLI Reference
description: "Man page and instructions for using the NGINX Service Mesh CLI"
draft: false
weight: 300
toc: true
categories: ["reference"]
docs: "DOCS-704"
---
`); err != nil {
return err
}

alt := 1
re := regexp.MustCompile("## nginx-meshctl ([a-z]+)")
caser := cases.Title(language.English)
scanner := bufio.NewScanner(strings.NewReader(buf.String()))
for scanner.Scan() {
line := strings.ReplaceAll(scanner.Text(), "ke.agarwal", "<user>")
if list := re.FindStringSubmatch(line); list != nil {
line = "## " + caser.String(list[len(list)-1]) + "\n"
} else if strings.Contains(line, "```") {
if alt > 0 {
line = strings.ReplaceAll(line, "```", "```txt")
}
alt *= -1
}
// don't add cobra-generated footer
if !strings.Contains(line, "SEE ALSO") && !strings.Contains(line, "* [nginx-meshctl") {
if _, err := finalFile.WriteString(line + "\n"); err != nil {
return err
}
}
}

return nil
}
Loading