flags

package
v0.13.0 Latest Latest
Warning

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

Go to latest
Published: Apr 30, 2026 License: Apache-2.0 Imports: 11 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 ContainsStdinRef added in v0.7.0

func ContainsStdinRef(values []string) bool

ContainsStdinRef returns true if any of the values contain the @- token (either standalone or as key=@-), indicating that stdin will be consumed.

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 LoadParameterReader added in v0.7.0

func LoadParameterReader(r io.Reader, source string) (map[string]any, error)

LoadParameterReader loads parameters from an io.Reader containing YAML or JSON. It tries YAML first, then JSON, matching the behavior of LoadParameterFile for files with unknown extensions. The source parameter is used in error messages to identify the input origin (e.g., "stdin").

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 ParseResolverFlagsWithStdin added in v0.7.0

func ParseResolverFlagsWithStdin(values []string, stdin io.Reader) (map[string]any, error)

ParseResolverFlagsWithStdin parses -r flag values like ParseResolverFlags, but additionally supports @- to read parameters from stdin as YAML or JSON.

Supported formats:

  • key=value: Simple key-value pair
  • key=value1,value2: Multiple values (becomes an array)
  • key=@-: Read raw stdin content as the value for key
  • key=@file: Read raw file content as the value for key
  • @file.yaml: Load all parameters from a YAML file
  • @file.json: Load all parameters from a JSON file
  • @-: Read all parameters from stdin (YAML or JSON)

The @- token (both standalone and in key=@-) may only appear once. If stdin is nil and @- is used, an error is returned. 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