{"id":28012317,"url":"https://github.com/chaindead/zerocfg","last_synced_at":"2025-05-09T23:01:15.276Z","repository":{"id":290639003,"uuid":"975103830","full_name":"chaindead/zerocfg","owner":"chaindead","description":"Zero-effort, concise configuration management that avoids boilerplate and repetitive actions.","archived":false,"fork":false,"pushed_at":"2025-05-08T08:50:48.000Z","size":79,"stargazers_count":159,"open_issues_count":2,"forks_count":5,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-05-08T09:40:01.130Z","etag":null,"topics":["config","configuration","environment","flag","go","golang","yaml"],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/chaindead/zerocfg","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/chaindead.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-04-29T19:38:07.000Z","updated_at":"2025-05-08T08:50:52.000Z","dependencies_parsed_at":"2025-04-30T16:27:06.184Z","dependency_job_id":"8a752a34-7c18-4121-a4cf-58cebb235a2c","html_url":"https://github.com/chaindead/zerocfg","commit_stats":null,"previous_names":["chaindead/zerocfg","chaindead/zeroconf"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chaindead%2Fzerocfg","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chaindead%2Fzerocfg/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chaindead%2Fzerocfg/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/chaindead%2Fzerocfg/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/chaindead","download_url":"https://codeload.github.com/chaindead/zerocfg/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253336909,"owners_count":21892797,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["config","configuration","environment","flag","go","golang","yaml"],"created_at":"2025-05-09T23:00:47.504Z","updated_at":"2025-05-09T23:01:15.041Z","avatar_url":"https://github.com/chaindead.png","language":"Go","readme":"# Zero Effort Configuration\n\n[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go?tab=readme-ov-file#configuration)\n[![GoDoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/chaindead/zerocfg)\n[![Go Report Card](https://goreportcard.com/badge/github.com/chaindead/zerocfg)](https://goreportcard.com/report/github.com/chaindead/zerocfg)\n[![Codecov](https://codecov.io/gh/chaindead/zerocfg/branch/main/graph/badge.svg)](https://codecov.io/gh/chaindead/zerocfg)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![visitors](https://api.visitorbadge.io/api/visitors?path=github.com%2Fchaindead%2Fzerocfg\u0026label=Visitors\u0026countColor=%23263759\u0026style=plastic\u0026labelStyle=none)](https://visitorbadge.io/status?path=github.com%2Fchaindead%2Fzerocfg)\n\nI've always loved the elegance of Go's flag package - how clean and straightforward it is to define and use configuration options. While working on various Go projects, I found myself wanting that same simplicity but with support for YAML configs. I couldn't find anything that preserved this paradigm, so I built zerocfg.\n\n- 🛠️ Simple and flexible API inspired by `flag` package\n- 🍳 Boilerplate usage prohibited by design\n- 🚦 Early detection of mistyped config keys\n- ✨ Multiple configuration sources with priority-based value resolution\n- 🕵️‍♂️ Render running configuration with secret protection\n- 🧩 Custom option types and providers are supported\n\n## Table of Contents\n\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Usage](#usage)\n  - [Options naming](#options-naming)\n  - [Restrictions](#restrictions)\n  - [Unknown values](#unknown-values)\n  - [Complex Types as string](#complex-types-as-string)\n- [Configuration Sources](#configuration-sources)\n  - [Command-line Arguments](#command-line-arguments)\n  - [Environment Variables](#environment-variables)\n  - [YAML Source](#yaml-source)\n- [Advanced Usage](#advanced-usage)\n  - [Value Representation](#value-representation)\n  - [Custom Options](#custom-options)\n  - [Custom Providers](#custom-providers)\n\n## Installation\n\n```bash\ngo get -u github.com/chaindead/zerocfg\n```\n\n## Quick Start\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n\n    zfg \"github.com/chaindead/zerocfg\"\n    \"github.com/chaindead/zerocfg/env\"\n    \"github.com/chaindead/zerocfg/yaml\"\n)\n\nvar (\n    // Configuration variables\n    path     = zfg.Str(\"config.path\", \"\", \"path to yaml conf file\", zfg.Alias(\"c\"))\n    ip       = zfg.IP(\"db.ip\", \"127.0.0.1\", \"database location\")\n    port     = zfg.Uint(\"db.port\", 5678, \"database port\")\n    username = zfg.Str(\"db.user\", \"guest\", \"user of database\")\n    password = zfg.Str(\"db.password\", \"qwerty\", \"password for user\", zfg.Secret())\n)\n\nfunc main() {\n    // Initialize configuration with multiple sources\n    err := zfg.Parse(\n        env.New(),\n        yaml.New(path),\n    )\n    if err != nil {\n        panic(err)\n    }\n\n    fmt.Printf(\"Connect to %s:%d creds=%s:%s\\n\", *ip, *port, *username, *password)\n    // OUTPUT: Connect to 127.0.0.1:5678 creds=guest:qwerty\n\n    fmt.Println(zfg.Show())\n    // CMD: go run ./... -c test.yaml\n    // OUTPUT:\n    //  config.path = test.yaml      (path to yaml conf file)\n    //  db.ip       = 127.0.0.1      (database location)\n    //  db.password = \u003csecret\u003e       (password for user)\n    //  db.port     = 5678           (database port)\n    //  db.user     = guest          (user of database)\n}\n```\n\n## Usage\n\n### Options naming\n\n- Dots (`.`) are used as separators for hierarchical options.\n- Option subnames preferred separation is camelCase, underscore (`_`), and dash (`-`) styles.\n\n**Example:**\n\n```go\nzfg.Str(\"groupOptions.thisOption\", \"\", \"camelCase usage\")\nzfg.Str(\"group_options.this_option\", \"\", \"underscore usage\")\nzfg.Str(\"group-options.this-option\", \"\", \"dash usage\")\n```\n\n### Restrictions\n\n- Options are registered at import time. Dynamic (runtime) option registration is not supported\n\n    ```go\n    // internal/db/client.go\n    package db\n\n    import zfg \"github.com/chaindead/zerocfg\"\n\n    // good: options registered at import\n    var dbHost = zfg.Str(\"db.host\", \"localhost\", \"called on import\")\n\n    // bad: dynamic registration\n    func AddOption() {\n        zfg.Str(\"db.dynamic\", \"\", \"not supported\")\n    }\n    ```\n\n- No key duplication is allowed. Each option key must be unique to ensure a single source of truth and avoid boilerplate\n- Simultaneous use of keys and sub-keys (e.g., `map` and `map.value`) are not allowed\n\n### Unknown values\n\nIf `zfg.Parse` encounters an unknown value (e.g. variable not registered as an option), it returns an error. \nThis helps avoid boilerplate and ensures only declared options are used. \n\nBut you can ignore unknown values if desired.\n\n```go\nerr := zfg.Parse(\n    env.New(),\n    yaml.New(path),\n)\nif u, ok := zfg.IsUnknown(err); !ok {\n    panic(err)\n} else {\n    // u is map \u003csource_name\u003e to slice of unknown keys\n    fmt.Println(u)\n}\n```\n\n\u003e `env` source does not trigger unknown options to avoid false positives.\n\n### Complex Types as string\n\n- Base values converted via `fmt.Sprint(\"%v\")`\n- If a type has a `String()` method, it is used for string conversion (e.g., `time.Duration`).\n- Otherwise, JSON representation is used for complex types (e.g., slices, maps).\n\n\u003e For converting any value to string, `zfg.ToString` is used internally.\n\n```go\nvar (\n    _ = zfg.Dur(\"timeout\", 5*time.Second, \"duration via fmt.Stringer interface\")\n    _ = zfg.Floats64(\"floats\", nil, \"list via json\")\n)\n\nfunc main() {\n    _ = zfg.Parse()\n\n    fmt.Printf(zfg.Show())\n    // CMD: go run ./... --timeout 10s --floats '[1.1, 2.2, 3.3]'\n    // OUTPUT:\n    //   floats  = [1.1,2.2,3.3] (list via json)\n    //   timeout = 10s           (duration via fmt.Stringer interface)\n}\n\n```\n\n## Configuration Sources\n\nThe configuration system follows a strict priority hierarchy:\n\n1. Command-line flags (always highest priority, enabled by default)\n2. Optional providers in order of addition (first added = higher priority)\n3. Default values (lowest priority)\n\nFor example, if you initialize configuration like this:\n```go\nzfg.Parse(\n    env.New(),      // Second highest priority (after cli flags)\n    yaml.New(path), // Third highest priority\n)\n```\n\nThe final value resolution order will be:\n1. Command-line flags (if provided)\n2. Providers from arguments of `zfg.Parse` in same order as it is passed.\n3. Default values\n\nImportant notes:\n- Lower priority sources cannot override values from higher priority sources\n- All providers except flags are optional\n- Provider priority is determined by the order in `Parse()` function\n- Values not found in higher priority sources fall back to lower priority sources\n\n### Command-line Arguments\n\n- The flag source is enabled by default and always has the highest priority\n- You can define configuration options with aliases for convenient CLI usage\n- Values are passed as space-separated arguments (no `=` allowed)\n- Both single dash (`-`) and double dash (`--`) prefixes are supported for flags and their aliases\n\n**Example:**\n\n```go\npath := zfg.Str(\"config.path\", \"\", \"path to yaml conf file\", zfg.Alias(\"c\"))\n```\n\nYou can run your application with:\n\n```\ngo run ./... -c test.yaml\n# or\ngo run ./... --config.path test.yaml\n```\n\nIn both cases, the value `test.yaml` will be assigned to `config.path`.\n\n### Environment Variables\n\nEnvironment variables are automatically transformed from the configuration key format:\n\n| Config Key | Environment Variable | Note |\n|------------|---------------------|------|\n| db.user | DB_USER | Basic transformation |\n| app.api.key | APP_API_KEY | Multi-level path |\n| camelCase.value | CAMELCASE_VALUE | CamelCase handling |\n| api-key.secret | APIKEY_SECRET | Dashes removed |\n| under_score.value | UNDERSCORE_VALUE | Underscores removed |\n\n\nThe transformation rules:\n1. Remove special characters (except letters, digits, and dots)\n2. Replace dots with underscores\n3. Convert to uppercase\n\n**Example:**\n\n```go\nimport (\n    \"fmt\"\n    zfg \"github.com/chaindead/zerocfg\"\n    \"github.com/chaindead/zerocfg/env\"\n)\nvar dbUser = zfg.Str(\"db.user\", \"\", \"database's username\")\n\nfunc main() {\n    _ = zfg.Parse(\n        env.New(),\n    )\n    fmt.Printf(\"DB user: %s\", *dbUser)\n}\n```\n\nWhen you run, `dbUser` will be set to `admin`.\n\n```bash\nDB_USER=admin go run main.go\n# OUTPUT: DB user: admin\n```\n\n### YAML Source\n\n- Options use dotted paths to map to YAML keys, supporting hierarchical configuration.\n- All naming styles are supported and mapped to YAML keys as written.\n\n**Example YAML file:**\n\n```yaml\ngroup:\n  option: \"foo\"\n\nnumbers:\n  - 1\n  - 2\n  - 3\n\nlimits:\n  max: 10\n  min: 1\n```\n\n**Example Go config:**\n\n```go\nzfg.Str(\"group.option\", \"\", \"hierarchical usage\")\nzfg.Ints(\"numbers\", nil, \"slice of server configs\")\nzfg.Map(\"limits\", nil, \"map of limits\")\n```\n\n## Advanced Usage\n\n### Value Representation\n\n\u003e [!IMPORTANT]\n\u003e Read this section before implementing custom options or providers.\n\u003e - All supported option values must have a string representation\n\u003e - Conversion to string is performed using `zfg.ToString`\n\u003e - Types must implement `Set(string)`; the string passed is produced by `ToString` and parsing must be compatible\n\u003e - Providers return `map[string]string` where values are produced by the `conv` function  argument in the provider interface (internally `zfg.ToString` is used)\n\n### Custom Options\n\nYou can define your own option types by implementing the `Value` interface and registering them via `Any` function.\nMethods `Set` and `String` should be compatible.\n\n```go\n// Custom type\ntype MyType struct{ V string }\n\nfunc newValue(val MyType, p *MyType) zfg.Value {\n    *p = val\n    return p\n}\n\nfunc (m *MyType) Set(s string) error { m.V = s; return nil }\nfunc (m *MyType) Type() string       { return \"custom\" }\nfunc (m *MyType) String() string { return m.V }\n\nfunc Custom(name string, defVal MyType, desc string, opts ...zfg.OptNode) *MyType {\n     return zfg.Any(name, defVal, desc, newValue, opts...)\n}\n\n// Register custom option\nvar myOpt = Custom(\"custom.opt\", MyType{\"default\"}, \"custom option\")\n```\n\n### Custom Providers\n\nYou can add your own configuration sources by implementing the `Provider` interface.\n\n- If `awaited[name] == true`, the name is an option\n- If `awaited[name] == false`, the name is an alias\n\n```go\ntype MyProvider struct{}\n\nfunc (p *MyProvider) Type() string { return \"my\" }\nfunc (p *MyProvider) Provide(awaited map[string]bool, conv func(any) string) (map[string]string, map[string]string, error) {\n    found := map[string]string{}\n    unknown := map[string]string{}\n    // ... fill found/unknown based on awaited ...\n    return found, unknown, nil\n}\n\n// Usage\nzfg.Parse(\u0026MyProvider{})\n```\n\n## Documentation\n\nFor detailed documentation and advanced usage examples, visit our [Godoc page](https://godoc.org/github.com/chaindead/zerocfg).\n\n## Star History\n\n\u003ca href=\"https://www.star-history.com/#chaindead/zerocfg\u0026Date\"\u003e\n \u003cpicture\u003e\n   \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://api.star-history.com/svg?repos=chaindead/zerocfg\u0026type=Date\u0026theme=dark\" /\u003e\n   \u003csource media=\"(prefers-color-scheme: light)\" srcset=\"https://api.star-history.com/svg?repos=chaindead/zerocfg\u0026type=Date\" /\u003e\n   \u003cimg alt=\"Star History Chart\" src=\"https://api.star-history.com/svg?repos=chaindead/zerocfg\u0026type=Date\" /\u003e\n \u003c/picture\u003e\n\u003c/a\u003e\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](https://github.com/chaindead/zerocfg/blob/main/LICENCE) file for details.","funding_links":[],"categories":["Configuration","配置"],"sub_categories":["Standard CLI","标准CLI"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchaindead%2Fzerocfg","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fchaindead%2Fzerocfg","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fchaindead%2Fzerocfg/lists"}