flags

package
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 19, 2026 License: Apache-2.0 Imports: 9 Imported by: 0

README

Package flags

Package flags provides utilities for parsing command-line flag values in key=value format with optional CSV support.

Features

  • Parse key=value pairs from command-line flags
  • Support for repeated flags with the same key (values are combined)
  • Optional CSV parsing within a single flag value
  • Quote support (single and double quotes)
  • Escaped quote support within quoted values
  • Automatic whitespace trimming

Usage

Basic Parsing (No CSV)

Use ParseKeyValue() when each flag value is a single key=value pair:

// Command: app --flag key1=value1 --flag key2=value2
var flagValues []string
cmd.Flags().StringArrayVarP(&flagValues, "flag", "f", []string{}, "Key-value pairs")

// In your command handler:
result, err := flags.ParseKeyValue(flagValues)
if err != nil {
    return err
}

value := flags.GetFirst(result, "key1")  // "value1"
CSV-Aware Parsing

Use ParseKeyValueCSV() to allow comma-separated pairs within a single flag. Supports both explicit key=value pairs and shorthand syntax where values without = use the previous key:

// Command: app -r env=prod,qa,staging -r region=us-east1,us-west1
var resolverPairs []string
cmd.Flags().StringArrayVarP(&resolverPairs, "resolver", "r", []string{}, "Resolver parameters")

// In your command handler:
result, err := flags.ParseKeyValueCSV(resolverPairs)
if err != nil {
    return err
}

envs := flags.GetAll(result, "env")         // ["prod", "qa", "staging"]
regions := flags.GetAll(result, "region")   // ["us-east1", "us-west1"]
Examples
Shorthand Syntax (New)

Values without = are treated as additional values for the previous key within the same flag:

# Shorthand - two values for same key
-r "env=prod,qa"
# Result: env = [prod, qa]

# Shorthand - multiple values
-r "env=prod,qa,staging"
# Result: env = [prod, qa, staging]

# Mixed keys with shorthand
-r "region=us-east,us-west,env=prod,qa"
# Result: region = [us-east, us-west], env = [prod, qa]

# Shorthand still allows explicit syntax
-r "env=prod,env=qa,staging"
# Result: env = [prod, qa, staging]

Note: Shorthand only applies within a single flag. Each -r flag resets the key context.

Traditional Explicit Syntax
# Multiple entries in one flag (explicit)
-r "region=us-east1,region=us-west1,region=eu-west1"
# Result: region = [us-east1, us-west1, eu-west1]

# Mixed keys (explicit)
-r "region=us-east1,env=prod,region=us-west1"
# Result: region = [us-east1, us-west1], env = [prod]

# Multiple separate flags
-r env=prod -r env=qa
# Result: env = [prod, qa]

# Quoted values preserve commas
-r "region=\"us-east1,region=us-west1,region=eu-west1\""
# Result: region = ["us-east1,region=us-west1,region=eu-west1"]

# Escaped quotes in values
-r "msg=\"Hello \\\"world\\\"\""
# Result: msg = ["Hello \"world\""]

# Combining flags
-r "region=us-east1,region=us-west1" -r region=eu-west1
# Result: region = [us-east1, us-west1, eu-west1]

# Whitespace is trimmed
-r "region=us-east1, region=us-west1 , region=eu-west1"
# Result: region = [us-east1, us-west1, eu-west1]
URI Scheme Support

New: Values can use URI schemes to avoid escaping special characters like quotes and commas in JSON, YAML, etc.

Supported schemes: json://, yaml://, base64://, http://, https://, file://

When a scheme is detected, all content after it (including commas, quotes, backslashes) is treated literally until the next key=value pattern.

# JSON without escaping - no commas require quotes
-r "data=json://{\"key\":\"value\"}"
# Result: data = ["json://{\"key\":\"value\"}"]

# JSON with commas in value
-r "data=json://[1,2,3]"
# Result: data = ["json://[1,2,3]"]

# JSON in CSV context
-r "env=prod,config=json://{\"a\":1,\"b\":2},region=us-east1"
# Result: env=[prod], config=["json://{\"a\":1,\"b\":2}"], region=[us-east1]

# Nested URLs in JSON
-r "data=json://{\"url\":\"https://example.com\"}"
# Result: data = ["json://{\"url\":\"https://example.com\"}"]

# YAML with commas
-r "config=yaml://items: [a, b, c]"
# Result: config = ["yaml://items: [a, b, c]"]

