Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Copilot Instructions

Project purpose
- Provide a small utility to bind struct fields to pflag flags for CLI programs.

Design principles
- Small, dependency-minimal, and well-tested.
- Keep public API stable and backward-compatible.

Error handling policy
- Use sentinel errors where appropriate and ensure `errors.Is` works.
- Guard nil-sensitive inputs and return clear errors.

Testing / validation
- Local validation command: `task test`
- Prefer deterministic tests using `httptest` or mocked `http.RoundTripper`.

Release process
- Tag from `main`/`master` with semver `vMAJOR.MINOR.PATCH`.
- Annotated tags: `git tag -a vX.Y.Z -m "Release vX.Y.Z"`.
- Push tag and create GitHub release with `gh release create`.

Operational pattern
1. Create focused branch per topic.
2. Make small, scoped changes and run `task test`.
3. Push branch, open PR, and merge after CI passes.
44 changes: 44 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: ci

on:
push:
branches:
- master
pull_request:

permissions:
contents: read

jobs:
test-and-lint:
name: lint and test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- uses: actions/setup-go@v6
with:
go-version-file: go.mod
cache-dependency-path: go.sum

- name: golangci-lint
uses: golangci/golangci-lint-action@v9
with:
version: latest
args: --enable gosec

- name: Test module
run: go test -shuffle on ./...

govulncheck:
name: govulncheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Run govulncheck
uses: golang/govulncheck-action@v1
with:
go-version-file: go.mod
go-package: ./...
repo-checkout: false
35 changes: 35 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: CodeQL

on:
push:
branches:
- master
pull_request:
branches:
- master
schedule:
- cron: "0 20 * * 0"

permissions:
actions: read
contents: read
security-events: write

jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6

- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: go

- name: Autobuild
uses: github/codeql-action/autobuild@v3

- name: Perform CodeQL analysis
uses: github/codeql-action/analyze@v3
50 changes: 0 additions & 50 deletions .github/workflows/lint.yml

This file was deleted.

50 changes: 48 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,34 @@
# [struct2pflag]

