-
Notifications
You must be signed in to change notification settings - Fork 65
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
created a tool to generate and install man pages
Signed-off-by: Teodor Adrian Miron <[email protected]>
- Loading branch information
1 parent
f43135c
commit 7bde584
Showing
4 changed files
with
335 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,328 @@ | ||
// SPDX-License-Identifier: Apache-2.0 | ||
// Copyright (c) 2023, Unikraft GmbH, spf13/cobra, The Cobra Authors and KraftKit Authors. | ||
// Licensed under the Apache-2.0 License (the "License"). | ||
// You may not use this file except in compliance with the License. | ||
|
||
package main | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"io" | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
"sort" | ||
"strconv" | ||
"strings" | ||
"time" | ||
|
||
kraft "kraftkit.sh/internal/cli/kraft" | ||
|
||
"github.com/cpuguy83/go-md2man/v2/md2man" | ||
"github.com/spf13/cobra" | ||
"github.com/spf13/pflag" | ||
) | ||
|
||
const ( | ||
MAN_DIR = "../../docs/man" | ||
MAN_INSTALL_DIR = "/usr/local/share/man/man1" | ||
) | ||
|
||
func main() { | ||
if len(os.Args[1:]) == 0 { | ||
fmt.Printf("usage: %s [--man | --install] \n", os.Args[0]) | ||
os.Exit(1) | ||
} | ||
|
||
arg := os.Args[1] | ||
|
||
switch arg { | ||
case "--man", "-m": | ||
if err := os.MkdirAll(MAN_DIR, 0o775); err != nil { | ||
fmt.Println("could not create MAN_DIR:", err) | ||
os.Exit(1) | ||
} | ||
|
||
cmd := kraft.NewCmd() | ||
header := &GenManHeader{ | ||
Title: "KraftKit CLI", | ||
Source: "KraftKit Documentation", | ||
Manual: "KraftKit Manual", | ||
} | ||
|
||
fmt.Println("Generating man pages in directory kraftkit/docs/man") | ||
if err := GenManTree(cmd, header, MAN_DIR); err != nil { | ||
fmt.Println("error generating man pages:", err) | ||
os.Exit(1) | ||
} | ||
|
||
fmt.Println("Man pages generated successfully in kraftkit/docs/man") | ||
|
||
case "--install", "-i": | ||
srcDir := MAN_DIR | ||
destDir := MAN_INSTALL_DIR | ||
if err := InstallManPages(srcDir, destDir); err != nil { | ||
fmt.Printf("Error installing man pages: %v\n", err) | ||
os.Exit(1) | ||
} | ||
fmt.Println("Man pages installed successfully!") | ||
default: | ||
fmt.Printf("usage: %s [--man | --install] \n", os.Args[0]) | ||
os.Exit(1) | ||
} | ||
} | ||
|
||
func GenManTree(cmd *cobra.Command, header *GenManHeader, dir string) error { | ||
return GenManTreeFromOpts(cmd, GenManTreeOptions{ | ||
Header: header, | ||
Path: dir, | ||
CommandSeparator: "-", | ||
}) | ||
} | ||
|
||
func GenManTreeFromOpts(cmd *cobra.Command, opts GenManTreeOptions) error { | ||
header := opts.Header | ||
if header == nil { | ||
header = &GenManHeader{} | ||
} | ||
for _, c := range cmd.Commands() { | ||
if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() { | ||
continue | ||
} | ||
if err := GenManTreeFromOpts(c, opts); err != nil { | ||
return err | ||
} | ||
} | ||
section := "1" | ||
if header.Section != "" { | ||
section = header.Section | ||
} | ||
|
||
separator := "_" | ||
if opts.CommandSeparator != "" { | ||
separator = opts.CommandSeparator | ||
} | ||
basename := strings.ReplaceAll(cmd.CommandPath(), " ", separator) | ||
filename := filepath.Join(opts.Path, basename+"."+section) | ||
f, err := os.Create(filename) | ||
if err != nil { | ||
return err | ||
} | ||
defer f.Close() | ||
|
||
headerCopy := *header | ||
return GenMan(cmd, &headerCopy, f) | ||
} | ||
|
||
type GenManTreeOptions struct { | ||
Header *GenManHeader | ||
Path string | ||
CommandSeparator string | ||
} | ||
|
||
type GenManHeader struct { | ||
Title string | ||
Section string | ||
Date *time.Time | ||
date string | ||
Source string | ||
Manual string | ||
} | ||
|
||
func GenMan(cmd *cobra.Command, header *GenManHeader, w io.Writer) error { | ||
if header == nil { | ||
header = &GenManHeader{} | ||
} | ||
if err := fillHeader(header, cmd.CommandPath(), cmd.DisableAutoGenTag); err != nil { | ||
return err | ||
} | ||
|
||
b := genMan(cmd, header) | ||
_, err := w.Write(md2man.Render(b)) | ||
return err | ||
} | ||
|
||
func fillHeader(header *GenManHeader, name string, disableAutoGen bool) error { | ||
if header.Title == "" { | ||
header.Title = strings.ToUpper(strings.ReplaceAll(name, " ", "\\-")) | ||
} | ||
if header.Section == "" { | ||
header.Section = "1" | ||
} | ||
if header.Date == nil { | ||
now := time.Now() | ||
if epoch := os.Getenv("SOURCE_DATE_EPOCH"); epoch != "" { | ||
unixEpoch, err := strconv.ParseInt(epoch, 10, 64) | ||
if err != nil { | ||
return fmt.Errorf("invalid SOURCE_DATE_EPOCH: %v", err) | ||
} | ||
now = time.Unix(unixEpoch, 0) | ||
} | ||
header.Date = &now | ||
} | ||
header.date = header.Date.Format("Jan 2006") | ||
if header.Source == "" && !disableAutoGen { | ||
header.Source = "Auto generated by KraftKit" | ||
} | ||
return nil | ||
} | ||
|
||
func manPreamble(buf io.StringWriter, header *GenManHeader, cmd *cobra.Command, dashedName string) { | ||
description := cmd.Long | ||
if len(description) == 0 { | ||
description = cmd.Short | ||
} | ||
|
||
cobra.WriteStringAndCheck(buf, fmt.Sprintf(`%% "%s" "%s" "%s" "%s" "%s" | ||
# NAME | ||
`, header.Title, header.Section, header.date, header.Source, header.Manual)) | ||
cobra.WriteStringAndCheck(buf, fmt.Sprintf("%s \\- %s\n\n", dashedName, cmd.Short)) | ||
cobra.WriteStringAndCheck(buf, "# SYNOPSIS\n") | ||
cobra.WriteStringAndCheck(buf, fmt.Sprintf("**%s**\n\n", cmd.UseLine())) | ||
cobra.WriteStringAndCheck(buf, "# DESCRIPTION\n") | ||
cobra.WriteStringAndCheck(buf, description+"\n\n") | ||
} | ||
|
||
func manPrintFlags(buf io.StringWriter, flags *pflag.FlagSet) { | ||
flags.VisitAll(func(flag *pflag.Flag) { | ||
if len(flag.Deprecated) > 0 || flag.Hidden { | ||
return | ||
} | ||
format := "" | ||
if len(flag.Shorthand) > 0 && len(flag.ShorthandDeprecated) == 0 { | ||
format = fmt.Sprintf("**-%s**, **--%s**", flag.Shorthand, flag.Name) | ||
} else { | ||
format = fmt.Sprintf("**--%s**", flag.Name) | ||
} | ||
if len(flag.NoOptDefVal) > 0 { | ||
format += "[" | ||
} | ||
if flag.Value.Type() == "string" { | ||
format += "=%q" | ||
} else { | ||
format += "=%s" | ||
} | ||
if len(flag.NoOptDefVal) > 0 { | ||
format += "]" | ||
} | ||
format += "\n\t%s\n\n" | ||
cobra.WriteStringAndCheck(buf, fmt.Sprintf(format, flag.DefValue, flag.Usage)) | ||
}) | ||
} | ||
|
||
func manPrintOptions(buf io.StringWriter, command *cobra.Command) { | ||
flags := command.NonInheritedFlags() | ||
if flags.HasAvailableFlags() { | ||
cobra.WriteStringAndCheck(buf, "# OPTIONS\n") | ||
manPrintFlags(buf, flags) | ||
cobra.WriteStringAndCheck(buf, "\n") | ||
} | ||
flags = command.InheritedFlags() | ||
if flags.HasAvailableFlags() { | ||
cobra.WriteStringAndCheck(buf, "# OPTIONS INHERITED FROM PARENT COMMANDS\n") | ||
manPrintFlags(buf, flags) | ||
cobra.WriteStringAndCheck(buf, "\n") | ||
} | ||
} | ||
|
||
func genMan(cmd *cobra.Command, header *GenManHeader) []byte { | ||
cmd.InitDefaultHelpCmd() | ||
cmd.InitDefaultHelpFlag() | ||
|
||
dashCommandName := strings.ReplaceAll(cmd.CommandPath(), " ", "-") | ||
|
||
buf := new(bytes.Buffer) | ||
|
||
manPreamble(buf, header, cmd, dashCommandName) | ||
manPrintOptions(buf, cmd) | ||
if len(cmd.Example) > 0 { | ||
buf.WriteString("# EXAMPLE\n") | ||
buf.WriteString(fmt.Sprintf("\n%s\n\n", cmd.Example)) | ||
} | ||
if hasSeeAlso(cmd) { | ||
buf.WriteString("# SEE ALSO\n") | ||
seealsos := make([]string, 0) | ||
if cmd.HasParent() { | ||
parentPath := cmd.Parent().CommandPath() | ||
dashParentPath := strings.ReplaceAll(parentPath, " ", "-") | ||
seealso := fmt.Sprintf("**%s(%s)**", dashParentPath, header.Section) | ||
seealsos = append(seealsos, seealso) | ||
cmd.VisitParents(func(c *cobra.Command) { | ||
if c.DisableAutoGenTag { | ||
cmd.DisableAutoGenTag = c.DisableAutoGenTag | ||
} | ||
}) | ||
} | ||
children := cmd.Commands() | ||
sort.Sort(byName(children)) | ||
for _, c := range children { | ||
if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() { | ||
continue | ||
} | ||
seealso := fmt.Sprintf("**%s-%s(%s)**", dashCommandName, c.Name(), header.Section) | ||
seealsos = append(seealsos, seealso) | ||
} | ||
buf.WriteString(strings.Join(seealsos, ", ") + "\n") | ||
} | ||
if !cmd.DisableAutoGenTag { | ||
buf.WriteString(fmt.Sprintf("# HISTORY\n%s Auto generated by KraftKit\n", header.Date.Format("2-Jan-2006"))) | ||
} | ||
return buf.Bytes() | ||
} | ||
|
||
func hasSeeAlso(cmd *cobra.Command) bool { | ||
if cmd.HasParent() { | ||
return true | ||
} | ||
for _, c := range cmd.Commands() { | ||
if !c.IsAvailableCommand() || c.IsAdditionalHelpTopicCommand() { | ||
continue | ||
} | ||
return true | ||
} | ||
return false | ||
} | ||
|
||
func InstallManPages(srcDir string, destDir string) error { | ||
if err := os.MkdirAll(destDir, 0o755); err != nil { | ||
return fmt.Errorf("failed to create destination directory: %w", err) | ||
} | ||
|
||
files, err := os.ReadDir(srcDir) | ||
if err != nil { | ||
return fmt.Errorf("failed to read source directory: %w", err) | ||
} | ||
|
||
for _, file := range files { | ||
if filepath.Ext(file.Name()) != ".1" { | ||
continue | ||
} | ||
|
||
srcPath := filepath.Join(srcDir, file.Name()) | ||
destPath := filepath.Join(destDir, file.Name()) | ||
|
||
fmt.Printf("Installing %s -> %s\n", srcPath, destPath) | ||
input, err := os.ReadFile(srcPath) | ||
if err != nil { | ||
return fmt.Errorf("failed to read file %s: %w", srcPath, err) | ||
} | ||
if err := os.WriteFile(destPath, input, 0o644); err != nil { | ||
return fmt.Errorf("failed to write file %s: %w", destPath, err) | ||
} | ||
} | ||
|
||
fmt.Println("Updating man database...") | ||
cmd := exec.Command("mandb") | ||
if err := cmd.Run(); err != nil { | ||
return fmt.Errorf("failed to update man database: %w", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
type byName []*cobra.Command | ||
|
||
func (s byName) Len() int { return len(s) } | ||
func (s byName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } | ||
func (s byName) Less(i, j int) bool { return s[i].Name() < s[j].Name() } |