diff --git a/.travis.yml b/.travis.yml index bfd1c4c..66c8fe0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,9 @@ go: - 1.7 - 1.8 - 1.9 + - "1.10" + - 1.11 + - 1.12 install: - make setup diff --git a/Makefile b/Makefile index a0476a7..f3a377b 100644 --- a/Makefile +++ b/Makefile @@ -1,31 +1,39 @@ HELP?=$$(go run main.go --help 2>&1) VERSION?=$$(cat VERSION) -GO18?=$(shell go version | grep -E "go1\.[89]") +GONEWER?=$(shell go version | grep -E "go1\.1[01]") DEP?=$$(which dep) +LINTER?=$$(which golangci-lint) +LINTER_VERSION=1.15.0 export GO15VENDOREXPERIMENT=1 ifeq ($(OS),Windows_NT) DEP_VERS=dep-windows-amd64.exe + LINTER_FILE=golangci-lint-$(LINTER_VERSION)-windows-amd64.zip + LINTER_UNPACK= >| app.zip; unzip -j app.zip -d $$GOPATH/bin; rm app.zip else ifeq ($(OS), Darwin) DEP_VERS=dep-darwin-amd64 + LINTER_FILE=golangci-lint-$(LINTER_VERSION)-darwin-amd64.tar.gz + LINTER_UNPACK= | tar xzf - -C $$GOPATH/bin --wildcards --strip 1 "**/golangci-lint" else DEP_VERS=dep-linux-amd64 + LINTER_FILE=golangci-lint-$(LINTER_VERSION)-linux-amd64.tar.gz + LINTER_UNPACK= | tar xzf - -C $$GOPATH/bin --wildcards --strip 1 "**/golangci-lint" endif setup: ## Install all the build and lint dependencies go get -u golang.org/x/tools/cmd/cover go get -u github.com/robertkrimen/godocdown/godocdown + @if [ "$(DEP)" = "" ]; then\ - curl -L https://github.com/golang/dep/releases/download/v0.4.1/$(DEP_VERS) >| $$GOPATH/bin/dep;\ + curl -L https://github.com/golang/dep/releases/download/v0.5.1/$(DEP_VERS) >| $$GOPATH/bin/dep;\ chmod +x $$GOPATH/bin/dep;\ fi dep ensure -ifeq ($(GO18),) - @echo no install metalinter, because metalinter need go1.8+ -else - go get -u github.com/alecthomas/gometalinter - gometalinter --install -endif + + @if [ "$(LINTER)" = "" ]; then\ + curl -L https://github.com/golangci/golangci-lint/releases/download/v$(LINTER_VERSION)/$(LINTER_FILE) $(LINTER_UNPACK) ;\ + chmod +x $$GOPATH/bin/golangci-lint;\ + fi generate: ## Generate README.md godocdown >| README.md @@ -40,30 +48,9 @@ fmt: ## gofmt and goimports all go files find . -name '*.go' -not -wholename './vendor/*' | while read -r file; do gofmt -w -s "$$file"; goimports -w "$$file"; done lint: ## Run all the linters -ifeq ($(GO18),) - @echo no run metalinter, because metalinter need go1.8+ -else - #https://github.com/golang/go/issues/19490 - #--enable=vetshadow \ - - gometalinter --vendor --disable-all \ - --enable=deadcode \ - --enable=ineffassign \ - --enable=gosimple \ - --enable=staticcheck \ - --enable=gofmt \ - --enable=goimports \ - --enable=dupl \ - --enable=misspell \ - --enable=errcheck \ - --enable=vet \ - --deadline=10m \ - --enable=vetshadow \ - ./... - -endif + golangci-lint run -ci: test lint ## Run all the tests and code checks +ci: test ## Run all the tests but no linters - use https://golangci.com integration instead build: ## Build the app go build diff --git a/VERSION b/VERSION index 0d91a54..9e11b32 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.3.0 +0.3.1 diff --git a/new.go b/new.go index ea5e973..a213888 100644 --- a/new.go +++ b/new.go @@ -38,6 +38,7 @@ type TempOpt struct { // directory where is temp file/dir create, empty string `""` (default) means TEMPDIR (`os.TempDir`) Dir string // name beginning with prefix + // if prefix includes a "*", the random string replaces the last "*". Prefix string } @@ -52,7 +53,7 @@ type TempOpt struct { // temp.Remove() // func NewTempFile(options TempOpt) (p Path, err error) { - file, err := ioutil.TempFile(options.Dir, options.Prefix) + file, err := tempFile(options.Dir, options.Prefix) if err != nil { return nil, errors.Wrapf(err, "NewTempFile(%+v) fail", options) } diff --git a/new_test.go b/new_test.go index 4bfa39b..834a60a 100644 --- a/new_test.go +++ b/new_test.go @@ -46,6 +46,18 @@ func TestTempFile(t *testing.T) { assert.Exactly(t, true, temp.Exists(), "new temp file exists") } +func TestTempFileWithPattern(t *testing.T) { + temp, err := NewTempFile(TempOpt{Prefix: "bla*.dat"}) + defer func() { + assert.NoError(t, temp.Remove()) + }() + + assert.NotNil(t, temp) + assert.Nil(t, err) + assert.Exactly(t, true, temp.Exists(), "new temp file exists") + assert.Regexp(t, "bla.+\\.dat$", temp.String()) +} + func TestCwd(t *testing.T) { cwd, err := Cwd() assert.NotNil(t, cwd) diff --git a/tempfile.go b/tempfile.go new file mode 100644 index 0000000..d9ee48f --- /dev/null +++ b/tempfile.go @@ -0,0 +1,66 @@ +package pathutil + +// copy of go1.11 version of ioutil.TempFile +// https://golang.org/src/io/ioutil/tempfile.go?s=1419:1477#L40 +// because eerlier versions don't support pattern + +import ( + "os" + "path/filepath" + "strconv" + "strings" + "sync" + "time" +) + +// Random number state. +// We generate random temporary file names so that there's a good +// chance the file doesn't exist yet - keeps the number of tries in +// TempFile to a minimum. +var rand uint32 +var randmu sync.Mutex + +func reseed() uint32 { + return uint32(time.Now().UnixNano() + int64(os.Getpid())) +} + +func nextRandom() string { + randmu.Lock() + r := rand + if r == 0 { + r = reseed() + } + r = r*1664525 + 1013904223 // constants from Numerical Recipes + rand = r + randmu.Unlock() + return strconv.Itoa(int(1e9 + r%1e9))[1:] +} + +func tempFile(dir, pattern string) (f *os.File, err error) { + if dir == "" { + dir = os.TempDir() + } + + var prefix, suffix string + if pos := strings.LastIndex(pattern, "*"); pos != -1 { + prefix, suffix = pattern[:pos], pattern[pos+1:] + } else { + prefix = pattern + } + + nconflict := 0 + for i := 0; i < 10000; i++ { + name := filepath.Join(dir, prefix+nextRandom()+suffix) + f, err = os.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) + if os.IsExist(err) { + if nconflict++; nconflict > 10 { + randmu.Lock() + rand = reseed() + randmu.Unlock() + } + continue + } + break + } + return +}