Compare commits

...
17 Commits
Author SHA1 Message Date
Simon Kellet 45addc5822 added i32 and i64 support 2026-08-12 07:32:29 +01:00
Simon Kellet be34abc683 added @(private) and nil checks 2026-08-12 07:16:53 +01:00
Simon Kellet b3d9634a91 better help when using required and FORCE_HELP_ON_EMPTY_ARGS 2026-08-05 21:40:56 +01:00
Simon Kellet c04b004f9c removed debug line, added free_all to allocator 2026-07-15 16:48:34 +01:00
Simon Kellet 2fa8bf4024 added temp_alloc for string builder in help flag. stops mem leak 2026-07-15 13:15:54 +01:00
Simon Kellet a8c2df53c2 added required string, removed print_usage on missing required flag 2026-07-10 22:54:51 +01:00
Simon Kellet e7b877cbad mirror notice 2026-07-10 12:08:24 +01:00
Simon Kellet 1210cc5a4c added license 2026-07-09 21:56:25 +01:00
Simon Kellet 45610c07f4 updated example to show off required flags 2026-07-09 19:43:58 +01:00
Simon Kellet d1b8884554 new feature: required flags 2026-07-09 19:42:09 +01:00
Simon Kellet a52445e536 updated example to show off required flags 2026-07-09 19:41:47 +01:00
Simon Kellet c93be3493d updated for required flags + TODO section 2026-07-09 19:41:12 +01:00
Simon Kellet 9ea1872f87 main to example 2026-07-09 17:46:55 +01:00
Simon Kellet 5128851965 added zlib 2026-07-09 16:37:03 +01:00
Simon Kellet 28329fa0b7 example custom flag format 2026-07-09 16:36:25 +01:00
Simon Kellet c675f6b659 added doc for custom flag foramt 2026-07-09 16:36:04 +01:00
Simon Kellet 49eaaeabf5 help flag now follows custom formatting 2026-07-09 16:32:34 +01:00
4 changed files with 215 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.
+39 -3
View File
@@ -19,8 +19,14 @@ This a very simple implementation of Go-like flags in Odin
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:
@@ -28,9 +34,11 @@ And on the command line, you pass flags like so:
./<program name> -isNumber=45 -isRunning=false ......
```
## Usage flag: -help
***Supports: bool, int, string, f32 and f64***
The package also contains a help context menu. This comes default and can be called like so
## 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"):
```txt
./<program name> -help
@@ -52,6 +60,23 @@ The package also contains a help context menu. This comes default and can be cal
**Note:** You can set the boolean *FORCE_HELP_ON_EMPTY_ARGS* to allow for the help context menu to be displayed when no arguments are passed
## 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!)
```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
If needed, you are able to setup a custom allocator like so:
@@ -66,3 +91,14 @@ If needed, you are able to setup a custom allocator like so:
fleg.init_custom_allocator(arena_alloc)
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}**)
+7 -5
View File
@@ -1,6 +1,7 @@
package main
import "core:fmt"
import vmem "core:mem/virtual"
import "fleg"
main :: proc(){
@@ -12,11 +13,12 @@ main :: proc(){
defer vmem.arena_destroy(&arena) //clean
fleg.init_custom_allocator(arena_alloc)
defer fleg.destroy()
*/
fleg.FLAG_START_CHAR = "--"
fleg.FLAG_SEP_CHAR = ":"
fleg.FORCE_HELP_ON_EMPTY_ARGS = true
fleg.FLAG_START_CHAR = ":"
fleg.FLAG_SEP_CHAR = "="
//fleg.FORCE_HELP_ON_EMPTY_ARGS = true
defer fleg.destroy()
isRunning: bool
@@ -24,8 +26,8 @@ main :: proc(){
isString: string
isFloat: f32
fleg.BoolVar(&isRunning, "isRunning", true, "A select piece of code is running!")
fleg.IntVar(&isNumber, "isNumber", 3434, "A number!")
fleg.BoolVar(&isRunning, "isRunning", false, "A select piece of code is running!", required = true)
fleg.IntVar(&isNumber, "isNumber", 3434, "A number!", required = true)
fleg.StringVar(&isString, "isString", "Hellope!", "A string!")
fleg.Float32Var(&isFloat, "isFloat", 3.1415, "A float!")
fleg.parse_flags()
+148 -25
View File
@@ -1,3 +1,25 @@
/*
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.
*/
#+vet explicit-allocators
package fleg
@@ -11,22 +33,32 @@ FORCE_HELP_ON_EMPTY_ARGS := false
FLAG_START_CHAR := "-"
FLAG_SEP_CHAR := "="
@(private)
Flag_Value_Ptr :: union {
^bool,
^int,
^i32,
^i64,
^string,
^f32,
^f64,
}
@(private)
Flag :: struct {
name: string,
value: Flag_Value_Ptr,
usage: string,
parsed: bool, // sck: flag to check if it has been parsed
required: bool, // sck: required flag
}
// Global dynamic array of Flags
@(private)
all_flags: [dynamic]Flag
// runtime allocator for the flags
@(private)
flag_allocator: runtime.Allocator
// sck: Used to create a customer allocator
@@ -38,78 +70,139 @@ init_custom_allocator :: proc(allocator: runtime.Allocator) {
}
destroy :: proc() {
free_all(flag_allocator)
delete(all_flags)
all_flags = {} // sck: needed?
all_flags = {}
}
@(private)
print_flags :: proc() {
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() {
fmt.println("\tUsage: ")
fmt.println("\n\tFlag format for this program:")
fmt.printfln("\t%s<flag>%s<value>\n", FLAG_START_CHAR, FLAG_SEP_CHAR)
for f in all_flags {
req_msg: string
if f.required { req_msg = " (required)"}
switch v in f.value {
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:
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:
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:
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:
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
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
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
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
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
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() {
// sck: We cannot have the start and seperator formats being the same
if FLAG_START_CHAR == FLAG_SEP_CHAR {
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)
os.exit(1)
}
if len(os.args) < 2 && FORCE_HELP_ON_EMPTY_ARGS {print_usage()}
for &a in os.args {
if a == "-h" || a == "-help" || a == "--help" {print_usage()}
if a == os.args[0] {continue}
// skip the first arg (1:)
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.
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 = context.temp_allocator) {print_usage(); os.exit(0)}
defer free_all(context.temp_allocator) //clean up!
// Begin parsing flags...
for &f in all_flags {
if f.parsed {continue}
//a = strings.trim_prefix(a, "-")
a = strings.trim_prefix(a, FLAG_START_CHAR)
a = strings.trim_prefix(a, FLAG_START_CHAR)
name := a
value: string
//split := strings.index_any(a, "=")
split := strings.index_any(a, FLAG_SEP_CHAR)
if split >= 0 {
name = a[:split]
@@ -117,10 +210,11 @@ parse_flags :: proc() {
} else {
name = a
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
}
// sck: We are going to parse all the flags
if name == f.name && !f.parsed {
f.parsed = true
switch &v in f.value {
@@ -144,6 +238,26 @@ parse_flags :: proc() {
}
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:
if value == "" {break}
// TODO: validate string?
@@ -172,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)
}
}
}