<!-- badges.cmd |-->
[![lint status](https://github.com/goark/struct2pflag/workflows/lint/badge.svg)](https://github.com/goark/struct2pflag/actions)
[![ci status](https://github.com/goark/struct2pflag/actions/workflows/ci.yml/badge.svg)](https://github.com/goark/struct2pflag/actions/workflows/ci.yml)
[![codeql status](https://github.com/goark/struct2pflag/actions/workflows/codeql.yml/badge.svg)](https://github.com/goark/struct2pflag/actions/workflows/codeql.yml)
[![GitHub license](http://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/goark/struct2pflag/master/LICENSE)
[![GitHub release](http://img.shields.io/github/release/goark/struct2pflag.svg)](https://github.com/goark/mt/releases/latest)
[![GitHub release](http://img.shields.io/github/release/goark/struct2pflag.svg)](https://github.com/goark/struct2pflag/releases/latest)
[![Go reference](https://pkg.go.dev/badge/github.com/goark/struct2pflag.svg)](https://pkg.go.dev/github.com/goark/struct2pflag)
<!-- -->

Design goals

- Provide a minimal, dependency-light helper to bind struct fields to pflag flags.
- Maintain backwards compatibility for public API and use clear sentinel errors.

Development

- Local validation: `task test` (preferred). Include `gofmt`, `go vet`, and `go test` in CI.
- Keep changes small and topic-focused; open one PR per change.

CI Workflows

- Keep two primary workflows in `.github/workflows/`: `ci.yml` and `codeql.yml`.
- Update README badges if workflow names change.

Usage

[`struct2pflag`][struct2pflag] automatically registers struct fields as flags for your Go command-line programs.

Supported types in `Bind` include `bool`, `int`, `uint`, `float32`, `float64`, `string`, and `time.Duration`.

(forked from [hymkor/struct2flag](https://github.com/hymkor/struct2flag))

Minimal example
Expand Down Expand Up @@ -71,6 +91,32 @@ N=1
S="foo"
```

Environment variable binding
----------------------------

`struct2pflag.BindEnv` can apply environment variables to your config struct before CLI parsing.

```go
type Env struct {
TopN int `env:"TOP_N" pflag:"top-n,n,number of top tags"`
Wait time.Duration `env:"WAIT" pflag:"wait,w,wait duration"`
}

func main() {
env := Env{TopN: 10, Wait: time.Second}

_ = struct2pflag.BindEnv(&env)
struct2pflag.BindDefault(&env)
pflag.Parse()
}
```

Recommended precedence is:

1. CLI flags
2. Environment variables
3. Struct default values

Reading default values from JSON and overriding them with command-line flags
----------------------------------------------------------------------------

Expand Down
8 changes: 4 additions & 4 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ tasks:
cmds:
- task: prepare
- task: test
# - task: nancy
- task: govulncheck
- task: graph

test:
Expand All @@ -18,10 +18,10 @@ tasks:
- ./go.mod
- '**/*.go'

nancy:
desc: Check vulnerability of external packages with Nancy.
govulncheck:
desc: Check reachable vulnerabilities with latest govulncheck.
cmds:
- depm list -j | nancy sleuth -n
- go run golang.org/x/vuln/cmd/govulncheck@latest ./...
sources:
- ./go.mod
- '**/*.go'
Expand Down
Binary file modified dependency.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
139 changes: 139 additions & 0 deletions docs/struct2pflag-env-bind-spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# struct2pflag: Additional Env Binding Specification

## Background

- We want to support environment variable-based configuration such as `TOP_N` in `tagtools`.
- Currently, `github.com/goark/struct2pflag` binds struct fields to CLI flags.
- To enable reuse across multiple tools, environment variable binding should also be implemented in `struct2pflag`.

## Goals

- Add support in `struct2pflag` for binding environment variables to struct fields.
- Standardize conversion rules and unify the behavior among flags, environment variables, and struct defaults.
- Include `time.Duration` conversion in the initial implementation.

## Scope

- Add functionality to `struct2pflag` itself:
- API to apply environment variables to struct fields
- Conversion error handling
- Implementation of supported types
- Unit tests
- Integrating this in `tagtools` will be handled in a separate PR/session.

## Out of Scope

- Full application on the `tagtools` side (not implemented in this session)
- Breaking compatibility of the existing `Bind` API
- Expanding to complex nested struct rules or custom tag specifications beyond the minimum needed

## Precedence Rules (Consumer Guide)

Recommended precedence:

1. CLI flags
2. Environment variables
3. Struct default values

Note: env binding in `struct2pflag` is responsible only for applying values to the struct. Final precedence is controlled by call order.
Example: `default -> env bind -> flag parse`

## Proposed API

### Minimal API

- `func BindEnv(v any, opts ...EnvOpt) error`

`v` must be a pointer to a struct.

### Option Proposals

- `WithEnvPrefix(prefix string)`
- Example: prepend `APP_`
- `WithEnvTag(tag string)`
- Default tag key: `env`
- `WithStrict(bool)`
- When `strict=true`, unsupported types and invalid values become errors

## Tag Specification Proposal

- Explicit declaration such as `env:"TOP_N"`
- When no tag is specified, either:
- A: Ignore the field (safe)
- B: Auto-generate from the field name (`TopN` -> `TOP_N`)

Start with A (explicit tags) as the recommended policy.

## Supported Types (Initial)

- `string`
- `bool`
- `int`, `int8`, `int16`, `int32`, `int64`
- `uint`, `uint8`, `uint16`, `uint32`, `uint64`
- `float32`, `float64`
- `time.Duration`

### Duration Conversion

- Use `time.ParseDuration`
- Examples: `100ms`, `5s`, `3m`, `1h30m`

## Error Handling

- Environment variable is unset: skip (no error)
- Environment variable is set but conversion fails: return error
- Error messages should include at least:
- Environment variable name
- Field name
- Expected type
- Original raw value

## Implementation Strategy (Recommended)

- Iterate struct fields with reflection
- Process only settable/exported fields
- Organize per-type conversion handlers in an internal table-like structure
- Keep room for future converter-registration API extensions

## Test Cases (struct2pflag side)

### Success Cases

- Correctly set values for each supported type
- Preserve defaults when env variables are unset
- Correct duration conversion behavior

### Failure Cases

- Error when assigning non-numeric string to int
- Error for invalid bool value
- Error for invalid duration value
- Error when input is not a struct pointer

### Boundary Cases

- Behavior for empty-string env values (documented per type)
- Behavior differences with and without strict option

## tagtools Integration Image (Next Session)

- Target: `TopN` in `toptags`
- Example:
- `TopN int `env:"TOP_N" pflag:"top-n,n,number of top tags"``
- `default -> BindEnv(&cfg) -> struct2pflag.Bind(fs, &cfg) -> fs.Parse(args)`
- Expected behavior:
- `TOP_N` overrides defaults when set
- `--top-n` takes precedence over `TOP_N` when provided

## Compatibility Considerations

- No impact on existing `Bind` call sites
- New API is additive only
- Existing users see no behavior change unless env binding is explicitly used

## Definition of Done

- Implement env binding API and duration conversion in `struct2pflag`
- Unit tests pass
- Add usage notes to README/package docs
- Verify PoC integration (`TOP_N`) in `tagtools` with a separate PR
Loading
Loading