usage.odin 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. package flags
  2. import "base:runtime"
  3. import "core:fmt"
  4. import "core:io"
  5. import "core:os"
  6. import "core:reflect"
  7. import "core:slice"
  8. import "core:strconv"
  9. import "core:strings"
  10. _, _, _, _, _, _, _, _ :: runtime, fmt, io, os, reflect, slice, strconv, strings
  11. // Write out the documentation for the command-line arguments.
  12. write_usage :: proc(out: io.Writer, data: ^$T, program: string = "") {
  13. Flag :: struct {
  14. name: string,
  15. usage: string,
  16. name_with_type: string,
  17. pos: int,
  18. is_positional: bool,
  19. is_required: bool,
  20. is_boolean: bool,
  21. is_hidden: bool,
  22. }
  23. sort_flags :: proc(a, b: Flag) -> slice.Ordering {
  24. if a.is_positional && b.is_positional {
  25. return slice.cmp(a.pos, b.pos)
  26. }
  27. if a.is_required && !b.is_required {
  28. return .Less
  29. } else if !a.is_required && b.is_required {
  30. return .Greater
  31. }
  32. if a.is_positional && !b.is_positional {
  33. return .Less
  34. } else if b.is_positional && !a.is_positional {
  35. return .Greater
  36. }
  37. return slice.cmp(a.name, b.name)
  38. }
  39. flags: [dynamic]Flag
  40. defer delete(flags)
  41. longest_flag_length: int
  42. for field in reflect.struct_fields_zipped(T) {
  43. flag: Flag
  44. flag.name = get_field_name(field)
  45. #partial switch t in field.type.variant {
  46. case runtime.Type_Info_Map:
  47. flag.name_with_type = fmt.tprintf("%s:<%v>=<%v>", flag.name, t.key.id, t.value.id)
  48. case runtime.Type_Info_Dynamic_Array:
  49. flag.name_with_type = fmt.tprintf("%s:<%v, ...>", flag.name, t.elem.id)
  50. case:
  51. flag.name_with_type = fmt.tprintf("%s:<%v>", flag.name, field.type.id)
  52. }
  53. if usage, ok := reflect.struct_tag_lookup(field.tag, TAG_USAGE); ok {
  54. flag.usage = usage
  55. } else {
  56. flag.usage = UNDOCUMENTED_FLAG
  57. }
  58. if args_tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS); ok {
  59. if pos_str, is_pos := get_struct_subtag(args_tag, SUBTAG_POS); is_pos {
  60. flag.is_positional = true
  61. if pos, ok := strconv.parse_int(pos_str); ok && pos >= 0 {
  62. flag.pos = pos
  63. } else {
  64. fmt.panicf("%v has incorrect pos subtag specifier `%s`", typeid_of(T), pos_str)
  65. }
  66. }
  67. if _, is_required := get_struct_subtag(args_tag, SUBTAG_REQUIRED); is_required {
  68. flag.is_required = true
  69. }
  70. if reflect.type_kind(field.type.id) == .Boolean {
  71. flag.is_boolean = true
  72. }
  73. if _, is_hidden := get_struct_subtag(args_tag, SUBTAG_HIDDEN); is_hidden {
  74. flag.is_hidden = true
  75. }
  76. }
  77. if !flag.is_hidden {
  78. longest_flag_length = max(longest_flag_length, len(flag.name_with_type))
  79. }
  80. append(&flags, flag)
  81. }
  82. slice.sort_by_cmp(flags[:], sort_flags)
  83. if len(program) > 0 {
  84. fmt.wprintf(out, "Usage:\n\t%s", program)
  85. for flag in flags {
  86. if flag.is_hidden {
  87. continue
  88. }
  89. io.write_byte(out, ' ')
  90. if flag.name == SUBTAG_POS {
  91. io.write_string(out, "...")
  92. continue
  93. }
  94. if !flag.is_required { io.write_byte(out, '[') }
  95. if !flag.is_positional { io.write_byte(out, '-') }
  96. io.write_string(out, flag.name)
  97. if !flag.is_required { io.write_byte(out, ']') }
  98. }
  99. io.write_byte(out, '\n')
  100. }
  101. fmt.wprintln(out, "Flags:")
  102. for flag in flags {
  103. if flag.is_hidden {
  104. continue
  105. }
  106. spacing := strings.repeat(" ",
  107. (MINIMUM_SPACING + longest_flag_length) - len(flag.name_with_type),
  108. context.temp_allocator)
  109. fmt.wprintf(out, "\t-%s%s%s\n", flag.name_with_type, spacing, flag.usage)
  110. }
  111. }
  112. // Print out the documentation for the command-line arguments.
  113. print_usage :: proc(data: ^$T, program: string = "") {
  114. write_usage(os.stream_from_handle(os.stdout), data, program)
  115. }