mirror of
https://github.com/odin-lang/Odin.git
synced 2025-12-29 01:14:40 +00:00
140 lines
3.5 KiB
Odin
140 lines
3.5 KiB
Odin
package flags
|
|
|
|
import "base:runtime"
|
|
import "core:fmt"
|
|
import "core:io"
|
|
import "core:os"
|
|
import "core:reflect"
|
|
import "core:slice"
|
|
import "core:strconv"
|
|
import "core:strings"
|
|
|
|
_, _, _, _, _, _, _, _ :: runtime, fmt, io, os, reflect, slice, strconv, strings
|
|
|
|
// Write out the documentation for the command-line arguments.
|
|
write_usage :: proc(out: io.Writer, data: ^$T, program: string = "") {
|
|
Flag :: struct {
|
|
name: string,
|
|
usage: string,
|
|
name_with_type: string,
|
|
pos: int,
|
|
is_positional: bool,
|
|
is_required: bool,
|
|
is_boolean: bool,
|
|
is_hidden: bool,
|
|
}
|
|
|
|
sort_flags :: proc(a, b: Flag) -> slice.Ordering {
|
|
if a.is_positional && b.is_positional {
|
|
return slice.cmp(a.pos, b.pos)
|
|
}
|
|
|
|
if a.is_required && !b.is_required {
|
|
return .Less
|
|
} else if !a.is_required && b.is_required {
|
|
return .Greater
|
|
}
|
|
|
|
if a.is_positional && !b.is_positional {
|
|
return .Less
|
|
} else if b.is_positional && !a.is_positional {
|
|
return .Greater
|
|
}
|
|
|
|
return slice.cmp(a.name, b.name)
|
|
}
|
|
|
|
flags: [dynamic]Flag
|
|
defer delete(flags)
|
|
|
|
longest_flag_length: int
|
|
|
|
for field in reflect.struct_fields_zipped(T) {
|
|
flag: Flag
|
|
|
|
flag.name = get_field_name(field)
|
|
#partial switch t in field.type.variant {
|
|
case runtime.Type_Info_Map:
|
|
flag.name_with_type = fmt.tprintf("%s:<%v>=<%v>", flag.name, t.key.id, t.value.id)
|
|
case runtime.Type_Info_Dynamic_Array:
|
|
flag.name_with_type = fmt.tprintf("%s:<%v, ...>", flag.name, t.elem.id)
|
|
case:
|
|
flag.name_with_type = fmt.tprintf("%s:<%v>", flag.name, field.type.id)
|
|
}
|
|
|
|
if usage, ok := reflect.struct_tag_lookup(field.tag, TAG_USAGE); ok {
|
|
flag.usage = usage
|
|
} else {
|
|
flag.usage = UNDOCUMENTED_FLAG
|
|
}
|
|
|
|
if args_tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS); ok {
|
|
if pos_str, is_pos := get_struct_subtag(args_tag, SUBTAG_POS); is_pos {
|
|
flag.is_positional = true
|
|
if pos, ok := strconv.parse_int(pos_str); ok && pos >= 0 {
|
|
flag.pos = pos
|
|
} else {
|
|
fmt.panicf("%v has incorrect pos subtag specifier `%s`", typeid_of(T), pos_str)
|
|
}
|
|
}
|
|
if _, is_required := get_struct_subtag(args_tag, SUBTAG_REQUIRED); is_required {
|
|
flag.is_required = true
|
|
}
|
|
if reflect.type_kind(field.type.id) == .Boolean {
|
|
flag.is_boolean = true
|
|
}
|
|
if _, is_hidden := get_struct_subtag(args_tag, SUBTAG_HIDDEN); is_hidden {
|
|
flag.is_hidden = true
|
|
}
|
|
}
|
|
|
|
if !flag.is_hidden {
|
|
longest_flag_length = max(longest_flag_length, len(flag.name_with_type))
|
|
}
|
|
|
|
append(&flags, flag)
|
|
}
|
|
|
|
slice.sort_by_cmp(flags[:], sort_flags)
|
|
|
|
if len(program) > 0 {
|
|
fmt.wprintf(out, "Usage:\n\t%s", program)
|
|
|
|
for flag in flags {
|
|
if flag.is_hidden {
|
|
continue
|
|
}
|
|
|
|
io.write_byte(out, ' ')
|
|
|
|
if flag.name == SUBTAG_POS {
|
|
io.write_string(out, "...")
|
|
continue
|
|
}
|
|
|
|
if !flag.is_required { io.write_byte(out, '[') }
|
|
if !flag.is_positional { io.write_byte(out, '-') }
|
|
io.write_string(out, flag.name)
|
|
if !flag.is_required { io.write_byte(out, ']') }
|
|
}
|
|
io.write_byte(out, '\n')
|
|
}
|
|
|
|
fmt.wprintln(out, "Flags:")
|
|
for flag in flags {
|
|
if flag.is_hidden {
|
|
continue
|
|
}
|
|
|
|
spacing := strings.repeat(" ",
|
|
(MINIMUM_SPACING + longest_flag_length) - len(flag.name_with_type),
|
|
context.temp_allocator)
|
|
fmt.wprintf(out, "\t-%s%s%s\n", flag.name_with_type, spacing, flag.usage)
|
|
}
|
|
}
|
|
|
|
// Print out the documentation for the command-line arguments.
|
|
print_usage :: proc(data: ^$T, program: string = "") {
|
|
write_usage(os.stream_from_handle(os.stdout), data, program)
|
|
}
|