Enum value descriptions with core:flags

Hellope,

I’m using core:flags to set an enum value via CLI args. However I’ve noticed that the possible values of the enum are not shown in the automatically generated usage message. They are only shown once I provide an incorrect value.

Minimal example:

package test

import "core:flags"
import "core:os"

main :: proc() {
  Foo :: enum {
    One,
    Two,
    Three,
  }

  Opts :: struct {
    foo: Foo `args:"pos=0" usage:"A Foo."`,
  }

  opts: Opts
  flags.parse_or_exit(&opts, os.args)
}
$ odin run . -- -h
Usage:
        test [foo]
Flags:
        -foo:<Foo>  | A Foo.
$ odin run . -- -foo:bar
[Parse_Error.Bad_Value] Unable to set `foo` of type Foo to `bar`. Invalid value name. Valid names are: [One, Two, Three]

Is it possible to automatically include something like [One, Two, Three] in the generated usage message or do I have to implement that myself?

Bonus question: Is it possible to ignore the case for enum values passed via CLI args? I think I read something about that somewhere but I might be misremembering…

You’ll have to do that yourself.

  Opts :: struct {
    foo: Foo `args:"pos=0" usage:"Possible values: One, Two, Three."`,
  }

Do you mean ignore errors for enum argument or do you mean to hide from usage output? If latter, then yes, it would look like:

  Opts :: struct {
    foo: Foo `args:"pos=0,hidden" usage:"A Foo."`,
  }

If former, than you’ll likely have to create and set your own validator (and or type setter) which can be seen in …Odin\core\flags\example\example.odin

Some good info in …\Odin\core\flags\doc.odin

You’ll have to do that yourself.

Fair enough. That is the first “I expected Odin do do something but it can’t” thing for me then :wink:

Do you mean ignore errors for enum argument or do you mean to hide from usage output?

What I mean was the parser also accepting -foo:one instead of just -foo:One. I’m used to things on the command line being lower case, but I like the convention of capitalizing enum values, so that would be the perfect behavior for me.

I’ll look into custom validators. Thanks!

You’ll have to create and set both a custom type setter and validator for that. Or… just name your enum definitions lower case instead of the standard upper_snake case :wink:

For what it is worth, here is an example of how I currently use Args with the flags package. This is an excerpt from a little weather program I made for myself. I’ve tried several ways to automate customization, and settled on the following after realizing how much code I had to write to traverse nested types, etc. It became more than it was worth. I decided to compromise and do this.

I posted about recommendation to facilitate custom usage functionality here write_usage

package custom_usage_example

import "core:fmt"
import "core:os"
import "core:reflect"
import "core:flags"
import "base:runtime"
import "base:intrinsics"

Args :: struct {
	loc:     string `args:"name=l,pos=0" usage:"Location: Zip, City, or City-st. If empty, location is based in IP."`,
	moon:    bool   `args:"name=m" usage:"Get moon phase information. Debug not availible."`,
	report:  bool   `args:"name=r" usage:"Verbose 3 day report."`,
	sol:     bool   `args:"name=s" usage:"Get Sun position times."`,
	weather: bool   `args:"name=w" usage:"Get weather for today and 3 day forecast."`,
	debug:   bool   `args:"name=d" usage:"Print debug information for weather service."`,
}

parse_args :: proc(args: ^$T, usage: proc(typeid)) -> (ok: bool) where intrinsics.type_is_struct(T) {
	if len(os.args) <= 1 {
		usage(T)
	} else {
		switch err in flags.parse(args, os.args[1:]) {
		case flags.Help_Request:     usage(T)
		case flags.Validation_Error: fmt.printfln("%v", err.message)
		case flags.Parse_Error:      fmt.printfln("%v", err.message)
		case flags.Open_File_Error:  fmt.printfln("%v", err)
		case: ok = true
		}
	}
	return
}

usage_tag :: proc(tags: []reflect.Struct_Tag, tag: string) -> (usage: string) {
	for t in tags {
		args := reflect.struct_tag_lookup(t, "args") or_continue
		if name := flags.get_subtag(args, "name") or_continue; name == tag {
			usage = reflect.struct_tag_lookup(t, "usage") or_break
			return
		}
	}
	return
}

