{"id":36701288,"url":"https://github.com/ahyalfan/gathuk","last_synced_at":"2026-01-12T11:38:27.343Z","repository":{"id":322093404,"uuid":"1087117552","full_name":"ahyalfan/gathuk","owner":"ahyalfan","description":"Type-safe configuration library for Go (currently .env, .json support, nested structs, env binding)","archived":false,"fork":false,"pushed_at":"2025-11-23T09:24:17.000Z","size":97,"stargazers_count":5,"open_issues_count":5,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-11-23T11:20:55.418Z","etag":null,"topics":["confg","configration","environment","gathuk","golang-library","parser"],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/ahyalfan/gathuk","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ahyalfan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-10-31T12:05:01.000Z","updated_at":"2025-11-22T02:36:14.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ahyalfan/gathuk","commit_stats":null,"previous_names":["ahyalfan/gathuk"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/ahyalfan/gathuk","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahyalfan%2Fgathuk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahyalfan%2Fgathuk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahyalfan%2Fgathuk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahyalfan%2Fgathuk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ahyalfan","download_url":"https://codeload.github.com/ahyalfan/gathuk/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ahyalfan%2Fgathuk/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28338971,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-12T10:58:46.209Z","status":"ssl_error","status_checked_at":"2026-01-12T10:58:42.742Z","response_time":98,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["confg","configration","environment","gathuk","golang-library","parser"],"created_at":"2026-01-12T11:38:27.283Z","updated_at":"2026-01-12T11:38:27.336Z","avatar_url":"https://github.com/ahyalfan.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Gathuk\n\n![Go Version](https://img.shields.io/badge/Go-1.24.3-blue.svg)\n![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)\n[![Go Report Card](https://goreportcard.com/badge/github.com/ahyalfan/gathuk)](https://goreportcard.com/report/github.com/ahyalfan/gathuk)\n[![GoDoc](https://godoc.org/github.com/ahyalfan/gathuk?status.svg)](https://godoc.org/github.com/ahyalfan/gathuk)\n\n**Gathuk** is a type-safe, flexible configuration management library for Go that converts configuration files into strongly-typed structs. It supports multiple file formats (currently `.env` `.json`), nested structures, and automatic environment variable binding.\n\n## Features\n\n- 🎯 **Type-Safe**: Uses Go generics for compile-time type safety\n- 📁 **Multiple File Formats**: Support for `.env`, `.json` (YAML, TOML coming soon)\n- 🔄 **Multiple File Loading**: Load and merge configurations from multiple files\n- 🔄 **Automatic Environment Variables**: Automatically bind OS environment variables to struct fields\n- 🏗️ **Nested Structures**: Full support for nested struct configurations with custom prefixes\n- 🔧 **Flexible Options**: Configure priority between file configs and environment variables\n- 💾 **Write Support**: Export configurations back to files\n- 🎨 **Custom Codecs**: Extensible codec system for adding new file formats\n- 🚀 **Zero Dependencies**: Minimal external dependencies\n- ⚡ **High Performance**: Optimized for speed with efficient parsing\n\n## Installation\n\n```bash\ngo get github.com/ahyalfan/gathuk\n```\n\n**Requirements:**\n\n- Go 1.21 or higher (for generics support)\n\n**Verify installation:**\n\n```bash\ngo list -m github.com/ahyalfan/gathuk\n```\n\n## Quick Start\n\n### Basic Usage with .env\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"log\"\n    \"github.com/ahyalfan/gathuk\"\n)\n\ntype Config struct {\n    Port int\n    Host string\n}\n\nfunc main() {\n    // Create a new Gathuk instance\n    gt := gathuk.NewGathuk[Config]()\n\n    // Load configuration from file\n    if err := gt.LoadConfigFiles(\".env\"); err != nil {\n        log.Fatal(err)\n    }\n\n    // Get the parsed configuration\n    config := gt.GetConfig()\n    fmt.Printf(\"Server: %s:%d\\n\", config.Host, config.Port)\n}\n```\n\n**`.env` file:**\n\n```env\nPORT=8080\nHOST=localhost\n```\n\n### Basic Usage with JSON\n\n```go\ntype Config struct {\n    Port int    `config:\"port\"`\n    Host string `config:\"host\"`\n}\n\nfunc main() {\n    gt := gathuk.NewGathuk[Config]()\n\n    if err := gt.LoadConfigFiles(\"config.json\"); err != nil {\n        log.Fatal(err)\n    }\n\n    config := gt.GetConfig()\n    fmt.Printf(\"Server: %s:%d\\n\", config.Host, config.Port)\n}\n```\n\n**`config.json` file:**\n\n```json\n{\n  \"port\": 8080,\n  \"host\": \"localhost\"\n}\n```\n\n## Core Concepts\n\n### 1. Generic Type Parameter\n\nGathuk uses Go generics to provide type-safe configuration loading:\n\n```go\n// Concrete struct type (RECOMMENDED)\ngt := gathuk.NewGathuk[Config]()\n\n// Generic any type (LIMITED - see warnings)\ngt := gathuk.NewGathuk[any]()\n\n// Map type (LIMITED - see warnings)\ngt := gathuk.NewGathuk[map[string]any]()\n```\n\n**Always prefer concrete struct types for:**\n\n- ✅ Type safety at compile time\n- ✅ Proper merging when loading multiple files\n- ✅ Better IDE support and autocomplete\n- ✅ Self-documenting code\n\n### 2. Configuration Loading Flow\n\n```\nConfig Files → Tokenize → Parse → Decode → Struct\n                                  ↓\n                          Environment Variables\n                                  ↓\n                           Merge \u0026 Apply Options\n                                  ↓\n                            Final Config Struct\n```\n\n### 3. Field Name Mapping\n\nGathuk automatically converts field names to appropriate conventions:\n\n| Go Field Name | .env Format      | JSON Format      |\n| ------------- | ---------------- | ---------------- |\n| `Port`        | `PORT`           | `port`           |\n| `ServerPort`  | `SERVER_PORT`    | `server_port`    |\n| `DatabaseURL` | `DATABASE_U_R_L` | `database_u_r_l` |\n| `APIKey`      | `A_P_I_KEY`      | `a_p_i_key`      |\n\n**Override with tags:**\n\n```go\ntype Config struct {\n    APIKey string `config:\"api_key\"`  // → API_KEY or api_key\n}\n```\n\n## Supported Formats\n\n| Format                | Extension       | Status         | Tag Convention   |\n| --------------------- | --------------- | -------------- | ---------------- |\n| Environment Variables | `.env`          | ✅ Stable      | UPPER_SNAKE_CASE |\n| JSON                  | `.json`         | ✅ Stable      | lower_snake_case |\n| YAML                  | `.yaml`, `.yml` | 🚧 Coming Soon | lower_snake_case |\n| TOML                  | `.toml`         | 🚧 Coming Soon | lower_snake_case |\n\n### Format-Specific Behavior\n\n#### .env Format\n\n**Features:**\n\n- Simple key-value pairs: `KEY=value`\n- Comments start with `#`\n- Keys automatically converted to UPPER_SNAKE_CASE\n- No quotes needed for string values\n- Inline comments supported: `PORT=8080 # server port`\n\n**Example:**\n\n```env\n# Server Configuration\nSERVER_PORT=8080\nSERVER_HOST=localhost\n\n# Database Configuration\nDB_HOST=localhost\nDB_PORT=5432\nDB_NAME=myapp\n\n# Feature Flags\nDEBUG=true\nENABLE_LOGGING=true\n```\n\n**Supported Types:**\n\n- `string`: Direct text\n- `int`, `int64`: Integers\n- `float64`: Floating-point numbers\n- `bool`: `true` or `false`\n\n#### JSON Format\n\n**Features:**\n\n- Full JSON specification compliance\n- Nested objects and arrays\n- Keys use lower_snake_case by default\n- Type-safe parsing\n- Pretty-print support for writing\n\n**Example:**\n\n```json\n{\n  \"server\": {\n    \"port\": 8080,\n    \"host\": \"localhost\",\n    \"timeout\": 30\n  },\n  \"database\": {\n    \"host\": \"localhost\",\n    \"port\": 5432,\n    \"credentials\": {\n      \"username\": \"admin\",\n      \"password\": \"secret\"\n    }\n  },\n  \"features\": {\n    \"debug\": true,\n    \"cache_enabled\": true\n  }\n}\n```\n\n**Supported Types:**\n\n- All primitive types (string, number, boolean, null)\n- Objects (nested structs)\n- Arrays (slices)\n- Mixed arrays with `[]interface{}`\n\n## Struct Tags\n\nGathuk supports two main struct tags for customization:\n\n### `config` Tag\n\nMaps struct fields to specific configuration keys:\n\n```go\ntype Config struct {\n    // .env: SERVER_PORT | JSON: server_port\n    Port int `config:\"server_port\"`\n\n    // .env: API_KEY | JSON: api_key\n    APIKey string `config:\"api_key\"`\n}\n```\n\n### `nested` Tag\n\nDefines prefix for nested structures:\n\n```go\ntype Config struct {\n    // All Database fields will have DB_ prefix in .env\n    // In JSON: nested under \"db\" object\n    Database Database `config:\"db\"`\n    // or\n    // Database Database `config:\"db\"`\n}\n\ntype Database struct {\n    Host string  // .env: DB_HOST | JSON: db.host\n    Port int     // .env: DB_PORT | JSON: db.port\n}\n```\n\n**Example `.env`:**\n\n```env\nDB_HOST=localhost\nDB_PORT=5432\n```\n\n**Example JSON:**\n\n```json\n{\n  \"db\": {\n    \"host\": \"localhost\",\n    \"port\": 5432\n  }\n}\n```\n\n### Ignoring Fields\n\nUse `-` to exclude fields from configuration:\n\n```go\ntype Config struct {\n    Internal string `config:\"-\"`  // Will be ignored\n}\n```\n\n## Configuration Options\n\n### Decode Options\n\n```go\ngt := gathuk.NewGathuk[Config]()\n\n// Enable automatic environment variable binding\ngt.GlobalDecodeOpt.AutomaticEnv = true\n\n// Prefer file values over environment variables\ngt.GlobalDecodeOpt.PreferFileOverEnv = true\n\n// Persist decoded values to OS environment\ngt.GlobalDecodeOpt.PersistToOSEnv = true\n\nerr := gt.LoadConfigFiles(\"config.env\")\n```\n\n### Option Behavior\n\n| Option              | Description                                                                               |\n| ------------------- | ----------------------------------------------------------------------------------------- |\n| `AutomaticEnv`      | When `true`, automatically reads from OS environment variables                            |\n| `PreferFileOverEnv` | When `true`, prioritizes file config over environment variables (requires `AutomaticEnv`) |\n| `PersistToOSEnv`    | When `true`, saves decoded values to OS environment variables                             |\n\n### Priority Examples\n\n**Scenario 1: File Only (Default)**\n\n```go\ngt := gathuk.NewGathuk[Config]()\n// Only reads from config.env\nerr := gt.LoadConfigFiles(\"config.env\")\n```\n\n**Scenario 2: Environment Override**\n\n```go\ngt := gathuk.NewGathuk[Config]()\ngt.GlobalDecodeOpt.AutomaticEnv = true\n// Environment variables override file values\nerr := gt.LoadConfigFiles(\"config.env\")\n```\n\n**Scenario 3: File Override**\n\n```go\ngt := gathuk.NewGathuk[Config]()\ngt.GlobalDecodeOpt.AutomaticEnv = true\ngt.GlobalDecodeOpt.PreferFileOverEnv = true\n// File values override environment variables\nerr := gt.LoadConfigFiles(\"config.env\")\n```\n\n## Multiple File Loading\n\nLoad and merge configurations from multiple files:\n\n```go\ngt := gathuk.NewGathuk[Config]()\n\n// Method 1: Load multiple files at once\nerr := gt.LoadConfigFiles(\"base.env\", \"dev.env\", \"local.env\")\n\n// Method 2: Set base files, then load additional files\ngt.SetConfigFiles(\"base.env\", \"defaults.env\")\nerr := gt.LoadConfigFiles(\"override.env\")\n\n// Method 3: Mix different formats\nerr := gt.LoadConfigFiles(\"base.json\", \"override.env\")\n```\n\n### Merge Behavior\n\n**Files are processed sequentially:**\n\n1. First file loaded → Initial config\n2. Second file loaded → Merged with first\n3. Third file loaded → Merged with result of 1+2\n4. Continue...\n\n**Merge rules:**\n\n- ✅ Non-zero values from later files **override** earlier files\n- ❌ Zero values from later files **do NOT override** earlier files\n- ✅ New fields from later files **are added**\n- ✅ Nested structs **merge recursively**\n\n### Example: Multi-Environment Setup\n\n```go\n// Load base config + environment-specific config\nenv := os.Getenv(\"APP_ENV\") // \"development\", \"staging\", \"production\"\nif env == \"\" {\n    env = \"development\"\n}\n\ngt := gathuk.NewGathuk[Config]()\ngt.SetConfigFiles(\"config/base.json\")\nerr := gt.LoadConfigFiles(fmt.Sprintf(\"config/%s.json\", env))\n```\n\n### Zero Value Behavior\n\n**IMPORTANT:** Zero values are NOT merged to prevent accidental clearing:\n\n```go\n// base.env\nPORT=8080\nHOST=localhost\nMAX_CONNECTIONS=100\n\n// override.env\nPORT=0         # Zero value - IGNORED\nHOST=          # Empty string - IGNORED\nMAX_CONNECTIONS=50  # Non-zero - USED\n```\n\n```go\ngt := gathuk.NewGathuk[Config]()\nerr := gt.LoadConfigFiles(\"base.env\", \"override.env\")\n\nconfig := gt.GetConfig()\n// Result:\n// Port:           8080 (NOT overridden by 0)\n// Host:           \"localhost\" (NOT overridden by \"\")\n// MaxConnections: 50 (overridden by non-zero)\n```\n\n**Rationale:** This prevents accidentally clearing important configuration values with empty or zero values in override files.\n\n### Environment-Specific Loading\n\n```go\nfunc LoadConfig() (*Config, error) {\n    env := os.Getenv(\"APP_ENV\")\n    if env == \"\" {\n        env = \"development\"\n    }\n\n    files := []string{\n        \"config/base.env\",                     // Always loaded\n        fmt.Sprintf(\"config/%s.env\", env),     // Environment-specific\n    }\n\n    // Add local overrides if exists\n    localFile := \"config/local.env\"\n    if _, err := os.Stat(localFile); err == nil {\n        files = append(files, localFile)\n    }\n\n    gt := gathuk.NewGathuk[Config]()\n    if err := gt.LoadConfigFiles(files...); err != nil {\n        return nil, err\n    }\n\n    return \u0026config, nil\n}\n```\n\n**Directory structure:**\n\n```\nconfig/\n  ├── base.env          # Common settings\n  ├── development.env   # Dev overrides\n  ├── staging.env       # Staging overrides\n  ├── production.env    # Production overrides\n  └── local.env         # Local dev (gitignored)\n```\n\n### Priority Examples\n\n#### Example 1: Environment Only\n\n```go\nos.Setenv(\"PORT\", \"9000\")\nos.Setenv(\"HOST\", \"0.0.0.0\")\n\ngt := gathuk.NewGathuk[Config]()\ngt.GlobalDecodeOpt.AutomaticEnv = true\n\n// No files - only environment\nerr := gt.LoadConfigFiles()\n\nconfig := gt.GetConfig()\n// Port: 9000, Host: \"0.0.0.0\"\n```\n\n#### Example 2: File + Environment (Env Wins)\n\n```env\n# config.env\nPORT=8080\nHOST=localhost\n```\n\n```go\nos.Setenv(\"PORT\", \"9000\") // This will win\n\ngt := gathuk.NewGathuk[Config]()\ngt.GlobalDecodeOpt.AutomaticEnv = true\nerr := gt.LoadConfigFiles(\"config.env\")\n\nconfig := gt.GetConfig()\n// Port: 9000 (from env), Host: \"localhost\" (from file)\n```\n\n#### Example 3: File + Environment (File Wins)\n\n```go\nos.Setenv(\"PORT\", \"9000\") // This will be ignored\n\ngt := gathuk.NewGathuk[Config]()\ngt.GlobalDecodeOpt.AutomaticEnv = true\ngt.GlobalDecodeOpt.PreferFileOverEnv = true\nerr := gt.LoadConfigFiles(\"config.env\")\n\nconfig := gt.GetConfig()\n// Port: 8080 (from file), Host: \"localhost\" (from file)\n```\n\n#### Example 4: Partial Environment\n\n```env\n# config.env\nPORT=8080\nHOST=localhost\n```\n\n```go\nos.Setenv(\"DEBUG\", \"true\")      // Additional env var\nos.Setenv(\"LOG_LEVEL\", \"info\")  // Additional env var\n\ngt := gathuk.NewGathuk[Config]()\ngt.GlobalDecodeOpt.AutomaticEnv = true\nerr := gt.LoadConfigFiles(\"config.env\")\n\nconfig := gt.GetConfig()\n// Port: 8080 (file), Host: \"localhost\" (file)\n// Debug: true (env), LogLevel: \"info\" (env)\n```\n\n### Docker/Kubernetes Integration\n\nGathuk works seamlessly with containerized deployments:\n\n```go\ntype Config struct {\n    Port         int    `config:\"port\"`\n    DatabaseURL  string `config:\"database_url\"`\n    RedisURL     string `config:\"redis_url\"`\n}\n\nfunc main() {\n    gt := gathuk.NewGathuk[Config]()\n    gt.GlobalDecodeOpt.AutomaticEnv = true\n\n    // In Docker/K8s, all config comes from environment\n    // Set via docker-compose.yml, Dockerfile ENV, or K8s ConfigMap\n    err := gt.LoadConfigFiles()\n\n    config := gt.GetConfig()\n    // Ready to use!\n}\n```\n\n**docker-compose.yml:**\n\n```yaml\nservices:\n  app:\n    environment:\n      - PORT=8080\n      - DATABASE_URL=postgres://localhost:5432/db\n      - REDIS_URL=redis://localhost:6379\n```\n\n## Writing Configuration\n\nExport your configuration to files:\n\n```go\nconfig := Config{\n    Port: 8080,\n    Host: \"localhost\",\n}\n\n// Write to .env file\nerr := gt.WriteConfigFile(\"output.env\", 0644, config)\n\n// Write to JSON file\nerr := gt.WriteConfigFile(\"output.json\", 0644, config)\n\n// Write to io.Writer\nvar buf bytes.Buffer\nerr := gt.WriteConfig(\u0026buf, \"json\", config)\nfmt.Println(buf.String())\n```\n\n## Advanced Usage\n\n### Custom Codec Registry\n\nCreate and register custom codecs for different file formats:\n\n```go\n// Create custom codec\ntype JSONCodec[T any] struct {\n    option.DefaultCodec[T]\n}\n\nfunc (c *JSONCodec[T]) Decode(buf []byte,val  *T) error {\n    err := json.Unmarshal(buf, val)\n    return  err\n}\n\nfunc (c *JSONCodec[T]) Encode(val T) ([]byte, error) {\n    return json.Marshal(val)\n}\n\n// Register codec\nfunc main() {\n    registry := gathuk.NewDefaultCodecRegister[Config]()\n    registry.RegisterCodec(\"json\", \u0026JSONCodec[Config]{})\n\n    gt := gathuk.NewGathuk[Config]()\n    gt.SetCustomCodecRegistry(registry)\n\n    err := gt.LoadConfigFiles(\"config.json\")\n}\n```\n\n### Loading from io.Reader\n\n```go\n// From file\nfile, err := os.Open(\"config.env\")\nif err != nil {\n    log.Fatal(err)\n}\ndefer file.Close()\n\ngt := gathuk.NewGathuk[Config]()\nerr = gt.LoadConfig(file, \"env\")\n\n// From HTTP response\nresp, err := http.Get(\"https://api.example.com/config\")\nif err != nil {\n    log.Fatal(err)\n}\ndefer resp.Body.Close()\n\nerr = gt.LoadConfig(resp.Body, \"json\")\n\n// From string\nconfigStr := `{\"port\": 8080, \"host\": \"localhost\"}`\nreader := strings.NewReader(configStr)\nerr = gt.LoadConfig(reader, \"json\")\n```\n\n### Format-Specific Options\n\n```go\ngt := gathuk.NewGathuk[Config]()\n\n// Set decode options for specific format\nenvOpt := \u0026option.DecodeOption{\n    AutomaticEnv: true,\n    PreferFileOverEnv: true,\n}\ngt.SetDecodeOption(\"env\", envOpt)\n\n// JSON doesn't need env options\njsonOpt := \u0026option.DecodeOption{\n    AutomaticEnv: false,\n}\ngt.SetDecodeOption(\"json\", jsonOpt)\n```\n\n### Validation After Loading\n\n```go\ntype Config struct {\n    Port     int    `config:\"port\"`\n    Host     string `config:\"host\"`\n    LogLevel string `config:\"log_level\"`\n}\n\nfunc (c *Config) Validate() error {\n    if c.Port \u003c 1 || c.Port \u003e 65535 {\n        return fmt.Errorf(\"invalid port: %d\", c.Port)\n    }\n\n    if c.Host == \"\" {\n        return fmt.Errorf(\"host is required\")\n    }\n\n    validLevels := map[string]bool{\n        \"debug\": true, \"info\": true, \"warn\": true, \"error\": true,\n    }\n    if !validLevels[c.LogLevel] {\n        return fmt.Errorf(\"invalid log level: %s\", c.LogLevel)\n    }\n\n    return nil\n}\n\nfunc main() {\n    gt := gathuk.NewGathuk[Config]()\n    err := gt.LoadConfigFiles(\"config.env\")\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    config := gt.GetConfig()\n    if err := config.Validate(); err != nil {\n        log.Fatal(\"Config validation failed:\", err)\n    }\n}\n```\n\n### Configuration Reloading\n\n```go\ntype App struct {\n    config *Config\n    gt     *gathuk.Gathuk[Config]\n}\n\nfunc (app *App) ReloadConfig() error {\n    if err := app.gt.LoadConfigFiles(\"config.env\"); err != nil {\n        return err\n    }\n\n    newConfig := app.gt.GetConfig()\n\n    // Validate before applying\n    if err := newConfig.Validate(); err != nil {\n        return err\n    }\n\n    // Atomic update\n    app.config = \u0026newConfig\n\n    log.Println(\"Configuration reloaded successfully\")\n    return nil\n}\n\n// Reload on signal\nfunc (app *App) WatchConfig() {\n    sigChan := make(chan os.Signal, 1)\n    signal.Notify(sigChan, syscall.SIGHUP)\n\n    for {\n        \u003c-sigChan\n        if err := app.ReloadConfig(); err != nil {\n            log.Printf(\"Failed to reload config: %v\", err)\n        }\n    }\n}\n```\n\n## Important Warnings\n\n### ⚠️ Warning 1: Generic Type `any` Behavior\n\n**CRITICAL:** When using `any` or `map[string]any`, multiple file loading does NOT merge:\n\n```go\n// ❌ WRONG: Files are NOT merged!\ngt := gathuk.NewGathuk[any]()\ngt.LoadConfigFiles(\"base.env\", \"dev.env\")\n// Only dev.env values are kept!\n// All base.env values are LOST!\n\n// ✅ CORRECT: Use struct type for merging\ntype Config struct {\n    Port int\n    Host string\n}\ngt := gathuk.NewGathuk[Config]()\ngt.LoadConfigFiles(\"base.env\", \"dev.env\")\n// Properly merged!\n```\n\n**Why?**\n\n- Struct types: Gathuk knows which fields to merge\n- `any`/`map`: Gathuk sees generic map, replaces entirely\n- Each load creates new map, discarding previous\n\n**Solutions:**\n\n1. **Use concrete struct types** (recommended)\n2. **Load files separately** and merge manually\n3. **Load single file** at a time\n\nSee [Multiple Files \u0026 Merging Documentation](docs/multiple-files.md) for details.\n\n### ⚠️ Warning 2: Zero Values\n\nZero values from later files do NOT override earlier files:\n\n```go\n// base.env\nPORT=8080\n\n// override.env\nPORT=0  # Will NOT override!\n\ngt.LoadConfigFiles(\"base.env\", \"override.env\")\n// Result: Port = 8080 (not 0)\n```\n\n**To force zero values:**\n\n- Load only the override file\n- Use a non-zero sentinel value\n- Manually set after loading\n\n### ⚠️ Warning 3: Field Names with Acronyms\n\n```go\ntype Config struct {\n    APIKey string  // → A_P_I_KEY (not API_KEY)\n    HTTPURL string // → H_T_T_P_U_R_L (not HTTP_URL)\n}\n\n// Use tags for better names\ntype Config struct {\n    APIKey string `config:\"api_key\"`  // → API_KEY\n    HTTPURL string `config:\"http_url\"` // → HTTP_URL\n}\n```\n\n### ⚠️ Warning 4: Concurrent Access\n\n```go\n// ❌ NOT safe for concurrent access during load\ngt := gathuk.NewGathuk[Config]()\n\ngo gt.LoadConfigFiles(\"config1.env\") // Unsafe!\ngo gt.LoadConfigFiles(\"config2.env\") // Unsafe!\n\n// ✅ Safe: Load once, read concurrently\ngt.LoadConfigFiles(\"config.env\")\nconfig := gt.GetConfig()\n\ngo func() { use(config) }() // Safe\ngo func() { use(config) }() // Safe\n```\n\n## Examples\n\n### Example 1: Simple Configuration\n\n```go\n// .env file\n// PORT=8080\n// HOST=localhost\n\ntype Config struct {\n    Port int\n    Host string\n}\n\ngt := gathuk.NewGathuk[Config]()\nerr := gt.LoadConfigFiles(\".env\")\n// Result: {Port: 8080, Host: \"localhost\"}\n```\n\n### Example 2: With Environment Variable Override\n\n```go\n// config.json\n// {\n//   \"server\": {\n//     \"port\": 8080,\n//     \"host\": \"localhost\"\n//   },\n//   \"database\": {\n//     \"host\": \"db.example.com\",\n//     \"port\": 5432\n//   }\n// }\n\ntype Server struct {\n    Port int    `config:\"port\"`\n    Host string `config:\"host\"`\n}\n\ntype Database struct {\n    Host string `config:\"host\"`\n    Port int    `config:\"port\"`\n}\n\ntype Config struct {\n    Server   Server   `config:\"server\"`\n    Database Database `config:\"database\"`\n}\n```\n\n### Example 3: Environment Variable Override\n\n```go\n// config.env\n// USER=file_user\n// PORT=8080\n\ntype Config struct {\n    User   string\n    Port   int\n    Editor string\n}\n\n// Set environment variables\nos.Setenv(\"USER\", \"env_user\")\nos.Setenv(\"EDITOR\", \"nvim\")\n\ngt := gathuk.NewGathuk[Config]()\ngt.GlobalDecodeOpt.AutomaticEnv = true\n\nerr := gt.LoadConfigFiles(\"config.env\")\n// Result: {User: \"env_user\", Port: 8080, Editor: \"nvim\"}\n// USER from env overrides file, EDITOR only in env, PORT from file\n\n// With PreferFileOverEnv\ngt.GlobalDecodeOpt.PreferFileOverEnv = true\nerr = gt.LoadConfigFiles(\"config.env\")\n// Result: {User: \"file_user\", Port: 8080, Editor: \"nvim\"}\n// USER from file overrides env, EDITOR still from env\n```\n\n### Example 4: Multi-Format Configuration\n\n```go\ntype Config struct {\n    Server   ServerConfig   `config:\"server\"`\n    Database DatabaseConfig `config:\"database\"`\n}\n\ngt := gathuk.NewGathuk[Config]()\n\n// Load base config from JSON, override with .env\nerr := gt.LoadConfigFiles(\"config.json\", \"override.env\")\n```\n\n### Example 5: Dynamic Configuration\n\n```go\n// Load based on environment\nenv := os.Getenv(\"APP_ENV\")\nif env == \"\" {\n    env = \"development\"\n}\n\nconfigFiles := []string{\n    \"config/base.json\",\n    fmt.Sprintf(\"config/%s.json\", env),\n}\n\n// Add local override if exists\nif _, err := os.Stat(\"config/local.json\"); err == nil {\n    configFiles = append(configFiles, \"config/local.json\")\n}\n\ngt := gathuk.NewGathuk[Config]()\nerr := gt.LoadConfigFiles(configFiles...)\n```\n\n## Complete Examples\n\n### Example 1: Web Server Configuration\n\n```go\ntype Config struct {\n    Server   ServerConfig   `config:\"server\"`\n    Database DatabaseConfig `config:\"db\"`\n    Redis    RedisConfig    `config:\"redis\"`\n    Logging  LogConfig      `config:\"log\"`\n}\n\ntype ServerConfig struct {\n    Port         int    `config:\"port\"`\n    Host         string `config:\"host\"`\n    ReadTimeout  int    `config:\"read_timeout\"`\n    WriteTimeout int    `config:\"write_timeout\"`\n}\n\ntype DatabaseConfig struct {\n    Host     string `config:\"host\"`\n    Port     int    `config:\"port\"`\n    User     string `config:\"user\"`\n    Password string `config:\"password\"`\n    Database string `config:\"name\"`\n    MaxConns int    `config:\"max_connections\"`\n}\n\ntype RedisConfig struct {\n    Host     string `config:\"host\"`\n    Port     int    `config:\"port\"`\n    Password string `config:\"password\"`\n    DB       int    `config:\"db\"`\n}\n\ntype LogConfig struct {\n    Level  string `config:\"level\"`\n    Format string `config:\"format\"`\n}\n\nfunc main() {\n    // Load configuration\n    gt := gathuk.NewGathuk[Config]()\n    gt.GlobalDecodeOpt.AutomaticEnv = true\n\n    env := os.Getenv(\"APP_ENV\")\n    if env == \"\" {\n        env = \"development\"\n    }\n\n    files := []string{\n        \"config/base.env\",\n        fmt.Sprintf(\"config/%s.env\", env),\n    }\n\n    if err := gt.LoadConfigFiles(files...); err != nil {\n        log.Fatal(\"Failed to load config:\", err)\n    }\n\n    config := gt.GetConfig()\n\n    // Validate configuration\n    if config.Server.Port \u003c 1 || config.Server.Port \u003e 65535 {\n        log.Fatal(\"Invalid server port\")\n    }\n\n    // Start server\n    addr := fmt.Sprintf(\"%s:%d\", config.Server.Host, config.Server.Port)\n    log.Printf(\"Starting server on %s\", addr)\n\n    // Use configuration\n    db := connectDatabase(config.Database)\n    redis := connectRedis(config.Redis)\n\n    server := \u0026http.Server{\n        Addr:         addr,\n        ReadTimeout:  time.Duration(config.Server.ReadTimeout) * time.Second,\n        WriteTimeout: time.Duration(config.Server.WriteTimeout) * time.Second,\n    }\n\n    log.Fatal(server.ListenAndServe())\n}\n```\n\n**`config/base.env`:**\n\n```env\n# Server Configuration\nSERVER_PORT=8080\nSERVER_HOST=0.0.0.0\nSERVER_READ_TIMEOUT=30\nSERVER_WRITE_TIMEOUT=30\n\n# Database Configuration\nDB_HOST=localhost\nDB_PORT=5432\nDB_USER=app\nDB_PASSWORD=secret\nDB_NAME=myapp\nDB_MAX_CONNECTIONS=25\n\n# Redis Configuration\nREDIS_HOST=localhost\nREDIS_PORT=6379\nREDIS_PASSWORD=\nREDIS_DB=0\n\n# Logging Configuration\nLOG_LEVEL=info\nLOG_FORMAT=json\n```\n\n**`config/development.env`:**\n\n```env\n# Override for development\nSERVER_PORT=3000\nDB_MAX_CONNECTIONS=5\nLOG_LEVEL=debug\nLOG_FORMAT=text\n```\n\n### Example 2: Microservice Configuration\n\n```go\ntype Config struct {\n    Service     ServiceConfig     `config:\"service\"`\n    HTTP        HTTPConfig        `config:\"http\"`\n    GRPC        GRPCConfig        `config:\"grpc\"`\n    Observability ObservabilityConfig `config:\"obs\"`\n    Dependencies  DependenciesConfig  `config:\"deps\"`\n}\n\ntype ServiceConfig struct {\n    Name        string `config:\"name\"`\n    Version     string `config:\"version\"`\n    Environment string `config:\"environment\"`\n}\n\ntype HTTPConfig struct {\n    Enabled bool   `config:\"enabled\"`\n    Port    int    `config:\"port\"`\n    Timeout int    `config:\"timeout\"`\n}\n\ntype GRPCConfig struct {\n    Enabled bool   `config:\"enabled\"`\n    Port    int    `config:\"port\"`\n    Timeout int    `config:\"timeout\"`\n}\n\ntype ObservabilityConfig struct {\n    Metrics   MetricsConfig   `config:\"metrics\"`\n    Tracing   TracingConfig   `config:\"tracing\"`\n    Logging   LoggingConfig   `config:\"logging\"`\n}\n\ntype MetricsConfig struct {\n    Enabled bool   `config:\"enabled\"`\n    Port    int    `config:\"port\"`\n    Path    string `config:\"path\"`\n}\n\ntype TracingConfig struct {\n    Enabled  bool   `config:\"enabled\"`\n    Endpoint string `config:\"endpoint\"`\n    SampleRate float64 `config:\"sample_rate\"`\n}\n\ntype LoggingConfig struct {\n    Level  string `config:\"level\"`\n    Format string `config:\"format\"`\n}\n\ntype DependenciesConfig struct {\n    Database DatabaseConfig `config:\"db\"`\n    Cache    CacheConfig    `config:\"cache\"`\n    Queue    QueueConfig    `config:\"queue\"`\n}\n\ntype DatabaseConfig struct {\n    Host         string `config:\"host\"`\n    Port         int    `config:\"port\"`\n    User         string `config:\"user\"`\n    Password     string `config:\"password\"`\n    Database     string `config:\"name\"`\n    MaxOpenConns int    `config:\"max_open_conns\"`\n    MaxIdleConns int    `config:\"max_idle_conns\"`\n}\n\ntype CacheConfig struct {\n    Host     string `config:\"host\"`\n    Port     int    `config:\"port\"`\n    Password string `config:\"password\"`\n    TTL      int    `config:\"ttl\"`\n}\n\ntype QueueConfig struct {\n    URL          string `config:\"url\"`\n    MaxRetries   int    `config:\"max_retries\"`\n    RetryDelay   int    `config:\"retry_delay\"`\n}\n\nfunc LoadConfig() (*Config, error) {\n    gt := gathuk.NewGathuk[Config]()\n    gt.GlobalDecodeOpt.AutomaticEnv = true\n\n    // Load base + environment-specific config\n    env := os.Getenv(\"SERVICE_ENVIRONMENT\")\n    if env == \"\" {\n        env = \"development\"\n    }\n\n    files := []string{\n        \"config/base.env\",\n        fmt.Sprintf(\"config/%s.env\", env),\n    }\n\n    // Add secrets file if exists (for local development)\n    if _, err := os.Stat(\"config/secrets.env\"); err == nil {\n        files = append(files, \"config/secrets.env\")\n    }\n\n    if err := gt.LoadConfigFiles(files...); err != nil {\n        return nil, fmt.Errorf(\"failed to load config: %w\", err)\n    }\n\n    config := gt.GetConfig()\n\n    // Validate\n    if err := validateConfig(\u0026config); err != nil {\n        return nil, fmt.Errorf(\"config validation failed: %w\", err)\n    }\n\n    return \u0026config, nil\n}\n\nfunc validateConfig(cfg *Config) error {\n    if cfg.Service.Name == \"\" {\n        return fmt.Errorf(\"service name is required\")\n    }\n\n    if !cfg.HTTP.Enabled \u0026\u0026 !cfg.GRPC.Enabled {\n        return fmt.Errorf(\"at least one protocol (HTTP or GRPC) must be enabled\")\n    }\n\n    if cfg.HTTP.Enabled \u0026\u0026 (cfg.HTTP.Port \u003c 1 || cfg.HTTP.Port \u003e 65535) {\n        return fmt.Errorf(\"invalid HTTP port: %d\", cfg.HTTP.Port)\n    }\n\n    if cfg.GRPC.Enabled \u0026\u0026 (cfg.GRPC.Port \u003c 1 || cfg.GRPC.Port \u003e 65535) {\n        return fmt.Errorf(\"invalid GRPC port: %d\", cfg.GRPC.Port)\n    }\n\n    return nil\n}\n```\n\n### Example 3: CLI Application Configuration\n\n```go\ntype Config struct {\n    App      AppConfig      `config:\"app\"`\n    API      APIConfig      `config:\"api\"`\n    Output   OutputConfig   `config:\"output\"`\n    Advanced AdvancedConfig `config:\"advanced\"`\n}\n\ntype AppConfig struct {\n    Name    string `config:\"name\"`\n    Version string `config:\"version\"`\n    Debug   bool   `config:\"debug\"`\n}\n\ntype APIConfig struct {\n    BaseURL string `config:\"base_url\"`\n    Token   string `config:\"token\"`\n    Timeout int    `config:\"timeout\"`\n}\n\ntype OutputConfig struct {\n    Format string `config:\"format\"` // json, yaml, table\n    Color  bool   `config:\"color\"`\n    Quiet  bool   `config:\"quiet\"`\n}\n\ntype AdvancedConfig struct {\n    CacheDir     string `config:\"cache_dir\"`\n    MaxRetries   int    `config:\"max_retries\"`\n    RetryDelay   int    `config:\"retry_delay\"`\n}\n\nfunc main() {\n    // Parse flags\n    configFile := flag.String(\"config\", \"\", \"Config file path\")\n    debug := flag.Bool(\"debug\", false, \"Enable debug mode\")\n    flag.Parse()\n\n    // Load configuration\n    gt := gathuk.NewGathuk[Config]()\n    gt.GlobalDecodeOpt.AutomaticEnv = true\n\n    files := []string{}\n\n    // Load from default locations\n    homeDir, _ := os.UserHomeDir()\n    defaultFiles := []string{\n        filepath.Join(homeDir, \".myapp\", \"config.env\"),\n        \".myapp.env\",\n    }\n\n    for _, f := range defaultFiles {\n        if _, err := os.Stat(f); err == nil {\n            files = append(files, f)\n        }\n    }\n\n    // Load from specified config file\n    if *configFile != \"\" {\n        files = append(files, *configFile)\n    }\n\n    if len(files) \u003e 0 {\n        if err := gt.LoadConfigFiles(files...); err != nil {\n            log.Fatal(\"Failed to load config:\", err)\n        }\n    }\n\n    config := gt.GetConfig()\n\n    // Override with flags\n    if *debug {\n        config.App.Debug = true\n    }\n\n    // Use configuration\n    runCLI(config)\n}\n```\n\n### Example 4: Testing Configuration\n\n```go\nfunc TestConfigLoading(t *testing.T) {\n    tests := []struct {\n        name     string\n        envFile  string\n        envVars  map[string]string\n        want     Config\n        wantErr  bool\n    }{\n        {\n            name:    \"basic config\",\n            envFile: \"testdata/basic.env\",\n            want: Config{\n                Port: 8080,\n                Host: \"localhost\",\n            },\n            wantErr: false,\n        },\n        {\n            name:    \"with environment override\",\n            envFile: \"testdata/basic.env\",\n            envVars: map[string]string{\n                \"PORT\": \"9000\",\n            },\n            want: Config{\n                Port: 9000,\n                Host: \"localhost\",\n            },\n            wantErr: false,\n        },\n    }\n\n    for _, tt := range tests {\n        t.Run(tt.name, func(t *testing.T) {\n            // Set environment variables\n            for k, v := range tt.envVars {\n                os.Setenv(k, v)\n                defer os.Unsetenv(k)\n            }\n\n            // Load config\n            gt := gathuk.NewGathuk[Config]()\n            gt.GlobalDecodeOpt.AutomaticEnv = true\n\n            err := gt.LoadConfigFiles(tt.envFile)\n            if (err != nil) != tt.wantErr {\n                t.Errorf(\"LoadConfigFiles() error = %v, wantErr %v\", err, tt.wantErr)\n                return\n            }\n\n            got := gt.GetConfig()\n            if !reflect.DeepEqual(got, tt.want) {\n                t.Errorf(\"GetConfig() = %v, want %v\", got, tt.want)\n            }\n        })\n    }\n}\nfunc TestConfigMerging(t *testing.T) {\n    // Create temporary files\n    baseFile := createTempFile(t, `\nPORT=8080\nHOST=localhost\nDEBUG=false\n`)\n    defer os.Remove(baseFile)\n\n    devFile := createTempFile(t, `\nDEBUG=true\nLOG_LEVEL=debug\n`)\n    defer os.Remove(devFile)\n\n    // Load and merge\n    gt := gathuk.NewGathuk[Config]()\n    err := gt.LoadConfigFiles(baseFile, devFile)\n    if err != nil {\n        t.Fatal(err)\n    }\n\n    config := gt.GetConfig()\n\n    // Verify merged results\n    if config.Port != 8080 {\n        t.Errorf(\"Port = %d, want 8080\", config.Port)\n    }\n    if config.Host != \"localhost\" {\n        t.Errorf(\"Host = %s, want localhost\", config.Host)\n    }\n    if config.Debug != true {\n        t.Errorf(\"Debug = %v, want true\", config.Debug)\n    }\n    if config.LogLevel != \"debug\" {\n        t.Errorf(\"LogLevel = %s, want debug\", config.LogLevel)\n    }\n}\n\nfunc createTempFile(t *testing.T, content string) string {\n    t.Helper()\n    file, err := os.CreateTemp(\"\", \"config-*.env\")\n    if err != nil {\n        t.Fatal(err)\n    }\n    if _, err := file.WriteString(content); err != nil {\n        t.Fatal(err)\n    }\n    file.Close()\n    return file.Name()\n}\n\n```\n\n### Example 5: Dynamic Configuration Switching\n\n```go\ntype ConfigManager struct {\n    configs map[string]*Config\n    active  string\n    mu      sync.RWMutex\n}\n\nfunc NewConfigManager() *ConfigManager {\n    return \u0026ConfigManager{\n        configs: make(map[string]*Config),\n        active:  \"default\",\n    }\n}\n\nfunc (cm *ConfigManager) LoadProfile(name, file string) error {\n    gt := gathuk.NewGathuk[Config]()\n    gt.GlobalDecodeOpt.AutomaticEnv = true\n\n    if err := gt.LoadConfigFiles(file); err != nil {\n        return fmt.Errorf(\"failed to load profile %s: %w\", name, err)\n    }\n\n    config := gt.GetConfig()\n\n    cm.mu.Lock()\n    cm.configs[name] = \u0026config\n    cm.mu.Unlock()\n\n    return nil\n}\n\nfunc (cm *ConfigManager) SwitchProfile(name string) error {\n    cm.mu.Lock()\n    defer cm.mu.Unlock()\n\n    if _, exists := cm.configs[name]; !exists {\n        return fmt.Errorf(\"profile %s not found\", name)\n    }\n\n    cm.active = name\n    log.Printf(\"Switched to profile: %s\", name)\n    return nil\n}\n\nfunc (cm *ConfigManager) GetConfig() Config {\n    cm.mu.RLock()\n    defer cm.mu.RUnlock()\n\n    return *cm.configs[cm.active]\n}\n\nfunc main() {\n    manager := NewConfigManager()\n\n    // Load multiple profiles\n    profiles := map[string]string{\n        \"development\": \"config/dev.env\",\n        \"staging\":     \"config/staging.env\",\n        \"production\":  \"config/prod.env\",\n    }\n\n    for name, file := range profiles {\n        if err := manager.LoadProfile(name, file); err != nil {\n            log.Printf(\"Warning: %v\", err)\n        }\n    }\n\n    // Use active profile\n    env := os.Getenv(\"APP_ENV\")\n    if env == \"\" {\n        env = \"development\"\n    }\n\n    if err := manager.SwitchProfile(env); err != nil {\n        log.Fatal(err)\n    }\n\n    config := manager.GetConfig()\n    log.Printf(\"Running with config: %+v\", config)\n}\n```\n\n## Performance\n\nGathuk is optimized for performance with efficient parsing and minimal allocations.\n\n### Benchmark Results\n\n```\ngoos: linux\ngoarch: amd64\npkg: github.com/ahyalfan/gathuk\ncpu: AMD Ryzen 5 6600H with Radeon Graphics\n\nBenchmarkGathuk/Simple_Load-12                  116638    10046 ns/op    3240 B/op    50 allocs/op\nBenchmarkGathuk/Nested_Struct-12                113784    10685 ns/op    3432 B/op    62 allocs/op\nBenchmarkGathuk/Multiple_Files-12                57288    20465 ns/op    6152 B/op   102 allocs/op\n```\n\n### Run Benchmarks\n\n```bash\n# Run all benchmarks\ngo test -bench=. -benchmem\n\n# Run specific benchmark\ngo test -bench=BenchmarkGathuk/Simple -benchmem\n\n# With CPU profiling\ngo test -bench=. -benchmem -cpuprofile=cpu.prof\n\n# With memory profiling\ngo test -bench=. -benchmem -memprofile=mem.prof\n```\n\n**What this means:**\n\n- **Simple Load**: ~10 microseconds per operation\n- **Nested Struct**: ~11 microseconds per operation\n- **Multiple Files**: ~20 microseconds per operation (loading 2 files)\n\n**Memory efficiency:**\n\n- Simple config: ~3.2 KB per load\n- Nested struct: ~3.4 KB per load\n- Multiple files: ~6.1 KB per load\n\n### Performance Tips\n\n1. **Reuse Gathuk instances when possible**\n\n```go\n// ✅ Good: Reuse instance\ngt := gathuk.NewGathuk[Config]()\nfor _, file := range files {\n    gt.LoadConfigFiles(file)\n}\n\n// ❌ Avoid: Creating new instance each time\nfor _, file := range files {\n    gt := gathuk.NewGathuk[Config]() // Unnecessary allocation\n    gt.LoadConfigFiles(file)\n}\n```\n\n1. **Load once, use many times**\n\n```go\n// ✅ Good: Load once at startup\nvar GlobalConfig Config\n\nfunc init() {\n    gt := gathuk.NewGathuk[Config]()\n    gt.LoadConfigFiles(\"config.env\")\n    GlobalConfig = gt.GetConfig()\n}\n\nfunc handler1() {\n    // Use GlobalConfig\n}\n\nfunc handler2() {\n    // Use GlobalConfig\n}\n```\n\n1. **Use concrete struct types**\n\n```go\n// ✅ Good: Concrete type (faster)\ngt := gathuk.NewGathuk[Config]()\n\n// ❌ Slower: Generic any type\ngt := gathuk.NewGathuk[any]()\n```\n\n## Best Practices\n\n### 1. Always Use Struct Types for Multiple Files\n\n```go\n// ✅ DO: Use concrete struct for merging\ntype Config struct {\n    Port int\n    Host string\n}\ngt := gathuk.NewGathuk[Config]()\ngt.LoadConfigFiles(\"base.env\", \"dev.env\")\n\n// ❌ DON'T: Use any with multiple files\ngt := gathuk.NewGathuk[any]()\ngt.LoadConfigFiles(\"base.env\", \"dev.env\") // Only last file kept!\n```\n\n### 2. Organize Config Files by Environment\n\n```\nconfig/\n  ├── base.env          # Common settings (all environments)\n  ├── development.env   # Dev-specific overrides\n  ├── staging.env       # Staging-specific overrides\n  ├── production.env    # Production-specific overrides\n  ├── secrets.env       # Secrets (gitignored, optional)\n  └── local.env         # Local dev overrides (gitignored)\n```\n\n### 3. Validate Configuration After Loading\n\n```go\ntype Config struct {\n    Port     int\n    Host     string\n    LogLevel string\n}\n\nfunc (c *Config) Validate() error {\n    if c.Port \u003c 1 || c.Port \u003e 65535 {\n        return fmt.Errorf(\"invalid port: %d\", c.Port)\n    }\n\n    if c.Host == \"\" {\n        return fmt.Errorf(\"host is required\")\n    }\n\n    validLevels := map[string]bool{\"debug\": true, \"info\": true, \"warn\": true, \"error\": true}\n    if !validLevels[c.LogLevel] {\n        return fmt.Errorf(\"invalid log level: %s\", c.LogLevel)\n    }\n\n    return nil\n}\n\nfunc main() {\n    gt := gathuk.NewGathuk[Config]()\n    gt.LoadConfigFiles(\"config.env\")\n\n    config := gt.GetConfig()\n    if err := config.Validate(); err != nil {\n        log.Fatal(\"Configuration error:\", err)\n    }\n}\n```\n\n### 4. Use Struct Tags Consistently\n\n```go\n// ✅ Good: Explicit and consistent\ntype Config struct {\n    ServerPort int    `config:\"server_port\"`\n    DBHost     string `config:\"db_host\"`\n    APIKey     string `config:\"api_key\"`\n}\n\n// ❌ Avoid: Mixing conventions\ntype Config struct {\n    ServerPort int              // Auto-mapped\n    DBHost     string `config:\"database_host\"` // Custom\n    APIKey     string            // Auto-mapped (becomes A_P_I_KEY!)\n}\n```\n\n### 5. Document Your Configuration\n\n```go\ntype Config struct {\n    // Server listening port (default: 8080, range: 1-65535)\n    Port int `config:\"port\"`\n\n    // Server bind address (default: \"localhost\")\n    // Use \"0.0.0.0\" to listen on all interfaces\n    Host string `config:\"host\"`\n\n    // Maximum number of database connections (default: 100)\n    MaxConnections int `config:\"max_connections\"`\n\n    // Enable debug logging (default: false)\n    // WARNING: Debug logging may expose sensitive information\n    Debug bool `config:\"debug\"`\n}\n```\n\n### 6. Use Environment Variables for Secrets\n\n```go\n// ❌ Don't: Store secrets in config files\n// config.env (committed to git)\nDATABASE_PASSWORD=mysecret123\n\n// ✅ Do: Use environment variables for secrets\n// config.env (committed to git)\nDATABASE_HOST=localhost\nDATABASE_PORT=5432\n\n// Set secrets via environment\nexport DATABASE_PASSWORD=mysecret123\n\n// Or use separate secrets file (gitignored)\n// config/secrets.env (gitignored)\nDATABASE_PASSWORD=mysecret123\n```\n\n### 7. Provide Sensible Defaults\n\n```go\ntype Config struct {\n    Port         int    `config:\"port\"`\n    Host         string `config:\"host\"`\n    ReadTimeout  int    `config:\"read_timeout\"`\n    WriteTimeout int    `config:\"write_timeout\"`\n}\n\nfunc LoadConfigWithDefaults() (*Config, error) {\n    // Set defaults\n    config := Config{\n        Port:         8080,\n        Host:         \"localhost\",\n        ReadTimeout:  30,\n        WriteTimeout: 30,\n    }\n\n    // Override with file values\n    gt := gathuk.NewGathuk[Config]()\n    gt.GlobalDecodeOpt.AutomaticEnv = true\n\n    // LoadConfigFiles will only override non-zero values\n    if err := gt.LoadConfigFiles(\"config.env\"); err != nil {\n        // If config file doesn't exist, use defaults\n        if !os.IsNotExist(err) {\n            return nil, err\n        }\n    } else {\n        config = gt.GetConfig()\n    }\n\n    return \u0026config, nil\n}\n```\n\n### 8. Test Configuration Loading\n\n```go\nfunc TestConfigLoading(t *testing.T) {\n    // Create test config file\n    content := `\nPORT=8080\nHOST=localhost\nDEBUG=true\n`\n    tmpfile, err := os.CreateTemp(\"\", \"config-*.env\")\n    if err != nil {\n        t.Fatal(err)\n    }\n    defer os.Remove(tmpfile.Name())\n\n    if _, err := tmpfile.WriteString(content); err != nil {\n        t.Fatal(err)\n    }\n    tmpfile.Close()\n\n    // Load config\n    gt := gathuk.NewGathuk[Config]()\n    if err := gt.LoadConfigFiles(tmpfile.Name()); err != nil {\n        t.Fatal(err)\n    }\n\n    config := gt.GetConfig()\n\n    // Assert values\n    if config.Port != 8080 {\n        t.Errorf(\"Port = %d, want 8080\", config.Port)\n    }\n    if config.Host != \"localhost\" {\n        t.Errorf(\"Host = %s, want localhost\", config.Host)\n    }\n    if config.Debug != true {\n        t.Errorf(\"Debug = %v, want true\", config.Debug)\n    }\n}\n```\n\n### 9. Handle Missing Files Gracefully\n\n```go\nfunc LoadConfig(files ...string) (*Config, error) {\n    gt := gathuk.NewGathuk[Config]()\n    gt.GlobalDecodeOpt.AutomaticEnv = true\n\n    // Filter existing files\n    existingFiles := []string{}\n    for _, file := range files {\n        if _, err := os.Stat(file); err == nil {\n            existingFiles = append(existingFiles, file)\n        } else {\n            log.Printf(\"Config file not found (skipping): %s\", file)\n        }\n    }\n\n    if len(existingFiles) == 0 {\n        return nil, fmt.Errorf(\"no config files found\")\n    }\n\n    if err := gt.LoadConfigFiles(existingFiles...); err != nil {\n        return nil, err\n    }\n\n    config := gt.GetConfig()\n    return \u0026config, nil\n}\n```\n\n### 10. Use Feature Flags Pattern\n\n```go\ntype Config struct {\n    Features FeatureFlags `config:\"feature\"`\n}\n\ntype FeatureFlags struct {\n    EnableNewUI      bool `config:\"new_ui\"`\n    EnableBetaAPI    bool `config:\"beta_api\"`\n    EnableCaching    bool `config:\"caching\"`\n}\n\n// Load base config with all features disabled\n// Then override based on environment\n\n// base.env\nFEATURE_NEW_UI=false\nFEATURE_BETA_API=false\nFEATURE_CACHING=true\n\n// production.env\nFEATURE_CACHING=true\n\n// development.env\nFEATURE_NEW_UI=true\nFEATURE_BETA_API=true\nFEATURE_CACHING=false\n```\n\n## Migration Guide\n\n### From Viper\n\n**Before (Viper):**\n\n```go\nimport \"github.com/spf13/viper\"\n\nviper.SetConfigName(\"config\")\nviper.SetConfigType(\"json\")\nviper.AddConfigPath(\".\")\nviper.AutomaticEnv()\n\nif err := viper.ReadInConfig(); err != nil {\n    log.Fatal(err)\n}\n\nport := viper.GetInt(\"server.port\")\nhost := viper.GetString(\"server.host\")\n```\n\n**After (Gathuk):**\n\n```go\nimport \"github.com/ahyalfan/gathuk\"\n\ntype Config struct {\n    Server struct {\n        Port int    `config:\"port\"`\n        Host string `config:\"host\"`\n    } `config:\"server\"`\n}\n\ngt := gathuk.NewGathuk[Config]()\ngt.GlobalDecodeOpt.AutomaticEnv = true\n\nif err := gt.LoadConfigFiles(\"config.json\"); err != nil {\n    log.Fatal(err)\n}\n\nconfig := gt.GetConfig()\nport := config.Server.Port\nhost := config.Server.Host\n```\n\n**Benefits:**\n\n- ✅ Type-safe access (no Get\\* methods)\n- ✅ Compile-time checking\n- ✅ Better IDE support\n- ✅ No string keys to remember\n\n### From godotenv\n\n**Before (godotenv):**\n\n```go\nimport \"github.com/joho/godotenv\"\n\nif err := godotenv.Load(); err != nil {\n    log.Fatal(err)\n}\n\nport, _ := strconv.Atoi(os.Getenv(\"PORT\"))\nhost := os.Getenv(\"HOST\")\ndebug := os.Getenv(\"DEBUG\") == \"true\"\n```\n\n**After (Gathuk):**\n\n```go\nimport \"github.com/ahyalfan/gathuk\"\n\ntype Config struct {\n    Port  int\n    Host  string\n    Debug bool\n}\n\ngt := gathuk.NewGathuk[Config]()\nif err := gt.LoadConfigFiles(\".env\"); err != nil {\n    log.Fatal(err)\n}\n\nconfig := gt.GetConfig()\nport := config.Port    // Automatically converted to int\nhost := config.Host\ndebug := config.Debug  // Automatically converted to bool\n```\n\n**Benefits:**\n\n- ✅ Automatic type conversion\n- ✅ No manual parsing\n- ✅ Type-safe struct\n- ✅ Less boilerplate\n\n### From encoding/json\n\n**Before (encoding/json):**\n\n```go\nimport \"encoding/json\"\n\nfile, err := os.Open(\"config.json\")\nif err != nil {\n    log.Fatal(err)\n}\ndefer file.Close()\n\nvar config Config\ndecoder := json.NewDecoder(file)\nif err := decoder.Decode(\u0026config); err != nil {\n    log.Fatal(err)\n}\n```\n\n**After (Gathuk):**\n\n```go\nimport \"github.com/ahyalfan/gathuk\"\n\ngt := gathuk.NewGathuk[Config]()\nif err := gt.LoadConfigFiles(\"config.json\"); err != nil {\n    log.Fatal(err)\n}\n\nconfig := gt.GetConfig()\n```\n\n**Benefits:**\n\n- ✅ Environment variable support\n- ✅ Multiple file merging\n- ✅ Format-agnostic (same code for .env, JSON, etc.)\n- ✅ Less boilerplate\n\n## API Reference\n\n### Core Functions\n\n#### `NewGathuk[T any]() *Gathuk[T]`\n\nCreates a new Gathuk instance with default configuration.\n\n#### `LoadConfigFiles(srcFiles ...string) error`\n\nLoads and merges configurations from one or more files.\n\n#### `LoadConfig(src io.Reader, format string) error`\n\nLoads configuration from an io.Reader with specified format.\n\n#### `GetConfig() T`\n\nReturns the parsed configuration struct.\n\n#### `WriteConfigFile(dst string, mode fs.FileMode, config T) error`\n\nWrites configuration to a file.\n\n#### `WriteConfig(out io.Writer, format string, config T) error`\n\nWrites configuration to an io.Writer.\n\n#### `SetConfigFiles(srcFiles ...string)`\n\nSets base configuration files without loading them.\n\n#### `SetCustomCodecRegistry(c option.CodecRegistry[T]) *Gathuk[T]`\n\nSets a custom codec registry for handling different file formats.\n\n#### `SetDecodeOption(format string, opt *option.DecodeOption)`\n\nSets decode options for a specific format.\n\n#### `SetEncodeOption(format string, opt *option.EncodeOption)`\n\nSets encode options for a specific format.\n\n### For complete API documentation, see [GoDoc](https://godoc.org/github.com/ahyalfan/gathuk)\n\n## FAQ\n\n**Q: Can I use multiple formats simultaneously?**  \nA: Yes! You can load different formats in sequence: `gt.LoadConfigFiles(\"base.json\", \"override.env\")`\n\n**Q: How do I handle missing configuration files?**  \nA: Check for `os.IsNotExist(err)` and provide defaults or use fallback files.\n\n**Q: Can I reload configuration at runtime?**  \nA: Yes, call `LoadConfigFiles()` again. Values will be merged with existing configuration.\n\n**Q: Does it support configuration validation?**  \nA: Validate after loading using your own validation logic or libraries like `go-playground/validator`.\n\n**Q: How do I set default values?**  \nA: Initialize your struct with defaults before loading: `config := Config{Port: 8080}`\n\n**Q: Can I use with Docker/Kubernetes?**  \nA: Yes! Use `AutomaticEnv` to read from environment variables set by orchestration tools.\n\n**Q: Is it thread-safe?**  \nA: Reading config after loading is thread-safe. Loading config should be done during initialization.\n\n## Roadmap\n\n- [x] .env format support\n- [x] JSON format support\n- [x] Environment variable binding\n- [x] Multiple file merging\n- [x] Nested structure support\n- [x] Write support\n- [ ] YAML format support\n- [ ] TOML format support\n- [ ] Configuration validation\n- [ ] Hot reload support\n- [ ] Configuration encryption\n- [ ] Remote config sources (etcd, consul)\n\n## Contributing\n\nContributions are welcome! Please feel free to submit a Pull Request.\n\n### Development Setup\n\n```bash\n# Clone the repository\ngit clone https://github.com/ahyalfan/gathuk.git\ncd gathuk\n\n# Run tests\ngo test ./...\n\n# Run benchmarks\ngo test -bench=. -benchmem\n\n# Run with coverage\ngo test -cover ./...\n\n# Generate coverage report\ngo test -coverprofile=coverage.out ./...\ngo tool cover -html=coverage.out\n```\n\n### Contribution Guidelines\n\n1. Fork the repository\n2. Create your feature branch (`git checkout -b feature/AmazingFeature`)\n3. Write tests for your changes\n4. Ensure all tests pass (`go test ./...`)\n5. Run `go fmt ./...` to format code\n6. Commit your changes (`git commit -m 'Add some AmazingFeature'`)\n7. Push to the branch (`git push origin feature/AmazingFeature`)\n8. Open a Pull Request\n\n## License\n\nThis project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.\n\n## Author\n\n- [@ahyalfan](https://github.com/ahyalfan)\n\n## Acknowledgments\n\n- Inspired by [Viper](https://github.com/spf13/viper) and [godotenv](https://github.com/joho/godotenv)\n- Thanks to all contributors\n\n## Support\n\nIf you find this project helpful, please give it a ⭐️!\n\nFor issues and questions, please use the [GitHub issue tracker](https://github.com/ahyalfan/gathuk/issues).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fahyalfan%2Fgathuk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fahyalfan%2Fgathuk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fahyalfan%2Fgathuk/lists"}