...
This commit is contained in:
137
pkg/heroscript/paramsparser/README.md
Normal file
137
pkg/heroscript/paramsparser/README.md
Normal file
@@ -0,0 +1,137 @@
|
||||
# ParamsParser
|
||||
|
||||
A Go package for parsing and manipulating parameters from text in a key-value format with support for multiline strings.
|
||||
|
||||
## Features
|
||||
|
||||
- Parse parameters in a natural format: `key: 'value' anotherKey: 'another value'`
|
||||
- Support for multiline string values
|
||||
- Support for numeric values without quotes: `port: 25`
|
||||
- Support for boolean-like values: `secure: 1`
|
||||
- Type conversion helpers (string, int, float, boolean)
|
||||
- Default value support
|
||||
- Required parameter validation with panic-on-missing options
|
||||
- Simple and intuitive API
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```go
|
||||
import (
|
||||
"github.com/freeflowuniverse/heroagent/pkg/paramsparser"
|
||||
)
|
||||
|
||||
// Create a new parser
|
||||
parser := paramsparser.New()
|
||||
|
||||
// Parse a string with parameters
|
||||
inputStr := `
|
||||
name: 'myapp'
|
||||
host: 'localhost'
|
||||
port: 25
|
||||
secure: 1
|
||||
reset: 1
|
||||
description: '
|
||||
A multiline description
|
||||
for my application.
|
||||
'
|
||||
`
|
||||
|
||||
err := parser.Parse(inputStr)
|
||||
if err != nil {
|
||||
// Handle error
|
||||
}
|
||||
|
||||
// Or parse a simpler one-line string
|
||||
parser.ParseString("name: 'myapp' version: '1.0' active: 1")
|
||||
|
||||
// Set default values
|
||||
parser.SetDefault("host", "localhost")
|
||||
parser.SetDefault("port", "8080")
|
||||
|
||||
// Or set multiple defaults at once
|
||||
parser.SetDefaults(map[string]string{
|
||||
"debug": "false",
|
||||
"timeout": "30",
|
||||
})
|
||||
|
||||
// Get values with type conversion
|
||||
name := parser.Get("name")
|
||||
port := parser.GetIntDefault("port", 8080)
|
||||
secure := parser.GetBool("secure")
|
||||
```
|
||||
|
||||
### Type Conversion
|
||||
|
||||
```go
|
||||
// String value (with default if not found)
|
||||
value := parser.Get("key")
|
||||
|
||||
// Integer value
|
||||
intValue, err := parser.GetInt("key")
|
||||
// Or with default
|
||||
intValue := parser.GetIntDefault("key", 42)
|
||||
|
||||
// Float value
|
||||
floatValue, err := parser.GetFloat("key")
|
||||
// Or with default
|
||||
floatValue := parser.GetFloatDefault("key", 3.14)
|
||||
|
||||
// Boolean value (true, yes, 1, on are considered true)
|
||||
boolValue := parser.GetBool("key")
|
||||
// Or with default
|
||||
boolValue := parser.GetBoolDefault("key", false)
|
||||
```
|
||||
|
||||
### Required Parameters
|
||||
|
||||
```go
|
||||
// These will panic if the parameter is missing or invalid
|
||||
value := parser.MustGet("required_param")
|
||||
intValue := parser.MustGetInt("required_int_param")
|
||||
floatValue := parser.MustGetFloat("required_float_param")
|
||||
```
|
||||
|
||||
### Getting All Parameters
|
||||
|
||||
```go
|
||||
// Get all parameters (including defaults)
|
||||
allParams := parser.GetAll()
|
||||
for key, value := range allParams {
|
||||
fmt.Printf("%s = %s\n", key, value)
|
||||
}
|
||||
```
|
||||
|
||||
## Example Input Format
|
||||
|
||||
The parser supports the following format:
|
||||
|
||||
```
|
||||
name: 'myname' host: 'localhost'
|
||||
port: 25
|
||||
secure: 1
|
||||
reset: 1
|
||||
description: '
|
||||
a description can be multiline
|
||||
|
||||
like this
|
||||
'
|
||||
```
|
||||
|
||||
Key features of the format:
|
||||
- Keys are alphanumeric (plus underscore)
|
||||
- String values are enclosed in single quotes
|
||||
- Numeric values don't need quotes
|
||||
- Boolean values can be specified as 1/0
|
||||
- Multiline strings start with a single quote and continue until a closing quote is found
|
||||
|
||||
## Example
|
||||
|
||||
See the [example](./example/main.go) for a complete demonstration of how to use this package.
|
||||
|
||||
## Running Tests
|
||||
|
||||
```bash
|
||||
go test -v ./pkg/paramsparser
|
||||
```
|
84
pkg/heroscript/paramsparser/example/main.go
Normal file
84
pkg/heroscript/paramsparser/example/main.go
Normal file
@@ -0,0 +1,84 @@
|
||||
// Example usage of the paramsparser package
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/heroscript/paramsparser"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create a new parser
|
||||
parser := paramsparser.New()
|
||||
|
||||
// Set some default values
|
||||
parser.SetDefaults(map[string]string{
|
||||
"host": "localhost",
|
||||
"port": "8080",
|
||||
"debug": "false",
|
||||
"timeout": "30",
|
||||
"greeting": "Hello, World!",
|
||||
})
|
||||
|
||||
// Parse a string in the specified format
|
||||
inputStr := `
|
||||
name: 'myapp'
|
||||
host: 'example.com'
|
||||
port: 25
|
||||
secure: 1
|
||||
reset: 1
|
||||
description: '
|
||||
This is a multiline description
|
||||
for my application.
|
||||
|
||||
It can span multiple lines.
|
||||
'
|
||||
`
|
||||
|
||||
err := parser.Parse(inputStr)
|
||||
if err != nil {
|
||||
fmt.Printf("Error parsing input: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Access parameters with type conversion
|
||||
name := parser.Get("name")
|
||||
host := parser.Get("host")
|
||||
port := parser.GetIntDefault("port", 8080)
|
||||
secure := parser.GetBool("secure")
|
||||
reset := parser.GetBool("reset")
|
||||
description := parser.Get("description")
|
||||
|
||||
fmt.Println("Configuration:")
|
||||
fmt.Printf(" Name: %s\n", name)
|
||||
fmt.Printf(" Host: %s\n", host)
|
||||
fmt.Printf(" Port: %d\n", port)
|
||||
fmt.Printf(" Secure: %t\n", secure)
|
||||
fmt.Printf(" Reset: %t\n", reset)
|
||||
fmt.Printf(" Description: %s\n", description)
|
||||
|
||||
// Get all parameters
|
||||
fmt.Println("\nAll parameters:")
|
||||
for key, value := range parser.GetAll() {
|
||||
if key == "description" {
|
||||
// Truncate long values for display
|
||||
if len(value) > 30 {
|
||||
value = value[:30] + "..."
|
||||
}
|
||||
}
|
||||
fmt.Printf(" %s = %s\n", key, value)
|
||||
}
|
||||
|
||||
// Example of using MustGet for required parameters
|
||||
if parser.Has("name") {
|
||||
fmt.Printf("\nRequired parameter: %s\n", parser.MustGet("name"))
|
||||
}
|
||||
|
||||
// Example of a simpler one-line parse
|
||||
simpleParser := paramsparser.New()
|
||||
simpleParser.ParseString("name: 'simple' version: '1.0' active: 1")
|
||||
fmt.Println("\nSimple parser results:")
|
||||
fmt.Printf(" Name: %s\n", simpleParser.Get("name"))
|
||||
fmt.Printf(" Version: %s\n", simpleParser.Get("version"))
|
||||
fmt.Printf(" Active: %t\n", simpleParser.GetBool("active"))
|
||||
}
|
447
pkg/heroscript/paramsparser/paramsparser.go
Normal file
447
pkg/heroscript/paramsparser/paramsparser.go
Normal file
@@ -0,0 +1,447 @@
|
||||
// Package paramsparser provides functionality for parsing and manipulating parameters
|
||||
// from text in a key-value format with support for multiline strings.
|
||||
package paramsparser
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/freeflowuniverse/heroagent/pkg/tools"
|
||||
)
|
||||
|
||||
// ParamsParser represents a parameter parser that can handle various parameter sources
|
||||
type ParamsParser struct {
|
||||
params map[string]string
|
||||
defaultParams map[string]string
|
||||
}
|
||||
|
||||
// New creates a new ParamsParser instance
|
||||
func New() *ParamsParser {
|
||||
return &ParamsParser{
|
||||
params: make(map[string]string),
|
||||
defaultParams: make(map[string]string),
|
||||
}
|
||||
}
|
||||
|
||||
// Parse parses a string containing key-value pairs in the format:
|
||||
// key:value or key:'value'
|
||||
// It supports multiline string values.
|
||||
func (p *ParamsParser) Parse(input string) error {
|
||||
// Normalize line endings
|
||||
input = strings.ReplaceAll(input, "\r\n", "\n")
|
||||
|
||||
// Track the current state
|
||||
var currentKey string
|
||||
var currentValue strings.Builder
|
||||
var inMultilineString bool
|
||||
|
||||
// Process each line
|
||||
lines := strings.Split(input, "\n")
|
||||
for i := 0; i < len(lines); i++ {
|
||||
// Only trim space for non-multiline string processing
|
||||
var line string
|
||||
if !inMultilineString {
|
||||
line = strings.TrimSpace(lines[i])
|
||||
} else {
|
||||
line = lines[i]
|
||||
}
|
||||
|
||||
// Skip empty lines unless we're in a multiline string
|
||||
if line == "" && !inMultilineString {
|
||||
continue
|
||||
}
|
||||
|
||||
// If we're in a multiline string
|
||||
if inMultilineString {
|
||||
// Check if this line ends the multiline string
|
||||
if strings.HasSuffix(line, "'") && !strings.HasSuffix(line, "\\'") {
|
||||
// Add the line without the closing quote
|
||||
currentValue.WriteString(line[:len(line)-1])
|
||||
p.params[currentKey] = currentValue.String()
|
||||
inMultilineString = false
|
||||
currentKey = ""
|
||||
currentValue.Reset()
|
||||
} else {
|
||||
// Continue the multiline string
|
||||
currentValue.WriteString(line)
|
||||
currentValue.WriteString("\n")
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Process the line to extract key-value pairs
|
||||
var processedPos int
|
||||
for processedPos < len(line) {
|
||||
// Skip leading whitespace
|
||||
for processedPos < len(line) && (line[processedPos] == ' ' || line[processedPos] == '\t') {
|
||||
processedPos++
|
||||
}
|
||||
|
||||
if processedPos >= len(line) {
|
||||
break
|
||||
}
|
||||
|
||||
// Find the next key by looking for a colon
|
||||
keyStart := processedPos
|
||||
colonPos := -1
|
||||
|
||||
for j := processedPos; j < len(line); j++ {
|
||||
if line[j] == ':' {
|
||||
colonPos = j
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if colonPos == -1 {
|
||||
// No colon found, skip this part
|
||||
break
|
||||
}
|
||||
|
||||
// Extract key and use NameFix to standardize it
|
||||
rawKey := strings.TrimSpace(line[keyStart:colonPos])
|
||||
key := tools.NameFix(rawKey)
|
||||
|
||||
if key == "" {
|
||||
// Invalid key, move past the colon and continue
|
||||
processedPos = colonPos + 1
|
||||
continue
|
||||
}
|
||||
|
||||
// Move position past the colon
|
||||
processedPos = colonPos + 1
|
||||
|
||||
if processedPos >= len(line) {
|
||||
// End of line reached, store empty value
|
||||
p.params[key] = ""
|
||||
break
|
||||
}
|
||||
|
||||
// Skip whitespace after the colon
|
||||
for processedPos < len(line) && (line[processedPos] == ' ' || line[processedPos] == '\t') {
|
||||
processedPos++
|
||||
}
|
||||
|
||||
if processedPos >= len(line) {
|
||||
// End of line reached after whitespace, store empty value
|
||||
p.params[key] = ""
|
||||
break
|
||||
}
|
||||
|
||||
// Check if the value is quoted
|
||||
if line[processedPos] == '\'' {
|
||||
// This is a quoted string
|
||||
processedPos++ // Skip the opening quote
|
||||
|
||||
// Look for the closing quote
|
||||
quoteEnd := -1
|
||||
for j := processedPos; j < len(line); j++ {
|
||||
// Check for escaped quote
|
||||
if line[j] == '\'' && (j == 0 || line[j-1] != '\\') {
|
||||
quoteEnd = j
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if quoteEnd != -1 {
|
||||
// Single-line quoted string
|
||||
value := line[processedPos:quoteEnd]
|
||||
// For quoted values, we preserve the original formatting
|
||||
// But for single-line values, we can apply NameFix if needed
|
||||
if key != "description" {
|
||||
value = tools.NameFix(value)
|
||||
}
|
||||
p.params[key] = value
|
||||
processedPos = quoteEnd + 1 // Move past the closing quote
|
||||
} else {
|
||||
// Start of multiline string
|
||||
currentKey = key
|
||||
currentValue.WriteString(line[processedPos:])
|
||||
currentValue.WriteString("\n")
|
||||
inMultilineString = true
|
||||
break
|
||||
}
|
||||
} else {
|
||||
// This is an unquoted value
|
||||
valueStart := processedPos
|
||||
valueEnd := valueStart
|
||||
|
||||
// Find the end of the value (space or end of line)
|
||||
for valueEnd < len(line) && line[valueEnd] != ' ' && line[valueEnd] != '\t' {
|
||||
valueEnd++
|
||||
}
|
||||
|
||||
value := line[valueStart:valueEnd]
|
||||
// For unquoted values, use NameFix to standardize them
|
||||
// This handles the 'without' keyword and other special cases
|
||||
p.params[key] = tools.NameFix(value)
|
||||
processedPos = valueEnd
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we're still in a multiline string at the end, that's an error
|
||||
if inMultilineString {
|
||||
return errors.New("unterminated multiline string")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseString is a simpler version that parses a string with the format:
|
||||
// key:value or key:'value'
|
||||
// This version doesn't support multiline strings and is optimized for one-line inputs
|
||||
func (p *ParamsParser) ParseString(input string) error {
|
||||
// Trim the input
|
||||
input = strings.TrimSpace(input)
|
||||
|
||||
// Process the input to extract key-value pairs
|
||||
var processedPos int
|
||||
for processedPos < len(input) {
|
||||
// Skip leading whitespace
|
||||
for processedPos < len(input) && (input[processedPos] == ' ' || input[processedPos] == '\t') {
|
||||
processedPos++
|
||||
}
|
||||
|
||||
if processedPos >= len(input) {
|
||||
break
|
||||
}
|
||||
|
||||
// Find the next key by looking for a colon
|
||||
keyStart := processedPos
|
||||
colonPos := -1
|
||||
|
||||
for j := processedPos; j < len(input); j++ {
|
||||
if input[j] == ':' {
|
||||
colonPos = j
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if colonPos == -1 {
|
||||
// No colon found, skip this part
|
||||
break
|
||||
}
|
||||
|
||||
// Extract key and use NameFix to standardize it
|
||||
rawKey := strings.TrimSpace(input[keyStart:colonPos])
|
||||
key := tools.NameFix(rawKey)
|
||||
|
||||
if key == "" {
|
||||
// Invalid key, move past the colon and continue
|
||||
processedPos = colonPos + 1
|
||||
continue
|
||||
}
|
||||
|
||||
// Move position past the colon
|
||||
processedPos = colonPos + 1
|
||||
|
||||
if processedPos >= len(input) {
|
||||
// End of input reached, store empty value
|
||||
p.params[key] = ""
|
||||
break
|
||||
}
|
||||
|
||||
// Skip whitespace after the colon
|
||||
for processedPos < len(input) && (input[processedPos] == ' ' || input[processedPos] == '\t') {
|
||||
processedPos++
|
||||
}
|
||||
|
||||
if processedPos >= len(input) {
|
||||
// End of input reached after whitespace, store empty value
|
||||
p.params[key] = ""
|
||||
break
|
||||
}
|
||||
|
||||
// Check if the value is quoted
|
||||
if input[processedPos] == '\'' {
|
||||
// This is a quoted string
|
||||
processedPos++ // Skip the opening quote
|
||||
|
||||
// Look for the closing quote
|
||||
quoteEnd := -1
|
||||
for j := processedPos; j < len(input); j++ {
|
||||
// Check for escaped quote
|
||||
if input[j] == '\'' && (j == 0 || input[j-1] != '\\') {
|
||||
quoteEnd = j
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if quoteEnd == -1 {
|
||||
return errors.New("unterminated quoted string")
|
||||
}
|
||||
|
||||
value := input[processedPos:quoteEnd]
|
||||
// For quoted values in ParseString, we can apply NameFix
|
||||
// since this method doesn't handle multiline strings
|
||||
if key != "description" {
|
||||
value = tools.NameFix(value)
|
||||
}
|
||||
p.params[key] = value
|
||||
processedPos = quoteEnd + 1 // Move past the closing quote
|
||||
} else {
|
||||
// This is an unquoted value
|
||||
valueStart := processedPos
|
||||
valueEnd := valueStart
|
||||
|
||||
// Find the end of the value (space or end of input)
|
||||
for valueEnd < len(input) && input[valueEnd] != ' ' && input[valueEnd] != '\t' {
|
||||
valueEnd++
|
||||
}
|
||||
|
||||
value := input[valueStart:valueEnd]
|
||||
// For unquoted values, use NameFix to standardize them
|
||||
// This handles the 'without' keyword and other special cases
|
||||
p.params[key] = tools.NameFix(value)
|
||||
processedPos = valueEnd
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseFile parses a file containing key-value pairs
|
||||
func (p *ParamsParser) ParseFile(filename string) error {
|
||||
data, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return p.Parse(string(data))
|
||||
}
|
||||
|
||||
// SetDefault sets a default value for a parameter
|
||||
func (p *ParamsParser) SetDefault(key, value string) {
|
||||
p.defaultParams[key] = value
|
||||
}
|
||||
|
||||
// SetDefaults sets multiple default values at once
|
||||
func (p *ParamsParser) SetDefaults(defaults map[string]string) {
|
||||
for k, v := range defaults {
|
||||
p.defaultParams[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// Set explicitly sets a parameter value
|
||||
func (p *ParamsParser) Set(key, value string) {
|
||||
p.params[key] = value
|
||||
}
|
||||
|
||||
// Get retrieves a parameter value, returning the default if not found
|
||||
func (p *ParamsParser) Get(key string) string {
|
||||
if value, exists := p.params[key]; exists {
|
||||
return value
|
||||
}
|
||||
if defaultValue, exists := p.defaultParams[key]; exists {
|
||||
return defaultValue
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// GetInt retrieves a parameter as an integer
|
||||
func (p *ParamsParser) GetInt(key string) (int, error) {
|
||||
value := p.Get(key)
|
||||
if value == "" {
|
||||
return 0, errors.New("parameter not found")
|
||||
}
|
||||
return strconv.Atoi(value)
|
||||
}
|
||||
|
||||
// GetIntDefault retrieves a parameter as an integer with a default value
|
||||
func (p *ParamsParser) GetIntDefault(key string, defaultValue int) int {
|
||||
value, err := p.GetInt(key)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// GetBool retrieves a parameter as a boolean
|
||||
func (p *ParamsParser) GetBool(key string) bool {
|
||||
value := p.Get(key)
|
||||
if value == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
// Check for common boolean string representations
|
||||
value = strings.ToLower(value)
|
||||
return value == "true" || value == "yes" || value == "1" || value == "on"
|
||||
}
|
||||
|
||||
// GetBoolDefault retrieves a parameter as a boolean with a default value
|
||||
func (p *ParamsParser) GetBoolDefault(key string, defaultValue bool) bool {
|
||||
if !p.Has(key) {
|
||||
return defaultValue
|
||||
}
|
||||
return p.GetBool(key)
|
||||
}
|
||||
|
||||
// GetFloat retrieves a parameter as a float64
|
||||
func (p *ParamsParser) GetFloat(key string) (float64, error) {
|
||||
value := p.Get(key)
|
||||
if value == "" {
|
||||
return 0, errors.New("parameter not found")
|
||||
}
|
||||
return strconv.ParseFloat(value, 64)
|
||||
}
|
||||
|
||||
// GetFloatDefault retrieves a parameter as a float64 with a default value
|
||||
func (p *ParamsParser) GetFloatDefault(key string, defaultValue float64) float64 {
|
||||
value, err := p.GetFloat(key)
|
||||
if err != nil {
|
||||
return defaultValue
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// Has checks if a parameter exists
|
||||
func (p *ParamsParser) Has(key string) bool {
|
||||
_, exists := p.params[key]
|
||||
return exists
|
||||
}
|
||||
|
||||
// GetAll returns all parameters as a map
|
||||
func (p *ParamsParser) GetAll() map[string]string {
|
||||
result := make(map[string]string)
|
||||
|
||||
// First add defaults
|
||||
for k, v := range p.defaultParams {
|
||||
result[k] = v
|
||||
}
|
||||
|
||||
// Then override with actual params
|
||||
for k, v := range p.params {
|
||||
result[k] = v
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// MustGet retrieves a parameter value, panicking if not found
|
||||
func (p *ParamsParser) MustGet(key string) string {
|
||||
value := p.Get(key)
|
||||
if value == "" {
|
||||
panic(fmt.Sprintf("required parameter '%s' not found", key))
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// MustGetInt retrieves a parameter as an integer, panicking if not found or invalid
|
||||
func (p *ParamsParser) MustGetInt(key string) int {
|
||||
value, err := p.GetInt(key)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("required integer parameter '%s' not found or invalid", key))
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
// MustGetFloat retrieves a parameter as a float64, panicking if not found or invalid
|
||||
func (p *ParamsParser) MustGetFloat(key string) float64 {
|
||||
value, err := p.GetFloat(key)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("required float parameter '%s' not found or invalid", key))
|
||||
}
|
||||
return value
|
||||
}
|
226
pkg/heroscript/paramsparser/paramsparser_test.go
Normal file
226
pkg/heroscript/paramsparser/paramsparser_test.go
Normal file
@@ -0,0 +1,226 @@
|
||||
package paramsparser
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestParamsParserBasic(t *testing.T) {
|
||||
input := "name:'myname' host:'localhost' port:25 secure:1 reset:1"
|
||||
parser := New()
|
||||
err := parser.ParseString(input)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse input: %v", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
expected string
|
||||
}{
|
||||
{"string value", "name", "myname"},
|
||||
{"another string value", "host", "localhost"},
|
||||
{"numeric value", "port", "25"},
|
||||
{"boolean-like value", "secure", "1"},
|
||||
{"another boolean-like value", "reset", "1"},
|
||||
{"non-existent key", "nonexistent", ""},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := parser.Get(tt.key); got != tt.expected {
|
||||
t.Errorf("ParamsParser.Get(%q) = %q, want %q", tt.key, got, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParamsParserMultiline(t *testing.T) {
|
||||
input := "name:'myname' description:'\n\t\ta description can be multiline\n\n\t\tlike this\n'"
|
||||
parser := New()
|
||||
err := parser.Parse(input)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to parse input: %v", err)
|
||||
}
|
||||
|
||||
// Check the name parameter
|
||||
if got := parser.Get("name"); got != "myname" {
|
||||
t.Errorf("ParamsParser.Get(\"name\") = %q, want %q", got, "myname")
|
||||
}
|
||||
|
||||
// Check the multiline description
|
||||
expectedDesc := "\n\t\ta description can be multiline\n\n\t\tlike this\n"
|
||||
if got := parser.Get("description"); got != expectedDesc {
|
||||
t.Errorf("ParamsParser.Get(\"description\") = %q, want %q", got, expectedDesc)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParamsParserDefaults(t *testing.T) {
|
||||
parser := New()
|
||||
parser.SetDefault("key1", "default1")
|
||||
parser.SetDefaults(map[string]string{
|
||||
"key2": "default2",
|
||||
"key3": "default3",
|
||||
})
|
||||
|
||||
// Override one default
|
||||
parser.Set("key2", "override")
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
key string
|
||||
expected string
|
||||
}{
|
||||
{"default value", "key1", "default1"},
|
||||
{"overridden value", "key2", "override"},
|
||||
{"another default", "key3", "default3"},
|
||||
{"non-existent key", "key4", ""},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := parser.Get(tt.key); got != tt.expected {
|
||||
t.Errorf("ParamsParser.Get(%q) = %q, want %q", tt.key, got, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParamsParserTypes(t *testing.T) {
|
||||
parser := New()
|
||||
parser.Set("int", "123")
|
||||
parser.Set("float", "3.14")
|
||||
parser.Set("bool_true", "true")
|
||||
parser.Set("bool_yes", "yes")
|
||||
parser.Set("bool_1", "1")
|
||||
parser.Set("bool_false", "false")
|
||||
parser.Set("invalid_int", "not_an_int")
|
||||
parser.Set("invalid_float", "not_a_float")
|
||||
|
||||
t.Run("GetInt", func(t *testing.T) {
|
||||
if val, err := parser.GetInt("int"); err != nil || val != 123 {
|
||||
t.Errorf("GetInt(\"int\") = %d, %v, want 123, nil", val, err)
|
||||
}
|
||||
if val, err := parser.GetInt("invalid_int"); err == nil {
|
||||
t.Errorf("GetInt(\"invalid_int\") = %d, %v, want error", val, err)
|
||||
}
|
||||
if val, err := parser.GetInt("nonexistent"); err == nil {
|
||||
t.Errorf("GetInt(\"nonexistent\") = %d, %v, want error", val, err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetIntDefault", func(t *testing.T) {
|
||||
if val := parser.GetIntDefault("int", 0); val != 123 {
|
||||
t.Errorf("GetIntDefault(\"int\", 0) = %d, want 123", val)
|
||||
}
|
||||
if val := parser.GetIntDefault("invalid_int", 42); val != 42 {
|
||||
t.Errorf("GetIntDefault(\"invalid_int\", 42) = %d, want 42", val)
|
||||
}
|
||||
if val := parser.GetIntDefault("nonexistent", 42); val != 42 {
|
||||
t.Errorf("GetIntDefault(\"nonexistent\", 42) = %d, want 42", val)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetFloat", func(t *testing.T) {
|
||||
if val, err := parser.GetFloat("float"); err != nil || val != 3.14 {
|
||||
t.Errorf("GetFloat(\"float\") = %f, %v, want 3.14, nil", val, err)
|
||||
}
|
||||
if val, err := parser.GetFloat("invalid_float"); err == nil {
|
||||
t.Errorf("GetFloat(\"invalid_float\") = %f, %v, want error", val, err)
|
||||
}
|
||||
if val, err := parser.GetFloat("nonexistent"); err == nil {
|
||||
t.Errorf("GetFloat(\"nonexistent\") = %f, %v, want error", val, err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetFloatDefault", func(t *testing.T) {
|
||||
if val := parser.GetFloatDefault("float", 0.0); val != 3.14 {
|
||||
t.Errorf("GetFloatDefault(\"float\", 0.0) = %f, want 3.14", val)
|
||||
}
|
||||
if val := parser.GetFloatDefault("invalid_float", 2.71); val != 2.71 {
|
||||
t.Errorf("GetFloatDefault(\"invalid_float\", 2.71) = %f, want 2.71", val)
|
||||
}
|
||||
if val := parser.GetFloatDefault("nonexistent", 2.71); val != 2.71 {
|
||||
t.Errorf("GetFloatDefault(\"nonexistent\", 2.71) = %f, want 2.71", val)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetBool", func(t *testing.T) {
|
||||
if val := parser.GetBool("bool_true"); !val {
|
||||
t.Errorf("GetBool(\"bool_true\") = %v, want true", val)
|
||||
}
|
||||
if val := parser.GetBool("bool_yes"); !val {
|
||||
t.Errorf("GetBool(\"bool_yes\") = %v, want true", val)
|
||||
}
|
||||
if val := parser.GetBool("bool_1"); !val {
|
||||
t.Errorf("GetBool(\"bool_1\") = %v, want true", val)
|
||||
}
|
||||
if val := parser.GetBool("bool_false"); val {
|
||||
t.Errorf("GetBool(\"bool_false\") = %v, want false", val)
|
||||
}
|
||||
if val := parser.GetBool("nonexistent"); val {
|
||||
t.Errorf("GetBool(\"nonexistent\") = %v, want false", val)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("GetBoolDefault", func(t *testing.T) {
|
||||
if val := parser.GetBoolDefault("bool_true", false); !val {
|
||||
t.Errorf("GetBoolDefault(\"bool_true\", false) = %v, want true", val)
|
||||
}
|
||||
if val := parser.GetBoolDefault("nonexistent", true); !val {
|
||||
t.Errorf("GetBoolDefault(\"nonexistent\", true) = %v, want true", val)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestParamsParserGetAll(t *testing.T) {
|
||||
parser := New()
|
||||
parser.SetDefault("key1", "default1")
|
||||
parser.SetDefault("key2", "default2")
|
||||
parser.Set("key2", "override")
|
||||
parser.Set("key3", "value3")
|
||||
|
||||
all := parser.GetAll()
|
||||
|
||||
expected := map[string]string{
|
||||
"key1": "default1",
|
||||
"key2": "override",
|
||||
"key3": "value3",
|
||||
}
|
||||
|
||||
if len(all) != len(expected) {
|
||||
t.Errorf("GetAll() returned map with %d entries, want %d", len(all), len(expected))
|
||||
}
|
||||
|
||||
for k, v := range expected {
|
||||
if all[k] != v {
|
||||
t.Errorf("GetAll()[%q] = %q, want %q", k, all[k], v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParamsParserMustGet(t *testing.T) {
|
||||
parser := New()
|
||||
parser.Set("key", "value")
|
||||
parser.Set("int", "123")
|
||||
parser.Set("float", "3.14")
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r == nil {
|
||||
t.Errorf("MustGet on non-existent key did not panic")
|
||||
}
|
||||
}()
|
||||
|
||||
// These should not panic
|
||||
if val := parser.MustGet("key"); val != "value" {
|
||||
t.Errorf("MustGet(\"key\") = %q, want \"value\"", val)
|
||||
}
|
||||
if val := parser.MustGetInt("int"); val != 123 {
|
||||
t.Errorf("MustGetInt(\"int\") = %d, want 123", val)
|
||||
}
|
||||
if val := parser.MustGetFloat("float"); val != 3.14 {
|
||||
t.Errorf("MustGetFloat(\"float\") = %f, want 3.14", val)
|
||||
}
|
||||
|
||||
// This should panic
|
||||
parser.MustGet("nonexistent")
|
||||
}
|
Reference in New Issue
Block a user