diff --git a/.golangci.yaml b/.golangci.yaml deleted file mode 100644 index 273e2af..0000000 --- a/.golangci.yaml +++ /dev/null @@ -1,39 +0,0 @@ -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/CHANGELOG.md b/CHANGELOG.md index 9020008..e1d7c98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,111 +1,37 @@ # Changelog -### [1.0.0] - 2025-03-31 - 🚀 Stable release! +### [0.2.0] - 2025-03-14 -#### 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 - -- ✨ Allow for different types of UUIDs in the UUID `Generator` -- ✨ `Config.AddOptions` method -- ✨ `Generator.MakeWithConfig` method - -#### Support - -- 📝 Some better examples - -### [0.4.0] - 2025-03-15 - ✨ More Generators, and `Generator.Make` method - -#### Features - -- ✨ 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 - -### [0.2.1] - 2025-03-14 - ✨ New Hash Generator - -#### Features - -- ✨ Add Hash Generator - -#### Dev Tooling - -- 🛠 Added a task to serve docs -- 🛠 Added tasts to check code complexity - -#### Miscellaneous - -- 💚 Fixed some `go vet` complaints - -### [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 - ✨ `WithSeparator` +## [0.0.3] - 2025-03-11 ### 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 - 🐛 Bugfix +## [0.0.2] - 2025-03-11 Bugfix release ### Fixes -- 🐛 Extension being ignored. Original included twice. +- Extension being ignored. Original included twice. -## [0.0.1] - 2025-03-10 - 🚀 Initial Release +## [0.0.1] - 2025-03-10 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! diff --git a/README.md b/README.md index e0b3f51..f08b801 100644 --- a/README.md +++ b/README.md @@ -4,80 +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). -Make sure to check out the [official documentation][docs]. +## TODO -## Installation +I'll fill this out more in depth later. -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][]. 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 -[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 +For now, check [official documentation](https://pkg.go.dev/codeberg.org/danjones000/nomino). diff --git a/Taskfile.yml b/Taskfile.yml index ef9f12e..025ff4b 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -5,7 +5,7 @@ tasks: cmds: - task: fmt - task: test - - task: lint + - task: build fmt: desc: Format go code @@ -22,16 +22,45 @@ tasks: cmds: - go generate ./... - lint: - desc: Do static analysis + vet: + desc: Vet go code sources: - '**/*.go' cmds: - - golangci-lint run + - go vet ./... + + critic: + desc: Critique go code + sources: + - '**/*.go' + cmds: + - gocritic check ./... + + staticcheck: + desc: Static check go code + sources: + - '**/*.go' + cmds: + - staticcheck ./... + + vuln: + desc: Check for vulnerabilities + sources: + - '**/*.go' + cmds: + - govulncheck ./... + + lint: + desc: Do static analysis + deps: + - vet + - critic + - staticcheck + - vuln test: desc: Run unit tests - deps: [fmt] + deps: [fmt, vet] sources: - '**/*.go' generates: @@ -54,9 +83,4 @@ tasks: deps: [coverage-report] cmds: - ip addr list | grep inet - - php -S 0.0.0.0:3434 -t build - - serve-docs: - desc: Serve the current docs - cmds: - - godoc -http=0.0.0.0:3434 -play + - php -S 0.0.0.0:3265 -t build diff --git a/config.go b/config.go index 54456bf..5c87729 100644 --- a/config.go +++ b/config.go @@ -1,6 +1,5 @@ package nomino -// Config controls how the generatred filename is created. type Config struct { original string prefix string @@ -10,25 +9,14 @@ type Config struct { generator Generator } -// 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 { conf := Config{ extension: ".txt", separator: "_", - generator: UUID(nil), + generator: uuidGen, } for _, opt := range options { opt(&conf) } return conf } - -// AddOptions creates a new [Config] with each [Option] 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 f6ed21d..adc35e6 100644 --- a/config_test.go +++ b/config_test.go @@ -20,12 +20,3 @@ 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/examples_test.go b/examples_test.go deleted file mode 100644 index c929985..0000000 --- a/examples_test.go +++ /dev/null @@ -1,60 +0,0 @@ -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 { - // 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"), - 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 { - // 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"), - )) - return newName -} - -// This 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) -} diff --git a/gen_file.go b/gen_file.go deleted file mode 100644 index c701f11..0000000 --- a/gen_file.go +++ /dev/null @@ -1,75 +0,0 @@ -package nomino - -import ( - "crypto" - "encoding/hex" - "errors" - "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 - -// 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 [Hasher] 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(h Hasher) Generator { - if h == nil { - h = crypto.MD5 - } - return func(c *Config) (string, error) { - if h == crypto.MD5SHA1 { - return "", ErrInvalidHash - } - name, err := getOriginal(c) - if err != nil { - return "", err - } - hs := h.New() - hs.Write([]byte(name)) - return hex.EncodeToString(hs.Sum(nil)), nil - } -} diff --git a/gen_file_examples_test.go b/gen_file_examples_test.go deleted file mode 100644 index 76f3260..0000000 --- a/gen_file_examples_test.go +++ /dev/null @@ -1,56 +0,0 @@ -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")) - fmt.Println(str) - - // Output: my-name-is-jimmy.txt -} - -func ExampleSlug_withLang() { - str, _ := nomino.Slug("de"). - Make(nomino.WithOriginal("Diese & Dass")) - fmt.Println(str) - - // Output: diese-und-dass.txt -} - -func ExampleHash_mD5() { - str, _ := nomino.Hash(crypto.MD5). - Make(nomino.WithOriginal("foobar")) - fmt.Println(str) - // Output: 3858f62230ac3c915f300c664312c63f.txt -} - -func ExampleHash_sHA1() { - str, _ := nomino.Hash(crypto.SHA1). - Make(nomino.WithOriginal("foobar")) - fmt.Println(str) - // Output: 8843d7f92416211de9ebb963ff4ce28125932878.txt -} - -func ExampleHash_sHA256() { - 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 deleted file mode 100644 index cd0a8bf..0000000 --- a/gen_file_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package nomino - -import ( - "crypto" - "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(crypto.MD5SHA1))) - st, err := conf.generator(&conf) - assert.Equal(t, "", st) - assert.ErrorIs(t, err, ErrInvalidHash) -} - -func TestHashMissingOriginal(t *testing.T) { - conf := NewConfig(WithGenerator(Hash(nil))) - st, err := conf.generator(&conf) - assert.Equal(t, "", st) - assert.ErrorIs(t, err, ErrMissingOriginal) -} diff --git a/gen_int.go b/gen_int.go deleted file mode 100644 index bb57261..0000000 --- a/gen_int.go +++ /dev/null @@ -1,55 +0,0 @@ -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 deleted file mode 100644 index f3e61c7..0000000 --- a/gen_int_examples_test.go +++ /dev/null @@ -1,121 +0,0 @@ -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/gen_rand.go b/gen_rand.go deleted file mode 100644 index 69f0487..0000000 --- a/gen_rand.go +++ /dev/null @@ -1,91 +0,0 @@ -package nomino - -import ( - "crypto/rand" - "strings" - - "github.com/deatil/go-encoding/base62" - "github.com/google/uuid" -) - -// 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) -} - -// 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 { - 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] -} - -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 { - c := randConf{8} - for _, opt := range opts { - opt(&c) - } - return func(*Config) (string, error) { - var buff strings.Builder - buff.Grow(c.length) - fillBuffer(&buff, c.length) - return buff.String(), nil - } -} diff --git a/gen_rand_examples_test.go b/gen_rand_examples_test.go deleted file mode 100644 index 29737fa..0000000 --- a/gen_rand_examples_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package nomino_test - -import ( - "fmt" - - "codeberg.org/danjones000/nomino" -) - -func ExampleUUID() { - gen := nomino.UUID(nil) - - str, _ := gen.Make() - fmt.Println(str) - - str, _ = gen.Make() - fmt.Println(str) -} - -func ExampleUUID_v7() { - gen := nomino.UUID(nomino.UUIDv7) - - str, _ := gen.Make() - fmt.Println(str) - - str, _ = gen.Make() - fmt.Println(str) - - str, _ = gen.Make() - fmt.Println(str) -} - -func ExampleRandom() { - str, _ := nomino.Random().Make() - fmt.Println(str) -} - -func ExampleRandomLength() { - str, _ := nomino.Random(nomino.RandomLength(32)).Make() - fmt.Println(str) -} diff --git a/gen_rand_test.go b/gen_rand_test.go deleted file mode 100644 index 193ac51..0000000 --- a/gen_rand_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package nomino - -import ( - "testing" - - "github.com/google/uuid" - "github.com/stretchr/testify/assert" -) - -func TestUUID(t *testing.T) { - st, err := UUID(nil)(nil) - assert.NoError(t, err) - _, parseErr := uuid.Parse(st) - assert.NoError(t, parseErr) -} - -type badRead struct{} - -func (badRead) Read([]byte) (int, error) { - return 0, errTest -} - -func TestUUIDFail(t *testing.T) { - uuid.SetRand(badRead{}) - defer uuid.SetRand(nil) - - _, err := UUID(nil)(nil) - assert.ErrorIs(t, err, errTest) -} - -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/gen_ts.go b/gen_ts.go deleted file mode 100644 index 62fe703..0000000 --- a/gen_ts.go +++ /dev/null @@ -1,59 +0,0 @@ -package nomino - -import "time" - -// FileTimestamp is the default format for [Timestamp]. -const FileTimestamp string = "2006-01-02T15-04-05-0700" - -// FileTimestampNoTZ is the default format when using the [TimestampUTC] [TimestampOption]. -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 - } -} - -// TimestampFormat sets the format for the generated name. -// Consult [time.Time.Format] for details on the format. -func TimestampFormat(format string) TimestampOption { - return func(c *timestampConf) { - c.format = format - } -} - -// TimestampTime sets the time for the generated name. -// By default, [Timestamp] uses the current time. -func TimestampTime(t time.Time) TimestampOption { - return func(c *timestampConf) { - c.ts = t - } -} - -// 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 deleted file mode 100644 index 26f8a91..0000000 --- a/gen_ts_examples_test.go +++ /dev/null @@ -1,41 +0,0 @@ -package nomino_test - -import ( - "fmt" - "time" - - "codeberg.org/danjones000/nomino" -) - -func ExampleTimestamp() { - gen := nomino.Timestamp() - s, _ := gen.Make() - 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)) - s, _ := gen.Make() - fmt.Println(s) - // Output: 2009-01-20T12-05-00-0500.txt -} - -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")) - s, _ := gen.Make() - fmt.Println(s) - // Output: 2009#01#20<>12|05|00-0500.txt -} - -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()) - s, _ := gen.Make() - fmt.Println(s) - // Output: 2009-01-20T17-05-00.txt -} diff --git a/gen_ts_test.go b/gen_ts_test.go deleted file mode 100644 index 2997702..0000000 --- a/gen_ts_test.go +++ /dev/null @@ -1,15 +0,0 @@ -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 28b666b..4b98284 100644 --- a/generators.go +++ b/generators.go @@ -2,7 +2,12 @@ package nomino import ( "errors" - "math/rand" + "fmt" + "strconv" + "time" + + "github.com/google/uuid" + "github.com/gosimple/slug" ) // Generator is a function that returns the "random" portion of the returned filename. @@ -10,25 +15,14 @@ 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) { - 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]. +// 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 a [Generator] isn't supplied. +// ErrMissingGenerators is returned by a multi-generator if no generators are supplied. var ErrMissingGenerators = errors.New("no generators supplied") func missingGen(*Config) (string, error) { @@ -36,7 +30,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 @@ -54,20 +48,140 @@ 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 +func uuidGen(*Config) (string, error) { + u, err := uuid.NewRandom() + if err != nil { + return "", err } + return u.String(), nil +} - if len(gens) == 1 { - return gens[0] - } +// UUID generates a UUIDv4. +func UUID() Generator { + return uuidGen +} - 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) +// 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) +} + +// 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") + +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 } } diff --git a/generators_examples_test.go b/generators_examples_test.go index b552089..0275877 100644 --- a/generators_examples_test.go +++ b/generators_examples_test.go @@ -1,20 +1,17 @@ -package nomino_test +package nomino -import ( - "fmt" +import "fmt" - "codeberg.org/danjones000/nomino" -) - -func ExampleWithGenerator_customGenerator() { - var gen nomino.Generator = func(*nomino.Config) (string, error) { +func ExampleWithGenerator_custom_generator() { + gen := func(*Config) (string, error) { return "hello", nil } + option := WithGenerator(gen) - str, _ := gen.Make() + str, _ := Make(NewConfig(option)) fmt.Println(str) - str, _ = gen.Make(nomino.WithoutExtension()) + str, _ = Make(NewConfig(option, WithoutExtension())) fmt.Println(str) // Output: @@ -22,58 +19,145 @@ func ExampleWithGenerator_customGenerator() { // hello } -func ExampleGenerator_Make() { - g := nomino.Incremental() - st, _ := g.Make() - fmt.Println(st) +func ExampleIncremental() { + conf := NewConfig(WithPrefix("foo"), WithGenerator(Incremental())) - st, _ = g.Make(nomino.WithPrefix("foo")) - fmt.Println(st) + str, _ := Make(conf) + fmt.Println(str) + + str, _ = Make(conf) + fmt.Println(str) + + str, _ = Make(conf) + fmt.Println(str) // Output: - // 0.txt + // foo_0.txt // foo_1.txt + // foo_2.txt } -func ExampleMultiGeneratorInOrder() { - gen1 := func(*nomino.Config) (string, error) { - return "bonjour", nil - } - gen2 := func(*nomino.Config) (string, error) { - return "goodbye", nil - } - gen := nomino.MultiGeneratorInOrder(gen1, gen2) +func ExampleIncrementalWithStart() { + conf := NewConfig(WithPrefix("foo"), WithGenerator(IncrementalWithStart(42))) - str, _ := gen.Make() + str, _ := Make(conf) fmt.Println(str) - str, _ = gen.Make() + str, _ = Make(conf) fmt.Println(str) - str, _ = gen.Make() + str, _ = Make(conf) fmt.Println(str) // Output: - // bonjour.txt - // goodbye.txt - // bonjour.txt + // foo_42.txt + // foo_43.txt + // foo_44.txt } -func ExampleMultiGeneratorRandomOrder() { - gen1 := func(*nomino.Config) (string, error) { - return "guten-tag", nil - } - gen2 := func(*nomino.Config) (string, error) { - return "wiedersehen", nil - } - gen := nomino.MultiGeneratorRandomOrder(gen1, gen2) +func ExampleIncrementalWithStep() { + conf := NewConfig(WithPrefix("foo"), WithGenerator(IncrementalWithStep(2))) - str, _ := gen.Make() + str, _ := Make(conf) fmt.Println(str) - str, _ = gen.Make() + str, _ = Make(conf) fmt.Println(str) - str, _ = gen.Make() + 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) + 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 } diff --git a/generators_test.go b/generators_test.go index 4c257ab..fb28ce7 100644 --- a/generators_test.go +++ b/generators_test.go @@ -3,59 +3,54 @@ package nomino import ( "errors" "testing" + "time" + "github.com/google/uuid" "github.com/stretchr/testify/assert" ) -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 TestWithGenerator(t *testing.T) { - g := func(*Config) (string, error) { return out1, nil } + 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, out1, st) + assert.Equal(t, "abc", st) } 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) { - g1 := func(*Config) (string, error) { return out1, nil } + st1 := "abc" + g1 := func(*Config) (string, error) { return st1, nil } g := MultiGeneratorInOrder(g1) 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, out1, st) + assert.Equal(t, st1, st) } func TestMultiGeneratorInOrderMissing(t *testing.T) { @@ -68,38 +63,60 @@ 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) +} + +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) + 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) } 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/make.go b/make.go index 2e9d754..c2c222c 100644 --- a/make.go +++ b/make.go @@ -2,31 +2,25 @@ 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. -func Make(conf Config, opts ...Option) (string, error) { - for _, opt := range opts { - opt(&conf) - } - +// If the name generator returns an error (generally, it shouldn't), that will be returned instead. +func Make(conf Config) (string, error) { name, err := conf.generator(&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 + conf.prefix = conf.prefix + conf.separator } if conf.original != "" { conf.original = conf.separator + conf.original } 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 88b8521..07c85ba 100644 --- a/make_examples_test.go +++ b/make_examples_test.go @@ -1,30 +1,9 @@ -package nomino_test +package nomino -import ( - "fmt" - - "codeberg.org/danjones000/nomino" -) +import "fmt" func ExampleMake_basic() { // Use default config - out, _ := nomino.Make(nomino.NewConfig()) + out, _ := Make(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 1ebb48e..28096ce 100644 --- a/make_test.go +++ b/make_test.go @@ -7,8 +7,6 @@ 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 { @@ -38,9 +36,7 @@ func TestMake(t *testing.T) { for _, testcase := range testcases { t.Run(testcase.name, func(sub *testing.T) { - opts := testcase.opts - opts = append(opts, genOpt) - + opts := append(testcase.opts, genOpt) conf := NewConfig(opts...) st, err := Make(conf) assert.NoError(t, err) @@ -50,10 +46,11 @@ func TestMake(t *testing.T) { } func TestMakeErr(t *testing.T) { - conf := NewConfig(WithGenerator(func(*Config) (string, error) { return "foobar", errTest })) + retErr := errors.New("oops") + conf := NewConfig(WithGenerator(func(*Config) (string, error) { return "foobar", retErr })) st, err := Make(conf) assert.Zero(t, st) - assert.ErrorIs(t, err, errTest) + assert.ErrorIs(t, err, retErr) } func TestMakeDoesntChangeConf(t *testing.T) { @@ -67,18 +64,3 @@ 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) -} diff --git a/nomino.go b/nomino.go deleted file mode 100644 index 185fcc3..0000000 --- a/nomino.go +++ /dev/null @@ -1,7 +0,0 @@ -// Package nomino is a utility that allows us to generate random filenames. -// -// There are two main methods of using nomino. -// -// 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 3550b02..c7e0338 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) @@ -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 = "" diff --git a/options_examples_test.go b/options_examples_test.go index 8263fe9..0c4e669 100644 --- a/options_examples_test.go +++ b/options_examples_test.go @@ -1,91 +1,11 @@ -package nomino_test +package nomino -import ( - "fmt" - - "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 -} +import "fmt" func ExampleWithOriginalSlug() { - st, _ := nomino.Make(nomino.NewConfig( - nomino.WithOriginalSlug("Hello, World"), - nomino.WithGenerator(nomino.Incremental()), + st, _ := Make(NewConfig( + WithOriginalSlug("Hello, World"), + WithGenerator(Incremental()), )) fmt.Println(st) @@ -93,9 +13,9 @@ func ExampleWithOriginalSlug() { } func ExampleWithOriginalSlugLang() { - st, _ := nomino.Make(nomino.NewConfig( - nomino.WithOriginalSlugLang("Diese & Dass", "de"), - nomino.WithGenerator(nomino.Incremental()), + st, _ := Make(NewConfig( + WithOriginalSlugLang("Diese & Dass", "de"), + WithGenerator(Incremental()), )) fmt.Println(st) diff --git a/options_test.go b/options_test.go new file mode 100644 index 0000000..6aded3a --- /dev/null +++ b/options_test.go @@ -0,0 +1,48 @@ +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) +}