util.odin 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130
  1. package flags
  2. import "core:fmt"
  3. @require import "core:os"
  4. @require import "core:path/filepath"
  5. import "core:strings"
  6. /*
  7. Parse any arguments into an annotated struct or exit if there was an error.
  8. *Allocates Using Provided Allocator*
  9. This is a convenience wrapper over `parse` and `print_errors`.
  10. Inputs:
  11. - model: A pointer to an annotated struct.
  12. - program_args: A slice of strings, usually `os.args`.
  13. - style: The argument parsing style.
  14. - allocator: (default: context.allocator)
  15. - loc: The caller location for debugging purposes (default: #caller_location)
  16. */
  17. @(optimization_mode="favor_size")
  18. parse_or_exit :: proc(
  19. model: ^$T,
  20. program_args: []string,
  21. style: Parsing_Style = .Odin,
  22. allocator := context.allocator,
  23. loc := #caller_location,
  24. ) {
  25. assert(len(program_args) > 0, "Program arguments slice is empty.", loc)
  26. program := filepath.base(program_args[0])
  27. args: []string
  28. if len(program_args) > 1 {
  29. args = program_args[1:]
  30. }
  31. error := parse(model, args, style)
  32. if error != nil {
  33. stderr := os.stream_from_handle(os.stderr)
  34. if len(args) == 0 {
  35. // No arguments entered, and there was an error; show the usage,
  36. // specifically on STDERR.
  37. write_usage(stderr, T, program, style)
  38. fmt.wprintln(stderr)
  39. }
  40. print_errors(T, error, program, style)
  41. _, was_help_request := error.(Help_Request)
  42. os.exit(0 if was_help_request else 1)
  43. }
  44. }
  45. /*
  46. Print out any errors that may have resulted from parsing.
  47. All error messages print to STDERR, while usage goes to STDOUT, if requested.
  48. Inputs:
  49. - data_type: The typeid of the data structure to describe, if usage is requested.
  50. - error: The error returned from `parse`.
  51. - style: The argument parsing style, required to show flags in the proper style, when usage is shown.
  52. */
  53. @(optimization_mode="favor_size")
  54. print_errors :: proc(data_type: typeid, error: Error, program: string, style: Parsing_Style = .Odin) {
  55. stderr := os.stream_from_handle(os.stderr)
  56. stdout := os.stream_from_handle(os.stdout)
  57. switch specific_error in error {
  58. case Parse_Error:
  59. fmt.wprintfln(stderr, "[%T.%v] %s", specific_error, specific_error.reason, specific_error.message)
  60. case Open_File_Error:
  61. fmt.wprintfln(stderr, "[%T#%i] Unable to open file with perms 0o%o in mode 0x%x: %s",
  62. specific_error,
  63. specific_error.errno,
  64. specific_error.perms,
  65. specific_error.mode,
  66. specific_error.filename)
  67. case Validation_Error:
  68. fmt.wprintfln(stderr, "[%T] %s", specific_error, specific_error.message)
  69. case Help_Request:
  70. write_usage(stdout, data_type, program, style)
  71. }
  72. }
  73. /*
  74. Get the value for a subtag.
  75. This is useful if you need to parse through the `args` tag for a struct field
  76. on a custom type setter or custom flag checker.
  77. Example:
  78. import "core:flags"
  79. import "core:fmt"
  80. subtag_example :: proc() {
  81. args_tag := "precision=3,signed"
  82. precision, has_precision := flags.get_subtag(args_tag, "precision")
  83. signed, is_signed := flags.get_subtag(args_tag, "signed")
  84. fmt.printfln("precision = %q, %t", precision, has_precision)
  85. fmt.printfln("signed = %q, %t", signed, is_signed)
  86. }
  87. Output:
  88. precision = "3", true
  89. signed = "", true
  90. */
  91. get_subtag :: proc(tag, id: string) -> (value: string, ok: bool) {
  92. // This proc was initially private in `internal_rtti.odin`, but given how
  93. // useful it would be to custom type setters and flag checkers, it lives
  94. // here now.
  95. tag := tag
  96. for subtag in strings.split_iterator(&tag, ",") {
  97. if equals := strings.index_byte(subtag, '='); equals != -1 && id == subtag[:equals] {
  98. return subtag[1 + equals:], true
  99. } else if id == subtag {
  100. return "", true
  101. }
  102. }
  103. return
  104. }