{"id":49003001,"url":"https://github.com/tinyauthapp/paerser","last_synced_at":"2026-04-18T19:07:19.136Z","repository":{"id":350474756,"uuid":"1206986439","full_name":"tinyauthapp/paerser","owner":"tinyauthapp","description":"A Go library for loading configuration into structs from multiple sources.","archived":false,"fork":false,"pushed_at":"2026-04-10T14:27:38.000Z","size":179,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-10T15:25:53.764Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","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/tinyauthapp.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":null,"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":"2026-04-10T13:05:55.000Z","updated_at":"2026-04-10T14:28:01.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/tinyauthapp/paerser","commit_stats":null,"previous_names":["tinyauthapp/paerser"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/tinyauthapp/paerser","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tinyauthapp%2Fpaerser","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tinyauthapp%2Fpaerser/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tinyauthapp%2Fpaerser/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tinyauthapp%2Fpaerser/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tinyauthapp","download_url":"https://codeload.github.com/tinyauthapp/paerser/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tinyauthapp%2Fpaerser/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31980840,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-18T17:30:12.329Z","status":"ssl_error","status_checked_at":"2026-04-18T17:29:59.069Z","response_time":103,"last_error":"SSL_read: 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":[],"created_at":"2026-04-18T19:07:18.278Z","updated_at":"2026-04-18T19:07:19.122Z","avatar_url":"https://github.com/tinyauthapp.png","language":"Go","readme":"# Paerser\n\nPaerser is a Go library for loading configuration into structs from multiple sources:\n\n- CLI flag\n- Configuration files\n- Environment variables\n\nIt also provides a lightweight CLI command system with sub-commands, automatic help generation, and resource loaders that chain these configuration sources together.\n\n\u003e [!NOTE]\n\u003e This fork is created to support the needs of the [Tinyauth](https://github.com/steveiliop56/tinyauth). Over time, we may remove some features that are not relevant to our use case, and we may also add new features that we need. If you want to contribute or have any questions, please feel free to open an issue or a pull request.\n\n*Before you ask, yes the readme is generated by an LLM because LLM readme is better than no readme ;)*\n\n## Installation\n\n```\ngo get github.com/tinyauthapp/paerser\n```\n\n## Quick Start\n\nDefine a configuration struct and populate it from any source:\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/tinyauthapp/paerser/env\"\n\t\"github.com/tinyauthapp/paerser/file\"\n\t\"github.com/tinyauthapp/paerser/flag\"\n)\n\ntype Config struct {\n\tHost string\n\tPort int\n\tDB   DatabaseConfig\n}\n\ntype DatabaseConfig struct {\n\tDSN     string\n\tMaxConn int\n}\n\nfunc main() {\n\tcfg := Config{}\n\n\t// From flags:\n\terr := flag.Decode([]string{\"--host=localhost\", \"--port=8080\", \"--db.dsn=postgres://localhost/mydb\"}, \u0026cfg)\n\n\t// From environment variables:\n\terr = env.Decode(os.Environ(), \"MYAPP_\", \u0026cfg)\n\n\t// From a file (YAML, TOML, or JSON):\n\terr = file.Decode(\"config.yml\", \u0026cfg)\n\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tfmt.Printf(\"%+v\\n\", cfg)\n}\n```\n\n## Packages\n\n### `flag` - CLI Flag Decoding \u0026 Encoding\n\nDecodes command-line flag arguments into a struct using dot-separated names that mirror the struct hierarchy.\n\n#### Decoding\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\n\t\"github.com/tinyauthapp/paerser/flag\"\n)\n\ntype Config struct {\n\tServer ServerConfig\n\tDebug  bool\n}\n\ntype ServerConfig struct {\n\tHost string\n\tPort int\n\tTags []string\n}\n\nfunc main() {\n\targs := []string{\n\t\t\"--server.host=0.0.0.0\",\n\t\t\"--server.port=9090\",\n\t\t\"--server.tags=web,api\",\n\t\t\"--debug\",\n\t}\n\n\tcfg := Config{}\n\tif err := flag.Decode(args, \u0026cfg); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tfmt.Println(cfg.Server.Host) // \"0.0.0.0\"\n\tfmt.Println(cfg.Server.Port) // 9090\n\tfmt.Println(cfg.Server.Tags) // [web api]\n\tfmt.Println(cfg.Debug)       // true\n}\n```\n\n*Flag syntax rules:\n\n| Syntax | Behavior |\n| - | - |\n| `--flag value` or `--flag=value` | Standard flag with a value |\n| `-flag value` or `-flag=value` | Short-form (single dash) |\n| `--boolflag` | Boolean flags are set to `true` implicitly |\n| `--slice=a,b,c` | Comma-separated values populate slices |\n| `--` | Stops flag parsing |\n\n#### Encoding\n\nEncode a struct back into a flat list of flag definitions (useful for help text generation):\n\n```go\nflats, err := flag.Encode(\u0026cfg)\n// Each flat has a Name (e.g. \"server.host\") and Default value.\n```\n\n#### Low-Level Parsing\n\nIf you only need the raw key-value map without populating a struct:\n\n```go\nparsed, err := flag.Parse(os.Args[1:], \u0026cfg)\n// parsed is a map[string]string, e.g. {\"traefik.server.host\": \"0.0.0.0\"}\n```\n\n### `env` - Environment Variable Decoding \u0026 Encoding\n\nDecodes environment variables into a struct. Variable names are derived from the struct field path, uppercased, with underscores as separators.\n\n#### Decoding\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"os\"\n\n\t\"github.com/tinyauthapp/paerser/env\"\n)\n\ntype Config struct {\n\tHost string\n\tPort int\n\tDB   DBConfig\n}\n\ntype DBConfig struct {\n\tDSN     string\n\tMaxConn int\n}\n\nfunc main() {\n\tos.Setenv(\"APP_HOST\", \"localhost\")\n\tos.Setenv(\"APP_PORT\", \"3000\")\n\tos.Setenv(\"APP_DB_DSN\", \"postgres://localhost/mydb\")\n\tos.Setenv(\"APP_DB_MAXCONN\", \"10\")\n\n\tcfg := Config{}\n\tif err := env.Decode(os.Environ(), \"APP_\", \u0026cfg); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tfmt.Println(cfg.Host)       // \"localhost\"\n\tfmt.Println(cfg.Port)       // 3000\n\tfmt.Println(cfg.DB.DSN)     // \"postgres://localhost/mydb\"\n\tfmt.Println(cfg.DB.MaxConn) // 10\n}\n```\n\nThe prefix (e.g. `\"APP_\"`) must match the pattern `^[a-zA-Z0-9]+_$` - alphanumeric characters followed by a single trailing underscore.\n\n#### Encoding\n\nEncode a struct into flat environment variable representations:\n\n```go\nflats, err := env.Encode(\"APP_\", \u0026cfg)\n// Each flat has a Name (e.g. \"APP_DB_DSN\") and Default value.\n```\n\n#### Filtering\n\nFind only the environment variables that are relevant to a given struct:\n\n```go\nvars := env.FindPrefixedEnvVars(os.Environ(), \"APP_\", \u0026cfg)\n// Returns only env vars whose keys match known struct field paths.\n```\n\nThis is more precise than a simple prefix match — it checks that the variable name corresponds to an actual field in the struct.\n\n### `file` - Configuration File Decoding\n\nDecodes YAML, TOML, or JSON configuration files into a struct. The format is detected from the file extension.\n\n#### From a File Path\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\n\t\"github.com/tinyauthapp/paerser/file\"\n)\n\ntype Config struct {\n\tServer ServerConfig\n\tDebug  bool\n}\n\ntype ServerConfig struct {\n\tHost string\n\tPort int\n}\n\nfunc main() {\n\tcfg := Config{}\n\tif err := file.Decode(\"config.yml\", \u0026cfg); err != nil {\n\t\tlog.Fatal(err)\n\t}\n\tfmt.Printf(\"%+v\\n\", cfg)\n}\n```\n\nWhere `config.yml` contains:\n\n```yaml\nserver:\n  host: localhost\n  port: 8080\ndebug: true\n```\n\nSupported extensions: `.toml`, `.yaml`, `.yml`, `.json`\n\n#### From a String\n\nIf you already have the content in memory:\n\n```go\ncontent := `\nserver:\n  host: localhost\n  port: 8080\n`\ncfg := Config{}\nerr := file.DecodeContent(content, \".yml\", \u0026cfg)\n```\n\n### `cli` - Command System\n\nA lightweight CLI framework that ties together flag, env, and file loaders with support for sub-commands and automatic help generation.\n\n#### Basic Command\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\n\t\"github.com/tinyauthapp/paerser/cli\"\n)\n\ntype Config struct {\n\tHost  string `description:\"Server host\"`\n\tPort  int    `description:\"Server port\"`\n\tDebug bool   `description:\"Enable debug mode\"`\n}\n\nfunc main() {\n\tcfg := \u0026Config{}\n\n\tcmd := \u0026cli.Command{\n\t\tName:          \"myapp\",\n\t\tDescription:   \"My awesome application\",\n\t\tConfiguration: cfg,\n\t\tResources: []cli.ResourceLoader{\n\t\t\t\u0026cli.FlagLoader{},\n\t\t},\n\t\tRun: func(args []string) error {\n\t\t\tfmt.Printf(\"Starting server on %s:%d (debug=%v)\\n\", cfg.Host, cfg.Port, cfg.Debug)\n\t\t\treturn nil\n\t\t},\n\t}\n\n\tif err := cli.Execute(cmd); err != nil {\n\t\tlog.Fatal(err)\n\t}\n}\n```\n\n```\n$ myapp --host=0.0.0.0 --port=8080 --debug\nStarting server on 0.0.0.0:8080 (debug=true)\n```\n\n#### Resource Loaders\n\nResource loaders populate `cmd.Configuration` and are executed in order. If a loader returns `done == true`, the chain stops.\n\n| Loader | Source | Description |\n| - | - | - |\n| `cli.FlagLoader{}` | CLI flags | Decodes `--flag=value` arguments |\n| `cli.EnvLoader{Prefix: \"MYAPP_\"}` | Environment | Decodes `MYAPP_*` environment variables |\n| `cli.FileLoader{...}` | Config file | Loads from a file (path given via a flag or searched from base paths) |\n\nChaining loaders (file first, then flags to override):\n\n```go\ncmd := \u0026cli.Command{\n\tName:          \"myapp\",\n\tDescription:   \"My application\",\n\tConfiguration: cfg,\n\tResources: []cli.ResourceLoader{\n\t\t\u0026cli.FileLoader{\n\t\t\tConfigFileFlag: \"configFile\",\n\t\t\tBasePaths:      []string{\"/etc/myapp/myapp\", \"$HOME/.config/myapp\"},\n\t\t\tExtensions:     []string{\"toml\", \"yaml\", \"yml\"},\n\t\t},\n\t\t\u0026cli.EnvLoader{Prefix: \"MYAPP_\"},\n\t\t\u0026cli.FlagLoader{},\n\t},\n\tRun: func(args []string) error {\n\t\t// cfg is now populated from file -\u003e env -\u003e flags (in order)\n\t\treturn nil\n\t},\n}\n```\n\n```\n$ myapp --configFile=/path/to/config.toml --port=9090\n```\n\n#### Sub-Commands\n\n```go\nrootCmd := \u0026cli.Command{\n\tName:        \"myapp\",\n\tDescription: \"My application\",\n}\n\nserveCmd := \u0026cli.Command{\n\tName:          \"serve\",\n\tDescription:   \"Start the server\",\n\tConfiguration: \u0026ServeConfig{},\n\tResources:     []cli.ResourceLoader{\u0026cli.FlagLoader{}},\n\tRun: func(args []string) error {\n\t\tfmt.Println(\"Server started!\")\n\t\treturn nil\n\t},\n}\n\nmigrateCmd := \u0026cli.Command{\n\tName:          \"migrate\",\n\tDescription:   \"Run database migrations\",\n\tConfiguration: \u0026MigrateConfig{},\n\tResources:     []cli.ResourceLoader{\u0026cli.FlagLoader{}},\n\tRun: func(args []string) error {\n\t\tfmt.Println(\"Migrations complete!\")\n\t\treturn nil\n\t},\n}\n\nrootCmd.AddCommand(serveCmd)\nrootCmd.AddCommand(migrateCmd)\n\ncli.Execute(rootCmd)\n```\n\n```\n$ myapp serve --port=8080\n$ myapp migrate --db.dsn=postgres://localhost/mydb\n```\n\n#### Automatic Help\n\nHelp is generated automatically from the struct's flags and descriptions. Pass `--help` or `-h` to any command:\n\n```\n$ myapp --help\nmyapp    My application\n\nUsage: myapp [command] [flags] [arguments]\n\nUse \"myapp [command] --help\" for help on any command.\n\nCommands:\n    serve      Start the server\n    migrate    Run database migrations\n```\n\nYou can also provide a custom help function:\n\n```go\ncmd := \u0026cli.Command{\n\tCustomHelpFunc: func(w io.Writer, cmd *cli.Command) error {\n\t\tfmt.Fprintf(w, \"Custom help for %s\\n\", cmd.Name)\n\t\treturn nil\n\t},\n}\n```\n\n### `generator` - Struct Initialization with Defaults\n\nRecursively initializes all pointer and struct fields in a configuration struct, calling `SetDefaults()` on any field that implements the `initializer` interface.\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/tinyauthapp/paerser/generator\"\n)\n\ntype Config struct {\n\tServer *ServerConfig\n}\n\ntype ServerConfig struct {\n\tHost string\n\tPort int\n}\n\nfunc (s *ServerConfig) SetDefaults() {\n\ts.Host = \"localhost\"\n\ts.Port = 8080\n}\n\nfunc main() {\n\tcfg := \u0026Config{}\n\tgenerator.Generate(cfg)\n\n\tfmt.Println(cfg.Server.Host) // \"localhost\"\n\tfmt.Println(cfg.Server.Port) // 8080\n}\n```\n\n`Generate` will:\n- Allocate nil pointers (so `*ServerConfig` is no longer nil)\n- Call `SetDefaults()` on any field whose pointer type implements it\n- Recurse into nested structs, maps, and slices\n\n### `types` - Custom Types\n\n#### `Duration`\n\nA duration type that works seamlessly with TOML, YAML, JSON, and plain integer values (interpreted as seconds).\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/tinyauthapp/paerser/types\"\n)\n\nfunc main() {\n\tvar d types.Duration\n\n\td.Set(\"30s\")          // 30 seconds\n\td.Set(\"5m30s\")        // 5 minutes and 30 seconds\n\td.Set(\"120\")          // 120 seconds (suffix-less integers are treated as seconds)\n\n\tfmt.Println(d.String()) // \"2m0s\"\n}\n```\n\nIt implements `encoding.TextMarshaler`, `encoding.TextUnmarshaler`, `json.Marshaler`, and `json.Unmarshaler`, so it works out of the box in configuration files:\n\n```yaml\ntimeout: 30s\ninterval: 120\n```\n\n### `parser` - Low-Level Parsing Engine\n\nThe `parser` package is the foundation that all other packages build on. It provides a tree-based intermediate representation (`Node`) for configuration data, along with encoding/decoding utilities.\n\nMost users won't need to use this package directly, but it's available for advanced use cases:\n\n```go\n// Decode a flat label map into a struct\nlabels := map[string]string{\n\t\"traefik.http.routers.myrouter.rule\": \"Host(`example.com`)\",\n\t\"traefik.http.routers.myrouter.tls\":  \"true\",\n}\nerr := parser.Decode(labels, \u0026cfg, \"traefik\")\n\n// Encode a struct into a flat label map\nlabels, err := parser.Encode(\u0026cfg, \"traefik\")\n```\n\nKey types and functions:\n- `Node`- A recursive tree node with `Name`, `Value`, `Kind`, `Children`, etc.\n- `Decode(labels, element, rootName)` - Flat map -\u003e struct\n- `Encode(element, rootName)` - Struct -\u003e flat map\n- `Flat` - A flattened key/value/default representation used by `flag.Encode` and `env.Encode`\n\n## Struct Tags\n\nThe library uses struct tags to control field naming and behavior in different contexts:\n\n| Tag | Used By | Purpose |\n|---|---|---|\n| `description` | `cli` (help generation) | Description shown in `--help` output |\n| `label` | `parser`, `generator` | Controls label-based encoding/decoding. Use `\"-\"` to skip a field |\n| `file` | `file` | Controls file-based decoding |\n\n## License\n\n[Apache License 2.0](LICENSE)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftinyauthapp%2Fpaerser","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftinyauthapp%2Fpaerser","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftinyauthapp%2Fpaerser/lists"}