Compare commits

..
12 Commits
4 changed files with 177 additions and 35 deletions
+19
View File
@@ -0,0 +1,19 @@
# zlib License
(C) 2026 Simon Kellet
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
+34 -2
View File
@@ -19,8 +19,14 @@ This a very simple implementation of Go-like flags in Odin
fleg.parse_flags() fleg.parse_flags()
``` ```
You can also marks flags as **required**. These will still be parsed, however will hault the application until they are passed:
***Supports: bool, int, string, f32*** ```odin
import "fleg"
isRunning: bool
fleg.BoolVar(&isRunning, "isRunning", true, "A select piece of code is running!", required = true)
fleg.parse_flags()
```
And on the command line, you pass flags like so: And on the command line, you pass flags like so:
@@ -28,6 +34,8 @@ And on the command line, you pass flags like so:
./<program name> -isNumber=45 -isRunning=false ...... ./<program name> -isNumber=45 -isRunning=false ......
``` ```
***Supports: bool, int, string, f32 and f64***
## Help flag: ## Help flag:
The package also contains a help context menu. This comes default and can be called like so (**Note:** You can also use "-h" or "--help"): The package also contains a help context menu. This comes default and can be called like so (**Note:** You can also use "-h" or "--help"):
@@ -54,7 +62,20 @@ The package also contains a help context menu. This comes default and can be cal
## Custom Flag Format ## Custom Flag Format
A custom flag format can be created by overwriting the strings *FLAG_START_CHAR* and *FLAG_SEP_CHAR*. These cannot be the same however! A custom flag format can be created by overwriting the strings *FLAG_START_CHAR* and *FLAG_SEP_CHAR*. (These *cannot* be the same however!)
```odin
fleg.FLAG_START_CHAR = "--"
fleg.FLAG_SEP_CHAR = ":"
```
Flags will now be in the format:
```txt
--isString:"......"
```
**Note:** Any change to the format of the flags is also relfected upon the "help" flag.
## Custom Allocator ## Custom Allocator
@@ -70,3 +91,14 @@ If needed, you are able to setup a custom allocator like so:
fleg.init_custom_allocator(arena_alloc) fleg.init_custom_allocator(arena_alloc)
defer fleg.destroy() //clean up flags defer fleg.destroy() //clean up flags
``` ```
### Mirror
This repo is being developed on Gitea [here](https://git.simonkellet.xyz/simonkellet/odin-flags). The Github ([here](https://github.com/simonkellet404/odin-flags)) is a mirror.
## TODO
* More type support
* Better logging of INFO and ERROR's
* String validation (perhaps not needed?)
* Array support (e.g. **--isArray:{4,5,6,7}**)
+5 -3
View File
@@ -5,6 +5,7 @@ import vmem "core:mem/virtual"
import "fleg" import "fleg"
main :: proc(){ main :: proc(){
/*
arena: vmem.Arena arena: vmem.Arena
arena_err := vmem.arena_init_growing(&arena) arena_err := vmem.arena_init_growing(&arena)
ensure(arena_err == nil) ensure(arena_err == nil)
@@ -13,10 +14,11 @@ main :: proc(){
fleg.init_custom_allocator(arena_alloc) fleg.init_custom_allocator(arena_alloc)
defer fleg.destroy() defer fleg.destroy()
*/
fleg.FLAG_START_CHAR = ":" fleg.FLAG_START_CHAR = ":"
fleg.FLAG_SEP_CHAR = "=" fleg.FLAG_SEP_CHAR = "="
fleg.FORCE_HELP_ON_EMPTY_ARGS = true //fleg.FORCE_HELP_ON_EMPTY_ARGS = true
defer fleg.destroy() defer fleg.destroy()
isRunning: bool isRunning: bool
@@ -24,8 +26,8 @@ main :: proc(){
isString: string isString: string
isFloat: f32 isFloat: f32
fleg.BoolVar(&isRunning, "isRunning", true, "A select piece of code is running!") fleg.BoolVar(&isRunning, "isRunning", false, "A select piece of code is running!", required = true)
fleg.IntVar(&isNumber, "isNumber", 3434, "A number!") fleg.IntVar(&isNumber, "isNumber", 3434, "A number!", required = true)
fleg.StringVar(&isString, "isString", "Hellope!", "A string!") fleg.StringVar(&isString, "isString", "Hellope!", "A string!")
fleg.Float32Var(&isFloat, "isFloat", 3.1415, "A float!") fleg.Float32Var(&isFloat, "isFloat", 3.1415, "A float!")
fleg.parse_flags() fleg.parse_flags()
+117 -28
View File
@@ -33,25 +33,32 @@ FORCE_HELP_ON_EMPTY_ARGS := false
FLAG_START_CHAR := "-" FLAG_START_CHAR := "-"
FLAG_SEP_CHAR := "=" FLAG_SEP_CHAR := "="
@(private)
Flag_Value_Ptr :: union { Flag_Value_Ptr :: union {
^bool, ^bool,
^int, ^int,
^i32,
^i64,
^string, ^string,
^f32, ^f32,
^f64, ^f64,
} }
@(private)
Flag :: struct { Flag :: struct {
name: string, name: string,
value: Flag_Value_Ptr, value: Flag_Value_Ptr,
usage: string, usage: string,
parsed: bool, // sck: flag to check if it has been parsed parsed: bool, // sck: flag to check if it has been parsed
required: bool, // sck: required flag
} }
// Global dynamic array of Flags // Global dynamic array of Flags
@(private)
all_flags: [dynamic]Flag all_flags: [dynamic]Flag
// runtime allocator for the flags // runtime allocator for the flags
@(private)
flag_allocator: runtime.Allocator flag_allocator: runtime.Allocator
// sck: Used to create a customer allocator // sck: Used to create a customer allocator
@@ -63,84 +70,136 @@ init_custom_allocator :: proc(allocator: runtime.Allocator) {
} }
destroy :: proc() { destroy :: proc() {
free_all(flag_allocator)
delete(all_flags) delete(all_flags)
all_flags = {} // sck: needed? all_flags = {}
} }
@(private)
print_flags :: proc() { print_flags :: proc() {
for f in all_flags { for f in all_flags {
fmt.printfln("%s: %v: %s", f.name, f.value, f.usage) fmt.printfln("%s: %s (required=%v)", f.name, f.usage, f.required)
} }
} }
@(private)
print_flag_format :: proc(){
fmt.printfln("Want %s<flag>%s<value>\n", FLAG_START_CHAR, FLAG_SEP_CHAR)
}
@(private)
print_usage :: proc() { print_usage :: proc() {
fmt.println("\tUsage: ") fmt.println("\tUsage: ")
fmt.println("\n\tFlag format for this program:") fmt.println("\n\tFlag format for this program:")
fmt.printfln("\t%s<flag>%s<value>\n", FLAG_START_CHAR, FLAG_SEP_CHAR) fmt.printfln("\t%s<flag>%s<value>\n", FLAG_START_CHAR, FLAG_SEP_CHAR)
for f in all_flags { for f in all_flags {
req_msg: string
if f.required { req_msg = " (required)"}
switch v in f.value { switch v in f.value {
case ^bool: case ^bool:
fmt.printfln("\t%s%s:\n\t\t%s (default: %v)\n", FLAG_START_CHAR, f.name, f.usage, v^) fmt.printfln("\t%s%s:\n\t\t%s (default: %v)%s\n", FLAG_START_CHAR, f.name, f.usage, v^, req_msg)
case ^int: case ^int:
fmt.printfln("\t%s%s:\n\t\t%s (default: %d)\n", FLAG_START_CHAR, f.name, f.usage, v^) fmt.printfln("\t%s%s:\n\t\t%s (default: %d)%s\n", FLAG_START_CHAR, f.name, f.usage, v^, req_msg)
case ^i32:
fmt.printfln("\t%s%s:\n\t\t%s (default: %d)%s\n", FLAG_START_CHAR, f.name, f.usage, v^, req_msg)
case ^i64:
fmt.printfln("\t%s%s:\n\t\t%s (default: %d)%s\n", FLAG_START_CHAR, f.name, f.usage, v^, req_msg)
case ^string: case ^string:
fmt.printfln("\t%s%s:\n\t\t%s (default: %s)\n", FLAG_START_CHAR, f.name, f.usage, v^) fmt.printfln("\t%s%s:\n\t\t%s (default: %s)%s\n", FLAG_START_CHAR, f.name, f.usage, v^, req_msg)
case ^f32: case ^f32:
fmt.printfln("\t%s%s:\n\t\t%s (default: %f)\n", FLAG_START_CHAR, f.name, f.usage, v^) fmt.printfln("\t%s%s:\n\t\t%s (default: %f)%s\n", FLAG_START_CHAR, f.name, f.usage, v^, req_msg)
case ^f64: case ^f64:
fmt.printfln("\t%s%s:\n\t\t%s (default: %f)\n", FLAG_START_CHAR, f.name, f.usage, v^) fmt.printfln("\t%s%s:\n\t\t%s (default: %f)%s\n", FLAG_START_CHAR, f.name, f.usage, v^, req_msg)
} }
} }
os.exit(0)
} }
BoolVar :: proc(ptr: ^bool, name: string, default: bool, usage: string) { BoolVar :: proc(ptr: ^bool, name: string, default: bool, usage: string, required := false) {
if ptr == nil {
fmt.fprintfln(os.stderr, "[ERROR]: Invalid usage of BoolVar: got nil for 'ptr' value!")
os.exit(1)
}
ptr^ = default ptr^ = default
append(&all_flags, Flag{name = name, value = ptr, usage = usage}) append(&all_flags, Flag{name = name, value = ptr, usage = usage, required = required})
} }
IntVar :: proc(ptr: ^int, name: string, default: int, usage: string) { IntVar :: proc(ptr: ^int, name: string, default: int, usage: string, required := false) {
if ptr == nil {
fmt.fprintfln(os.stderr, "[ERROR]: Invalid usage of IntVar: got nil for 'ptr' value!")
os.exit(1)
}
ptr^ = default ptr^ = default
append(&all_flags, Flag{name = name, value = ptr, usage = usage}) append(&all_flags, Flag{name = name, value = ptr, usage = usage, required = required})
} }
StringVar :: proc(ptr: ^string, name: string, default: string, usage: string) { Int32Var :: proc(ptr: ^i32, name: string, default: i32, usage: string, required := false) {
if ptr == nil {
fmt.fprintfln(os.stderr, "[ERROR]: Invalid usage of Int32Var: got nil for 'ptr' value!")
os.exit(1)
}
ptr^ = default ptr^ = default
append(&all_flags, Flag{name = name, value = ptr, usage = usage}) append(&all_flags, Flag{name = name, value = ptr, usage = usage, required = required})
} }
Float32Var :: proc(ptr: ^f32, name: string, default: f32, usage: string) { Int64Var :: proc(ptr: ^i64, name: string, default: i64, usage: string, required := false) {
if ptr == nil {
fmt.fprintfln(os.stderr, "[ERROR]: Invalid usage of Int32Var: got nil for 'ptr' value!")
os.exit(1)
}
ptr^ = default ptr^ = default
append(&all_flags, Flag{name = name, value = ptr, usage = usage}) append(&all_flags, Flag{name = name, value = ptr, usage = usage, required = required})
} }
Float64Var :: proc(ptr: ^f64, name: string, default: f64, usage: string) { StringVar :: proc(ptr: ^string, name: string, default: string, usage: string, required := false) {
if ptr == nil {
fmt.fprintfln(os.stderr, "[ERROR]: Invalid usage of StringVar: got nil for 'ptr' value!")
os.exit(1)
}
ptr^ = default ptr^ = default
append(&all_flags, Flag{name = name, value = ptr, usage = usage}) append(&all_flags, Flag{name = name, value = ptr, usage = usage, required = required})
}
Float32Var :: proc(ptr: ^f32, name: string, default: f32, usage: string, required := false) {
if ptr == nil {
fmt.fprintfln(os.stderr, "[ERROR]: Invalid usage of Float32Var: got nil for 'ptr' value!")
os.exit(1)
}
ptr^ = default
append(&all_flags, Flag{name = name, value = ptr, usage = usage, required = required})
}
Float64Var :: proc(ptr: ^f64, name: string, default: f64, usage: string, required := false) {
if ptr == nil {
fmt.fprintfln(os.stderr, "[ERROR]: Invalid usage of Float64Var: got nil for 'ptr' value!")
os.exit(1)
}
ptr^ = default
append(&all_flags, Flag{name = name, value = ptr, usage = usage, required = required})
} }
parse_flags :: proc() { parse_flags :: proc() {
// sck: We cannot have the start and seperator formats being the same // sck: We cannot have the start and seperator formats being the same
// ...leads to parsing errors...
if FLAG_START_CHAR == FLAG_SEP_CHAR { if FLAG_START_CHAR == FLAG_SEP_CHAR {
fmt.println("[ERROR]: FLAG_START_CHAR and FLAG_SEP_CHAR cannot be the same!") fmt.fprintfln(os.stderr, "[ERROR]: FLAG_START_CHAR and FLAG_SEP_CHAR cannot be the same!")
fmt.printfln("\t FLAG_START_CHAR= \"%s\"\t FLAG_SEP_CHAR= \"%s\"", FLAG_START_CHAR, FLAG_SEP_CHAR) fmt.printfln("\t FLAG_START_CHAR= \"%s\"\t FLAG_SEP_CHAR= \"%s\"", FLAG_START_CHAR, FLAG_SEP_CHAR)
os.exit(1) os.exit(1)
} }
if len(os.args) < 2 && FORCE_HELP_ON_EMPTY_ARGS {print_usage()} if len(os.args) < 2 && FORCE_HELP_ON_EMPTY_ARGS {print_usage()}
for &a in os.args { // skip the first arg (1:)
if a == "-h" || a == "-help" || a == "--help" {print_usage()} for &a in os.args[1:] {
if a == "-h" || a == "-help" || a == "--help" {print_usage(); os.exit(0)}
// sck: if the user has a custom flag format, copy that to the help flag too. // sck: if the user has a custom flag format, copy that to the help flag too.
if a == fmt.aprintf("%s%s", FLAG_START_CHAR, "help", allocator = flag_allocator) {print_usage()} if a == fmt.aprintf("%s%s", FLAG_START_CHAR, "help", allocator = context.temp_allocator) {print_usage(); os.exit(0)}
if a == fmt.aprintf("%s%s", FLAG_START_CHAR, "h", allocator = flag_allocator) {print_usage()} if a == fmt.aprintf("%s%s", FLAG_START_CHAR, "h", allocator = context.temp_allocator) {print_usage(); os.exit(0)}
defer free_all(context.temp_allocator) //clean up!
if a == os.args[0] {continue}
// Begin parsing flags...
for &f in all_flags { for &f in all_flags {
if f.parsed {continue} if f.parsed {continue}
a = strings.trim_prefix(a, FLAG_START_CHAR)
a = strings.trim_prefix(a, FLAG_START_CHAR)
name := a name := a
value: string value: string
@@ -151,10 +210,11 @@ parse_flags :: proc() {
} else { } else {
name = a name = a
fmt.printf("[INFO]: Could not read flag: %s. ", name) fmt.printf("[INFO]: Could not read flag: %s. ", name)
fmt.printfln("Want %s<flag>%s<value>\n", FLAG_START_CHAR, FLAG_SEP_CHAR) print_flag_format()
break break
} }
// sck: We are going to parse all the flags
if name == f.name && !f.parsed { if name == f.name && !f.parsed {
f.parsed = true f.parsed = true
switch &v in f.value { switch &v in f.value {
@@ -178,6 +238,26 @@ parse_flags :: proc() {
} }
v^ = parsed v^ = parsed
case ^i32:
if value == "" {break}
parsed, ok := strconv.parse_int(value)
if !ok {
fmt.fprintfln(os.stderr, "[ERROR] Failed to parse flag %s: got %T, wanted int",
f.name, value)
os.exit(1)
}
v^ = cast(i32)parsed
case ^i64:
if value == "" {break}
parsed, ok := strconv.parse_i64(value)
if !ok {
fmt.fprintfln(os.stderr, "[ERROR] Failed to parse flag %s: got %T, wanted int",
f.name, value)
os.exit(1)
}
v^ = parsed
case ^string: case ^string:
if value == "" {break} if value == "" {break}
// TODO: validate string? // TODO: validate string?
@@ -206,4 +286,13 @@ parse_flags :: proc() {
} }
} }
} }
for f in all_flags {
if f.required && !f.parsed {
// sck: INFO or ERROR?
fmt.fprintfln(os.stderr, "[INFO] Missing required flag: %s%s", FLAG_START_CHAR, f.name)
if !FORCE_HELP_ON_EMPTY_ARGS {fmt.fprintfln(os.stderr, "use -help or -h to view all flags")}
os.exit(1)
}
}
} }