diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..6ef6cdd --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,70 @@ +# Copilot Instructions for csvdata + +## Project Purpose + +- `csvdata` provides a small, composable interface to read tabular data as CSV-like rows. +- The package supports plain CSV and adapters for spreadsheet formats: + - `exceldata` for `.xlsx` + - `calcdata` for `.ods` + +## Design Principles + +- Keep the public API small and predictable. +- Keep adapters thin: format-specific logic stays in adapter packages. +- Favor explicit error returns over hidden fallback behavior. +- Preserve backward compatibility unless a breaking change is explicitly planned. + +## Error Handling Policy + +- Return wrapped errors so callers can still use `errors.Is` and `errors.As`. +- Keep sentinel errors stable when exposed from public API. +- Handle nil-sensitive paths defensively (`io.Reader`, closer, and adapter resources). +- Do not panic for normal runtime failures; return errors with context. + +## Testing and Validation + +- Primary local check: + +```text +task test +``` + +- Equivalent direct commands: + +```text +go mod verify +go test -shuffle on ./... +golangci-lint-v2 run --enable gosec --timeout 3m0s ./... +``` + +- Prefer deterministic tests. Avoid external network dependencies. + +## Lint and Security Notes + +- In deferred cleanup, merge close errors with existing return errors using `errs.Join`. + - Example policy: `err = errs.Join(err, errs.Wrap(closeErr, errs.WithContext(...)))` +- For file path handling, prefer sanitizing with `filepath.Clean` before opening files. + - Avoid using `#nosec G304` as the first option when path normalization can mitigate the warning. +- For narrowing integer conversions (`G115`), prefer explicit range checks before conversion. + - If linter still reports a false positive after range checks, allow minimal `#nosec G115` with a reason comment. +- Prefer clear conversions over bit-mask tricks for readability. + - After range checks, use direct conversion (e.g. `byte(v)`), not masking (e.g. `byte(v & 0xff)`). + +## Release Process + +- Create releases from `main`. +- Use semantic version tags in `vMAJOR.MINOR.PATCH` format. +- Create annotated tag and push: + +```text +git tag -a vX.Y.Z -m "Release vX.Y.Z" +git push origin vX.Y.Z +``` + +- Create GitHub release with generated notes: + +```text +gh release create vX.Y.Z --generate-notes +``` + +- Verify module version and release assets after publishing. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..a4dcb48 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,44 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + +permissions: + contents: read + +jobs: + test-and-lint: + name: Test and Lint + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + - name: Verify modules + run: go mod verify + - name: Run tests + run: go test -shuffle on ./... + - name: Run golangci-lint + uses: golangci/golangci-lint-action@v8 + with: + version: latest + args: --enable gosec --timeout 3m0s ./... + + vulncheck: + name: Vulnerability Check + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + - name: Run govulncheck + run: go run golang.org/x/vuln/cmd/govulncheck@latest ./... diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index 3ddedbe..0000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,58 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -name: "CodeQL" - -on: - push: - branches: [main] - pull_request: - # The branches below must be a subset of the branches above - branches: [main] - schedule: - - cron: '0 20 * * 0' - -jobs: - CodeQL-Build: - # CodeQL runs on ubuntu-latest, windows-latest, and macos-latest - runs-on: ubuntu-latest - - permissions: - # required for all workflows - security-events: write - - # only required for workflows in private repositories - actions: read - contents: read - - steps: - - name: Checkout repository - uses: actions/checkout@v3 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - # Override language selection by uncommenting this and choosing your languages - with: - languages: go - - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below). - - name: Autobuild - uses: github/codeql-action/autobuild@v2 - - # â„šī¸ Command-line programs to run using the OS shell. - # 📚 https://git.io/JvXDl - - # âœī¸ If the Autobuild fails above, remove it and uncomment the following - # three lines and modify them (or add more) to build your code if your - # project uses a compiled language - - #- run: | - # make bootstrap - # make release - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..369b1d7 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,32 @@ +name: CodeQL + +on: + push: + branches: + - main + pull_request: + branches: + - main + schedule: + - cron: '0 20 * * 0' + +permissions: + actions: read + contents: read + security-events: write + +jobs: + analyze: + name: Analyze (Go) + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - 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 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index 31d1f54..0000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: lint -on: - push: - branches: - - main - pull_request: - -permissions: - contents: read - # Optional: allow read access to pull request. Use with `only-new-issues` option. - # pull-requests: read -jobs: - golangci: - name: lint - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-go@v3 - with: - go-version-file: 'go.mod' - - name: golangci-lint - uses: golangci/golangci-lint-action@v3 - with: - # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version - version: latest - - # Optional: working directory, useful for monorepos - # working-directory: somedir - - # Optional: golangci-lint command line arguments. - args: --enable gosec - - # Optional: show only new issues if it's a pull request. The default value is `false`. - # only-new-issues: true - - # Optional: if set to true then the all caching functionality will be complete disabled, - # takes precedence over all other caching options. - # skip-cache: true - - # Optional: if set to true then the action don't cache or restore ~/go/pkg. - # skip-pkg-cache: true - - # Optional: if set to true then the action don't cache or restore ~/.cache/go-build. - # skip-build-cache: true - - name: testing - run: go test -shuffle on ./... diff --git a/.github/workflows/vulns.yml b/.github/workflows/vulns.yml deleted file mode 100644 index b8467bd..0000000 --- a/.github/workflows/vulns.yml +++ /dev/null @@ -1,19 +0,0 @@ -name: vulns -on: - push: - branches: - - main - pull_request: -jobs: - vulns: - name: Vulnerability scanner - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-go@v3 - with: - go-version-file: 'go.mod' - - name: WriteGoList - run: go list -json -m all > go.list - - name: Nancy - uses: sonatype-nexus-community/nancy-github-action@main diff --git a/README.md b/README.md index 1dc8e1d..2cce64c 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,54 @@ # [csvdata] -- Reading CSV Data -[![check vulns](https://github.com/goark/csvdata/workflows/vulns/badge.svg)](https://github.com/goark/csvdata/actions) -[![lint status](https://github.com/goark/csvdata/workflows/lint/badge.svg)](https://github.com/goark/csvdata/actions) +[![Go Reference](https://pkg.go.dev/badge/github.com/goark/csvdata.svg)](https://pkg.go.dev/github.com/goark/csvdata) +[![CI](https://github.com/goark/csvdata/actions/workflows/ci.yml/badge.svg)](https://github.com/goark/csvdata/actions/workflows/ci.yml) +[![CodeQL](https://github.com/goark/csvdata/actions/workflows/codeql.yml/badge.svg)](https://github.com/goark/csvdata/actions/workflows/codeql.yml) [![GitHub license](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://raw.githubusercontent.com/goark/csvdata/master/LICENSE) [![GitHub release](https://img.shields.io/github/release/goark/csvdata.svg)](https://github.com/goark/csvdata/releases/latest) -This package is required Go 1.16 or later. +This package is required Go 1.25 or later. -**Migrated repository to [github.com/goark/csvdata][csvdata]** +## Design Goals -## Import +- Provide a simple and stable interface for reading CSV-like rows. +- Keep parsing concerns separated by format adapters (`exceldata`, `calcdata`). +- Return explicit errors with context, and avoid hidden side effects. + +## Development + +### Local Validation + +Primary command: + +```text +task test +``` + +Equivalent direct commands: + +```text +go mod verify +go test -shuffle on ./... +golangci-lint-v2 run --enable gosec --timeout 3m0s ./... +``` + +### Optional Checks + +```text +task govulncheck +task graph +``` + +## CI Workflows + +- `CI`: lint, tests, and vulnerability checks. +- `CodeQL`: security analysis. + +All workflows are available in GitHub Actions for this repository. + +## Usage + +### Import ```go import "github.com/goark/csvdata" diff --git a/Taskfile.yml b/Taskfile.yml index 8304557..680f2db 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -3,9 +3,9 @@ version: '3' tasks: default: cmds: - - task: clean + - task: prepare - task: test - - task: nancy + - task: govulncheck - task: graph test: @@ -13,26 +13,28 @@ tasks: cmds: - go mod verify - go test -shuffle on ./... - - docker run --rm -v $(pwd):/app -w /app golangci/golangci-lint:v1.51.1 golangci-lint run --enable gosec --timeout 3m0s ./... + - golangci-lint-v2 run --enable gosec --timeout 3m0s ./... sources: - ./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' + prepare: + - go mod tidy -v -go=1.25.10 + clean: desc: Initialize module and build cache, and remake go.sum file. cmds: - rm -f ./go.sum - go clean -cache - go clean -modcache - - go mod tidy -v -go=1.20 graph: desc: Make grapth of dependency modules. diff --git a/calcdata/calcdata.go b/calcdata/calcdata.go index 15b4a7e..12c8a19 100644 --- a/calcdata/calcdata.go +++ b/calcdata/calcdata.go @@ -62,12 +62,14 @@ func (r *Reader) Read() ([]string, error) { return cols, nil } -func openFile(path string) (*ods.Doc, error) { +func openFile(path string) (_ *ods.Doc, err error) { f, err := ods.Open(path) if err != nil { return nil, errs.Wrap(err, errs.WithContext("path", path)) } - defer f.Close() + defer func() { + err = errs.Join(err, errs.Wrap(f.Close(), errs.WithContext("path", path))) + }() var doc ods.Doc if err := f.ParseContent(&doc); err != nil { return nil, errs.Wrap(err, errs.WithContext("path", path)) diff --git a/calcdata/example_test.go b/calcdata/example_test.go index a23d629..3f15749 100644 --- a/calcdata/example_test.go +++ b/calcdata/example_test.go @@ -19,7 +19,11 @@ func ExampleNew() { return } rc := csvdata.NewRows(r, true) - defer rc.Close() //dummy + defer func() { + if err := rc.Close(); err != nil { + panic(err) + } + }() if err := rc.Next(); err != nil { fmt.Println(err) diff --git a/csvdata.go b/csvdata.go index 0b4737e..df1968f 100644 --- a/csvdata.go +++ b/csvdata.go @@ -4,6 +4,7 @@ import ( "encoding/csv" "io" "os" + "path/filepath" "github.com/goark/errs" ) @@ -19,9 +20,13 @@ var _ RowsReader = (*Reader)(nil) //Reader is compatible with RowsReader interfa // OpenFile returns CSV file Reader. func OpenFile(path string) (*os.File, error) { - file, err := os.Open(path) + cleanPath := path + if len(path) > 0 { + cleanPath = filepath.Clean(path) + } + file, err := os.Open(cleanPath) if err != nil { - return nil, errs.Wrap(err, errs.WithContext("path", path)) + return nil, errs.Wrap(err, errs.WithContext("path", cleanPath)) } return file, nil } diff --git a/csvdata_test.go b/csvdata_test.go index d86399f..88ed7e7 100644 --- a/csvdata_test.go +++ b/csvdata_test.go @@ -28,7 +28,11 @@ const ( func TestWithNil(t *testing.T) { r := csvdata.NewRows((*csvdata.Reader)(nil).WithComma(',').WithTrimSpace(true).WithLazyQuotes(true).WithTrimLeadingSpace(true).WithFieldsPerRecord(1), true) - defer r.Close() //dummy + t.Cleanup(func() { + if err := r.Close(); err != nil { + t.Errorf("Close() is \"%+v\", want .", err) + } + }) if err := r.Next(); !errors.Is(err, csvdata.ErrNullPointer) { t.Errorf("Next() is \"%+v\", want \"%+v\".", err, csvdata.ErrNullPointer) } @@ -63,10 +67,33 @@ func TestWithNil(t *testing.T) { } } +func TestNewRowsWithNilReader(t *testing.T) { + r := csvdata.NewRows(nil, true) + t.Cleanup(func() { + if err := r.Close(); err != nil { + t.Errorf("Close() is \"%+v\", want .", err) + } + }) + + if _, err := r.Header(); !errors.Is(err, csvdata.ErrNullPointer) { + t.Errorf("Header() is \"%+v\", want \"%+v\".", err, csvdata.ErrNullPointer) + } + if err := r.Next(); !errors.Is(err, csvdata.ErrNullPointer) { + t.Errorf("Next() is \"%+v\", want \"%+v\".", err, csvdata.ErrNullPointer) + } + if _, err := r.GetString(0); !errors.Is(err, csvdata.ErrOutOfIndex) { + t.Errorf("GetString() is \"%+v\", want \"%+v\".", err, csvdata.ErrOutOfIndex) + } +} + func TestErrReader(t *testing.T) { errtest := errors.New("test") r := csvdata.NewRows(csvdata.New(iotest.ErrReader(errtest)).WithComma(',').WithTrimSpace(true).WithLazyQuotes(true).WithTrimLeadingSpace(true).WithFieldsPerRecord(1), true) - defer r.Close() //dummy + t.Cleanup(func() { + if err := r.Close(); err != nil { + t.Errorf("Close() is \"%+v\", want .", err) + } + }) if err := r.Next(); !errors.Is(err, errtest) { t.Errorf("Next() is \"%+v\", want \"%+v\".", err, errtest) } @@ -77,7 +104,11 @@ func TestErrReader(t *testing.T) { func TestBlankReader(t *testing.T) { r := csvdata.NewRows(csvdata.New(strings.NewReader("")).WithComma(',').WithFieldsPerRecord(1), true) - defer r.Close() //dummy + t.Cleanup(func() { + if err := r.Close(); err != nil { + t.Errorf("Close() is \"%+v\", want .", err) + } + }) if err := r.Next(); !errors.Is(err, io.EOF) { t.Errorf("Next() is \"%+v\", want \"%+v\".", err, io.EOF) } @@ -220,7 +251,7 @@ func TestNormal(t *testing.T) { if !errors.Is(err, tc.err) { t.Errorf("ColumnByte() is \"%+v\", want \"%+v\".", err, tc.err) } - if err == nil && orderB != byte(tc.order) { + if err == nil && int(orderB) != int(tc.order) { t.Errorf("ColumnByte() is \"%+v\", want \"%+v\".", orderB, tc.order) } orderByte, err := rc.ColumnNullByte("order", 10) diff --git a/dependency.png b/dependency.png index 1ea9b3b..46a2ed4 100644 Binary files a/dependency.png and b/dependency.png differ diff --git a/example_test.go b/example_test.go index 25283ed..f90d78f 100644 --- a/example_test.go +++ b/example_test.go @@ -13,7 +13,11 @@ func ExampleNew() { return } rc := csvdata.NewRows(csvdata.New(file), true) - defer rc.Close() + defer func() { + if err := rc.Close(); err != nil { + panic(err) + } + }() if err := rc.Next(); err != nil { fmt.Println(err) diff --git a/exceldata/example_test.go b/exceldata/example_test.go index e6ae8c8..876330a 100644 --- a/exceldata/example_test.go +++ b/exceldata/example_test.go @@ -19,7 +19,11 @@ func ExampleNew() { return } rc := csvdata.NewRows(r, true) - defer rc.Close() //dummy + defer func() { + if err := rc.Close(); err != nil { + panic(err) + } + }() if err := rc.Next(); err != nil { fmt.Println(err) diff --git a/go.mod b/go.mod index 0dc1d1c..fe8b0f8 100644 --- a/go.mod +++ b/go.mod @@ -1,20 +1,22 @@ module github.com/goark/csvdata -go 1.20 +go 1.25.10 + +toolchain go1.26.3 require ( - github.com/goark/errs v1.2.2 + github.com/goark/errs v1.3.4 github.com/knieriem/odf v0.1.0 - github.com/xuri/excelize/v2 v2.7.0 + github.com/xuri/excelize/v2 v2.10.1 ) require ( - github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect - github.com/richardlehane/mscfb v1.0.4 // indirect - github.com/richardlehane/msoleps v1.0.3 // indirect - github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 // indirect - github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 // indirect - golang.org/x/crypto v0.5.0 // indirect - golang.org/x/net v0.7.0 // indirect - golang.org/x/text v0.7.0 // indirect + github.com/richardlehane/mscfb v1.0.6 // indirect + github.com/richardlehane/msoleps v1.0.6 // indirect + github.com/tiendc/go-deepcopy v1.7.2 // indirect + github.com/xuri/efp v0.0.1 // indirect + github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 // indirect + golang.org/x/crypto v0.52.0 // indirect + golang.org/x/net v0.55.0 // indirect + golang.org/x/text v0.37.0 // indirect ) diff --git a/go.sum b/go.sum index df15f1c..1b37e61 100644 --- a/go.sum +++ b/go.sum @@ -1,66 +1,32 @@ -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/goark/errs v1.2.2 h1:UrMZZJL0WaOzaO+ErSV+nz/k/+bmW2wUiFe5V7pUeEo= -github.com/goark/errs v1.2.2/go.mod h1:ZsQucxaDFVfSB8I99j4bxkDRfNOrlKINwg72QMuRWKw= +github.com/goark/errs v1.3.4 h1:/+/xwF3UwXGxGGLurzBTaMMoryTBeaPfheJ1aW9cglA= +github.com/goark/errs v1.3.4/go.mod h1:4xM7rorwYQlqh9kUhfKpC5P7VAJW2KfvuQpYnTaU0ek= github.com/knieriem/odf v0.1.0 h1:9nas0pxrk9EfhD7PouL9RawIaPfETwCnxKCqMjwsjHA= github.com/knieriem/odf v0.1.0/go.mod h1:jRlg9+5Aya1ajQBX2ltU//o50Kn+cApfrsnkLCBjzJA= -github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= -github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM= -github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk= -github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= -github.com/richardlehane/msoleps v1.0.3 h1:aznSZzrwYRl3rLKRT3gUk9am7T/mLNSnJINvN0AQoVM= -github.com/richardlehane/msoleps v1.0.3/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 h1:6932x8ltq1w4utjmfMPVj09jdMlkY0aiA6+Skbtl3/c= -github.com/xuri/efp v0.0.0-20220603152613-6918739fd470/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= -github.com/xuri/excelize/v2 v2.7.0 h1:Hri/czwyRCW6f6zrCDWXcXKshlq4xAZNpNOpdfnFhEw= -github.com/xuri/excelize/v2 v2.7.0/go.mod h1:ebKlRoS+rGyLMyUx3ErBECXs/HNYqyj+PbkkKRK5vSI= -github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22 h1:OAmKAfT06//esDdpi/DZ8Qsdt4+M5+ltca05dA5bG2M= -github.com/xuri/nfp v0.0.0-20220409054826-5e722a1d9e22/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.5.0 h1:U/0M97KRkSFvyD/3FSmdP5W5swImpNgle/EHFhOsQPE= -golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= -golang.org/x/image v0.0.0-20220902085622-e7cb96979f69 h1:Lj6HJGCSn5AjxRAH2+r35Mir4icalbqku+CLUtjnvXY= -golang.org/x/image v0.0.0-20220902085622-e7cb96979f69/go.mod h1:doUCurBvlfPMKfmIpRIywoHmhN3VyhnoFDbvIEWF4hY= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +github.com/richardlehane/mscfb v1.0.6 h1:eN3bvvZCp00bs7Zf52bxNwAx5lJDBK1tCuH19qq5aC8= +github.com/richardlehane/mscfb v1.0.6/go.mod h1:pe0+IUIc0AHh0+teNzBlJCtSyZdFOGgV4ZK9bsoV+Jo= +github.com/richardlehane/msoleps v1.0.6 h1:9BvkpjvD+iUBalUY4esMwv6uBkfOip/Lzvd93jvR9gg= +github.com/richardlehane/msoleps v1.0.6/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tiendc/go-deepcopy v1.7.2 h1:Ut2yYR7W9tWjTQitganoIue4UGxZwCcJy3orjrrIj44= +github.com/tiendc/go-deepcopy v1.7.2/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ= +github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8= +github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= +github.com/xuri/excelize/v2 v2.10.1 h1:V62UlqopMqha3kOpnlHy2CcRVw1V8E63jFoWUmMzxN0= +github.com/xuri/excelize/v2 v2.10.1/go.mod h1:iG5tARpgaEeIhTqt3/fgXCGoBRt4hNXgCp3tfXKoOIc= +github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 h1:+C0TIdyyYmzadGaL/HBLbf3WdLgC29pgyhTjAT/0nuE= +github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ= +golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988= +golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc= +golang.org/x/image v0.25.0 h1:Y6uW6rH1y5y/LK1J8BPWZtr6yZ7hrsy6hFrXjgsc2fQ= +golang.org/x/image v0.25.0/go.mod h1:tCAmOEGthTtkalusGp1g3xa2gke8J6c2N565dTyl9Rs= +golang.org/x/net v0.55.0 h1:bcvxaJn3e1U6InsFWt1JUq1aSjnRxLzT2rtD2KfkDF8= +golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/rows.go b/rows.go index 518c6ee..d91d33a 100644 --- a/rows.go +++ b/rows.go @@ -27,17 +27,44 @@ type Rows struct { rowdata []string } +type nullRowsReader struct{} + +func (nullRowsReader) Read() ([]string, error) { + return nil, errs.Wrap(ErrNullPointer) +} + +func (nullRowsReader) Close() error { + return nil +} + +func (nullRowsReader) TrimSpace() bool { + return false +} + +func (nullRowsReader) LazyQuotes() bool { + return false +} + func NewRows(rr RowsReader, headerFlag bool) *Rows { + if rr == nil { + rr = nullRowsReader{} + } return &Rows{reader: rr, headerFlag: headerFlag, headerMap: map[string]int{}} } // TrimSpace returns TrimSpace option value. func (r *Rows) TrimSpace() bool { + if r == nil || r.reader == nil { + return false + } return r.reader.TrimSpace() } // LazyQuotes returns LazyQuotes option value. func (r *Rows) LazyQuotes() bool { + if r == nil || r.reader == nil { + return false + } return r.reader.LazyQuotes() } @@ -46,6 +73,9 @@ func (r *Rows) Header() ([]string, error) { if r == nil { return nil, errs.Wrap(ErrNullPointer) } + if r.reader == nil { + return nil, errs.Wrap(ErrNullPointer) + } var err error if r.headerFlag { r.headerFlag = false @@ -64,6 +94,9 @@ func (r *Rows) Next() error { if r == nil { return errs.Wrap(ErrNullPointer) } + if r.reader == nil { + return errs.Wrap(ErrNullPointer) + } if r.headerFlag { if _, err := r.Header(); err != nil { return errs.Wrap(err) @@ -286,10 +319,13 @@ func (r *Rows) ColumnNullInt32(s string, base int) (sql.NullInt32, error) { if err != nil { return sql.NullInt32{}, errs.Wrap(err) } - if res.Valid && (res.Int64 < math.MinInt32 || res.Int64 > math.MaxInt32) { + if !res.Valid { + return sql.NullInt32{Valid: false}, nil + } + if res.Int64 < math.MinInt32 || res.Int64 > math.MaxInt32 { return sql.NullInt32{}, errs.Wrap(strconv.ErrRange) } - return sql.NullInt32{Int32: int32(res.Int64 & 0xffffffff), Valid: true}, nil + return sql.NullInt32{Int32: int32(res.Int64), Valid: true}, nil } // ColumnNullInt16 method returns sql.NullFloat64 data in current row. @@ -298,10 +334,13 @@ func (r *Rows) ColumnNullInt16(s string, base int) (sql.NullInt16, error) { if err != nil { return sql.NullInt16{Valid: false}, errs.Wrap(err) } - if res.Valid && (res.Int64 < math.MinInt16 || res.Int64 > math.MaxInt16) { + if !res.Valid { + return sql.NullInt16{Valid: false}, nil + } + if res.Int64 < math.MinInt16 || res.Int64 > math.MaxInt16 { return sql.NullInt16{Valid: false}, errs.Wrap(strconv.ErrRange) } - return sql.NullInt16{Int16: int16(res.Int64 & 0xffff), Valid: true}, nil + return sql.NullInt16{Int16: int16(res.Int64), Valid: true}, nil } // ColumnNullByte method returns sql.NullByte data in current row. @@ -310,10 +349,13 @@ func (r *Rows) ColumnNullByte(s string, base int) (sql.NullByte, error) { if err != nil { return sql.NullByte{Valid: false}, errs.Wrap(err) } - if res.Valid && (res.Int64 < 0 || res.Int64 > math.MaxUint8) { + if !res.Valid { + return sql.NullByte{Valid: false}, nil + } + if res.Int64 < 0 || res.Int64 > math.MaxUint8 { return sql.NullByte{Valid: false}, errs.Wrap(strconv.ErrRange) } - return sql.NullByte{Byte: byte(res.Int64 & 0xff), Valid: true}, nil + return sql.NullByte{Byte: byte(res.Int64), Valid: true}, nil } // ColumnInt64 method returns type int64 data in current row. @@ -362,7 +404,7 @@ func (r *Rows) ColumnInt8(s string, base int) (int8, error) { if res.Int16 < math.MinInt8 || res.Int16 > math.MaxInt8 { return 0, errs.Wrap(strconv.ErrRange) } - return int8(res.Int16 & 0xff), nil + return int8(res.Int16), nil } return 0, errs.Wrap(ErrNullValue) } @@ -428,6 +470,9 @@ func (r *Rows) ColumnTime(s, layout string) (time.Time, error) { // Close method is closing RowsReader instance. func (r *Rows) Close() error { + if r == nil || r.reader == nil { + return nil + } return r.reader.Close() }