diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 4888a38..10b5e73 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -2,42 +2,87 @@ name: Go on: push: - tags: - - 'v*' + tags: + - "v*" jobs: - build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 + + - name: Set up Go + uses: actions/setup-go@v3 + with: + go-version: 1.19 + + - name: Test + run: make check test - - name: Set up Go - uses: actions/setup-go@v2 - with: - go-version: 1.17 + - name: Build + run: bash ./build.sh - - name: Build - run: make build - - - name: Create Release - id: release - uses: actions/create-release@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - tag_name: ${{ github.ref }} - release_name: Release ${{ github.ref }} - draft: false - prerelease: false - - name: Upload Release Asset - id: upload-release-asset - uses: actions/upload-release-asset@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ steps.release.outputs.upload_url }} # This pulls from the CREATE RELEASE step above, referencing it's ID to get its outputs object, which include a `upload_url`. See this blog post for more info: https://jasonet.co/posts/new-features-of-github-actions/#passing-data-to-future-steps - asset_path: ./lingualeo - asset_name: lingualeo - asset_content_type: application/octet-stream - + - name: Create Release + id: release + uses: actions/create-release@latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ github.ref }} + release_name: Release ${{ github.ref }} + draft: false + prerelease: false + - name: Upload Release Asset Linux AMD64 + uses: actions/upload-release-asset@latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.release.outputs.upload_url }} + asset_path: ./build/lingualeo-linux-amd64 + asset_name: lingualeo-linux-amd64 + asset_content_type: application/octet-stream + - name: Upload Release Asset Linux ARM64 + uses: actions/upload-release-asset@latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.release.outputs.upload_url }} + asset_path: ./build/lingualeo-linux-arm64 + asset_name: lingualeo-linux-arm64 + asset_content_type: application/octet-stream + - name: Upload Release Asset Darwin AMD64 + uses: actions/upload-release-asset@latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.release.outputs.upload_url }} + asset_path: ./build/lingualeo-darwin-amd64 + asset_name: lingualeo-darwin-amd64 + asset_content_type: application/octet-stream + - name: Upload Release Asset Darwin ARM64 + uses: actions/upload-release-asset@latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.release.outputs.upload_url }} + asset_path: ./build/lingualeo-darwin-arm64 + asset_name: lingualeo-darwin-arm64 + asset_content_type: application/octet-stream + - name: Upload Release Asset Windows AMD64 + uses: actions/upload-release-asset@latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.release.outputs.upload_url }} + asset_path: ./build/lingualeo-windows-amd64 + asset_name: lingualeo-windows-amd64 + asset_content_type: application/octet-stream + - name: Upload Release Asset Windows ARM64 + uses: actions/upload-release-asset@latest + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.release.outputs.upload_url }} + asset_path: ./build/lingualeo-windows-arm64 + asset_name: lingualeo-windows-arm64 + asset_content_type: application/octet-stream diff --git a/.gitignore b/.gitignore index 8c617c9..2d91b54 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ .vscode -/lingualeo +/lingualeo* .idea diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..9b43615 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,280 @@ +## Golden config for golangci-lint v1.46.2 +# +# This is the best config for golangci-lint based on my experience and opinion. +# It is very strict, but not extremely strict. +# Feel free to adopt and change it for your needs. + +run: + # Timeout for analysis, e.g. 30s, 5m. + # Default: 1m + timeout: 3m + + # Define the Go version limit. + # Mainly related to generics support in go1.18. + # Default: use Go version from the go.mod file, fallback on the env var `GOVERSION`, fallback on 1.17 + go: "1.19" # TODO: change to 1.18 when most linters support it, see https://github.com/golangci/golangci-lint/issues/2649 + + +# This file contains only configs which differ from defaults. +# All possible options can be found here https://github.com/golangci/golangci-lint/blob/master/.golangci.reference.yml +linters-settings: + cyclop: + # The maximal code complexity to report. + # Default: 10 + max-complexity: 30 + # The maximal average package complexity. + # If it's higher than 0.0 (float) the check is enabled + # Default: 0.0 + package-average: 10.0 + + errcheck: + # Report about not checking of errors in type assertions: `a := b.(MyStruct)`. + # Such cases aren't reported by default. + # Default: false + check-type-assertions: true + + funlen: + # Checks the number of lines in a function. + # If lower than 0, disable the check. + # Default: 60 + lines: 100 + # Checks the number of statements in a function. + # If lower than 0, disable the check. + # Default: 40 + statements: 50 + + gocognit: + # Minimal code complexity to report + # Default: 30 (but we recommend 10-20) + min-complexity: 20 + + gocritic: + # Settings passed to gocritic. + # The settings key is the name of a supported gocritic checker. + # The list of supported checkers can be find in https://go-critic.github.io/overview. + settings: + captLocal: + # Whether to restrict checker to params only. + # Default: true + paramsOnly: false + underef: + # Whether to skip (*x).method() calls where x is a pointer receiver. + # Default: true + skipRecvDeref: false + + gomnd: + # List of function patterns to exclude from analysis. + # Values always ignored: `time.Date` + # Default: [] + ignored-functions: + - os.Chmod + - os.Mkdir + - os.MkdirAll + - os.OpenFile + - os.WriteFile + - prometheus.ExponentialBuckets + - prometheus.ExponentialBucketsRange + - prometheus.LinearBuckets + - strconv.FormatFloat + - strconv.FormatInt + - strconv.FormatUint + - strconv.ParseFloat + - strconv.ParseInt + - strconv.ParseUint + + gomodguard: + blocked: + # List of blocked modules. + # Default: [] + modules: + - github.com/golang/protobuf: + recommendations: + - google.golang.org/protobuf + reason: "see https://developers.google.com/protocol-buffers/docs/reference/go/faq#modules" + - github.com/satori/go.uuid: + 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. + # Default: false + enable-all: true + # Disable analyzers by name. + # Run `go tool vet help` to see all analyzers. + # Default: [] + disable: + - fieldalignment # too strict + # Settings per analyzer. + settings: + shadow: + # Whether to be strict about shadowing; can be noisy. + # Default: false + strict: true + + nakedret: + # Make an issue if func has more lines of code than this setting, and it has naked returns. + # Default: 30 + max-func-lines: 0 + + nolintlint: + # Exclude following linters from requiring an explanation. + # Default: [] + allow-no-explanation: [ funlen, gocognit, lll ] + # Enable to require an explanation of nonzero length after each nolint directive. + # Default: false + require-explanation: true + # Enable to require nolint directives to mention the specific linter being suppressed. + # Default: false + require-specific: true + + rowserrcheck: + # database/sql is always checked + # Default: [] + packages: + - github.com/jmoiron/sqlx + + tenv: + # The option `all` will run against whole test files (`_test.go`) regardless of method/function signatures. + # Otherwise, only methods that take `*testing.T`, `*testing.B`, and `testing.TB` as arguments are checked. + # Default: false + all: true + + varcheck: + # Check usage of exported fields and variables. + # Default: false + exported-fields: false # default false # TODO: enable after fixing false positives + + +linters: + disable-all: true + enable: + ## enabled by default + # - deadcode # Finds unused code + - errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases + - gosimple # Linter for Go source code that specializes in simplifying a code + - govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string + - ineffassign # Detects when assignments to existing variables are not used + - staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks + # - structcheck # Finds unused struct fields + - typecheck # Like the front-end of a Go compiler, parses and type-checks Go code + - unused # Checks Go code for unused constants, variables, functions and types + # - varcheck # Finds unused global variables and constants + ## disabled by default + + # - asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers + # - bidichk # Checks for dangerous unicode character sequences + - bodyclose # checks whether HTTP response body is closed successfully + - contextcheck # check the function whether use a non-inherited context + # - cyclop # checks function and package cyclomatic complexity + # - dupl # Tool for code clone detection + # - durationcheck # check for two durations multiplied together + # - errname # Checks that sentinel errors are prefixed with the Err and error types are suffixed with the Error. + - errorlint # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13. + # - execinquery # execinquery is a linter about query string checker in Query function which reads your Go src files and warning it finds + # - exhaustive # check exhaustiveness of enum switch statements + # - exportloopref # checks for pointers to enclosing loop variables + # - forbidigo # Forbids identifiers + # - funlen # Tool for detection of long functions + # - gochecknoglobals # check that no global variables exist + # - gochecknoinits # Checks that no init functions are present in Go code + # - gocognit # Computes and checks the cognitive complexity of functions + # - 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 # Check if comments end in a period + - goimports # In addition to fixing imports, goimports also formats your code in the same style as gofmt. + # - gomnd # An analyzer to detect magic numbers. + # - gomoddirectives # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod. + # - gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations. + # - goprintffuncname # Checks that printf-like functions are named with f at the end + # - gosec # Inspects source code for security problems + # - lll # Reports long lines + # - makezero # Finds slice declarations with non-zero initial length + # - nakedret # Finds naked returns in functions greater than a specified function length + # - nestif # Reports deeply nested if statements + # - nilerr # Finds the code that returns nil even if it checks that the error is not nil. + # - nilnil # Checks that there is no simultaneous return of nil error and an invalid value. + # - noctx # noctx finds sending http request without context.Context + # - nolintlint # Reports ill-formed or insufficient nolint directives + # - nonamedreturns # Reports all named returns + # - nosprintfhostport # Checks for misuse of Sprintf to construct a host with port in a URL. + # - predeclared # find code that shadows one of Go's predeclared identifiers + # - promlinter # Check Prometheus metrics naming via promlint + - revive # Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint. + # - rowserrcheck # checks whether Err of rows is checked successfully + # - sqlclosecheck # Checks that sql.Rows and sql.Stmt are closed. + - stylecheck # Stylecheck is a replacement for golint + - tenv # tenv is analyzer that detects using os.Setenv instead of t.Setenv since Go1.17 + # - testpackage # linter that makes you use a separate _test package + # - tparallel # tparallel detects inappropriate usage of t.Parallel() method in your Go test codes + - unconvert # Remove unnecessary type conversions + - unparam # Reports unused function parameters + # - wastedassign # wastedassign finds wasted assignment statements. + # - whitespace # Tool for detection of leading and trailing whitespace + + ## you may want to enable + #- decorder # check declaration order and count of types, constants, variables and functions + #- exhaustruct # Checks if all structure fields are initialized + #- goheader # Checks is file header matches to pattern + #- ireturn # Accept Interfaces, Return Concrete Types + #- prealloc # [premature optimization, but can be used in some cases] Finds slice declarations that could potentially be preallocated + #- varnamelen # [great idea, but too many false positives] checks that the length of a variable's name matches its scope + #- wrapcheck # Checks that errors returned from external packages are wrapped + ## disabled + #- containedctx # containedctx is a linter that detects struct contained context.Context field + #- depguard # [replaced by gomodguard] Go linter that checks if package imports are in a list of acceptable packages + #- dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) + #- errchkjson # [don't see profit + I'm against of omitting errors like in the first example https://github.com/breml/errchkjson] Checks types passed to the json encoding functions. Reports unsupported types and optionally reports occasions, where the check for the returned error can be omitted. + #- forcetypeassert # [replaced by errcheck] finds forced type assertions + #- gci # Gci controls golang package import order and makes it always deterministic. + #- godox # Tool for detection of FIXME, TODO and other comment keywords + #- goerr113 # [too strict] Golang linter to check the errors handling expressions + #- gofmt # [replaced by goimports] Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification + #- gofumpt # [replaced by goimports, gofumports is not available yet] Gofumpt checks whether code was gofumpt-ed. + #- grouper # An analyzer to analyze expression groups. + #- ifshort # Checks that your code uses short syntax for if-statements whenever possible + #- importas # Enforces consistent import aliases + #- maintidx # maintidx measures the maintainability index of each function. + #- misspell # [useless] Finds commonly misspelled English words in comments + #- nlreturn # [too strict and mostly code is not more readable] nlreturn checks for a new line before return and branch statements to increase code clarity + #- paralleltest # [too many false positives] paralleltest detects missing usage of t.Parallel() method in your Go test + #- tagliatelle # Checks the struct tags. + #- thelper # thelper detects golang test helpers without t.Helper() call and checks the consistency of test helpers + #- wsl # [too strict and mostly code is not more readable] Whitespace Linter - Forces you to use empty lines! + ## deprecated + #- exhaustivestruct # [deprecated, replaced by exhaustruct] Checks if all struct's fields are initialized + #- golint # [deprecated, replaced by revive] Golint differs from gofmt. Gofmt reformats Go source code, whereas golint prints out style mistakes + #- interfacer # [deprecated] Linter that suggests narrower interface types + #- maligned # [deprecated, replaced by govet fieldalignment] Tool to detect Go structs that would take less memory if their fields were sorted + #- scopelint # [deprecated, replaced by exportloopref] Scopelint checks for unpinned variables in go programs + + +issues: + # Maximum count of issues with the same text. + # Set to 0 to disable. + # Default: 3 + max-same-issues: 50 + + exclude-rules: + - source: "^//\\s*go:generate\\s" + linters: [ lll ] + - source: "(noinspection|TODO)" + linters: [ godot ] + - source: "//noinspection" + linters: [ gocritic ] + - source: "^\\s+if _, ok := err\\.\\([^.]+\\.InternalError\\); ok {" + linters: [ errorlint ] + - path: "_test\\.go" + linters: + - bodyclose + - dupl + - funlen + - goconst + - gosec + - noctx + - wrapcheck diff --git a/Makefile b/Makefile index e007882..56bcee0 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,6 @@ PKG := $(shell awk '/^module/ { print $$2 }' go.mod) DEST := $(GOPATH)/src/$(GIT_HOST)/$(BASE_DIR) SOURCES := $(shell find $(DEST) -name '*.go' 2>/dev/null) HAS_GOLANGCI := $(shell command -v golangci-lint;) -HAS_GOIMPORTS := $(shell command -v goimports;) TARGETS ?= darwin/amd64 linux/amd64 linux/386 linux/arm linux/arm64 linux/ppc64le linux/s390x DIST_DIRS = find * -type d -exec @@ -26,7 +25,7 @@ TEMP_DIR :=$(shell mktemp -d) GOOS ?= $(shell go env GOOS) VERSION ?= $(shell git describe --tags 2> /dev/null || \ git describe --match=$(git rev-parse --short=8 HEAD) --always --dirty --abbrev=8) -GOARCH := amd64 +GOARCH ?= $(shell go env GOARCH) TAGS := LDFLAGS := "-w -s -X 'main.version=${VERSION}'" CMD_PACKAGE := ./cmd/lingualeo @@ -43,7 +42,13 @@ work: $(GOBIN) build: clean cache check test CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) go build \ -ldflags $(LDFLAGS) \ - -o $(BINARY) \ + -o $(BINARY)-$(GOOS)-$(GOARCH) \ + $(CMD_PACKAGE) + +build_no_tests: + CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) go build \ + -ldflags $(LDFLAGS) \ + -o build/$(BINARY)-$(GOOS)-$(GOARCH) \ $(CMD_PACKAGE) cache: @@ -56,21 +61,13 @@ install: clean check test test: unit -check: work prepare fmt vet goimports golangci +check: work prepare fmt vet golangci unit: work go test -tags=unit $(TESTARGS) ./... fmt: go fmt ./... -goimports: -ifndef HAS_GOIMPORTS - echo "installing goimports" - GO111MODULE=off go get golang.org/x/tools/cmd/goimports -endif - goimports -d $(shell find . -path ./.go -prune -o -type f -iname "*.go") - find . -iname "*.go" - vet: go vet ./... @@ -78,7 +75,7 @@ golangci: ifndef HAS_GOLANGCI curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell go env GOPATH)/bin v1.49.0 endif - golangci-lint run ./... + golangci-lint run cover: work go test $(TESTARGS) -tags=unit -cover -coverpkg=./ ./... @@ -89,11 +86,6 @@ ifndef HAS_GOLANGCI curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(shell go env GOPATH)/bin v1.49.0 endif echo "golangci-lint already installed" -ifndef HAS_GOIMPORTS - echo "installing goimports" - GO111MODULE=off go get golang.org/x/tools/cmd/goimports -endif - echo "goimports already installed" shell: $(SHELL) -i @@ -104,4 +96,4 @@ clean: work version: @echo ${VERSION} -.PHONY: install build cover work fmt test version clean prepare +.PHONY: install build build_no_tests -cover work fmt test version clean prepare diff --git a/README.md b/README.md index b56e73d..098f30a 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Lingualeo API console helper Install ------------------------------------ - go get -u github.com/trezorg/lingualeo/cmd/lingualeo + curl -sfL https://raw.githubusercontent.com/trezorg/lingualeo/master/install.sh | sh -s -- -d your_directory Using ------------------------------------ diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..9509f5a --- /dev/null +++ b/build.sh @@ -0,0 +1,5 @@ +for os in linux darwin windows; do + for arch in amd64 arm64; + do make build_no_tests GOOS="${os}" GOARCH="${arch}" + done +done diff --git a/go.mod b/go.mod index 5845db6..716ecfe 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/trezorg/lingualeo -go 1.17 +go 1.19 require ( github.com/BurntSushi/toml v0.3.1 @@ -20,6 +20,6 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/russross/blackfriday/v2 v2.0.1 // indirect github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect - golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f // indirect + golang.org/x/sys v0.1.0 // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect ) diff --git a/go.sum b/go.sum index d92628f..a050aaa 100644 --- a/go.sum +++ b/go.sum @@ -36,8 +36,8 @@ golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f h1:gWF768j/LaZugp8dyS4UwsslYCYz9XgFxvlgsn0n9H8= -golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= diff --git a/install.sh b/install.sh new file mode 100644 index 0000000..d75137a --- /dev/null +++ b/install.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env sh + +set -eou pipefail + +function usage() { + echo "Usage: $0 [ -d directory ]" + exit 2 +} + +INSTALL_DIR="${HOME}/bin" +NAME=lingualeo +OS=$(uname -o | tr '[:upper:]' '[:lower:]') +ARCH=$(uname -m | tr '[:upper:]' '[:lower:]') + +if which go &>/dev/null; then + gobin=$(go env GOBIN) + gopath="$(go env GOPATH)" + gopathbin="${gopath}/bin" + if [ -n "${gobin}" ] && [ -d "${gobin}" ]; then + INSTALL_DIR="${gobin}" + elif [ -n "${gopath}" ] && [ -d ${gopathbin} ]; then + INSTALL_DIR="${gopathbin}" + fi +fi + +while getopts hd: flag; do + case "${flag}" in + d) INSTALL_DIR=${OPTARG} ;; + h) usage ;; + *) usage ;; + esac +done + +if [ ! -d "${INSTALL_DIR}" ]; then + echo "Directory ${INSTALL_DIR} does not exist" + exit 1 +fi +if [ ! -w "${INSTALL_DIR}" ]; then + echo "Directory ${INSTALL_DIR} is not writable" + exit 1 +fi +APP_PATH="${INSTALL_DIR}/${NAME}" + +echo "Installalling into ${APP_PATH}..." + +TAG_NAME=$(curl -s https://api.github.com/repos/trezorg/${NAME}/releases/latest | awk -F ':' '/tag_name/ { gsub("[\", ]", "", $2); print $2 }') +DOWNLOAD_URL="https://github.com/trezorg/${NAME}/releases/download/${TAG_NAME}/${NAME}-${OS}-${ARCH}" + +if ! curl --fail-with-body -sL "${DOWNLOAD_URL}" -o "${APP_PATH}"; then + err=$? + echo "Failed to download ${DOWNLOAD_URL} into ${APP_PATH}" + exit ${err} +fi + +chmod +x "${APP_PATH}" +"${NAME}" --help +echo $? diff --git a/internal/fakeapi/api.go b/internal/fakeapi/api.go index edfd199..8286170 100644 --- a/internal/fakeapi/api.go +++ b/internal/fakeapi/api.go @@ -47,7 +47,7 @@ func (f *FakeAPI) TranslateWords(ctx context.Context, results <-chan string) <-c wg.Add(1) go func() { defer wg.Done() - for word := range channel.OrStringDone(ctx, results) { + for word := range channel.OrDone(ctx, results) { wg.Add(1) go func(word string) { defer wg.Done() diff --git a/pkg/api/api.go b/pkg/api/api.go index 17decd5..ec5352e 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -144,7 +144,7 @@ func debugResponse(response *http.Response) { } func request(method string, url string, client *http.Client, body []byte, query string, debug bool) (*string, error) { - var requestBody io.Reader = nil + var requestBody io.Reader if len(body) > 0 { requestBody = bytes.NewBuffer(body) } @@ -178,9 +178,9 @@ func request(method string, url string, client *http.Client, body []byte, query debugResponse(resp) } defer func() { - err := resp.Body.Close() - if err != nil { - logger.Error(err) + dErr := resp.Body.Close() + if dErr != nil { + logger.Error(dErr) } }() responseBody, err := readBody(resp) @@ -190,7 +190,7 @@ func request(method string, url string, client *http.Client, body []byte, query } if resp.StatusCode != 200 { return nil, fmt.Errorf( - "Response status code: %d\nbody:\n%s", + "response status code: %d\nbody:\n%s", resp.StatusCode, *responseBody, ) @@ -249,7 +249,7 @@ func (api *API) TranslateWords(ctx context.Context, results <-chan string) <-cha wg.Add(1) go func() { defer wg.Done() - for word := range channel.OrStringDone(ctx, results) { + for word := range channel.OrDone(ctx, results) { wg.Add(1) go func(word string) { defer wg.Done() diff --git a/pkg/api/items.go b/pkg/api/items.go index 1e4b516..e9a6ef0 100644 --- a/pkg/api/items.go +++ b/pkg/api/items.go @@ -95,7 +95,7 @@ func (result *TranslationResult) PrintTranslation() { printTranslation(result) } -// PrintAddedTranslation prints transcription diring adding operation +// PrintAddedTranslation prints transcription during adding operation func (result *TranslationResult) PrintAddedTranslation() { printAddedTranslation(result) } diff --git a/pkg/api/testing/translate_words_test.go b/pkg/api/testing/translate_words_test.go index 74099ed..caa661d 100644 --- a/pkg/api/testing/translate_words_test.go +++ b/pkg/api/testing/translate_words_test.go @@ -18,10 +18,10 @@ func TestTranslateWord(t *testing.T) { func TestTranslateWords(t *testing.T) { searchWords := []string{fakeapi.SearchWord} - fakeApi := fakeapi.FakeAPI{} + fakeAPI := fakeapi.FakeAPI{} ctx := context.Background() - ch := channel.ToStringChannel(ctx, searchWords...) - out := fakeApi.TranslateWords(ctx, ch) + ch := channel.ToChannel(ctx, searchWords...) + out := fakeAPI.TranslateWords(ctx, ch) res := (<-out).Result fakeapi.CheckResult(t, res, searchWords[0], fakeapi.Expected) } diff --git a/pkg/api/utils.go b/pkg/api/utils.go index 81b89d5..b46cfb6 100644 --- a/pkg/api/utils.go +++ b/pkg/api/utils.go @@ -35,8 +35,8 @@ func fromResponse(result Result, body string) error { err := getJSONFromString(body, result) if err != nil { res := NoResult{} - if err := getJSONFromString(body, res); err != nil { - return fmt.Errorf("cannot translate word: %s, %w", result.GetWord(), err) + if fErr := getJSONFromString(body, res); fErr != nil { + return fmt.Errorf("cannot translate word: %s, %w", result.GetWord(), fErr) } return err } diff --git a/pkg/channel/patterns.go b/pkg/channel/patterns.go index d0110f5..5bd32a3 100644 --- a/pkg/channel/patterns.go +++ b/pkg/channel/patterns.go @@ -4,8 +4,8 @@ import ( "context" ) -func ToStringChannel(ctx context.Context, input ...string) <-chan string { - out := make(chan string) +func ToChannel[V any](ctx context.Context, input ...V) <-chan V { + out := make(chan V) go func() { defer close(out) for _, val := range input { @@ -19,8 +19,8 @@ func ToStringChannel(ctx context.Context, input ...string) <-chan string { return out } -func OrStringDone(ctx context.Context, input <-chan string) <-chan string { - out := make(chan string) +func OrDone[V any](ctx context.Context, input <-chan V) <-chan V { + out := make(chan V) go func() { defer close(out) for { @@ -40,113 +40,3 @@ func OrStringDone(ctx context.Context, input <-chan string) <-chan string { }() return out } - -/* -func fanOut(ctx context.Context, inputs ...<-chan interface{}) <-chan interface{} { - var wg sync.WaitGroup - wg.Add(len(inputs)) - out := make(chan interface{}) - - multiplex := func(c <-chan interface{}) { - defer wg.Done() - for val := range c { - select { - case <-ctx.Done(): - return - case out <- val: - } - } - } - - for _, c := range inputs { - go multiplex(c) - } - - go func() { - defer close(out) - wg.Wait() - }() - return out -} - -func tee(ctx context.Context, input <-chan interface{}) (_, _ <-chan interface{}) { - out1 := make(chan interface{}) - out2 := make(chan interface{}) - go func() { - defer close(out1) - defer close(out2) - for val := range OrDone(ctx, input) { - var out1, out2 = out1, out2 - for i := 0; i < 2; i++ { - select { - case <-ctx.Done(): - case out1 <- val: - out1 = nil - case out2 <- val: - out2 = nil - } - } - } - }() - return out1, out2 -} - -func repeat(ctx context.Context, input ...interface{}) <-chan interface{} { - out := make(chan interface{}) - go func() { - defer close(out) - for { - for _, val := range input { - select { - case <-ctx.Done(): - return - case out <- val: - } - } - } - }() - return out -} - -func take(ctx context.Context, input <-chan interface{}, num int) <-chan interface{} { - out := make(chan interface{}) - go func() { - defer close(out) - for i := 0; i < num; i++ { - select { - case <-ctx.Done(): - return - case out <- <-input: - } - } - }() - return out -} - -func bridge(ctx context.Context, inputs <-chan <-chan interface{}) <-chan interface{} { - out := make(chan interface{}) - go func() { - defer close(out) - for { - var stream <-chan interface{} - select { - case maybe, ok := <-inputs: - if !ok { - return - } - stream = maybe - case <-ctx.Done(): - return - } - for val := range OrDone(ctx, stream) { - select { - case out <- val: - case <-ctx.Done(): - } - } - } - }() - return out -} - -*/ diff --git a/pkg/files/download.go b/pkg/files/download.go index 4cdb833..d334a74 100644 --- a/pkg/files/download.go +++ b/pkg/files/download.go @@ -66,9 +66,9 @@ func (f *FileDownloader) DownloadFile() (string, error) { return "", err } defer func() { - err := fd.Close() - if err != nil { - logger.Error(err) + cErr := fd.Close() + if cErr != nil { + logger.Error(cErr) } }() resp, err := http.Get(f.URL) @@ -76,9 +76,9 @@ func (f *FileDownloader) DownloadFile() (string, error) { return "", fmt.Errorf("cannot read url: %s, %w", f.URL, err) } defer func() { - err := resp.Body.Close() - if err != nil { - logger.Error(err) + cErr := resp.Body.Close() + if cErr != nil { + logger.Error(cErr) } }() if resp.StatusCode != http.StatusOK { @@ -99,7 +99,7 @@ func DownloadFiles(ctx context.Context, urls <-chan string, downloader NewDownlo go func() { defer wg.Done() idx := 0 - for url := range channel.OrStringDone(ctx, urls) { + for url := range channel.OrDone(ctx, urls) { wg.Add(1) go func(idx int, url string) { defer wg.Done() diff --git a/pkg/heap/heap.go b/pkg/heap/heap.go index 3fd6b96..9d90ead 100644 --- a/pkg/heap/heap.go +++ b/pkg/heap/heap.go @@ -52,7 +52,7 @@ func (h *IndexedHeap) Pull() *IndexedItem { if h.Len() == 0 { return nil } - message := heap.Pop(h).(IndexedItem) + message, _ := heap.Pop(h).(IndexedItem) return &message } @@ -60,7 +60,7 @@ func (h *IndexedHeap) PullWithCondition(check func(*IndexedItem) bool) *IndexedI h.lock.Lock() defer h.lock.Unlock() if check(h.pick()) { - message := heap.Pop(h).(IndexedItem) + message, _ := heap.Pop(h).(IndexedItem) return &message } return nil diff --git a/pkg/translator/linguleo.go b/pkg/translator/linguleo.go index 8070f9c..035c888 100644 --- a/pkg/translator/linguleo.go +++ b/pkg/translator/linguleo.go @@ -20,14 +20,12 @@ func (args *Lingualeo) checkMediaPlayer() { return } if len(args.Player) == 0 { - err := messages.Message(messages.RED, "Please set player parameter\n") - if err != nil { + if err := messages.Message(messages.RED, "Please set player parameter\n"); err != nil { logger.Debug(err) } args.Sound = false } else if !utils.IsCommandAvailable(args.Player) { - err := messages.Message(messages.RED, "Executable file %s is not available on your system\n", args.Player) - if err != nil { + if err := messages.Message(messages.RED, "Executable file %s is not available on your system\n", args.Player); err != nil { logger.Debug(err) } args.Sound = false @@ -37,8 +35,7 @@ func (args *Lingualeo) checkMediaPlayer() { // NewLingualeo initialize lingualeo client func NewLingualeo(version string) (Lingualeo, error) { client := prepareCliArgs(version) - err := client.checkConfig() - if err != nil { + if err := client.checkConfig(); err != nil { return client, err } configArgs, err := fromConfigs(&client.Config) @@ -46,8 +43,7 @@ func NewLingualeo(version string) (Lingualeo, error) { return client, err } client.mergeConfigs(configArgs) - err = client.checkArgs() - if err != nil { + if err = client.checkArgs(); err != nil { return client, err } if client.Debug { @@ -56,31 +52,27 @@ func NewLingualeo(version string) (Lingualeo, error) { } logger.InitLogger(client.LogLevel, client.LogPrettyPrint) client.checkMediaPlayer() - a, err := api.NewAPI(client.Email, client.Password, client.Debug) - if err != nil { + if client.API, err = api.NewAPI(client.Email, client.Password, client.Debug); err != nil { return client, err } - client.API = a return client, nil } func (args *Lingualeo) translateWords(ctx context.Context) <-chan api.OpResult { results := make(chan api.OpResult, len(args.Words)) - input := channel.ToStringChannel(ctx, args.Words...) + input := channel.ToChannel(ctx, args.Words...) go func() { defer close(results) for res := range api.OrOpResultDone(ctx, args.API.TranslateWords(ctx, input)) { if res.Error != nil { - err := messages.Message(messages.RED, "%s\n", utils.Capitalize(res.Error.Error())) - if err != nil { + if err := messages.Message(messages.RED, "%s\n", utils.Capitalize(res.Error.Error())); err != nil { logger.Error(err) } continue } if len(res.Result.GetTranslate()) == 0 { _ = messages.Message(messages.RED, "There are no translations for word: ") - err := messages.Message(messages.GREEN, "['%s']\n", res.Result.GetWord()) - if err != nil { + if err := messages.Message(messages.GREEN, "['%s']\n", res.Result.GetWord()); err != nil { logger.Error(err) } continue @@ -113,12 +105,10 @@ func (args *Lingualeo) downloadAndPronounce(ctx context.Context, urls <-chan str if res.Filename == "" { continue } - err := utils.PlaySound(args.Player, res.Filename) - if err != nil { + if err := utils.PlaySound(ctx, args.Player, res.Filename); err != nil { logger.Error(err) } - err = os.Remove(res.Filename) - if err != nil { + if err := os.Remove(res.Filename); err != nil { logger.Error(err) } } @@ -126,9 +116,8 @@ func (args *Lingualeo) downloadAndPronounce(ctx context.Context, urls <-chan str func (args *Lingualeo) pronounce(ctx context.Context, urls <-chan string, wg *sync.WaitGroup) { defer wg.Done() - for res := range channel.OrStringDone(ctx, urls) { - err := utils.PlaySound(args.Player, res) - if err != nil { + for res := range channel.OrDone(ctx, urls) { + if err := utils.PlaySound(ctx, args.Player, res); err != nil { logger.Error(err) } } diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go index 01fba1d..7b25cc0 100644 --- a/pkg/utils/utils.go +++ b/pkg/utils/utils.go @@ -1,6 +1,7 @@ package utils import ( + "context" "encoding/json" "fmt" "io" @@ -66,17 +67,15 @@ func FailIfError(err error) { } } -func PlaySound(player string, url string) error { +func PlaySound(ctx context.Context, player string, url string) error { parts := strings.Split(player, " ") playerExec := parts[0] params := append(parts[1:], url) - cmd := exec.Command(playerExec, params...) - err := cmd.Start() - if err != nil { + cmd := exec.CommandContext(ctx, playerExec, params...) + if err := cmd.Start(); err != nil { return err } - err = cmd.Wait() - if err != nil { + if err := cmd.Wait(); err != nil { return err } return nil