From 61a51996992facd1f115bff535f8d101a7a8a35b Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Fri, 14 Mar 2025 17:39:42 -0500 Subject: [PATCH 01/29] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20Timestamp?= =?UTF-8?q?=20Generator=20to=20single=20function=20with=20options?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gen_ts.go | 59 +++++++++++++++++++++++++++++++++++++++++ gen_ts_examples_test.go | 38 ++++++++++++++++++++++++++ gen_ts_test.go | 15 +++++++++++ generators.go | 42 ----------------------------- generators_test.go | 26 +----------------- 5 files changed, 113 insertions(+), 67 deletions(-) create mode 100644 gen_ts.go create mode 100644 gen_ts_examples_test.go create mode 100644 gen_ts_test.go diff --git a/gen_ts.go b/gen_ts.go new file mode 100644 index 0000000..4d0e13d --- /dev/null +++ b/gen_ts.go @@ -0,0 +1,59 @@ +package nomino + +import "time" + +// FileTimestamp is the default format for WithTimestamp and WithTime +const FileTimestamp string = "2006-01-02T15-04-05-0700" + +// FileTimestampNoTZ is the default format for WithTimestampUTC and WithTimeUTC +const FileTimestampNoTZ string = "2006-01-02T15-04-05" + +type timestampConf struct { + format string + ts time.Time + utc bool +} + +// TimestampOption provides options for the Timestamp Generator +type TimestampOption func(c *timestampConf) + +// Timestamp generates a a date and time. By default, it uses the current time, and will. +// be formatted accourding to FileTimestamp +func Timestamp(opts ...TimestampOption) Generator { + c := timestampConf{format: FileTimestamp, ts: time.Now()} + for _, opt := range opts { + opt(&c) + } + + if c.utc { + c.ts = c.ts.UTC() + } + + return func(*Config) (string, error) { + return c.ts.Format(c.format), nil + } +} + +// TSFormat sets the format for the generated name. +// Consult time.Time.Format for details on the format. +func TSFormat(format string) TimestampOption { + return func(c *timestampConf) { + c.format = format + } +} + +// TSTime sets the time for the generated name. +// By default, it uses the current time. +func TSTime(t time.Time) TimestampOption { + return func(c *timestampConf) { + c.ts = t + } +} + +// TSUTC uses the time in UTC, while also stripping the timezone from the format. +func TSUTC() TimestampOption { + return func(c *timestampConf) { + c.utc = true + c.format = FileTimestampNoTZ + } +} diff --git a/gen_ts_examples_test.go b/gen_ts_examples_test.go new file mode 100644 index 0000000..0dcc0b6 --- /dev/null +++ b/gen_ts_examples_test.go @@ -0,0 +1,38 @@ +package nomino_test + +import ( + "fmt" + "time" + + "codeberg.org/danjones000/nomino" +) + +func ExampleTimestamp_withTime() { + tz, _ := time.LoadLocation("America/New_York") + ts := time.Date(2009, time.January, 20, 12, 5, 0, 0, tz) + gen := nomino.Timestamp(nomino.TSTime(ts)) + conf := nomino.NewConfig(nomino.WithGenerator(gen)) + s, _ := nomino.Make(conf) + fmt.Println(s) + // Output: 2009-01-20T12-05-00-0500.txt +} + +func ExampleTimestamp_withFormat() { + tz, _ := time.LoadLocation("America/New_York") + ts := time.Date(2009, time.January, 20, 12, 5, 0, 0, tz) + gen := nomino.Timestamp(nomino.TSTime(ts), nomino.TSFormat("2006#01#02<>15|04|05-0700")) + conf := nomino.NewConfig(nomino.WithGenerator(gen)) + s, _ := nomino.Make(conf) + fmt.Println(s) + // Output: 2009#01#20<>12|05|00-0500.txt +} + +func ExampleTimestamp_withUTC() { + tz, _ := time.LoadLocation("America/New_York") + ts := time.Date(2009, time.January, 20, 12, 5, 0, 0, tz) + gen := nomino.Timestamp(nomino.TSTime(ts), nomino.TSUTC()) + conf := nomino.NewConfig(nomino.WithGenerator(gen)) + s, _ := nomino.Make(conf) + fmt.Println(s) + // Output: 2009-01-20T17-05-00.txt +} diff --git a/gen_ts_test.go b/gen_ts_test.go new file mode 100644 index 0000000..2997702 --- /dev/null +++ b/gen_ts_test.go @@ -0,0 +1,15 @@ +package nomino + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestTimestamp(t *testing.T) { + n := time.Now() + st, err := Timestamp()(nil) + assert.NoError(t, err) + assert.Equal(t, n.Format(FileTimestamp), st) +} diff --git a/generators.go b/generators.go index 8685662..a1b2b52 100644 --- a/generators.go +++ b/generators.go @@ -8,7 +8,6 @@ import ( "fmt" "hash" "strconv" - "time" "github.com/google/uuid" "github.com/gosimple/slug" @@ -65,46 +64,6 @@ func UUID() Generator { return uuidGen } -// FileTimestamp is the default format for WithTimestamp and WithTime -const FileTimestamp string = "2006-01-02_03-05-06-0700" - -// Timestamp generates a a date and time for the current time. -// It is formatted accourding to FileTimestamp -func Timestamp() Generator { - return TimestampWithFormat(FileTimestamp) -} - -// TimestampWithFormat generates a date and time for the current time with the supplied format. -func TimestampWithFormat(f string) Generator { - return FormattedTime(time.Now(), 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) -} - -// FormattedTime generates a date and time for the supplied time with the supplied format. -func FormattedTime(t time.Time, f string) Generator { - return func(*Config) (string, error) { - return t.Format(f), nil - } -} - -// FileTimestampNoTZ is the default format for WithTimestampUTC and WithTimeUTC -const FileTimestampNoTZ string = "2006-01-02_03-05-06" - -// 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()) -} - -// 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) -} - // Incremental generates a name that is a series of integers starting at 0 func Incremental() Generator { return IncrementalWithStartAndStep(0, 1) @@ -136,7 +95,6 @@ func IncrementalWithStep(step int) Generator { func IncrementalFormatWithStep(step int, format string) Generator { return IncrementalFormatWithStartAndStep(0, step, format) } - func incrementalHelper(start, step int, cb func(int) string) Generator { next := start return func(*Config) (string, error) { diff --git a/generators_test.go b/generators_test.go index c6b3bbe..330b169 100644 --- a/generators_test.go +++ b/generators_test.go @@ -2,9 +2,7 @@ package nomino import ( "errors" - "fmt" "testing" - "time" "github.com/google/uuid" "github.com/stretchr/testify/assert" @@ -85,28 +83,6 @@ func TestUUIDFail(t *testing.T) { assert.Equal(t, errors.New("sorry"), err) } -func TestTimestamp(t *testing.T) { - n := time.Now() - st, err := Timestamp()(nil) - assert.NoError(t, err) - assert.Equal(t, n.Format(FileTimestamp), st) -} - -func TestTime(t *testing.T) { - d := time.Date(1986, time.March, 28, 12, 0, 0, 0, time.UTC) - - st, err := Time(d)(nil) - assert.NoError(t, err) - assert.Equal(t, d.Format(FileTimestamp), st) -} - -func TestTimestampUTC(t *testing.T) { - n := time.Now() - st, err := TimestampUTC()(nil) - assert.NoError(t, err) - assert.Equal(t, n.UTC().Format(FileTimestampNoTZ), st) -} - func TestSlugMissingFilename(t *testing.T) { conf := NewConfig(WithGenerator(Slug())) st, err := conf.generator(&conf) @@ -138,6 +114,6 @@ func TestHashMissingOriginal(t *testing.T) { } func TestHashTypeStringer(t *testing.T) { - s := fmt.Sprintf("%s", MD5) + s := MD5.String() assert.Equal(t, "MD5", s) } From 63d538d889fc8bfd65466c51b8db82e1f3b85ba3 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Fri, 14 Mar 2025 18:09:07 -0500 Subject: [PATCH 02/29] =?UTF-8?q?=F0=9F=9A=9A=20Rename=20TS*=20to=20Timest?= =?UTF-8?q?amp?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gen_ts.go | 12 ++++++------ gen_ts_examples_test.go | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/gen_ts.go b/gen_ts.go index 4d0e13d..abb4181 100644 --- a/gen_ts.go +++ b/gen_ts.go @@ -34,24 +34,24 @@ func Timestamp(opts ...TimestampOption) Generator { } } -// TSFormat sets the format for the generated name. +// TimestampFormat sets the format for the generated name. // Consult time.Time.Format for details on the format. -func TSFormat(format string) TimestampOption { +func TimestampFormat(format string) TimestampOption { return func(c *timestampConf) { c.format = format } } -// TSTime sets the time for the generated name. +// TimestampTime sets the time for the generated name. // By default, it uses the current time. -func TSTime(t time.Time) TimestampOption { +func TimestampTime(t time.Time) TimestampOption { return func(c *timestampConf) { c.ts = t } } -// TSUTC uses the time in UTC, while also stripping the timezone from the format. -func TSUTC() TimestampOption { +// TimestampUTC uses the time in UTC, while also stripping the timezone from the format. +func TimestampUTC() TimestampOption { return func(c *timestampConf) { c.utc = true c.format = FileTimestampNoTZ diff --git a/gen_ts_examples_test.go b/gen_ts_examples_test.go index 0dcc0b6..527728d 100644 --- a/gen_ts_examples_test.go +++ b/gen_ts_examples_test.go @@ -10,7 +10,7 @@ import ( func ExampleTimestamp_withTime() { tz, _ := time.LoadLocation("America/New_York") ts := time.Date(2009, time.January, 20, 12, 5, 0, 0, tz) - gen := nomino.Timestamp(nomino.TSTime(ts)) + gen := nomino.Timestamp(nomino.TimestampTime(ts)) conf := nomino.NewConfig(nomino.WithGenerator(gen)) s, _ := nomino.Make(conf) fmt.Println(s) @@ -20,7 +20,7 @@ func ExampleTimestamp_withTime() { func ExampleTimestamp_withFormat() { tz, _ := time.LoadLocation("America/New_York") ts := time.Date(2009, time.January, 20, 12, 5, 0, 0, tz) - gen := nomino.Timestamp(nomino.TSTime(ts), nomino.TSFormat("2006#01#02<>15|04|05-0700")) + gen := nomino.Timestamp(nomino.TimestampTime(ts), nomino.TimestampFormat("2006#01#02<>15|04|05-0700")) conf := nomino.NewConfig(nomino.WithGenerator(gen)) s, _ := nomino.Make(conf) fmt.Println(s) @@ -30,7 +30,7 @@ func ExampleTimestamp_withFormat() { func ExampleTimestamp_withUTC() { tz, _ := time.LoadLocation("America/New_York") ts := time.Date(2009, time.January, 20, 12, 5, 0, 0, tz) - gen := nomino.Timestamp(nomino.TSTime(ts), nomino.TSUTC()) + gen := nomino.Timestamp(nomino.TimestampTime(ts), nomino.TimestampUTC()) conf := nomino.NewConfig(nomino.WithGenerator(gen)) s, _ := nomino.Make(conf) fmt.Println(s) From 586fe4f1deee3de760c61841b714dd7880380bc0 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Fri, 14 Mar 2025 18:33:30 -0500 Subject: [PATCH 03/29] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20Increment?= =?UTF-8?q?al=20Generator=20to=20single=20function=20with=20options?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gen_int.go | 55 ++++++++++++++++ gen_int_examples_test.go | 121 ++++++++++++++++++++++++++++++++++ generators.go | 55 ---------------- generators_examples_test.go | 127 ------------------------------------ 4 files changed, 176 insertions(+), 182 deletions(-) create mode 100644 gen_int.go create mode 100644 gen_int_examples_test.go diff --git a/gen_int.go b/gen_int.go new file mode 100644 index 0000000..aeb6076 --- /dev/null +++ b/gen_int.go @@ -0,0 +1,55 @@ +package nomino + +import ( + "fmt" + "strconv" +) + +type incConf struct { + start int + step int + cb func(int) string +} + +// IncrementalOption sets an option for the Incremental Generator +type IncrementalOption func(c *incConf) + +// Incremental generates a name that is a series of integers. +// By default it begins at 0 and increments by 1 each time. +func Incremental(opts ...IncrementalOption) Generator { + c := incConf{step: 1, cb: strconv.Itoa} + for _, opt := range opts { + opt(&c) + } + + next := c.start + return func(*Config) (string, error) { + out := c.cb(next) + next += c.step + return out, nil + } +} + +// IncrementalStart sets the starting integer for Incremental +func IncrementalStart(start int) IncrementalOption { + return func(c *incConf) { + c.start = start + } +} + +// IncrementalStepsets the step by which Incremental increases with each invocation. +func IncrementalStep(step int) IncrementalOption { + return func(c *incConf) { + c.step = step + } +} + +// IncrementalFormatsets the format for the number generated by Incremental. +// It will be formatted with Printf. This is mostly likely useful with a format like "%02d" +func IncrementalFormat(format string) IncrementalOption { + return func(c *incConf) { + c.cb = func(i int) string { + return fmt.Sprintf(format, i) + } + } +} diff --git a/gen_int_examples_test.go b/gen_int_examples_test.go new file mode 100644 index 0000000..f3e61c7 --- /dev/null +++ b/gen_int_examples_test.go @@ -0,0 +1,121 @@ +package nomino_test + +import ( + "fmt" + + "codeberg.org/danjones000/nomino" +) + +func ExampleIncremental() { + conf := nomino.NewConfig( + nomino.WithPrefix("foo"), + nomino.WithGenerator(nomino.Incremental()), + ) + + str, _ := nomino.Make(conf) + fmt.Println(str) + + str, _ = nomino.Make(conf) + fmt.Println(str) + + str, _ = nomino.Make(conf) + fmt.Println(str) + + // Output: + // foo_0.txt + // foo_1.txt + // foo_2.txt +} + +func ExampleIncrementalStart() { + conf := nomino.NewConfig( + nomino.WithPrefix("foo"), + nomino.WithGenerator(nomino.Incremental( + nomino.IncrementalStart(42), + )), + ) + + str, _ := nomino.Make(conf) + fmt.Println(str) + + str, _ = nomino.Make(conf) + fmt.Println(str) + + str, _ = nomino.Make(conf) + fmt.Println(str) + + // Output: + // foo_42.txt + // foo_43.txt + // foo_44.txt +} + +func ExampleIncrementalStep() { + conf := nomino.NewConfig( + nomino.WithPrefix("foo"), + nomino.WithGenerator(nomino.Incremental( + nomino.IncrementalStep(2), + )), + ) + + str, _ := nomino.Make(conf) + fmt.Println(str) + + str, _ = nomino.Make(conf) + fmt.Println(str) + + str, _ = nomino.Make(conf) + fmt.Println(str) + + // Output: + // foo_0.txt + // foo_2.txt + // foo_4.txt +} + +func ExampleIncremental_withStartAndStep() { + conf := nomino.NewConfig( + nomino.WithPrefix("foo"), + nomino.WithGenerator(nomino.Incremental( + nomino.IncrementalStart(42), + nomino.IncrementalStep(2), + )), + ) + + str, _ := nomino.Make(conf) + fmt.Println(str) + + str, _ = nomino.Make(conf) + fmt.Println(str) + + str, _ = nomino.Make(conf) + fmt.Println(str) + + // Output: + // foo_42.txt + // foo_44.txt + // foo_46.txt +} + +func ExampleIncrementalFormat() { + conf := nomino.NewConfig( + nomino.WithPrefix("foo"), + nomino.WithGenerator(nomino.Incremental( + nomino.IncrementalFormat("%03d"), + )), + ) + + str, _ := nomino.Make(conf) + fmt.Println(str) + + str, _ = nomino.Make(conf) + fmt.Println(str) + + str, _ = nomino.Make(conf) + fmt.Println(str) + + // Output: + // foo_000.txt + // foo_001.txt + // foo_002.txt +} diff --git a/generators.go b/generators.go index a1b2b52..2a12236 100644 --- a/generators.go +++ b/generators.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "hash" - "strconv" "github.com/google/uuid" "github.com/gosimple/slug" @@ -64,60 +63,6 @@ func UUID() Generator { return uuidGen } -// Incremental generates a name that is a series of integers starting at 0 -func Incremental() Generator { - return IncrementalWithStartAndStep(0, 1) -} - -// IncrementalFormat generates a name that is a series of integers starting at 0, formatted with Printf -// This is mostly likely useful with a format like "%02d" -func IncrementalFormat(format string) Generator { - return IncrementalFormatWithStartAndStep(0, 1, format) -} - -// IncrementalWithStart generates a name that is a series of integers starting at the specified number -func IncrementalWithStart(start int) Generator { - return IncrementalWithStartAndStep(start, 1) -} - -// IncrementalFormatWithStart generates a name that is a series of integers starting at the specified number, formatted with Printf -func IncrementalFormatWithStart(start int, format string) Generator { - return IncrementalFormatWithStartAndStep(start, 1, format) -} - -// IncrementalWithStep generates a name that is a series of integers, starting at 0, and increasing the specified number each time -func IncrementalWithStep(step int) Generator { - return IncrementalWithStartAndStep(0, step) -} - -// IncrementalFormatWithStep generates a name that is a series of integers, starting at 0, and increasing the specified number each time, -// formatted with Printf -func IncrementalFormatWithStep(step int, format string) Generator { - return IncrementalFormatWithStartAndStep(0, step, format) -} -func incrementalHelper(start, step int, cb func(int) string) Generator { - next := start - return func(*Config) (string, error) { - out := cb(next) - next += step - return out, nil - } -} - -// InrementalWithStartAndStep generates a name that is a series of integers, starting at the specified number, -// and increasing the specified step each time -func IncrementalWithStartAndStep(start, step int) Generator { - return incrementalHelper(start, step, strconv.Itoa) -} - -// IncrementalFormatWithStartAndStep generates a name that is a series of integers, starting at the specified number, -// and increasing the specified step each time, formatted with Printf -func IncrementalFormatWithStartAndStep(start, step int, format string) Generator { - return incrementalHelper(start, step, func(i int) string { - return fmt.Sprintf(format, i) - }) -} - // ErrMissingOriginal is the error returned by Slug if there is no filename var ErrMissingOriginal = errors.New("missing original filename") diff --git a/generators_examples_test.go b/generators_examples_test.go index 11cfae2..85563f9 100644 --- a/generators_examples_test.go +++ b/generators_examples_test.go @@ -19,133 +19,6 @@ func ExampleWithGenerator_custom_generator() { // hello } -func ExampleIncremental() { - conf := NewConfig(WithPrefix("foo"), WithGenerator(Incremental())) - - str, _ := Make(conf) - fmt.Println(str) - - str, _ = Make(conf) - fmt.Println(str) - - str, _ = Make(conf) - fmt.Println(str) - - // Output: - // foo_0.txt - // foo_1.txt - // foo_2.txt -} - -func ExampleIncrementalWithStart() { - conf := NewConfig(WithPrefix("foo"), WithGenerator(IncrementalWithStart(42))) - - str, _ := Make(conf) - fmt.Println(str) - - str, _ = Make(conf) - fmt.Println(str) - - str, _ = Make(conf) - fmt.Println(str) - - // Output: - // foo_42.txt - // foo_43.txt - // foo_44.txt -} - -func ExampleIncrementalWithStep() { - conf := NewConfig(WithPrefix("foo"), WithGenerator(IncrementalWithStep(2))) - - str, _ := Make(conf) - fmt.Println(str) - - str, _ = Make(conf) - fmt.Println(str) - - str, _ = Make(conf) - fmt.Println(str) - - // Output: - // foo_0.txt - // foo_2.txt - // foo_4.txt -} - -func ExampleIncrementalWithStartAndStep() { - conf := NewConfig(WithPrefix("foo"), WithGenerator(IncrementalWithStartAndStep(42, 2))) - - str, _ := Make(conf) - fmt.Println(str) - - str, _ = Make(conf) - fmt.Println(str) - - str, _ = Make(conf) - fmt.Println(str) - - // Output: - // foo_42.txt - // foo_44.txt - // foo_46.txt -} - -func ExampleIncrementalFormat() { - conf := NewConfig( - WithPrefix("foo"), - WithGenerator(IncrementalFormat("%03d")), - ) - - str, _ := Make(conf) - fmt.Println(str) - - str, _ = Make(conf) - fmt.Println(str) - - str, _ = Make(conf) - fmt.Println(str) - - // Output: - // foo_000.txt - // foo_001.txt - // foo_002.txt -} - -func ExampleIncrementalFormatWithStart() { - conf := NewConfig( - WithPrefix("foo"), - WithGenerator(IncrementalFormatWithStart(9, "%02d")), - ) - - str, _ := Make(conf) - fmt.Println(str) - - str, _ = Make(conf) - fmt.Println(str) - - // Output: - // foo_09.txt - // foo_10.txt -} - -func ExampleIncrementalFormatWithStep() { - conf := NewConfig( - WithPrefix("foo"), - WithGenerator(IncrementalFormatWithStep(10, "%02d")), - ) - - str, _ := Make(conf) - fmt.Println(str) - - str, _ = Make(conf) - fmt.Println(str) - - // Output: - // foo_00.txt - // foo_10.txt -} - func ExampleSlug() { conf := NewConfig(WithGenerator(Slug()), WithOriginal("My name is Jimmy")) str, _ := Make(conf) From 1d0f2238b3a9e8ea99afbefe12039dbc8deac814 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Fri, 14 Mar 2025 19:50:24 -0500 Subject: [PATCH 04/29] =?UTF-8?q?=F0=9F=9A=9A=20Move=20Slug/Hash=20Generat?= =?UTF-8?q?ors=20to=20own=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gen_file.go | 81 +++++++++++++++++++++++++++++++++++++ gen_file_examples_test.go | 66 ++++++++++++++++++++++++++++++ gen_file_test.go | 42 +++++++++++++++++++ generators.go | 77 ----------------------------------- generators_examples_test.go | 46 --------------------- generators_test.go | 35 ---------------- hashtype_string.go | 8 ++-- 7 files changed, 193 insertions(+), 162 deletions(-) create mode 100644 gen_file.go create mode 100644 gen_file_examples_test.go create mode 100644 gen_file_test.go diff --git a/gen_file.go b/gen_file.go new file mode 100644 index 0000000..ef239cb --- /dev/null +++ b/gen_file.go @@ -0,0 +1,81 @@ +package nomino + +import ( + "crypto/md5" + "crypto/sha1" + "crypto/sha256" + "errors" + "fmt" + "hash" + + "github.com/gosimple/slug" +) + +// ErrMissingOriginal is the error returned by Slug if there is no filename +var ErrMissingOriginal = errors.New("missing original filename") + +func getOriginal(c *Config) (string, error) { + if c.original == "" { + return "", ErrMissingOriginal + } + name := c.original + c.original = "" + return name, nil +} + +// Slug generates a name from the original filename. +// When this is used, the original filename will be removed from the final filename. +// If a language is specified, that may affect the resulting slug. +func Slug(lang ...string) Generator { + ret := slug.Make + if len(lang) > 0 { + ret = func(in string) string { + return slug.MakeLang(in, lang[0]) + } + } + return func(c *Config) (string, error) { + name, err := getOriginal(c) + return ret(name), err + } +} + +// HashingFunc is a function that generates a hash.Hash +type HashingFunc func() hash.Hash + +//go:generate stringer -type=HashType -trimprefix=Hash + +// HashType represents a particular hashing algorithm +type HashType uint8 + +const ( + HashMD5 HashType = iota + 1 + HashSHA1 + HashSHA256 +) + +// ErrInvalidHashType is returned by the Hash generator when an invalid HashType is passed +var ErrInvalidHashType = errors.New("invalid hash type") + +var hashMap = map[HashType]HashingFunc{ + HashMD5: md5.New, + HashSHA1: sha1.New, + HashSHA256: sha256.New, +} + +// Hash generates a name from a hash of the filename. +// When this is used, the original filename will be removed from the final filename. +func Hash(t HashType) Generator { + f, ok := hashMap[t] + return func(c *Config) (string, error) { + if !ok { + return "", fmt.Errorf("%w: %s", ErrInvalidHashType, t) + } + name, err := getOriginal(c) + if err != nil { + return "", err + } + hs := f() + hs.Write([]byte(name)) + return fmt.Sprintf("%x", hs.Sum(nil)), nil + } +} diff --git a/gen_file_examples_test.go b/gen_file_examples_test.go new file mode 100644 index 0000000..3d8036f --- /dev/null +++ b/gen_file_examples_test.go @@ -0,0 +1,66 @@ +package nomino_test + +import ( + "fmt" + + "codeberg.org/danjones000/nomino" +) + +func ExampleSlug() { + conf := nomino.NewConfig( + nomino.WithOriginal("My name is Jimmy"), + nomino.WithGenerator(nomino.Slug()), + ) + str, _ := nomino.Make(conf) + fmt.Println(str) + + // Output: my-name-is-jimmy.txt +} + +func ExampleSlug_withLang() { + conf := nomino.NewConfig( + nomino.WithOriginal("Diese & Dass"), + nomino.WithGenerator(nomino.Slug("de")), + ) + + str, _ := nomino.Make(conf) + fmt.Println(str) + + // Output: diese-und-dass.txt +} + +func ExampleHash_mD5() { + conf := nomino.NewConfig( + nomino.WithOriginal("foobar"), + nomino.WithGenerator( + nomino.Hash(nomino.HashMD5), + ), + ) + str, _ := nomino.Make(conf) + fmt.Println(str) + // Output: 3858f62230ac3c915f300c664312c63f.txt +} + +func ExampleHash_sHA1() { + conf := nomino.NewConfig( + nomino.WithOriginal("foobar"), + nomino.WithGenerator( + nomino.Hash(nomino.HashSHA1), + ), + ) + str, _ := nomino.Make(conf) + fmt.Println(str) + // Output: 8843d7f92416211de9ebb963ff4ce28125932878.txt +} + +func ExampleHash_sHA256() { + conf := nomino.NewConfig( + nomino.WithOriginal("foobar"), + nomino.WithGenerator( + nomino.Hash(nomino.HashSHA256), + ), + ) + str, _ := nomino.Make(conf) + fmt.Println(str) + // Output: c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2.txt +} diff --git a/gen_file_test.go b/gen_file_test.go new file mode 100644 index 0000000..0d03d16 --- /dev/null +++ b/gen_file_test.go @@ -0,0 +1,42 @@ +package nomino + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestSlugMissingFilename(t *testing.T) { + conf := NewConfig(WithGenerator(Slug())) + st, err := conf.generator(&conf) + assert.Zero(t, st) + assert.ErrorIs(t, err, ErrMissingOriginal) +} + +func TestSlugRemovesOriginal(t *testing.T) { + conf := NewConfig(WithGenerator(Slug()), WithOriginal("Hello, World")) + st, err := conf.generator(&conf) + assert.Zero(t, conf.original) + assert.Equal(t, "hello-world", st) + assert.NoError(t, err) +} + +func TestHashBadHash(t *testing.T) { + conf := NewConfig(WithOriginal("foobar"), WithGenerator(Hash(0))) + st, err := conf.generator(&conf) + assert.Equal(t, "", st) + assert.ErrorIs(t, err, ErrInvalidHashType) + assert.ErrorContains(t, err, "invalid hash type: HashType(0)") +} + +func TestHashMissingOriginal(t *testing.T) { + conf := NewConfig(WithGenerator(Hash(HashMD5))) + st, err := conf.generator(&conf) + assert.Equal(t, "", st) + assert.ErrorIs(t, err, ErrMissingOriginal) +} + +func TestHashTypeStringer(t *testing.T) { + s := HashMD5.String() + assert.Equal(t, "MD5", s) +} diff --git a/generators.go b/generators.go index 2a12236..25cd36e 100644 --- a/generators.go +++ b/generators.go @@ -1,15 +1,9 @@ package nomino import ( - "crypto/md5" - "crypto/sha1" - "crypto/sha256" "errors" - "fmt" - "hash" "github.com/google/uuid" - "github.com/gosimple/slug" ) // Generator is a function that returns the "random" portion of the returned filename. @@ -62,74 +56,3 @@ func uuidGen(*Config) (string, error) { func UUID() Generator { return uuidGen } - -// ErrMissingOriginal is the error returned by Slug if there is no filename -var ErrMissingOriginal = errors.New("missing original filename") - -func getOriginal(c *Config) (string, error) { - if c.original == "" { - return "", ErrMissingOriginal - } - name := c.original - c.original = "" - return name, nil -} - -// Slug generates a name from the original filename. -// When this is used, the original filename will be removed from the final filename. -func Slug() Generator { - return func(c *Config) (string, error) { - name, err := getOriginal(c) - return slug.Make(name), err - } -} - -// SlugWithLang generates a name from the original filename, accounting for the given language. -// When this is used, the original filename will be removed from the final filename. -func SlugWithLang(lang string) Generator { - return func(c *Config) (string, error) { - name, err := getOriginal(c) - return slug.MakeLang(name, lang), err - } -} - -// HashingFunc is a function that generates a hash.Hash -type HashingFunc func() hash.Hash - -//go:generate stringer -type=HashType - -// HashType represents a particular hashing algorithm -type HashType uint8 - -const ( - MD5 HashType = iota + 1 - SHA1 - SHA256 -) - -// ErrInvalidHashType is returned by the Hash generator when an invalid HashType is passed -var ErrInvalidHashType = errors.New("invalid hash type") - -var hashMap = map[HashType]HashingFunc{ - MD5: md5.New, - SHA1: sha1.New, - SHA256: sha256.New, -} - -// Hash generates a name from a hash of the filename. -// When this is used, the original filename will be removed from the final filename. -func Hash(t HashType) Generator { - f, ok := hashMap[t] - return func(c *Config) (string, error) { - if !ok { - return "", fmt.Errorf("%w: %s", ErrInvalidHashType, t) - } - name, err := getOriginal(c) - if err != nil { - return "", err - } - hs := f() - hs.Write([]byte(name)) - return fmt.Sprintf("%x", hs.Sum(nil)), nil - } -} diff --git a/generators_examples_test.go b/generators_examples_test.go index 85563f9..d46ebef 100644 --- a/generators_examples_test.go +++ b/generators_examples_test.go @@ -18,49 +18,3 @@ func ExampleWithGenerator_custom_generator() { // hello.txt // hello } - -func ExampleSlug() { - conf := NewConfig(WithGenerator(Slug()), WithOriginal("My name is Jimmy")) - str, _ := Make(conf) - fmt.Println(str) - - // Output: my-name-is-jimmy.txt -} - -func ExampleSlugWithLang() { - conf := NewConfig(WithGenerator(SlugWithLang("de")), WithOriginal("Diese & Dass")) - str, _ := Make(conf) - fmt.Println(str) - - // Output: diese-und-dass.txt -} - -func ExampleHash_mD5() { - conf := NewConfig( - WithOriginal("foobar"), - WithGenerator(Hash(MD5)), - ) - str, _ := Make(conf) - fmt.Println(str) - // Output: 3858f62230ac3c915f300c664312c63f.txt -} - -func ExampleHash_sHA1() { - conf := NewConfig( - WithOriginal("foobar"), - WithGenerator(Hash(SHA1)), - ) - str, _ := Make(conf) - fmt.Println(str) - // Output: 8843d7f92416211de9ebb963ff4ce28125932878.txt -} - -func ExampleHash_sHA256() { - conf := NewConfig( - WithOriginal("foobar"), - WithGenerator(Hash(SHA256)), - ) - str, _ := Make(conf) - fmt.Println(str) - // Output: c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2.txt -} diff --git a/generators_test.go b/generators_test.go index 330b169..7886ded 100644 --- a/generators_test.go +++ b/generators_test.go @@ -82,38 +82,3 @@ func TestUUIDFail(t *testing.T) { _, err := UUID()(nil) assert.Equal(t, errors.New("sorry"), err) } - -func TestSlugMissingFilename(t *testing.T) { - conf := NewConfig(WithGenerator(Slug())) - st, err := conf.generator(&conf) - assert.Zero(t, st) - assert.ErrorIs(t, err, ErrMissingOriginal) -} - -func TestSlugRemovesOriginal(t *testing.T) { - conf := NewConfig(WithGenerator(Slug()), WithOriginal("Hello, World")) - st, err := conf.generator(&conf) - assert.Zero(t, conf.original) - assert.Equal(t, "hello-world", st) - assert.NoError(t, err) -} - -func TestHashBadHash(t *testing.T) { - conf := NewConfig(WithOriginal("foobar"), WithGenerator(Hash(0))) - st, err := conf.generator(&conf) - assert.Equal(t, "", st) - assert.ErrorIs(t, err, ErrInvalidHashType) - assert.ErrorContains(t, err, "invalid hash type: HashType(0)") -} - -func TestHashMissingOriginal(t *testing.T) { - conf := NewConfig(WithGenerator(Hash(MD5))) - st, err := conf.generator(&conf) - assert.Equal(t, "", st) - assert.ErrorIs(t, err, ErrMissingOriginal) -} - -func TestHashTypeStringer(t *testing.T) { - s := MD5.String() - assert.Equal(t, "MD5", s) -} diff --git a/hashtype_string.go b/hashtype_string.go index 16aa752..a4ce423 100644 --- a/hashtype_string.go +++ b/hashtype_string.go @@ -1,4 +1,4 @@ -// Code generated by "stringer -type=HashType"; DO NOT EDIT. +// Code generated by "stringer -type=HashType -trimprefix=Hash"; DO NOT EDIT. package nomino @@ -8,9 +8,9 @@ func _() { // An "invalid array index" compiler error signifies that the constant values have changed. // Re-run the stringer command to generate them again. var x [1]struct{} - _ = x[MD5-1] - _ = x[SHA1-2] - _ = x[SHA256-3] + _ = x[HashMD5-1] + _ = x[HashSHA1-2] + _ = x[HashSHA256-3] } const _HashType_name = "MD5SHA1SHA256" From bd448eb5db729217771cf2867fb8e53610d01289 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Fri, 14 Mar 2025 20:12:22 -0500 Subject: [PATCH 05/29] =?UTF-8?q?=F0=9F=9A=9A=20Move=20remaining=20example?= =?UTF-8?q?s=20to=20nomino=5Ftest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- generators_examples_test.go | 21 ++++++++++++++------- make_examples_test.go | 10 +++++++--- options_examples_test.go | 20 ++++++++++++-------- 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/generators_examples_test.go b/generators_examples_test.go index d46ebef..3e63db3 100644 --- a/generators_examples_test.go +++ b/generators_examples_test.go @@ -1,17 +1,24 @@ -package nomino +package nomino_test -import "fmt" +import ( + "fmt" -func ExampleWithGenerator_custom_generator() { - gen := func(*Config) (string, error) { + "codeberg.org/danjones000/nomino" +) + +func ExampleWithGenerator_customGenerator() { + gen := func(*nomino.Config) (string, error) { return "hello", nil } - option := WithGenerator(gen) + option := nomino.WithGenerator(gen) - str, _ := Make(NewConfig(option)) + str, _ := nomino.Make(nomino.NewConfig(option)) fmt.Println(str) - str, _ = Make(NewConfig(option, WithoutExtension())) + str, _ = nomino.Make(nomino.NewConfig( + option, + nomino.WithoutExtension(), + )) fmt.Println(str) // Output: diff --git a/make_examples_test.go b/make_examples_test.go index 07c85ba..be09fc5 100644 --- a/make_examples_test.go +++ b/make_examples_test.go @@ -1,9 +1,13 @@ -package nomino +package nomino_test -import "fmt" +import ( + "fmt" + + "codeberg.org/danjones000/nomino" +) func ExampleMake_basic() { // Use default config - out, _ := Make(NewConfig()) + out, _ := nomino.Make(nomino.NewConfig()) fmt.Println(out) } diff --git a/options_examples_test.go b/options_examples_test.go index 0c4e669..b60171f 100644 --- a/options_examples_test.go +++ b/options_examples_test.go @@ -1,11 +1,15 @@ -package nomino +package nomino_test -import "fmt" +import ( + "fmt" + + "codeberg.org/danjones000/nomino" +) func ExampleWithOriginalSlug() { - st, _ := Make(NewConfig( - WithOriginalSlug("Hello, World"), - WithGenerator(Incremental()), + st, _ := nomino.Make(nomino.NewConfig( + nomino.WithOriginalSlug("Hello, World"), + nomino.WithGenerator(nomino.Incremental()), )) fmt.Println(st) @@ -13,9 +17,9 @@ func ExampleWithOriginalSlug() { } func ExampleWithOriginalSlugLang() { - st, _ := Make(NewConfig( - WithOriginalSlugLang("Diese & Dass", "de"), - WithGenerator(Incremental()), + st, _ := nomino.Make(nomino.NewConfig( + nomino.WithOriginalSlugLang("Diese & Dass", "de"), + nomino.WithGenerator(nomino.Incremental()), )) fmt.Println(st) From 9402d807043cc1668eb62deb42d650e2db52d062 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Fri, 14 Mar 2025 21:22:29 -0500 Subject: [PATCH 06/29] =?UTF-8?q?=E2=9C=85=20Examples=20for=20everything?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gen_ts_examples_test.go | 13 +++++-- generators_examples_test.go | 38 +++++++++++++++++++ options_examples_test.go | 76 +++++++++++++++++++++++++++++++++++++ options_test.go | 48 ----------------------- 4 files changed, 124 insertions(+), 51 deletions(-) delete mode 100644 options_test.go diff --git a/gen_ts_examples_test.go b/gen_ts_examples_test.go index 527728d..2f49392 100644 --- a/gen_ts_examples_test.go +++ b/gen_ts_examples_test.go @@ -7,7 +7,14 @@ import ( "codeberg.org/danjones000/nomino" ) -func ExampleTimestamp_withTime() { +func ExampleTimestamp() { + gen := nomino.Timestamp() + conf := nomino.NewConfig(nomino.WithGenerator(gen)) + s, _ := nomino.Make(conf) + fmt.Println(s) +} + +func ExampleTimestampTime() { tz, _ := time.LoadLocation("America/New_York") ts := time.Date(2009, time.January, 20, 12, 5, 0, 0, tz) gen := nomino.Timestamp(nomino.TimestampTime(ts)) @@ -17,7 +24,7 @@ func ExampleTimestamp_withTime() { // Output: 2009-01-20T12-05-00-0500.txt } -func ExampleTimestamp_withFormat() { +func ExampleTimestampFormat() { tz, _ := time.LoadLocation("America/New_York") ts := time.Date(2009, time.January, 20, 12, 5, 0, 0, tz) gen := nomino.Timestamp(nomino.TimestampTime(ts), nomino.TimestampFormat("2006#01#02<>15|04|05-0700")) @@ -27,7 +34,7 @@ func ExampleTimestamp_withFormat() { // Output: 2009#01#20<>12|05|00-0500.txt } -func ExampleTimestamp_withUTC() { +func ExampleTimestampUTC() { tz, _ := time.LoadLocation("America/New_York") ts := time.Date(2009, time.January, 20, 12, 5, 0, 0, tz) gen := nomino.Timestamp(nomino.TimestampTime(ts), nomino.TimestampUTC()) diff --git a/generators_examples_test.go b/generators_examples_test.go index 3e63db3..c9d8710 100644 --- a/generators_examples_test.go +++ b/generators_examples_test.go @@ -25,3 +25,41 @@ func ExampleWithGenerator_customGenerator() { // hello.txt // hello } + +func ExampleMultiGeneratorInOrder() { + gen1 := func(*nomino.Config) (string, error) { + return "hello", nil + } + gen2 := func(*nomino.Config) (string, error) { + return "goodbye", nil + } + gen := nomino.MultiGeneratorInOrder(gen1, gen2) + option := nomino.WithGenerator(gen) + + str, _ := nomino.Make(nomino.NewConfig(option)) + fmt.Println(str) + + str, _ = nomino.Make(nomino.NewConfig(option)) + fmt.Println(str) + + str, _ = nomino.Make(nomino.NewConfig(option)) + fmt.Println(str) + + // Output: + // hello.txt + // goodbye.txt + // hello.txt +} + +func ExampleUUID() { + option := nomino.WithGenerator(nomino.UUID()) + + str, _ := nomino.Make(nomino.NewConfig(option)) + fmt.Println(str) + + str, _ = nomino.Make(nomino.NewConfig(option)) + fmt.Println(str) + + str, _ = nomino.Make(nomino.NewConfig(option)) + fmt.Println(str) +} diff --git a/options_examples_test.go b/options_examples_test.go index b60171f..8263fe9 100644 --- a/options_examples_test.go +++ b/options_examples_test.go @@ -6,6 +6,82 @@ import ( "codeberg.org/danjones000/nomino" ) +func ExampleWithExtension() { + st, _ := nomino.Make(nomino.NewConfig( + nomino.WithExtension("xml"), + nomino.WithGenerator(nomino.Incremental()), + )) + + fmt.Println(st) + // Output: 0.xml +} + +func ExampleWithoutExtension() { + st, _ := nomino.Make(nomino.NewConfig( + nomino.WithoutExtension(), + nomino.WithGenerator(nomino.Incremental()), + )) + + fmt.Println(st) + // Output: 0 +} + +func ExampleWithPrefix() { + conf := nomino.NewConfig( + nomino.WithPrefix("pref"), + nomino.WithGenerator(nomino.Incremental()), + ) + st, _ := nomino.Make(conf) + fmt.Println(st) + + st, _ = nomino.Make(conf) + fmt.Println(st) + // Output: + // pref_0.txt + // pref_1.txt +} + +func ExampleWithSeparator() { + conf := nomino.NewConfig( + nomino.WithPrefix("pref"), + nomino.WithSeparator("---"), + nomino.WithGenerator(nomino.Incremental()), + ) + st, _ := nomino.Make(conf) + fmt.Println(st) + + st, _ = nomino.Make(conf) + fmt.Println(st) + // Output: + // pref---0.txt + // pref---1.txt +} + +func ExampleWithSuffix() { + conf := nomino.NewConfig( + nomino.WithSuffix("suff"), + nomino.WithGenerator(nomino.Incremental()), + ) + st, _ := nomino.Make(conf) + fmt.Println(st) + + st, _ = nomino.Make(conf) + fmt.Println(st) + // Output: + // 0_suff.txt + // 1_suff.txt +} + +func ExampleWithOriginal() { + st, _ := nomino.Make(nomino.NewConfig( + nomino.WithOriginal("Hello, World"), + nomino.WithGenerator(nomino.Incremental()), + )) + + fmt.Println(st) + // Output: 0_Hello, World.txt +} + func ExampleWithOriginalSlug() { st, _ := nomino.Make(nomino.NewConfig( nomino.WithOriginalSlug("Hello, World"), diff --git a/options_test.go b/options_test.go deleted file mode 100644 index 6aded3a..0000000 --- a/options_test.go +++ /dev/null @@ -1,48 +0,0 @@ -package nomino - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -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) -} - -func TestWithSeparator(t *testing.T) { - var c Config - sep := "---" - WithSeparator(sep)(&c) - assert.Equal(t, sep, c.separator) -} From db2f12522dcdc3e791d0d3b5a8d5532b8220f0fc Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Fri, 14 Mar 2025 21:46:30 -0500 Subject: [PATCH 07/29] =?UTF-8?q?=E2=9C=A8=20Add=20extra=20Options=20to=20?= =?UTF-8?q?Make?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- make.go | 14 ++++++++++---- make_examples_test.go | 17 +++++++++++++++++ make_test.go | 15 +++++++++++++++ 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/make.go b/make.go index 538c7e0..640ec39 100644 --- a/make.go +++ b/make.go @@ -5,12 +5,21 @@ 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) { +func Make(conf Config, opts ...Option) (string, error) { + for _, opt := range opts { + opt(&conf) + } + name, err := conf.generator(&conf) if err != nil { return "", err } + seperateConf(&conf) + return fmt.Sprintf("%s%s%s%s%s", conf.prefix, name, conf.original, conf.suffix, conf.extension), nil +} + +func seperateConf(conf *Config) { if conf.prefix != "" { conf.prefix += conf.separator } @@ -19,8 +28,5 @@ func Make(conf Config) (string, error) { } if conf.suffix != "" { conf.suffix = conf.separator + conf.suffix - } - - return fmt.Sprintf("%s%s%s%s%s", conf.prefix, name, conf.original, conf.suffix, conf.extension), nil } diff --git a/make_examples_test.go b/make_examples_test.go index be09fc5..88b8521 100644 --- a/make_examples_test.go +++ b/make_examples_test.go @@ -11,3 +11,20 @@ func ExampleMake_basic() { out, _ := nomino.Make(nomino.NewConfig()) fmt.Println(out) } + +func ExampleMake_withExtraOptions() { + gen := nomino.Incremental() + conf := nomino.NewConfig( + nomino.WithGenerator(gen), + nomino.WithPrefix("pre"), + ) + + st, _ := nomino.Make(conf, nomino.WithOriginal("foobar")) + fmt.Println(st) + st, _ = nomino.Make(conf, nomino.WithOriginal("baz")) + fmt.Println(st) + + // Output: + // pre_0_foobar.txt + // pre_1_baz.txt +} diff --git a/make_test.go b/make_test.go index ffdecee..0970c48 100644 --- a/make_test.go +++ b/make_test.go @@ -66,3 +66,18 @@ func TestMakeDoesntChangeConf(t *testing.T) { assert.Equal(t, "foo.txt", st) assert.NoError(t, err) } + +func TestMakeOptsDoesntChangeConf(t *testing.T) { + gen := Incremental() + conf := NewConfig(WithGenerator(gen), WithPrefix("pre")) + + st, err := Make(conf, WithOriginal("foobar")) + assert.Equal(t, "", conf.original) + assert.Equal(t, "pre_0_foobar.txt", st) + assert.NoError(t, err) + + st, err = Make(conf, WithOriginal("baz")) + assert.Equal(t, "", conf.original) + assert.Equal(t, "pre_1_baz.txt", st) + assert.NoError(t, err) +} From 46a40031dd6dc2ab9af0ef858299bd62bb823a2f Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Fri, 14 Mar 2025 21:56:12 -0500 Subject: [PATCH 08/29] =?UTF-8?q?=F0=9F=93=9D=20Update=20CHANGELOG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 14 ++++++++++++++ README.md | 2 ++ 2 files changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c597e7d..0611f05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Changelog +### [0.3.0] - 2025-03-14 + +#### Features + +- Simplified multiple Generator functions to single function with options +- Added a lot of examples for docs +- Can add extra Options to Make + +Multiple breaking changes around Generators. + +#### Bugs + +- Fixed date formats + ### [0.2.1] - 2025-03-14 #### Features diff --git a/README.md b/README.md index f08b801..09b7bb7 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,8 @@ The purpose of nomino is to generate (probably random) filenames, for example, i It takes a lot of inspiration (although no actual code) from [Onym](https://github.com/Blaspsoft/onym). +Note that this is still not at a stable release. There will be breaking changes between minor releases until it reaches 1.0.0. Patch releases shouldn't contain breaking changes however. Once it reaches 1.0.0, breaking changes will only happen between major releases. + ## TODO I'll fill this out more in depth later. From 5c1132e414336deecab76613419450148d00a0f0 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Sat, 15 Mar 2025 15:35:40 -0500 Subject: [PATCH 09/29] =?UTF-8?q?=F0=9F=92=A1=20Fix=20some=20doc=20blocks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.go | 1 + make.go | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/config.go b/config.go index 5c87729..a7da5f5 100644 --- a/config.go +++ b/config.go @@ -9,6 +9,7 @@ type Config struct { generator Generator } +// NewConfig returns a new Config with the specified Options. func NewConfig(options ...Option) Config { conf := Config{ extension: ".txt", diff --git a/make.go b/make.go index 640ec39..2e9d754 100644 --- a/make.go +++ b/make.go @@ -2,9 +2,9 @@ package nomino import "fmt" -// Make generates a random filename. The behavior can be controlled by specifying Options +// 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. +// If the name generator returns an error (generally, it shouldn't), that error will be returned instead. func Make(conf Config, opts ...Option) (string, error) { for _, opt := range opts { opt(&conf) From 1677a692d16e825b90a650f2f8b45136996213d4 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Sat, 15 Mar 2025 15:36:43 -0500 Subject: [PATCH 10/29] =?UTF-8?q?=E2=9C=A8=20Add=20Random=20Generator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gen_rand.go | 60 +++++++++++++++++++++++++++++++++++++ gen_rand_examples_test.go | 36 ++++++++++++++++++++++ gen_rand_test.go | 42 ++++++++++++++++++++++++++ generators.go | 19 +----------- generators_examples_test.go | 13 -------- generators_test.go | 22 -------------- go.mod | 1 + go.sum | 2 ++ 8 files changed, 142 insertions(+), 53 deletions(-) create mode 100644 gen_rand.go create mode 100644 gen_rand_examples_test.go create mode 100644 gen_rand_test.go diff --git a/gen_rand.go b/gen_rand.go new file mode 100644 index 0000000..8b4ce5a --- /dev/null +++ b/gen_rand.go @@ -0,0 +1,60 @@ +package nomino + +import ( + "crypto/rand" + "strings" + + "github.com/deatil/go-encoding/base62" + "github.com/google/uuid" +) + +func uuidGen(*Config) (string, error) { + u, err := uuid.NewRandom() + if err != nil { + return "", err + } + return u.String(), nil +} + +// UUID generates a UUIDv4. +func UUID() Generator { + return uuidGen +} + +type randConf struct { + length int +} + +// RandomOption is an option for the Random Generator +type RandomOption func(*randConf) + +// RandomLength controls the length of the string generated by Random +func RandomLength(length int) RandomOption { + return func(c *randConf) { + c.length = length + } +} + +func getRandomBytes(l int) []byte { + key := make([]byte, l) + rand.Read(key) + e := base62.StdEncoding.Encode(key) + return e[:l] +} + +// Random generates a random string containing the characters [A-Za-z0-9]. +// By default, it will be eight characters long. +func Random(opts ...RandomOption) Generator { + c := randConf{8} + for _, opt := range opts { + opt(&c) + } + return func(*Config) (string, error) { + var buff strings.Builder + buff.Grow(c.length) + for buff.Len() < c.length { + buff.Write(getRandomBytes(c.length - buff.Len())) + } + return buff.String(), nil + } +} diff --git a/gen_rand_examples_test.go b/gen_rand_examples_test.go new file mode 100644 index 0000000..45cb7fe --- /dev/null +++ b/gen_rand_examples_test.go @@ -0,0 +1,36 @@ +package nomino_test + +import ( + "fmt" + + "codeberg.org/danjones000/nomino" +) + +func ExampleUUID() { + option := nomino.WithGenerator(nomino.UUID()) + + str, _ := nomino.Make(nomino.NewConfig(option)) + fmt.Println(str) + + str, _ = nomino.Make(nomino.NewConfig(option)) + fmt.Println(str) + + str, _ = nomino.Make(nomino.NewConfig(option)) + fmt.Println(str) +} + +func ExampleRandom() { + option := nomino.WithGenerator(nomino.Random()) + + str, _ := nomino.Make(nomino.NewConfig(option)) + fmt.Println(str) +} + +func ExampleRandomLength() { + option := nomino.WithGenerator(nomino.Random( + nomino.RandomLength(32), + )) + + str, _ := nomino.Make(nomino.NewConfig(option)) + fmt.Println(str) +} diff --git a/gen_rand_test.go b/gen_rand_test.go new file mode 100644 index 0000000..832275b --- /dev/null +++ b/gen_rand_test.go @@ -0,0 +1,42 @@ +package nomino + +import ( + "errors" + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" +) + +func TestUUID(t *testing.T) { + st, err := UUID()(nil) + 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 TestUUIDFail(t *testing.T) { + uuid.SetRand(badRead{}) + defer uuid.SetRand(nil) + + _, err := UUID()(nil) + assert.Equal(t, errors.New("sorry"), err) +} + +func TestRand(t *testing.T) { + st, err := Random()(nil) + assert.NoError(t, err) + assert.Len(t, st, 8) +} + +func TestRandLen(t *testing.T) { + st, err := Random(RandomLength(32))(nil) + assert.NoError(t, err) + assert.Len(t, st, 32) +} diff --git a/generators.go b/generators.go index 25cd36e..94326dc 100644 --- a/generators.go +++ b/generators.go @@ -1,10 +1,6 @@ package nomino -import ( - "errors" - - "github.com/google/uuid" -) +import "errors" // 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, @@ -43,16 +39,3 @@ func MultiGeneratorInOrder(gens ...Generator) Generator { return st, err } } - -func uuidGen(*Config) (string, error) { - u, err := uuid.NewRandom() - if err != nil { - return "", err - } - return u.String(), nil -} - -// UUID generates a UUIDv4. -func UUID() Generator { - return uuidGen -} diff --git a/generators_examples_test.go b/generators_examples_test.go index c9d8710..a7f7a1c 100644 --- a/generators_examples_test.go +++ b/generators_examples_test.go @@ -50,16 +50,3 @@ func ExampleMultiGeneratorInOrder() { // goodbye.txt // hello.txt } - -func ExampleUUID() { - option := nomino.WithGenerator(nomino.UUID()) - - str, _ := nomino.Make(nomino.NewConfig(option)) - fmt.Println(str) - - str, _ = nomino.Make(nomino.NewConfig(option)) - fmt.Println(str) - - str, _ = nomino.Make(nomino.NewConfig(option)) - fmt.Println(str) -} diff --git a/generators_test.go b/generators_test.go index 7886ded..997d87c 100644 --- a/generators_test.go +++ b/generators_test.go @@ -4,7 +4,6 @@ import ( "errors" "testing" - "github.com/google/uuid" "github.com/stretchr/testify/assert" ) @@ -61,24 +60,3 @@ func TestMultiGeneratorInOrderMissing(t *testing.T) { assert.Zero(t, st) assert.ErrorIs(t, err, ErrMissingGenerators) } - -func TestUUID(t *testing.T) { - st, err := UUID()(nil) - 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 TestUUIDFail(t *testing.T) { - uuid.SetRand(badRead{}) - defer uuid.SetRand(nil) - - _, err := UUID()(nil) - assert.Equal(t, errors.New("sorry"), err) -} diff --git a/go.mod b/go.mod index 6917d49..c733bcf 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module codeberg.org/danjones000/nomino go 1.23.6 require ( + github.com/deatil/go-encoding v1.0.3003 github.com/google/uuid v1.6.0 github.com/gosimple/slug v1.15.0 github.com/stretchr/testify v1.10.0 diff --git a/go.sum b/go.sum index 8638c59..209d634 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/deatil/go-encoding v1.0.3003 h1:2b05UO+5JfVcXcOa8n/X3pm8aC6L6ET0mBZCb1kj3ck= +github.com/deatil/go-encoding v1.0.3003/go.mod h1:lTMMKsG0RRPGZzdW2EPVJCA7HQy4o1ZQKPf5CmVDy2k= 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/gosimple/slug v1.15.0 h1:wRZHsRrRcs6b0XnxMUBM6WK1U1Vg5B0R7VkIf1Xzobo= From 1008a064d017aa259db31db1112af7b0c2e7f5d2 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Sat, 15 Mar 2025 16:24:37 -0500 Subject: [PATCH 11/29] =?UTF-8?q?=E2=9C=A8=20Add=20Make=20method=20to=20Ge?= =?UTF-8?q?nerator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- generators.go | 6 ++++++ generators_examples_test.go | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/generators.go b/generators.go index 94326dc..fabf650 100644 --- a/generators.go +++ b/generators.go @@ -7,6 +7,12 @@ import "errors" // for example. type Generator func(conf *Config) (string, error) +// Make allows you to generate a new string directly from a generator. +func (g Generator) Make(opts ...Option) (string, error) { + opts = append(opts, WithGenerator(g)) + return Make(NewConfig(opts...)) +} + // WithGenerator sets the specified generator func WithGenerator(g Generator) Option { return func(c *Config) { diff --git a/generators_examples_test.go b/generators_examples_test.go index a7f7a1c..31951f5 100644 --- a/generators_examples_test.go +++ b/generators_examples_test.go @@ -26,6 +26,19 @@ func ExampleWithGenerator_customGenerator() { // hello } +func ExampleGenerator_Make() { + g := nomino.Incremental() + st, _ := g.Make() + fmt.Println(st) + + st, _ = g.Make(nomino.WithPrefix("foo")) + fmt.Println(st) + + // Output: + // 0.txt + // foo_1.txt +} + func ExampleMultiGeneratorInOrder() { gen1 := func(*nomino.Config) (string, error) { return "hello", nil From 2440f55563982d1cc01a6b4f5c86a4d6d79a1a98 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Sat, 15 Mar 2025 20:09:38 -0500 Subject: [PATCH 12/29] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Replace=20HashType?= =?UTF-8?q?=20with=20Hasher?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Compatible with crypto.Hash --- Taskfile.yml | 3 +-- gen_file.go | 46 +++++++++++++++------------------ gen_file_examples_test.go | 54 ++++++++++++++++----------------------- gen_file_test.go | 13 +++------- hashtype_string.go | 26 ------------------- 5 files changed, 47 insertions(+), 95 deletions(-) delete mode 100644 hashtype_string.go diff --git a/Taskfile.yml b/Taskfile.yml index 474a888..35792f6 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -26,7 +26,6 @@ tasks: desc: Vet go code sources: - '**/*.go' - deps: [gen] cmds: - go vet ./... @@ -82,7 +81,7 @@ tasks: test: desc: Run unit tests - deps: [fmt, vet, gen] + deps: [fmt, vet] sources: - '**/*.go' generates: diff --git a/gen_file.go b/gen_file.go index ef239cb..497fdcc 100644 --- a/gen_file.go +++ b/gen_file.go @@ -1,9 +1,7 @@ package nomino import ( - "crypto/md5" - "crypto/sha1" - "crypto/sha256" + "crypto" "errors" "fmt" "hash" @@ -42,39 +40,35 @@ func Slug(lang ...string) Generator { // HashingFunc is a function that generates a hash.Hash type HashingFunc func() hash.Hash -//go:generate stringer -type=HashType -trimprefix=Hash - -// HashType represents a particular hashing algorithm -type HashType uint8 - -const ( - HashMD5 HashType = iota + 1 - HashSHA1 - HashSHA256 -) - -// ErrInvalidHashType is returned by the Hash generator when an invalid HashType is passed -var ErrInvalidHashType = errors.New("invalid hash type") - -var hashMap = map[HashType]HashingFunc{ - HashMD5: md5.New, - HashSHA1: sha1.New, - HashSHA256: sha256.New, +// New allows HashingFunc to be used as a Hasher +func (hf HashingFunc) New() hash.Hash { + return hf() } +// Hasher is a type returns a hash.Hash. +// All crypto.Hash may be used. +type Hasher interface { + New() hash.Hash +} + +// ErrInvalidHash is returned by the Hash generator when an invalid HashType is passed +var ErrInvalidHash = errors.New("invalid hash type") + // Hash generates a name from a hash of the filename. // When this is used, the original filename will be removed from the final filename. -func Hash(t HashType) Generator { - f, ok := hashMap[t] +func Hash(h Hasher) Generator { + if h == nil { + h = crypto.MD5 + } return func(c *Config) (string, error) { - if !ok { - return "", fmt.Errorf("%w: %s", ErrInvalidHashType, t) + if h == crypto.MD5SHA1 { + return "", ErrInvalidHash } name, err := getOriginal(c) if err != nil { return "", err } - hs := f() + hs := h.New() hs.Write([]byte(name)) return fmt.Sprintf("%x", hs.Sum(nil)), nil } diff --git a/gen_file_examples_test.go b/gen_file_examples_test.go index 3d8036f..76f3260 100644 --- a/gen_file_examples_test.go +++ b/gen_file_examples_test.go @@ -1,66 +1,56 @@ package nomino_test import ( + "crypto" + "crypto/hmac" "fmt" + "hash" "codeberg.org/danjones000/nomino" ) func ExampleSlug() { - conf := nomino.NewConfig( - nomino.WithOriginal("My name is Jimmy"), - nomino.WithGenerator(nomino.Slug()), - ) - str, _ := nomino.Make(conf) + str, _ := nomino.Slug().Make(nomino.WithOriginal("My name is Jimmy")) fmt.Println(str) // Output: my-name-is-jimmy.txt } func ExampleSlug_withLang() { - conf := nomino.NewConfig( - nomino.WithOriginal("Diese & Dass"), - nomino.WithGenerator(nomino.Slug("de")), - ) - - str, _ := nomino.Make(conf) + str, _ := nomino.Slug("de"). + Make(nomino.WithOriginal("Diese & Dass")) fmt.Println(str) // Output: diese-und-dass.txt } func ExampleHash_mD5() { - conf := nomino.NewConfig( - nomino.WithOriginal("foobar"), - nomino.WithGenerator( - nomino.Hash(nomino.HashMD5), - ), - ) - str, _ := nomino.Make(conf) + str, _ := nomino.Hash(crypto.MD5). + Make(nomino.WithOriginal("foobar")) fmt.Println(str) // Output: 3858f62230ac3c915f300c664312c63f.txt } func ExampleHash_sHA1() { - conf := nomino.NewConfig( - nomino.WithOriginal("foobar"), - nomino.WithGenerator( - nomino.Hash(nomino.HashSHA1), - ), - ) - str, _ := nomino.Make(conf) + str, _ := nomino.Hash(crypto.SHA1). + Make(nomino.WithOriginal("foobar")) fmt.Println(str) // Output: 8843d7f92416211de9ebb963ff4ce28125932878.txt } func ExampleHash_sHA256() { - conf := nomino.NewConfig( - nomino.WithOriginal("foobar"), - nomino.WithGenerator( - nomino.Hash(nomino.HashSHA256), - ), - ) - str, _ := nomino.Make(conf) + str, _ := nomino.Hash(crypto.SHA256). + Make(nomino.WithOriginal("foobar")) fmt.Println(str) // Output: c3ab8ff13720e8ad9047dd39466b3c8974e592c2fa383d4a3960714caef0c4f2.txt } + +func ExampleHashingFunc_hMAC() { + var hasher nomino.HashingFunc = func() hash.Hash { + return hmac.New(crypto.SHA1.New, []byte("hello")) + } + g := nomino.Hash(hasher) + str, _ := g.Make(nomino.WithOriginal("foobar")) + fmt.Println(str) + // Output: 85f767c284c80a3a59a9635194321d20dd90f31b.txt +} diff --git a/gen_file_test.go b/gen_file_test.go index 0d03d16..cd0a8bf 100644 --- a/gen_file_test.go +++ b/gen_file_test.go @@ -1,6 +1,7 @@ package nomino import ( + "crypto" "testing" "github.com/stretchr/testify/assert" @@ -22,21 +23,15 @@ func TestSlugRemovesOriginal(t *testing.T) { } func TestHashBadHash(t *testing.T) { - conf := NewConfig(WithOriginal("foobar"), WithGenerator(Hash(0))) + conf := NewConfig(WithOriginal("foobar"), WithGenerator(Hash(crypto.MD5SHA1))) st, err := conf.generator(&conf) assert.Equal(t, "", st) - assert.ErrorIs(t, err, ErrInvalidHashType) - assert.ErrorContains(t, err, "invalid hash type: HashType(0)") + assert.ErrorIs(t, err, ErrInvalidHash) } func TestHashMissingOriginal(t *testing.T) { - conf := NewConfig(WithGenerator(Hash(HashMD5))) + conf := NewConfig(WithGenerator(Hash(nil))) st, err := conf.generator(&conf) assert.Equal(t, "", st) assert.ErrorIs(t, err, ErrMissingOriginal) } - -func TestHashTypeStringer(t *testing.T) { - s := HashMD5.String() - assert.Equal(t, "MD5", s) -} diff --git a/hashtype_string.go b/hashtype_string.go deleted file mode 100644 index a4ce423..0000000 --- a/hashtype_string.go +++ /dev/null @@ -1,26 +0,0 @@ -// Code generated by "stringer -type=HashType -trimprefix=Hash"; DO NOT EDIT. - -package nomino - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[HashMD5-1] - _ = x[HashSHA1-2] - _ = x[HashSHA256-3] -} - -const _HashType_name = "MD5SHA1SHA256" - -var _HashType_index = [...]uint8{0, 3, 7, 13} - -func (i HashType) String() string { - i -= 1 - if i >= HashType(len(_HashType_index)-1) { - return "HashType(" + strconv.FormatInt(int64(i+1), 10) + ")" - } - return _HashType_name[_HashType_index[i]:_HashType_index[i+1]] -} From 7c016df30f0b9f260b6cd14541275870a80815e4 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Sat, 15 Mar 2025 21:00:54 -0500 Subject: [PATCH 13/29] =?UTF-8?q?=E2=9C=A8=20Add=20MultiGeneratorRandomOrd?= =?UTF-8?q?er?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- generators.go | 22 ++++++++++++- generators_examples_test.go | 36 +++++++++++++------- generators_test.go | 66 ++++++++++++++++++++++++++++++------- 3 files changed, 101 insertions(+), 23 deletions(-) diff --git a/generators.go b/generators.go index fabf650..c7ad4e8 100644 --- a/generators.go +++ b/generators.go @@ -1,6 +1,9 @@ package nomino -import "errors" +import ( + "errors" + "math/rand" +) // 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, @@ -45,3 +48,20 @@ func MultiGeneratorInOrder(gens ...Generator) Generator { return st, err } } + +// MultiGeneratorRandomOrder allows the use of multiple generators. Each new invokation will use one of the generators randomly. +// If none are passed, the generator will always return ErrMissingGenerators. +func MultiGeneratorRandomOrder(gens ...Generator) Generator { + if len(gens) == 0 { + return missingGen + } + + if len(gens) == 1 { + return gens[0] + } + + return func(c *Config) (string, error) { + idx := rand.Int() % len(gens) + return gens[idx](c) + } +} diff --git a/generators_examples_test.go b/generators_examples_test.go index 31951f5..5efc4d8 100644 --- a/generators_examples_test.go +++ b/generators_examples_test.go @@ -7,18 +7,14 @@ import ( ) func ExampleWithGenerator_customGenerator() { - gen := func(*nomino.Config) (string, error) { + var gen nomino.Generator = func(*nomino.Config) (string, error) { return "hello", nil } - option := nomino.WithGenerator(gen) - str, _ := nomino.Make(nomino.NewConfig(option)) + str, _ := gen.Make() fmt.Println(str) - str, _ = nomino.Make(nomino.NewConfig( - option, - nomino.WithoutExtension(), - )) + str, _ = gen.Make(nomino.WithoutExtension()) fmt.Println(str) // Output: @@ -47,15 +43,14 @@ func ExampleMultiGeneratorInOrder() { return "goodbye", nil } gen := nomino.MultiGeneratorInOrder(gen1, gen2) - option := nomino.WithGenerator(gen) - str, _ := nomino.Make(nomino.NewConfig(option)) + str, _ := gen.Make() fmt.Println(str) - str, _ = nomino.Make(nomino.NewConfig(option)) + str, _ = gen.Make() fmt.Println(str) - str, _ = nomino.Make(nomino.NewConfig(option)) + str, _ = gen.Make() fmt.Println(str) // Output: @@ -63,3 +58,22 @@ func ExampleMultiGeneratorInOrder() { // goodbye.txt // hello.txt } + +func ExampleMultiGeneratorRandomOrder() { + gen1 := func(*nomino.Config) (string, error) { + return "hello", nil + } + gen2 := func(*nomino.Config) (string, error) { + return "goodbye", nil + } + gen := nomino.MultiGeneratorRandomOrder(gen1, gen2) + + str, _ := gen.Make() + fmt.Println(str) + + str, _ = gen.Make() + fmt.Println(str) + + str, _ = gen.Make() + fmt.Println(str) +} diff --git a/generators_test.go b/generators_test.go index 997d87c..f8a81fa 100644 --- a/generators_test.go +++ b/generators_test.go @@ -16,26 +16,34 @@ func TestWithGenerator(t *testing.T) { assert.Equal(t, "abc", st) } +const ( + out1 string = "abc" + out2 string = "def" +) + +var ( + outs = []string{out1, out2} + err1 = errors.New("oops") + gen1 Generator = func(*Config) (string, error) { return out1, nil } + gen2 Generator = func(*Config) (string, error) { return out2, nil } + gen3 Generator = func(*Config) (string, error) { return "", err1 } + gens = []Generator{gen1, gen2, gen3} +) + func TestMultiGeneratorInOrder(t *testing.T) { - st1 := "abc" - st2 := "def" - er1 := errors.New("oops") - g1 := func(*Config) (string, error) { return st1, nil } - g2 := func(*Config) (string, error) { return st2, nil } - g3 := func(*Config) (string, error) { return "", er1 } - g := MultiGeneratorInOrder(g1, g2, g3) + g := MultiGeneratorInOrder(gens...) st, err := g(nil) assert.NoError(t, err) - assert.Equal(t, st1, st) + assert.Equal(t, out1, st) st, err = g(nil) assert.NoError(t, err) - assert.Equal(t, st2, st) + assert.Equal(t, out2, st) st, err = g(nil) assert.Zero(t, st) - assert.ErrorIs(t, err, er1) + assert.ErrorIs(t, err, err1) st, err = g(nil) assert.NoError(t, err) - assert.Equal(t, st1, st) + assert.Equal(t, out1, st) } func TestMultiGeneratorInOrderOne(t *testing.T) { @@ -60,3 +68,39 @@ func TestMultiGeneratorInOrderMissing(t *testing.T) { assert.Zero(t, st) assert.ErrorIs(t, err, ErrMissingGenerators) } + +func TestMultiGeneratorRandomOrder(t *testing.T) { + g := MultiGeneratorRandomOrder(gens...) + for i := 0; i < 4; i++ { + st, err := g(nil) + if err != nil { + assert.Zero(t, st) + assert.ErrorIs(t, err, err1) + } else { + assert.Contains(t, outs, st) + } + } +} + +func TestMultiGeneratorRandomOrderOne(t *testing.T) { + st1 := "abc" + g1 := func(*Config) (string, error) { return st1, nil } + g := MultiGeneratorRandomOrder(g1) + + st, err := g(nil) + assert.NoError(t, err) + assert.Equal(t, st1, st) + st, err = g(nil) + assert.NoError(t, err) + assert.Equal(t, st1, st) +} + +func TestMultiGeneratorRandomOrderMissing(t *testing.T) { + g := MultiGeneratorRandomOrder() + st, err := g(nil) + assert.Zero(t, st) + assert.ErrorIs(t, err, ErrMissingGenerators) + st, err = g(nil) + assert.Zero(t, st) + assert.ErrorIs(t, err, ErrMissingGenerators) +} From 4d6cd82b74dd54754a380a094ce9cd62010ebe07 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Sat, 15 Mar 2025 21:06:16 -0500 Subject: [PATCH 14/29] =?UTF-8?q?=F0=9F=93=9D=20Update=20CHANGELOG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0611f05..c2674b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +### [0.4.0] - 2025-03-15 + +#### Features + +- Add Random Generator +- Add Make method to Generator +- Add MultiGeneratorRandomOrder + +#### Changes + +- Replace HashType with Hasher: This supports all crypto.Hash + +#### Support + +- Add some missing doc comments + ### [0.3.0] - 2025-03-14 #### Features From fee2e3cc2f3dbc274ad0cdea48253bacd47efea4 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Sun, 16 Mar 2025 12:38:36 -0500 Subject: [PATCH 15/29] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Modify=20UUID=20to?= =?UTF-8?q?=20allow=20for=20other=20versions.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.go | 2 +- gen_rand.go | 45 +++++++++++++++++++++++++++++++-------- gen_rand_examples_test.go | 28 +++++++++++++----------- gen_rand_test.go | 4 ++-- 4 files changed, 55 insertions(+), 24 deletions(-) diff --git a/config.go b/config.go index a7da5f5..945e588 100644 --- a/config.go +++ b/config.go @@ -14,7 +14,7 @@ func NewConfig(options ...Option) Config { conf := Config{ extension: ".txt", separator: "_", - generator: uuidGen, + generator: UUID(nil), } for _, opt := range options { opt(&conf) diff --git a/gen_rand.go b/gen_rand.go index 8b4ce5a..de71723 100644 --- a/gen_rand.go +++ b/gen_rand.go @@ -8,17 +8,44 @@ import ( "github.com/google/uuid" ) -func uuidGen(*Config) (string, error) { - u, err := uuid.NewRandom() - if err != nil { - return "", err - } - return u.String(), nil +// UUIDer is an interface for generating UUIDs. +// It is recommended that you use either the UUIDv4 or UUIDv7 variables. +type UUIDer interface { + UUID() (uuid.UUID, error) } -// UUID generates a UUIDv4. -func UUID() Generator { - return uuidGen +// UUIDFunc is a function that generates a UUID. +type UUIDFunc func() (uuid.UUID, error) + +// UUID allows UUIDFunc to be used as a UUIDer. +func (u UUIDFunc) UUID() (uuid.UUID, error) { + return u() +} + +var ( + // UUIDv1. You probably don't want to use this. It is included for completeness sake. + UUIDv1 = UUIDFunc(uuid.NewUUID) + // UUIDv4 is the default. + UUIDv4 = UUIDFunc(uuid.NewRandom) + // UUIDv6 is primarily a replacement for UUIDv1. You probably should use 4 or 7. + UUIDv6 = UUIDFunc(uuid.NewV6) + // UUIDv7 should be used if you want it sortable by time. + UUIDv7 = UUIDFunc(uuid.NewV7) +) + +// UUID generates a UUID. If nil.is passed as an argument, +// a UUIDv4 is generated. +func UUID(u UUIDer) Generator { + if u == nil { + u = UUIDv4 + } + return func(*Config) (string, error) { + uu, err := u.UUID() + if err != nil { + return "", err + } + return uu.String(), nil + } } type randConf struct { diff --git a/gen_rand_examples_test.go b/gen_rand_examples_test.go index 45cb7fe..29737fa 100644 --- a/gen_rand_examples_test.go +++ b/gen_rand_examples_test.go @@ -7,30 +7,34 @@ import ( ) func ExampleUUID() { - option := nomino.WithGenerator(nomino.UUID()) + gen := nomino.UUID(nil) - str, _ := nomino.Make(nomino.NewConfig(option)) + str, _ := gen.Make() fmt.Println(str) - str, _ = nomino.Make(nomino.NewConfig(option)) + str, _ = gen.Make() + fmt.Println(str) +} + +func ExampleUUID_v7() { + gen := nomino.UUID(nomino.UUIDv7) + + str, _ := gen.Make() fmt.Println(str) - str, _ = nomino.Make(nomino.NewConfig(option)) + str, _ = gen.Make() + fmt.Println(str) + + str, _ = gen.Make() fmt.Println(str) } func ExampleRandom() { - option := nomino.WithGenerator(nomino.Random()) - - str, _ := nomino.Make(nomino.NewConfig(option)) + str, _ := nomino.Random().Make() fmt.Println(str) } func ExampleRandomLength() { - option := nomino.WithGenerator(nomino.Random( - nomino.RandomLength(32), - )) - - str, _ := nomino.Make(nomino.NewConfig(option)) + str, _ := nomino.Random(nomino.RandomLength(32)).Make() fmt.Println(str) } diff --git a/gen_rand_test.go b/gen_rand_test.go index 832275b..51fc73f 100644 --- a/gen_rand_test.go +++ b/gen_rand_test.go @@ -9,7 +9,7 @@ import ( ) func TestUUID(t *testing.T) { - st, err := UUID()(nil) + st, err := UUID(nil)(nil) assert.NoError(t, err) _, parseErr := uuid.Parse(st) assert.NoError(t, parseErr) @@ -25,7 +25,7 @@ func TestUUIDFail(t *testing.T) { uuid.SetRand(badRead{}) defer uuid.SetRand(nil) - _, err := UUID()(nil) + _, err := UUID(nil)(nil) assert.Equal(t, errors.New("sorry"), err) } From d7b14f804ca99355764b73e1032ec576afd16c2d Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Sun, 16 Mar 2025 15:56:31 -0500 Subject: [PATCH 16/29] =?UTF-8?q?=F0=9F=93=9D=20Redo=20ts=20examples=20to?= =?UTF-8?q?=20use=20gen.Make?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gen_ts_examples_test.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/gen_ts_examples_test.go b/gen_ts_examples_test.go index 2f49392..26f8a91 100644 --- a/gen_ts_examples_test.go +++ b/gen_ts_examples_test.go @@ -9,8 +9,7 @@ import ( func ExampleTimestamp() { gen := nomino.Timestamp() - conf := nomino.NewConfig(nomino.WithGenerator(gen)) - s, _ := nomino.Make(conf) + s, _ := gen.Make() fmt.Println(s) } @@ -18,8 +17,7 @@ func ExampleTimestampTime() { tz, _ := time.LoadLocation("America/New_York") ts := time.Date(2009, time.January, 20, 12, 5, 0, 0, tz) gen := nomino.Timestamp(nomino.TimestampTime(ts)) - conf := nomino.NewConfig(nomino.WithGenerator(gen)) - s, _ := nomino.Make(conf) + s, _ := gen.Make() fmt.Println(s) // Output: 2009-01-20T12-05-00-0500.txt } @@ -28,8 +26,7 @@ func ExampleTimestampFormat() { tz, _ := time.LoadLocation("America/New_York") ts := time.Date(2009, time.January, 20, 12, 5, 0, 0, tz) gen := nomino.Timestamp(nomino.TimestampTime(ts), nomino.TimestampFormat("2006#01#02<>15|04|05-0700")) - conf := nomino.NewConfig(nomino.WithGenerator(gen)) - s, _ := nomino.Make(conf) + s, _ := gen.Make() fmt.Println(s) // Output: 2009#01#20<>12|05|00-0500.txt } @@ -38,8 +35,7 @@ func ExampleTimestampUTC() { tz, _ := time.LoadLocation("America/New_York") ts := time.Date(2009, time.January, 20, 12, 5, 0, 0, tz) gen := nomino.Timestamp(nomino.TimestampTime(ts), nomino.TimestampUTC()) - conf := nomino.NewConfig(nomino.WithGenerator(gen)) - s, _ := nomino.Make(conf) + s, _ := gen.Make() fmt.Println(s) // Output: 2009-01-20T17-05-00.txt } From 1abfaa44d117533a5b46c1f31d0046cdd87e133a Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Tue, 18 Mar 2025 09:22:30 -0500 Subject: [PATCH 17/29] =?UTF-8?q?=E2=9C=A8=20Config.AddOptions=20and=20Gen?= =?UTF-8?q?erator.MakeWithConfig?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.go | 8 ++++++++ config_test.go | 9 +++++++++ generators.go | 11 ++++++++--- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/config.go b/config.go index 945e588..c889a06 100644 --- a/config.go +++ b/config.go @@ -21,3 +21,11 @@ func NewConfig(options ...Option) Config { } return conf } + +// AddOptions creates a new Config with options added. +func (c Config) AddOptions(options ...Option) Config { + for _, opt := range options { + opt(&c) + } + return c +} diff --git a/config_test.go b/config_test.go index adc35e6..f6ed21d 100644 --- a/config_test.go +++ b/config_test.go @@ -20,3 +20,12 @@ func TestNewConfWithOpts(t *testing.T) { assert.Equal(t, "", c.extension) assert.Equal(t, "foobar", c.prefix) } + +func TestConfAddOpts(t *testing.T) { + c := Config{original: "hi"} + c2 := c.AddOptions(WithOriginalSlug("Hello, my dear"), WithPrefix("yo")) + assert.Equal(t, "", c.prefix) + assert.Equal(t, "hi", c.original) + assert.Equal(t, "hello-my-dear", c2.original) + assert.Equal(t, "yo", c2.prefix) +} diff --git a/generators.go b/generators.go index c7ad4e8..e01ccc6 100644 --- a/generators.go +++ b/generators.go @@ -10,10 +10,15 @@ import ( // for example. type Generator func(conf *Config) (string, error) -// Make allows you to generate a new string directly from a generator. +// Make allows you to generate a new string directly from a Generator. func (g Generator) Make(opts ...Option) (string, error) { - opts = append(opts, WithGenerator(g)) - return Make(NewConfig(opts...)) + return g.MakeWithConfig(NewConfig(opts...)) +} + +// MakeWithConfig allows you to generate a new string directly from a Generator +// with a pre-existing Config. +func (g Generator) MakeWithConfig(c Config) (string, error) { + return Make(c.AddOptions(WithGenerator(g))) } // WithGenerator sets the specified generator From f20e737f2b4211053456eade0d1f6d29333d412a Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Wed, 19 Mar 2025 14:37:40 -0500 Subject: [PATCH 18/29] =?UTF-8?q?=F0=9F=93=9D=20Update=20CHANGELOG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 78 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 45 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c2674b9..c21a656 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,84 +1,96 @@ # Changelog -### [0.4.0] - 2025-03-15 +### [0.5.0] - 2025-03-19 - ✨ Different types of UUIDs #### Features -- Add Random Generator -- Add Make method to Generator -- Add MultiGeneratorRandomOrder - -#### Changes - -- Replace HashType with Hasher: This supports all crypto.Hash +- ✨ Allow for different types of UUIDs in the UUID `Generator` +- ✨ `Config.AddOptions` method +- ✨ `Generator.MakeWithConfig` method #### Support -- Add some missing doc comments +- 📝 Some better examples` -### [0.3.0] - 2025-03-14 +### [0.4.0] - 2025-03-15 - ✨ More Generators, and `Generator.Make` method #### Features -- Simplified multiple Generator functions to single function with options -- Added a lot of examples for docs -- Can add extra Options to Make +- ✨ Add Random Generator +- ✨ Add Make method to Generator +- ✨ Add MultiGeneratorRandomOrder + +#### Changes + +- 💔 Breaking changes: Replace HashType with Hasher: This supports all crypto.Hash + +#### Support + +- 📝 Add some missing doc comments + +### [0.3.0] - 2025-03-14 - ♻️ Refactor multiple Generators into one + +#### Features + +- ♻️ Simplified multiple `Generator` functions to single function with options +- 📝 Added a lot of examples for docs +- ✨ Can add extra `Option`s to `Make` Multiple breaking changes around Generators. #### Bugs -- Fixed date formats +- 🐛 Fixed date formats -### [0.2.1] - 2025-03-14 +### [0.2.1] - 2025-03-14 - ✨ New Hash Generator #### Features -- Add Hash Generator +- ✨ Add Hash Generator #### Dev Tooling -- Added a task to serve docs -- Added tasts to check code complexity +- 🛠 Added a task to serve docs +- 🛠 Added tasts to check code complexity #### Miscellaneous -- Fixed some `go vet` complaints +- 💚 Fixed some `go vet` complaints -### [0.2.0] - 2025-03-14 +### [0.2.0] - 2025-03-14 - ✨ New `Generator`s #### Features -- Add `IncrementalFormat`* Generators -- Add `Slug`* Generators -- Add `WithOriginalSlug`* Options -- Change signature of `Generator` function +- ✨ Add `IncrementalFormat`* Generators +- ✨ Add `Slug`* Generators +- ✨ Add `WithOriginalSlug`* Options +- 💔 Change signature of `Generator` function Note that this last change **is** a breaking change from 0.0.3, but only for custom Generators. -## [0.0.3] - 2025-03-11 +## [0.0.3] - 2025-03-11 - ✨ `WithSeparator` ### Features -- Added `WithSeparator` to allow for different separators between the parts of the generated filename. +- ✨ Added `WithSeparator` to allow for different separators between the parts of the generated filename. -## [0.0.2] - 2025-03-11 +## [0.0.2] - 2025-03-11 - 🐛 Bugfix Bugfix release ### Fixes -- Extension being ignored. Original included twice. +- 🐛 Extension being ignored. Original included twice. -## [0.0.1] - 2025-03-10 +## [0.0.1] - 2025-03-10 - 🚀 Initial Release Initial Release! Hope you like it! ### Added -- nomino.Make -- nomino.Config -- nomino.Generator +- ✨ `nomino.Make`` +- ✨ `nomino.Config` +- ✨ `nomino.Generator` + We needs more of these until I'm ready -- Lots of tests! +- ✅ Lots of tests! From 8072ae267abb03ea5404bc2c819d285a4aa26392 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Wed, 19 Mar 2025 16:16:00 -0500 Subject: [PATCH 19/29] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Extra=20backtick?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c21a656..5ec72e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ #### Support -- 📝 Some better examples` +- 📝 Some better examples ### [0.4.0] - 2025-03-15 - ✨ More Generators, and `Generator.Make` method From 480e36763f29acade5381c9707134887e05206bb Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Wed, 19 Mar 2025 17:48:23 -0500 Subject: [PATCH 20/29] =?UTF-8?q?=F0=9F=9B=A0=20Replace=20all=20linting=20?= =?UTF-8?q?with=20golangci-lint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .golangci.yaml | 39 +++++++++++++++++++++++++++++++ Taskfile.yml | 62 +++++--------------------------------------------- 2 files changed, 45 insertions(+), 56 deletions(-) create mode 100644 .golangci.yaml diff --git a/.golangci.yaml b/.golangci.yaml new file mode 100644 index 0000000..273e2af --- /dev/null +++ b/.golangci.yaml @@ -0,0 +1,39 @@ +linters: + enable: + - errcheck + - gosimple + - govet + - ineffassign + - staticcheck + - unused + - copyloopvar + - dupl + - err113 + - errname + - exptostd + - fatcontext + - funlen + - gocognit + - goconst + - gocritic + - gocyclo + - godot + - godox + - gosec + - perfsprint + - testifylint + +linters-settings: + testifylint: + enable-all: true + disable: + - require-error + gocognit: + min-complexity: 5 + gocyclo: + min-complexity: 5 + gocritic: + enable-all: true + settings: + hugeParam: + sizeThreshold: 255 diff --git a/Taskfile.yml b/Taskfile.yml index 35792f6..ef9f12e 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -5,7 +5,7 @@ tasks: cmds: - task: fmt - task: test - - task: build + - task: lint fmt: desc: Format go code @@ -22,66 +22,16 @@ tasks: 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 ./... - - cog-complex: - desc: Calculate cognitive complexity - sources: - - '**/*.go' - cmds: - - gocognit -over 5 . - - cyc-complex: - desc: Calculate cyclomatic complexity - sources: - - '**/*.go' - cmds: - - gocyclo -over 5 . - - complex: - desc: Calculate complexities - deps: - - cog-complex - - cyc-complex - - vuln: - desc: Check for vulnerabilities - sources: - - '**/*.go' - cmds: - - govulncheck ./... - lint: desc: Do static analysis - deps: - - vet - - critic - - staticcheck - - complex - - vuln + sources: + - '**/*.go' + cmds: + - golangci-lint run test: desc: Run unit tests - deps: [fmt, vet] + deps: [fmt] sources: - '**/*.go' generates: From 8f02956ecd7bb5bf3ee956eb59f7b2e0bc0912f0 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Wed, 19 Mar 2025 18:05:16 -0500 Subject: [PATCH 21/29] =?UTF-8?q?=F0=9F=9A=A8=20A=20bunch=20of=20small=20i?= =?UTF-8?q?mprovements=20from=20linter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gen_file.go | 12 ++++++------ gen_int.go | 6 +++--- gen_rand.go | 16 ++++++++++------ gen_rand_test.go | 5 ++--- gen_ts.go | 8 ++++---- generators.go | 3 ++- generators_examples_test.go | 10 +++++----- generators_test.go | 25 ++++++++++++------------- make_test.go | 7 ++++--- options.go | 6 +++--- 10 files changed, 51 insertions(+), 47 deletions(-) diff --git a/gen_file.go b/gen_file.go index 497fdcc..b602557 100644 --- a/gen_file.go +++ b/gen_file.go @@ -2,14 +2,14 @@ package nomino import ( "crypto" + "encoding/hex" "errors" - "fmt" "hash" "github.com/gosimple/slug" ) -// ErrMissingOriginal is the error returned by Slug if there is no filename +// ErrMissingOriginal is the error returned by Slug if there is no filename. var ErrMissingOriginal = errors.New("missing original filename") func getOriginal(c *Config) (string, error) { @@ -37,10 +37,10 @@ func Slug(lang ...string) Generator { } } -// HashingFunc is a function that generates a hash.Hash +// HashingFunc is a function that generates a hash.Hash. type HashingFunc func() hash.Hash -// New allows HashingFunc to be used as a Hasher +// New allows HashingFunc to be used as a Hasher. func (hf HashingFunc) New() hash.Hash { return hf() } @@ -51,7 +51,7 @@ type Hasher interface { New() hash.Hash } -// ErrInvalidHash is returned by the Hash generator when an invalid HashType is passed +// ErrInvalidHash is returned by the Hash generator when an invalid HashType is passed. var ErrInvalidHash = errors.New("invalid hash type") // Hash generates a name from a hash of the filename. @@ -70,6 +70,6 @@ func Hash(h Hasher) Generator { } hs := h.New() hs.Write([]byte(name)) - return fmt.Sprintf("%x", hs.Sum(nil)), nil + return hex.EncodeToString(hs.Sum(nil)), nil } } diff --git a/gen_int.go b/gen_int.go index aeb6076..3174d18 100644 --- a/gen_int.go +++ b/gen_int.go @@ -11,7 +11,7 @@ type incConf struct { cb func(int) string } -// IncrementalOption sets an option for the Incremental Generator +// IncrementalOption sets an option for the Incremental Generator. type IncrementalOption func(c *incConf) // Incremental generates a name that is a series of integers. @@ -30,7 +30,7 @@ func Incremental(opts ...IncrementalOption) Generator { } } -// IncrementalStart sets the starting integer for Incremental +// IncrementalStart sets the starting integer for Incremental. func IncrementalStart(start int) IncrementalOption { return func(c *incConf) { c.start = start @@ -45,7 +45,7 @@ func IncrementalStep(step int) IncrementalOption { } // IncrementalFormatsets the format for the number generated by Incremental. -// It will be formatted with Printf. This is mostly likely useful with a format like "%02d" +// It will be formatted with Printf. This is mostly likely useful with a format like "%02d". func IncrementalFormat(format string) IncrementalOption { return func(c *incConf) { c.cb = func(i int) string { diff --git a/gen_rand.go b/gen_rand.go index de71723..bdabef6 100644 --- a/gen_rand.go +++ b/gen_rand.go @@ -52,10 +52,10 @@ type randConf struct { length int } -// RandomOption is an option for the Random Generator +// RandomOption is an option for the Random Generator. type RandomOption func(*randConf) -// RandomLength controls the length of the string generated by Random +// RandomLength controls the length of the string generated by Random. func RandomLength(length int) RandomOption { return func(c *randConf) { c.length = length @@ -64,11 +64,17 @@ func RandomLength(length int) RandomOption { func getRandomBytes(l int) []byte { key := make([]byte, l) - rand.Read(key) + _, _ = rand.Read(key) e := base62.StdEncoding.Encode(key) return e[:l] } +func fillBuffer(buff *strings.Builder, length int) { + for buff.Len() < length { + buff.Write(getRandomBytes(length - buff.Len())) + } +} + // Random generates a random string containing the characters [A-Za-z0-9]. // By default, it will be eight characters long. func Random(opts ...RandomOption) Generator { @@ -79,9 +85,7 @@ func Random(opts ...RandomOption) Generator { return func(*Config) (string, error) { var buff strings.Builder buff.Grow(c.length) - for buff.Len() < c.length { - buff.Write(getRandomBytes(c.length - buff.Len())) - } + fillBuffer(&buff, c.length) return buff.String(), nil } } diff --git a/gen_rand_test.go b/gen_rand_test.go index 51fc73f..193ac51 100644 --- a/gen_rand_test.go +++ b/gen_rand_test.go @@ -1,7 +1,6 @@ package nomino import ( - "errors" "testing" "github.com/google/uuid" @@ -18,7 +17,7 @@ func TestUUID(t *testing.T) { type badRead struct{} func (badRead) Read([]byte) (int, error) { - return 0, errors.New("sorry") + return 0, errTest } func TestUUIDFail(t *testing.T) { @@ -26,7 +25,7 @@ func TestUUIDFail(t *testing.T) { defer uuid.SetRand(nil) _, err := UUID(nil)(nil) - assert.Equal(t, errors.New("sorry"), err) + assert.ErrorIs(t, err, errTest) } func TestRand(t *testing.T) { diff --git a/gen_ts.go b/gen_ts.go index abb4181..05d6e0c 100644 --- a/gen_ts.go +++ b/gen_ts.go @@ -2,10 +2,10 @@ package nomino import "time" -// FileTimestamp is the default format for WithTimestamp and WithTime +// FileTimestamp is the default format for WithTimestamp and WithTime. const FileTimestamp string = "2006-01-02T15-04-05-0700" -// FileTimestampNoTZ is the default format for WithTimestampUTC and WithTimeUTC +// FileTimestampNoTZ is the default format for WithTimestampUTC and WithTimeUTC. const FileTimestampNoTZ string = "2006-01-02T15-04-05" type timestampConf struct { @@ -14,11 +14,11 @@ type timestampConf struct { utc bool } -// TimestampOption provides options for the Timestamp Generator +// TimestampOption provides options for the Timestamp Generator. type TimestampOption func(c *timestampConf) // Timestamp generates a a date and time. By default, it uses the current time, and will. -// be formatted accourding to FileTimestamp +// be formatted accourding to FileTimestamp. func Timestamp(opts ...TimestampOption) Generator { c := timestampConf{format: FileTimestamp, ts: time.Now()} for _, opt := range opts { diff --git a/generators.go b/generators.go index e01ccc6..9e31913 100644 --- a/generators.go +++ b/generators.go @@ -21,7 +21,7 @@ func (g Generator) MakeWithConfig(c Config) (string, error) { return Make(c.AddOptions(WithGenerator(g))) } -// WithGenerator sets the specified generator +// WithGenerator sets the specified generator. func WithGenerator(g Generator) Option { return func(c *Config) { c.generator = g @@ -66,6 +66,7 @@ func MultiGeneratorRandomOrder(gens ...Generator) Generator { } return func(c *Config) (string, error) { + //nolint:gosec // This is not security sensitive, so a weak number generator is fine. idx := rand.Int() % len(gens) return gens[idx](c) } diff --git a/generators_examples_test.go b/generators_examples_test.go index 5efc4d8..b552089 100644 --- a/generators_examples_test.go +++ b/generators_examples_test.go @@ -37,7 +37,7 @@ func ExampleGenerator_Make() { func ExampleMultiGeneratorInOrder() { gen1 := func(*nomino.Config) (string, error) { - return "hello", nil + return "bonjour", nil } gen2 := func(*nomino.Config) (string, error) { return "goodbye", nil @@ -54,17 +54,17 @@ func ExampleMultiGeneratorInOrder() { fmt.Println(str) // Output: - // hello.txt + // bonjour.txt // goodbye.txt - // hello.txt + // bonjour.txt } func ExampleMultiGeneratorRandomOrder() { gen1 := func(*nomino.Config) (string, error) { - return "hello", nil + return "guten-tag", nil } gen2 := func(*nomino.Config) (string, error) { - return "goodbye", nil + return "wiedersehen", nil } gen := nomino.MultiGeneratorRandomOrder(gen1, gen2) diff --git a/generators_test.go b/generators_test.go index f8a81fa..4c257ab 100644 --- a/generators_test.go +++ b/generators_test.go @@ -7,15 +7,6 @@ import ( "github.com/stretchr/testify/assert" ) -func TestWithGenerator(t *testing.T) { - g := func(*Config) (string, error) { return "abc", nil } - var c Config - WithGenerator(g)(&c) - st, err := c.generator(&c) - assert.NoError(t, err) - assert.Equal(t, "abc", st) -} - const ( out1 string = "abc" out2 string = "def" @@ -30,6 +21,15 @@ var ( gens = []Generator{gen1, gen2, gen3} ) +func TestWithGenerator(t *testing.T) { + g := func(*Config) (string, error) { return out1, nil } + var c Config + WithGenerator(g)(&c) + st, err := c.generator(&c) + assert.NoError(t, err) + assert.Equal(t, out1, st) +} + func TestMultiGeneratorInOrder(t *testing.T) { g := MultiGeneratorInOrder(gens...) st, err := g(nil) @@ -47,16 +47,15 @@ func TestMultiGeneratorInOrder(t *testing.T) { } func TestMultiGeneratorInOrderOne(t *testing.T) { - st1 := "abc" - g1 := func(*Config) (string, error) { return st1, nil } + g1 := func(*Config) (string, error) { return out1, nil } g := MultiGeneratorInOrder(g1) st, err := g(nil) assert.NoError(t, err) - assert.Equal(t, st1, st) + assert.Equal(t, out1, st) st, err = g(nil) assert.NoError(t, err) - assert.Equal(t, st1, st) + assert.Equal(t, out1, st) } func TestMultiGeneratorInOrderMissing(t *testing.T) { diff --git a/make_test.go b/make_test.go index 0970c48..1ebb48e 100644 --- a/make_test.go +++ b/make_test.go @@ -7,6 +7,8 @@ import ( "github.com/stretchr/testify/assert" ) +var errTest = errors.New("sorry") + func TestMake(t *testing.T) { genOpt := WithGenerator(func(*Config) (string, error) { return "abc", nil }) testcases := []struct { @@ -48,11 +50,10 @@ func TestMake(t *testing.T) { } func TestMakeErr(t *testing.T) { - retErr := errors.New("oops") - conf := NewConfig(WithGenerator(func(*Config) (string, error) { return "foobar", retErr })) + conf := NewConfig(WithGenerator(func(*Config) (string, error) { return "foobar", errTest })) st, err := Make(conf) assert.Zero(t, st) - assert.ErrorIs(t, err, retErr) + assert.ErrorIs(t, err, errTest) } func TestMakeDoesntChangeConf(t *testing.T) { diff --git a/options.go b/options.go index c7e0338..6cc87a8 100644 --- a/options.go +++ b/options.go @@ -18,7 +18,7 @@ func WithOriginal(o string) Option { } // WithOriginal sets the original filename as a slug. -// This should not be used with the Slug Generator (as it would be redundant) +// This should not be used with the Slug Generator (as it would be redundant). func WithOriginalSlug(o string) Option { return func(c *Config) { c.original = slug.Make(o) @@ -26,7 +26,7 @@ func WithOriginalSlug(o string) Option { } // WithOriginal sets the original filename as a slug, taking the language into account. -// This should not be used with the Slug Generator (as it would be redundant) +// This should not be used with the Slug Generator (as it would be redundant). func WithOriginalSlugLang(o, lang string) Option { return func(c *Config) { c.original = slug.MakeLang(o, lang) @@ -47,7 +47,7 @@ func WithSuffix(s string) Option { } } -// WithoutExtension sets no extension for the generated filename. By default, it will be txt +// WithoutExtension sets no extension for the generated filename. By default, it will be txt. func WithoutExtension() Option { return func(c *Config) { c.extension = "" From 797c616447a7496994b15f334888619e971890ae Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Wed, 19 Mar 2025 18:45:36 -0500 Subject: [PATCH 22/29] =?UTF-8?q?=F0=9F=93=9D=20Add=20package=20level=20ex?= =?UTF-8?q?ample?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples_test.go | 56 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 examples_test.go diff --git a/examples_test.go b/examples_test.go new file mode 100644 index 0000000..ee5f8e0 --- /dev/null +++ b/examples_test.go @@ -0,0 +1,56 @@ +package nomino_test + +import ( + "fmt" + + "codeberg.org/danjones000/nomino" +) + +// Define a global Config. +func NominoConfig() nomino.Config { + return nomino.NewConfig( + nomino.WithSeparator("-"), + nomino.WithPrefix("upload"), + nomino.WithGenerator(nomino.UUID( + nomino.UUIDv7, + )), + ) +} + +// / HandleImgUploads generates new filenames for images with a png extension. +func HandleImgUploads(orig string) string { + newName, _ := nomino.Make( + NominoConfig(), + nomino.WithExtension("png"), + nomino.WithOriginalSlug(orig), + ) + + return newName +} + +// HandleVidUploads generates a new filename for videos. +// We ignore the original filename and use a timestamp for the generated part +// with a webm extension. +func HandleVidUploads() string { + newName, _ := nomino.Timestamp(nomino.TimestampUTC()). + MakeWithConfig(NominoConfig().AddOptions( + nomino.WithExtension("webm"), + )) + return newName +} + +// Example shows how to use nomino. +func Example() { + // Pretend we have an image upload + filename := "George" + uploadImgName := HandleImgUploads(filename) + + // Upload to storage + fmt.Println(uploadImgName) + + // New Video Upload + uploadVidName := HandleVidUploads() + + // Upload to storage + fmt.Println(uploadVidName) +} From c32a15f4a1bbd48ded03a737dc198bca91d50460 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Sun, 23 Mar 2025 10:52:54 -0500 Subject: [PATCH 23/29] =?UTF-8?q?=F0=9F=92=A1=20Add=20some=20helpful=20com?= =?UTF-8?q?ments=20in=20example?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples_test.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/examples_test.go b/examples_test.go index ee5f8e0..c929985 100644 --- a/examples_test.go +++ b/examples_test.go @@ -17,8 +17,10 @@ func NominoConfig() nomino.Config { ) } -// / HandleImgUploads generates new filenames for images with a png extension. +// HandleImgUploads generates new filenames for images with a png extension. func HandleImgUploads(orig string) string { + // Here, we use nomino.Make function to generate the filename. + // We use our global config, and add in a few extra Options specific to this. newName, _ := nomino.Make( NominoConfig(), nomino.WithExtension("png"), @@ -32,6 +34,8 @@ func HandleImgUploads(orig string) string { // We ignore the original filename and use a timestamp for the generated part // with a webm extension. func HandleVidUploads() string { + // Because we're using a different Generator, we chose to use the Make method on the Generator. + // We add in additional Options with the `AddOptions` method on the `Config` newName, _ := nomino.Timestamp(nomino.TimestampUTC()). MakeWithConfig(NominoConfig().AddOptions( nomino.WithExtension("webm"), @@ -39,7 +43,7 @@ func HandleVidUploads() string { return newName } -// Example shows how to use nomino. +// This example shows how to use nomino. func Example() { // Pretend we have an image upload filename := "George" From ac3d1a556575e068e859efb3f600f6bd963c71cb Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Sun, 23 Mar 2025 10:53:49 -0500 Subject: [PATCH 24/29] =?UTF-8?q?=F0=9F=92=A1=20Add=20package=20level=20do?= =?UTF-8?q?c=20comment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nomino.go | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 nomino.go diff --git a/nomino.go b/nomino.go new file mode 100644 index 0000000..8310519 --- /dev/null +++ b/nomino.go @@ -0,0 +1,6 @@ +// Package nomino is a utility that allows us to generate random filenames. +// +// There are two main methods of using nomino. +// 1. Using the `nomini.Make` function. +// 2. Creating a generator, and using its `Make` method. +package nomino From c5a9f0916656d57adadc1d19dd8585da0fa7d656 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Mon, 31 Mar 2025 12:10:05 -0500 Subject: [PATCH 25/29] =?UTF-8?q?=F0=9F=92=A1=20Improve=20go=20doc=20comme?= =?UTF-8?q?nts=20with=20internal=20links?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.go | 7 +++++-- gen_file.go | 12 ++++++------ gen_int.go | 8 ++++---- gen_rand.go | 12 ++++++------ gen_ts.go | 10 +++++----- generators.go | 12 ++++++------ nomino.go | 5 +++-- options.go | 6 +++--- 8 files changed, 38 insertions(+), 34 deletions(-) diff --git a/config.go b/config.go index c889a06..0c06449 100644 --- a/config.go +++ b/config.go @@ -1,5 +1,6 @@ package nomino +// Config controls how the generatred filename is created. type Config struct { original string prefix string @@ -9,7 +10,9 @@ type Config struct { generator Generator } -// NewConfig returns a new Config with the specified Options. +// NewConfig returns a new [Config] with the specified [Option]s. +// With no Options, the Config uses an extension of .txt, a separator +// of _, and the [UUID] [Generator]. func NewConfig(options ...Option) Config { conf := Config{ extension: ".txt", @@ -22,7 +25,7 @@ func NewConfig(options ...Option) Config { return conf } -// AddOptions creates a new Config with options added. +// AddOptions creates a new [Config] with the given [Option]s added. func (c Config) AddOptions(options ...Option) Config { for _, opt := range options { opt(&c) diff --git a/gen_file.go b/gen_file.go index b602557..c701f11 100644 --- a/gen_file.go +++ b/gen_file.go @@ -9,7 +9,7 @@ import ( "github.com/gosimple/slug" ) -// ErrMissingOriginal is the error returned by Slug if there is no filename. +// ErrMissingOriginal is the error returned by [Slug] if there is no filename. var ErrMissingOriginal = errors.New("missing original filename") func getOriginal(c *Config) (string, error) { @@ -37,21 +37,21 @@ func Slug(lang ...string) Generator { } } -// HashingFunc is a function that generates a hash.Hash. +// HashingFunc is a function that generates a [hash.Hash]. type HashingFunc func() hash.Hash -// New allows HashingFunc to be used as a Hasher. +// New allows [HashingFunc] to be used as a [Hasher]. func (hf HashingFunc) New() hash.Hash { return hf() } -// Hasher is a type returns a hash.Hash. -// All crypto.Hash may be used. +// Hasher is a type returns a [hash.Hash]. +// All [crypto.Hash] may be used. type Hasher interface { New() hash.Hash } -// ErrInvalidHash is returned by the Hash generator when an invalid HashType is passed. +// ErrInvalidHash is returned by the [Hash] [Generator] when an invalid [Hasher] is passed. var ErrInvalidHash = errors.New("invalid hash type") // Hash generates a name from a hash of the filename. diff --git a/gen_int.go b/gen_int.go index 3174d18..bb57261 100644 --- a/gen_int.go +++ b/gen_int.go @@ -11,7 +11,7 @@ type incConf struct { cb func(int) string } -// IncrementalOption sets an option for the Incremental Generator. +// IncrementalOption sets an option for the [Incremental] [Generator]. type IncrementalOption func(c *incConf) // Incremental generates a name that is a series of integers. @@ -30,21 +30,21 @@ func Incremental(opts ...IncrementalOption) Generator { } } -// IncrementalStart sets the starting integer for Incremental. +// IncrementalStart sets the starting integer for [Incremental]. func IncrementalStart(start int) IncrementalOption { return func(c *incConf) { c.start = start } } -// IncrementalStepsets the step by which Incremental increases with each invocation. +// IncrementalStepsets the step by which [Incremental] increases with each invocation. func IncrementalStep(step int) IncrementalOption { return func(c *incConf) { c.step = step } } -// IncrementalFormatsets the format for the number generated by Incremental. +// IncrementalFormatsets the format for the number generated by [Incremental]. // It will be formatted with Printf. This is mostly likely useful with a format like "%02d". func IncrementalFormat(format string) IncrementalOption { return func(c *incConf) { diff --git a/gen_rand.go b/gen_rand.go index bdabef6..7a9e9ea 100644 --- a/gen_rand.go +++ b/gen_rand.go @@ -9,7 +9,7 @@ import ( ) // UUIDer is an interface for generating UUIDs. -// It is recommended that you use either the UUIDv4 or UUIDv7 variables. +// It is recommended that you use either the [UUIDv4] or [UUIDv7] variables. type UUIDer interface { UUID() (uuid.UUID, error) } @@ -17,7 +17,7 @@ type UUIDer interface { // UUIDFunc is a function that generates a UUID. type UUIDFunc func() (uuid.UUID, error) -// UUID allows UUIDFunc to be used as a UUIDer. +// UUID allows [UUIDFunc] to be used as a [UUIDer]. func (u UUIDFunc) UUID() (uuid.UUID, error) { return u() } @@ -33,7 +33,7 @@ var ( UUIDv7 = UUIDFunc(uuid.NewV7) ) -// UUID generates a UUID. If nil.is passed as an argument, +// UUID generates a UUID. If nil is passed as an argument, // a UUIDv4 is generated. func UUID(u UUIDer) Generator { if u == nil { @@ -52,10 +52,10 @@ type randConf struct { length int } -// RandomOption is an option for the Random Generator. +// RandomOption is an option for the [Random] [Generator]. type RandomOption func(*randConf) -// RandomLength controls the length of the string generated by Random. +// RandomLength controls the length of the string generated by [Random]. func RandomLength(length int) RandomOption { return func(c *randConf) { c.length = length @@ -75,7 +75,7 @@ func fillBuffer(buff *strings.Builder, length int) { } } -// Random generates a random string containing the characters [A-Za-z0-9]. +// Random generates a random string containing the characters A-Za-z0-9. // By default, it will be eight characters long. func Random(opts ...RandomOption) Generator { c := randConf{8} diff --git a/gen_ts.go b/gen_ts.go index 05d6e0c..62fe703 100644 --- a/gen_ts.go +++ b/gen_ts.go @@ -2,10 +2,10 @@ package nomino import "time" -// FileTimestamp is the default format for WithTimestamp and WithTime. +// FileTimestamp is the default format for [Timestamp]. const FileTimestamp string = "2006-01-02T15-04-05-0700" -// FileTimestampNoTZ is the default format for WithTimestampUTC and WithTimeUTC. +// FileTimestampNoTZ is the default format when using the [TimestampUTC] [TimestampOption]. const FileTimestampNoTZ string = "2006-01-02T15-04-05" type timestampConf struct { @@ -14,7 +14,7 @@ type timestampConf struct { utc bool } -// TimestampOption provides options for the Timestamp Generator. +// TimestampOption provides options for the [Timestamp] [Generator]. type TimestampOption func(c *timestampConf) // Timestamp generates a a date and time. By default, it uses the current time, and will. @@ -35,7 +35,7 @@ func Timestamp(opts ...TimestampOption) Generator { } // TimestampFormat sets the format for the generated name. -// Consult time.Time.Format for details on the format. +// Consult [time.Time.Format] for details on the format. func TimestampFormat(format string) TimestampOption { return func(c *timestampConf) { c.format = format @@ -43,7 +43,7 @@ func TimestampFormat(format string) TimestampOption { } // TimestampTime sets the time for the generated name. -// By default, it uses the current time. +// By default, [Timestamp] uses the current time. func TimestampTime(t time.Time) TimestampOption { return func(c *timestampConf) { c.ts = t diff --git a/generators.go b/generators.go index 9e31913..fe7b59d 100644 --- a/generators.go +++ b/generators.go @@ -10,25 +10,25 @@ import ( // for example. type Generator func(conf *Config) (string, error) -// Make allows you to generate a new string directly from a Generator. +// Make allows you to generate a new string directly from a [Generator]. func (g Generator) Make(opts ...Option) (string, error) { return g.MakeWithConfig(NewConfig(opts...)) } -// MakeWithConfig allows you to generate a new string directly from a Generator +// MakeWithConfig allows you to generate a new string directly from a [Generator] // with a pre-existing Config. func (g Generator) MakeWithConfig(c Config) (string, error) { return Make(c.AddOptions(WithGenerator(g))) } -// WithGenerator sets the specified generator. +// WithGenerator sets the specified [Generator]. func WithGenerator(g Generator) Option { return func(c *Config) { c.generator = g } } -// ErrMissingGenerators is returned by a multi-generator if no generators are supplied. +// ErrMissingGenerators is returned by a multi-generator if no [Generator]s are supplied. var ErrMissingGenerators = errors.New("no generators supplied") func missingGen(*Config) (string, error) { @@ -36,7 +36,7 @@ func missingGen(*Config) (string, error) { } // 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. +// If none are passed, the generator will always return [ErrMissingGenerators]. func MultiGeneratorInOrder(gens ...Generator) Generator { if len(gens) == 0 { return missingGen @@ -55,7 +55,7 @@ func MultiGeneratorInOrder(gens ...Generator) Generator { } // MultiGeneratorRandomOrder allows the use of multiple generators. Each new invokation will use one of the generators randomly. -// If none are passed, the generator will always return ErrMissingGenerators. +// If none are passed, the generator will always return [ErrMissingGenerators]. func MultiGeneratorRandomOrder(gens ...Generator) Generator { if len(gens) == 0 { return missingGen diff --git a/nomino.go b/nomino.go index 8310519..185fcc3 100644 --- a/nomino.go +++ b/nomino.go @@ -1,6 +1,7 @@ // Package nomino is a utility that allows us to generate random filenames. // // There are two main methods of using nomino. -// 1. Using the `nomini.Make` function. -// 2. Creating a generator, and using its `Make` method. +// +// 1. Using the [Make] function. +// 2. Creating a generator, and using its [Generator.Make] method. package nomino diff --git a/options.go b/options.go index 6cc87a8..3550b02 100644 --- a/options.go +++ b/options.go @@ -6,7 +6,7 @@ import ( "github.com/gosimple/slug" ) -// Option sets configuration parameters for Config. +// Option sets configuration parameters for [Config]. type Option func(c *Config) // WithOriginal sets the original filename. @@ -18,7 +18,7 @@ func WithOriginal(o string) Option { } // WithOriginal sets the original filename as a slug. -// This should not be used with the Slug Generator (as it would be redundant). +// This should not be used with the [Slug] [Generator] (as it would be redundant). func WithOriginalSlug(o string) Option { return func(c *Config) { c.original = slug.Make(o) @@ -26,7 +26,7 @@ func WithOriginalSlug(o string) Option { } // WithOriginal sets the original filename as a slug, taking the language into account. -// This should not be used with the Slug Generator (as it would be redundant). +// This should not be used with the [Slug] [Generator] (as it would be redundant). func WithOriginalSlugLang(o, lang string) Option { return func(c *Config) { c.original = slug.MakeLang(o, lang) From 70c74c2b03218521c57e9083e74ac1fdf02ca943 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Mon, 31 Mar 2025 15:46:48 -0500 Subject: [PATCH 26/29] =?UTF-8?q?=F0=9F=92=A1=20Fix=20a=20few=20go=20doc?= =?UTF-8?q?=20comments?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.go | 4 ++-- gen_rand.go | 2 +- generators.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/config.go b/config.go index 0c06449..54456bf 100644 --- a/config.go +++ b/config.go @@ -10,7 +10,7 @@ type Config struct { generator Generator } -// NewConfig returns a new [Config] with the specified [Option]s. +// NewConfig returns a new [Config] with each [Option] specified. // With no Options, the Config uses an extension of .txt, a separator // of _, and the [UUID] [Generator]. func NewConfig(options ...Option) Config { @@ -25,7 +25,7 @@ func NewConfig(options ...Option) Config { return conf } -// AddOptions creates a new [Config] with the given [Option]s added. +// AddOptions creates a new [Config] with each [Option] added. func (c Config) AddOptions(options ...Option) Config { for _, opt := range options { opt(&c) diff --git a/gen_rand.go b/gen_rand.go index 7a9e9ea..69f0487 100644 --- a/gen_rand.go +++ b/gen_rand.go @@ -8,7 +8,7 @@ import ( "github.com/google/uuid" ) -// UUIDer is an interface for generating UUIDs. +// UUIDer is an interface for generating UUIDs, by the [UUID] [Generator]. // It is recommended that you use either the [UUIDv4] or [UUIDv7] variables. type UUIDer interface { UUID() (uuid.UUID, error) diff --git a/generators.go b/generators.go index fe7b59d..28b666b 100644 --- a/generators.go +++ b/generators.go @@ -28,7 +28,7 @@ func WithGenerator(g Generator) Option { } } -// ErrMissingGenerators is returned by a multi-generator if no [Generator]s are supplied. +// ErrMissingGenerators is returned by a multi-generator if a [Generator] isn't supplied. var ErrMissingGenerators = errors.New("no generators supplied") func missingGen(*Config) (string, error) { From cdb504c1a3bcac46e2713742b68b7d3fcb807b09 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Mon, 31 Mar 2025 15:47:36 -0500 Subject: [PATCH 27/29] =?UTF-8?q?=F0=9F=93=9D=20Fill=20out=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 75 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 09b7bb7..96bc19a 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,80 @@ It takes a lot of inspiration (although no actual code) from [Onym](https://gith Note that this is still not at a stable release. There will be breaking changes between minor releases until it reaches 1.0.0. Patch releases shouldn't contain breaking changes however. Once it reaches 1.0.0, breaking changes will only happen between major releases. -## TODO +Make sure to check out the [official documentation][docs]. -I'll fill this out more in depth later. +## Installation -For now, check [official documentation](https://pkg.go.dev/codeberg.org/danjones000/nomino). +Add `codeberg.org/danjones000/nomino` to your project. You can do `go get codeberg.org/danjones000/nomino`, or simply add it to an import and run `go mod tidy`. + +But, you probably already know this. + +## Usage + +There are **a lot** of examples in the [official documentation][examples]. Take a look through these to get an idea. + +The main concept in `nomino` is the [Generator][]. The simplest way to generate a random filename, is to use the [Generator][] directly, in this way: + +```go +import ( + "fmt" + "codeberg.org/danjones000/nomino" +) + +func main() { + name, _ := nomino.Random().Make() + fmt.Println(name) // Du3Sfh8p.txt +} +``` + +The second argument is an error. Most of the generators are always successful, and that error will be `nil`, but you should check the errors if you're not sure. + +The second way to generate a new filename is using the [Make][] function. + +```go +import ( + "fmt" + "codeberg.org/danjones000/nomino" +) + +func main() { + config := nomino.NewConfig(nomino.WithGenerator(nomino.Random())) + name, _ := nomino.Make(config) + fmt.Println(name) // Du3Sfh8p.txt +} +``` + +Although in these examples, `nomino.Make` is more verbose, it can be beneficial to using that function if you have some customizations to how you generate the filenames. + +### Configuration + +The [Config][] allows you to customize how the generated filename works with various [Options][Option]. The [options][Option] allows you to customize things like [adding a prefix](https://pkg.go.dev/codeberg.org/danjones000/nomino#WithPrefix), or changing the [extension](https://pkg.go.dev/codeberg.org/danjones000/nomino#WithExtension) of the generated filename (by default, it uses `.txt`). + +Have a look at [all the Options][Option]. + +### Generator + +The [Generator][] is the piece that returns the "random" portion of the generated filename, although, it doesn't actually have to be random. + +Here are the built-in [Generators][Generator]: + +- [UUID](https://pkg.go.dev/codeberg.org/danjones000/nomino#UUID) generates a UUID. This is the default if none is specified. +- [Random](https://pkg.go.dev/codeberg.org/danjones000/nomino#Random) generates a random string. By default, it's 8 characters long, but can be whatever length you provide. +- [Incremental](https://pkg.go.dev/codeberg.org/danjones000/nomino#Incremental) will generate just a series of integers, starting at 0. +- [Timestamp](https://pkg.go.dev/codeberg.org/danjones000/nomino#Timestamp) generates a string from the current time. It will look like "2009-11-10T23-00-00+0000.txt", although this can be customized. +- Both [Slug](https://pkg.go.dev/codeberg.org/danjones000/nomino#Slug) and [Hash](https://pkg.go.dev/codeberg.org/danjones000/nomino#Hash) work on the original name provided by [WithOriginal](https://pkg.go.dev/codeberg.org/danjones000/nomino#WithOriginal). Slug generats a slug from the name, while Hash hashes it. By default, it uses MD5. + +You can also use multiple generators, either [in order](https://pkg.go.dev/codeberg.org/danjones000/nomino#MultiGeneratorInOrder), or [in a random order](https://pkg.go.dev/codeberg.org/danjones000/nomino#MultiGeneratorRandomOrder). + +Finally, you can create a [custom generator](https://pkg.go.dev/codeberg.org/danjones000/nomino#example-WithGenerator-CustomGenerator) as well. + +## RTFM (Read the fabulous manual) + +[Official docs][docs], especially the [examples][]. + +[docs]: https://pkg.go.dev/codeberg.org/danjones000/nomino +[examples]: https://pkg.go.dev/codeberg.org/danjones000/nomino#pkg-examples +[Generator]: https://pkg.go.dev/codeberg.org/danjones000/nomino#Generator +[Config]: https://pkg.go.dev/codeberg.org/danjones000/nomino#Config +[Option]: https://pkg.go.dev/codeberg.org/danjones000/nomino#Option +[Make]: https://pkg.go.dev/codeberg.org/danjones000/nomino#Make From 17961ddd41ec8259b11fc3af4d4bea8da03a38c7 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Mon, 31 Mar 2025 15:52:10 -0500 Subject: [PATCH 28/29] =?UTF-8?q?=F0=9F=93=9D=20Add=20one=20more=20link=20?= =?UTF-8?q?to=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 96bc19a..e0b3f51 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,6 @@ The purpose of nomino is to generate (probably random) filenames, for example, i It takes a lot of inspiration (although no actual code) from [Onym](https://github.com/Blaspsoft/onym). -Note that this is still not at a stable release. There will be breaking changes between minor releases until it reaches 1.0.0. Patch releases shouldn't contain breaking changes however. Once it reaches 1.0.0, breaking changes will only happen between major releases. - Make sure to check out the [official documentation][docs]. ## Installation @@ -75,7 +73,7 @@ Finally, you can create a [custom generator](https://pkg.go.dev/codeberg.org/dan ## RTFM (Read the fabulous manual) -[Official docs][docs], especially the [examples][]. +[Official docs][docs], especially the [examples][]. Especially check out the [full example](https://pkg.go.dev/codeberg.org/danjones000/nomino#example-package), which includes how to use a global configuration. [docs]: https://pkg.go.dev/codeberg.org/danjones000/nomino [examples]: https://pkg.go.dev/codeberg.org/danjones000/nomino#pkg-examples From 2561fe3cff93c025c6a6fada7569ebaf08265f84 Mon Sep 17 00:00:00 2001 From: Dan Jones Date: Mon, 31 Mar 2025 15:56:34 -0500 Subject: [PATCH 29/29] =?UTF-8?q?=F0=9F=93=9D=20Update=20CHANGELOG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ec72e7..9020008 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Changelog +### [1.0.0] - 2025-03-31 - 🚀 Stable release! + +#### Support + +- 📝 Vastly improved [go docs](https://pkg.go.dev/codeberg.org/danjones000/nomino). +- 📝 Fill out README with examples and links. + +#### Tools + +- 🛠 Replace all linting with golangci-lint + +#### Code quality + +- 🚨 A bunch of small improvements from new linter + ### [0.5.0] - 2025-03-19 - ✨ Different types of UUIDs #### Features