From 8854d5c79f7dc5dd39f030472ddd2ab6be19b69a Mon Sep 17 00:00:00 2001 From: Snider Date: Fri, 20 Mar 2026 12:15:57 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20utils.go=20=E2=80=94=20FilterArgs,=20Pa?= =?UTF-8?q?rseFlag=20with=20short/long=20flag=20rules?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - FilterArgs: removes empty strings and Go test runner flags - ParseFlag: single dash (-v, -🔥) must be 1 char, double dash (--verbose) must be 2+ chars - Cli.Run() now uses FilterArgs and ParseFlag — no test flag awareness in surface layer - Invalid flags silently ignored (e.g. -verbose, --v) 221 tests, 79.7% coverage. Co-Authored-By: Virgil --- pkg/core/cli.go | 28 +++++----------- pkg/core/utils.go | 67 ++++++++++++++++++++++++++++++++++++++ tests/utils_test.go | 78 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 153 insertions(+), 20 deletions(-) create mode 100644 pkg/core/utils.go create mode 100644 tests/utils_test.go diff --git a/pkg/core/cli.go b/pkg/core/cli.go index 61375ec..af7a158 100644 --- a/pkg/core/cli.go +++ b/pkg/core/cli.go @@ -34,13 +34,7 @@ func (cl *Cli) Run(args ...string) Result[any] { args = os.Args[1:] } - // Filter out empty args and test flags - var clean []string - for _, a := range args { - if a != "" && !strings.HasPrefix(a, "-test.") { - clean = append(clean, a) - } - } + clean := FilterArgs(args) if cl.core == nil || cl.core.commands == nil || len(cl.core.commands.commands) == 0 { // No commands registered — print banner and exit @@ -76,23 +70,17 @@ func (cl *Cli) Run(args ...string) Result[any] { // Build options from remaining args (flags become Options) opts := Options{} for _, arg := range remaining { - if strings.HasPrefix(arg, "--") { - parts := strings.SplitN(strings.TrimPrefix(arg, "--"), "=", 2) - if len(parts) == 2 { - opts = append(opts, Option{K: parts[0], V: parts[1]}) + key, val, valid := ParseFlag(arg) + if valid { + if val != "" { + opts = append(opts, Option{K: key, V: val}) } else { - opts = append(opts, Option{K: parts[0], V: true}) + opts = append(opts, Option{K: key, V: true}) } - } else if strings.HasPrefix(arg, "-") { - parts := strings.SplitN(strings.TrimPrefix(arg, "-"), "=", 2) - if len(parts) == 2 { - opts = append(opts, Option{K: parts[0], V: parts[1]}) - } else { - opts = append(opts, Option{K: parts[0], V: true}) - } - } else { + } else if !strings.HasPrefix(arg, "-") { opts = append(opts, Option{K: "_arg", V: arg}) } + // Invalid flags (e.g. -verbose, --v) are silently ignored } return cmd.Run(opts) diff --git a/pkg/core/utils.go b/pkg/core/utils.go new file mode 100644 index 0000000..8b059b1 --- /dev/null +++ b/pkg/core/utils.go @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: EUPL-1.2 + +// Utility functions for the Core framework. + +package core + +import ( + "strings" + "unicode/utf8" +) + +// FilterArgs removes empty strings and Go test runner flags from an argument list. +// +// clean := core.FilterArgs(os.Args[1:]) +func FilterArgs(args []string) []string { + var clean []string + for _, a := range args { + if a == "" || strings.HasPrefix(a, "-test.") { + continue + } + clean = append(clean, a) + } + return clean +} + +// ParseFlag parses a single flag argument into key, value, and validity. +// Single dash (-) requires exactly 1 character (letter, emoji, unicode). +// Double dash (--) requires 2+ characters. +// +// "-v" → "v", "", true +// "-🔥" → "🔥", "", true +// "--verbose" → "verbose", "", true +// "--port=8080" → "port", "8080", true +// "-verbose" → "", "", false (single dash, 2+ chars) +// "--v" → "", "", false (double dash, 1 char) +// "hello" → "", "", false (not a flag) +func ParseFlag(arg string) (key, value string, valid bool) { + if strings.HasPrefix(arg, "--") { + // Long flag: must be 2+ chars + rest := strings.TrimPrefix(arg, "--") + parts := strings.SplitN(rest, "=", 2) + name := parts[0] + if utf8.RuneCountInString(name) < 2 { + return "", "", false + } + if len(parts) == 2 { + return name, parts[1], true + } + return name, "", true + } + + if strings.HasPrefix(arg, "-") { + // Short flag: must be exactly 1 char (rune) + rest := strings.TrimPrefix(arg, "-") + parts := strings.SplitN(rest, "=", 2) + name := parts[0] + if utf8.RuneCountInString(name) != 1 { + return "", "", false + } + if len(parts) == 2 { + return name, parts[1], true + } + return name, "", true + } + + return "", "", false +} diff --git a/tests/utils_test.go b/tests/utils_test.go new file mode 100644 index 0000000..5e8e756 --- /dev/null +++ b/tests/utils_test.go @@ -0,0 +1,78 @@ +package core_test + +import ( + "testing" + + . "forge.lthn.ai/core/go/pkg/core" + "github.com/stretchr/testify/assert" +) + +// --- FilterArgs --- + +func TestFilterArgs_Good(t *testing.T) { + args := []string{"deploy", "", "to", "-test.v", "homelab", "-test.paniconexit0"} + clean := FilterArgs(args) + assert.Equal(t, []string{"deploy", "to", "homelab"}, clean) +} + +func TestFilterArgs_Empty_Good(t *testing.T) { + clean := FilterArgs(nil) + assert.Nil(t, clean) +} + +// --- ParseFlag --- + +func TestParseFlag_ShortValid_Good(t *testing.T) { + // Single letter + k, v, ok := ParseFlag("-v") + assert.True(t, ok) + assert.Equal(t, "v", k) + assert.Equal(t, "", v) + + // Single emoji + k, v, ok = ParseFlag("-🔥") + assert.True(t, ok) + assert.Equal(t, "🔥", k) + assert.Equal(t, "", v) + + // Short with value + k, v, ok = ParseFlag("-p=8080") + assert.True(t, ok) + assert.Equal(t, "p", k) + assert.Equal(t, "8080", v) +} + +func TestParseFlag_ShortInvalid_Bad(t *testing.T) { + // Multiple chars with single dash — invalid + _, _, ok := ParseFlag("-verbose") + assert.False(t, ok) + + _, _, ok = ParseFlag("-port") + assert.False(t, ok) +} + +func TestParseFlag_LongValid_Good(t *testing.T) { + k, v, ok := ParseFlag("--verbose") + assert.True(t, ok) + assert.Equal(t, "verbose", k) + assert.Equal(t, "", v) + + k, v, ok = ParseFlag("--port=8080") + assert.True(t, ok) + assert.Equal(t, "port", k) + assert.Equal(t, "8080", v) +} + +func TestParseFlag_LongInvalid_Bad(t *testing.T) { + // Single char with double dash — invalid + _, _, ok := ParseFlag("--v") + assert.False(t, ok) +} + +func TestParseFlag_NotAFlag_Bad(t *testing.T) { + _, _, ok := ParseFlag("hello") + assert.False(t, ok) + + _, _, ok = ParseFlag("") + assert.False(t, ok) +}