mirror of
https://github.com/vlang/v.git
synced 2023-08-10 21:13:21 +03:00
179 lines
4.6 KiB
V
179 lines
4.6 KiB
V
module cli
|
|
|
|
import term
|
|
import strings
|
|
|
|
const (
|
|
base_indent_len = 2
|
|
min_description_indent_len = 20
|
|
spacing = 2
|
|
)
|
|
|
|
fn help_flag(with_abbrev bool) Flag {
|
|
sabbrev := if with_abbrev { 'h' } else { '' }
|
|
return Flag{
|
|
flag: .bool
|
|
name: 'help'
|
|
abbrev: sabbrev
|
|
description: 'Prints help information.'
|
|
}
|
|
}
|
|
|
|
fn help_cmd() Command {
|
|
return Command{
|
|
name: 'help'
|
|
usage: '<command>'
|
|
description: 'Prints help information.'
|
|
execute: print_help_for_command
|
|
}
|
|
}
|
|
|
|
// print_help_for_command outputs the help message of `help_cmd`.
|
|
pub fn print_help_for_command(help_cmd Command) ! {
|
|
if help_cmd.args.len > 0 {
|
|
mut cmd := help_cmd.parent
|
|
for arg in help_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 := help_cmd.args.join(' ')
|
|
println('Invalid command: ${args}')
|
|
return
|
|
}
|
|
}
|
|
print(cmd.help_message())
|
|
} else {
|
|
if unsafe { help_cmd.parent != 0 } {
|
|
print(help_cmd.parent.help_message())
|
|
}
|
|
}
|
|
}
|
|
|
|
// help_message returns a generated help message as a `string` for the `Command`.
|
|
pub fn (cmd Command) help_message() string {
|
|
mut help := ''
|
|
help += 'Usage: ${cmd.full_name()}'
|
|
if cmd.flags.len > 0 {
|
|
help += ' [flags]'
|
|
}
|
|
if cmd.commands.len > 0 {
|
|
help += ' [commands]'
|
|
}
|
|
if cmd.usage.len > 0 {
|
|
help += ' ${cmd.usage}'
|
|
} else {
|
|
for i in 0 .. cmd.required_args {
|
|
help += ' <arg${i}>'
|
|
}
|
|
}
|
|
help += '\n'
|
|
if cmd.description != '' {
|
|
help += '\n${cmd.description}\n'
|
|
}
|
|
mut abbrev_len := 0
|
|
mut name_len := cli.min_description_indent_len
|
|
if cmd.posix_mode {
|
|
for flag in cmd.flags {
|
|
if flag.abbrev != '' {
|
|
abbrev_len = max(abbrev_len, flag.abbrev.len + cli.spacing + 1) // + 1 for '-' in front
|
|
}
|
|
name_len = max(name_len, abbrev_len + flag.name.len + cli.spacing + 2) // + 2 for '--' in front
|
|
}
|
|
for command in cmd.commands {
|
|
name_len = max(name_len, command.name.len + cli.spacing)
|
|
}
|
|
} else {
|
|
for flag in cmd.flags {
|
|
if flag.abbrev != '' {
|
|
abbrev_len = max(abbrev_len, flag.abbrev.len + cli.spacing + 1) // + 1 for '-' in front
|
|
}
|
|
name_len = max(name_len, abbrev_len + flag.name.len + cli.spacing + 1) // + 1 for '-' in front
|
|
}
|
|
for command in cmd.commands {
|
|
name_len = max(name_len, command.name.len + cli.spacing)
|
|
}
|
|
}
|
|
if cmd.flags.len > 0 {
|
|
help += '\nFlags:\n'
|
|
for flag in cmd.flags {
|
|
mut flag_name := ''
|
|
prefix := if cmd.posix_mode { '--' } else { '-' }
|
|
if flag.abbrev != '' {
|
|
abbrev_indent := ' '.repeat(abbrev_len - flag.abbrev.len - 1) // - 1 for '-' in front
|
|
flag_name = '-${flag.abbrev}${abbrev_indent}${prefix}${flag.name}'
|
|
} else {
|
|
abbrev_indent := ' '.repeat(abbrev_len)
|
|
flag_name = '${abbrev_indent}${prefix}${flag.name}'
|
|
}
|
|
mut required := ''
|
|
if flag.required {
|
|
required = ' (required)'
|
|
}
|
|
base_indent := ' '.repeat(cli.base_indent_len)
|
|
description_indent := ' '.repeat(name_len - flag_name.len)
|
|
help += '${base_indent}${flag_name}${description_indent}' +
|
|
pretty_description(flag.description + required, cli.base_indent_len + name_len) +
|
|
'\n'
|
|
}
|
|
}
|
|
if cmd.commands.len > 0 {
|
|
help += '\nCommands:\n'
|
|
for command in cmd.commands {
|
|
base_indent := ' '.repeat(cli.base_indent_len)
|
|
description_indent := ' '.repeat(name_len - command.name.len)
|
|
help += '${base_indent}${command.name}${description_indent}' +
|
|
pretty_description(command.description, name_len) + '\n'
|
|
}
|
|
}
|
|
return help
|
|
}
|
|
|
|
// pretty_description resizes description text depending on terminal width.
|
|
// Essentially, smart wrap-around
|
|
fn pretty_description(s string, indent_len int) string {
|
|
width, _ := term.get_terminal_size()
|
|
// Don't prettify if the terminal is that small, it won't be pretty anyway.
|
|
if indent_len > width {
|
|
return s
|
|
}
|
|
indent := ' '.repeat(indent_len)
|
|
chars_per_line := width - indent_len
|
|
// Give us enough room, better a little bigger than smaller
|
|
mut acc := strings.new_builder(((s.len / chars_per_line) + 1) * (width + 1))
|
|
for k, line in s.split('\n') {
|
|
if k != 0 {
|
|
acc.write_string('\n${indent}')
|
|
}
|
|
mut i := chars_per_line - 2
|
|
mut j := 0
|
|
for ; i < line.len; i += chars_per_line - 2 {
|
|
for j > 0 && line[j] != ` ` {
|
|
j--
|
|
}
|
|
// indent was already done the first iteration
|
|
if j != 0 {
|
|
acc.write_string(indent)
|
|
}
|
|
acc.writeln(line[j..i].trim_space())
|
|
j = i
|
|
}
|
|
// We need this even though it should never happen
|
|
if j != 0 {
|
|
acc.write_string(indent)
|
|
}
|
|
acc.write_string(line[j..].trim_space())
|
|
}
|
|
return acc.str()
|
|
}
|
|
|
|
fn max(a int, b int) int {
|
|
res := if a > b { a } else { b }
|
|
return res
|
|
}
|