diff options
| author | Bradley Lewis <22850972+BradLewis@users.noreply.github.com> | 2025-09-19 09:21:26 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-09-19 09:21:26 -0400 |
| commit | 7ff84eb0b24912db1b3f3b0cff323e1728f47b0b (patch) | |
| tree | a18bb1dacba6ff52bd74461441058623b572d644 | |
| parent | 5bf8612ce6f39f8e7a1774b09948ddecdeb471d3 (diff) | |
| parent | e6a3b84466e13abd74e81068ee7f06f7c4c569a3 (diff) | |
Merge pull request #1015 from godalming123/master
Use core:flags instead of custom flag parsing code and fix 2 bugs
| -rw-r--r-- | src/odin/printer/document.odin | 2 | ||||
| -rw-r--r-- | tools/odinfmt/flag/flag.odin | 216 | ||||
| -rw-r--r-- | tools/odinfmt/main.odin | 97 |
3 files changed, 32 insertions, 283 deletions
diff --git a/src/odin/printer/document.odin b/src/odin/printer/document.odin index f3ef61e..5fba1b9 100644 --- a/src/odin/printer/document.odin +++ b/src/odin/printer/document.odin @@ -447,7 +447,7 @@ format :: proc(width: int, list: ^[dynamic]Tuple, builder: ^strings.Builder, p: if v.amount > 0 { flush_line_suffix(builder, &suffix_builder) // ensure we strip any misplaced trailing whitespace - for builder.buf[len(builder.buf)-1] == ' ' { + for len(builder.buf) > 0 && builder.buf[len(builder.buf) - 1] == ' ' { pop(&builder.buf) } for i := 0; i < v.amount; i += 1 { diff --git a/tools/odinfmt/flag/flag.odin b/tools/odinfmt/flag/flag.odin deleted file mode 100644 index b52e359..0000000 --- a/tools/odinfmt/flag/flag.odin +++ /dev/null @@ -1,216 +0,0 @@ -package flag - -import "base:runtime" - -import "core:strings" -import "core:reflect" -import "core:fmt" -import "core:mem" -import "core:strconv" - -Flag_Error :: enum { - None, - No_Base_Struct, - Arg_Error, - Arg_Unsupported_Field_Type, - Arg_Not_Defined, - Arg_Non_Optional, - Value_Parse_Error, - Tag_Error, -} - -Flag :: struct { - optional: bool, - type: ^runtime.Type_Info, - data: rawptr, - tag_ptr: rawptr, - parsed: bool, - offset: uintptr, -} - -Flag_Context :: struct { - seen_flags: map[string]Flag, -} - -parse_args :: proc(ctx: ^Flag_Context, args: []string) -> Flag_Error { - - using runtime; - - args := args; - - for { - if len(args) == 0 { - return .None; - } - - arg := args[0]; - - if len(arg) < 2 || arg[0] != '-' { - return .Arg_Error; - } - - minus_count := 1; - - if arg[1] == '-' { - minus_count += 1; - - if len(arg) == 2 { - return .Arg_Error; - } - } - - name := arg[minus_count:]; - - if len(name) == 0 { - return .Arg_Error; - } - - args = args[1:]; - - assign_index := strings.index(name, "="); - - value := ""; - - if assign_index > 0 { - value = name[assign_index + 1:]; - name = name[0:assign_index]; - } - - flag := &ctx.seen_flags[name]; - - if flag == nil { - return .Arg_Not_Defined; - } - - if reflect.is_boolean(flag.type) { - tmp: b64 = true; - mem.copy(flag.data, &tmp, flag.type.size); - flag.parsed = true; - continue; - } else if value == "" { // must be in the next argument - if len(args) == 0 { - return .Arg_Error; - } - - value = args[0]; - args = args[1:]; - } - - #partial switch _ in flag.type.variant { - case Type_Info_Integer: - if v, ok := strconv.parse_int(value); ok { - mem.copy(flag.data, &v, flag.type.size); - } else { - return .Value_Parse_Error; - } - case Type_Info_String: - raw_string := cast(^mem.Raw_String)flag.data; - raw_string.data = raw_data(value); - raw_string.len = len(value); - case Type_Info_Float: - switch flag.type.size { - case 32: - if v, ok := strconv.parse_f32(value); ok { - mem.copy(flag.data, &v, flag.type.size); - } else { - return .Value_Parse_Error; - } - case 64: - if v, ok := strconv.parse_f64(value); ok { - mem.copy(flag.data, &v, flag.type.size); - } else { - return .Value_Parse_Error; - } - } - } - - flag.parsed = true; - } - - return .None; -} - -reflect_args_structure :: proc(ctx: ^Flag_Context, v: any) -> Flag_Error { - using runtime; - - if !reflect.is_struct(type_info_of(v.id)) { - return .No_Base_Struct; - } - - names := reflect.struct_field_names(v.id); - types := reflect.struct_field_types(v.id); - offsets := reflect.struct_field_offsets(v.id); - tags := reflect.struct_field_tags(v.id); - - for name, i in names { - flag: Flag; - - type := types[i]; - - if named_type, ok := type.variant.(Type_Info_Named); ok { - if union_type, ok := named_type.base.variant.(Type_Info_Union); ok && len(union_type.variants) == 1 { - flag.optional = true; - flag.offset = union_type.tag_offset + offsets[i]; - flag.tag_ptr = rawptr(flag.offset + uintptr(v.data)); - type = union_type.variants[0]; - } else { - return .Arg_Unsupported_Field_Type; - } - } - - #partial switch _ in type.variant { - case Type_Info_Integer, Type_Info_String, Type_Info_Boolean, Type_Info_Float: - flag.type = type; - flag.data = rawptr(uintptr(v.data) + uintptr(offsets[i])); - case: - return .Arg_Unsupported_Field_Type; - } - - flag_name: string; - - if value, ok := reflect.struct_tag_lookup(tags[i], "flag"); ok { - flag_name = cast(string)value; - } else { - return .Tag_Error; - } - - ctx.seen_flags[flag_name] = flag; - } - - return .None; -} - -parse :: proc(v: any, args: []string) -> Flag_Error { - - if v == nil { - return .None; - } - - ctx: Flag_Context; - - size := type_info_of(v.id).size; - - if res := reflect_args_structure(&ctx, v); res != .None { - return res; - } - - if res := parse_args(&ctx, args); res != .None { - return res; - } - - //validate that the required flags were actually set - for k, v in ctx.seen_flags { - if v.optional && v.parsed { - tag_value: i32 = 1; - mem.copy(v.tag_ptr, &tag_value, size - int(v.offset)); - } else if !v.parsed && !v.optional { - return .Arg_Non_Optional; - } - } - - return .None; -} - -usage :: proc(v: any) -> string { - return "failed"; -} diff --git a/tools/odinfmt/main.odin b/tools/odinfmt/main.odin index a829be6..3470ca5 100644 --- a/tools/odinfmt/main.odin +++ b/tools/odinfmt/main.odin @@ -1,48 +1,24 @@ package odinfmt import "core:encoding/json" +import "core:flags" import "core:fmt" +import "core:io" import "core:mem" import "core:odin/tokenizer" import "core:os" import "core:path/filepath" import "core:strings" import "core:time" -import "flag" import "src:odin/format" import "src:odin/printer" Args :: struct { - write: Maybe(bool) `flag:"w" usage:"write the new format to file"`, - stdin: Maybe(bool) `flag:"stdin" usage:"formats code from standard input"`, + write: bool `args:"name=w" usage:"write the new format to file"`, + stdin: bool `usage:"formats code from standard input"`, + path: string `args:"pos=0" usage:"set the file or directory to format"`, } -print_help :: proc(args: []string) { - fmt.eprintln("usage: odinfmt [-w {filepath}] [-stdin]") -} - -print_arg_error :: proc(args: []string, error: flag.Flag_Error) { - switch error { - case .None: - print_help(args) - case .No_Base_Struct: - fmt.eprintln(args[0], "no base struct") - case .Arg_Error: - fmt.eprintln(args[0], "argument error") - case .Arg_Unsupported_Field_Type: - fmt.eprintln(args[0], "argument: unsupported field type") - case .Arg_Not_Defined: - fmt.eprintln(args[0], "argument: no defined") - case .Arg_Non_Optional: - fmt.eprintln(args[0], "argument: non optional") - case .Value_Parse_Error: - fmt.eprintln(args[0], "argument: value parse error") - case .Tag_Error: - fmt.eprintln(args[0], "argument: tag error") - } -} - - format_file :: proc(filepath: string, config: printer.Config, allocator := context.allocator) -> (string, bool) { if data, ok := os.read_entire_file(filepath, allocator); ok { return format.format(filepath, string(data), config, {.Optional_Semicolons}, allocator) @@ -76,29 +52,18 @@ main :: proc() { init_global_temporary_allocator(mem.Megabyte * 20) //enough space for the walk args: Args + flags.parse_or_exit(&args, os.args) - if len(os.args) < 2 { - print_help(os.args) - os.exit(1) - } - - // When running `$ odinfmt some_odin_file.odin`, the file shouldn't be passed to - // `flag.parse`. But, when running `$ odinfmt -stdin`, we do want to pass `-stdin` to - // `flag.parse`. Need to check if last arg is a flag, and if so, include it in the - // args to parse. - args_to_parse := os.args[1:len(os.args) - 1] - path := os.args[len(os.args) - 1] - if strings.has_prefix(os.args[len(os.args) - 1], "-") { - args_to_parse = os.args[1:] - } - - if len(path) <= 1 { - path = "." // if no file was specified, use current directory as the starting path to look for `odinfmt.json` - } - - if res := flag.parse(args, args_to_parse); res != .None { - print_arg_error(os.args, res) - os.exit(1) + // only allow the path to not be specified when formatting from stdin + if args.path == "" { + if args.stdin { + // use current directory as the starting path to look for `odinfmt.json` + args.path = "." + } else { + fmt.fprint(os.stderr, "Missing path to format\n") + flags.write_usage(os.stream_from_handle(os.stderr), Args, os.args[0]) + os.exit(1) + } } tick_time := time.tick_now() @@ -107,9 +72,9 @@ main :: proc() { watermark := 0 - config := format.find_config_file_or_default(path) + config := format.find_config_file_or_default(args.path) - if _, ok := args.stdin.(bool); ok { + if args.stdin { data := make([dynamic]byte, arena_allocator) for { @@ -128,28 +93,28 @@ main :: proc() { } write_failure = !ok - } else if os.is_file(path) { - if _, ok := args.write.(bool); ok { - backup_path := strings.concatenate({path, "_bk"}) + } else if os.is_file(args.path) { + if args.write { + backup_path := strings.concatenate({args.path, "_bk"}) defer delete(backup_path) - if data, ok := format_file(path, config, arena_allocator); ok { - os.rename(path, backup_path) + if data, ok := format_file(args.path, config, arena_allocator); ok { + os.rename(args.path, backup_path) - if os.write_entire_file(path, transmute([]byte)data) { + if os.write_entire_file(args.path, transmute([]byte)data) { os.remove(backup_path) } } else { - fmt.eprintf("Failed to write %v", path) + fmt.eprintf("Failed to write %v", args.path) write_failure = true } } else { - if data, ok := format_file(path, config, arena_allocator); ok { + if data, ok := format_file(args.path, config, arena_allocator); ok { fmt.println(data) } } - } else if os.is_dir(path) { - filepath.walk(path, walk_files, nil) + } else if os.is_dir(args.path) { + filepath.walk(args.path, walk_files, nil) for file in files { fmt.println(file) @@ -158,7 +123,7 @@ main :: proc() { defer delete(backup_path) if data, ok := format_file(file, config, arena_allocator); ok { - if _, ok := args.write.(bool); ok { + if args.write { os.rename(file, backup_path) if os.write_entire_file(file, transmute([]byte)data) { @@ -184,9 +149,9 @@ main :: proc() { ) fmt.printf("Peak memory used: %v \n", watermark / mem.Megabyte) } else { - fmt.eprintf("%v is neither a directory nor a file \n", path) + fmt.eprintf("%v is neither a directory nor a file \n", args.path) os.exit(1) } os.exit(1 if write_failure else 0) -}
\ No newline at end of file +} |