From 30826707cde3e52133c08bedbc74ba6e84b46003 Mon Sep 17 00:00:00 2001 From: "l.weber" Date: Tue, 12 Aug 2025 11:42:29 +0200 Subject: [PATCH 01/10] update go module --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index df807f9..14050e1 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,6 @@ module catos.directory/tasks/awl-ntfy go 1.23 -require github.com/alexflint/go-arg v1.5.1 +require github.com/alexflint/go-arg v1.6.0 require github.com/alexflint/go-scalar v1.2.0 // indirect diff --git a/go.sum b/go.sum index 43a8011..588e7aa 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/alexflint/go-arg v1.5.1 h1:nBuWUCpuRy0snAG+uIJ6N0UvYxpxA0/ghA/AaHxlT8Y= github.com/alexflint/go-arg v1.5.1/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8= +github.com/alexflint/go-arg v1.6.0 h1:wPP9TwTPO54fUVQl4nZoxbFfKCcy5E6HBCumj1XVRSo= +github.com/alexflint/go-arg v1.6.0/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8= github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw= github.com/alexflint/go-scalar v1.2.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= From 0d7461fa92d755e251c325b0b23da6104419f46d Mon Sep 17 00:00:00 2001 From: cato-001 Date: Mon, 18 Aug 2025 07:44:19 +0200 Subject: [PATCH 02/10] use blueprint for flake setup --- default.nix | 21 --------------------- devshell.nix | 8 ++++++++ flake.nix | 40 ++++++++++++++++------------------------ package.nix | 9 +++++++++ result | 1 + shell.nix | 24 ------------------------ 6 files changed, 34 insertions(+), 69 deletions(-) delete mode 100644 default.nix create mode 100644 devshell.nix create mode 100644 package.nix create mode 120000 result delete mode 100644 shell.nix diff --git a/default.nix b/default.nix deleted file mode 100644 index 3f96341..0000000 --- a/default.nix +++ /dev/null @@ -1,21 +0,0 @@ -{ pkgs ? ( - let - inherit (builtins) fetchTree fromJSON readFile; - inherit ((fromJSON (readFile ./flake.lock)).nodes) nixpkgs gomod2nix; - in - import (fetchTree nixpkgs.locked) { - overlays = [ - (import "${fetchTree gomod2nix.locked}/overlay.nix") - ]; - } - ) -, buildGoApplication ? pkgs.buildGoApplication -}: - - buildGoApplication { - pname = "awl-ntfy"; - version = "0.1"; - pwd = ./.; - src = ./.; - modules = ./gomod2nix.toml; - } diff --git a/devshell.nix b/devshell.nix new file mode 100644 index 0000000..ef720cb --- /dev/null +++ b/devshell.nix @@ -0,0 +1,8 @@ +{ pkgs }: + +pkgs.mkShell { + packages = [ + pkgs.mkGoEnv { pwd = ./.; } + pkgs.gomod2nix + ]; +} diff --git a/flake.nix b/flake.nix index d09d65e..16cb1c6 100644 --- a/flake.nix +++ b/flake.nix @@ -1,31 +1,23 @@ { - description = "A basic gomod2nix flake"; + description = "Send notification for your local awl trash collection to your phone"; - inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - inputs.flake-utils.url = "github:numtide/flake-utils"; + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; - inputs.gomod2nix = { - url = "github:nix-community/gomod2nix"; - inputs = { - nixpkgs.follows = "nixpkgs"; - flake-utils.follows = "flake-utils"; + gomod2nix = { + url = "github:nix-community/gomod2nix"; + inputs = { + nixpkgs.follows = "nixpkgs"; + flake-utils.follows = "flake-utils"; + }; + }; + + blueprint = { + url = "github:numtide/blueprint"; + inputs.nixpkgs.follows = "nixpkgs"; }; }; - outputs = { self, nixpkgs, flake-utils, gomod2nix }: - (flake-utils.lib.eachDefaultSystem - (system: - let - pkgs = nixpkgs.legacyPackages.${system}; - callPackage = pkgs.callPackage; - in - { - packages.default = callPackage ./. { - inherit (gomod2nix.legacyPackages.${system}) buildGoApplication; - }; - devShells.default = callPackage ./shell.nix { - inherit (gomod2nix.legacyPackages.${system}) mkGoEnv gomod2nix; - }; - }) - ); + outputs = inputs: inputs.blueprint inputs; } diff --git a/package.nix b/package.nix new file mode 100644 index 0000000..d798923 --- /dev/null +++ b/package.nix @@ -0,0 +1,9 @@ +{ pkgs }: + +pkgs.buildGoApplication { + pname = "awl-ntfy"; + version = "0.1"; + pwd = ./.; + src = ./.; + modules = ./gomod2nix.toml; +} diff --git a/result b/result new file mode 120000 index 0000000..8ac3b08 --- /dev/null +++ b/result @@ -0,0 +1 @@ +/nix/store/zd7pa71h9lihln596dzpdg081kcn9g90-awl-ntfy-0.1 \ No newline at end of file diff --git a/shell.nix b/shell.nix deleted file mode 100644 index d4f21a8..0000000 --- a/shell.nix +++ /dev/null @@ -1,24 +0,0 @@ -{ pkgs ? ( - let - inherit (builtins) fetchTree fromJSON readFile; - inherit ((fromJSON (readFile ./flake.lock)).nodes) nixpkgs gomod2nix; - in - import (fetchTree nixpkgs.locked) { - overlays = [ - (import "${fetchTree gomod2nix.locked}/overlay.nix") - ]; - } - ) -, mkGoEnv ? pkgs.mkGoEnv -, gomod2nix ? pkgs.gomod2nix -}: - -let - goEnv = mkGoEnv { pwd = ./.; }; -in -pkgs.mkShell { - packages = [ - goEnv - gomod2nix - ]; -} From 7534b04fabedc4ebc86d81f05fe1ef0bb548f6ea Mon Sep 17 00:00:00 2001 From: cato-001 Date: Mon, 18 Aug 2025 07:56:18 +0200 Subject: [PATCH 03/10] fix nix flake --- flake.lock | 39 ++++++++++++++++++++++++++++++++++++++- flake.nix | 2 +- package.nix | 4 ++-- result | 1 - 4 files changed, 41 insertions(+), 5 deletions(-) delete mode 120000 result diff --git a/flake.lock b/flake.lock index a151ef3..2a59a6d 100644 --- a/flake.lock +++ b/flake.lock @@ -1,8 +1,29 @@ { "nodes": { + "blueprint": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ], + "systems": "systems" + }, + "locked": { + "lastModified": 1755332143, + "narHash": "sha256-jaiZPA5ND7HPJ4U/bzp+BKGOYR14+rIe9tC6XA4jBHU=", + "owner": "numtide", + "repo": "blueprint", + "rev": "3c8bf84e28df2be19cc6623cb3ceeb6fc0839b91", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "blueprint", + "type": "github" + } + }, "flake-utils": { "inputs": { - "systems": "systems" + "systems": "systems_2" }, "locked": { "lastModified": 1731533236, @@ -59,6 +80,7 @@ }, "root": { "inputs": { + "blueprint": "blueprint", "flake-utils": "flake-utils", "gomod2nix": "gomod2nix", "nixpkgs": "nixpkgs" @@ -78,6 +100,21 @@ "repo": "default", "type": "github" } + }, + "systems_2": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 16cb1c6..f4e75ef 100644 --- a/flake.nix +++ b/flake.nix @@ -19,5 +19,5 @@ }; }; - outputs = inputs: inputs.blueprint inputs; + outputs = inputs: inputs.blueprint { inherit inputs; }; } diff --git a/package.nix b/package.nix index d798923..7bbea09 100644 --- a/package.nix +++ b/package.nix @@ -1,6 +1,6 @@ -{ pkgs }: +{ perSystem }: -pkgs.buildGoApplication { +perSystem.gomod2nix.buildGoApplication { pname = "awl-ntfy"; version = "0.1"; pwd = ./.; diff --git a/result b/result deleted file mode 120000 index 8ac3b08..0000000 --- a/result +++ /dev/null @@ -1 +0,0 @@ -/nix/store/zd7pa71h9lihln596dzpdg081kcn9g90-awl-ntfy-0.1 \ No newline at end of file From f62531accb65925f8d4d401db3e4e47952b0ee2f Mon Sep 17 00:00:00 2001 From: cato-001 Date: Mon, 18 Aug 2025 07:59:00 +0200 Subject: [PATCH 04/10] update nix flake --- flake.lock | 12 ++++++------ go.sum | 2 -- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/flake.lock b/flake.lock index 2a59a6d..f7cc648 100644 --- a/flake.lock +++ b/flake.lock @@ -49,11 +49,11 @@ ] }, "locked": { - "lastModified": 1750314194, - "narHash": "sha256-SjpXWEeB+UIMzuCAF94PuyAXpJdnBLF45JvI6o4wKIU=", + "lastModified": 1754078208, + "narHash": "sha256-YVoIFDCDpYuU3riaDEJ3xiGdPOtsx4sR5eTzHTytPV8=", "owner": "nix-community", "repo": "gomod2nix", - "rev": "a5f75f563748599d448a4a076816041d7b0fc07e", + "rev": "7f963246a71626c7fc70b431a315c4388a0c95cf", "type": "github" }, "original": { @@ -64,11 +64,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1751271578, - "narHash": "sha256-P/SQmKDu06x8yv7i0s8bvnnuJYkxVGBWLWHaU+tt4YY=", + "lastModified": 1755186698, + "narHash": "sha256-wNO3+Ks2jZJ4nTHMuks+cxAiVBGNuEBXsT29Bz6HASo=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "3016b4b15d13f3089db8a41ef937b13a9e33a8df", + "rev": "fbcf476f790d8a217c3eab4e12033dc4a0f6d23c", "type": "github" }, "original": { diff --git a/go.sum b/go.sum index 588e7aa..5586a02 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -github.com/alexflint/go-arg v1.5.1 h1:nBuWUCpuRy0snAG+uIJ6N0UvYxpxA0/ghA/AaHxlT8Y= -github.com/alexflint/go-arg v1.5.1/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8= github.com/alexflint/go-arg v1.6.0 h1:wPP9TwTPO54fUVQl4nZoxbFfKCcy5E6HBCumj1XVRSo= github.com/alexflint/go-arg v1.6.0/go.mod h1:A7vTJzvjoaSTypg4biM5uYNTkJ27SkNTArtYXnlqVO8= github.com/alexflint/go-scalar v1.2.0 h1:WR7JPKkeNpnYIOfHRa7ivM21aWAdHD0gEWHCx+WQBRw= From 6d35ec5210bba8b5368dce9738834149c5db4a90 Mon Sep 17 00:00:00 2001 From: cato-001 Date: Sat, 23 Aug 2025 14:52:27 +0200 Subject: [PATCH 05/10] update go vendors --- vendor/github.com/alexflint/go-arg/README.md | 300 +++++++++++++++---- vendor/github.com/alexflint/go-arg/parse.go | 83 +++-- vendor/github.com/alexflint/go-arg/usage.go | 51 ++-- vendor/modules.txt | 2 +- 4 files changed, 329 insertions(+), 107 deletions(-) diff --git a/vendor/github.com/alexflint/go-arg/README.md b/vendor/github.com/alexflint/go-arg/README.md index f105b17..e9075ba 100644 --- a/vendor/github.com/alexflint/go-arg/README.md +++ b/vendor/github.com/alexflint/go-arg/README.md @@ -64,7 +64,7 @@ fmt.Println("Input:", args.Input) fmt.Println("Output:", args.Output) ``` -``` +```shell $ ./example src.txt x.out y.out z.out Input: src.txt Output: [x.out y.out z.out] @@ -80,12 +80,12 @@ arg.MustParse(&args) fmt.Println("Workers:", args.Workers) ``` -``` +```shell $ WORKERS=4 ./example Workers: 4 ``` -``` +```shell $ WORKERS=4 ./example --workers=6 Workers: 6 ``` @@ -100,12 +100,12 @@ arg.MustParse(&args) fmt.Println("Workers:", args.Workers) ``` -``` +```shell $ NUM_WORKERS=4 ./example Workers: 4 ``` -You can provide multiple values using the CSV (RFC 4180) format: +You can provide multiple values in environment variables using commas: ```go var args struct { @@ -115,12 +115,50 @@ arg.MustParse(&args) fmt.Println("Workers:", args.Workers) ``` -``` +```shell $ WORKERS='1,99' ./example Workers: [1 99] ``` +Command line arguments take precedence over environment variables: + +```go +var args struct { + Workers int `arg:"--count,env:NUM_WORKERS"` +} +arg.MustParse(&args) +fmt.Println("Workers:", args.Workers) +``` + +```shell +$ NUM_WORKERS=6 ./example +Workers: 6 +$ NUM_WORKERS=6 ./example --count 4 +Workers: 4 +``` + +Configuring a global environment variable name prefix is also possible: + +```go +var args struct { + Workers int `arg:"--count,env:NUM_WORKERS"` +} + +p, err := arg.NewParser(arg.Config{ + EnvPrefix: "MYAPP_", +}, &args) + +p.MustParse(os.Args[1:]) +fmt.Println("Workers:", args.Workers) +``` + +```shell +$ MYAPP_NUM_WORKERS=6 ./example +Workers: 6 +``` + ### Usage strings + ```go var args struct { Input string `arg:"positional"` @@ -158,20 +196,7 @@ var args struct { arg.MustParse(&args) ``` -### Default values (before v1.2) - -```go -var args struct { - Foo string - Bar bool -} -arg.Foo = "abc" -arg.MustParse(&args) -``` - -### Combining command line options, environment variables, and default values - -You can combine command line arguments, environment variables, and default values. Command line arguments take precedence over environment variables, which take precedence over default values. This means that we check whether a certain option was provided on the command line, then if not, we check for an environment variable (only if an `env` tag was provided), then if none is found, we check for a `default` tag containing a default value. +Command line arguments take precedence over environment variables, which take precedence over default values. This means that we check whether a certain option was provided on the command line, then if not, we check for an environment variable (only if an `env` tag was provided), then if none is found, we check for a `default` tag containing a default value. ```go var args struct { @@ -182,9 +207,6 @@ arg.MustParse(&args) #### Ignoring environment variables and/or default values -The values in an existing structure can be kept in-tact by ignoring environment -variables and/or default values. - ```go var args struct { Test string `arg:"-t,env:TEST" default:"something"` @@ -195,10 +217,11 @@ p, err := arg.NewParser(arg.Config{ IgnoreDefault: true, }, &args) -err = p.Parse(os.Args) +err = p.Parse(os.Args[1:]) ``` ### Arguments with multiple values + ```go var args struct { Database string @@ -214,6 +237,7 @@ Fetching the following IDs from foo: [1 2 3] ``` ### Arguments that can be specified multiple times, mixed with positionals + ```go var args struct { Commands []string `arg:"-c,separate"` @@ -231,6 +255,7 @@ Databases [db1 db2 db3] ``` ### Arguments with keys and values + ```go var args struct { UserIDs map[string]int @@ -244,24 +269,6 @@ fmt.Println(args.UserIDs) map[john:123 mary:456] ``` -### Custom validation -```go -var args struct { - Foo string - Bar string -} -p := arg.MustParse(&args) -if args.Foo == "" && args.Bar == "" { - p.Fail("you must provide either --foo or --bar") -} -``` - -```shell -./example -Usage: samples [--foo FOO] [--bar BAR] -error: you must provide either --foo or --bar -``` - ### Version strings ```go @@ -284,6 +291,28 @@ $ ./example --version someprogram 4.3.0 ``` +> **Note** +> If a `--version` flag is defined in `args` or any subcommand, it overrides the built-in versioning. + +### Custom validation + +```go +var args struct { + Foo string + Bar string +} +p := arg.MustParse(&args) +if args.Foo == "" && args.Bar == "" { + p.Fail("you must provide either --foo or --bar") +} +``` + +```shell +./example +Usage: samples [--foo FOO] [--bar BAR] +error: you must provide either --foo or --bar +``` + ### Overriding option names ```go @@ -308,13 +337,11 @@ Options: --help, -h display this help and exit ``` - ### Embedded structs The fields of embedded structs are treated just like regular fields: ```go - type DatabaseOptions struct { Host string Username string @@ -382,6 +409,7 @@ func main() { fmt.Printf("%#v\n", args.Name) } ``` + ```shell $ ./example --name=foo.bar main.NameDotName{Head:"foo", Tail:"bar"} @@ -418,6 +446,7 @@ func main() { fmt.Printf("%#v\n", args.Name) } ``` + ```shell $ ./example --help Usage: test [--name NAME] @@ -432,8 +461,6 @@ main.NameDotName{Head:"file", Tail:"txt"} ### Custom placeholders -*Introduced in version 1.3.0* - Use the `placeholder` tag to control which placeholder text is used in the usage text. ```go @@ -445,6 +472,7 @@ var args struct { } arg.MustParse(&args) ``` + ```shell $ ./example -h Usage: example [--optimize LEVEL] [--maxjobs N] SRC [DST [DST ...]] @@ -521,8 +549,6 @@ For more information visit github.com/alexflint/go-arg ### Subcommands -*Introduced in version 1.1.0* - Subcommands are commonly used in tools that wish to group multiple functions into a single program. An example is the `git` tool: ```shell $ git checkout [arguments specific to checking out code] @@ -583,15 +609,187 @@ if p.Subcommand() == nil { } ``` +### Custom handling of --help and --version + +The following reproduces the internal logic of `MustParse` for the simple case where +you are not using subcommands or --version. This allows you to respond +programatically to --help, and to any errors that come up. + +```go +var args struct { + Something string +} + +p, err := arg.NewParser(arg.Config{}, &args) +if err != nil { + log.Fatalf("there was an error in the definition of the Go struct: %v", err) +} + +err = p.Parse(os.Args[1:]) +switch { +case err == arg.ErrHelp: // indicates that user wrote "--help" on command line + p.WriteHelp(os.Stdout) + os.Exit(0) +case err != nil: + fmt.Printf("error: %v\n", err) + p.WriteUsage(os.Stdout) + os.Exit(1) +} +``` + +```shell +$ go run ./example --help +Usage: ./example --something SOMETHING + +Options: + --something SOMETHING + --help, -h display this help and exit + +$ ./example --wrong +error: unknown argument --wrong +Usage: ./example --something SOMETHING + +$ ./example +error: --something is required +Usage: ./example --something SOMETHING +``` + +To also handle --version programatically, use the following: + +```go +type args struct { + Something string +} + +func (args) Version() string { + return "1.2.3" +} + +func main() { + var args args + p, err := arg.NewParser(arg.Config{}, &args) + if err != nil { + log.Fatalf("there was an error in the definition of the Go struct: %v", err) + } + + err = p.Parse(os.Args[1:]) + switch { + case err == arg.ErrHelp: // found "--help" on command line + p.WriteHelp(os.Stdout) + os.Exit(0) + case err == arg.ErrVersion: // found "--version" on command line + fmt.Println(args.Version()) + os.Exit(0) + case err != nil: + fmt.Printf("error: %v\n", err) + p.WriteUsage(os.Stdout) + os.Exit(1) + } + + fmt.Printf("got %q\n", args.Something) +} +``` + +```shell +$ ./example --version +1.2.3 + +$ go run ./example --help +1.2.3 +Usage: example --something SOMETHING + +Options: + --something SOMETHING + --help, -h display this help and exit + +$ ./example --wrong +1.2.3 +error: unknown argument --wrong +Usage: example --something SOMETHING + +$ ./example +error: --something is required +Usage: example --something SOMETHING +``` + +To generate subcommand-specific help messages, use the following most general version +(this also works in absence of subcommands but is a bit more complex): + +```go +type fetchCmd struct { + Count int +} + +type args struct { + Something string + Fetch *fetchCmd `arg:"subcommand"` +} + +func (args) Version() string { + return "1.2.3" +} + +func main() { + var args args + p, err := arg.NewParser(arg.Config{}, &args) + if err != nil { + log.Fatalf("there was an error in the definition of the Go struct: %v", err) + } + + err = p.Parse(os.Args[1:]) + switch { + case err == arg.ErrHelp: // found "--help" on command line + p.WriteHelpForSubcommand(os.Stdout, p.SubcommandNames()...) + os.Exit(0) + case err == arg.ErrVersion: // found "--version" on command line + fmt.Println(args.Version()) + os.Exit(0) + case err != nil: + fmt.Printf("error: %v\n", err) + p.WriteUsageForSubcommand(os.Stdout, p.SubcommandNames()...) + os.Exit(1) + } +} +``` + +```shell +$ ./example --version +1.2.3 + +$ ./example --help +1.2.3 +Usage: example [--something SOMETHING] [] + +Options: + --something SOMETHING + --help, -h display this help and exit + --version display version and exit + +Commands: + fetch + +$ ./example fetch --help +1.2.3 +Usage: example fetch [--count COUNT] + +Options: + --count COUNT + +Global options: + --something SOMETHING + --help, -h display this help and exit + --version display version and exit +``` + ### API Documentation -https://godoc.org/github.com/alexflint/go-arg +https://pkg.go.dev/github.com/alexflint/go-arg ### Rationale There are many command line argument parsing libraries for Go, including one in the standard library, so why build another? -The `flag` library that ships in the standard library seems awkward to me. Positional arguments must preceed options, so `./prog x --foo=1` does what you expect but `./prog --foo=1 x` does not. It also does not allow arguments to have both long (`--foo`) and short (`-f`) forms. +The `flag` library that ships in the standard library seems awkward to me. Positional arguments must precede options, so `./prog x --foo=1` does what you expect but `./prog --foo=1 x` does not. It also does not allow arguments to have both long (`--foo`) and short (`-f`) forms. Many third-party argument parsing libraries are great for writing sophisticated command line interfaces, but feel to me like overkill for a simple script with a few flags. @@ -599,4 +797,4 @@ The idea behind `go-arg` is that Go already has an excellent way to describe dat ### Backward compatibility notes -Earlier versions of this library required the help text to be part of the `arg` tag. This is still supported but is now deprecated. Instead, you should use a separate `help` tag, described above, which removes most of the limits on the text you can write. In particular, you will need to use the new `help` tag if your help text includes any commas. +Earlier versions of this library required the help text to be part of the `arg` tag. This is still supported but is now deprecated. Instead, you should use a separate `help` tag, described above, which makes it possible to include commas inside help text. diff --git a/vendor/github.com/alexflint/go-arg/parse.go b/vendor/github.com/alexflint/go-arg/parse.go index 98c21cd..d5eee12 100644 --- a/vendor/github.com/alexflint/go-arg/parse.go +++ b/vendor/github.com/alexflint/go-arg/parse.go @@ -90,7 +90,7 @@ func mustParse(config Config, dest ...interface{}) *Parser { p, err := NewParser(config, dest...) if err != nil { fmt.Fprintln(config.Out, err) - config.Exit(-1) + config.Exit(2) return nil } @@ -131,6 +131,9 @@ type Config struct { // subcommand StrictSubcommands bool + // EnvPrefix instructs the library to use a name prefix when reading environment variables. + EnvPrefix string + // Exit is called to terminate the process with an error code (defaults to os.Exit) Exit func(int) @@ -235,7 +238,7 @@ func NewParser(config Config, dests ...interface{}) (*Parser, error) { panic(fmt.Sprintf("%s is not a pointer (did you forget an ampersand?)", t)) } - cmd, err := cmdFromStruct(name, path{root: i}, t) + cmd, err := cmdFromStruct(name, path{root: i}, t, config.EnvPrefix) if err != nil { return nil, err } @@ -282,10 +285,17 @@ func NewParser(config Config, dests ...interface{}) (*Parser, error) { } } + // Set the parent of the subcommands to be the top-level command + // to make sure that global options work when there is more than one + // dest supplied. + for _, subcommand := range p.cmd.subcommands { + subcommand.parent = p.cmd + } + return &p, nil } -func cmdFromStruct(name string, dest path, t reflect.Type) (*command, error) { +func cmdFromStruct(name string, dest path, t reflect.Type, envPrefix string) (*command, error) { // commands can only be created from pointers to structs if t.Kind() != reflect.Ptr { return nil, fmt.Errorf("subcommands must be pointers to structs but %s is a %s", @@ -372,9 +382,9 @@ func cmdFromStruct(name string, dest path, t reflect.Type) (*command, error) { case key == "env": // Use override name if provided if value != "" { - spec.env = value + spec.env = envPrefix + value } else { - spec.env = strings.ToUpper(field.Name) + spec.env = envPrefix + strings.ToUpper(field.Name) } case key == "subcommand": // decide on a name for the subcommand @@ -389,7 +399,7 @@ func cmdFromStruct(name string, dest path, t reflect.Type) (*command, error) { } // parse the subcommand recursively - subcmd, err := cmdFromStruct(cmdnames[0], subdest, field.Type) + subcmd, err := cmdFromStruct(cmdnames[0], subdest, field.Type, envPrefix) if err != nil { errs = append(errs, err.Error()) return false @@ -493,8 +503,15 @@ func cmdFromStruct(name string, dest path, t reflect.Type) (*command, error) { return &cmd, nil } -// Parse processes the given command line option, storing the results in the field -// of the structs from which NewParser was constructed +// Parse processes the given command line option, storing the results in the fields +// of the structs from which NewParser was constructed. +// +// It returns ErrHelp if "--help" is one of the command line args and ErrVersion if +// "--version" is one of the command line args (the latter only applies if the +// destination struct passed to NewParser implements Versioned.) +// +// To respond to --help and --version in the way that MustParse does, see examples +// in the README under "Custom handling of --help and --version". func (p *Parser) Parse(args []string) error { err := p.process(args) if err != nil { @@ -608,7 +625,7 @@ func (p *Parser) process(args []string) error { // must use explicit for loop, not range, because we manipulate i inside the loop for i := 0; i < len(args); i++ { arg := args[i] - if arg == "--" { + if arg == "--" && !allpositional { allpositional = true continue } @@ -683,7 +700,7 @@ func (p *Parser) process(args []string) error { if spec.cardinality == multiple { var values []string if value == "" { - for i+1 < len(args) && !isFlag(args[i+1]) && args[i+1] != "--" { + for i+1 < len(args) && isValue(args[i+1], spec.field.Type, specs) && args[i+1] != "--" { values = append(values, args[i+1]) i++ if spec.separate { @@ -711,7 +728,7 @@ func (p *Parser) process(args []string) error { if i+1 == len(args) { return fmt.Errorf("missing value for %s", arg) } - if !nextIsNumeric(spec.field.Type, args[i+1]) && isFlag(args[i+1]) { + if !isValue(args[i+1], spec.field.Type, specs) { return fmt.Errorf("missing value for %s", arg) } value = args[i+1] @@ -736,13 +753,13 @@ func (p *Parser) process(args []string) error { if spec.cardinality == multiple { err := setSliceOrMap(p.val(spec.dest), positionals, true) if err != nil { - return fmt.Errorf("error processing %s: %v", spec.field.Name, err) + return fmt.Errorf("error processing %s: %v", spec.placeholder, err) } positionals = nil } else { err := scalar.ParseValue(p.val(spec.dest), positionals[0]) if err != nil { - return fmt.Errorf("error processing %s: %v", spec.field.Name, err) + return fmt.Errorf("error processing %s: %v", spec.placeholder, err) } positionals = positionals[1:] } @@ -757,18 +774,13 @@ func (p *Parser) process(args []string) error { continue } - name := strings.ToLower(spec.field.Name) - if spec.long != "" && !spec.positional { - name = "--" + spec.long - } - if spec.required { if spec.short == "" && spec.long == "" { msg := fmt.Sprintf("environment variable %s is required", spec.env) return errors.New(msg) } - msg := fmt.Sprintf("%s is required", name) + msg := fmt.Sprintf("%s is required", spec.placeholder) if spec.env != "" { msg += " (or environment variable " + spec.env + ")" } @@ -789,24 +801,31 @@ func (p *Parser) process(args []string) error { return nil } -func nextIsNumeric(t reflect.Type, s string) bool { - switch t.Kind() { - case reflect.Ptr: - return nextIsNumeric(t.Elem(), s) - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - v := reflect.New(t) - err := scalar.ParseValue(v, s) - return err == nil - default: - return false - } -} - // isFlag returns true if a token is a flag such as "-v" or "--user" but not "-" or "--" func isFlag(s string) bool { return strings.HasPrefix(s, "-") && strings.TrimLeft(s, "-") != "" } +// isValue returns true if a token should be consumed as a value for a flag of type t. This +// is almost always the inverse of isFlag. The one exception is for negative numbers, in which +// case we check the list of active options and return true if its not present there. +func isValue(s string, t reflect.Type, specs []*spec) bool { + switch t.Kind() { + case reflect.Ptr, reflect.Slice: + return isValue(s, t.Elem(), specs) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Float32, reflect.Float64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + v := reflect.New(t) + err := scalar.ParseValue(v, s) + // if value can be parsed and is not an explicit option declared elsewhere, then use it as a value + if err == nil && (!strings.HasPrefix(s, "-") || findOption(specs, strings.TrimPrefix(s, "-")) == nil) { + return true + } + } + + // default case that is used in all cases other than negative numbers: inverse of isFlag + return !isFlag(s) +} + // val returns a reflect.Value corresponding to the current value for the // given path func (p *Parser) val(dest path) reflect.Value { diff --git a/vendor/github.com/alexflint/go-arg/usage.go b/vendor/github.com/alexflint/go-arg/usage.go index 6b578a5..d091bcb 100644 --- a/vendor/github.com/alexflint/go-arg/usage.go +++ b/vendor/github.com/alexflint/go-arg/usage.go @@ -9,13 +9,13 @@ import ( // the width of the left column const colWidth = 25 -// Fail prints usage information to stderr and exits with non-zero status +// Fail prints usage information to p.Config.Out and exits with status code 2. func (p *Parser) Fail(msg string) { p.FailSubcommand(msg) } -// FailSubcommand prints usage information for a specified subcommand to stderr, -// then exits with non-zero status. To write usage information for a top-level +// FailSubcommand prints usage information for a specified subcommand to p.Config.Out, +// then exits with status code 2. To write usage information for a top-level // subcommand, provide just the name of that subcommand. To write usage // information for a subcommand that is nested under another subcommand, provide // a sequence of subcommand names starting with the top-level subcommand and so @@ -27,7 +27,7 @@ func (p *Parser) FailSubcommand(msg string, subcommand ...string) error { } fmt.Fprintln(p.config.Out, "error:", msg) - p.config.Exit(-1) + p.config.Exit(2) return nil } @@ -59,10 +59,6 @@ func (p *Parser) WriteUsageForSubcommand(w io.Writer, subcommand ...string) erro } } - if p.version != "" { - fmt.Fprintln(w, p.version) - } - // print the beginning of the usage string fmt.Fprintf(w, "Usage: %s", p.cmd.name) for _, s := range subcommand { @@ -208,6 +204,9 @@ func (p *Parser) WriteHelpForSubcommand(w io.Writer, subcommand ...string) error positionals = append(positionals, spec) case spec.long != "": longOptions = append(longOptions, spec) + if spec.long == "version" { + hasVersionOption = true + } case spec.short != "": shortOptions = append(shortOptions, spec) case spec.short == "" && spec.long == "": @@ -215,16 +214,36 @@ func (p *Parser) WriteHelpForSubcommand(w io.Writer, subcommand ...string) error } } + // obtain a flattened list of options from all ancestors + // also determine if any ancestor has a version option spec + var globals []*spec + ancestor := cmd.parent + for ancestor != nil { + for _, spec := range ancestor.specs { + if spec.long == "version" { + hasVersionOption = true + break + } + } + globals = append(globals, ancestor.specs...) + ancestor = ancestor.parent + } + if p.description != "" { fmt.Fprintln(w, p.description) } + + if !hasVersionOption && p.version != "" { + fmt.Fprintln(w, p.version) + } + p.WriteUsageForSubcommand(w, subcommand...) // write the list of positionals if len(positionals) > 0 { fmt.Fprint(w, "\nPositional arguments:\n") for _, spec := range positionals { - print(w, spec.placeholder, spec.help) + print(w, spec.placeholder, spec.help, withDefault(spec.defaultString), withEnv(spec.env)) } } @@ -236,28 +255,14 @@ func (p *Parser) WriteHelpForSubcommand(w io.Writer, subcommand ...string) error } for _, spec := range longOptions { p.printOption(w, spec) - if spec.long == "version" { - hasVersionOption = true - } } } - // obtain a flattened list of options from all ancestors - var globals []*spec - ancestor := cmd.parent - for ancestor != nil { - globals = append(globals, ancestor.specs...) - ancestor = ancestor.parent - } - // write the list of global options if len(globals) > 0 { fmt.Fprint(w, "\nGlobal options:\n") for _, spec := range globals { p.printOption(w, spec) - if spec.long == "version" { - hasVersionOption = true - } } } diff --git a/vendor/modules.txt b/vendor/modules.txt index 9af7e7d..ef9ca63 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,4 +1,4 @@ -# github.com/alexflint/go-arg v1.5.1 +# github.com/alexflint/go-arg v1.6.0 ## explicit; go 1.18 github.com/alexflint/go-arg # github.com/alexflint/go-scalar v1.2.0 From b6634b7eaa99e9511c70b82dbcd8a71f1eac99d9 Mon Sep 17 00:00:00 2001 From: cato-001 Date: Sat, 23 Aug 2025 14:59:00 +0200 Subject: [PATCH 06/10] update nix flake --- flake.lock | 14 +++++++------- flake.nix | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/flake.lock b/flake.lock index f7cc648..f659388 100644 --- a/flake.lock +++ b/flake.lock @@ -49,11 +49,11 @@ ] }, "locked": { - "lastModified": 1754078208, - "narHash": "sha256-YVoIFDCDpYuU3riaDEJ3xiGdPOtsx4sR5eTzHTytPV8=", + "lastModified": 1755527833, + "narHash": "sha256-pmoZQXlCbOO/kEJuvKrE8Z03MM+5YMvcYFy0W7M/ZNU=", "owner": "nix-community", "repo": "gomod2nix", - "rev": "7f963246a71626c7fc70b431a315c4388a0c95cf", + "rev": "4212d75925019d716ea6ca525e9cd7b47e7cb27a", "type": "github" }, "original": { @@ -64,16 +64,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1755186698, - "narHash": "sha256-wNO3+Ks2jZJ4nTHMuks+cxAiVBGNuEBXsT29Bz6HASo=", + "lastModified": 1755704039, + "narHash": "sha256-gKlP0LbyJ3qX0KObfIWcp5nbuHSb5EHwIvU6UcNBg2A=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "fbcf476f790d8a217c3eab4e12033dc4a0f6d23c", + "rev": "9cb344e96d5b6918e94e1bca2d9f3ea1e9615545", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-unstable", + "ref": "nixos-25.05", "repo": "nixpkgs", "type": "github" } diff --git a/flake.nix b/flake.nix index f4e75ef..ba91c86 100644 --- a/flake.nix +++ b/flake.nix @@ -2,7 +2,7 @@ description = "Send notification for your local awl trash collection to your phone"; inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05"; flake-utils.url = "github:numtide/flake-utils"; gomod2nix = { From ee642d526794456ffcf3aed2fd92be7fd87979b3 Mon Sep 17 00:00:00 2001 From: cato-001 Date: Sat, 23 Aug 2025 22:49:22 +0200 Subject: [PATCH 07/10] update ntfy channel special characters --- main.go | 3 +++ ntfy.go | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index 6795ee4..07ac0a7 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( "fmt" "os" + "strings" arg "github.com/alexflint/go-arg" ) @@ -29,7 +30,9 @@ func main() { args.Pink, args.Yellow, args.Blue, args.Gray, args.Brown = true, true, true, true, true } + replacer := strings.NewReplacer("ö", "oe", "ä", "ae", "ü", "ue", "ß", "ss") NotifyChannel = fmt.Sprintf("%s-%d", args.Street, args.Home) + NotifyChannel = replacer.Replace(NotifyChannel) streetNumbers, err := GetStreetNumbers() if err != nil { diff --git a/ntfy.go b/ntfy.go index a3e34f4..41f6cd4 100644 --- a/ntfy.go +++ b/ntfy.go @@ -58,7 +58,7 @@ func SendNotification(message string) error { func SendErr(err error) error { fmt.Println(err) - topic := fmt.Sprintf("https://ntfy.sh/awl-neuss-%s-err", NotifyChannel) - _, err = http.Post(topic, "text/plain", strings.NewReader(err.Error())) + topic := "https://ntfy.sh/awl-neuss-err" + _, err = http.Post(topic, "text/plain", strings.NewReader(NotifyChannel + "\n" + err.Error())) return err } From 318240993c61be7b857c79b6a540bc79432d85c2 Mon Sep 17 00:00:00 2001 From: cato-001 Date: Sat, 23 Aug 2025 23:00:46 +0200 Subject: [PATCH 08/10] add listing of streets --- main.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/main.go b/main.go index 07ac0a7..e63d48b 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "fmt" "os" "strings" @@ -17,6 +18,7 @@ func main() { Blue bool `arg:"--blue"` Gray bool `arg:"--gray"` Brown bool `arg:"--brown"` + List bool `arg:"--list"` } parser, err := arg.NewParser(arg.Config{}, &args) @@ -40,6 +42,14 @@ func main() { return } + if args.List { + content, err := json.Marshal(streetNumbers) + if err == nil { + fmt.Println(string(content)) + } + return + } + streetNumber, ok := streetNumbers[args.Street] if !ok { fmt.Println("street could not be found:", args.Street) From ab793ad49866ea1c5709f5398fd4fe7eb560255a Mon Sep 17 00:00:00 2001 From: cato-001 Date: Sun, 24 Aug 2025 22:18:33 +0200 Subject: [PATCH 09/10] use nixpkgs-unstable --- flake.lock | 14 +++++++------- flake.nix | 2 +- result | 1 + 3 files changed, 9 insertions(+), 8 deletions(-) create mode 120000 result diff --git a/flake.lock b/flake.lock index f659388..66e3f8f 100644 --- a/flake.lock +++ b/flake.lock @@ -49,11 +49,11 @@ ] }, "locked": { - "lastModified": 1755527833, - "narHash": "sha256-pmoZQXlCbOO/kEJuvKrE8Z03MM+5YMvcYFy0W7M/ZNU=", + "lastModified": 1756047880, + "narHash": "sha256-JeuGh9kA1SPL70fnvpLxkIkCWpTjtoPaus3jzvdna0k=", "owner": "nix-community", "repo": "gomod2nix", - "rev": "4212d75925019d716ea6ca525e9cd7b47e7cb27a", + "rev": "47d628dc3b506bd28632e47280c6b89d3496909d", "type": "github" }, "original": { @@ -64,16 +64,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1755704039, - "narHash": "sha256-gKlP0LbyJ3qX0KObfIWcp5nbuHSb5EHwIvU6UcNBg2A=", + "lastModified": 1755615617, + "narHash": "sha256-HMwfAJBdrr8wXAkbGhtcby1zGFvs+StOp19xNsbqdOg=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "9cb344e96d5b6918e94e1bca2d9f3ea1e9615545", + "rev": "20075955deac2583bb12f07151c2df830ef346b4", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-25.05", + "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } diff --git a/flake.nix b/flake.nix index ba91c86..f4e75ef 100644 --- a/flake.nix +++ b/flake.nix @@ -2,7 +2,7 @@ description = "Send notification for your local awl trash collection to your phone"; inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; flake-utils.url = "github:numtide/flake-utils"; gomod2nix = { diff --git a/result b/result new file mode 120000 index 0000000..8300dbd --- /dev/null +++ b/result @@ -0,0 +1 @@ +/nix/store/9y0rgw9gsskk35cs9xdmqmh6ldvqv6cq-awl-ntfy-0.1 \ No newline at end of file From 0ca27b6f475449c16a23ef15acb1fe838504cdc3 Mon Sep 17 00:00:00 2001 From: Cato <46494570+cato-001@users.noreply.github.com> Date: Mon, 26 Jan 2026 10:19:56 +0100 Subject: [PATCH 10/10] Delete result --- result | 1 - 1 file changed, 1 deletion(-) delete mode 120000 result diff --git a/result b/result deleted file mode 120000 index 8300dbd..0000000 --- a/result +++ /dev/null @@ -1 +0,0 @@ -/nix/store/9y0rgw9gsskk35cs9xdmqmh6ldvqv6cq-awl-ntfy-0.1 \ No newline at end of file