aboutsummaryrefslogtreecommitdiff
path: root/README.md
diff options
context:
space:
mode:
author2026-03-09 23:05:42 +0300
committer2026-03-09 23:05:42 +0300
commit00394a80501960ad26787b5c44435ed5ed67ad84 (patch)
tree672eb918c552c858f32e9533dc3799af6b75769f /README.md
parent'-' sign in words accepted (diff)
downloadconf-00394a80501960ad26787b5c44435ed5ed67ad84.tar.gz
conf-00394a80501960ad26787b5c44435ed5ed67ad84.tar.bz2
conf-00394a80501960ad26787b5c44435ed5ed67ad84.tar.xz
conf-00394a80501960ad26787b5c44435ed5ed67ad84.zip
Полностью переписал библиотеку. Перевёл с EBNF на PEG.v0.1.0
Diffstat (limited to '')
-rw-r--r--README.md431
1 files changed, 331 insertions, 100 deletions
diff --git a/README.md b/README.md
index 267b226..773c77a 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,9 @@
# conf
-Go библиотека для парсинга конфигурационных файлов `.conf`.
+Go библиотека для парсинга конфигурационных файлов `.conf` похожем на
+классические UNIX конфиги, как у nginx или bind9.
+
+[English version](#english)
## Установка
@@ -10,13 +13,12 @@ go get go.neonxp.ru/conf
## Особенности формата
-- **Присваивания**: `key = value;`
-- **Типы значений**: строки (двойные/одинарные кавычки, backticks), числа (целые/дробные), булевы значения
-- **Директивы**: `directive arg1 arg2;`
-- **Блочные директивы**: `directive { ... }`
+- **Команды**: `directive arg1 arg2;`
+- **Команды с телом**: `directive arg1 arg2 { ... }`
+- **Типы аргументов**: строки (двойные/одинарные кавычки/backticks для многострочных строк), числа (целые/дробные), булевы значения
+- **Вложенные блоки**: произвольная глубина вложенности
- **Комментарии**: `#` до конца строки
- **UTF-8**: включая кириллицу
-- **Подстановка переменных окружения**: `$VAR`
## Быстрый старт
@@ -26,68 +28,64 @@ package main
import (
"fmt"
"go.neonxp.ru/conf"
- "go.neonxp.ru/conf/model"
- "go.neonxp.ru/conf/visitor"
)
func main() {
- cfg := conf.New()
- if err := cfg.LoadFile("config.conf"); err != nil {
- panic(err)
- }
-
- v := visitor.NewDefault()
- if err := cfg.Process(v); err != nil {
+ // Загрузка из файла
+ cfg, err := conf.LoadFile("config.conf")
+ if err != nil {
panic(err)
}
- // Доступ по пути с точечной нотацией
- val, err := v.Get("server.host")
- if err != nil {
- panic(err)
+ // Получение команды и её значения
+ if hostCmd := cfg.Get("server"); hostCmd != nil {
+ fmt.Printf("Server: %v\n", hostCmd.Value())
}
- fmt.Println(val.String()) // localhost
- port, err := v.Get("server.port")
- fmt.Println(port.Int()) // 8080
+ // Навигация по вложенной структуре
+ sslEnabled := cfg.Get("server").Group.Get("ssl").Group.Get("enabled")
+ fmt.Printf("SSL enabled: %v\n", sslEnabled.Value())
}
```
## Пример конфигурационного файла
```conf
-# Переменная окружения: db_file = $HOME "/app/data.db";
-
-# Простые присваивания
-rss = "https://neonxp.ru/feed/";
-host = "localhost";
-port = 8080;
-debug = true;
-
-# Директивы с аргументами
-telegram "bot123" "-1003888840756" {
- token = "token_value";
- admin_chat = "@admin";
-}
+# Простые команды без тела
+listen 8080;
+host "127.0.0.1";
+debug false;
-# Вложенные блоки
-server {
- host = "localhost";
- port = 8080;
+# Команды с аргументами и телом
+server "web" {
+ host "localhost";
+ port 8080;
ssl {
- enabled = true;
- cert = "/etc/ssl/cert.pem";
+ enabled true;
+ cert "/etc/ssl/cert.pem";
+ key "/etc/ssl/key.pem";
}
middleware "auth" {
- enabled = true;
- secret = "$JWT_SECRET";
+ enabled true;
+ secret "secret123";
}
}
+# Несколько команд с одинаковым именем
+cache "redis" {
+ host "redis.local";
+ port 6379;
+}
+
+cache "memcached" {
+ host "memcached.local";
+ port 11211;
+}
+
# Многострочные строки
-template = `
+template `
<!DOCTYPE html>
<html>
<body>Hello</body>
@@ -100,93 +98,106 @@ template = `
### Загрузка конфигурации
```go
-cfg := conf.New()
-
// Из файла
-cfg.LoadFile("config.conf")
+cfg, err := conf.LoadFile("path/to/config.conf")
// Из памяти
-cfg.Load("inline", []byte("key = value;"))
+cfg, err := conf.Load("inline", []byte("listen 8080;"))
```
-### Обработка через Visitor
-
-Библиотека использует паттерн Visitor для обхода конфигурации:
+### Типы данных
```go
-type Visitor interface {
- VisitDirective(ident string, args Values, body Body) error
- VisitSetting(key string, values Values) error
+// model.Ident — идентификатор команды (псевдоним для string)
+ident := model.Ident("server")
+
+// model.Command — команда с именем, аргументами и вложенной группой
+type Command struct {
+ Name Ident // Имя команды
+ Args []any // Аргументы команды
+ Group Group // Вложенные команды (тело команды)
}
+
+// model.Group — срез команд
+type Group []Command
```
-### Get-методы на Values
+### Методы Command
-| Метод | Описание |
-|-------|----------|
-| `String()` | Строковое представление через пробел |
-| `Int()` | Преобразование в int (одно значение) |
-| `BuildString(lookups...)` | Сборка строки с подстановками |
+| Метод | Описание |
+| ------------- | -------------------------------------------- |
+| `Value() any` | Возвращает первый аргумент команды или `nil` |
-### Подстановка переменных окружения
+### Методы Group
-```go
-vals, _ := v.Get("db_file")
-path := vals.BuildString(model.LookupEnv)
-// $HOME → "/home/user", результат: "/home/user/app/data.db"
-```
+| Метод | Описание |
+| ---------------------------------------------------------- | ---------------------------------------------------------- |
+| `Get(name Ident) *Command` | Возвращает первую команду с указанным именем или `nil` |
+| `Filter(predicate func(*Command) bool) iter.Seq[*Command]` | Возвращает итератор по командам, удовлетворяющим предикату |
-### Кастомные подстановки
+## Навигация по конфигурации
```go
-substitutions := map[model.Word]string{
- "APP_DIR": "/opt/myapp",
- "LOG_LEVEL": "debug",
+// Простой доступ
+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())
}
-
-path := vals.BuildString(model.LookupSubst(substitutions), model.Origin)
```
-## Реализация собственного Visitor
+## Работа с аргументами
-```go
-type MyVisitor struct{}
+Аргументы сохраняются в срез `Args` типа `[]any`. Возможные типы:
-func (m *MyVisitor) VisitDirective(ident string, args model.Values, body model.Body) error {
- fmt.Printf("Directive: %s, args: %s\n", ident, args.String())
- return body.Execute(m) // Рекурсивный обход тела
-}
+```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)
+ }
-func (m *MyVisitor) VisitSetting(key string, values model.Values) error {
- fmt.Printf("Setting: %s = %s\n", key, values.String())
- return nil
+ // Приведение типа для чисел
+ if num, ok := val2.(int); ok {
+ fmt.Println("Int:", num)
+ }
}
```
-## Грамматика (EBNF)
+## Грамматика (PEG)
-```
-Config = Doc .
-Doc = Stmt { Stmt } .
-Stmt = Word ( Assignment | Command ) .
+Формат использует PEG (Parsing Expression Grammar). Грамматика описана в файле `parser/grammar.peg`.
-Assignment = "=" Values br .
-Command = [Values] ( Body | br ) .
+Основные правила:
-Values = Value { Value } .
-Value = Word | String | Number | Boolean .
-Body = "{" [ Doc ] "}" .
-
-Word = word (alpha | "$" | "_") {alpha | number | "$" | "_"} .
-String = `"[^"]*"` | `'[^']*'` | '`' { `[^`]' } '`' .
-Number = `-?[0-9]+(\.[0-9]+)?` .
-Boolean = `true` | `false` .
-br = ";" .
-```
+- Все команды без тела заканчиваются точкой с запятой `;`
+- Тело команды заключается в фигурные скобки `{ }`
+- Аргументы разделяются пробелами
+- Комментарии начинаются с `#`
## Требования
-- Go 1.25+
+- Go 1.23+ (для использования `iter.Seq`)
## Лицензия
@@ -205,3 +216,223 @@ br = ";" .
## Автор
- Александр Кирюхин <i@neonxp.ru>
+
+---
+
+<a name="english"></a>
+
+# conf (English)
+
+Go library for parsing `.conf` configuration files (like many classic UNIX programms like nginx or bind9).
+
+## Installation
+
+```bash
+go get go.neonxp.ru/conf
+```
+
+## Format Features
+
+- **Commands**: `directive arg1 arg2;`
+- **Commands 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
+- **UTF-8**: including Cyrillic
+
+## Quick Start
+
+```go
+package main
+
+import (
+ "fmt"
+ "go.neonxp.ru/conf"
+)
+
+func main() {
+ // Load from file
+ cfg, err := conf.LoadFile("config.conf")
+ if err != nil {
+ panic(err)
+ }
+
+ // Get command and its value
+ if hostCmd := cfg.Get("server"); hostCmd != nil {
+ fmt.Printf("Server: %v\n", hostCmd.Value())
+ }
+
+ // Navigate through nested structure
+ sslEnabled := cfg.Get("server").Group.Get("ssl").Group.Get("enabled")
+ fmt.Printf("SSL enabled: %v\n", sslEnabled.Value())
+}
+```
+
+## Example Configuration File
+
+```conf
+# Simple commands without body
+listen 8080;
+host "127.0.0.1";
+debug false;
+
+# Commands with arguments and body
+server "web" {
+ host "localhost";
+ port 8080;
+
+ ssl {
+ enabled true;
+ cert "/etc/ssl/cert.pem";
+ key "/etc/ssl/key.pem";
+ }
+
+ middleware "auth" {
+ enabled true;
+ secret "secret123";
+ }
+}
+
+# Multiple commands with same name
+cache "redis" {
+ host "redis.local";
+ port 6379;
+}
+
+cache "memcached" {
+ host "memcached.local";
+ port 11211;
+}
+
+# Multiline strings
+template `
+ <!DOCTYPE html>
+ <html>
+ <body>Hello</body>
+ </html>
+`;
+```
+
+## 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`)
+
+## License
+
+This project is licensed under GNU General Public License version 3 (GPLv3).
+See [LICENSE](LICENSE) file for details.
+
+```
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+Copyright (C) 2026 Alexander NeonXP Kiryukhin <i@neonxp.ru>
+Everyone is permitted to copy and distribute verbatim copies
+of this license document, but changing it is not allowed.
+```
+
+## Author
+
+- Alexander Kiryukhin <i@neonxp.ru>