2019-11-21 13:03:12 +01:00
module cli
2020-04-26 19:49:31 +08:00
import term
import strings
2020-04-20 08:50:15 -04:00
2023-11-25 10:02:51 +03:00
const base_indent_len = 2
const min_description_indent_len = 20
const spacing = 2
2019-11-21 13:03:12 +01:00
2020-07-01 10:54:34 +02:00
fn help_flag ( with_abbrev bool ) Flag {
2020-07-18 14:24:10 +03:00
sabbrev := if with_abbrev { ' h ' } else { ' ' }
2019-11-21 13:03:12 +01:00
return Flag {
2024-08-11 14:11:24 +08:00
flag : . bool
name : ' h e l p '
abbrev : sabbrev
2020-08-20 23:14:53 +02:00
description : ' P r i n t s h e l p i n f o r m a t i o n . '
2019-11-21 13:03:12 +01:00
}
}
fn help_cmd ( ) Command {
return Command {
2024-08-11 14:11:24 +08:00
name : ' h e l p '
usage : ' < c o m m a n d > '
2020-08-20 23:14:53 +02:00
description : ' P r i n t s h e l p i n f o r m a t i o n . '
2024-08-11 14:11:24 +08:00
execute : print_help_for_command
2019-11-21 13:03:12 +01:00
}
}
2022-04-25 13:41:46 +02:00
// print_help_for_command outputs the help message of `help_cmd`.
2024-05-08 12:47:26 +02:00
pub fn print_help_for_command ( cmd Command ) ! {
if cmd . args . len > 0 {
for sub_cmd in cmd . commands {
if sub_cmd . name == cmd . args [ 0 ] {
cmd_ := unsafe { & sub_cmd }
print ( cmd_ . help_message ( ) )
2020-07-18 14:24:10 +03:00
return
}
}
2024-05-08 12:47:26 +02:00
print ( ' I n v a l i d c o m m a n d : $ { cmd . args . join ( ' ' ) } ' )
} else if cmd . parent != unsafe { nil } {
print ( cmd . parent . help_message ( ) )
2020-07-18 14:24:10 +03:00
}
}
2019-11-21 13:03:12 +01:00
2022-04-25 13:41:46 +02:00
// help_message returns a generated help message as a `string` for the `Command`.
2024-10-17 16:04:23 +08:00
pub fn ( cmd & Command ) help_message ( ) string {
2019-11-21 13:03:12 +01:00
mut help := ' '
2022-11-15 21:53:13 +08:00
help += ' U s a g e : $ { cmd . full_name ( ) } '
2020-07-18 14:24:10 +03:00
if cmd . flags . len > 0 {
help += ' [ f l a g s ] '
}
if cmd . commands . len > 0 {
help += ' [ c o m m a n d s ] '
}
if cmd . usage . len > 0 {
2022-11-15 21:53:13 +08:00
help += ' $ { cmd . usage } '
2020-09-16 22:01:44 +02:00
} else {
for i in 0 .. cmd . required_args {
2022-11-15 21:53:13 +08:00
help += ' < a r g $ { i } > '
2020-09-16 22:01:44 +02:00
}
2020-07-18 14:24:10 +03:00
}
2020-09-16 22:01:44 +02:00
help += ' \n '
2019-11-21 13:03:12 +01:00
if cmd . description != ' ' {
2022-11-15 21:53:13 +08:00
help += ' \n $ { cmd . description } \n '
2019-11-21 13:03:12 +01:00
}
2020-06-29 17:59:55 +02:00
mut abbrev_len := 0
2024-09-10 16:25:56 +08:00
mut name_len := min_description_indent_len
2021-08-11 12:26:17 +03:00
if cmd . posix_mode {
2020-07-18 14:24:10 +03:00
for flag in cmd . flags {
2021-08-12 15:25:28 +09:00
if flag . abbrev != ' ' {
2024-09-10 16:25:56 +08:00
abbrev_len = max ( abbrev_len , flag . abbrev . len + spacing + 1 ) // + 1 for '-' in front
2021-08-12 15:25:28 +09:00
}
2024-09-10 16:25:56 +08:00
name_len = max ( name_len , abbrev_len + flag . name . len + spacing + 2 ) // + 2 for '--' in front
2020-07-18 14:24:10 +03:00
}
for command in cmd . commands {
2024-09-10 16:25:56 +08:00
name_len = max ( name_len , command . name . len + spacing )
2020-07-18 14:24:10 +03:00
}
} else {
for flag in cmd . flags {
2021-08-12 15:25:28 +09:00
if flag . abbrev != ' ' {
2024-09-10 16:25:56 +08:00
abbrev_len = max ( abbrev_len , flag . abbrev . len + spacing + 1 ) // + 1 for '-' in front
2021-08-12 15:25:28 +09:00
}
2024-09-10 16:25:56 +08:00
name_len = max ( name_len , abbrev_len + flag . name . len + spacing + 1 ) // + 1 for '-' in front
2020-07-18 14:24:10 +03:00
}
for command in cmd . commands {
2024-09-10 16:25:56 +08:00
name_len = max ( name_len , command . name . len + spacing )
2020-07-18 14:24:10 +03:00
}
2020-06-29 17:59:55 +02:00
}
2019-11-21 13:03:12 +01:00
if cmd . flags . len > 0 {
2020-09-16 22:01:44 +02:00
help += ' \n F l a g s : \n '
2019-11-21 13:03:12 +01:00
for flag in cmd . flags {
mut flag_name := ' '
2021-08-11 12:26:17 +03:00
prefix := if cmd . posix_mode { ' - - ' } else { ' - ' }
if flag . abbrev != ' ' {
2020-06-29 17:59:55 +02:00
abbrev_indent := ' ' . repeat ( abbrev_len - flag . abbrev . len - 1 ) // - 1 for '-' in front
2022-11-15 21:53:13 +08:00
flag_name = ' - $ { flag . abbrev } $ { abbrev_indent } $ { prefix } $ { flag . name } '
2020-07-18 14:24:10 +03:00
} else {
2021-08-11 12:26:17 +03:00
abbrev_indent := ' ' . repeat ( abbrev_len )
2022-11-15 21:53:13 +08:00
flag_name = ' $ { abbrev_indent } $ { prefix } $ { flag . name } '
2019-11-21 13:03:12 +01:00
}
mut required := ' '
if flag . required {
required = ' ( r e q u i r e d ) '
}
2024-09-10 16:25:56 +08:00
base_indent := ' ' . repeat ( base_indent_len )
2020-06-29 17:59:55 +02:00
description_indent := ' ' . repeat ( name_len - flag_name . len )
2022-11-15 21:53:13 +08:00
help += ' $ { base_indent } $ { flag_name } $ { description_indent } ' +
2024-09-10 16:25:56 +08:00
pretty_description ( flag . description + required , base_indent_len + name_len ) + ' \n '
2019-11-21 13:03:12 +01:00
}
}
if cmd . commands . len > 0 {
2020-09-16 22:01:44 +02:00
help += ' \n C o m m a n d s : \n '
2019-11-21 13:03:12 +01:00
for command in cmd . commands {
2024-09-10 16:25:56 +08:00
base_indent := ' ' . repeat ( base_indent_len )
2020-06-29 17:59:55 +02:00
description_indent := ' ' . repeat ( name_len - command . name . len )
2022-11-15 21:53:13 +08:00
help += ' $ { base_indent } $ { command . name } $ { description_indent } ' +
2025-03-20 05:33:27 -06:00
pretty_description ( command . description , base_indent_len + name_len ) + ' \n '
2019-11-21 13:03:12 +01:00
}
}
2020-07-18 14:24:10 +03:00
return help
2019-11-21 13:03:12 +01:00
}
2020-04-16 17:01:04 -04:00
2020-04-20 08:50:15 -04:00
// pretty_description resizes description text depending on terminal width.
// Essentially, smart wrap-around
2020-06-29 17:59:55 +02:00
fn pretty_description ( s string , indent_len int ) string {
2020-04-20 08:50:15 -04:00
width , _ := term . get_terminal_size ( )
// Don't prettify if the terminal is that small, it won't be pretty anyway.
2020-07-18 14:24:10 +03:00
if indent_len > width {
2020-04-20 08:50:15 -04:00
return s
}
2020-06-29 17:59:55 +02:00
indent := ' ' . repeat ( indent_len )
chars_per_line := width - indent_len
2020-04-20 08:50:15 -04:00
// Give us enough room, better a little bigger than smaller
mut acc := strings . new_builder ( ( ( s . len / chars_per_line ) + 1 ) * ( width + 1 ) )
2020-06-29 14:47:20 +02:00
for k , line in s . split ( ' \n ' ) {
2020-07-18 14:24:10 +03:00
if k != 0 {
2022-11-15 21:53:13 +08:00
acc . write_string ( ' \n $ { indent } ' )
2020-07-18 14:24:10 +03:00
}
2020-06-29 14:47:20 +02:00
mut i := chars_per_line - 2
mut j := 0
for ; i < line . len ; i += chars_per_line - 2 {
2023-04-18 06:37:26 -03:00
for j > 0 && line [ j ] != ` ` {
j --
2020-07-18 14:24:10 +03:00
}
2020-06-29 14:47:20 +02:00
// indent was already done the first iteration
2020-07-18 14:24:10 +03:00
if j != 0 {
2021-02-22 20:18:11 +09:00
acc . write_string ( indent )
2020-07-18 14:24:10 +03:00
}
2020-06-29 14:47:20 +02:00
acc . writeln ( line [ j .. i ] . trim_space ( ) )
j = i
}
// We need this even though it should never happen
if j != 0 {
2021-02-22 20:18:11 +09:00
acc . write_string ( indent )
2020-06-29 14:47:20 +02:00
}
2021-02-22 20:18:11 +09:00
acc . write_string ( line [ j .. ] . trim_space ( ) )
2020-04-20 08:50:15 -04:00
}
return acc . str ( )
}
2020-10-21 12:23:03 +03:00
fn max ( a int , b int ) int {
2020-07-18 14:24:10 +03:00
res := if a > b { a } else { b }
return res
2020-04-16 17:01:04 -04:00
}