From 1fec4678c35ea8a2b4f920e5d0b3fbac719f6909 Mon Sep 17 00:00:00 2001 From: Benji Vesterby Date: Mon, 11 Mar 2024 12:17:06 -0400 Subject: [PATCH] feat: add cursor impl --- .cm/approve-safe-changes.cm | 4 +- .cm/assign-the-relevant-reviewers-to-prs.cm | 4 +- .cm/label-prs-by-complexity.cm | 2 +- .cm/mark-prs-with-deleted-files.cm | 2 +- .cm/mark-prs-without-tests.cm | 4 +- .cm/more-approvals-for-complex-changes.cm | 2 +- .github/CODEOWNERS | 2 +- .github/dependabot.yml | 2 +- .github/workflows/automerge.yml | 2 +- .github/workflows/benchmark.yml | 36 +--- .github/workflows/build.yml | 48 +---- .github/workflows/gitstream.yml | 2 +- .github/workflows/release.yml | 43 ++--- .gitignore | 16 +- .golangci.yml | 11 +- .goreleaser.yaml | 18 ++ .pre-commit-config.yaml | 59 +++--- .secrets.baseline | 2 +- LICENSE_HEADER | 13 ++ Makefile | 110 +++++++++++ lists/cursor/cursor.go | 153 +++++++++++++++ lists/cursor/cursor_test.go | 196 ++++++++++++++++++++ scripts/fuzz_go.py | 40 ++++ scripts/license.py | 30 +++ scripts/tag.py | 40 ++++ trees/errs.go | 14 ++ trees/nary/nary.go | 14 ++ trees/nary/nary_test.go | 14 ++ 28 files changed, 738 insertions(+), 145 deletions(-) create mode 100644 .goreleaser.yaml create mode 100644 LICENSE_HEADER create mode 100644 Makefile create mode 100644 lists/cursor/cursor.go create mode 100644 lists/cursor/cursor_test.go create mode 100755 scripts/fuzz_go.py create mode 100755 scripts/license.py create mode 100755 scripts/tag.py diff --git a/.cm/approve-safe-changes.cm b/.cm/approve-safe-changes.cm index 52bab11..c7cd8b5 100644 --- a/.cm/approve-safe-changes.cm +++ b/.cm/approve-safe-changes.cm @@ -7,14 +7,14 @@ automations: safe_changes: if: - {{ is.formatting or is.docs or is.tests or is.asset }} - run: + run: - action: add-label@v1 args: label: 'safe-changes' - action: approve@v1 # To simplify the automation, this calculation is placed under a unique YAML key. -# The result is is assigned to `is.formatting`, `is.docs` and `is.tests` which is +# The result is is assigned to `is.formatting`, `is.docs` and `is.tests` which is # used in the automation above. You can add as many keys as you like. is: formatting: {{ source.diff.files | isFormattingChange }} diff --git a/.cm/assign-the-relevant-reviewers-to-prs.cm b/.cm/assign-the-relevant-reviewers-to-prs.cm index 161d644..b74a231 100644 --- a/.cm/assign-the-relevant-reviewers-to-prs.cm +++ b/.cm/assign-the-relevant-reviewers-to-prs.cm @@ -5,7 +5,7 @@ manifest: automations: the_right_reviewer: - if: + if: - true run: - action: add-comment@v1 @@ -14,4 +14,4 @@ automations: {{ repo | explainRankByGitBlame(gt=25) }} - action: add-reviewers@v1 args: - reviewers: {{ repo | rankByGitBlame(gt=25) | random }} \ No newline at end of file + reviewers: {{ repo | rankByGitBlame(gt=25) | random }} diff --git a/.cm/label-prs-by-complexity.cm b/.cm/label-prs-by-complexity.cm index 5776be0..8da8613 100644 --- a/.cm/label-prs-by-complexity.cm +++ b/.cm/label-prs-by-complexity.cm @@ -17,4 +17,4 @@ automations: # The result is assigned to `calc.etr` which is used in the automation above. # You can add as many keys as you like. calc: - etr: {{ branch | estimatedReviewTime }} \ No newline at end of file + etr: {{ branch | estimatedReviewTime }} diff --git a/.cm/mark-prs-with-deleted-files.cm b/.cm/mark-prs-with-deleted-files.cm index ff7508f..dbbc2fe 100644 --- a/.cm/mark-prs-with-deleted-files.cm +++ b/.cm/mark-prs-with-deleted-files.cm @@ -7,7 +7,7 @@ automations: deleted: if: - {{ has.deleted_files }} - run: + run: - action: add-label@v1 args: label: 'deleted-files' diff --git a/.cm/mark-prs-without-tests.cm b/.cm/mark-prs-without-tests.cm index 0908197..556ab9c 100644 --- a/.cm/mark-prs-without-tests.cm +++ b/.cm/mark-prs-without-tests.cm @@ -7,8 +7,8 @@ automations: no_tests: if: - {{ files | match(regex=r/(test|spec)/) | nope }} - run: + run: - action: add-label@v1 args: label: 'missing-tests' - color: '#E94637' \ No newline at end of file + color: '#E94637' diff --git a/.cm/more-approvals-for-complex-changes.cm b/.cm/more-approvals-for-complex-changes.cm index dd83039..675f50d 100644 --- a/.cm/more-approvals-for-complex-changes.cm +++ b/.cm/more-approvals-for-complex-changes.cm @@ -12,4 +12,4 @@ automations: run: - action: set-required-approvals@v1 args: - approvals: 2 \ No newline at end of file + approvals: 2 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 8da9d2a..4b90f2d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @benjivesterby \ No newline at end of file +* @benjivesterby diff --git a/.github/dependabot.yml b/.github/dependabot.yml index c475bcc..b98422b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -11,4 +11,4 @@ updates: schedule: interval: "daily" labels: - - "automerge" \ No newline at end of file + - "automerge" diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml index 0fa9c36..e0d6e08 100644 --- a/.github/workflows/automerge.yml +++ b/.github/workflows/automerge.yml @@ -16,4 +16,4 @@ jobs: env: GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" MERGE_METHOD: squash - MERGE_LABELS: automerge \ No newline at end of file + MERGE_LABELS: automerge diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index a3583c7..39938b3 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -8,37 +8,11 @@ permissions: contents: write deployments: write +env: + GH_ACCESS_TOKEN: ${{ secrets.GH_PAT }} + jobs: benchmark: name: Execute Benchmarks - strategy: - matrix: - go-version: [1.19.x] - runs-on: ubuntu-latest - steps: - - uses: actions/setup-go@v5 - with: - go-version: ${{ matrix.go-version }} - stable: false - - name: Checkout code - uses: actions/checkout@v4 - - name: Run benchmark - run: go test -bench=. ./... | tee output.txt - - name: Download previous benchmark data - uses: actions/cache@v4 - with: - path: ./cache - key: ${{ runner.os }}-benchmark - - name: Store benchmark result - uses: benchmark-action/github-action-benchmark@v1 - with: - name: Benchmark Results - tool: 'go' - output-file-path: output.txt - benchmark-data-dir-path: dev/bench - github-token: ${{ secrets.GITHUB_TOKEN }} - auto-push: true - alert-threshold: '200%' - comment-on-alert: true - fail-on-alert: true - alert-comment-cc-users: '@benjivesterby' + uses: devnw/workflows/.github/workflows/make-bench.yml@main + secrets: inherit # pragma: allowlist secret diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4ad2311..a0151e5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,44 +1,12 @@ -name: Build & Test +name: Lint, Build & Test -# this is triggered on push to the repository on: [push, pull_request] +env: + GH_ACCESS_TOKEN: ${{ secrets.GH_PAT }} + jobs: - build: - name: "Build" - strategy: - matrix: - platform: [ubuntu-latest, macos-latest, windows-latest] - fail-fast: true - runs-on: ${{ matrix.platform }} - steps: - - name: Install Go - uses: actions/setup-go@v5 - with: - go-version: 1.21.x - - name: Checkout code - uses: actions/checkout@v4 - - name: Build - run: go build ./... - test: - name: "Unit Tests" - needs: [build] - strategy: - matrix: - platform: [ubuntu-latest, macos-latest, windows-latest] - fail-fast: true - runs-on: ${{ matrix.platform }} - steps: - - name: Checkout code - uses: actions/checkout@v4 - - name: Install Go - uses: actions/setup-go@v5 - with: - go-version: 1.21.x - - name: Test - run: go test -failfast ./... -race -coverprofile=coverage.txt -covermode=atomic - - name: Push Coverage to codecov.io - uses: codecov/codecov-action@v4.0.1 - with: - token: ${{ secrets.CODECOV_TOKEN }} - file: ./coverage.txt + lint-build-test: + name: Lint, Build & Test + uses: devnw/workflows/.github/workflows/make-build-nix.yml@main + secrets: inherit # pragma: allowlist secret diff --git a/.github/workflows/gitstream.yml b/.github/workflows/gitstream.yml index 7fa77fa..7f1afaa 100644 --- a/.github/workflows/gitstream.yml +++ b/.github/workflows/gitstream.yml @@ -15,7 +15,7 @@ on: description: the head sha required: true base_ref: - description: the base ref + description: the base ref required: true installation_id: description: the installation id diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 61460c3..4aaf985 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,37 +4,16 @@ on: push: tags: - "v*" +env: + GH_ACCESS_TOKEN: ${{ secrets.GH_PAT }} + +permissions: + contents: write + packages: write + issues: write jobs: - build: - name: "Build & Unit Tests" - strategy: - matrix: - platform: [ubuntu-latest] - fail-fast: true - runs-on: ${{ matrix.platform }} - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Fetch all tags - run: git fetch --force --tags - - name: Install Go - uses: actions/setup-go@v5 - with: - go-version: 1.21.x - - name: Docker Login - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.repository_owner }} - password: ${{ secrets.GH_PAT }} - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v5 - with: - distribution: goreleaser - version: latest - args: release --rm-dist - env: - GITHUB_TOKEN: ${{ secrets.GH_PAT }} + release: + name: Tagged Release + uses: devnw/workflows/.github/workflows/make-release.yml@main + secrets: inherit # pragma: allowlist secret diff --git a/.gitignore b/.gitignore index cf0992c..ea6f7dd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,20 @@ -eml-hdr-extract +*cweb.log +*.tmp +.venv +.act +# outputs +output.txt dist/ +# build folders +bin/ +ui/public/ +ui/build/ +ui/node_modules/ + # Removing Vendor vendor/ -testdata/remote .vscode/ @@ -21,6 +31,8 @@ testdata/remote # Output of the go coverage tool, specifically when used with LiteIDE *.out coverage.html +coverage +coverage.txt # ignore keyfiles *.crt diff --git a/.golangci.yml b/.golangci.yml index 4300279..0cc34ca 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -89,6 +89,10 @@ linters-settings: recommendations: - github.com/google/uuid reason: "satori's package is not maintained" + - github.com/gofrs/uuid: + recommendations: + - github.com/google/uuid + reason: "see recommendation from dev-infra team: https://confluence.gtforge.com/x/gQI6Aw" govet: # Enable all analyzers. @@ -138,10 +142,10 @@ linters-settings: tagliatelle: case: rules: - # Support string case: - # `camel`, `pascal`, `kebab`, `snake`, `goCamel`, + # Support string case: + # `camel`, `pascal`, `kebab`, `snake`, `goCamel`, # `goPascal`, `goKebab`, `goSnake`, `upper`, `lower` - json: snake + json: snake yaml: camel xml: camel @@ -178,7 +182,6 @@ linters: - goconst # finds repeated strings that could be replaced by a constant - gocritic # provides diagnostics that check for bugs, performance and style issues - gocyclo # computes and checks the cyclomatic complexity of functions - - godot # checks if comments end in a period - goimports # in addition to fixing imports, goimports also formats your code in the same style as gofmt - gomnd # detects magic numbers - gomoddirectives # manages the use of 'replace', 'retract', and 'excludes' directives in go.mod diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..b0a3bfc --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,18 @@ +builds: + - skip: true + +checksum: + name_template: 'checksums.txt' + +snapshot: + name_template: "{{ incpatch .Version }}-{{ .ShortCommit }}" + +changelog: + sort: asc + filters: + exclude: + - '^docs:' + - '^test:' + +release: + prerelease: auto diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 20bc0f2..20a5cd4 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,32 +1,47 @@ +exclude: '^data/|.env' +fail_fast: true repos: -- repo: https://github.com/Yelp/detect-secrets + - repo: local + hooks: + - id: license-header + name: License Header + entry: ./scripts/license.py + language: system + always_run: true + - repo: https://github.com/Yelp/detect-secrets rev: v1.4.0 hooks: - - id: detect-secrets + - id: detect-secrets name: Detect secrets language: python entry: detect-secrets-hook - args: ['--baseline', '.secrets.baseline'] -- repo: https://github.com/golangci/golangci-lint - rev: v1.54.2 - hooks: - - id: golangci-lint -- repo: https://github.com/Bahjat/pre-commit-golang - rev: v1.0.3 # pragma: allowlist secret + args: + - '--baseline' + - '.secrets.baseline' + - repo: https://github.com/sqlfluff/sqlfluff + rev: 3.0.0a6 hooks: - - id: go-unit-tests -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + - id: sqlfluff-fix + - repo: https://github.com/syntaqx/git-hooks + rev: v0.0.18 hooks: - - id: check-json - - id: pretty-format-json - - id: check-merge-conflict - - id: check-yaml -- repo: https://github.com/igorshubovych/markdownlint-cli - rev: v0.37.0 + - id: forbid-binary + - id: shellcheck + - id: shfmt + - repo: https://github.com/commitizen-tools/commitizen + rev: v3.18.2 hooks: - - id: markdownlint-fix -- repo: https://github.com/koalaman/shellcheck-precommit - rev: v0.9.0 + - id: commitizen + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.5.0 hooks: - - id: shellcheck + - id: check-json + - id: check-merge-conflict + - id: check-yaml + - id: trailing-whitespace + - id: check-shebang-scripts-are-executable + - id: check-vcs-permalinks + - id: check-toml + - id: check-xml + - id: check-yaml + - id: end-of-file-fixer diff --git a/.secrets.baseline b/.secrets.baseline index 44c3937..bf1b1af 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -108,5 +108,5 @@ } ], "results": {}, - "generated_at": "2023-10-09T19:16:05Z" + "generated_at": "2024-03-11T16:14:00Z" } diff --git a/LICENSE_HEADER b/LICENSE_HEADER new file mode 100644 index 0000000..a9b4527 --- /dev/null +++ b/LICENSE_HEADER @@ -0,0 +1,13 @@ +Copyright 2022 Developer Network + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f421a45 --- /dev/null +++ b/Makefile @@ -0,0 +1,110 @@ +all: deps tidy fmt build test lint + +op=op run --env-file="./.env" -- + +opact=op run --env-file="./.env.act" -- + +#------------------------------------------------------------------------- +# Variables +# ------------------------------------------------------------------------ +env=CGO_ENABLED=0 + +pyenv=.venv/bin + +#------------------------------------------------------------------------- +# Targets +#------------------------------------------------------------------------- +deps: + python3 -m venv .venv --clear + + $(pyenv)/pip install --upgrade pre-commit + $(pyenv)/pip install --upgrade detect-secrets + $(pyenv)/detect-secrets scan > .secrets.baseline + go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest + go install github.com/goreleaser/goreleaser@latest + go install github.com/google/go-licenses@latest + go install github.com/google/addlicense@latest + +ci-test-deps: + # install act + if [ ! -d .act ]; then make install-act; git clone git@github.com:nektos/act.git .act; fi + cd .act && git pull && sudo make install + +test: lint + CGO_ENABLED=1 go test -cover -failfast -race ./... + +fuzz: + $(pyenv)/python ./scripts/fuzz_go.py + +bench: + go test -bench=. -benchmem ./... + +lint: tidy + $(pyenv)/python ./scripts/license.py + golangci-lint run + $(pyenv)/pre-commit run --all-files + +build: update upgrade tidy lint test + $(env) go build ./... + +release: build-ci + goreleaser release --snapshot --clean + +upgrade: + $(pyenv)/pre-commit autoupdate + go get -u ./... + +update: + git submodule update --recursive + +fmt: + gofmt -s -w . + +tidy: fmt + go mod tidy + +clean: + rm -rf dist coverage .act .venv + +#------------------------------------------------------------------------- +# Git targets +#------------------------------------------------------------------------- + +tag: + $(pyenv)/python ./scripts/tag.py + +#------------------------------------------------------------------------- +# CI targets +#------------------------------------------------------------------------- +build-ci: deps + $(env) go build ./... + CGO_ENABLED=1 go test \ + -cover \ + -covermode=atomic \ + -coverprofile=coverage.txt \ + -failfast \ + -race ./... + make fuzz FUZZ_TIME=10 + +bench-ci: build-ci + go test -bench=. ./... | tee output.txt + +test-ci: + DOCKER_HOST=$(shell docker context inspect --format='{{json .Endpoints.docker.Host}}' $(shell docker context show)) \ + $(opact) act \ + -s GIT_CREDENTIALS \ + -s GITHUB_TOKEN="$(shell gh auth token)" \ + --var GO_VERSION \ + --var ALERT_CC_USERS + +#------------------------------------------------------------------------- +# Force targets +#------------------------------------------------------------------------- + +FORCE: + +#------------------------------------------------------------------------- +# Phony targets +#------------------------------------------------------------------------- + +.PHONY: build test lint fuzz bench fmt tidy clean release update upgrade deps translate test-act ci-test-deps diff --git a/lists/cursor/cursor.go b/lists/cursor/cursor.go new file mode 100644 index 0000000..9550664 --- /dev/null +++ b/lists/cursor/cursor.go @@ -0,0 +1,153 @@ +// Copyright 2022 Developer Network +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cursor + +import ( + "errors" +) + +var ErrIndexOutOfRange = errors.New("index out of range") + +type Cursor[T any] struct { + buff []T + pos int +} + +// Slice returns a new slice with the elements from start to end +// If start or end are out of range, it returns an error +func (c *Cursor[T]) Slice(start, end int) ([]T, error) { + if !c.isValidPOS(start) || !c.isValidPOS(end) { + return nil, ErrIndexOutOfRange + } + + // Create a new buffer and copy the slice + out := make([]T, end-start) + copy(out, c.buff[start:end]) + + return out, nil +} + +// Take returns a new cursor with the elements from start to end +// If start or end are out of range, it returns an error +func (c *Cursor[T]) Take(start, end int) (*Cursor[T], error) { + slice, err := c.Slice(start, end) + if err != nil { + return nil, err + } + + return New(slice), nil +} + +// Chop removes the elements from start to end +// If start or end are out of range, it returns an error +func (c *Cursor[T]) Chop(start, end int) error { + if !c.isValidPOS(start) || !c.isValidPOS(end) { + return ErrIndexOutOfRange + } + + c.buff = append(c.buff[:start], c.buff[end:]...) + return nil +} + +func (c *Cursor[T]) First() (T, error) { + return c.Seek(0) +} + +func (c *Cursor[T]) Last() (T, error) { + return c.Seek(len(c.buff) - 1) +} + +func (c *Cursor[T]) IterFn(f func(T) error) error { + if !c.validPOS() { + return ErrIndexOutOfRange + } + + for c.pos < len(c.buff) { + err := f(c.buff[c.pos]) + if err != nil { + return err + } + + if !c.isValidPOS(c.pos + 1) { + break + } + + c.pos++ + } + + return nil +} + +func (c *Cursor[T]) Len() int { + return len(c.buff) +} + +func (c *Cursor[T]) Next() (T, error) { + return c.Seek(c.pos + 1) +} + +func (c *Cursor[T]) Prev() (T, error) { + return c.Seek(c.pos - 1) +} + +func (c *Cursor[T]) Get() (T, error) { + return c.Seek(c.pos) +} + +func (c *Cursor[T]) Seek(pos int) (T, error) { + if c.isValidPOS(pos) { + c.pos = pos + return c.buff[c.pos], nil + } + + var out T + return out, ErrIndexOutOfRange +} + +func (c *Cursor[T]) isValidPOS(pos int) bool { + return pos >= 0 && pos < len(c.buff) +} + +func (c *Cursor[T]) validPOS() bool { + return c.isValidPOS(c.pos) +} + +func (c *Cursor[T]) Set(v T) { + if c.validPOS() { + c.buff[c.pos] = v + } +} + +func (c *Cursor[T]) Delete() { + if c.validPOS() { + c.buff = append(c.buff[:c.pos], c.buff[c.pos+1:]...) + } +} + +func Append[T any](c *Cursor[T], v T) { + c.buff = append(c.buff, v) +} + +func Prepend[T any](c *Cursor[T], v T) { + c.buff = append([]T{v}, c.buff...) +} + +func New[T any](buff []T) *Cursor[T] { + if buff == nil { + buff = make([]T, 0) + } + + return &Cursor[T]{buff: buff, pos: 0} +} diff --git a/lists/cursor/cursor_test.go b/lists/cursor/cursor_test.go new file mode 100644 index 0000000..416b3a9 --- /dev/null +++ b/lists/cursor/cursor_test.go @@ -0,0 +1,196 @@ +// Copyright 2022 Developer Network +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cursor + +import ( + "fmt" + "testing" +) + +func Test_New(t *testing.T) { + c := New([]int{1, 2, 3, 4, 5}) + if c == nil { + t.Fatal("expected a new cursor") + } + + if c.Len() != 5 { + t.Fatal("expected a cursor with length 5") + } + + if c.pos != 0 { + t.Fatal("expected a cursor with position 0") + } + + if c.buff[0] != 1 { + t.Fatal("expected a cursor with first element 1") + } + + if c.buff[4] != 5 { + t.Fatal("expected a cursor with last element 5") + } +} + +func Test_Cursor_Next(t *testing.T) { + tests := []struct { + data []int + pos int + want int + err error + }{ + {[]int{1, 2, 3, 4, 5}, 0, 2, nil}, + {[]int{1, 2, 3, 4, 5}, 1, 3, nil}, + {[]int{1, 2, 3, 4, 5}, 2, 4, nil}, + {[]int{1, 2, 3, 4, 5}, 3, 5, nil}, + {[]int{1, 2, 3, 4, 5}, 4, 0, ErrIndexOutOfRange}, + } + + for i, tt := range tests { + t.Run(fmt.Sprintf("test_%v", i), func(t *testing.T) { + c := New(tt.data) + c.pos = tt.pos + got, err := c.Next() + + if err != tt.err { + t.Fatalf("expected %v, got %v", tt.err, err) + } + + if got != tt.want { + t.Fatalf("expected %d, got %d", tt.want, got) + } + }) + } +} + +func Test_Cursor_Prev(t *testing.T) { + tests := []struct { + data []int + pos int + want int + err error + }{ + {[]int{1, 2, 3, 4, 5}, 4, 4, nil}, + {[]int{1, 2, 3, 4, 5}, 3, 3, nil}, + {[]int{1, 2, 3, 4, 5}, 2, 2, nil}, + {[]int{1, 2, 3, 4, 5}, 1, 1, nil}, + {[]int{1, 2, 3, 4, 5}, 0, 0, ErrIndexOutOfRange}, + } + + for i, tt := range tests { + t.Run(fmt.Sprintf("test_%v", i), func(t *testing.T) { + c := New(tt.data) + c.pos = tt.pos + got, err := c.Prev() + + if err != tt.err { + t.Fatalf("expected %v, got %v", tt.err, err) + } + + if got != tt.want { + t.Fatalf("expected %d, got %d", tt.want, got) + } + }) + } +} + +func Test_Cursor_Get(t *testing.T) { + tests := []struct { + data []int + pos int + want int + err error + }{ + {[]int{1, 2, 3, 4, 5}, 0, 1, nil}, + {[]int{1, 2, 3, 4, 5}, 1, 2, nil}, + {[]int{1, 2, 3, 4, 5}, 2, 3, nil}, + {[]int{1, 2, 3, 4, 5}, 3, 4, nil}, + {[]int{1, 2, 3, 4, 5}, 4, 5, nil}, + {[]int{1, 2, 3, 4, 5}, 5, 0, ErrIndexOutOfRange}, + } + + for i, tt := range tests { + t.Run(fmt.Sprintf("test_%v", i), func(t *testing.T) { + c := New(tt.data) + c.pos = tt.pos + got, err := c.Get() + + if err != tt.err { + t.Fatalf("expected %v, got %v", tt.err, err) + } + + if got != tt.want { + t.Fatalf("expected %d, got %d", tt.want, got) + } + }) + } +} + +func Test_Cursor_Seek(t *testing.T) { + tests := []struct { + data []int + pos int + want int + err error + }{ + {[]int{1, 2, 3, 4, 5}, 0, 1, nil}, + {[]int{1, 2, 3, 4, 5}, 1, 2, nil}, + {[]int{1, 2, 3, 4, 5}, 2, 3, nil}, + {[]int{1, 2, 3, 4, 5}, 3, 4, nil}, + {[]int{1, 2, 3, 4, 5}, 4, 5, nil}, + {[]int{1, 2, 3, 4, 5}, 5, 0, ErrIndexOutOfRange}, + } + + for i, tt := range tests { + t.Run(fmt.Sprintf("test_%v", i), func(t *testing.T) { + c := New(tt.data) + got, err := c.Seek(tt.pos) + + if err != tt.err { + t.Fatalf("expected %v, got %v", tt.err, err) + } + + if got != tt.want { + t.Fatalf("expected %d, got %d", tt.want, got) + } + }) + } +} + +func Test_Cursor_Set(t *testing.T) { + tests := []struct { + data []int + pos int + val int + want []int + }{ + {[]int{1, 2, 3, 4, 5}, 0, 10, []int{10, 2, 3, 4, 5}}, + {[]int{1, 2, 3, 4, 5}, 1, 10, []int{1, 10, 3, 4, 5}}, + {[]int{1, 2, 3, 4, 5}, 2, 10, []int{1, 2, 10, 4, 5}}, + {[]int{1, 2, 3, 4, 5}, 3, 10, []int{1, 2, 3, 10, 5}}, + {[]int{1, 2, 3, 4, 5}, 4, 10, []int{1, 2, 3, 4, 10}}, + } + + for i, tt := range tests { + t.Run(fmt.Sprintf("test_%v", i), func(t *testing.T) { + c := New(tt.data) + c.Set(tt.val) + + for i, v := range c.buff { + if tt.want[i] != v { + t.Fatalf("expected %v, got %v", tt.want[i], v) + } + } + }) + } +} diff --git a/scripts/fuzz_go.py b/scripts/fuzz_go.py new file mode 100755 index 0000000..824d094 --- /dev/null +++ b/scripts/fuzz_go.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 + +import os +import subprocess +import sys + +def run_command(command): + """Executes a shell command and returns its output.""" + return subprocess.check_output(command, shell=True, text=True) + +def main(): + fuzz_time = os.environ.get('FUZZ_TIME', '10') + + try: + # Find all Go test files containing fuzz functions + files = run_command("grep -r --include='**_test.go' --files-with-matches 'func Fuzz' .").strip().split('\n') + + for file in files: + # Extract fuzz function names from each file + funcs = run_command(f"grep -o 'func Fuzz\\w*' {file} | sed 's/func //'").strip().split('\n') + + for func in funcs: + print(f"Fuzzing {func} in {file}") + parent_dir = os.path.dirname(file) + + # Run the fuzz function with go test + try: + subprocess.check_call(f"go test {parent_dir} -run={func} -fuzz={func} -fuzztime={fuzz_time}s", shell=True) + except subprocess.CalledProcessError: + print(f"Fuzzing {func} in {file} failed") + sys.exit(1) + except subprocess.CalledProcessError as e: + # Handle cases where the initial grep command finds no matching files + print("No Go test files with fuzz functions found.") + except Exception as e: + print(f"An error occurred: {e}") + sys.exit(1) + +if __name__ == "__main__": + main() diff --git a/scripts/license.py b/scripts/license.py new file mode 100755 index 0000000..f59ec7d --- /dev/null +++ b/scripts/license.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 + +import os +import sys +import subprocess + +# Define lists of supported file types and directories to ignore +supported = ['go', 'py', 'js', 'ts', 'sh', 'c', 'h', 'cpp', 'hpp', 'rs', 'zig', 'hcl', 'zir'] +ignored_dirs = ['scripts', 'vendor', 'node_modules', '.git', '.github', '.vscode', '.idea', '.terraform', '.act', '.circleci', '.gitlab', '.venv', '.cache'] + +# Function to check if a file should be ignored based on its path +def is_file_in_ignored_dir(file_path, ignored_dirs): + return any(ignored_dir in file_path for ignored_dir in ignored_dirs) + +# Iterate over the command-line arguments (excluding the script name itself) +for file_path in sys.argv[1:]: + # Extract the file extension + extension = os.path.splitext(file_path)[1].lstrip('.') + + # Check if the extension is supported + if extension not in supported: + continue + + # Check if the file is in an ignored directory + if is_file_in_ignored_dir(file_path, ignored_dirs): + continue + + print(f"Adding license to {file_path}") + # Call the addlicense tool (ensure it's accessible from your Python environment) + subprocess.run(['addlicense', '-f', './LICENSE_HEADER', '-v', file_path], check=True) diff --git a/scripts/tag.py b/scripts/tag.py new file mode 100755 index 0000000..2f913f7 --- /dev/null +++ b/scripts/tag.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 + +import subprocess +import sys + +def run_command(command, capture_output=True, text=True): + """Executes a shell command and optionally returns its output.""" + result = subprocess.run(command, capture_output=capture_output, text=text, shell=True) + if result.returncode != 0: + print(f"Command '{command}' failed with return code {result.returncode}") + sys.exit(result.returncode) + out = result.stdout + try: + return out.strip() + except AttributeError: + return out + +def main(): + # Fetch the latest tag + latest_tag = run_command("git describe --tags $(git rev-list --tags --max-count=1)") + current_major, current_minor, current_patch = latest_tag.split('.') + + # Calculate the next minor version + next_minor = str(int(current_minor) + 1) + default_version = f"{current_major}.{next_minor}.{current_patch}" + + # Prompt the user for a version number, defaulting to the next minor version + version = input(f"Enter the version number [{default_version}]: ").strip() or default_version + + # Collect commits since the last tag + commits = run_command(f"git log {latest_tag}..HEAD --pretty=format:'%h %s'", capture_output=True, text=True) + commits_formatted = "\n".join([f"- {line}" for line in commits.split('\n')]) + + # Create and push the new tag + tag_message = f"Release {version}\n\n{commits_formatted}" + run_command(f"git tag -a {version} -m \"{tag_message}\"", capture_output=False, text=False) + run_command(f"git push origin {version}", capture_output=False, text=False) + +if __name__ == "__main__": + main() diff --git a/trees/errs.go b/trees/errs.go index 11d8d1a..b630cfd 100644 --- a/trees/errs.go +++ b/trees/errs.go @@ -1,3 +1,17 @@ +// Copyright 2022 Developer Network +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package trees import "errors" diff --git a/trees/nary/nary.go b/trees/nary/nary.go index fc11c43..4126660 100644 --- a/trees/nary/nary.go +++ b/trees/nary/nary.go @@ -1,3 +1,17 @@ +// Copyright 2022 Developer Network +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + // Package nary provides a n-ary tree implementation. package nary diff --git a/trees/nary/nary_test.go b/trees/nary/nary_test.go index 6548ff5..8bed6f4 100644 --- a/trees/nary/nary_test.go +++ b/trees/nary/nary_test.go @@ -1,3 +1,17 @@ +// Copyright 2022 Developer Network +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package nary import (