Files
Odin/core/flags/usage.odin
gingerBill f4dd48aa5d Add core:flags
Based on the Feoramund's original package
2024-06-04 19:08:03 +01:00

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)
}