# Base64 data
-r "token=base64://SGVsbG8sIFdvcmxkIQ=="
# Result: token = ["base64://SGVsbG8sIFdvcmxkIQ=="]

# HTTP/HTTPS URLs with commas (quote if used in CSV)
-r "url=\"https://example.com?a=1,b=2\""
# Result: url = ["https://example.com?a=1,b=2"]

# File paths
-r "path=file:///etc/config.json"
# Result: path = ["file:///etc/config.json"]

Important: The scheme prefix is preserved in the value - your application code should detect and process it accordingly.

Resolution

For validating AND fetching/parsing scheme-prefixed values in one operation, use the resolve package:

import (
    "context"
    "github.com/oakwood-commons/scafctl/pkg/flags"
    "github.com/oakwood-commons/scafctl/pkg/flags/resolve"
)

ctx := context.Background()

// Parse flags
parsed, err := flags.ParseKeyValueCSV([]string{
    `config=json://{"db":"postgres"}`,
    `data=yaml://items: [a,b,c]`,
    `token=base64://SGVsbG8=`,
})
if err != nil {
    return err
}

// Resolve all values (validate + fetch + parse)
resolved, err := resolve.ResolveAll(ctx, parsed)
if err != nil {
    return err // Returns error if JSON is invalid, file missing, HTTP fails, etc.
}

// Use resolved values as native Go types
config := resolve.GetFirst(resolved, "config").(map[string]any)
// config is a parsed map, not a string with "json://" prefix

token := resolve.GetFirst(resolved, "token").([]byte)
// token is decoded bytes, not a base64 string

See resolve/README.md for detailed resolution documentation.

Validation

For validating scheme-prefixed values (JSON, YAML, Base64, file existence, URL format), use the validate package:

import (
    "github.com/oakwood-commons/scafctl/pkg/flags"
    "github.com/oakwood-commons/scafctl/pkg/flags/validate"
)

// Parse flags
parsed, err := flags.ParseKeyValueCSV([]string{
    `config=json://{"db":"postgres"}`,
    `data=yaml://items: [a,b,c]`,
})
if err != nil {
    return err
}

// Validate all values
validated, err := validate.ValidateAll(parsed)
if err != nil {
    return err // Returns error if JSON is invalid, YAML is malformed, etc.
}

// Use validated values (schemes still present)
for key, values := range validated {
    for _, val := range values {
        // Process value - strip scheme if needed
        if strings.HasPrefix(val, "json://") {
            jsonContent := val[7:]
            // Use jsonContent...
        }
    }
}

See validate/README.md for detailed validation documentation.

Helper Functions

  • GetFirst(m, key) - Returns the first value for a key, or empty string
  • GetAll(m, key) - Returns all values for a key as a slice
  • Has(m, key) - Checks if a key exists

Important Notes

  1. Use StringArrayVarP, not StringSliceVarP: Cobra's StringSliceVarP uses CSV parsing which causes issues with special characters. Always use StringArrayVarP to avoid this.

  2. Key Restrictions: Keys cannot contain whitespace or newlines. They are trimmed automatically.

  3. Value Support: Values support ALL characters including newlines, quotes, commas, and special characters.

  4. Combining Values: When the same key appears multiple times (either within CSV or across multiple flags), all values are combined into a slice.

Cobra Integration

var resolverPairs []string

cmd := &cobra.Command{
    RunE: func(cmd *cobra.Command, args []string) error {
        resolvers, err := flags.ParseKeyValueCSV(resolverPairs)
        if err != nil {
            return fmt.Errorf("invalid resolver parameters: %w", err)
        }
        
        // Use parsed values
        if flags.Has(resolvers, "env") {
            env := flags.GetFirst(resolvers, "env")
            // ...
        }
        
        return nil
    },
}

// IMPORTANT: Use StringArrayVarP, NOT StringSliceVarP
cmd.Flags().StringArrayVarP(&resolverPairs, "resolver", "r", []string{},
    "Resolver parameters in key=value format (repeatable)")

Input Key Validation

Use ValidateInputKeys to check user-provided input keys against a known set of valid keys. Unknown keys produce an error with a "did you mean?" suggestion based on Levenshtein distance:

inputs := map[string]any{"urll": "https://example.com", "method": "GET"}
validKeys := []string{"url", "method", "headers", "body", "timeout"}

