From 511a81cab33ca98b5ff55a03dc2c2b8daad4e50c Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Fri, 7 Mar 2025 16:25:40 -0600 Subject: [PATCH 01/11] =?UTF-8?q?=F0=9F=9B=A0=20Add=20Taskfile?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 11 +++---- Taskfile.yml | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 6 deletions(-) create mode 100644 Taskfile.yml diff --git a/.gitignore b/.gitignore index 1a431c2..71f6a2d 100644 --- a/.gitignore +++ b/.gitignore @@ -11,11 +11,8 @@ # Test binary, built with `go test -c` *.test -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -# Dependency directories (remove the comment below to include it) -# vendor/ +# Dependency directories +vendor/ # Go workspace file go.work @@ -23,4 +20,6 @@ go.work.sum # env file .env -cover.* + +build/ +.task/ diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..025ff4b --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,86 @@ +version: '3' + +tasks: + default: + cmds: + - task: fmt + - task: test + - task: build + + fmt: + desc: Format go code + sources: + - '**/*.go' + cmds: + - go fmt ./... + - go mod tidy + + gen: + desc: Generate files + sources: + - '**/*.go' + cmds: + - go generate ./... + + vet: + desc: Vet go code + sources: + - '**/*.go' + cmds: + - go vet ./... + + critic: + desc: Critique go code + sources: + - '**/*.go' + cmds: + - gocritic check ./... + + staticcheck: + desc: Static check go code + sources: + - '**/*.go' + cmds: + - staticcheck ./... + + vuln: + desc: Check for vulnerabilities + sources: + - '**/*.go' + cmds: + - govulncheck ./... + + lint: + desc: Do static analysis + deps: + - vet + - critic + - staticcheck + - vuln + + test: + desc: Run unit tests + deps: [fmt, vet] + sources: + - '**/*.go' + generates: + - build/cover.out + cmds: + - go test -race -cover -coverprofile build/cover.out ./... + + coverage-report: + desc: Build coverage report + deps: [test] + sources: + - build/cover.out + generates: + - build/cover.html + cmds: + - go tool cover -html=build/cover.out -o build/cover.html + + serve-report: + desc: Serve the coverage report + deps: [coverage-report] + cmds: + - ip addr list | grep inet + - php -S 0.0.0.0:3265 -t build From bd8f9ae8a68d800918f452876449c967d2a1abe8 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Fri, 7 Mar 2025 16:26:24 -0600 Subject: [PATCH 02/11] =?UTF-8?q?=E2=9C=A8=20Add=20options.go?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 8 ++++++++ go.sum | 10 ++++++++++ options.go | 52 +++++++++++++++++++++++++++++++++++++++++++++++++ options_test.go | 48 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 118 insertions(+) create mode 100644 go.sum create mode 100644 options.go create mode 100644 options_test.go diff --git a/go.mod b/go.mod index ff9df1b..d428f6c 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,11 @@ module codeberg.org/danjones000/nomino go 1.23.6 + +require github.com/stretchr/testify v1.10.0 + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..713a0b4 --- /dev/null +++ b/go.sum @@ -0,0 +1,10 @@ +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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +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/options.go b/options.go new file mode 100644 index 0000000..395925f --- /dev/null +++ b/options.go @@ -0,0 +1,52 @@ +package nomino + +import "strings" + +type generator func() string + +type config struct { + original string + prefix string + suffix string + extension string + generator generator +} + +func defaultConf() config { + return config{ + extension: ".txt", + generator: func() string { return "abc" }, + } +} + +type Option func(c *config) + +func WithOriginal(o string) Option { + return func(c *config) { + c.original = "_" + o + } +} + +func WithPrefix(p string) Option { + return func(c *config) { + c.prefix = p + "_" + } +} + +func WithSuffix(s string) Option { + return func(c *config) { + c.suffix = "_" + s + } +} + +func WithoutExtension() Option { + return func(c *config) { + c.extension = "" + } +} + +func WithExtension(ext string) Option { + return func(c *config) { + c.extension = "." + strings.TrimPrefix(ext, ".") + } +} diff --git a/options_test.go b/options_test.go new file mode 100644 index 0000000..1c7486b --- /dev/null +++ b/options_test.go @@ -0,0 +1,48 @@ +package nomino + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDefaultConf(t *testing.T) { + c := defaultConf() + assert.Equal(t, ".txt", c.extension) + // assert.Nil(t, c.generator) + assert.Equal(t, "abc", c.generator()) +} + +func TestWithOriginal(t *testing.T) { + var c config + name := "foobar" + WithOriginal(name)(&c) + assert.Equal(t, "_"+name, c.original) +} + +func TestWithPrefix(t *testing.T) { + var c config + pref := "draft" + WithPrefix(pref)(&c) + assert.Equal(t, pref+"_", c.prefix) +} + +func TestWithSuffix(t *testing.T) { + var c config + suff := "out" + WithSuffix(suff)(&c) + assert.Equal(t, "_"+suff, c.suffix) +} + +func TestWithoutExtension(t *testing.T) { + c := config{extension: ".foobar"} + WithoutExtension()(&c) + assert.Equal(t, "", c.extension) +} + +func TestWithExtension(t *testing.T) { + var c config + ext := "yaml" + WithExtension(ext)(&c) + assert.Equal(t, "."+ext, c.extension) +} From 0fc4369679f230655d38b0734f20007742ed4538 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Fri, 7 Mar 2025 17:00:38 -0600 Subject: [PATCH 03/11] =?UTF-8?q?=E2=9C=A8=20Add=20UUID=20generator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- generators.go | 23 +++++++++++++++++++++++ generators_test.go | 33 +++++++++++++++++++++++++++++++++ go.mod | 5 ++++- go.sum | 2 ++ options.go | 4 ++-- options_test.go | 6 ++++-- 6 files changed, 68 insertions(+), 5 deletions(-) create mode 100644 generators.go create mode 100644 generators_test.go diff --git a/generators.go b/generators.go new file mode 100644 index 0000000..099bc13 --- /dev/null +++ b/generators.go @@ -0,0 +1,23 @@ +package nomino + +import ( + "github.com/google/uuid" +) + +func setGenerator(c *config, g generator) { + c.generator = g +} + +func uuidGen() (string, error) { + u, err := uuid.NewRandom() + if err != nil { + return "", err + } + return u.String(), nil +} + +func WithUUID() Option { + return func(c *config) { + setGenerator(c, uuidGen) + } +} diff --git a/generators_test.go b/generators_test.go new file mode 100644 index 0000000..3691736 --- /dev/null +++ b/generators_test.go @@ -0,0 +1,33 @@ +package nomino + +import ( + "errors" + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" +) + +func TestWithUUID(t *testing.T) { + var c config + WithUUID()(&c) + st, err := c.generator() + assert.NoError(t, err) + _, parseErr := uuid.Parse(st) + assert.NoError(t, parseErr) +} + +type badRead struct{} + +func (badRead) Read([]byte) (int, error) { + return 0, errors.New("sorry") +} + +func TestWithUUIDFail(t *testing.T) { + var c config + uuid.SetRand(badRead{}) + defer uuid.SetRand(nil) + WithUUID()(&c) + _, err := c.generator() + assert.Equal(t, errors.New("sorry"), err) +} diff --git a/go.mod b/go.mod index d428f6c..de6af60 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,10 @@ module codeberg.org/danjones000/nomino go 1.23.6 -require github.com/stretchr/testify v1.10.0 +require ( + github.com/google/uuid v1.6.0 + github.com/stretchr/testify v1.10.0 +) require ( github.com/davecgh/go-spew v1.1.1 // indirect diff --git a/go.sum b/go.sum index 713a0b4..14c872b 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ 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/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= diff --git a/options.go b/options.go index 395925f..67d09c1 100644 --- a/options.go +++ b/options.go @@ -2,7 +2,7 @@ package nomino import "strings" -type generator func() string +type generator func() (string, error) type config struct { original string @@ -15,7 +15,7 @@ type config struct { func defaultConf() config { return config{ extension: ".txt", - generator: func() string { return "abc" }, + generator: uuidGen, } } diff --git a/options_test.go b/options_test.go index 1c7486b..ea9ed47 100644 --- a/options_test.go +++ b/options_test.go @@ -3,14 +3,16 @@ package nomino import ( "testing" + "github.com/google/uuid" "github.com/stretchr/testify/assert" ) func TestDefaultConf(t *testing.T) { c := defaultConf() assert.Equal(t, ".txt", c.extension) - // assert.Nil(t, c.generator) - assert.Equal(t, "abc", c.generator()) + st, _ := c.generator() + _, parseErr := uuid.Parse(st) + assert.NoError(t, parseErr) } func TestWithOriginal(t *testing.T) { From ee627547a8d5c775fef3c581fac21347bd5a17a4 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Mon, 10 Mar 2025 11:47:01 -0500 Subject: [PATCH 04/11] =?UTF-8?q?=E2=9C=A8=20Add=20time-based=20generators?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- generators.go | 26 ++++++++++++++++++++++++++ generators_test.go | 19 +++++++++++++++++++ options.go | 2 -- 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/generators.go b/generators.go index 099bc13..403f440 100644 --- a/generators.go +++ b/generators.go @@ -1,9 +1,13 @@ package nomino import ( + "time" + "github.com/google/uuid" ) +type generator func() (string, error) + func setGenerator(c *config, g generator) { c.generator = g } @@ -21,3 +25,25 @@ func WithUUID() Option { setGenerator(c, uuidGen) } } + +const FileTimestamp string = "2006-01-02_03-05-06-0700" + +func WithTimestamp() Option { + return WithTimestampFormat(FileTimestamp) +} + +func WithTimestampFormat(f string) Option { + return WithFormattedTime(time.Now(), f) +} + +func WithTime(t time.Time) Option { + return WithFormattedTime(t, FileTimestamp) +} + +func WithFormattedTime(t time.Time, f string) Option { + return func(c *config) { + setGenerator(c, func() (string, error) { + return t.Format(f), nil + }) + } +} diff --git a/generators_test.go b/generators_test.go index 3691736..fb3f8c3 100644 --- a/generators_test.go +++ b/generators_test.go @@ -3,6 +3,7 @@ package nomino import ( "errors" "testing" + "time" "github.com/google/uuid" "github.com/stretchr/testify/assert" @@ -31,3 +32,21 @@ func TestWithUUIDFail(t *testing.T) { _, err := c.generator() assert.Equal(t, errors.New("sorry"), err) } + +func TestWithTimestamp(t *testing.T) { + var c config + WithTimestamp()(&c) + n := time.Now() + st, err := c.generator() + assert.NoError(t, err) + assert.Equal(t, n.Format(FileTimestamp), st) +} + +func TestWithTime(t *testing.T) { + var c config + d := time.Date(1986, time.March, 28, 12, 0, 0, 0, time.UTC) + WithTime(d)(&c) + st, err := c.generator() + assert.NoError(t, err) + assert.Equal(t, d.Format(FileTimestamp), st) +} diff --git a/options.go b/options.go index 67d09c1..6f122ce 100644 --- a/options.go +++ b/options.go @@ -2,8 +2,6 @@ package nomino import "strings" -type generator func() (string, error) - type config struct { original string prefix string From 1af608d7c9e95da2464c6d9f8a10f6064b4b7cc1 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Mon, 10 Mar 2025 11:52:55 -0500 Subject: [PATCH 05/11] =?UTF-8?q?=E2=9C=A8=20Add=20UTC=20time-based=20gene?= =?UTF-8?q?rators?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- generators.go | 10 ++++++++++ generators_test.go | 9 +++++++++ 2 files changed, 19 insertions(+) diff --git a/generators.go b/generators.go index 403f440..db2bce1 100644 --- a/generators.go +++ b/generators.go @@ -47,3 +47,13 @@ func WithFormattedTime(t time.Time, f string) Option { }) } } + +const FileTimestampNoTZ string = "2006-01-02_03-05-06" + +func WithTimestampUTC() Option { + return WithTimeUTC(time.Now()) +} + +func WithTimeUTC(t time.Time) Option { + return WithFormattedTime(t.UTC(), FileTimestampNoTZ) +} diff --git a/generators_test.go b/generators_test.go index fb3f8c3..6f4cc2d 100644 --- a/generators_test.go +++ b/generators_test.go @@ -50,3 +50,12 @@ func TestWithTime(t *testing.T) { assert.NoError(t, err) assert.Equal(t, d.Format(FileTimestamp), st) } + +func TestWithTimestampUTC(t *testing.T) { + var c config + WithTimestampUTC()(&c) + n := time.Now() + st, err := c.generator() + assert.NoError(t, err) + assert.Equal(t, n.UTC().Format(FileTimestampNoTZ), st) +} From abe7acffd453eb312613d8611780ae7a37c9d5f9 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Mon, 10 Mar 2025 13:46:13 -0500 Subject: [PATCH 06/11] =?UTF-8?q?=F0=9F=92=A1=20Document=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- generators.go | 11 +++++++++++ options.go | 7 +++++++ 2 files changed, 18 insertions(+) diff --git a/generators.go b/generators.go index db2bce1..682c405 100644 --- a/generators.go +++ b/generators.go @@ -20,26 +20,34 @@ func uuidGen() (string, error) { return u.String(), nil } +// WithUUID sets a generator that creates a UUIDv4 func WithUUID() Option { return func(c *config) { setGenerator(c, uuidGen) } } +// FileTimestamp is the default format for WithTimestamp and WithTime const FileTimestamp string = "2006-01-02_03-05-06-0700" +// WithTimestamp sets a generator that creates a date and time for the current time. +// The format is FileStamp func WithTimestamp() Option { return WithTimestampFormat(FileTimestamp) } +// WithTimestampFormat sets a generator the creates a date and time for the current time with the supplied format. func WithTimestampFormat(f string) Option { return WithFormattedTime(time.Now(), f) } +// WithTime sets a generator that creates a date and time for the supplied time. +// The format is FileStamp func WithTime(t time.Time) Option { return WithFormattedTime(t, FileTimestamp) } +// WithFormattedTime sets a generator that creates a date and time for the supplied time with the supplied format. func WithFormattedTime(t time.Time, f string) Option { return func(c *config) { setGenerator(c, func() (string, error) { @@ -48,12 +56,15 @@ func WithFormattedTime(t time.Time, f string) Option { } } +// FileTimestamp is the default format for WithTimestampUTC and WithTimeUTC const FileTimestampNoTZ string = "2006-01-02_03-05-06" +// WithTimestampUTC sets a generator the creates a date and time for the current time in UTC without a timezone in the format. func WithTimestampUTC() Option { return WithTimeUTC(time.Now()) } +// WithTimeUTC sets a generate that creates a date and time for the supplied time in UTC without a timezone in the format. func WithTimeUTC(t time.Time) Option { return WithFormattedTime(t.UTC(), FileTimestampNoTZ) } diff --git a/options.go b/options.go index 6f122ce..e318446 100644 --- a/options.go +++ b/options.go @@ -17,32 +17,39 @@ func defaultConf() config { } } +// Option is an option for nomino type Option func(c *config) +// WithOriginal sets the original filename. +// This will be included in the generated name after the generated string and before the suffix. func WithOriginal(o string) Option { return func(c *config) { c.original = "_" + o } } +// WithPrefix sets a prefix for the generated name. func WithPrefix(p string) Option { return func(c *config) { c.prefix = p + "_" } } +// WithSuffix sets a suffix for the generated name. It will be included in the base name before the suffix. func WithSuffix(s string) Option { return func(c *config) { c.suffix = "_" + s } } +// WithoutExtension sets no extension for the generated filename. By default, it will be txt func WithoutExtension() Option { return func(c *config) { c.extension = "" } } +// WithExtension sets the extension for the generated filename. func WithExtension(ext string) Option { return func(c *config) { c.extension = "." + strings.TrimPrefix(ext, ".") From ce5f823d6401476c1d699fe716d6be65310ab37a Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Mon, 10 Mar 2025 13:48:11 -0500 Subject: [PATCH 07/11] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Export=20Generator,?= =?UTF-8?q?=20and=20switch=20to=20WithGenerator=20Option?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- generators.go | 64 ++++++++++++++++++++++++---------------------- generators_test.go | 37 ++++++++++++++------------- options.go | 2 +- 3 files changed, 53 insertions(+), 50 deletions(-) diff --git a/generators.go b/generators.go index 682c405..01242fe 100644 --- a/generators.go +++ b/generators.go @@ -6,10 +6,16 @@ import ( "github.com/google/uuid" ) -type generator func() (string, error) +// Generator is a function that returns the "random" portion of the returned filename. +// Technically, it doesn't necessarily need to be random, and could be based on time, or a counter, +// for example. +type Generator func() (string, error) -func setGenerator(c *config, g generator) { - c.generator = g +// WithGenerator sets the specified generator +func WithGenerator(g Generator) Option { + return func(c *config) { + c.generator = g + } } func uuidGen() (string, error) { @@ -20,51 +26,47 @@ func uuidGen() (string, error) { return u.String(), nil } -// WithUUID sets a generator that creates a UUIDv4 -func WithUUID() Option { - return func(c *config) { - setGenerator(c, uuidGen) - } +// UUID generates a UUIDv4. +func UUID() Generator { + return uuidGen } // FileTimestamp is the default format for WithTimestamp and WithTime const FileTimestamp string = "2006-01-02_03-05-06-0700" -// WithTimestamp sets a generator that creates a date and time for the current time. -// The format is FileStamp -func WithTimestamp() Option { - return WithTimestampFormat(FileTimestamp) +// Timestamp generates a a date and time for the current time. +// It is formatted accourding to FileTimestamp +func Timestamp() Generator { + return TimestampWithFormat(FileTimestamp) } -// WithTimestampFormat sets a generator the creates a date and time for the current time with the supplied format. -func WithTimestampFormat(f string) Option { - return WithFormattedTime(time.Now(), f) +// TimestampWithFormat generates a date and time for the current time with the supplied format. +func TimestampWithFormat(f string) Generator { + return FormattedTime(time.Now(), FileTimestamp) } -// WithTime sets a generator that creates a date and time for the supplied time. -// The format is FileStamp -func WithTime(t time.Time) Option { - return WithFormattedTime(t, FileTimestamp) +// Time generates a date and time for the supplied time. +// It is formatted accourding to FileTimestamp +func Time(t time.Time) Generator { + return FormattedTime(t, FileTimestamp) } -// WithFormattedTime sets a generator that creates a date and time for the supplied time with the supplied format. -func WithFormattedTime(t time.Time, f string) Option { - return func(c *config) { - setGenerator(c, func() (string, error) { - return t.Format(f), nil - }) +// FormattedTime generates a date and time for the supplied time with the supplied format. +func FormattedTime(t time.Time, f string) Generator { + return func() (string, error) { + return t.Format(f), nil } } // FileTimestamp is the default format for WithTimestampUTC and WithTimeUTC const FileTimestampNoTZ string = "2006-01-02_03-05-06" -// WithTimestampUTC sets a generator the creates a date and time for the current time in UTC without a timezone in the format. -func WithTimestampUTC() Option { - return WithTimeUTC(time.Now()) +// TimestampUTC generates a date and time for the current time in UTC without a timezone in the format. +func TimestampUTC() Generator { + return TimeUTC(time.Now()) } -// WithTimeUTC sets a generate that creates a date and time for the supplied time in UTC without a timezone in the format. -func WithTimeUTC(t time.Time) Option { - return WithFormattedTime(t.UTC(), FileTimestampNoTZ) +// TimeUTC generates a date and time for the supplied time in UTC without a timezone in the format. +func TimeUTC(t time.Time) Generator { + return FormattedTime(t.UTC(), FileTimestampNoTZ) } diff --git a/generators_test.go b/generators_test.go index 6f4cc2d..96759ff 100644 --- a/generators_test.go +++ b/generators_test.go @@ -9,11 +9,18 @@ import ( "github.com/stretchr/testify/assert" ) -func TestWithUUID(t *testing.T) { +func TestWithGenerator(t *testing.T) { + g := func() (string, error) { return "abc", nil } var c config - WithUUID()(&c) + WithGenerator(g)(&c) st, err := c.generator() assert.NoError(t, err) + assert.Equal(t, "abc", st) +} + +func TestUUID(t *testing.T) { + st, err := UUID()() + assert.NoError(t, err) _, parseErr := uuid.Parse(st) assert.NoError(t, parseErr) } @@ -24,38 +31,32 @@ func (badRead) Read([]byte) (int, error) { return 0, errors.New("sorry") } -func TestWithUUIDFail(t *testing.T) { - var c config +func TestUUIDFail(t *testing.T) { uuid.SetRand(badRead{}) defer uuid.SetRand(nil) - WithUUID()(&c) - _, err := c.generator() + + _, err := UUID()() assert.Equal(t, errors.New("sorry"), err) } -func TestWithTimestamp(t *testing.T) { - var c config - WithTimestamp()(&c) +func TestTimestamp(t *testing.T) { n := time.Now() - st, err := c.generator() + st, err := Timestamp()() assert.NoError(t, err) assert.Equal(t, n.Format(FileTimestamp), st) } -func TestWithTime(t *testing.T) { - var c config +func TestTime(t *testing.T) { d := time.Date(1986, time.March, 28, 12, 0, 0, 0, time.UTC) - WithTime(d)(&c) - st, err := c.generator() + + st, err := Time(d)() assert.NoError(t, err) assert.Equal(t, d.Format(FileTimestamp), st) } -func TestWithTimestampUTC(t *testing.T) { - var c config - WithTimestampUTC()(&c) +func TestTimestampUTC(t *testing.T) { n := time.Now() - st, err := c.generator() + st, err := TimestampUTC()() assert.NoError(t, err) assert.Equal(t, n.UTC().Format(FileTimestampNoTZ), st) } diff --git a/options.go b/options.go index e318446..7f93d57 100644 --- a/options.go +++ b/options.go @@ -7,7 +7,7 @@ type config struct { prefix string suffix string extension string - generator generator + generator Generator } func defaultConf() config { From 5c4e66d1443593b1446cd0b9d82be58cdd824cf3 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Mon, 10 Mar 2025 14:25:00 -0500 Subject: [PATCH 08/11] =?UTF-8?q?=E2=9C=A8=20Add=20MultiGenerator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- generators.go | 27 +++++++++++++++++++++++++++ generators_test.go | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/generators.go b/generators.go index 01242fe..c409988 100644 --- a/generators.go +++ b/generators.go @@ -1,6 +1,7 @@ package nomino import ( + "errors" "time" "github.com/google/uuid" @@ -18,6 +19,32 @@ func WithGenerator(g Generator) Option { } } +// ErrMissingGenerators is returned by a multi-generator if no generators are supplied. +var ErrMissingGenerators = errors.New("no generators supplied") + +func missingGen() (string, error) { + return "", ErrMissingGenerators +} + +// MultiGeneratorInOrder allows the use of multiple generators. Each new invokation will use the next generator in turn. +// If none are passed, the generator will always return ErrMissingGenerators. +func MultiGeneratorInOrder(gens ...Generator) Generator { + if len(gens) == 0 { + return missingGen + } + + if len(gens) == 1 { + return gens[0] + } + + var idx int + return func() (string, error) { + st, err := gens[idx]() + idx = (idx + 1) % len(gens) + return st, err + } +} + func uuidGen() (string, error) { u, err := uuid.NewRandom() if err != nil { diff --git a/generators_test.go b/generators_test.go index 96759ff..210181f 100644 --- a/generators_test.go +++ b/generators_test.go @@ -18,6 +18,51 @@ func TestWithGenerator(t *testing.T) { assert.Equal(t, "abc", st) } +func TestMultiGeneratorInOrder(t *testing.T) { + st1 := "abc" + st2 := "def" + er1 := errors.New("oops") + g1 := func() (string, error) { return st1, nil } + g2 := func() (string, error) { return st2, nil } + g3 := func() (string, error) { return "", er1 } + g := MultiGeneratorInOrder(g1, g2, g3) + st, err := g() + assert.NoError(t, err) + assert.Equal(t, st1, st) + st, err = g() + assert.NoError(t, err) + assert.Equal(t, st2, st) + st, err = g() + assert.Zero(t, st) + assert.ErrorIs(t, err, er1) + st, err = g() + assert.NoError(t, err) + assert.Equal(t, st1, st) +} + +func TestMultiGeneratorInOrderOne(t *testing.T) { + st1 := "abc" + g1 := func() (string, error) { return st1, nil } + g := MultiGeneratorInOrder(g1) + + st, err := g() + assert.NoError(t, err) + assert.Equal(t, st1, st) + st, err = g() + assert.NoError(t, err) + assert.Equal(t, st1, st) +} + +func TestMultiGeneratorInOrderMissing(t *testing.T) { + g := MultiGeneratorInOrder() + st, err := g() + assert.Zero(t, st) + assert.ErrorIs(t, err, ErrMissingGenerators) + st, err = g() + assert.Zero(t, st) + assert.ErrorIs(t, err, ErrMissingGenerators) +} + func TestUUID(t *testing.T) { st, err := UUID()() assert.NoError(t, err) From 7bd55036138ddc0e5549650eadd6cfb6f8cbc0cd Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Mon, 10 Mar 2025 14:52:50 -0500 Subject: [PATCH 09/11] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Export=20Config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.go | 20 ++++++++++++++++++++ config_test.go | 22 ++++++++++++++++++++++ generators.go | 2 +- generators_test.go | 2 +- options.go | 29 +++++++---------------------- options_test.go | 19 +++++-------------- 6 files changed, 56 insertions(+), 38 deletions(-) create mode 100644 config.go create mode 100644 config_test.go diff --git a/config.go b/config.go new file mode 100644 index 0000000..dc6af3d --- /dev/null +++ b/config.go @@ -0,0 +1,20 @@ +package nomino + +type Config struct { + original string + prefix string + suffix string + extension string + generator Generator +} + +func NewConfig(options ...Option) Config { + conf := Config{ + extension: ".txt", + generator: uuidGen, + } + for _, opt := range options { + opt(&conf) + } + return conf +} diff --git a/config_test.go b/config_test.go new file mode 100644 index 0000000..bb88b53 --- /dev/null +++ b/config_test.go @@ -0,0 +1,22 @@ +package nomino + +import ( + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" +) + +func TestNewConf(t *testing.T) { + c := NewConfig() + assert.Equal(t, ".txt", c.extension) + st, _ := c.generator() + _, parseErr := uuid.Parse(st) + assert.NoError(t, parseErr) +} + +func TestNewConfWithOpts(t *testing.T) { + c := NewConfig(WithoutExtension(), WithPrefix("foobar")) + assert.Equal(t, "", c.extension) + assert.Equal(t, "foobar_", c.prefix) +} diff --git a/generators.go b/generators.go index c409988..fdbdabd 100644 --- a/generators.go +++ b/generators.go @@ -14,7 +14,7 @@ type Generator func() (string, error) // WithGenerator sets the specified generator func WithGenerator(g Generator) Option { - return func(c *config) { + return func(c *Config) { c.generator = g } } diff --git a/generators_test.go b/generators_test.go index 210181f..7282656 100644 --- a/generators_test.go +++ b/generators_test.go @@ -11,7 +11,7 @@ import ( func TestWithGenerator(t *testing.T) { g := func() (string, error) { return "abc", nil } - var c config + var c Config WithGenerator(g)(&c) st, err := c.generator() assert.NoError(t, err) diff --git a/options.go b/options.go index 7f93d57..fa6c81c 100644 --- a/options.go +++ b/options.go @@ -2,56 +2,41 @@ package nomino import "strings" -type config struct { - original string - prefix string - suffix string - extension string - generator Generator -} - -func defaultConf() config { - return config{ - extension: ".txt", - generator: uuidGen, - } -} - -// Option is an option for nomino -type Option func(c *config) +// Option sets configuration parameters for Config. +type Option func(c *Config) // WithOriginal sets the original filename. // This will be included in the generated name after the generated string and before the suffix. func WithOriginal(o string) Option { - return func(c *config) { + return func(c *Config) { c.original = "_" + o } } // WithPrefix sets a prefix for the generated name. func WithPrefix(p string) Option { - return func(c *config) { + return func(c *Config) { c.prefix = p + "_" } } // WithSuffix sets a suffix for the generated name. It will be included in the base name before the suffix. func WithSuffix(s string) Option { - return func(c *config) { + return func(c *Config) { c.suffix = "_" + s } } // WithoutExtension sets no extension for the generated filename. By default, it will be txt func WithoutExtension() Option { - return func(c *config) { + return func(c *Config) { c.extension = "" } } // WithExtension sets the extension for the generated filename. func WithExtension(ext string) Option { - return func(c *config) { + return func(c *Config) { c.extension = "." + strings.TrimPrefix(ext, ".") } } diff --git a/options_test.go b/options_test.go index ea9ed47..12a2bb5 100644 --- a/options_test.go +++ b/options_test.go @@ -3,47 +3,38 @@ package nomino import ( "testing" - "github.com/google/uuid" "github.com/stretchr/testify/assert" ) -func TestDefaultConf(t *testing.T) { - c := defaultConf() - assert.Equal(t, ".txt", c.extension) - st, _ := c.generator() - _, parseErr := uuid.Parse(st) - assert.NoError(t, parseErr) -} - func TestWithOriginal(t *testing.T) { - var c config + var c Config name := "foobar" WithOriginal(name)(&c) assert.Equal(t, "_"+name, c.original) } func TestWithPrefix(t *testing.T) { - var c config + var c Config pref := "draft" WithPrefix(pref)(&c) assert.Equal(t, pref+"_", c.prefix) } func TestWithSuffix(t *testing.T) { - var c config + var c Config suff := "out" WithSuffix(suff)(&c) assert.Equal(t, "_"+suff, c.suffix) } func TestWithoutExtension(t *testing.T) { - c := config{extension: ".foobar"} + c := Config{extension: ".foobar"} WithoutExtension()(&c) assert.Equal(t, "", c.extension) } func TestWithExtension(t *testing.T) { - var c config + var c Config ext := "yaml" WithExtension(ext)(&c) assert.Equal(t, "."+ext, c.extension) From 7126ef97a4e24eeeaa69be9b506870128662857e Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Mon, 10 Mar 2025 14:53:59 -0500 Subject: [PATCH 10/11] =?UTF-8?q?=E2=9C=A8=20Add=20Make=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is the important one --- make.go | 15 +++++++++++++++ make_test.go | 23 +++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 make.go create mode 100644 make_test.go diff --git a/make.go b/make.go new file mode 100644 index 0000000..3cd537a --- /dev/null +++ b/make.go @@ -0,0 +1,15 @@ +package nomino + +import "fmt" + +// Make generates a random filename. The behavior can be controlled by specifying Options +// In general, the final filename will be [prefix]_[generated_string]_[original_filename]_[suffix].[extension]. +// If the name generator returns an error (generally, it shouldn't), that will be returned instead. +func Make(conf Config) (string, error) { + name, err := conf.generator() + if err != nil { + return "", err + } + + return fmt.Sprintf("%s%s%s%s%s", conf.prefix, name, conf.original, conf.suffix, conf.original), nil +} diff --git a/make_test.go b/make_test.go new file mode 100644 index 0000000..e9cc006 --- /dev/null +++ b/make_test.go @@ -0,0 +1,23 @@ +package nomino + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMake(t *testing.T) { + conf := NewConfig(WithGenerator(func() (string, error) { return "abc", nil })) + st, err := Make(conf) + assert.NoError(t, err) + assert.Equal(t, "abc", st) +} + +func TestMakeErr(t *testing.T) { + retErr := errors.New("oops") + conf := NewConfig(WithGenerator(func() (string, error) { return "foobar", retErr })) + st, err := Make(conf) + assert.Zero(t, st) + assert.ErrorIs(t, err, retErr) +} From 899fbedd9a14c3e6487147c0d1d06ce6f1ddaa40 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Mon, 10 Mar 2025 17:03:52 -0500 Subject: [PATCH 11/11] =?UTF-8?q?=F0=9F=94=96=20Version=200.0.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 14 ++++++++++++++ LICENSE | 21 +++++++++++++++++++++ README.md | 11 +++++++++++ 3 files changed, 46 insertions(+) create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 README.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b47e994 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,14 @@ +# Changelog + +## [0.0.1] - 2025-03-10 + +Initial Release! Hope you like it! + +### Added + +- nomino.Make +- nomino.Config +- nomino.Generator + + We needs more of these until I'm ready +- Lots of tests! + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9f61e2e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2025, Dan Jones . + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..e364bfc --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# nomino - A filename generator + +The purpose of nomino is to generate (probably random) filenames, for example, if you want to save an uploaded file to storage under a new name. + +It takes a lot of inspiration (although no actual code) from [Onym](https://github.com/Blaspsoft/onym). + +## TODO + +I'll fill this out more in depth later. + +For now, add it to a new project, and run `go doc codeberg.org/danjones000/nomino`