diff --git a/CHANGELOG.md b/CHANGELOG.md index c2674b9..0611f05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,21 +1,5 @@ # 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 diff --git a/Taskfile.yml b/Taskfile.yml index 35792f6..474a888 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -26,6 +26,7 @@ tasks: desc: Vet go code sources: - '**/*.go' + deps: [gen] cmds: - go vet ./... @@ -81,7 +82,7 @@ tasks: test: desc: Run unit tests - deps: [fmt, vet] + deps: [fmt, vet, gen] sources: - '**/*.go' generates: diff --git a/config.go b/config.go index a7da5f5..5c87729 100644 --- a/config.go +++ b/config.go @@ -9,7 +9,6 @@ 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/gen_file.go b/gen_file.go index 497fdcc..ef239cb 100644 --- a/gen_file.go +++ b/gen_file.go @@ -1,7 +1,9 @@ package nomino import ( - "crypto" + "crypto/md5" + "crypto/sha1" + "crypto/sha256" "errors" "fmt" "hash" @@ -40,35 +42,39 @@ func Slug(lang ...string) Generator { // HashingFunc is a function that generates a hash.Hash type HashingFunc func() hash.Hash -// New allows HashingFunc to be used as a Hasher -func (hf HashingFunc) New() hash.Hash { - return hf() -} +//go:generate stringer -type=HashType -trimprefix=Hash -// Hasher is a type returns a hash.Hash. -// All crypto.Hash may be used. -type Hasher interface { - New() hash.Hash -} +// HashType represents a particular hashing algorithm +type HashType uint8 -// ErrInvalidHash is returned by the Hash generator when an invalid HashType is passed -var ErrInvalidHash = errors.New("invalid hash type") +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(h Hasher) Generator { - if h == nil { - h = crypto.MD5 - } +func Hash(t HashType) Generator { + f, ok := hashMap[t] return func(c *Config) (string, error) { - if h == crypto.MD5SHA1 { - return "", ErrInvalidHash + if !ok { + return "", fmt.Errorf("%w: %s", ErrInvalidHashType, t) } name, err := getOriginal(c) if err != nil { return "", err } - hs := h.New() + 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 index 76f3260..3d8036f 100644 --- a/gen_file_examples_test.go +++ b/gen_file_examples_test.go @@ -1,56 +1,66 @@ package nomino_test import ( - "crypto" - "crypto/hmac" "fmt" - "hash" "codeberg.org/danjones000/nomino" ) func ExampleSlug() { - str, _ := nomino.Slug().Make(nomino.WithOriginal("My name is Jimmy")) + 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() { - str, _ := nomino.Slug("de"). - Make(nomino.WithOriginal("Diese & Dass")) + 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() { - str, _ := nomino.Hash(crypto.MD5). - Make(nomino.WithOriginal("foobar")) + 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() { - str, _ := nomino.Hash(crypto.SHA1). - Make(nomino.WithOriginal("foobar")) + 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() { - str, _ := nomino.Hash(crypto.SHA256). - Make(nomino.WithOriginal("foobar")) + conf := nomino.NewConfig( + nomino.WithOriginal("foobar"), + nomino.WithGenerator( + nomino.Hash(nomino.HashSHA256), + ), + ) + str, _ := nomino.Make(conf) 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 cd0a8bf..0d03d16 100644 --- a/gen_file_test.go +++ b/gen_file_test.go @@ -1,7 +1,6 @@ package nomino import ( - "crypto" "testing" "github.com/stretchr/testify/assert" @@ -23,15 +22,21 @@ func TestSlugRemovesOriginal(t *testing.T) { } func TestHashBadHash(t *testing.T) { - conf := NewConfig(WithOriginal("foobar"), WithGenerator(Hash(crypto.MD5SHA1))) + conf := NewConfig(WithOriginal("foobar"), WithGenerator(Hash(0))) st, err := conf.generator(&conf) assert.Equal(t, "", st) - assert.ErrorIs(t, err, ErrInvalidHash) + assert.ErrorIs(t, err, ErrInvalidHashType) + assert.ErrorContains(t, err, "invalid hash type: HashType(0)") } func TestHashMissingOriginal(t *testing.T) { - conf := NewConfig(WithGenerator(Hash(nil))) + 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/gen_rand.go b/gen_rand.go deleted file mode 100644 index 8b4ce5a..0000000 --- a/gen_rand.go +++ /dev/null @@ -1,60 +0,0 @@ -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 deleted file mode 100644 index 45cb7fe..0000000 --- a/gen_rand_examples_test.go +++ /dev/null @@ -1,36 +0,0 @@ -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 deleted file mode 100644 index 832275b..0000000 --- a/gen_rand_test.go +++ /dev/null @@ -1,42 +0,0 @@ -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 c7ad4e8..25cd36e 100644 --- a/generators.go +++ b/generators.go @@ -2,7 +2,8 @@ package nomino import ( "errors" - "math/rand" + + "github.com/google/uuid" ) // Generator is a function that returns the "random" portion of the returned filename. @@ -10,12 +11,6 @@ import ( // 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) { @@ -49,19 +44,15 @@ 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. -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) +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 5efc4d8..c9d8710 100644 --- a/generators_examples_test.go +++ b/generators_examples_test.go @@ -7,14 +7,18 @@ import ( ) func ExampleWithGenerator_customGenerator() { - var gen nomino.Generator = func(*nomino.Config) (string, error) { + gen := func(*nomino.Config) (string, error) { return "hello", nil } + option := nomino.WithGenerator(gen) - str, _ := gen.Make() + str, _ := nomino.Make(nomino.NewConfig(option)) fmt.Println(str) - str, _ = gen.Make(nomino.WithoutExtension()) + str, _ = nomino.Make(nomino.NewConfig( + option, + nomino.WithoutExtension(), + )) fmt.Println(str) // Output: @@ -22,19 +26,6 @@ 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 @@ -43,14 +34,15 @@ func ExampleMultiGeneratorInOrder() { return "goodbye", nil } gen := nomino.MultiGeneratorInOrder(gen1, gen2) + option := nomino.WithGenerator(gen) - str, _ := gen.Make() + str, _ := nomino.Make(nomino.NewConfig(option)) fmt.Println(str) - str, _ = gen.Make() + str, _ = nomino.Make(nomino.NewConfig(option)) fmt.Println(str) - str, _ = gen.Make() + str, _ = nomino.Make(nomino.NewConfig(option)) fmt.Println(str) // Output: @@ -59,21 +51,15 @@ func ExampleMultiGeneratorInOrder() { // 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) +func ExampleUUID() { + option := nomino.WithGenerator(nomino.UUID()) - str, _ := gen.Make() + str, _ := nomino.Make(nomino.NewConfig(option)) fmt.Println(str) - str, _ = gen.Make() + str, _ = nomino.Make(nomino.NewConfig(option)) fmt.Println(str) - str, _ = gen.Make() + str, _ = nomino.Make(nomino.NewConfig(option)) fmt.Println(str) } diff --git a/generators_test.go b/generators_test.go index f8a81fa..7886ded 100644 --- a/generators_test.go +++ b/generators_test.go @@ -4,6 +4,7 @@ import ( "errors" "testing" + "github.com/google/uuid" "github.com/stretchr/testify/assert" ) @@ -16,34 +17,26 @@ 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) { - g := MultiGeneratorInOrder(gens...) + 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) st, err := g(nil) assert.NoError(t, err) - assert.Equal(t, out1, st) + assert.Equal(t, st1, st) st, err = g(nil) assert.NoError(t, err) - assert.Equal(t, out2, st) + assert.Equal(t, st2, st) st, err = g(nil) assert.Zero(t, st) - assert.ErrorIs(t, err, err1) + assert.ErrorIs(t, err, er1) st, err = g(nil) assert.NoError(t, err) - assert.Equal(t, out1, st) + assert.Equal(t, st1, st) } func TestMultiGeneratorInOrderOne(t *testing.T) { @@ -69,38 +62,23 @@ func TestMultiGeneratorInOrderMissing(t *testing.T) { 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) +func TestUUID(t *testing.T) { + st, err := UUID()(nil) assert.NoError(t, err) - assert.Equal(t, st1, st) - st, err = g(nil) - assert.NoError(t, err) - assert.Equal(t, st1, st) + _, parseErr := uuid.Parse(st) + assert.NoError(t, parseErr) } -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) +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 c733bcf..6917d49 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ 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 209d634..8638c59 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ 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= diff --git a/hashtype_string.go b/hashtype_string.go new file mode 100644 index 0000000..a4ce423 --- /dev/null +++ b/hashtype_string.go @@ -0,0 +1,26 @@ +// 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]] +} diff --git a/make.go b/make.go index 2e9d754..640ec39 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 error will be returned instead. +// If the name generator returns an error (generally, it shouldn't), that will be returned instead. func Make(conf Config, opts ...Option) (string, error) { for _, opt := range opts { opt(&conf)