diff --git a/.copier-answers.yml b/.copier-answers.yml deleted file mode 100644 index 726f05a..0000000 --- a/.copier-answers.yml +++ /dev/null @@ -1,14 +0,0 @@ -# Changes here will be overwritten by Copier; NEVER EDIT MANUALLY -_commit: v0.10.3 -_src_path: gh:FollowTheProcess/go_copier -author_email: tomfleet2018@gmail.com -author_name: Tom Fleet -create_github_repo: true -description: Toy clone of coreutils wc in Go -github_url: https://github.com/FollowTheProcess/gowc -github_username: FollowTheProcess -initialise_git_repo: true -license: MIT License -project_name: gowc -project_slug: gowc -project_type: binary diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 3511300..a91d087 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -2,14 +2,12 @@ version: 2 updates: - package-ecosystem: github-actions - directory: "/" + directory: / schedule: - interval: daily + interval: weekly - package-ecosystem: gomod - directory: "/" + directory: / rebase-strategy: auto - allow: - - dependency-type: all schedule: - interval: daily + interval: weekly diff --git a/.github/labels.yml b/.github/labels.yml index aac86cd..bb4444e 100644 --- a/.github/labels.yml +++ b/.github/labels.yml @@ -22,6 +22,10 @@ description: Improvements or additions to documentation color: 0075ca +- name: duplicate + description: This issue or pull request already exists + color: cfd3d7 + - name: enhancement description: New feature or request color: a2eeef @@ -30,6 +34,14 @@ description: Good for newcomers color: 7057ff +- name: help wanted + description: Extra attention is needed + color: 008672 + +- name: invalid + description: This doesn't seem right + color: e4e669 + - name: performance description: Performance color: "016175" @@ -50,6 +62,14 @@ description: Style color: c120e5 +- name: chore + description: General project admin + color: cfd3d7 + - name: testing description: Testing color: b1fc6f + +- name: wontfix + description: This will not be worked on + color: ffffff diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index a21758f..8a68cc8 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -1,23 +1,24 @@ name: CI on: + workflow_call: pull_request: push: branches: - main - tags: - - v* concurrency: - group: ${{ github.ref }} + group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true -permissions: read-all +permissions: {} jobs: test: name: Test runs-on: ${{ matrix.os }} + permissions: + contents: read strategy: matrix: os: @@ -39,6 +40,8 @@ jobs: cov: name: CodeCov runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Checkout Code @@ -56,10 +59,13 @@ jobs: uses: codecov/codecov-action@v4 with: files: ./coverage.out + token: ${{ secrets.CODECOV_TOKEN }} lint: name: Lint runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Checkout Code @@ -77,58 +83,3 @@ jobs: uses: golangci/golangci-lint-action@v6 with: version: latest - - release: - name: Release - runs-on: ubuntu-latest - permissions: - contents: write - - needs: - - test - - cov - - lint - - if: github.event_name == 'push' && contains(github.ref, 'refs/tags/') - - steps: - - name: Checkout Code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Fetch Existing Tags - run: git fetch --force --tags - - - name: Install Syft - uses: jaxxstorm/action-install-gh-release@v1.12.0 - with: - repo: anchore/syft - - - name: Parse Release Version - id: version - run: | - VERSION=${GITHUB_REF#refs/tags/v} - echo "version=$VERSION" >> $GITHUB_OUTPUT - - - name: Set up Go - uses: actions/setup-go@v5 - with: - go-version-file: go.mod - - - name: Publish Draft Release - uses: release-drafter/release-drafter@v6 - with: - version: ${{ steps.version.outputs.version }} - publish: true - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v6 - with: - version: latest - args: release --clean - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index cfc179e..d2127e0 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -21,4 +21,4 @@ jobs: - name: Run Labeler uses: crazy-max/ghaction-github-labeler@v5 with: - skip-delete: true + skip-delete: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..06b881c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,68 @@ +name: Release + +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' + +permissions: {} + +jobs: + ci: + name: CI + uses: FollowTheProcess/gowc/.github/workflows/CI.yml@main + secrets: inherit + permissions: + contents: read + + release: + name: Release + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: read + + needs: + - ci + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Fetch Existing Tags + run: git fetch --force --tags + + - name: Install Syft + uses: jaxxstorm/action-install-gh-release@v1.12.0 + with: + repo: anchore/syft + + - name: Parse Release Version + id: version + run: | + VERSION=${GITHUB_REF#refs/tags/v} + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Publish Draft Release + uses: release-drafter/release-drafter@v6 + with: + version: ${{ steps.version.outputs.version }} + publish: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v6 + with: + version: latest + args: release --clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HOMEBREW_TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }} diff --git a/.github/workflows/release_drafter.yml b/.github/workflows/release_drafter.yml index 7bc2efb..c1ae333 100644 --- a/.github/workflows/release_drafter.yml +++ b/.github/workflows/release_drafter.yml @@ -18,6 +18,7 @@ jobs: runs-on: ubuntu-latest permissions: contents: write + pull-requests: read steps: - name: Run Release Drafter uses: release-drafter/release-drafter@v6 diff --git a/.gitignore b/.gitignore index b92d69a..bdc45d5 100755 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,13 @@ *.out coverage.html +# Taskfile +.task + +# Comparative benchmarks +before.txt +after.txt + # Dependency directories (remove the comment below to include it) # vendor/ diff --git a/.golangci.yml b/.golangci.yml index 2b1d881..131092c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -15,21 +15,22 @@ linters: - godot - gofumpt - goimports - - gomnd - gosimple - govet - ineffassign - misspell + - mnd - nilerr - nilnil - nolintlint - - nonamedreturns + - nakedret - predeclared - reassign - revive - staticcheck - tagliatelle - tenv + - testpackage - thelper - unconvert - unparam @@ -37,19 +38,90 @@ linters: - whitespace linters-settings: + errcheck: + check-type-assertions: true + check-blank: true + + exhaustive: + check: + - switch + - map + default-signifies-exhaustive: true + gocyclo: min-complexity: 20 staticcheck: - go: "1.21" checks: ["all"] - errcheck: - check-type-assertions: true - check-blank: true - gosimple: checks: ["all"] govet: enable-all: true + + revive: + max-open-files: 256 + ignore-generated-header: true + rules: + - name: argument-limit + disabled: false + arguments: [5] + + - name: atomic + disabled: false + + - name: blank-imports + disabled: false + + - name: call-to-gc + disabled: false + + - name: constant-logical-expr + disabled: false + + - name: context-as-argument + disabled: false + + - name: datarace + disabled: false + + - name: deep-exit + disabled: false + + - name: defer + disabled: false + + - name: dot-imports + disabled: false + + - name: early-return + disabled: false + + - name: exported + arguments: + - checkPrivateReceivers + + - name: modifies-value-receiver + disabled: false + + - name: package-comments + disabled: false + + - name: range + disabled: false + + - name: range-val-in-closure + disabled: false + + - name: range-val-address + disabled: false + + - name: time-equal + disabled: false + + - name: use-any + disabled: false + + - name: waitgroup-by-value + disabled: false diff --git a/.goreleaser.yml b/.goreleaser.yml index 2ad0898..1e12b70 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,3 +1,5 @@ +version: 2 + project_name: gowc before: @@ -9,6 +11,8 @@ builds: dir: "." main: "." binary: gowc + flags: + - -trimpath ldflags: - -s -w - -X main.version={{.Version}} @@ -31,17 +35,17 @@ brews: name: homebrew-tap token: "{{.Env.HOMEBREW_TAP_TOKEN}}" goarm: "7" - folder: Formula + directory: Formula commit_author: name: Tom Fleet email: tomfleet2018@gmail.com homepage: https://github.com/FollowTheProcess/gowc - description: Toy clone of coreutils wc in Go + description: Implementation of wc in Go license: MIT License install: | bin.install "gowc" test: | - "#{bin}/gowc -version" + "#{bin}/gowc --version" archives: - id: gowc @@ -59,7 +63,7 @@ sboms: documents: - >- {{ .ProjectName }}- - {{- .Version}}- + {{- .Version }}- {{- .Os }}- {{- if eq .Arch "amd64" }}x86_64 {{- else if eq .Arch "386" }}i386 @@ -73,4 +77,4 @@ snapshot: changelog: # The changelog is handled by release drafter - skip: true + disable: true diff --git a/LICENSE b/LICENSE index 6d3afb7..308431c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ MIT License -Copyright (c) 2023, Tom Fleet +Copyright (c) 2024, Tom Fleet Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile deleted file mode 100644 index 89313cb..0000000 --- a/Makefile +++ /dev/null @@ -1,48 +0,0 @@ -.PHONY: help tidy fmt test bench lint cover clean check sloc install uninstall -.DEFAULT_GOAL := help - -COVERAGE_DATA := coverage.out -COVERAGE_HTML := coverage.html - -help: ## Show the list of available tasks - @echo "Available Tasks:\n" - @grep -E '^[a-zA-Z_0-9%-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "%-10s %s\n", $$1, $$2}' - -tidy: ## Tidy dependencies in go.mod - go mod tidy - -fmt: ## Run go fmt on all source files - go fmt ./... - -test: ## Run the test suite - go test -race ./... - -bench: ## Run benchmarks - go test ./... -run=None -benchmem -bench . - -lint: ## Run the linters and auto-fix if possible - golangci-lint run --fix - -cover: ## Calculate test coverage and render the html - go test -race -cover -covermode atomic -coverprofile $(COVERAGE_DATA) ./... - go tool cover -html $(COVERAGE_DATA) -o $(COVERAGE_HTML) - open $(COVERAGE_HTML) - -clean: ## Remove build artifacts and other clutter - go clean ./... - rm -rf $(COVERAGE_DATA) $(COVERAGE_HTML) ./bin ./dist - -check: test lint ## Run tests and linting in one go - -sloc: ## Print lines of code (for fun) - find . -name "*.go" | xargs wc -l | sort -nr - -build: ## Compile the project binary - mkdir -p ./bin - goreleaser build --single-target --skip-before --snapshot --clean --output ./bin/gowc - -install: uninstall build ## Install the project on your machine - cp ./bin/gowc ${GOBIN} - -uninstall: ## Uninstall the project from your machine - rm -rf ${GOBIN}/gowc diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..1db217e --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,114 @@ +# https://taskfile.dev + +version: "3" + +vars: + COV_DATA: coverage.out + +tasks: + default: + desc: List all available tasks + silent: true + cmds: + - task --list + + tidy: + desc: Tidy dependencies in go.mod and go.sum + sources: + - "**/*.go" + - go.mod + - go.sum + cmds: + - go mod tidy + + fmt: + desc: Run go fmt on all source files + sources: + - "**/*.go" + preconditions: + - sh: command -v golines + msg: golines not installed, see https://github.com/segmentio/golines + cmds: + - go fmt ./... + - golines . --ignore-generated --write-output + + test: + desc: Run the test suite + sources: + - "**/*.go" + cmds: + - go test -race ./... {{ .CLI_ARGS }} + + build: + desc: Compile the project binary + sources: + - "**/*.go" + - .goreleaser.yml + generates: + - bin + - dist + cmds: + - mkdir -p ./bin + - goreleaser build --single-target --skip before --snapshot --clean --output ./bin/gowc + + bench: + desc: Run all project benchmarks + sources: + - "**/*.go" + cmds: + - go test ./... -run None -benchmem -bench . {{ .CLI_ARGS }} + + lint: + desc: Run the linters and auto-fix if possible + sources: + - "**/*.go" + - .golangci.yml + cmds: + - golangci-lint run --fix + preconditions: + - sh: command -v golangci-lint + msg: golangci-lint not installed, see https://golangci-lint.run/usage/install/#local-installation + + cov: + desc: Calculate test coverage and render the html + generates: + - "{{ .COV_DATA }}" + cmds: + - go test -race -cover -covermode atomic -coverprofile {{ .COV_DATA }} ./... + - go tool cover -html {{ .COV_DATA }} + + check: + desc: Run tests and linting in one + cmds: + - task: test + - task: lint + + sloc: + desc: Print lines of code + cmds: + - fd . -e go | xargs wc -l | sort -nr | head + + clean: + desc: Remove build artifacts and other clutter + cmds: + - go clean ./... + - rm -rf {{ .COV_DATA }} + + install: + desc: Install the project on your machine + deps: + - uninstall + - build + cmds: + - cp ./bin/gowc $GOBIN/gowc + + uninstall: + desc: Uninstall the project from your machine + cmds: + - rm -rf $GOBIN/gowc + + update: + desc: Updates dependencies in go.mod and go.sum + cmds: + - go get -u ./... + - go mody tidy diff --git a/codecov.yml b/codecov.yml deleted file mode 100644 index a3f27a1..0000000 --- a/codecov.yml +++ /dev/null @@ -1,4 +0,0 @@ -ignore: - # This is actually tested in main_test.go, but by compiling the program and executing the compiled version - # in the tests with various args, which the go coverage tool can't pick up on - - main.go diff --git a/img/bench.png b/img/bench.png deleted file mode 100644 index 1fa350e..0000000 Binary files a/img/bench.png and /dev/null differ diff --git a/main_test.go b/main_test.go index 2a3156f..0947fd1 100644 --- a/main_test.go +++ b/main_test.go @@ -12,20 +12,30 @@ import ( "github.com/FollowTheProcess/test" ) -const binName = "./gowc" +const binName = "gowc" + +var binPath string func TestMain(m *testing.M) { - build := exec.Command("go", "build", `-ldflags=-X 'main.version=0.1.0' -X 'main.commit=blah'`, "-o", binName) + cwd, err := os.Getwd() + if err != nil { + fmt.Fprintf(os.Stderr, "could not get cwd: %v", err) + os.Exit(1) + } + bin := filepath.Join(cwd, binName) + build := exec.Command("go", "build", `-ldflags=-X 'main.version=0.1.0' -X 'main.commit=blah'`, "-o", bin) build.Stdout = os.Stdout build.Stderr = os.Stderr if err := build.Run(); err != nil { - fmt.Fprintf(os.Stderr, "failed to compile %s: %v\n", binName, err) + fmt.Fprintf(os.Stderr, "failed to compile %s: %v\n", bin, err) os.Exit(1) } + binPath = bin + result := m.Run() - os.Remove(binName) + os.Remove(binPath) os.Exit(result) } @@ -33,7 +43,7 @@ func TestMain(m *testing.M) { func TestHelp(t *testing.T) { stdout := &bytes.Buffer{} - cmd := exec.Command(binName, "-help") + cmd := exec.Command(binPath, "-help") cmd.Stdout = stdout err := cmd.Run() test.Ok(t, err) @@ -47,7 +57,7 @@ func TestHelp(t *testing.T) { func TestVersion(t *testing.T) { stdout := &bytes.Buffer{} - cmd := exec.Command(binName, "-version") + cmd := exec.Command(binPath, "-version") cmd.Stdout = stdout err := cmd.Run() test.Ok(t, err) @@ -59,7 +69,7 @@ func TestVersion(t *testing.T) { } func TestBadFlag(t *testing.T) { - cmd := exec.Command(binName, "-bad") + cmd := exec.Command(binPath, "-bad") if err := cmd.Run(); err == nil { t.Fatal("-bad did not error") } @@ -69,7 +79,7 @@ func TestCountStdin(t *testing.T) { stdout := &bytes.Buffer{} stderr := &bytes.Buffer{} - cmd := exec.Command(binName) + cmd := exec.Command(binPath) cmd.Stdin = strings.NewReader("hello there\n") cmd.Stdout = stdout cmd.Stderr = stderr @@ -88,7 +98,7 @@ func TestCountStdinJSON(t *testing.T) { stdout := &bytes.Buffer{} stderr := &bytes.Buffer{} - cmd := exec.Command(binName, "-json") + cmd := exec.Command(binPath, "-json") cmd.Stdin = strings.NewReader("hello there\n") cmd.Stdout = stdout cmd.Stderr = stderr @@ -106,7 +116,7 @@ func TestCountStdinJSON(t *testing.T) { func TestCountStdinEmpty(t *testing.T) { stderr := &bytes.Buffer{} - cmd := exec.Command(binName) + cmd := exec.Command(binPath) cmd.Stderr = stderr if err := cmd.Run(); err == nil { @@ -126,7 +136,7 @@ func TestCountFile(t *testing.T) { mobyDick := filepath.Join("internal", "count", "testdata", "moby_dick.txt") - cmd := exec.Command(binName, mobyDick) + cmd := exec.Command(binPath, mobyDick) cmd.Stdout = stdout cmd.Stderr = stderr @@ -146,7 +156,7 @@ func TestCountFileJSON(t *testing.T) { mobyDick := filepath.Join("internal", "count", "testdata", "moby_dick.txt") - cmd := exec.Command(binName, "-json", mobyDick) + cmd := exec.Command(binPath, "-json", mobyDick) cmd.Stdout = stdout cmd.Stderr = stderr