ref 12 + readme
This commit is contained in:
parent
3481962080
commit
57d38fd82e
80
README.md
80
README.md
|
@ -1,24 +1,19 @@
|
||||||
# Go Config Parser
|
# Sharkk Config File
|
||||||
|
|
||||||
A lightweight, intuitive configuration parser for Go applications with a clean, readable syntax.`
|
SCF, pronounced scuff!
|
||||||
|
|
||||||
## Features
|
A very light, very intuitive config file format! Few symbols, few rules, quite flexible. Has support for comments, arrays, maps, and type guarantees.
|
||||||
|
|
||||||
- Simple, human-readable configuration format
|
Mercilessly benchmarked, fully tested. SCF competes toe-to-toe with Go's native JSON library, and handily outperforms the reference TOML and YAML implementations.
|
||||||
- Strong typing with automatic type conversion
|
|
||||||
- Nested structures with dot notation access
|
|
||||||
- Support for arrays and maps
|
|
||||||
- Inline and block comments
|
|
||||||
- Fast parsing with no dependencies
|
|
||||||
- Full test coverage
|
|
||||||
|
|
||||||
## Configuration Format
|
## Format
|
||||||
|
|
||||||
This parser uses a clean, minimal syntax that's easy to read and write:
|
SCF has very very simple syntax.
|
||||||
|
|
||||||
```
|
```
|
||||||
|
-- Lua style comments!
|
||||||
host "localhost"
|
host "localhost"
|
||||||
port 8080
|
port 8080 -- no = for assignment!
|
||||||
debug true
|
debug true
|
||||||
|
|
||||||
allowed_ips {
|
allowed_ips {
|
||||||
|
@ -27,6 +22,11 @@ allowed_ips {
|
||||||
"10.0.0.1"
|
"10.0.0.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Feel free to space out your explanations!
|
||||||
|
|
||||||
|
All is well and good.
|
||||||
|
]]
|
||||||
database {
|
database {
|
||||||
host "db.example.com"
|
host "db.example.com"
|
||||||
port 5432
|
port 5432
|
||||||
|
@ -35,11 +35,6 @@ database {
|
||||||
password "secure123"
|
password "secure123"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
-- This is a line comment
|
|
||||||
--[[ This is a
|
|
||||||
block comment spanning
|
|
||||||
multiple lines ]]
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
@ -63,25 +58,29 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Load configuration from file
|
|
||||||
file, err := os.Open("config.conf")
|
file, err := os.Open("config.conf")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
|
// Pass a string to be loaded by the parser!
|
||||||
cfg, err := config.Load(file)
|
cfg, err := config.Load(file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Access values with type conversion
|
// Access values with type conversion.
|
||||||
host, err := cfg.GetString("database.host")
|
host, err := cfg.GetString("database.host")
|
||||||
port, err := cfg.GetInt("database.port")
|
port, err := cfg.GetInt("database.port")
|
||||||
debug, err := cfg.GetBool("debug")
|
debug, err := cfg.GetBool("debug")
|
||||||
|
|
||||||
// Use default values for missing keys
|
// Use default values for missing keys
|
||||||
timeout := cfg.GetOr("timeout", 30).(int64)
|
timeout := cfg.GetOr("timeout", 30).(int)
|
||||||
|
|
||||||
|
// Get the entire map to traverse directly (cuts traversal time by 50%!)
|
||||||
|
entireMap := cfg.GetData()
|
||||||
|
other, err := entireMap["database"]["host"].(string)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -90,17 +89,14 @@ func main() {
|
||||||
The parser automatically converts values to appropriate types:
|
The parser automatically converts values to appropriate types:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// These will return properly typed values
|
boolValue, err := cfg.GetBool("feature.enabled")
|
||||||
boolValue, err := cfg.GetBool("feature.enabled")
|
intValue, err := cfg.GetInt("server.port")
|
||||||
intValue, err := cfg.GetInt("server.port")
|
floatValue, err := cfg.GetFloat("threshold")
|
||||||
floatValue, err := cfg.GetFloat("threshold")
|
|
||||||
stringValue, err := cfg.GetString("app.name")
|
stringValue, err := cfg.GetString("app.name")
|
||||||
|
arrayValue, err := cfg.GetArray("allowed_ips")
|
||||||
|
mapValue, err := cfg.GetMap("database")
|
||||||
|
|
||||||
// For complex types
|
// Generic getter (returns any)
|
||||||
arrayValue, err := cfg.GetArray("allowed_ips")
|
|
||||||
mapValue, err := cfg.GetMap("database")
|
|
||||||
|
|
||||||
// Generic getter (returns interface{})
|
|
||||||
value, err := cfg.Get("some.key")
|
value, err := cfg.Get("some.key")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -123,20 +119,20 @@ This parser provides competitive performance compared to popular formats like JS
|
||||||
| Benchmark | Operations | Time (ns/op) | Memory (B/op) | Allocations (allocs/op) |
|
| Benchmark | Operations | Time (ns/op) | Memory (B/op) | Allocations (allocs/op) |
|
||||||
|-----------|----------:|-------------:|--------------:|------------------------:|
|
|-----------|----------:|-------------:|--------------:|------------------------:|
|
||||||
| **Small Config Files** |
|
| **Small Config Files** |
|
||||||
| Config | 718,893 | 1,611 | 5,256 | 25 |
|
| Config | 1,000,000 | 1,052 | 1,743 | 15 |
|
||||||
| JSON | 1,000,000 | 1,170 | 1,384 | 23 |
|
| JSON | 1,000,000 | 1,112 | 1,384 | 23 |
|
||||||
| YAML | 213,438 | 5,668 | 8,888 | 82 |
|
| YAML | 215,121 | 5,600 | 8,888 | 82 |
|
||||||
| TOML | 273,586 | 4,505 | 4,520 | 67 |
|
| TOML | 286,334 | 4,483 | 4,520 | 67 |
|
||||||
| **Medium Config Files** |
|
| **Medium Config Files** |
|
||||||
| Config | 138,517 | 8,777 | 11,247 | 114 |
|
| Config | 211,863 | 5,696 | 4,056 | 74 |
|
||||||
| JSON | 241,069 | 4,996 | 5,344 | 89 |
|
| JSON | 261,925 | 4,602 | 5,344 | 89 |
|
||||||
| YAML | 47,695 | 24,183 | 21,577 | 347 |
|
| YAML | 50,010 | 23,965 | 21,577 | 347 |
|
||||||
| TOML | 66,411 | 17,709 | 16,349 | 208 |
|
| TOML | 68,420 | 17,639 | 16,348 | 208 |
|
||||||
| **Large Config Files** |
|
| **Large Config Files** |
|
||||||
| Config | 33,177 | 35,591 | 31,791 | 477 |
|
| Config | 55,338 | 21,556 | 12,208 | 207 |
|
||||||
| JSON | 66,384 | 18,066 | 18,138 | 297 |
|
| JSON | 70,219 | 17,202 | 18,140 | 297 |
|
||||||
| YAML | 12,482 | 95,248 | 65,574 | 1,208 |
|
| YAML | 12,536 | 95,945 | 65,568 | 1,208 |
|
||||||
| TOML | 17,594 | 67,928 | 66,038 | 669 |
|
| TOML | 14,732 | 74,198 | 66,050 | 669 |
|
||||||
|
|
||||||
*Benchmarked on AMD Ryzen 9 7950X 16-Core Processor*
|
*Benchmarked on AMD Ryzen 9 7950X 16-Core Processor*
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ func BenchmarkRetrieveSimpleValues(b *testing.B) {
|
||||||
|
|
||||||
// Generic retrieval
|
// Generic retrieval
|
||||||
timeout, err := cfg.Get("timeout")
|
timeout, err := cfg.Get("timeout")
|
||||||
if err != nil || timeout.(int64) != 30 {
|
if err != nil || timeout.(int) != 30 {
|
||||||
b.Fatalf("Failed to retrieve timeout: %v", err)
|
b.Fatalf("Failed to retrieve timeout: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -223,7 +223,7 @@ func BenchmarkRetrieveSimpleValuesJSON(b *testing.B) {
|
||||||
}`
|
}`
|
||||||
|
|
||||||
// Parse once before benchmarking retrieval
|
// Parse once before benchmarking retrieval
|
||||||
var result map[string]interface{}
|
var result map[string]any
|
||||||
err := json.Unmarshal([]byte(configData), &result)
|
err := json.Unmarshal([]byte(configData), &result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("Failed to parse JSON: %v", err)
|
b.Fatalf("Failed to parse JSON: %v", err)
|
||||||
|
@ -278,7 +278,7 @@ func BenchmarkRetrieveNestedValuesJSON(b *testing.B) {
|
||||||
}`
|
}`
|
||||||
|
|
||||||
// Parse once before benchmarking retrieval
|
// Parse once before benchmarking retrieval
|
||||||
var result map[string]interface{}
|
var result map[string]any
|
||||||
err := json.Unmarshal([]byte(configData), &result)
|
err := json.Unmarshal([]byte(configData), &result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("Failed to parse JSON: %v", err)
|
b.Fatalf("Failed to parse JSON: %v", err)
|
||||||
|
@ -287,7 +287,7 @@ func BenchmarkRetrieveNestedValuesJSON(b *testing.B) {
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
// First level nesting
|
// First level nesting
|
||||||
app, ok := result["app"].(map[string]interface{})
|
app, ok := result["app"].(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
b.Fatalf("Failed to retrieve app")
|
b.Fatalf("Failed to retrieve app")
|
||||||
}
|
}
|
||||||
|
@ -296,7 +296,7 @@ func BenchmarkRetrieveNestedValuesJSON(b *testing.B) {
|
||||||
b.Fatalf("Failed to retrieve app.name")
|
b.Fatalf("Failed to retrieve app.name")
|
||||||
}
|
}
|
||||||
|
|
||||||
database, ok := result["database"].(map[string]interface{})
|
database, ok := result["database"].(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
b.Fatalf("Failed to retrieve database")
|
b.Fatalf("Failed to retrieve database")
|
||||||
}
|
}
|
||||||
|
@ -306,7 +306,7 @@ func BenchmarkRetrieveNestedValuesJSON(b *testing.B) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Second level nesting
|
// Second level nesting
|
||||||
settings, ok := app["settings"].(map[string]interface{})
|
settings, ok := app["settings"].(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
b.Fatalf("Failed to retrieve app.settings")
|
b.Fatalf("Failed to retrieve app.settings")
|
||||||
}
|
}
|
||||||
|
@ -315,7 +315,7 @@ func BenchmarkRetrieveNestedValuesJSON(b *testing.B) {
|
||||||
b.Fatalf("Failed to retrieve app.settings.enableLogging")
|
b.Fatalf("Failed to retrieve app.settings.enableLogging")
|
||||||
}
|
}
|
||||||
|
|
||||||
credentials, ok := database["credentials"].(map[string]interface{})
|
credentials, ok := database["credentials"].(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
b.Fatalf("Failed to retrieve database.credentials")
|
b.Fatalf("Failed to retrieve database.credentials")
|
||||||
}
|
}
|
||||||
|
@ -338,7 +338,7 @@ func BenchmarkRetrieveArrayValuesJSON(b *testing.B) {
|
||||||
}`
|
}`
|
||||||
|
|
||||||
// Parse once before benchmarking retrieval
|
// Parse once before benchmarking retrieval
|
||||||
var result map[string]interface{}
|
var result map[string]any
|
||||||
err := json.Unmarshal([]byte(configData), &result)
|
err := json.Unmarshal([]byte(configData), &result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("Failed to parse JSON: %v", err)
|
b.Fatalf("Failed to parse JSON: %v", err)
|
||||||
|
@ -347,7 +347,7 @@ func BenchmarkRetrieveArrayValuesJSON(b *testing.B) {
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
// Get entire array
|
// Get entire array
|
||||||
features, ok := result["features"].([]interface{})
|
features, ok := result["features"].([]any)
|
||||||
if !ok || len(features) != 4 {
|
if !ok || len(features) != 4 {
|
||||||
b.Fatalf("Failed to retrieve features array")
|
b.Fatalf("Failed to retrieve features array")
|
||||||
}
|
}
|
||||||
|
@ -358,7 +358,7 @@ func BenchmarkRetrieveArrayValuesJSON(b *testing.B) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Numeric array
|
// Numeric array
|
||||||
numbers, ok := result["numbers"].([]interface{})
|
numbers, ok := result["numbers"].([]any)
|
||||||
if !ok || len(numbers) != 5 {
|
if !ok || len(numbers) != 5 {
|
||||||
b.Fatalf("Failed to retrieve numbers array")
|
b.Fatalf("Failed to retrieve numbers array")
|
||||||
}
|
}
|
||||||
|
@ -375,7 +375,7 @@ timeout: 30
|
||||||
`
|
`
|
||||||
|
|
||||||
// Parse once before benchmarking retrieval
|
// Parse once before benchmarking retrieval
|
||||||
var result map[string]interface{}
|
var result map[string]any
|
||||||
err := yaml.Unmarshal([]byte(configData), &result)
|
err := yaml.Unmarshal([]byte(configData), &result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("Failed to parse YAML: %v", err)
|
b.Fatalf("Failed to parse YAML: %v", err)
|
||||||
|
@ -419,7 +419,7 @@ timeout = 30
|
||||||
`
|
`
|
||||||
|
|
||||||
// Parse once before benchmarking retrieval
|
// Parse once before benchmarking retrieval
|
||||||
var result map[string]interface{}
|
var result map[string]any
|
||||||
err := toml.Unmarshal([]byte(configData), &result)
|
err := toml.Unmarshal([]byte(configData), &result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("Failed to parse TOML: %v", err)
|
b.Fatalf("Failed to parse TOML: %v", err)
|
||||||
|
@ -537,26 +537,28 @@ write = 10
|
||||||
}
|
}
|
||||||
|
|
||||||
// JSON config
|
// JSON config
|
||||||
var jsonResult map[string]interface{}
|
var jsonResult map[string]any
|
||||||
err = json.Unmarshal([]byte(jsonConfig), &jsonResult)
|
err = json.Unmarshal([]byte(jsonConfig), &jsonResult)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("Failed to parse JSON: %v", err)
|
b.Fatalf("Failed to parse JSON: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// YAML config
|
// YAML config
|
||||||
var yamlResult map[string]interface{}
|
var yamlResult map[string]any
|
||||||
err = yaml.Unmarshal([]byte(yamlConfig), &yamlResult)
|
err = yaml.Unmarshal([]byte(yamlConfig), &yamlResult)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("Failed to parse YAML: %v", err)
|
b.Fatalf("Failed to parse YAML: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TOML config
|
// TOML config
|
||||||
var tomlResult map[string]interface{}
|
var tomlResult map[string]any
|
||||||
err = toml.Unmarshal([]byte(tomlConfig), &tomlResult)
|
err = toml.Unmarshal([]byte(tomlConfig), &tomlResult)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("Failed to parse TOML: %v", err)
|
b.Fatalf("Failed to parse TOML: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
directData := customCfg.GetData()
|
||||||
|
|
||||||
b.Run("Custom-SimpleGet", func(b *testing.B) {
|
b.Run("Custom-SimpleGet", func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_, err := customCfg.GetString("app.name")
|
_, err := customCfg.GetString("app.name")
|
||||||
|
@ -584,74 +586,98 @@ write = 10
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
b.Run("Direct-SimpleGet", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
app := directData["app"].(map[string]any)
|
||||||
|
_ = app["name"].(string)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("Direct-DeepGet", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
app := directData["app"].(map[string]any)
|
||||||
|
settings := app["settings"].(map[string]any)
|
||||||
|
timeouts := settings["timeouts"].(map[string]any)
|
||||||
|
_ = timeouts["read"].(int)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("Direct-ArrayGet", func(b *testing.B) {
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
app := directData["app"].(map[string]any)
|
||||||
|
environments := app["environments"].([]any)
|
||||||
|
_ = environments[1].(string)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
b.Run("JSON-SimpleGet", func(b *testing.B) {
|
b.Run("JSON-SimpleGet", func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
app := jsonResult["app"].(map[string]interface{})
|
app := jsonResult["app"].(map[string]any)
|
||||||
_ = app["name"].(string)
|
_ = app["name"].(string)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("JSON-DeepGet", func(b *testing.B) {
|
b.Run("JSON-DeepGet", func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
app := jsonResult["app"].(map[string]interface{})
|
app := jsonResult["app"].(map[string]any)
|
||||||
settings := app["settings"].(map[string]interface{})
|
settings := app["settings"].(map[string]any)
|
||||||
timeouts := settings["timeouts"].(map[string]interface{})
|
timeouts := settings["timeouts"].(map[string]any)
|
||||||
_ = timeouts["read"].(float64)
|
_ = timeouts["read"].(float64)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("JSON-ArrayGet", func(b *testing.B) {
|
b.Run("JSON-ArrayGet", func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
app := jsonResult["app"].(map[string]interface{})
|
app := jsonResult["app"].(map[string]any)
|
||||||
environments := app["environments"].([]interface{})
|
environments := app["environments"].([]any)
|
||||||
_ = environments[1].(string)
|
_ = environments[1].(string)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("YAML-SimpleGet", func(b *testing.B) {
|
b.Run("YAML-SimpleGet", func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
app := yamlResult["app"].(map[string]interface{})
|
app := yamlResult["app"].(map[string]any)
|
||||||
_ = app["name"].(string)
|
_ = app["name"].(string)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("YAML-DeepGet", func(b *testing.B) {
|
b.Run("YAML-DeepGet", func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
app := yamlResult["app"].(map[string]interface{})
|
app := yamlResult["app"].(map[string]any)
|
||||||
settings := app["settings"].(map[string]interface{})
|
settings := app["settings"].(map[string]any)
|
||||||
timeouts := settings["timeouts"].(map[string]interface{})
|
timeouts := settings["timeouts"].(map[string]any)
|
||||||
_ = timeouts["read"].(int)
|
_ = timeouts["read"].(int)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("YAML-ArrayGet", func(b *testing.B) {
|
b.Run("YAML-ArrayGet", func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
app := yamlResult["app"].(map[string]interface{})
|
app := yamlResult["app"].(map[string]any)
|
||||||
environments := app["environments"].([]interface{})
|
environments := app["environments"].([]any)
|
||||||
_ = environments[1].(string)
|
_ = environments[1].(string)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("TOML-SimpleGet", func(b *testing.B) {
|
b.Run("TOML-SimpleGet", func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
app := tomlResult["app"].(map[string]interface{})
|
app := tomlResult["app"].(map[string]any)
|
||||||
_ = app["name"].(string)
|
_ = app["name"].(string)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("TOML-DeepGet", func(b *testing.B) {
|
b.Run("TOML-DeepGet", func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
app := tomlResult["app"].(map[string]interface{})
|
app := tomlResult["app"].(map[string]any)
|
||||||
settings := app["settings"].(map[string]interface{})
|
settings := app["settings"].(map[string]any)
|
||||||
timeouts := settings["timeouts"].(map[string]interface{})
|
timeouts := settings["timeouts"].(map[string]any)
|
||||||
_ = timeouts["read"].(int64)
|
_ = timeouts["read"].(int64)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("TOML-ArrayGet", func(b *testing.B) {
|
b.Run("TOML-ArrayGet", func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
app := tomlResult["app"].(map[string]interface{})
|
app := tomlResult["app"].(map[string]any)
|
||||||
environments := app["environments"].([]interface{})
|
environments := app["environments"].([]any)
|
||||||
_ = environments[1].(string)
|
_ = environments[1].(string)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
46
config.go
46
config.go
|
@ -6,7 +6,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config holds a single hierarchical structure like JSON and handles parsing
|
// Config holds a single hierarchical structure and handles parsing
|
||||||
type Config struct {
|
type Config struct {
|
||||||
data map[string]any
|
data map[string]any
|
||||||
dataRef *map[string]any // Reference to pooled map
|
dataRef *map[string]any // Reference to pooled map
|
||||||
|
@ -46,26 +46,30 @@ func (c *Config) Release() {
|
||||||
c.stack = nil
|
c.stack = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetData retrieves the entirety of the internal data map
|
||||||
|
func (c *Config) GetData() map[string]any {
|
||||||
|
return c.data
|
||||||
|
}
|
||||||
|
|
||||||
// Get retrieves a value from the config using dot notation
|
// Get retrieves a value from the config using dot notation
|
||||||
func (c *Config) Get(key string) (any, error) {
|
func (c *Config) Get(key string) (any, error) {
|
||||||
if key == "" {
|
if key == "" {
|
||||||
return c.data, nil
|
return c.data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the dot-notation path manually
|
|
||||||
var start, i int
|
|
||||||
var current any = c.data
|
var current any = c.data
|
||||||
|
start := 0
|
||||||
|
keyLen := len(key)
|
||||||
|
|
||||||
for i = 0; i < len(key); i++ {
|
for i := 0; i < keyLen; i++ {
|
||||||
if key[i] == '.' || i == len(key)-1 {
|
if key[i] == '.' || i == keyLen-1 {
|
||||||
end := i
|
end := i
|
||||||
if i == len(key)-1 && key[i] != '.' {
|
if i == keyLen-1 && key[i] != '.' {
|
||||||
end = i + 1
|
end = i + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
part := key[start:end]
|
part := key[start:end]
|
||||||
|
|
||||||
// Handle current node based on its type
|
|
||||||
switch node := current.(type) {
|
switch node := current.(type) {
|
||||||
case map[string]any:
|
case map[string]any:
|
||||||
val, ok := node[part]
|
val, ok := node[part]
|
||||||
|
@ -73,7 +77,6 @@ func (c *Config) Get(key string) (any, error) {
|
||||||
return nil, fmt.Errorf("key %s not found", part)
|
return nil, fmt.Errorf("key %s not found", part)
|
||||||
}
|
}
|
||||||
current = val
|
current = val
|
||||||
|
|
||||||
case []any:
|
case []any:
|
||||||
index, err := strconv.Atoi(part)
|
index, err := strconv.Atoi(part)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -83,17 +86,13 @@ func (c *Config) Get(key string) (any, error) {
|
||||||
return nil, fmt.Errorf("array index out of bounds: %d", index)
|
return nil, fmt.Errorf("array index out of bounds: %d", index)
|
||||||
}
|
}
|
||||||
current = node[index]
|
current = node[index]
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("cannot access %s in non-container value", part)
|
return nil, fmt.Errorf("cannot access %s in non-container value", part)
|
||||||
}
|
}
|
||||||
|
|
||||||
if i == len(key)-1 || (i < len(key)-1 && key[i] == '.' && end == i) {
|
if i == keyLen-1 {
|
||||||
if i == len(key)-1 {
|
return current, nil
|
||||||
return current, nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
start = i + 1
|
start = i + 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,8 +121,8 @@ func (c *Config) GetString(key string) (string, error) {
|
||||||
return v, nil
|
return v, nil
|
||||||
case bool:
|
case bool:
|
||||||
return strconv.FormatBool(v), nil
|
return strconv.FormatBool(v), nil
|
||||||
case int64:
|
case int:
|
||||||
return strconv.FormatInt(v, 10), nil
|
return strconv.Itoa(v), nil
|
||||||
case float64:
|
case float64:
|
||||||
return strconv.FormatFloat(v, 'f', -1, 64), nil
|
return strconv.FormatFloat(v, 'f', -1, 64), nil
|
||||||
default:
|
default:
|
||||||
|
@ -149,19 +148,20 @@ func (c *Config) GetBool(key string) (bool, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetInt gets a value as int64
|
// GetInt gets a value as int64
|
||||||
func (c *Config) GetInt(key string) (int64, error) {
|
func (c *Config) GetInt(key string) (int, error) {
|
||||||
val, err := c.Get(key)
|
val, err := c.Get(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v := val.(type) {
|
switch v := val.(type) {
|
||||||
case int64:
|
case int:
|
||||||
return v, nil
|
return v, nil
|
||||||
case float64:
|
case float64:
|
||||||
return int64(v), nil
|
return int(v), nil
|
||||||
case string:
|
case string:
|
||||||
return strconv.ParseInt(v, 10, 64)
|
parsed, err := strconv.Atoi(v)
|
||||||
|
return parsed, err
|
||||||
default:
|
default:
|
||||||
return 0, fmt.Errorf("value for key %s cannot be converted to int", key)
|
return 0, fmt.Errorf("value for key %s cannot be converted to int", key)
|
||||||
}
|
}
|
||||||
|
@ -177,7 +177,7 @@ func (c *Config) GetFloat(key string) (float64, error) {
|
||||||
switch v := val.(type) {
|
switch v := val.(type) {
|
||||||
case float64:
|
case float64:
|
||||||
return v, nil
|
return v, nil
|
||||||
case int64:
|
case int:
|
||||||
return float64(v), nil
|
return float64(v), nil
|
||||||
case string:
|
case string:
|
||||||
return strconv.ParseFloat(v, 64)
|
return strconv.ParseFloat(v, 64)
|
||||||
|
@ -491,7 +491,7 @@ func (c *Config) tokenToValue(token Token) any {
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
// Integer
|
// Integer
|
||||||
val, _ := strconv.ParseInt(valueStr, 10, 64)
|
val, _ := strconv.Atoi(valueStr)
|
||||||
return val
|
return val
|
||||||
|
|
||||||
case TokenBoolean:
|
case TokenBoolean:
|
||||||
|
@ -519,7 +519,7 @@ func (c *Config) tokenToValue(token Token) any {
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val, err := strconv.ParseInt(valueStr, 10, 64)
|
val, err := strconv.Atoi(valueStr)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user