v
Зеркало из https://github.com/vlang/v
1module cli
2
3import term
4import strings
5
6const base_indent_len = 2
7const min_description_indent_len = 20
8const spacing = 2
9
10fn help_flag(with_abbrev bool) Flag {
11sabbrev := if with_abbrev { 'h' } else { '' }
12return Flag{
13flag: .bool
14name: 'help'
15abbrev: sabbrev
16description: 'Prints help information.'
17}
18}
19
20fn help_cmd() Command {
21return Command{
22name: 'help'
23usage: '<command>'
24description: 'Prints help information.'
25execute: print_help_for_command
26}
27}
28
29// print_help_for_command outputs the help message of `help_cmd`.
30pub fn print_help_for_command(cmd Command) ! {
31if cmd.args.len > 0 {
32for sub_cmd in cmd.commands {
33if sub_cmd.name == cmd.args[0] {
34cmd_ := unsafe { &sub_cmd }
35print(cmd_.help_message())
36return
37}
38}
39print('Invalid command: ${cmd.args.join(' ')}')
40} else if cmd.parent != unsafe { nil } {
41print(cmd.parent.help_message())
42}
43}
44
45// help_message returns a generated help message as a `string` for the `Command`.
46pub fn (cmd Command) help_message() string {
47mut help := ''
48help += 'Usage: ${cmd.full_name()}'
49if cmd.flags.len > 0 {
50help += ' [flags]'
51}
52if cmd.commands.len > 0 {
53help += ' [commands]'
54}
55if cmd.usage.len > 0 {
56help += ' ${cmd.usage}'
57} else {
58for i in 0 .. cmd.required_args {
59help += ' <arg${i}>'
60}
61}
62help += '\n'
63if cmd.description != '' {
64help += '\n${cmd.description}\n'
65}
66mut abbrev_len := 0
67mut name_len := min_description_indent_len
68if cmd.posix_mode {
69for flag in cmd.flags {
70if flag.abbrev != '' {
71abbrev_len = max(abbrev_len, flag.abbrev.len + spacing + 1) // + 1 for '-' in front
72}
73name_len = max(name_len, abbrev_len + flag.name.len + spacing + 2) // + 2 for '--' in front
74}
75for command in cmd.commands {
76name_len = max(name_len, command.name.len + spacing)
77}
78} else {
79for flag in cmd.flags {
80if flag.abbrev != '' {
81abbrev_len = max(abbrev_len, flag.abbrev.len + spacing + 1) // + 1 for '-' in front
82}
83name_len = max(name_len, abbrev_len + flag.name.len + spacing + 1) // + 1 for '-' in front
84}
85for command in cmd.commands {
86name_len = max(name_len, command.name.len + spacing)
87}
88}
89if cmd.flags.len > 0 {
90help += '\nFlags:\n'
91for flag in cmd.flags {
92mut flag_name := ''
93prefix := if cmd.posix_mode { '--' } else { '-' }
94if flag.abbrev != '' {
95abbrev_indent := ' '.repeat(abbrev_len - flag.abbrev.len - 1) // - 1 for '-' in front
96flag_name = '-${flag.abbrev}${abbrev_indent}${prefix}${flag.name}'
97} else {
98abbrev_indent := ' '.repeat(abbrev_len)
99flag_name = '${abbrev_indent}${prefix}${flag.name}'
100}
101mut required := ''
102if flag.required {
103required = ' (required)'
104}
105base_indent := ' '.repeat(base_indent_len)
106description_indent := ' '.repeat(name_len - flag_name.len)
107help += '${base_indent}${flag_name}${description_indent}' +
108pretty_description(flag.description + required, base_indent_len + name_len) + '\n'
109}
110}
111if cmd.commands.len > 0 {
112help += '\nCommands:\n'
113for command in cmd.commands {
114base_indent := ' '.repeat(base_indent_len)
115description_indent := ' '.repeat(name_len - command.name.len)
116help += '${base_indent}${command.name}${description_indent}' +
117pretty_description(command.description, name_len) + '\n'
118}
119}
120return help
121}
122
123// pretty_description resizes description text depending on terminal width.
124// Essentially, smart wrap-around
125fn pretty_description(s string, indent_len int) string {
126width, _ := term.get_terminal_size()
127// Don't prettify if the terminal is that small, it won't be pretty anyway.
128if indent_len > width {
129return s
130}
131indent := ' '.repeat(indent_len)
132chars_per_line := width - indent_len
133// Give us enough room, better a little bigger than smaller
134mut acc := strings.new_builder(((s.len / chars_per_line) + 1) * (width + 1))
135for k, line in s.split('\n') {
136if k != 0 {
137acc.write_string('\n${indent}')
138}
139mut i := chars_per_line - 2
140mut j := 0
141for ; i < line.len; i += chars_per_line - 2 {
142for j > 0 && line[j] != ` ` {
143j--
144}
145// indent was already done the first iteration
146if j != 0 {
147acc.write_string(indent)
148}
149acc.writeln(line[j..i].trim_space())
150j = i
151}
152// We need this even though it should never happen
153if j != 0 {
154acc.write_string(indent)
155}
156acc.write_string(line[j..].trim_space())
157}
158return acc.str()
159}
160
161fn max(a int, b int) int {
162res := if a > b { a } else { b }
163return res
164}
165