err := flags.ValidateInputKeys(inputs, validKeys, `provider "http"`)
// Error: provider "http" does not accept input "urll" — did you mean "url"? (valid inputs: body, headers, method, timeout, url)

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func GetAll

func GetAll(m map[string][]string, key string) []string

GetAll returns all values for a key, or nil if not present.

func GetFirst

func GetFirst(m map[string][]string, key string) string

GetFirst returns the first value for a key, or empty string if not present.

func Has

func Has(m map[string][]string, key string) bool

Has checks if a key exists in the map.

func LoadParameterFile added in v0.6.0

func LoadParameterFile(path string) (map[string]any, error)

LoadParameterFile loads parameters from a YAML or JSON file. The file format is auto-detected based on extension, or by trying YAML first then JSON if the extension is not recognized.

func MergeValue added in v0.6.0

func MergeValue(existing, newVal any) any

MergeValue merges a new value with an existing value, creating arrays as needed. If existing is nil, returns newVal. If both are slices, concatenates them. If existing is a scalar and newVal is provided, creates a slice.

func ParseDynamicInputArgs added in v0.6.0

func ParseDynamicInputArgs(args []string) ([]string, error)

ParseDynamicInputArgs normalises raw CLI arguments into key=value strings suitable for ParseResolverFlags.

Three forms are recognised:

--key=value    → strip the leading "--" → "key=value"
key=value      → passed through unchanged
@file.yaml     → passed through unchanged (file reference)

A bare "--key" (no "=") is rejected because we cannot distinguish a boolean flag from a flag that expects the next token as a value. Single-dash forms ("-k=v") are also rejected to avoid collisions with existing short flags.

func ParseKeyValue

func ParseKeyValue(pairs []string) (map[string][]string, error)

ParseKeyValue parses a slice of "key=value" strings into a map where multiple values for the same key are combined into a slice. The first '=' separates key from value - any additional '=' chars are part of the value.

This function does NOT parse CSV - each string in pairs is treated as a single key=value entry. Use ParseKeyValueCSV for CSV support.

func ParseKeyValueCSV

func ParseKeyValueCSV(pairs []string) (map[string][]string, error)

ParseKeyValueCSV parses a slice of "key=value" strings that may contain comma-separated pairs. Commas within quoted values are preserved. Multiple values for the same key are combined into a slice.

Supports shorthand syntax: values without '=' are treated as additional values for the previous key within the same flag.

Examples:

  • "env=prod,qa" -> env: [prod, qa] (shorthand)
  • "region=us-east1,region=us-west1" -> region: [us-east1, us-west1]
  • "env=prod,qa,staging" -> env: [prod, qa, staging] (shorthand)
  • "region=us-east,env=prod,debug" -> region: [us-east], env: [prod, debug]
  • "msg=\"Hello, world\"" -> msg: ["Hello, world"]
  • "key=\"escaped \\\"quotes\\\"\"" -> key: ["escaped \"quotes\""]

Whitespace around commas is trimmed.

func ParseResolverFlags added in v0.6.0

func ParseResolverFlags(values []string) (map[string]any, error)

ParseResolverFlags parses -r flag values, handling both key=value syntax and @file.yaml syntax for loading parameters from files.

Supported formats:

  • key=value: Simple key-value pair
  • key=value1,value2: Multiple values (becomes an array)
  • @file.yaml: Load all parameters from a YAML file
  • @file.json: Load all parameters from a JSON file

Multiple values for the same key are automatically combined into an array.

func ValidateInputKeys added in v0.6.0

func ValidateInputKeys(inputs map[string]any, validKeys []string, contextName string) error

ValidateInputKeys checks that every key in inputs exists in validKeys. Unknown keys produce an error with a "did you mean?" suggestion when a close match (Levenshtein distance ≤ maxSuggestionDistance) is found.

contextName is used in error messages (e.g. "provider \"http\"" or "solution").

Types

This section is empty.

Directories

Path Synopsis
Package main provides an example of using pkg/flags for key-value parsing with validation.
Package main provides an example of using pkg/flags for key-value parsing with validation.
Package resolve provides resolution and fetching of key-value flag values based on URI scheme prefixes.
Package resolve provides resolution and fetching of key-value flag values based on URI scheme prefixes.
Package validate provides validation for key-value flag values based on URI scheme prefixes.
Package validate provides validation for key-value flag values based on URI scheme prefixes.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL