import macros, tables, os, strutils const version = "1.1.1" ## .. include:: header.md # generated by `make` using README.md # Labels used to name generated identifiers. None of them should bleed into parent scope, # but having them as const makes for more reliable usage. const options_type_label = "Simple_Parseopt_Options" supplied_type_label = "Simple_Parseopt_Supplied" options_instance_label = "simple_parseopt_options" supplied_instance_label = "simple_parseopt_supplied" field_names_label = "simple_parseopt_field_names" field_offsets_label = "simple_parseopt_field_offset" field_supplied_label = "simple_parseopt_field_supplied_offset" field_types_label = "simple_parseopt_field_type" field_default_values_label = "simple_parseopt_field_defaults" field_lengths_label = "simple_parseopt_field_lengths" field_descriptions_label = "simple_parseopt_field_descriptions" bare_fields_label = "simple_parseopt_field_bare_indexes" required_fields_label = "simple_parseopt_field_requried" field_alias_names_label = "simple_parseopt_field_alias_names" field_alias_indexes_label = "simple_parseopt_field_alias_indexes" proc_label = "simple_parseopt_parse" # Config variables, exposed via succeeding procs. var dash_denotes_param = true slash_denotes_param = true use_double_dash = false double_dash_separator = false split_on_colon = false split_on_equals = false implicit_bare = true quit_on_error = true automatic_help = true parameters_are_unique = true bare_can_still_be_named = false program_name = "" help_text_pre = "" help_text_post = "" macro config*(body: untyped): untyped = ## Helper macro to let you easily specify several config options. ## ## Example: ## ## `simple_parseopt.config: no_slash.dash_dash_parameters.allow_repetition` ## proc check_for_help_text(node: Nim_Node): Nim_Node = if node.kind == nnk_ident and (node.str_val == "help_text" or node.str_val == "command_name"): return node elif node.len > 0: for child in node.children: var help_node = check_for_help_text(child) if help_node != nil: return help_node return nil let help_node = check_for_help_text(body) if help_node != nil: error("Cannot include `help_text` or `command_name` in config chain", help_node) body.expect_kind(nnk_stmt_list) if body.len != 1: error("Expected dot expression", body) result = new_nim_node(nnk_stmt_list) if body[0].kind == nnk_ident: result.add nnk_call.new_tree(body[0].copy_nim_node) else: body[0].expect_kind(nnk_dot_expr) proc walk(node: Nim_Node, write: Nim_Node, count = 0) = if node.len == 2 and node[1].kind == nnk_ident: if node[0].kind == nnk_dot_expr: walk(node[0], write, count+1) write.add nnk_call.new_tree(node[1].copy_nim_node) elif node[0].kind == nnk_ident: write.add nnk_call.new_tree(node[0].copy_nim_node) write.add nnk_call.new_tree(node[1].copy_nim_node) else: error("Expected dot expression", node) else: error("Expected dot expression", node) walk body[0], result proc no_dash*() = ## Disable parameter being identified by prefixing with `-` dash_denotes_param = false proc no_slash*() = ## Disable parameter being identified by prefixing with `/` slash_denotes_param = false proc dash_dash_parameters*() = ## Require that parameters which have more than one character in their name ## be prefixed with `--` instead of `-`. ## ## Single-character parameters may then be entered grouped together under ## one `-` use_double_dash = true proc dash_dash_separator*() = ## `--` on its own in the command line will disable parameter names on every ## argument after it; they will all be treated as bare. double_dash_separator = true proc value_after_colon*() = ## Allow the user to specify parameter & value together, separated by a `:` ## ## e.g. `-param:value` ## ## Note this will not play nicely with quoted string values. split_on_colon = true proc value_after_equals*() = ## Allow the user to specify parameter & value together, separated by a `=` ## ## e.g. `-param=value` ## ## Note this will not play nicely with quoted string values. split_on_equals = true proc allow_repetition*() = ## Allow the user to specify the same parameter more than once without ## reporting an error. parameters_are_unique = false proc allow_errors*() = ## Allow program execution to continue after erroneous input. quit_on_error = true proc no_implicit_bare*() = ## Do not automatically use the last `seq[string]` parameter to gather any ## bare parameters the user enters (instead they become erroneous) implicit_bare = false proc can_name_bare*() = ## Allows user to set bare parameters by name. bare_can_still_be_named = true proc manual_help*() = ## Disable automatic generation of help message when user enters ## `-?`, `-h` or `-help` (when you do not include them as parameters) automatic_help = false proc command_name*(name: string) = ## Set the name of the executable, for use in the auto-generated ## help-message when the user enters `-?`, `-h`, or `-help`. ## ## Note: `command_name` may not be included in a `config:` chain program_name = name proc help_text*(text, footer = "") = ## Set the text which is included in the auto-generated help-message ## when the user enters `-?`, `-h`, or `-help`. ## ## `text` is displayed at the top, before the parameters. ## `footer` is displayed at the bottom, after them. ## ## Note: `help_text` may not be included in a `config:` chain help_text_pre = text help_text_post = footer # Rename echo so when we want to clean up after echo debugging we can easily find # all instances. template print(s: string) = echo s # Data structures for holding details on each parameter, and procs to manipulate them. type Param_Kind = enum param_undefined, param_int, param_i8, param_i16, param_i32, param_i64, param_uint, param_u8, param_u16, param_u32, param_u64, param_float, param_f32, param_f64, param_char, param_string, param_bool, param_seq_int, param_seq_i8, param_seq_i16, param_seq_i32, param_seq_i64, param_seq_uint, param_seq_u8, param_seq_u16, param_seq_u32, param_seq_u64, param_seq_float, param_seq_f32, param_seq_f64, param_seq_char, param_seq_string type Param = object name: string accepts_bare: bool description: string alias: seq[string] required: bool seq_len: int case kind: Param_Kind of param_undefined: discard of param_int: int_value: int of param_i8: i8_value: int8 of param_i16: i16_value: int16 of param_i32: i32_value: int32 of param_i64: i64_value: int64 of param_uint: uint_value: uint of param_u8: u8_value: uint8 of param_u16: u16_value: uint16 of param_u32: u32_value: uint32 of param_u64: u64_value: uint64 of param_float: float_value: float of param_f32: f32_value: float32 of param_f64: f64_value: float64 of param_char: char_value: char of param_string: string_value: string of param_bool: bool_value: bool of param_seq_int: seq_int_value: seq[int] of param_seq_i8: seq_i8_value: seq[int8] of param_seq_i16: seq_i16_value: seq[int16] of param_seq_i32: seq_i32_value: seq[int32] of param_seq_i64: seq_i64_value: seq[int64] of param_seq_uint: seq_uint_value: seq[uint] of param_seq_u8: seq_u8_value: seq[uint8] of param_seq_u16: seq_u16_value: seq[uint16] of param_seq_u32: seq_u32_value: seq[uint32] of param_seq_u64: seq_u64_value: seq[uint64] of param_seq_float: seq_float_value: seq[float] of param_seq_f32: seq_f32_value: seq[float32] of param_seq_f64: seq_f64_value: seq[float64] of param_seq_char: seq_char_value: seq[char] of param_seq_string: seq_string_value: seq[string] # int versions of Param_Kind, so that we don't need to export it to enclosing scope. const int_param_undefined = 0 int_param_int = 1 int_param_i8 = 2 int_param_i16 = 3 int_param_i32 = 4 int_param_i64 = 5 int_param_uint = 6 int_param_u8 = 7 int_param_u16 = 8 int_param_u32 = 9 int_param_u64 = 10 int_param_float = 11 int_param_f32 = 12 int_param_f64 = 13 int_param_char = 14 int_param_string = 15 int_param_bool = 16 int_param_seq_int = 17 int_param_seq_i8 = 18 int_param_seq_i16 = 19 int_param_seq_i32 = 20 int_param_seq_i64 = 21 int_param_seq_uint = 22 int_param_seq_u8 = 23 int_param_seq_u16 = 24 int_param_seq_u32 = 25 int_param_seq_u64 = 26 int_param_seq_float = 27 int_param_seq_f32 = 28 int_param_seq_f64 = 29 int_param_seq_char = 30 int_param_seq_string = 31 first_seq_int = 17 proc is_numeric(int_param: int): bool = case int_param: of int_param_undefined: return false of int_param_char: return false of int_param_string: return false of int_param_bool: return false of int_param_seq_char: return false of int_param_seq_string: return false else: return true proc int_param_from_param(kind: Param_Kind): int = case kind: of param_undefined: return int_param_undefined of param_int: return int_param_int of param_i8: return int_param_i8 of param_i16: return int_param_i16 of param_i32: return int_param_i32 of param_i64: return int_param_i64 of param_uint: return int_param_uint of param_u8: return int_param_u8 of param_u16: return int_param_u16 of param_u32: return int_param_u32 of param_u64: return int_param_u64 of param_float: return int_param_float of param_f32: return int_param_f32 of param_f64: return int_param_f64 of param_char: return int_param_char of param_string: return int_param_string of param_bool: return int_param_bool of param_seq_int: return int_param_seq_int of param_seq_i8: return int_param_seq_i8 of param_seq_i16: return int_param_seq_i16 of param_seq_i32: return int_param_seq_i32 of param_seq_i64: return int_param_seq_i64 of param_seq_uint: return int_param_seq_uint of param_seq_u8: return int_param_seq_u8 of param_seq_u16: return int_param_seq_u16 of param_seq_u32: return int_param_seq_u32 of param_seq_u64: return int_param_seq_u64 of param_seq_float: return int_param_seq_float of param_seq_f32: return int_param_seq_f32 of param_seq_f64: return int_param_seq_f64 of param_seq_char: return int_param_seq_char of param_seq_string: return int_param_seq_string proc param_from_nodes(name_node: Nim_Node, kind: Param_Kind, value_node: Nim_Node, pragma_node: Nim_Node): (Param, string) = let name = name_node.str_val var param: Param var sign = +1; var float_sign = +1.0; var node = value_node; if node.kind == nnk_prefix: if node[0].kind == nnk_ident and node[0].str_val == "-": sign = -1; float_sign = -1; node = node[1]; if node == nil: case kind: of param_undefined: param = Param(name: name, kind: param_undefined) of param_int: param = Param(name: name, kind: param_int) of param_i8: param = Param(name: name, kind: param_i8) of param_i16: param = Param(name: name, kind: param_i16) of param_i32: param = Param(name: name, kind: param_i32) of param_i64: param = Param(name: name, kind: param_i64) of param_uint: param = Param(name: name, kind: param_uint) of param_u8: param = Param(name: name, kind: param_u8) of param_u16: param = Param(name: name, kind: param_u16) of param_u32: param = Param(name: name, kind: param_u32) of param_u64: param = Param(name: name, kind: param_u64) of param_float: param = Param(name: name, kind: param_float) of param_f32: param = Param(name: name, kind: param_f32) of param_f64: param = Param(name: name, kind: param_f64) of param_char: param = Param(name: name, kind: param_char) of param_string: param = Param(name: name, kind: param_string) of param_bool: param = Param(name: name, kind: param_bool) of param_seq_int: param = Param(name: name, kind: param_seq_int) of param_seq_i8: param = Param(name: name, kind: param_seq_i8) of param_seq_i16: param = Param(name: name, kind: param_seq_i16) of param_seq_i32: param = Param(name: name, kind: param_seq_i32) of param_seq_i64: param = Param(name: name, kind: param_seq_i64) of param_seq_uint: param = Param(name: name, kind: param_seq_uint) of param_seq_u8: param = Param(name: name, kind: param_seq_u8) of param_seq_u16: param = Param(name: name, kind: param_seq_u16) of param_seq_u32: param = Param(name: name, kind: param_seq_u32) of param_seq_u64: param = Param(name: name, kind: param_seq_u64) of param_seq_float: param = Param(name: name, kind: param_seq_float) of param_seq_f32: param = Param(name: name, kind: param_seq_f32) of param_seq_f64: param = Param(name: name, kind: param_seq_f64) of param_seq_char: param = Param(name: name, kind: param_seq_char) of param_seq_string: param = Param(name: name, kind: param_seq_string) else: case kind: of param_undefined: param = Param(name: name, kind: param_undefined) of param_int: param = Param(name: name, kind: param_int, int_value: cast[int](node.int_val * sign)) of param_i8: param = Param(name: name, kind: param_i8, i8_value: cast[int8](node.int_val * sign)) of param_i16: param = Param(name: name, kind: param_i16, i16_value: cast[int16](node.int_val * sign)) of param_i32: param = Param(name: name, kind: param_i32, i32_value: cast[int32](node.int_val * sign)) of param_i64: param = Param(name: name, kind: param_i64, i64_value: node.int_val * sign) of param_uint: param = Param(name: name, kind: param_uint, uint_value: cast[uint](node.int_val)) of param_u8: param = Param(name: name, kind: param_u8, u8_value: cast[uint8](node.int_val)) of param_u16: param = Param(name: name, kind: param_u16, u16_value: cast[uint16](node.int_val)) of param_u32: param = Param(name: name, kind: param_u32, u32_value: cast[uint32](node.int_val)) of param_u64: param = Param(name: name, kind: param_u64, u64_value: cast[uint64](node.int_val)) of param_float: param = Param(name: name, kind: param_float, float_value: node.float_val * float_sign) of param_f32: param = Param(name: name, kind: param_f32, f32_value: node.float_val * float_sign) of param_f64: param = Param(name: name, kind: param_f64, f64_value: node.float_val * float_sign) of param_char: param = Param(name: name, kind: param_char, char_value: cast[char](node.int_val)) of param_string: param = Param(name: name, kind: param_string, string_value: node.str_val) of param_bool: param = Param(name: name, kind: param_bool, bool_value: node.str_val == "true") of param_seq_int: param = Param(name: name, kind: param_seq_int, seq_int_value: @[]) of param_seq_i8: param = Param(name: name, kind: param_seq_i8, seq_i8_value: @[]) of param_seq_i16: param = Param(name: name, kind: param_seq_i16, seq_i16_value: @[]) of param_seq_i32: param = Param(name: name, kind: param_seq_i32, seq_i32_value: @[]) of param_seq_i64: param = Param(name: name, kind: param_seq_i64, seq_i64_value: @[]) of param_seq_uint: param = Param(name: name, kind: param_seq_uint, seq_uint_value: @[]) of param_seq_u8: param = Param(name: name, kind: param_seq_u8, seq_u8_value: @[]) of param_seq_u16: param = Param(name: name, kind: param_seq_u16, seq_u16_value: @[]) of param_seq_u32: param = Param(name: name, kind: param_seq_u32, seq_u32_value: @[]) of param_seq_u64: param = Param(name: name, kind: param_seq_u64, seq_u64_value: @[]) of param_seq_float: param = Param(name: name, kind: param_seq_float, seq_float_value: @[]) of param_seq_f32: param = Param(name: name, kind: param_seq_f32, seq_f32_value: @[]) of param_seq_f64: param = Param(name: name, kind: param_seq_f64, seq_f64_value: @[]) of param_seq_char: param = Param(name: name, kind: param_seq_char, seq_char_value: @[]) of param_seq_string: param = Param(name: name, kind: param_seq_string, seq_string_value: @[]) if pragma_node != nil: if pragma_node.kind == nnk_ident: if pragma_node.str_val == "bare" or pragma_node.str_val == "positional": param.accepts_bare = true elif pragma_node.str_val == "need" or pragma_node.str_val == "required": param.required = true else: error("invalid pragma", pragma_node) else: for child in pragma_node.children: if child.kind == nnk_ident: if child.str_val == "bare" or child.str_val == "positional": param.accepts_bare = true elif child.str_val == "need" or child.str_val == "required": param.required = true else: error("invalid pragma", child) elif child.kind != nnk_call or len(child) < 2 or child[0].kind != nnk_ident: error("invalid pragma", child) elif child[0].str_val == "aka" or child[0].str_val == "alias": if child[0].kind != nnk_ident or child.len < 2: error("invalid pragma", child) param.alias = @[] for i in 1 ..< child.len: let alias = child[i] if alias.kind != nnk_str_lit: error("invalid pragma", alias) else: param.alias.add alias.str_val elif child[0].str_val == "info" or child[0].str_val == "description": if child[1].kind != nnk_str_lit: error("invalid pragma", child[1]) else: param.description = child[1].str_val elif child[0].str_val == "len" or child[0].str_val == "count": if child[1].kind != nnk_int_lit: error("invalid pragma", child[1]) else: param.seq_len = cast[int](child[1].int_val) else: error("invalid pragma", pragma_node) return (param, name) proc kind_from_lit(lit: Nim_Node): Param_Kind = case lit.kind of nnk_prefix: return kind_from_lit(lit[1]) of nnk_int_lit: return param_int of nnk_float_lit: return param_float of nnk_char_lit: return param_char of nnk_str_lit: return param_string of nnk_ident: if lit.str_val == "true" or lit.str_val == "false": return param_bool else: return param_undefined else: return param_undefined proc ident_from_kind(kind: Param_Kind): Nim_Node = case kind of param_undefined: return nil of param_int: return ident("int") of param_i8: return ident("int8") of param_i16: return ident("int16") of param_i32: return ident("int32") of param_i64: return ident("int64") of param_uint: return ident("uint") of param_u8: return ident("uint8") of param_u16: return ident("uint16") of param_u32: return ident("uint32") of param_u64: return ident("uint64") of param_float: return ident("float") of param_f32: return ident("float32") of param_f64: return ident("float64") of param_char: return ident("char") of param_string: return ident("string") of param_bool: return ident("bool") of param_seq_int: return nnk_bracket_expr.new_tree(ident("seq"), ident("int")) of param_seq_i8: return nnk_bracket_expr.new_tree(ident("seq"), ident("int8")) of param_seq_i16: return nnk_bracket_expr.new_tree(ident("seq"), ident("int16")) of param_seq_i32: return nnk_bracket_expr.new_tree(ident("seq"), ident("int32")) of param_seq_i64: return nnk_bracket_expr.new_tree(ident("seq"), ident("int64")) of param_seq_uint: return nnk_bracket_expr.new_tree(ident("seq"), ident("uint")) of param_seq_u8: return nnk_bracket_expr.new_tree(ident("seq"), ident("uint8")) of param_seq_u16: return nnk_bracket_expr.new_tree(ident("seq"), ident("uint16")) of param_seq_u32: return nnk_bracket_expr.new_tree(ident("seq"), ident("uint32")) of param_seq_u64: return nnk_bracket_expr.new_tree(ident("seq"), ident("uint64")) of param_seq_float: return nnk_bracket_expr.new_tree(ident("seq"), ident("float")) of param_seq_f32: return nnk_bracket_expr.new_tree(ident("seq"), ident("float32")) of param_seq_f64: return nnk_bracket_expr.new_tree(ident("seq"), ident("float64")) of param_seq_char: return nnk_bracket_expr.new_tree(ident("seq"), ident("char")) of param_seq_string: return nnk_bracket_expr.new_tree(ident("seq"), ident("string")) proc kind_from_ident(ident: Nim_node): Param_Kind = if ident.kind == nnk_dot_expr and ident.len == 2 and ident[0].kind == nnk_ident and ident[0].str_val == "seq" and ident[1].kind == nnk_ident: case ident[1].str_val: of "int": return param_seq_int of "int8": return param_seq_i8 of "int16": return param_seq_i16 of "int32": return param_seq_i32 of "int64": return param_seq_i64 of "uint": return param_seq_uint of "uint8": return param_seq_u8 of "uint16": return param_seq_u16 of "uint32": return param_seq_u32 of "uint64": return param_seq_u64 of "float": return param_seq_float of "float32": return param_seq_f32 of "float64": return param_seq_f64 of "char": return param_seq_char of "string": return param_seq_string of "bool": error("Cannot use seq[bool]") else: return param_undefined else: case ident.str_val of "int": return param_int of "int8": return param_i8 of "int16": return param_i16 of "int32": return param_i32 of "int64": return param_i64 of "uint": return param_uint of "uint8": return param_u8 of "uint16": return param_u16 of "uint32": return param_u32 of "uint64": return param_u64 of "float": return param_float of "float32": return param_f32 of "float64": return param_f64 of "char": return param_char of "string": return param_string of "true": return param_bool of "false": return param_bool of "bool": return param_bool else: return param_undefined proc seq_kind_from_ident(ident: Nim_node): Param_Kind = case ident.str_val of "int": return param_seq_int of "int8": return param_seq_i8 of "int16": return param_seq_i16 of "int32": return param_seq_i32 of "int64": return param_seq_i64 of "uint": return param_seq_uint of "uint8": return param_seq_u8 of "uint16": return param_seq_u16 of "uint32": return param_seq_u32 of "uint64": return param_seq_u64 of "float": return param_seq_float of "float32": return param_seq_f32 of "float64": return param_seq_f64 of "char": return param_seq_char of "string": return param_seq_string of "bool": error("Cannot use seq[bool]") else: return param_undefined proc value_node_from_param(param: Param): Nim_Node = case param.kind: of param_undefined: return nil of param_int: return new_int_lit_node(param.int_value) of param_i8: return new_int_lit_node(param.i8_value) of param_i16: return new_int_lit_node(param.i16_value) of param_i32: return new_int_lit_node(param.i32_value) of param_i64: return new_int_lit_node(param.i64_value) of param_uint: return new_int_lit_node(cast[Biggest_Int](param.uint_value)) of param_u8: return new_int_lit_node(cast[Biggest_Int](param.u8_value)) of param_u16: return new_int_lit_node(cast[Biggest_Int](param.u16_value)) of param_u32: return new_int_lit_node(cast[Biggest_Int](param.u32_value)) of param_u64: return new_int_lit_node(cast[Biggest_Int](param.u64_value)) of param_float: return new_float_lit_node(param.float_value) of param_f32: return new_float_lit_node(param.f32_value) of param_f64: return new_float_lit_node(param.f64_value) of param_char: return new_lit(param.char_value) of param_string: return new_str_lit_node(param.string_value) of param_bool: if param.bool_value: return ident("true") else: return ident("false") else: # seq return nil proc string_from_param(param: Param): string = case param.kind: of param_undefined: return "" of param_int: return repr(param.int_value) of param_i8: return repr(param.i8_value) of param_i16: return repr(param.i16_value) of param_i32: return repr(param.i32_value) of param_i64: return repr(param.i64_value) of param_uint: return repr(param.uint_value) of param_u8: return repr(param.u8_value) of param_u16: return repr(param.u16_value) of param_u32: return repr(param.u32_value) of param_u64: return repr(param.u64_value) of param_float: return repr(param.float_value) of param_f32: return repr(param.f32_value) of param_f64: return repr(param.f64_value) of param_char: return repr(param.char_value) of param_string: return param.string_value of param_bool: if param.bool_value: return "true" else: return "false" else: # seq return "" # Field declaration, parsed from block nodes. type Declaration = tuple kind: Param_Kind name_node: Nim_Node value_node: Nim_Node pragma_node: Nim_Node error: int # purely for internal debugging proc declaration_from_node(node: Nim_Node): Declaration = if node.kind == nnk_asgn: if node.len != 2 or node[0].kind != nnk_ident: # error 1 return (param_undefined, nil, nil, nil, 1) elif node[1].kind == nnk_pragma_expr and len(node[1]) == 2 and node[1][1].kind == nnk_pragma: # x = n {.pragma.} return (kind_from_lit(node[1][0]), node[0], node[1][0], node[1][1], 0) else: # x = n return (kind_from_lit(node[1]), node[0], node[1], nil, 0) elif node.kind == nnk_call: if node.len != 2 or node[0].kind != nnk_ident or node[1].kind != nnk_stmt_list: # error 2 return (param_undefined, nil, nil, nil, 2) elif node[1].len == 1 and node[1][0].kind == nnk_bracket_expr and node[1][0].len == 2 and # x:seq[t] node[1][0][0].kind == nnk_ident and node[1][0][0].str_val == "seq" and node[1][0][1].kind == nnk_ident: return (seq_kind_from_ident(node[1][0][1]), node[0], nil, nil, 0) elif node[1].len == 1 and node[1][0].kind == nnk_pragma_expr and # x:seq[t] {.pragma.} node[1][0][0].kind == nnk_bracket_expr and node[1][0][0].len == 2 and node[1][0][0][0].kind == nnk_ident and node[1][0][0][0].str_val == "seq" and node[1][0][0][1].kind == nnk_ident: return (seq_kind_from_ident(node[1][0][0][1]), node[0], nil, node[1][0][1], 0) elif node[1].len == 1 and node[1][0].kind == nnk_ident: # x:t return (kind_from_ident(node[1][0]), node[0], nil, nil, 0) elif node[1].len == 1 and node[1][0].kind == nnk_pragma_expr: # x:t {.pragma.} return (kind_from_ident(node[1][0][0]), node[0], nil, node[1][0][1], 0) elif node[1].len != 1 or node[1][0].kind != nnk_asgn: # error 3 return (param_undefined, nil, nil, nil, 3) elif node[1][0].len != 2 or node[1][0][0].kind != nnk_ident: # error 4 return (param_undefined, nil, nil, nil, 4) elif node[1][0].len == 2 and node[1][0][1].kind == nnk_pragma_expr: # x:t = n {.pragma.} return (kind_from_ident(node[1][0][0]), node[0], node[1][0][1][0], node[1][0][1][1], 0) else: # x:t = n return (kind_from_ident(node[1][0][0]), node[0], node[1][0][1], nil, 0) # Do all the work! macro get_options_and_supplied*(body: untyped): untyped = ## Parses the command-line arguments provided by the user, ## using it and the code block to fill out an object's fields. ## ## Returns a tuple of two objects: the first as detailed by ## the code block, the second a mirror of it, but all of type `bool`. ## For any parameters which the user has supplied on the command line, ## the field on the second object will be set to `true`. ## ## ## All basic intrinsic types are supported: ## `string`, `char`, `bool`, `int`, `uint`, `float` ## (and all size variants: `int16`, `float32`, etc.) ## ## The block is written as if it were a `var`, i.e. all of these are valid: ## ``` ## x = 0 ## x:int32 ## x:int = 20 ## ``` ## You may also use any `seq` of the above: `seq[string]`, `seq[int]`, ## `seq[float]`, etc. These may not be initialized to a default ## value, however: they must be empty. ## ``` ## args: seq[string] ## ``` ## If you do not explicitly specify a `{. bare .}` `seq[string]`, but do ## have one or more `seq[string]`, then the last one will be used to store ## all bare arguments. (i.e. will be treated as if it had an implicit ## `{. bare .}` pragma.) ## This may be disabled with the `no_implicit_bare` setting. ## ## ## You may also add pragmas to the end of any line to modify parameter ## behaviour. Each pragma has a more verbose alias, if you prefer that ## style of code. ## ## `{. info("text") .}` or `{. description("text") .}` ## Description of the parameter shown in help text. ## ## ## `{. aka("a", "b", ...) .}` or `{. alias("a", "b", ...) .}` ## Aliases for the parameter - user may use these as parameters; ## they will write to the variable. ## ## ## `{. bare .}` or `{. positional .}` ## Accepts a bare, positional argument (an argument which has not ## been prefixed with a parameter name). User will not be able to ## refer to the argument with its parameter name. ## ## ## `{. need .}` or `.{ required .}` ## Parameter must be supplied by user or an error is shown. ## ## ## `{. len(i) .}` or `.{ count(i) .}` ## Place on a `seq` field to require that many values be supplied to it. ## For example: ## `position:seq[float] {. len(3) .} # x y z` ## ## Note that this does not set a limit on the total length of the seq, ## only on how many values the user must specify. Using `len` in ## conjunction with the `allow_repetition` setting, you can accept ## multiple batches of values (see `normalize.nim` example) ## ## ## *Example:* ## ``` ## let options, supplied = get_options_and_supplied: ## teenager = "Joe Random" {. aka("name", "n") .} ## age[int8] = 13 ## nin:string {. info("National Insurance Number") .} ## arguments:seq[string] ## ## if not supplied.nin: ## report "Must supply NIN" ## quit(1) ## ## if options.age < 13 or options.age > 19: ## report "Not a teenager!" ## quit(1) ## ``` # Parse block and generate params data if body.kind != nnk_stmt_list: error("Block expected, e.g. var opts = parse_params: ...", body) var params = init_table[string, Param]() var params_in_order: seq[string] = @[] var aliases:seq[string] = @[] for node in body.children: let (kind, name_node, value_node, pragma_node, err) = declaration_from_node(node) if kind == param_undefined: error("Expected declaration, e.g. x = 1 or x:int = 1 or x:int [ERR:" & err.repr & "]", node) let (param, name) = param_from_nodes(name_node, kind, value_node, pragma_node) for alias in param.alias: if alias in params or alias in aliases: error("Duplicate parameter: " & alias, node) aliases.add alias if name in params or name in aliases: error("Duplicate parameter: " & name, node) params[name] = param params_in_order.add(name) # Generate AST nodes proc new_seq_node(label: string, kind: string): Nim_Node = result = new_nim_node(nnk_var_section) result.add( nnk_ident_defs.new_tree( ident(label), new_empty_node(), nnk_call.new_tree( nnk_bracket_expr.new_tree( ident("new_seq"), ident(kind))))) let field_names_node = new_seq_node(field_names_label, "string") let field_offsets_node = new_seq_node(field_offsets_label, "uint") let field_supplied_node = new_seq_node(field_supplied_label, "uint") let field_types_node = new_seq_node(field_types_label, "int") let field_default_values_node = new_seq_node(field_default_values_label, "string") let field_lengths_node = new_seq_node(field_lengths_label, "int") let field_descriptions_node = new_seq_node(field_descriptions_label, "string") let field_alias_names_node = new_seq_node(field_alias_names_label, "string") let field_alias_indexes_node = new_seq_node(field_alias_indexes_label, "int") let bare_fields_node = new_seq_node(bare_fields_label, "int") let required_fields_node = new_seq_node(required_fields_label, "int") let options_type_root_node = nnk_type_section.new_tree( nnk_type_def.new_tree( ident(options_type_label), new_empty_node(), nnk_object_ty.new_tree( new_empty_node(), new_empty_node(), new_nim_node(nnk_rec_list)))) var options_type_node = options_type_root_node[0][2][2] let supplied_type_root_node = nnk_type_section.new_tree( nnk_type_def.new_tree( ident(supplied_type_label), new_empty_node(), nnk_object_ty.new_tree( new_empty_node(), new_empty_node(), new_nim_node(nnk_rec_list)))) var supplied_type_node = supplied_type_root_node[0][2][2] let options_root_node = nnk_var_section.new_tree( nnk_ident_defs.new_tree( ident(options_instance_label), new_empty_node(), nnk_obj_constr.new_tree( ident(options_type_label) ))) var options_node = options_root_node[0][2] let supplied_root_node = nnk_var_section.new_tree( nnk_ident_defs.new_tree( ident(supplied_instance_label), new_empty_node(), nnk_obj_constr.new_tree( ident(supplied_type_label) ))) let assignments_root_node = nnk_block_stmt.new_tree( new_empty_node(), new_nim_node(nnk_stmt_list)) var assignments_node = assignments_root_node[1] let call_node = nnk_call.new_tree( ident(proc_label), ident(options_instance_label), ident(supplied_instance_label)) let info_node = new_nim_node(nnk_var_section) # Set up identifiers to access generated variables let proc_name = ident(proc_label) let type_name = ident(options_type_label) let supplied_type_name = ident(supplied_type_label) let field_names = ident(field_names_label) let field_offset = ident(field_offsets_label) let field_supplied_offset = ident(field_supplied_label) let field_type = ident(field_types_label) #let field_default_values = ident(field_default_values_label) # Could be used to display defaults in do_help let bare_indexes = ident(bare_fields_label) let field_descriptions = ident(field_descriptions_label) let required_indexes = ident(required_fields_label) let field_length = ident(field_lengths_label) let alias_names = ident(field_alias_names_label) let alias_index = ident(field_alias_indexes_label) proc int_from_string[T](s: string): (T, bool) = try: result = (cast[T](parse_biggest_int(s)), true) except: return (cast[T](0), false) proc uint_from_string[T](s: string): (T, bool) = try: result = (cast[T](parse_biggest_uint(s)), true) except: return (cast[T](0), false) proc float_from_string[T](s: string): (T, bool) = try: result = (cast[T](parse_float(s)), true) except: return (cast[T](0), false) proc can_parse_as(value: string, kind: int): bool = case kind of int_param_undefined: return false of int_param_string: return true of int_param_bool: return false of int_param_int: return int_from_string[int](value)[1] of int_param_i8: return int_from_string[int](value)[1] of int_param_i16: return int_from_string[int](value)[1] of int_param_i32: return int_from_string[int](value)[1] of int_param_i64: return int_from_string[int](value)[1] of int_param_uint: return uint_from_string[int](value)[1] of int_param_u8: return uint_from_string[int](value)[1] of int_param_u16: return uint_from_string[int](value)[1] of int_param_u32: return uint_from_string[int](value)[1] of int_param_u64: return uint_from_string[int](value)[1] of int_param_float: return float_from_string[int](value)[1] of int_param_f32: return float_from_string[int](value)[1] of int_param_f64: return float_from_string[int](value)[1] of int_param_char: return value.len == 1 of int_param_seq_string: return true of int_param_seq_int: return int_from_string[int](value)[1] of int_param_seq_i8: return int_from_string[int](value)[1] of int_param_seq_i16: return int_from_string[int](value)[1] of int_param_seq_i32: return int_from_string[int](value)[1] of int_param_seq_i64: return int_from_string[int](value)[1] of int_param_seq_uint: return uint_from_string[int](value)[1] of int_param_seq_u8: return uint_from_string[int](value)[1] of int_param_seq_u16: return uint_from_string[int](value)[1] of int_param_seq_u32: return uint_from_string[int](value)[1] of int_param_seq_u64: return uint_from_string[int](value)[1] of int_param_seq_float: return float_from_string[int](value)[1] of int_param_seq_f32: return float_from_string[int](value)[1] of int_param_seq_f64: return float_from_string[int](value)[1] of int_param_seq_char: return value.len == 1 else: return false # Generate code which assigns values to fields at runtime (from supplied command line) var proc_node = quote do: template parse_error(err: string) = print err if quit_on_error: quit(1) # Helper functions to get and set fields inside generated object. # A lot of this would be cleaner using tables, but as this proc is # called by the enclosing scope, that would require exporting # table, or having the user import it. We want to be clean, # so we will stick to just using Seq's proc index_from_name(name: string): int = for (i, n) in `field_names`.pairs: if n == name: return i for (i, n) in `alias_names`.pairs: if n == name: return `alias_index`[i] parse_error("Cannot find option: " & name) proc get_param_offset(name: string): uint = return `field_offset`[index_from_name(name)] proc get_param_supplied_offset(name: string): uint = return `field_supplied_offset`[index_from_name(name)] proc get_param_type(name: string): int = return `field_type`[index_from_name(name)] proc get_desired_seq_len(name: string): int = return `field_length`[index_from_name(name)] proc is_bare(index: int): bool = `bare_indexes`.contains(index) proc is_bare(name: string): bool = is_bare(index_from_name(name)) proc is_seq(kind: int): bool = return kind >= first_seq_int proc is_seq(name: string): bool = return is_seq(get_param_type(name)) template get_prefix(name: string, index = -1): string = if not bare_can_still_be_named and index >= 0 and is_bare(index): "" elif not bare_can_still_be_named and index >= 0 and is_bare(name): "" elif dash_denotes_param: if use_double_dash and name.len > 1: "--" else: "-" elif slash_denotes_param: "/" else: "" proc is_supplied(address: ptr `supplied_type_name`, name: string): bool = let field = cast[ptr bool](cast[uint](address) + get_param_supplied_offset(name)) return field[] proc set_supplied(address: ptr `supplied_type_name`, name: string) = let field = cast[ptr bool](cast[uint](address) + get_param_supplied_offset(name)) field[] = true proc get_seq_len(address: ptr `type_name`, name: string, kind: int): int = template return_length(T) = let field = cast[ptr seq[T]](cast[uint](address) + get_param_offset(name)) return field[].len case kind of `int_param_seq_int`: return_length int of `int_param_seq_i8`: return_length int8 of `int_param_seq_i16`: return_length int16 of `int_param_seq_i32`: return_length int32 of `int_param_seq_i64`: return_length int64 of `int_param_seq_uint`: return_length uint of `int_param_seq_u8`: return_length uint8 of `int_param_seq_u16`: return_length uint16 of `int_param_seq_u32`: return_length uint32 of `int_param_seq_u64`: return_length uint64 of `int_param_seq_float`: return_length float of `int_param_seq_f32`: return_length float32 of `int_param_seq_f64`: return_length float64 of `int_param_seq_char`: return_length char of `int_param_seq_string`: return_length string else: return 0 proc set_value(address: ptr `type_name`, name: string, value: string = ""): bool = template parse_and_set(call, T) = let (parsed_value, success) = call[T](value) if success: let field = cast[ptr T](cast[uint](address) + get_param_offset(name)) field[] = parsed_value return success template parse_and_add(call, T) = let (parsed_value, success) = call[T](value) if success: let field = cast[ptr seq[T]](cast[uint](address) + get_param_offset(name)) field[].add parsed_value return success let kind = get_param_type(name) case kind of `int_param_undefined`: return false of `int_param_string`: let field = cast[ptr string](cast[uint](address) + get_param_offset(name)) field[] = value return true of `int_param_bool`: let field = cast[ptr bool](cast[uint](address) + get_param_offset(name)) field[] = not field[] return true of `int_param_char`: if value.len != 1: return false let field = cast[ptr char](cast[uint](address) + get_param_offset(name)) field[] = value[0] return true of `int_param_seq_string`: let field = cast[ptr seq[string]](cast[uint](address) + get_param_offset(name)) field[].add value return true of `int_param_seq_char`: if value.len != 1: return false let field = cast[ptr seq[char]](cast[uint](address) + get_param_offset(name)) field[].add value[0] return true of `int_param_int`: parse_and_set int_from_string, int of `int_param_i8`: parse_and_set int_from_string, int8 of `int_param_i16`: parse_and_set int_from_string, int16 of `int_param_i32`: parse_and_set int_from_string, int32 of `int_param_i64`: parse_and_set int_from_string, int64 of `int_param_uint`: parse_and_set int_from_string, uint of `int_param_u8`: parse_and_set int_from_string, uint8 of `int_param_u16`: parse_and_set int_from_string, uint16 of `int_param_u32`: parse_and_set int_from_string, uint32 of `int_param_u64`: parse_and_set int_from_string, uint64 of `int_param_float`: parse_and_set float_from_string, float of `int_param_f32`: parse_and_set float_from_string, float32 of `int_param_f64`: parse_and_set float_from_string, float64 of `int_param_seq_int`: parse_and_add int_from_string, int of `int_param_seq_i8`: parse_and_add int_from_string, int8 of `int_param_seq_i16`: parse_and_add int_from_string, int16 of `int_param_seq_i32`: parse_and_add int_from_string, int32 of `int_param_seq_i64`: parse_and_add int_from_string, int64 of `int_param_seq_uint`: parse_and_add int_from_string, uint of `int_param_seq_u8`: parse_and_add int_from_string, uint8 of `int_param_seq_u16`: parse_and_add int_from_string, uint16 of `int_param_seq_u32`: parse_and_add int_from_string, uint32 of `int_param_seq_u64`: parse_and_add int_from_string, uint64 of `int_param_seq_float`: parse_and_add float_from_string, float of `int_param_seq_f32`: parse_and_add float_from_string, float32 of `int_param_seq_f64`: parse_and_add float_from_string, float64 else: return false # Automated help message for `-?`, `-h`, `--help` proc do_help() = if help_text_pre != "": print help_text_pre print "" proc full_name(index: int): string = var name = `field_names`[index].replace("_", "-") if `alias_index`.contains(index): for i, alias_index in `alias_index`.pairs: if alias_index == index: let alias = `alias_names`[i] name = name & ", " & get_prefix(alias, index) & alias return name proc extract_exe(path:string): string = var startchar = len(path) var endchar = -1 while startchar > 0: startchar -= 1; if "/\\:".find($path[startchar]) >= 0: startchar += 1 break elif path[startchar] == '.' and endchar == -1: endchar = startchar if endchar == -1: return path[startchar ..< ^0] else: return path[startchar ..< endchar] var added_options = false var usage:string if program_name != "": usage = program_name else: usage = extract_exe(get_app_filename()) for i, name in `field_names`: if is_bare(i): if `required_indexes`.contains(i): usage &= " " & name else: usage &= " [" & name & "]" else: if `required_indexes`.contains(i): usage &= " " & get_prefix(name) & name if `field_type`[i] != `int_param_bool`: usage &= " " & "" elif not added_options: usage &= " " & "[options]" added_options = true print " " & usage print "" var letters = 0 for i, _ in `field_names`: let name = full_name(i) if name.len > letters: letters = name.len var printed_header = false for i, name in `field_names`: if not is_bare(i): continue let display_name = name.replace("_", "-") if not printed_header: print "Arguments:" print "" printed_header = true let postfix = `field_descriptions`[i] if postfix == "": print " " & display_name else: var spacer = "" while display_name.len + spacer.len < letters: spacer = spacer & " " print " " & display_name & spacer & " " & postfix if printed_header: print "" printed_header = false for i, name in `field_names`: if is_bare(i): continue if not printed_header: print "Options:" print "" printed_header = true let display_name = full_name(i) let postfix = `field_descriptions`[i] if postfix == "": print " " & get_prefix(name) & display_name else: var spacer = "" while display_name.len + spacer.len < letters: spacer = spacer & " " print " " & get_prefix(name) & display_name & spacer & " " & postfix if printed_header: print "" if help_text_post != "": print help_text_post print "" # Actual proc called on generated objects to assign user values to them proc `proc_name`(options: var `type_name`, supplied: var `supplied_type_name`): (`type_name`, `supplied_type_name`) = var awaiting_value = false awaiting_value_for = "" awaiting_value_for_type = 0 writing_to_seq = false writing_bare_seq = false current_seq_length = 0 desired_seq_length = 0 current_bare_index = 0 no_await_value_check_until = 0 force_bare = false if implicit_bare: var last_seq_string = -1 var add_implicit_bare = true for i, kind in `field_type`: if kind == int_param_seq_string: if i in `bare_indexes`: add_implicit_bare = false break else: last_seq_string = i if add_implicit_bare and last_seq_string >= 0: `bare_indexes`.add(last_seq_string) var words:seq[string] = command_line_params() var next_word_index = 0 while next_word_index < words.len: let word = words[next_word_index] next_word_index += 1 let word_is_value = force_bare or (awaiting_value and is_numeric(awaiting_value_for_type) and can_parse_as(word, awaiting_value_for_type)) if not word_is_value and ((dash_denotes_param and word.starts_with("-")) or (slash_denotes_param and word.starts_with("/"))): if writing_to_seq: if desired_seq_length != 0 and desired_seq_length != current_seq_length: parse_error("Expected " & desired_seq_length.repr & " values for: " & get_prefix(awaiting_value_for) & awaiting_value_for) writing_to_seq = false elif awaiting_value and next_word_index > no_await_value_check_until: parse_error("Expected value for: " & get_prefix(awaiting_value_for) & awaiting_value_for) if dash_denotes_param and word == "--": if double_dash_separator: force_bare = true continue if split_on_colon and word.contains(":"): var c = word.find(":") words.insert(word[0 ..< c], next_word_index) words.insert(word[c + 1 ..< ^0], next_word_index + 1) continue elif split_on_equals and word.contains("="): var c = word.find("=") words.insert(word[0 ..< c], next_word_index) words.insert(word[c + 1 ..< ^0], next_word_index + 1) continue var name = word[1 ..< ^0] if use_double_dash: if name.starts_with("-"): name = name[1 ..< ^0] elif name.len > 1: for i, letter in name.pairs: words.insert "-" & letter, next_word_index + i no_await_value_check_until = next_word_index + name.len + 1 continue name = name.replace("-", "_") if name in `alias_names`: let index = `alias_names`.find(name) let name_index = `alias_index`[index] name = `field_names`[name_index] let found = `field_names`.contains(name) and (not is_bare(name) or bare_can_still_be_named) if not found: if automatic_help and (name == "help" or name == "h" or name == "?"): do_help() quit(0) else: parse_error("No such parameter: " & word) if parameters_are_unique and is_supplied(addr supplied, name): parse_error("Parameter already set: " & word) if get_param_type(name) == `int_param_bool`: if set_value(addr options, name): set_supplied(addr supplied, name) else: parse_error("Failed to set " & word & ": this should not happen!") else: awaiting_value = true awaiting_value_for = name awaiting_value_for_type = get_param_type(name) else: if awaiting_value: if set_value(addr options, awaiting_value_for, word): set_supplied(addr supplied, awaiting_value_for) if writing_to_seq: current_seq_length += 1 else: if is_seq(awaiting_value_for): writing_to_seq = true desired_seq_length = get_desired_seq_len(awaiting_value_for) current_seq_length = 1 else: awaiting_value = false if writing_to_seq and desired_seq_length != 0 and current_seq_length >= desired_seq_length: awaiting_value = false else: parse_error("'" & word & "' is not a valid value for '" & awaiting_value_for & "'") else: var ok = false while current_bare_index < `bare_indexes`.len: let index = `bare_indexes`[current_bare_index] let kind = `field_type`[index] writing_bare_seq = false if can_parse_as(word, kind): if is_seq(kind): if `field_length`[index] == 0 or get_seq_len(addr options, `field_names`[index], kind) < `field_length`[index]: writing_bare_seq = true ok = true break else: ok = true break current_bare_index += 1 if not ok: parse_error("Could not accept argument: " & word) let bare_name = `field_names`[`bare_indexes`[current_bare_index]] if set_value(addr options, bare_name, word): set_supplied(addr supplied, bare_name) else: parse_error("'" & word & "' is not a valid value for '" & awaiting_value_for & "'") let kind = `field_type`[`bare_indexes`[current_bare_index]] if not is_seq(kind): current_bare_index += 1 if awaiting_value: if writing_to_seq: if desired_seq_length != 0 and current_seq_length < desired_seq_length: parse_error("Expected " & desired_seq_length.repr & " values for: " & get_prefix(awaiting_value_for) & awaiting_value_for) else: parse_error("Expected value for: " & get_prefix(awaiting_value_for) & awaiting_value_for) elif writing_bare_seq: let index = `bare_indexes`[current_bare_index] let kind = `field_type`[index] let name = `field_names`[index] desired_seq_length = `field_length`[index] if desired_seq_length != 0 and get_seq_len(addr options, `field_names`[index], kind) < desired_seq_length: parse_error("Expected " & desired_seq_length.repr & " values for: " & get_prefix(name) & name) var missing = false for index in `required_indexes`: let name = `field_names`[index] if not is_supplied(addr supplied, name): parse_error("Value required for: " & get_prefix(name) & name) missing = true if missing: parse_error("Missing required value(s).") return (options, supplied) # Generate AST skeleton result = nnk_block_stmt.new_tree( new_empty_node(), nnk_stmt_list.new_tree( options_type_root_node, supplied_type_root_node, options_root_node, supplied_root_node, field_names_node, field_offsets_node, field_supplied_node, field_types_node, info_node, field_alias_names_node, field_alias_indexes_node, field_default_values_node, field_lengths_node, field_descriptions_node, required_fields_node, bare_fields_node, assignments_node, proc_node, call_node)) # Fill it out template add_field_to_type(name: string, kind: ParamKind) = options_type_node.add( nnk_ident_defs.new_tree( ident(name), ident_from_kind(kind), new_empty_node())) supplied_type_node.add( nnk_ident_defs.new_tree( ident(name), ident("bool"), new_empty_node())) template add_field_value_assignment(name: string, value_node: Nim_Node) = if value_node != nil: options_node.add( nnk_expr_colon_expr.new_tree( ident(name), value_node.copy_nim_node)) template add_field_value_offset(name: string) = assignments_node.add( nnk_call.new_tree( nnk_dot_expr.new_tree( ident(field_offsets_label), ident("add")), nnk_infix.new_tree( ident("-"), nnk_cast.new_tree( ident("uint"), nnk_command.new_tree( ident("addr"), nnk_dot_expr.new_tree( ident(options_instance_label), ident(name)))), nnk_cast.new_tree( ident("uint"), nnk_command.new_tree( ident("addr"), ident(options_instance_label)))))) template add_field_supplied_offset(name: string) = assignments_node.add( nnk_call.new_tree( nnk_dot_expr.new_tree( ident(field_supplied_label), ident("add")), nnk_infix.new_tree( ident("-"), nnk_cast.new_tree( ident("uint"), nnk_command.new_tree( ident("addr"), nnk_dot_expr.new_tree( ident(supplied_instance_label), ident(name)))), nnk_cast.new_tree( ident("uint"), nnk_command.new_tree( ident("addr"), ident(supplied_instance_label)))))) template add_seq_value(seq_name: string, value: string) = assignments_node.add( nnk_call.new_tree( nnk_dot_expr.new_tree( ident(seq_name), ident("add")), new_str_lit_node(value))) template add_seq_value(seq_name: string, value: int) = assignments_node.add( nnk_call.new_tree( nnk_dot_expr.new_tree( ident(seq_name), ident("add")), new_int_lit_node(value))) var alias_list:seq[string] = @[] for i, name in params_in_order.pairs: let param = params[name] add_field_to_type name, param.kind add_field_value_assignment name, value_node_from_param(param) add_field_value_offset name add_field_supplied_offset name add_seq_value field_names_label, name add_seq_value field_types_label, int_param_from_param(param.kind) add_seq_value field_default_values_label, string_from_param(param) add_seq_value field_lengths_label, param.seq_len add_seq_value field_descriptions_label, param.description if param.required: add_seq_value required_fields_label, i if param.accepts_bare: add_seq_value bare_fields_label, i for alias in param.alias: if alias in alias_list or alias in params: error("Parameter already exists: " & alias) add_seq_value field_alias_names_label, alias add_seq_value field_alias_indexes_label, i alias_list.add alias macro get_options*(body: untyped): untyped = ## Parses the command-line arguments provided by the user, ## using it and the code block to fill out an object's fields. ## ## Returns an object whose fields are detailed by the code block. ## ## ## All basic intrinsic types are supported: ## `string`, `char`, `bool`, `int`, `uint`, `float` ## (and all size variants: `int16`, `float32`, etc.) ## ## The block is written as if it were a `var`, i.e. all of these are valid: ## ``` ## x = 0 ## x:int32 ## x:int = 20 ## ``` ## You may also use any `seq` of the above: `seq[string]`, `seq[int]`, ## `seq[float]`, etc. These may not be initialized to a default ## value, however: they must be empty. ## ``` ## args: seq[string] ## ``` ## If you do not explicitly specify a `{. bare .}` `seq[string]`, but do ## have one or more `seq[string]`, then the last one will be used to store ## all bare arguments. (i.e. will be treated as if it had an implicit ## `{. bare .}` pragma.) ## This may be disabled with the `no_implicit_bare` setting. ## ## ## You may also add pragmas to the end of any line to modify parameter ## behaviour. Each pragma has a more verbose alias, if you prefer that ## style of code. ## ## `{. info("text") .}` or `{. description("text") .}` ## Description of the parameter shown in help text. ## ## ## `{. aka("a", "b", ...) .}` or `{. alias("a", "b", ...) .}` ## Aliases for the parameter - user may use these as parameters; ## they will write to the variable. ## ## ## `{. bare .}` or `{. positional .}` ## Accepts a bare, positional argument (an argument which has not ## been prefixed with a parameter name). User will not be able to ## refer to the argument with its parameter name. ## ## ## `{. need .}` or `.{ required .}` ## Parameter must be supplied by user or an error is shown. ## ## ## `{. len(i) .}` or `.{ count(i) .}` ## Place on a `seq` field to require that many values be supplied to it. ## For example: ## `position:seq[float] {. len(3) .} # x y z` ## ## Note that this does not set a limit on the total length of the seq, ## only on how many values the user must specify. Using `len` in ## conjunction with the `allow_repetition` setting, you can accept ## multiple batches of values (see `normalize.nim` example) ## ## ## *Example:* ## ``` ## let options = get_options: ## teenager = "Joe Random" {. alias("name", "n") .} ## age[int8] = 13 ## nin:string {. info("National Insurance Number") .} ## ## if options.nin.len != 9: ## report "Must supply valid NIN" ## quit(1) ## ## if options.age < 13 or options.age > 19: ## report "Not a teenager!" ## quit(1) ## ``` var options, _ = quote do: get_options_and_supplied(`body`)[0] return options when is_main_module: proc prettify[T](title: string, data: T, new_section = false) = if new_section: print "--------------" print title let s = $data var indenting = false for output in s[1 ..< ^1].strip.replace("[", "").replace("]", "").split(','): var line = output.strip() if line.ends_with("\"") and " = 0" in line: let start = line.find(" = 0") let skip = line.find("\"") line = line[0 ..< start + 3] & line[skip ..< ^0] if line.contains(": @"): let c = line.find("@") print line[0 ..< c] line = line[c + 1 ..< ^0] indenting = true if indenting: if line.contains(":"): indenting = false else: line = " " & line if line != "": print line print "" config: no_slash.dash_dash_parameters.value_after_colon.value_after_equals help_text "Nim module v" & version # vscode-nim arguments: -? --namu Bob B --position 10 20 10 -a -A --letter-test a b c d let (options, is_set) = get_options_and_supplied: name = "Default Name" {. alias("n", "namu") .} toggle = false {. info("Flip me") .} letter = 'a' {. bare, info("Initial") .} age = 1 {. info("How old they are") .} iq = -57 half_iq = -28.5 here = true {. alias("h"), info("And now!") .} there = false {. info("And back!") .} big:float64 = 1.1 small:float = 2.2 active:bool flat:uint = 2 letter_test: seq[char] position:seq[int8] {. len(3) .} args: seq[string] a: bool A: bool prettify("Options", options, true) prettify("Supplied", is_set) echo options.letterTest