From 92d84f6633767830ed964ebab8c3b94c77663394 Mon Sep 17 00:00:00 2001 From: Alexander Neonxp Kiryukhin Date: Tue, 10 Mar 2026 14:44:50 +0300 Subject: =?UTF-8?q?=D0=9E=D1=82=D0=BF=D0=BE=D0=BB=D0=B8=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B0=D0=BB=20=D0=B1=D0=B8=D0=B1=D0=BB=D0=B8=D0=BE=D1=82=D0=B5?= =?UTF-8?q?=D0=BA=D1=83,=20=D1=87=D1=82=D0=BE=D0=B1=D1=8B=20=D0=B2=20?= =?UTF-8?q?=D0=B8=D0=B4=D0=B5=D0=B0=D0=BB=D0=B5=20=D0=B1=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D1=88=D0=B5=20=D0=B5=D1=91=20=D0=BD=D0=B5=20=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D1=8F=D1=82=D1=8C=20=D0=B4=D0=BE=D0=BB=D0=B3=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=B3=D0=BE=D0=B4=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 236 +++++--------------------------------------------------------- 1 file changed, 19 insertions(+), 217 deletions(-) (limited to 'README.md') diff --git a/README.md b/README.md index 773c77a..f8fa840 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # conf +[![🌱 Organic Code -- Code written by human](https://oc.neonxp.ru/organiccode.svg)](https://oc.neonxp.ru) +[![Go Doc](https://pkg.go.dev/badge/go.neonxp.ru/conf.svg)](https://pkg.go.dev/go.neonxp.ru/conf) + Go Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊΠ° для парсинга ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΎΠ½Π½Ρ‹Ρ… Ρ„Π°ΠΉΠ»ΠΎΠ² `.conf` ΠΏΠΎΡ…ΠΎΠΆΠ΅ΠΌ Π½Π° классичСскиС UNIX ΠΊΠΎΠ½Ρ„ΠΈΠ³ΠΈ, ΠΊΠ°ΠΊ Ρƒ nginx ΠΈΠ»ΠΈ bind9. @@ -13,8 +16,8 @@ go get go.neonxp.ru/conf ## ΠžΡΠΎΠ±Π΅Π½Π½ΠΎΡΡ‚ΠΈ Ρ„ΠΎΡ€ΠΌΠ°Ρ‚Π° -- **ΠšΠΎΠΌΠ°Π½Π΄Ρ‹**: `directive arg1 arg2;` -- **ΠšΠΎΠΌΠ°Π½Π΄Ρ‹ с Ρ‚Π΅Π»ΠΎΠΌ**: `directive arg1 arg2 { ... }` +- **Π”ΠΈΡ€Π΅ΠΊΡ‚ΠΈΠ²Ρ‹**: `directive arg1 arg2;` +- **Π”ΠΈΡ€Π΅ΠΊΡ‚ΠΈΠ²Ρ‹ с Ρ‚Π΅Π»ΠΎΠΌ**: `directive arg1 arg2 { ... }` - **Π’ΠΈΠΏΡ‹ Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚ΠΎΠ²**: строки (Π΄Π²ΠΎΠΉΠ½Ρ‹Π΅/ΠΎΠ΄ΠΈΠ½Π°Ρ€Π½Ρ‹Π΅ ΠΊΠ°Π²Ρ‹Ρ‡ΠΊΠΈ/backticks для многострочных строк), числа (Ρ†Π΅Π»Ρ‹Π΅/Π΄Ρ€ΠΎΠ±Π½Ρ‹Π΅), Π±ΡƒΠ»Π΅Π²Ρ‹ значСния - **Π’Π»ΠΎΠΆΠ΅Π½Π½Ρ‹Π΅ Π±Π»ΠΎΠΊΠΈ**: ΠΏΡ€ΠΎΠΈΠ·Π²ΠΎΠ»ΡŒΠ½Π°Ρ Π³Π»ΡƒΠ±ΠΈΠ½Π° влоТСнности - **ΠšΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΈ**: `#` Π΄ΠΎ ΠΊΠΎΠ½Ρ†Π° строки @@ -37,13 +40,13 @@ func main() { panic(err) } - // ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ ΠΈ Π΅Ρ‘ значСния + // ΠŸΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠ΅ Π΄ΠΈΡ€Π΅ΠΊΡ‚ΠΈΠ²Ρ‹ ΠΈ Π΅Ρ‘ значСния if hostCmd := cfg.Get("server"); hostCmd != nil { fmt.Printf("Server: %v\n", hostCmd.Value()) } // Навигация ΠΏΠΎ Π²Π»ΠΎΠΆΠ΅Π½Π½ΠΎΠΉ структурС - sslEnabled := cfg.Get("server").Group.Get("ssl").Group.Get("enabled") + sslEnabled := cfg.Get("server").Group().Get("ssl").Group().Get("enabled") fmt.Printf("SSL enabled: %v\n", sslEnabled.Value()) } ``` @@ -51,12 +54,12 @@ func main() { ## ΠŸΡ€ΠΈΠΌΠ΅Ρ€ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΎΠ½Π½ΠΎΠ³ΠΎ Ρ„Π°ΠΉΠ»Π° ```conf -# ΠŸΡ€ΠΎΡΡ‚Ρ‹Π΅ ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ Π±Π΅Π· Ρ‚Π΅Π»Π° +# ΠŸΡ€ΠΎΡΡ‚Ρ‹Π΅ Π΄ΠΈΡ€Π΅ΠΊΡ‚ΠΈΠ²Ρ‹ Π±Π΅Π· Ρ‚Π΅Π»Π° listen 8080; host "127.0.0.1"; debug false; -# ΠšΠΎΠΌΠ°Π½Π΄Ρ‹ с Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚Π°ΠΌΠΈ ΠΈ Ρ‚Π΅Π»ΠΎΠΌ +# Π”ΠΈΡ€Π΅ΠΊΡ‚ΠΈΠ²Ρ‹ с Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚Π°ΠΌΠΈ ΠΈ Ρ‚Π΅Π»ΠΎΠΌ server "web" { host "localhost"; port 8080; @@ -73,7 +76,7 @@ server "web" { } } -# НСсколько ΠΊΠΎΠΌΠ°Π½Π΄ с ΠΎΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²Ρ‹ΠΌ ΠΈΠΌΠ΅Π½Π΅ΠΌ +# НСсколько Π΄ΠΈΡ€Π΅ΠΊΡ‚ΠΈΠ² с ΠΎΠ΄ΠΈΠ½Π°ΠΊΠΎΠ²Ρ‹ΠΌ ΠΈΠΌΠ΅Π½Π΅ΠΌ cache "redis" { host "redis.local"; port 6379; @@ -93,108 +96,6 @@ template ` `; ``` -## API - -### Π—Π°Π³Ρ€ΡƒΠ·ΠΊΠ° ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ - -```go -// Из Ρ„Π°ΠΉΠ»Π° -cfg, err := conf.LoadFile("path/to/config.conf") - -// Из памяти -cfg, err := conf.Load("inline", []byte("listen 8080;")) -``` - -### Π’ΠΈΠΏΡ‹ Π΄Π°Π½Π½Ρ‹Ρ… - -```go -// model.Ident β€” ΠΈΠ΄Π΅Π½Ρ‚ΠΈΡ„ΠΈΠΊΠ°Ρ‚ΠΎΡ€ ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ (псСвдоним для string) -ident := model.Ident("server") - -// model.Command β€” ΠΊΠΎΠΌΠ°Π½Π΄Π° с ΠΈΠΌΠ΅Π½Π΅ΠΌ, Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚Π°ΠΌΠΈ ΠΈ Π²Π»ΠΎΠΆΠ΅Π½Π½ΠΎΠΉ Π³Ρ€ΡƒΠΏΠΏΠΎΠΉ -type Command struct { - Name Ident // Имя ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ - Args []any // АргумСнты ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ - Group Group // Π’Π»ΠΎΠΆΠ΅Π½Π½Ρ‹Π΅ ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ (Ρ‚Π΅Π»ΠΎ ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹) -} - -// model.Group β€” срСз ΠΊΠΎΠΌΠ°Π½Π΄ -type Group []Command -``` - -### ΠœΠ΅Ρ‚ΠΎΠ΄Ρ‹ Command - -| ΠœΠ΅Ρ‚ΠΎΠ΄ | ОписаниС | -| ------------- | -------------------------------------------- | -| `Value() any` | Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ ΠΏΠ΅Ρ€Π²Ρ‹ΠΉ Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚ ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ ΠΈΠ»ΠΈ `nil` | - -### ΠœΠ΅Ρ‚ΠΎΠ΄Ρ‹ Group - -| ΠœΠ΅Ρ‚ΠΎΠ΄ | ОписаниС | -| ---------------------------------------------------------- | ---------------------------------------------------------- | -| `Get(name Ident) *Command` | Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ ΠΏΠ΅Ρ€Π²ΡƒΡŽ ΠΊΠΎΠΌΠ°Π½Π΄Ρƒ с ΡƒΠΊΠ°Π·Π°Π½Π½Ρ‹ΠΌ ΠΈΠΌΠ΅Π½Π΅ΠΌ ΠΈΠ»ΠΈ `nil` | -| `Filter(predicate func(*Command) bool) iter.Seq[*Command]` | Π’ΠΎΠ·Π²Ρ€Π°Ρ‰Π°Π΅Ρ‚ ΠΈΡ‚Π΅Ρ€Π°Ρ‚ΠΎΡ€ ΠΏΠΎ ΠΊΠΎΠΌΠ°Π½Π΄Π°ΠΌ, ΡƒΠ΄ΠΎΠ²Π»Π΅Ρ‚Π²ΠΎΡ€ΡΡŽΡ‰ΠΈΠΌ ΠΏΡ€Π΅Π΄ΠΈΠΊΠ°Ρ‚Ρƒ | - -## Навигация ΠΏΠΎ ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ - -```go -// ΠŸΡ€ΠΎΡΡ‚ΠΎΠΉ доступ -listenCmd := cfg.Get("listen") -fmt.Println(listenCmd.Value()) // 8080 - -// Π¦Π΅ΠΏΠΎΡ‡ΠΊΠ° Π²Π»ΠΎΠΆΠ΅Π½Π½ΠΎΠΉ Π½Π°Π²ΠΈΠ³Π°Ρ†ΠΈΠΈ -port := cfg.Get("server").Group.Get("http").Group.Get("port") -fmt.Println(port.Value()) // 8080 - -// ИспользованиС Filter -for cmd := range cfg.Filter(func(c *model.Command) bool { - return c.Name == "cache" -}) { - fmt.Printf("Cache: %v\n", cmd.Value()) -} -``` - -## Π Π°Π±ΠΎΡ‚Π° с Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚Π°ΠΌΠΈ - -АргумСнты ΡΠΎΡ…Ρ€Π°Π½ΡΡŽΡ‚ΡΡ Π² срСз `Args` Ρ‚ΠΈΠΏΠ° `[]any`. Π’ΠΎΠ·ΠΌΠΎΠΆΠ½Ρ‹Π΅ Ρ‚ΠΈΠΏΡ‹: - -```go -// Π‘Ρ‚Ρ€ΠΎΠΊΠ° Π² Π΄Π²ΠΎΠΉΠ½Ρ‹Ρ… ΠΊΠ°Π²Ρ‹Ρ‡ΠΊΠ°Ρ…: "value" -// Π‘Ρ‚Ρ€ΠΎΠΊΠ° Π² ΠΎΠ΄ΠΈΠ½Π°Ρ€Π½Ρ‹Ρ… ΠΊΠ°Π²Ρ‹Ρ‡ΠΊΠ°Ρ…: 'value' -// Π‘Ρ‚Ρ€ΠΎΠΊΠ° Π² backticks: `value` -// Число Ρ†Π΅Π»ΠΎΠ΅: 42 -// Число Π΄Ρ€ΠΎΠ±Π½ΠΎΠ΅: 3.14 -// Π‘ΡƒΠ»Π΅Π²ΠΎ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅: true, false - -// ΠŸΡ€ΠΈΠΌΠ΅Ρ€ доступа ΠΊ Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚Π°ΠΌ: -cmd := cfg.Get("test") -if cmd != nil && len(cmd.Args) > 0 { - val1 := cmd.Args[0] // ΠŸΠ΅Ρ€Π²Ρ‹ΠΉ Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚ - val2 := cmd.Args[1] // Π’Ρ‚ΠΎΡ€ΠΎΠΉ Π°Ρ€Π³ΡƒΠΌΠ΅Π½Ρ‚ - - // ΠŸΡ€ΠΈΠ²Π΅Π΄Π΅Π½ΠΈΠ΅ Ρ‚ΠΈΠΏΠ° для строк - if str, ok := val1.(string); ok { - fmt.Println("String:", str) - } - - // ΠŸΡ€ΠΈΠ²Π΅Π΄Π΅Π½ΠΈΠ΅ Ρ‚ΠΈΠΏΠ° для чисСл - if num, ok := val2.(int); ok { - fmt.Println("Int:", num) - } -} -``` - -## Π“Ρ€Π°ΠΌΠΌΠ°Ρ‚ΠΈΠΊΠ° (PEG) - -Π€ΠΎΡ€ΠΌΠ°Ρ‚ ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ PEG (Parsing Expression Grammar). Π“Ρ€Π°ΠΌΠΌΠ°Ρ‚ΠΈΠΊΠ° описана Π² Ρ„Π°ΠΉΠ»Π΅ `parser/grammar.peg`. - -ΠžΡΠ½ΠΎΠ²Π½Ρ‹Π΅ ΠΏΡ€Π°Π²ΠΈΠ»Π°: - -- ВсС ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ Π±Π΅Π· Ρ‚Π΅Π»Π° Π·Π°ΠΊΠ°Π½Ρ‡ΠΈΠ²Π°ΡŽΡ‚ΡΡ Ρ‚ΠΎΡ‡ΠΊΠΎΠΉ с запятой `;` -- Π’Π΅Π»ΠΎ ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ Π·Π°ΠΊΠ»ΡŽΡ‡Π°Π΅Ρ‚ΡΡ Π² Ρ„ΠΈΠ³ΡƒΡ€Π½Ρ‹Π΅ скобки `{ }` -- АргумСнты Ρ€Π°Π·Π΄Π΅Π»ΡΡŽΡ‚ΡΡ ΠΏΡ€ΠΎΠ±Π΅Π»Π°ΠΌΠΈ -- ΠšΠΎΠΌΠΌΠ΅Π½Ρ‚Π°Ρ€ΠΈΠΈ Π½Π°Ρ‡ΠΈΠ½Π°ΡŽΡ‚ΡΡ с `#` - ## ВрСбования - Go 1.23+ (для использования `iter.Seq`) @@ -223,6 +124,9 @@ if cmd != nil && len(cmd.Args) > 0 { # conf (English) +[![🌱 Organic Code -- Code written by human](https://oc.neonxp.ru/organiccode.svg)](https://oc.neonxp.ru) +[![Go Doc](https://pkg.go.dev/badge/go.neonxp.ru/conf.svg)](https://pkg.go.dev/go.neonxp.ru/conf) + Go library for parsing `.conf` configuration files (like many classic UNIX programms like nginx or bind9). ## Installation @@ -233,8 +137,8 @@ go get go.neonxp.ru/conf ## Format Features -- **Commands**: `directive arg1 arg2;` -- **Commands with body**: `directive arg1 arg2 { ... }` +- **Directives**: `directive arg1 arg2;` +- **Directives with body**: `directive arg1 arg2 { ... }` - **Argument types**: strings (double/single quotes, backticks for multiline strings), numbers (integer/float), boolean values - **Nested blocks**: arbitrary nesting depth - **Comments**: `#` until end of line @@ -257,7 +161,7 @@ func main() { panic(err) } - // Get command and its value + // Get directive and its value if hostCmd := cfg.Get("server"); hostCmd != nil { fmt.Printf("Server: %v\n", hostCmd.Value()) } @@ -271,12 +175,12 @@ func main() { ## Example Configuration File ```conf -# Simple commands without body +# Simple directives without body listen 8080; host "127.0.0.1"; debug false; -# Commands with arguments and body +# Directives with arguments and body server "web" { host "localhost"; port 8080; @@ -293,7 +197,7 @@ server "web" { } } -# Multiple commands with same name +# Multiple directives with same name cache "redis" { host "redis.local"; port 6379; @@ -313,108 +217,6 @@ template ` `; ``` -## API - -### Loading Configuration - -```go -// From file -cfg, err := conf.LoadFile("path/to/config.conf") - -// From memory -cfg, err := conf.Load("inline", []byte("listen 8080;")) -``` - -### Data Types - -```go -// model.Ident β€” command identifier (alias for string) -ident := model.Ident("server") - -// model.Command β€” command with name, arguments and nested group -type Command struct { - Name Ident // Command name - Args []any // Command arguments - Group Group // Nested commands (command body) -} - -// model.Group β€” slice of commands -type Group []Command -``` - -### Command Methods - -| Method | Description | -| ------------- | ------------------------------------------ | -| `Value() any` | Returns first argument of command or `nil` | - -### Group Methods - -| Method | Description | -| ---------------------------------------------------------- | -------------------------------------------------- | -| `Get(name Ident) *Command` | Returns first command with specified name or `nil` | -| `Filter(predicate func(*Command) bool) iter.Seq[*Command]` | Returns iterator over commands matching predicate | - -## Configuration Navigation - -```go -// Simple access -listenCmd := cfg.Get("listen") -fmt.Println(listenCmd.Value()) // 8080 - -// Nested navigation chain -port := cfg.Get("server").Group.Get("http").Group.Get("port") -fmt.Println(port.Value()) // 8080 - -// Using Filter -for cmd := range cfg.Filter(func(c *model.Command) bool { - return c.Name == "cache" -}) { - fmt.Printf("Cache: %v\n", cmd.Value()) -} -``` - -## Working with Arguments - -Arguments are stored in `Args` slice of type `[]any`. Possible types: - -```go -// Double-quoted string: "value" -// Single-quoted string: 'value' -// Backtick string: `value` -// Integer number: 42 -// Float number: 3.14 -// Boolean value: true, false - -// Example accessing arguments: -cmd := cfg.Get("test") -if cmd != nil && len(cmd.Args) > 0 { - val1 := cmd.Args[0] // First argument - val2 := cmd.Args[1] // Second argument - - // Type assertion for strings - if str, ok := val1.(string); ok { - fmt.Println("String:", str) - } - - // Type assertion for numbers - if num, ok := val2.(int); ok { - fmt.Println("Int:", num) - } -} -``` - -## Grammar (PEG) - -The format uses PEG (Parsing Expression Grammar). Grammar is described in file `parser/grammar.peg`. - -Basic rules: - -- All commands without body end with semicolon `;` -- Command body is enclosed in curly braces `{ }` -- Arguments are separated by spaces -- Comments start with `#` - ## Requirements - Go 1.23+ (for `iter.Seq`) -- cgit v1.2.3