cli: add automatic manpage generation with -man (#13911)
parent
71dc6c224a
commit
340543dfc0
|
@ -16,11 +16,13 @@ pub mut:
|
||||||
name string
|
name string
|
||||||
usage string
|
usage string
|
||||||
description string
|
description string
|
||||||
|
man_description string
|
||||||
version string
|
version string
|
||||||
pre_execute FnCommandCallback
|
pre_execute FnCommandCallback
|
||||||
execute FnCommandCallback
|
execute FnCommandCallback
|
||||||
post_execute FnCommandCallback
|
post_execute FnCommandCallback
|
||||||
disable_help bool
|
disable_help bool
|
||||||
|
disable_man bool
|
||||||
disable_version bool
|
disable_version bool
|
||||||
disable_flags bool
|
disable_flags bool
|
||||||
sort_flags bool
|
sort_flags bool
|
||||||
|
@ -41,7 +43,9 @@ pub fn (cmd Command) str() string {
|
||||||
res << ' usage: "$cmd.usage"'
|
res << ' usage: "$cmd.usage"'
|
||||||
res << ' version: "$cmd.version"'
|
res << ' version: "$cmd.version"'
|
||||||
res << ' description: "$cmd.description"'
|
res << ' description: "$cmd.description"'
|
||||||
|
res << ' man_description: "$cmd.man_description"'
|
||||||
res << ' disable_help: $cmd.disable_help'
|
res << ' disable_help: $cmd.disable_help'
|
||||||
|
res << ' disable_man: $cmd.disable_man'
|
||||||
res << ' disable_flags: $cmd.disable_flags'
|
res << ' disable_flags: $cmd.disable_flags'
|
||||||
res << ' disable_version: $cmd.disable_version'
|
res << ' disable_version: $cmd.disable_version'
|
||||||
res << ' sort_flags: $cmd.sort_flags'
|
res << ' sort_flags: $cmd.sort_flags'
|
||||||
|
@ -155,6 +159,9 @@ fn (mut cmd Command) add_default_flags() {
|
||||||
use_version_abbrev := !cmd.flags.contains('v') && cmd.posix_mode
|
use_version_abbrev := !cmd.flags.contains('v') && cmd.posix_mode
|
||||||
cmd.add_flag(version_flag(use_version_abbrev))
|
cmd.add_flag(version_flag(use_version_abbrev))
|
||||||
}
|
}
|
||||||
|
if !cmd.disable_man && !cmd.flags.contains('man') {
|
||||||
|
cmd.add_flag(man_flag())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// add_default_commands adds the command functions of the
|
// add_default_commands adds the command functions of the
|
||||||
|
@ -166,6 +173,9 @@ fn (mut cmd Command) add_default_commands() {
|
||||||
if !cmd.disable_version && cmd.version != '' && !cmd.commands.contains('version') {
|
if !cmd.disable_version && cmd.version != '' && !cmd.commands.contains('version') {
|
||||||
cmd.add_command(version_cmd())
|
cmd.add_command(version_cmd())
|
||||||
}
|
}
|
||||||
|
if !cmd.disable_man && !cmd.commands.contains('man') && cmd.is_root() {
|
||||||
|
cmd.add_command(man_cmd())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn (mut cmd Command) parse_flags() {
|
fn (mut cmd Command) parse_flags() {
|
||||||
|
@ -197,6 +207,7 @@ fn (mut cmd Command) parse_commands() {
|
||||||
global_flags := cmd.flags.filter(it.global)
|
global_flags := cmd.flags.filter(it.global)
|
||||||
cmd.check_help_flag()
|
cmd.check_help_flag()
|
||||||
cmd.check_version_flag()
|
cmd.check_version_flag()
|
||||||
|
cmd.check_man_flag()
|
||||||
for i in 0 .. cmd.args.len {
|
for i in 0 .. cmd.args.len {
|
||||||
arg := cmd.args[i]
|
arg := cmd.args[i]
|
||||||
for j in 0 .. cmd.commands.len {
|
for j in 0 .. cmd.commands.len {
|
||||||
|
@ -248,6 +259,16 @@ fn (cmd Command) check_help_flag() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn (cmd Command) check_man_flag() {
|
||||||
|
if !cmd.disable_man && cmd.flags.contains('man') {
|
||||||
|
man_flag := cmd.flags.get_bool('man') or { return } // ignore error and handle command normally
|
||||||
|
if man_flag {
|
||||||
|
cmd.execute_man()
|
||||||
|
exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn (cmd Command) check_version_flag() {
|
fn (cmd Command) check_version_flag() {
|
||||||
if !cmd.disable_version && cmd.version != '' && cmd.flags.contains('version') {
|
if !cmd.disable_version && cmd.version != '' && cmd.flags.contains('version') {
|
||||||
version_flag := cmd.flags.get_bool('version') or { return } // ignore error and handle command normally
|
version_flag := cmd.flags.get_bool('version') or { return } // ignore error and handle command normally
|
||||||
|
@ -279,6 +300,17 @@ pub fn (cmd Command) execute_help() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// execute_help executes the callback registered
|
||||||
|
// for the `-man` flag option.
|
||||||
|
pub fn (cmd Command) execute_man() {
|
||||||
|
if cmd.commands.contains('man') {
|
||||||
|
man_cmd := cmd.commands.get('man') or { return }
|
||||||
|
man_cmd.execute(man_cmd) or { panic(err) }
|
||||||
|
} else {
|
||||||
|
print(cmd.manpage())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn (cmds []Command) get(name string) ?Command {
|
fn (cmds []Command) get(name string) ?Command {
|
||||||
for cmd in cmds {
|
for cmd in cmds {
|
||||||
if cmd.name == name {
|
if cmd.name == name {
|
||||||
|
|
|
@ -0,0 +1,181 @@
|
||||||
|
module cli
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
fn man_flag() Flag {
|
||||||
|
return Flag{
|
||||||
|
flag: .bool
|
||||||
|
name: 'man'
|
||||||
|
description: 'Prints the auto-generated manpage.'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn man_cmd() Command {
|
||||||
|
return Command{
|
||||||
|
name: 'man'
|
||||||
|
usage: '<subcommand>'
|
||||||
|
description: 'Prints the auto-generated manpage.'
|
||||||
|
execute: print_manpage_for_command
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// print_manpage_for_command prints the manpage for the
|
||||||
|
// command or subcommand in `man_cmd` to stdout
|
||||||
|
pub fn print_manpage_for_command(man_cmd Command) ? {
|
||||||
|
if man_cmd.args.len > 0 {
|
||||||
|
mut cmd := man_cmd.parent
|
||||||
|
for arg in man_cmd.args {
|
||||||
|
mut found := false
|
||||||
|
for sub_cmd in cmd.commands {
|
||||||
|
if sub_cmd.name == arg {
|
||||||
|
cmd = unsafe { &sub_cmd }
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
args := man_cmd.args.join(' ')
|
||||||
|
println('Invalid command: $args')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print(cmd.manpage())
|
||||||
|
} else {
|
||||||
|
if man_cmd.parent != 0 {
|
||||||
|
print(man_cmd.parent.manpage())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// manpage returns a `string` containing the mdoc(7) manpage for
|
||||||
|
// this `Command`
|
||||||
|
pub fn (cmd Command) manpage() string {
|
||||||
|
mut mdoc := '.Dd ${time.now().strftime('%B %d, %Y')}\n'
|
||||||
|
mdoc += '.Dt ${cmd.full_name().replace(' ', '-').to_upper()} 1\n'
|
||||||
|
mdoc += '.Os\n.Sh NAME\n.Nm ${cmd.full_name().replace(' ', '-')}\n.Nd $cmd.description\n'
|
||||||
|
mdoc += '.Sh SYNOPSIS\n'
|
||||||
|
mdoc += '.Nm $cmd.root().name\n'
|
||||||
|
if cmd.parent != 0 {
|
||||||
|
mut parents := []Command{}
|
||||||
|
if !cmd.parent.is_root() {
|
||||||
|
parents.prepend(cmd.parent)
|
||||||
|
for {
|
||||||
|
p := parents[0]
|
||||||
|
if p.parent.is_root() {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
parents.prepend(p.parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for c in parents {
|
||||||
|
mdoc += '.Ar $c.name\n'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mdoc += '.Ar $cmd.name\n'
|
||||||
|
}
|
||||||
|
for flag in cmd.flags {
|
||||||
|
mdoc += '.Op'
|
||||||
|
if flag.abbrev != '' {
|
||||||
|
mdoc += ' Fl $flag.abbrev'
|
||||||
|
} else {
|
||||||
|
if cmd.posix_mode {
|
||||||
|
mdoc += ' Fl -$flag.name'
|
||||||
|
} else {
|
||||||
|
mdoc += ' Fl $flag.name'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match flag.flag {
|
||||||
|
.int, .float, .int_array, .float_array { mdoc += ' Ar num' }
|
||||||
|
.string, .string_array { mdoc += ' Ar string' }
|
||||||
|
else {}
|
||||||
|
}
|
||||||
|
mdoc += '\n'
|
||||||
|
}
|
||||||
|
for i in 0 .. cmd.required_args {
|
||||||
|
mdoc += '.Ar arg$i\n'
|
||||||
|
}
|
||||||
|
if cmd.commands.len > 0 {
|
||||||
|
mdoc += '.Nm $cmd.root().name\n'
|
||||||
|
if cmd.parent != 0 {
|
||||||
|
mut parents := []Command{}
|
||||||
|
if !cmd.parent.is_root() {
|
||||||
|
parents.prepend(cmd.parent)
|
||||||
|
for {
|
||||||
|
p := parents[0]
|
||||||
|
if p.parent.is_root() {
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
parents.prepend(p.parent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for c in parents {
|
||||||
|
mdoc += '.Ar $c.name\n'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mdoc += '.Ar $cmd.name\n'
|
||||||
|
}
|
||||||
|
mdoc += '.Ar subcommand\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
mdoc += '.Sh DESCRIPTION\n'
|
||||||
|
if cmd.man_description != '' {
|
||||||
|
mdoc += '$cmd.man_description\n'
|
||||||
|
} else if cmd.description != '' {
|
||||||
|
mdoc += '$cmd.description\n'
|
||||||
|
}
|
||||||
|
if cmd.flags.len > 0 {
|
||||||
|
mdoc += '.Pp\nThe options are as follows:\n'
|
||||||
|
mdoc += '.Bl -tag -width indent\n'
|
||||||
|
for flag in cmd.flags {
|
||||||
|
mdoc += '.It'
|
||||||
|
if flag.abbrev != '' {
|
||||||
|
mdoc += ' Fl $flag.abbrev'
|
||||||
|
}
|
||||||
|
if cmd.posix_mode {
|
||||||
|
mdoc += ' Fl -$flag.name'
|
||||||
|
} else {
|
||||||
|
mdoc += ' Fl $flag.name'
|
||||||
|
}
|
||||||
|
mdoc += '\n'
|
||||||
|
if flag.description != '' {
|
||||||
|
mdoc += '$flag.description\n'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mdoc += '.El\n'
|
||||||
|
}
|
||||||
|
if cmd.commands.len > 0 {
|
||||||
|
mdoc += '.Pp\nThe subcommands are as follows:\n'
|
||||||
|
mdoc += '.Bl -tag -width indent\n'
|
||||||
|
for c in cmd.commands {
|
||||||
|
mdoc += '.It Cm $c.name\n'
|
||||||
|
if c.description != '' {
|
||||||
|
mdoc += '$c.description\n'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mdoc += '.El\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd.commands.len > 0 {
|
||||||
|
mdoc += '.Sh SEE ALSO\n'
|
||||||
|
mut cmds := []string{}
|
||||||
|
if cmd.parent != 0 {
|
||||||
|
cmds << cmd.parent.full_name().replace(' ', '-')
|
||||||
|
}
|
||||||
|
for c in cmd.commands {
|
||||||
|
cmds << c.full_name().replace(' ', '-')
|
||||||
|
}
|
||||||
|
cmds.sort()
|
||||||
|
mut i := 1
|
||||||
|
for c in cmds {
|
||||||
|
mdoc += '.Xr $c 1'
|
||||||
|
if i == cmds.len {
|
||||||
|
mdoc += '\n'
|
||||||
|
} else {
|
||||||
|
mdoc += ' ,\n'
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mdoc
|
||||||
|
}
|
|
@ -0,0 +1,110 @@
|
||||||
|
module cli
|
||||||
|
|
||||||
|
fn test_manpage() {
|
||||||
|
mut cmd := Command{
|
||||||
|
name: 'command'
|
||||||
|
description: 'description'
|
||||||
|
commands: [
|
||||||
|
Command{
|
||||||
|
name: 'sub'
|
||||||
|
description: 'subcommand'
|
||||||
|
},
|
||||||
|
Command{
|
||||||
|
name: 'sub2'
|
||||||
|
description: 'another subcommand'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
flags: [
|
||||||
|
Flag{
|
||||||
|
flag: .string
|
||||||
|
name: 'str'
|
||||||
|
description: 'str flag'
|
||||||
|
},
|
||||||
|
Flag{
|
||||||
|
flag: .bool
|
||||||
|
name: 'bool'
|
||||||
|
description: 'bool flag'
|
||||||
|
abbrev: 'b'
|
||||||
|
},
|
||||||
|
Flag{
|
||||||
|
flag: .string
|
||||||
|
name: 'required'
|
||||||
|
abbrev: 'r'
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
cmd.setup()
|
||||||
|
assert cmd.manpage().after_char(`\n`) == r'.Dt COMMAND 1
|
||||||
|
.Os
|
||||||
|
.Sh NAME
|
||||||
|
.Nm command
|
||||||
|
.Nd description
|
||||||
|
.Sh SYNOPSIS
|
||||||
|
.Nm command
|
||||||
|
.Op Fl str Ar string
|
||||||
|
.Op Fl b
|
||||||
|
.Op Fl r Ar string
|
||||||
|
.Nm command
|
||||||
|
.Ar subcommand
|
||||||
|
.Sh DESCRIPTION
|
||||||
|
description
|
||||||
|
.Pp
|
||||||
|
The options are as follows:
|
||||||
|
.Bl -tag -width indent
|
||||||
|
.It Fl str
|
||||||
|
str flag
|
||||||
|
.It Fl b Fl bool
|
||||||
|
bool flag
|
||||||
|
.It Fl r Fl required
|
||||||
|
.El
|
||||||
|
.Pp
|
||||||
|
The subcommands are as follows:
|
||||||
|
.Bl -tag -width indent
|
||||||
|
.It Cm sub
|
||||||
|
subcommand
|
||||||
|
.It Cm sub2
|
||||||
|
another subcommand
|
||||||
|
.El
|
||||||
|
.Sh SEE ALSO
|
||||||
|
.Xr command-sub 1 ,
|
||||||
|
.Xr command-sub2 1
|
||||||
|
'
|
||||||
|
|
||||||
|
cmd.posix_mode = true
|
||||||
|
assert cmd.manpage().after_char(`\n`) == r'.Dt COMMAND 1
|
||||||
|
.Os
|
||||||
|
.Sh NAME
|
||||||
|
.Nm command
|
||||||
|
.Nd description
|
||||||
|
.Sh SYNOPSIS
|
||||||
|
.Nm command
|
||||||
|
.Op Fl -str Ar string
|
||||||
|
.Op Fl b
|
||||||
|
.Op Fl r Ar string
|
||||||
|
.Nm command
|
||||||
|
.Ar subcommand
|
||||||
|
.Sh DESCRIPTION
|
||||||
|
description
|
||||||
|
.Pp
|
||||||
|
The options are as follows:
|
||||||
|
.Bl -tag -width indent
|
||||||
|
.It Fl -str
|
||||||
|
str flag
|
||||||
|
.It Fl b Fl -bool
|
||||||
|
bool flag
|
||||||
|
.It Fl r Fl -required
|
||||||
|
.El
|
||||||
|
.Pp
|
||||||
|
The subcommands are as follows:
|
||||||
|
.Bl -tag -width indent
|
||||||
|
.It Cm sub
|
||||||
|
subcommand
|
||||||
|
.It Cm sub2
|
||||||
|
another subcommand
|
||||||
|
.El
|
||||||
|
.Sh SEE ALSO
|
||||||
|
.Xr command-sub 1 ,
|
||||||
|
.Xr command-sub2 1
|
||||||
|
'
|
||||||
|
}
|
|
@ -3,5 +3,6 @@ import os
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
mut cmd := Command {}
|
mut cmd := Command {}
|
||||||
|
cmd.disable_man = true
|
||||||
cmd.parse(os.args)
|
cmd.parse(os.args)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue