From 67db2cf17ab64095debd525e1977689b8e163182 Mon Sep 17 00:00:00 2001 From: Peymaneh Nejad Date: Wed, 25 Aug 2021 00:36:55 +0200 Subject: [PATCH] Add module for generating manual pages --- cmd/commandfuncs.go | 2 +- cmd/commands.go | 6 ++ cmd/main.go | 6 +- modules/doc/manpages.go | 199 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 209 insertions(+), 4 deletions(-) create mode 100644 modules/doc/manpages.go diff --git a/cmd/commandfuncs.go b/cmd/commandfuncs.go index c456a813fa92..8f1c68c82b66 100644 --- a/cmd/commandfuncs.go +++ b/cmd/commandfuncs.go @@ -331,7 +331,7 @@ func cmdReload(fl Flags) (int, error) { } func cmdVersion(_ Flags) (int, error) { - fmt.Println(caddyVersion()) + fmt.Println(CaddyVersion()) return caddy.ExitCodeSuccess, nil } diff --git a/cmd/commands.go b/cmd/commands.go index 89c4fe43d166..4fb73971cedf 100644 --- a/cmd/commands.go +++ b/cmd/commands.go @@ -61,6 +61,12 @@ type Command struct { // any error that occurred. type CommandFunc func(Flags) (int, error) +// Commands returns a list of commands initialised by +// RegisterCommand +func Commands() map[string]Command { + return commands +} + var commands = make(map[string]Command) func init() { diff --git a/cmd/main.go b/cmd/main.go index deac87fec652..8a9a289687ce 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -420,7 +420,7 @@ func printEnvironment() { fmt.Printf("caddy.AppDataDir=%s\n", caddy.AppDataDir()) fmt.Printf("caddy.AppConfigDir=%s\n", caddy.AppConfigDir()) fmt.Printf("caddy.ConfigAutosavePath=%s\n", caddy.ConfigAutosavePath) - fmt.Printf("caddy.Version=%s\n", caddyVersion()) + fmt.Printf("caddy.Version=%s\n", CaddyVersion()) fmt.Printf("runtime.GOOS=%s\n", runtime.GOOS) fmt.Printf("runtime.GOARCH=%s\n", runtime.GOARCH) fmt.Printf("runtime.Compiler=%s\n", runtime.Compiler) @@ -437,8 +437,8 @@ func printEnvironment() { } } -// caddyVersion returns a detailed version string, if available. -func caddyVersion() string { +// CaddyVersion returns a detailed version string, if available. +func CaddyVersion() string { goModule := caddy.GoModule() ver := goModule.Version if goModule.Sum != "" { diff --git a/modules/doc/manpages.go b/modules/doc/manpages.go new file mode 100644 index 000000000000..40bde194ad3c --- /dev/null +++ b/modules/doc/manpages.go @@ -0,0 +1,199 @@ +package doc + +import ( + "errors" + "flag" + "fmt" + "os" + "path/filepath" + "sort" + "strings" + "text/template" + "time" + + "github.com/caddyserver/caddy/v2" + caddycmd "github.com/caddyserver/caddy/v2/cmd" +) + +func init() { + + caddy.RegisterModule(Manpages{}) + + caddycmd.RegisterCommand(caddycmd.Command{ + Name: "man", + Func: cmdMan, + Short: "Print manual pages for Caddy", + Long: ` +Creates subfolder "man" in current directory and prints manual pages for Caddy and its available subcommands into. +`, + }) +} + +type Manpages struct { +} + +func (Manpages) CaddyModule() caddy.ModuleInfo { + return caddy.ModuleInfo{ + ID: "caddy.doc.manpages", + New: func() caddy.Module { return new(Manpages) }, + } +} + +func cmdMan(fl caddycmd.Flags) (int, error) { + + type Macro struct { + App string + Name string + Date string + Long string + Title string + Short string + Usage string + Version string + Flags []*flag.Flag + Other []caddycmd.Command + } + + const DateFormat = "January 2006" + + const manpageTemplate = `.TH "{{ toUpper .Title }}" "1" "{{ .Date }}" +.SH NAME +.HP +{{ .Title }} \- {{ .Short }} +.SH SYNOPSIS +.HP +{{ .App }} {{if ne .Name .App}}{{ .Name }} {{ end }}{{ .Usage }}{{ if .Long }} +.SH DESCRIPTION +.PP +{{ .Long }}{{ end }} +{{ if .Flags}}.SH OPTIONS{{ range .Flags }} +.HP +\fB-{{ .Name }}\fR +.RS +{{ .Usage }} +.RE{{ end }}{{ end }}{{ if eq .Name .App }} +.SH COMMANDS +.PP +These are available commands to use with caddy. +See their manpages for usage and available flags.{{ range .Other }} +.HP +\fB{{ .Name }}\fR +.RS +{{ .Short }}. See \fBcaddy-{{ .Name }}\fR(1) +.RE{{ end }}{{ end }}{{if ne .Name .App}} +.SH SEE ALSO +.PP +{{ range .Other }}\fBcaddy-{{ .Name }}\fR(1), {{ end }}\fBcaddy\fR(1){{ end }} +.SH DOCUMENTATION +.HP +Full documentation is available at: https://caddyserver.com/docs/ +.SH VERSION +.HP +{{ .Version }} +.SH BUGS +.HP +Report Bugs to: https://github.com/caddyserver/caddy +.SH COPYRIGHT +.HP +(c) Matthew Holt and The Caddy Authors` + + // Get list of subcommands + commands := caddycmd.Commands() + + // Create dummy function for os.Args[0] + cmdCaddy := caddycmd.Command{ + Name: os.Args[0], + Short: "an extensible server platform", + Usage: " []", + } + + // Abort if os.Args[0] matches the name of a registered subcommand + if _, exists := commands[cmdCaddy.Name]; exists { + return caddy.ExitCodeFailedStartup, + errors.New("Main command is named similar as subcommand: " + cmdCaddy.Name + "\nAborting") + } + // Create dummy commands list and append dummy function for os.Args[0] + all := commands + all[cmdCaddy.Name] = cmdCaddy + + // Create "man" subdirectory in current directory + curDir, err := os.Getwd() + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + manDir := filepath.Join(curDir, "man") + err = os.MkdirAll(manDir, 0775) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + fmt.Printf("Printing manual pages for " + os.Args[0] + " into " + manDir + "...\n") + + // Iterate through dummy command list + for _, cmd := range all { + // Allocate macros to be passed to the template parser + m := Macro{ + App: os.Args[0], + Name: cmd.Name, + Date: time.Now().Format(DateFormat), + Long: strings.TrimSuffix(strings.TrimPrefix(cmd.Long, "\n"), "\n"), + Short: strings.TrimSuffix(cmd.Short, "."), + Usage: cmd.Usage, + Version: caddycmd.CaddyVersion(), + } + + // Title of man page will be be os.Args[0]-subcommand + // except for os.Args[0] itself + if m.Name == m.App { + m.Title = m.App + } else { + m.Title = m.App + "-" + m.Name + } + + // Collect available cli flags for command + if cmd.Flags != nil { + cmd.Flags.VisitAll(func(f *flag.Flag) { + m.Flags = append(m.Flags, f) + }) + } + + // Collect list of "other" subcommands then itself + keys := make([]string, 0, len(commands)) + for k := range commands { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + if k != m.Name { + m.Other = append(m.Other, commands[k]) + } + } + + // Create file + output := m.Title + ".1" + f, err := os.Create(filepath.Join(manDir, output)) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + // Initialize template + t := template.New(output) + + // Make toUpper function available to template parser + t = t.Funcs(template.FuncMap{"toUpper": strings.ToUpper}) + + // Parse template + t, err = t.Parse(manpageTemplate) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + + // Write template into file + err = t.Execute(f, m) + if err != nil { + return caddy.ExitCodeFailedStartup, err + } + } + fmt.Printf("Done.\nTo inspect a files content as rendered manual page, use 'nroff -man .1' or a similar text formatter\n") + return caddy.ExitCodeSuccess, nil +}