usage :: proc(T: typeid) {
	tinfo := runtime.type_info_base(type_info_of(T))
	t, ok := tinfo.variant.(runtime.Type_Info_Struct)
	tags  := ok ? transmute([]reflect.Struct_Tag)t.tags[:t.field_count] : nil
	usage := [][]string {
		{"Usage:", "mist l [-m] [-r] [-s] [-w] [-d]"},
		{"",""},
		{"-l:<string>", usage_tag(tags, "l")},
		{"-m:<bool>"  , usage_tag(tags, "m")},
		{"-r:<bool>"  , usage_tag(tags, "r")},
		{"-s:<bool>"  , usage_tag(tags, "s")},
		{"-w:<bool>"  , usage_tag(tags, "w")},
		{"-d:<bool>"  , usage_tag(tags, "d")},
	}
	for u in usage {
		fmt.printfln("%-12s%s", u[0], u[1])
	}
}

main :: proc() {
	if args: Args; parse_args(&args, usage) {
		//do stuff
	}
}

Wait a minute! Back to the original question. The values do show up for me! I’m on Windows and using the odin release from May: dev-2026-05

main :: proc() {
	args: struct {
		root: string `args:"pos=0,required"`,
		mode: enum { horizontal, vertical } `args:"pos=1" usage:"bla bla"`,
	}
	flags.parse_or_exit(&args, os.args)
}
➜  odin run . -- -h
Usage:
        odin_screenshot_combiner.exe root [mode]
Flags:
        -root:<string>, required                 | <This flag has not been documented yet.>
        -mode:<enum int {horizontal, vertical}>  | bla bla

Ooh! It’s because I’m using an anonymous enum. Good enough for me, but probably not what you were looking for :smiley:

Thanks for the example. That’ll help getting something custom going.

Interesting! That’s not really what I would want to have in a user-facing usage message, but much closer to my original idea.

Also works in dev-2026-04 btw

I was wondering how hard it could be to address this directly in the flags package. I now opened a PR so if things go well, it might land :crossed_fingers: flags: usage includes enum values by GigaGrunch · Pull Request #6665 · odin-lang/Odin · GitHub

1 Like

Nice! Let’s hope for the best. I think that’s a reasonable addition to core:flags.

1 Like

I made a pass at processing and parsing args and was able to cover all possible types supported as args input by flags. The problem is the nested nature of types, especially when they are named, that I ran into the fact that some hard decisions were needed for then printing it all out. It’s been a while since I took a break from it, so I’m having trouble remembering the specifics. Maybe I’ll dust off the parsing stuff, and post it, so others who are interested can help finish off the printing part.

1 Like

I made an update to my example. I had left the name for the Args struct hardcoded in one of the procedures. I had meant to fix that and make it so the Args struct can have any name. I made the change and now the only explicit reference to the Args struct by name is in main. This allows for most of the helper code stuff to be copy-n-paste, requiring only an Args struct of your own making and some explicit lines added to the [ ][ ]string in usage().

I looked through my code and remember now why I took a break. It was not just the printing stuff, it was that fact that the core:flags package inter-weaved much of the functionality, making it difficult to pick the parts you want for customizing and impossible to pick-n-choose procedures to control things. I felt that in order for customization to be possible, the parsing had to be completely separate and populate a known structure with the data, so a user would have the ability to get that data independent of the other procedures to use for customization.

This then had a fallout effect (realization of several things)

  • If the parsing is separated out, then the validation procedures would have to be completely re-written to use the parsed structure.
  • Support for custom validator would have to be re-written.
  • Support for custom type setter would have to be re-written.
  • Sorting would have to be re-written.
  • Printing would have to be re-written.
  • Decisions made on what is printed for various flag data. Type names, input options, etc.
  • Since this would be a focus on customization, options for column width, colors, and other formatting options should be supported.
  • A default procedure flow would need to be worked out for users who do not wish to customize or only customize a few things.
  • Probably a few other details I’m leaving out.

Conclusion:
This meant that much (if not most of) the flags package would have to be re-written. While this is something that is possible, and I might still do someday (which is why I didn’t delete my existing effort), it seemed to make more sense that this was done in the core. So I posted about it in this thread.

Making a Case for Change:
The existing core:flags package appears to have been written with a focus on speed and efficiency, which is why I believe much of it is interwoven together. The result is less than ideal modularity. This make sense for many things in Odin, and a reason why most programs run so fast much of the time. For a package targeting usage (parsing, validation, printing, etc), the focus should be more on customization and modularity. Usage processing and printing is usually one of two things: 1. parse, print, exit - or - 2. parse, hold data for when ever user wants to print usage while program is active, exit. Performance and speed is negligible in those scenarios and virtually unnoticeable to a user, so customization should be the primary focus, not performance.