usage.odin 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. package flags
  2. import "base:runtime"
  3. import "core:fmt"
  4. import "core:io"
  5. import "core:reflect"
  6. import "core:slice"
  7. import "core:strconv"
  8. import "core:strings"
  9. /*
  10. Write out the documentation for the command-line arguments to a stream.
  11. Inputs:
  12. - out: The stream to write to.
  13. - data_type: The typeid of the data structure to describe.
  14. - program: The name of the program, usually the first argument to `os.args`.
  15. - style: The argument parsing style, required to show flags in the proper style.
  16. */
  17. @(optimization_mode="favor_size")
  18. write_usage :: proc(out: io.Writer, data_type: typeid, program: string = "", style: Parsing_Style = .Odin) {
  19. // All flags get their tags parsed so they can be reasoned about later.
  20. Flag :: struct {
  21. name: string,
  22. usage: string,
  23. type_description: string,
  24. full_length: int,
  25. pos: int,
  26. required_min, required_max: int,
  27. is_positional: bool,
  28. is_required: bool,
  29. is_boolean: bool,
  30. is_variadic: bool,
  31. variadic_length: int,
  32. }
  33. //
  34. // POSITIONAL+REQUIRED, POSITIONAL, REQUIRED, NON_REQUIRED+NON_POSITIONAL, ...
  35. //
  36. sort_flags :: proc(i, j: Flag) -> slice.Ordering {
  37. // `varg` goes to the end.
  38. if i.name == INTERNAL_VARIADIC_FLAG {
  39. return .Greater
  40. } else if j.name == INTERNAL_VARIADIC_FLAG {
  41. return .Less
  42. }
  43. // Handle positionals.
  44. if i.is_positional {
  45. if j.is_positional {
  46. return slice.cmp(i.pos, j.pos)
  47. } else {
  48. return .Less
  49. }
  50. } else {
  51. if j.is_positional {
  52. return .Greater
  53. }
  54. }
  55. // Then required flags.
  56. if i.is_required {
  57. if !j.is_required {
  58. return .Less
  59. }
  60. } else if j.is_required {
  61. return .Greater
  62. }
  63. // Finally, sort by name.
  64. return slice.cmp(i.name, j.name)
  65. }
  66. describe_array_requirements :: proc(flag: Flag) -> (spec: string) {
  67. if flag.is_required {
  68. if flag.required_min == flag.required_max - 1 {
  69. spec = fmt.tprintf(", exactly %i", flag.required_min)
  70. } else if flag.required_min > 0 && flag.required_max == max(int) {
  71. spec = fmt.tprintf(", at least %i", flag.required_min)
  72. } else if flag.required_min == 0 && flag.required_max > 1 {
  73. spec = fmt.tprintf(", at most %i", flag.required_max - 1)
  74. } else if flag.required_min > 0 && flag.required_max > 1 {
  75. spec = fmt.tprintf(", between %i and %i", flag.required_min, flag.required_max - 1)
  76. } else {
  77. spec = ", required"
  78. }
  79. }
  80. return
  81. }
  82. builder := strings.builder_make()
  83. defer strings.builder_destroy(&builder)
  84. flag_prefix, flag_assignment: string = ---, ---
  85. switch style {
  86. case .Odin: flag_prefix = "-"; flag_assignment = ":"
  87. case .Unix: flag_prefix = "--"; flag_assignment = " "
  88. }
  89. visible_flags: [dynamic]Flag
  90. defer delete(visible_flags)
  91. longest_flag_length: int
  92. for field in reflect.struct_fields_zipped(data_type) {
  93. flag: Flag
  94. if args_tag, ok := reflect.struct_tag_lookup(field.tag, TAG_ARGS); ok {
  95. if _, is_hidden := get_struct_subtag(args_tag, SUBTAG_HIDDEN); is_hidden {
  96. // Hidden flags stay hidden.
  97. continue
  98. }
  99. if pos_str, is_pos := get_struct_subtag(args_tag, SUBTAG_POS); is_pos {
  100. flag.is_positional = true
  101. if pos, parse_ok := strconv.parse_u64_of_base(pos_str, 10); parse_ok {
  102. flag.pos = cast(int)pos
  103. }
  104. }
  105. if requirement, is_required := get_struct_subtag(args_tag, SUBTAG_REQUIRED); is_required {
  106. flag.is_required = true
  107. flag.required_min, flag.required_max, _ = parse_requirements(requirement)
  108. }
  109. if length_str, is_variadic := get_struct_subtag(args_tag, SUBTAG_VARIADIC); is_variadic {
  110. flag.is_variadic = true
  111. if length, parse_ok := strconv.parse_u64_of_base(length_str, 10); parse_ok {
  112. flag.variadic_length = cast(int)length
  113. }
  114. }
  115. }
  116. flag.name = get_field_name(field)
  117. flag.is_boolean = reflect.is_boolean(field.type)
  118. if usage, ok := reflect.struct_tag_lookup(field.tag, TAG_USAGE); ok {
  119. flag.usage = usage
  120. } else {
  121. flag.usage = UNDOCUMENTED_FLAG
  122. }
  123. #partial switch specific_type_info in field.type.variant {
  124. case runtime.Type_Info_Map:
  125. flag.type_description = fmt.tprintf("<%v>=<%v>%s",
  126. specific_type_info.key.id,
  127. specific_type_info.value.id,
  128. ", required" if flag.is_required else "")
  129. case runtime.Type_Info_Dynamic_Array:
  130. requirement_spec := describe_array_requirements(flag)
  131. if flag.is_variadic || flag.name == INTERNAL_VARIADIC_FLAG {
  132. if flag.variadic_length == 0 {
  133. flag.type_description = fmt.tprintf("<%v, ...>%s",
  134. specific_type_info.elem.id,
  135. requirement_spec)
  136. } else {
  137. flag.type_description = fmt.tprintf("<%v, %i at once>%s",
  138. specific_type_info.elem.id,
  139. flag.variadic_length,
  140. requirement_spec)
  141. }
  142. } else {
  143. flag.type_description = fmt.tprintf("<%v>%s", specific_type_info.elem.id,
  144. requirement_spec if len(requirement_spec) > 0 else ", multiple")
  145. }
  146. case:
  147. if flag.is_boolean {
  148. /*
  149. if flag.is_required {
  150. flag.type_description = ", required"
  151. }
  152. */
  153. } else {
  154. flag.type_description = fmt.tprintf("<%v>%s",
  155. field.type.id,
  156. ", required" if flag.is_required else "")
  157. }
  158. }
  159. if flag.name == INTERNAL_VARIADIC_FLAG {
  160. flag.full_length = len(flag.type_description)
  161. } else if flag.is_boolean {
  162. flag.full_length = len(flag_prefix) + len(flag.name) + len(flag.type_description)
  163. } else {
  164. flag.full_length = len(flag_prefix) + len(flag.name) + len(flag_assignment) + len(flag.type_description)
  165. }
  166. longest_flag_length = max(longest_flag_length, flag.full_length)
  167. append(&visible_flags, flag)
  168. }
  169. slice.sort_by_cmp(visible_flags[:], sort_flags)
  170. // All the flags have been figured out now.
  171. if len(program) > 0 {
  172. keep_it_short := len(visible_flags) >= ONE_LINE_FLAG_CUTOFF_COUNT
  173. strings.write_string(&builder, "Usage:\n\t")
  174. strings.write_string(&builder, program)
  175. for flag in visible_flags {
  176. if keep_it_short && !(flag.is_required || flag.is_positional || flag.name == INTERNAL_VARIADIC_FLAG) {
  177. continue
  178. }
  179. strings.write_byte(&builder, ' ')
  180. if flag.name == INTERNAL_VARIADIC_FLAG {
  181. strings.write_string(&builder, "...")
  182. continue
  183. }
  184. if !flag.is_required { strings.write_byte(&builder, '[') }
  185. if !flag.is_positional { strings.write_string(&builder, flag_prefix) }
  186. strings.write_string(&builder, flag.name)
  187. if !flag.is_required { strings.write_byte(&builder, ']') }
  188. }
  189. strings.write_byte(&builder, '\n')
  190. }
  191. if len(visible_flags) == 0 {
  192. // No visible flags. An unusual situation, but prevent any extra work.
  193. fmt.wprint(out, strings.to_string(builder))
  194. return
  195. }
  196. strings.write_string(&builder, "Flags:\n")
  197. // Divide the positional/required arguments and the non-required arguments.
  198. divider_index := -1
  199. for flag, i in visible_flags {
  200. if !flag.is_positional && !flag.is_required {
  201. divider_index = i
  202. break
  203. }
  204. }
  205. if divider_index == 0 {
  206. divider_index = -1
  207. }
  208. for flag, i in visible_flags {
  209. if i == divider_index {
  210. SPACING :: 2 // Number of spaces before the '|' from below.
  211. strings.write_byte(&builder, '\t')
  212. spacing := strings.repeat(" ", SPACING + longest_flag_length, context.temp_allocator)
  213. strings.write_string(&builder, spacing)
  214. strings.write_string(&builder, "|\n")
  215. }
  216. strings.write_byte(&builder, '\t')
  217. if flag.name == INTERNAL_VARIADIC_FLAG {
  218. strings.write_string(&builder, flag.type_description)
  219. } else {
  220. strings.write_string(&builder, flag_prefix)
  221. strings.write_string(&builder, flag.name)
  222. if !flag.is_boolean {
  223. strings.write_string(&builder, flag_assignment)
  224. }
  225. strings.write_string(&builder, flag.type_description)
  226. }
  227. if strings.contains_rune(flag.usage, '\n') {
  228. // Multi-line usage documentation. Let's make it look nice.
  229. usage_builder := strings.builder_make(context.temp_allocator)
  230. strings.write_byte(&usage_builder, '\n')
  231. iter := strings.trim_space(flag.usage)
  232. for line in strings.split_lines_iterator(&iter) {
  233. strings.write_string(&usage_builder, "\t\t")
  234. strings.write_string(&usage_builder, strings.trim_left_space(line))
  235. strings.write_byte(&usage_builder, '\n')
  236. }
  237. strings.write_string(&builder, strings.to_string(usage_builder))
  238. } else {
  239. // Single-line usage documentation.
  240. spacing := strings.repeat(" ",
  241. (longest_flag_length) - flag.full_length,
  242. context.temp_allocator)
  243. strings.write_string(&builder, spacing)
  244. strings.write_string(&builder, " | ")
  245. strings.write_string(&builder, flag.usage)
  246. strings.write_byte(&builder, '\n')
  247. }
  248. }
  249. fmt.wprint(out, strings.to_string(builder))
  250. }