Compare commits

..

No commits in common. "stable" and "v0.4.0" have entirely different histories.

20 changed files with 183 additions and 392 deletions

View file

@ -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

View file

@ -1,111 +1,84 @@
# Changelog # Changelog
### [1.0.0] - 2025-03-31 - 🚀 Stable release! ### [0.4.0] - 2025-03-15
#### 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 #### Features
- ✨ Allow for different types of UUIDs in the UUID `Generator` - Add Random Generator
- ✨ `Config.AddOptions` method - Add Make method to Generator
- ✨ `Generator.MakeWithConfig` method - Add MultiGeneratorRandomOrder
#### 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 #### Changes
- 💔 Breaking changes: Replace HashType with Hasher: This supports all crypto.Hash - Replace HashType with Hasher: This supports all crypto.Hash
#### Support #### Support
- 📝 Add some missing doc comments - Add some missing doc comments
### [0.3.0] - 2025-03-14 - ♻️ Refactor multiple Generators into one ### [0.3.0] - 2025-03-14
#### Features #### Features
- ♻️ Simplified multiple `Generator` functions to single function with options - Simplified multiple Generator functions to single function with options
- 📝 Added a lot of examples for docs - Added a lot of examples for docs
- Can add extra `Option`s to `Make` - Can add extra Options to Make
Multiple breaking changes around Generators. Multiple breaking changes around Generators.
#### Bugs #### Bugs
- 🐛 Fixed date formats - Fixed date formats
### [0.2.1] - 2025-03-14 - ✨ New Hash Generator ### [0.2.1] - 2025-03-14
#### Features #### Features
- Add Hash Generator - Add Hash Generator
#### Dev Tooling #### Dev Tooling
- 🛠 Added a task to serve docs - Added a task to serve docs
- 🛠 Added tasts to check code complexity - Added tasts to check code complexity
#### Miscellaneous #### Miscellaneous
- 💚 Fixed some `go vet` complaints - Fixed some `go vet` complaints
### [0.2.0] - 2025-03-14 - ✨ New `Generator`s ### [0.2.0] - 2025-03-14
#### Features #### Features
- Add `IncrementalFormat`* Generators - Add `IncrementalFormat`* Generators
- Add `Slug`* Generators - Add `Slug`* Generators
- Add `WithOriginalSlug`* Options - Add `WithOriginalSlug`* Options
- 💔 Change signature of `Generator` function - Change signature of `Generator` function
Note that this last change **is** a breaking change from 0.0.3, but only for custom Generators. 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 ### 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 Bugfix release
### Fixes ### 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! Initial Release! Hope you like it!
### Added ### Added
- `nomino.Make`` - nomino.Make
- `nomino.Config` - nomino.Config
- `nomino.Generator` - nomino.Generator
+ We needs more of these until I'm ready + We needs more of these until I'm ready
- Lots of tests! - Lots of tests!

View file

@ -4,80 +4,10 @@ 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). 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]. 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.
## Installation ## TODO
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`. I'll fill this out more in depth later.
But, you probably already know this. For now, check [official documentation](https://pkg.go.dev/codeberg.org/danjones000/nomino).
## 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

View file

@ -5,7 +5,7 @@ tasks:
cmds: cmds:
- task: fmt - task: fmt
- task: test - task: test
- task: lint - task: build
fmt: fmt:
desc: Format go code desc: Format go code
@ -22,16 +22,66 @@ tasks:
cmds: cmds:
- go generate ./... - go generate ./...
lint: vet:
desc: Do static analysis desc: Vet go code
sources: sources:
- '**/*.go' - '**/*.go'
cmds: 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 ./...
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
test: test:
desc: Run unit tests desc: Run unit tests
deps: [fmt] deps: [fmt, vet]
sources: sources:
- '**/*.go' - '**/*.go'
generates: generates:

View file

@ -1,6 +1,5 @@
package nomino package nomino
// Config controls how the generatred filename is created.
type Config struct { type Config struct {
original string original string
prefix string prefix string
@ -10,25 +9,15 @@ type Config struct {
generator Generator generator Generator
} }
// NewConfig returns a new [Config] with each [Option] specified. // NewConfig returns a new Config with the specified Options.
// With no Options, the Config uses an extension of .txt, a separator
// of _, and the [UUID] [Generator].
func NewConfig(options ...Option) Config { func NewConfig(options ...Option) Config {
conf := Config{ conf := Config{
extension: ".txt", extension: ".txt",
separator: "_", separator: "_",
generator: UUID(nil), generator: uuidGen,
} }
for _, opt := range options { for _, opt := range options {
opt(&conf) opt(&conf)
} }
return 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
}

View file

@ -20,12 +20,3 @@ func TestNewConfWithOpts(t *testing.T) {
assert.Equal(t, "", c.extension) assert.Equal(t, "", c.extension)
assert.Equal(t, "foobar", c.prefix) 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)
}

View file

@ -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)
}

View file

@ -2,14 +2,14 @@ package nomino
import ( import (
"crypto" "crypto"
"encoding/hex"
"errors" "errors"
"fmt"
"hash" "hash"
"github.com/gosimple/slug" "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") var ErrMissingOriginal = errors.New("missing original filename")
func getOriginal(c *Config) (string, error) { 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 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 { func (hf HashingFunc) New() hash.Hash {
return hf() return hf()
} }
// Hasher is a type returns a [hash.Hash]. // Hasher is a type returns a hash.Hash.
// All [crypto.Hash] may be used. // All crypto.Hash may be used.
type Hasher interface { type Hasher interface {
New() hash.Hash New() hash.Hash
} }
// ErrInvalidHash is returned by the [Hash] [Generator] when an invalid [Hasher] is passed. // ErrInvalidHash is returned by the Hash generator when an invalid HashType is passed
var ErrInvalidHash = errors.New("invalid hash type") var ErrInvalidHash = errors.New("invalid hash type")
// Hash generates a name from a hash of the filename. // Hash generates a name from a hash of the filename.
@ -70,6 +70,6 @@ func Hash(h Hasher) Generator {
} }
hs := h.New() hs := h.New()
hs.Write([]byte(name)) hs.Write([]byte(name))
return hex.EncodeToString(hs.Sum(nil)), nil return fmt.Sprintf("%x", hs.Sum(nil)), nil
} }
} }

View file

@ -11,7 +11,7 @@ type incConf struct {
cb func(int) string 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) type IncrementalOption func(c *incConf)
// Incremental generates a name that is a series of integers. // Incremental generates a name that is a series of integers.
@ -30,22 +30,22 @@ func Incremental(opts ...IncrementalOption) Generator {
} }
} }
// IncrementalStart sets the starting integer for [Incremental]. // IncrementalStart sets the starting integer for Incremental
func IncrementalStart(start int) IncrementalOption { func IncrementalStart(start int) IncrementalOption {
return func(c *incConf) { return func(c *incConf) {
c.start = start 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 { func IncrementalStep(step int) IncrementalOption {
return func(c *incConf) { return func(c *incConf) {
c.step = step 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". // It will be formatted with Printf. This is mostly likely useful with a format like "%02d"
func IncrementalFormat(format string) IncrementalOption { func IncrementalFormat(format string) IncrementalOption {
return func(c *incConf) { return func(c *incConf) {
c.cb = func(i int) string { c.cb = func(i int) string {

View file

@ -8,54 +8,27 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
) )
// UUIDer is an interface for generating UUIDs, by the [UUID] [Generator]. func uuidGen(*Config) (string, error) {
// It is recommended that you use either the [UUIDv4] or [UUIDv7] variables. u, err := uuid.NewRandom()
type UUIDer interface { if err != nil {
UUID() (uuid.UUID, error) return "", err
}
return u.String(), nil
} }
// UUIDFunc is a function that generates a UUID. // UUID generates a UUIDv4.
type UUIDFunc func() (uuid.UUID, error) func UUID() Generator {
return uuidGen
// 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 { type randConf struct {
length int length int
} }
// RandomOption is an option for the [Random] [Generator]. // RandomOption is an option for the Random Generator
type RandomOption func(*randConf) 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 { func RandomLength(length int) RandomOption {
return func(c *randConf) { return func(c *randConf) {
c.length = length c.length = length
@ -64,18 +37,12 @@ func RandomLength(length int) RandomOption {
func getRandomBytes(l int) []byte { func getRandomBytes(l int) []byte {
key := make([]byte, l) key := make([]byte, l)
_, _ = rand.Read(key) rand.Read(key)
e := base62.StdEncoding.Encode(key) e := base62.StdEncoding.Encode(key)
return e[:l] return e[:l]
} }
func fillBuffer(buff *strings.Builder, length int) { // Random generates a random string containing the characters [A-Za-z0-9].
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. // By default, it will be eight characters long.
func Random(opts ...RandomOption) Generator { func Random(opts ...RandomOption) Generator {
c := randConf{8} c := randConf{8}
@ -85,7 +52,9 @@ func Random(opts ...RandomOption) Generator {
return func(*Config) (string, error) { return func(*Config) (string, error) {
var buff strings.Builder var buff strings.Builder
buff.Grow(c.length) buff.Grow(c.length)
fillBuffer(&buff, c.length) for buff.Len() < c.length {
buff.Write(getRandomBytes(c.length - buff.Len()))
}
return buff.String(), nil return buff.String(), nil
} }
} }

View file

@ -7,34 +7,30 @@ import (
) )
func ExampleUUID() { func ExampleUUID() {
gen := nomino.UUID(nil) option := nomino.WithGenerator(nomino.UUID())
str, _ := gen.Make() str, _ := nomino.Make(nomino.NewConfig(option))
fmt.Println(str) fmt.Println(str)
str, _ = gen.Make() str, _ = nomino.Make(nomino.NewConfig(option))
fmt.Println(str)
}
func ExampleUUID_v7() {
gen := nomino.UUID(nomino.UUIDv7)
str, _ := gen.Make()
fmt.Println(str) fmt.Println(str)
str, _ = gen.Make() str, _ = nomino.Make(nomino.NewConfig(option))
fmt.Println(str)
str, _ = gen.Make()
fmt.Println(str) fmt.Println(str)
} }
func ExampleRandom() { func ExampleRandom() {
str, _ := nomino.Random().Make() option := nomino.WithGenerator(nomino.Random())
str, _ := nomino.Make(nomino.NewConfig(option))
fmt.Println(str) fmt.Println(str)
} }
func ExampleRandomLength() { func ExampleRandomLength() {
str, _ := nomino.Random(nomino.RandomLength(32)).Make() option := nomino.WithGenerator(nomino.Random(
nomino.RandomLength(32),
))
str, _ := nomino.Make(nomino.NewConfig(option))
fmt.Println(str) fmt.Println(str)
} }

View file

@ -1,6 +1,7 @@
package nomino package nomino
import ( import (
"errors"
"testing" "testing"
"github.com/google/uuid" "github.com/google/uuid"
@ -8,7 +9,7 @@ import (
) )
func TestUUID(t *testing.T) { func TestUUID(t *testing.T) {
st, err := UUID(nil)(nil) st, err := UUID()(nil)
assert.NoError(t, err) assert.NoError(t, err)
_, parseErr := uuid.Parse(st) _, parseErr := uuid.Parse(st)
assert.NoError(t, parseErr) assert.NoError(t, parseErr)
@ -17,15 +18,15 @@ func TestUUID(t *testing.T) {
type badRead struct{} type badRead struct{}
func (badRead) Read([]byte) (int, error) { func (badRead) Read([]byte) (int, error) {
return 0, errTest return 0, errors.New("sorry")
} }
func TestUUIDFail(t *testing.T) { func TestUUIDFail(t *testing.T) {
uuid.SetRand(badRead{}) uuid.SetRand(badRead{})
defer uuid.SetRand(nil) defer uuid.SetRand(nil)
_, err := UUID(nil)(nil) _, err := UUID()(nil)
assert.ErrorIs(t, err, errTest) assert.Equal(t, errors.New("sorry"), err)
} }
func TestRand(t *testing.T) { func TestRand(t *testing.T) {

View file

@ -2,10 +2,10 @@ package nomino
import "time" import "time"
// FileTimestamp is the default format for [Timestamp]. // FileTimestamp is the default format for WithTimestamp and WithTime
const FileTimestamp string = "2006-01-02T15-04-05-0700" const FileTimestamp string = "2006-01-02T15-04-05-0700"
// FileTimestampNoTZ is the default format when using the [TimestampUTC] [TimestampOption]. // FileTimestampNoTZ is the default format for WithTimestampUTC and WithTimeUTC
const FileTimestampNoTZ string = "2006-01-02T15-04-05" const FileTimestampNoTZ string = "2006-01-02T15-04-05"
type timestampConf struct { type timestampConf struct {
@ -14,11 +14,11 @@ type timestampConf struct {
utc bool utc bool
} }
// TimestampOption provides options for the [Timestamp] [Generator]. // TimestampOption provides options for the Timestamp Generator
type TimestampOption func(c *timestampConf) type TimestampOption func(c *timestampConf)
// Timestamp generates a a date and time. By default, it uses the current time, and will. // 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 { func Timestamp(opts ...TimestampOption) Generator {
c := timestampConf{format: FileTimestamp, ts: time.Now()} c := timestampConf{format: FileTimestamp, ts: time.Now()}
for _, opt := range opts { for _, opt := range opts {
@ -35,7 +35,7 @@ func Timestamp(opts ...TimestampOption) Generator {
} }
// TimestampFormat sets the format for the generated name. // 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 { func TimestampFormat(format string) TimestampOption {
return func(c *timestampConf) { return func(c *timestampConf) {
c.format = format c.format = format
@ -43,7 +43,7 @@ func TimestampFormat(format string) TimestampOption {
} }
// TimestampTime sets the time for the generated name. // TimestampTime sets the time for the generated name.
// By default, [Timestamp] uses the current time. // By default, it uses the current time.
func TimestampTime(t time.Time) TimestampOption { func TimestampTime(t time.Time) TimestampOption {
return func(c *timestampConf) { return func(c *timestampConf) {
c.ts = t c.ts = t

View file

@ -9,7 +9,8 @@ import (
func ExampleTimestamp() { func ExampleTimestamp() {
gen := nomino.Timestamp() gen := nomino.Timestamp()
s, _ := gen.Make() conf := nomino.NewConfig(nomino.WithGenerator(gen))
s, _ := nomino.Make(conf)
fmt.Println(s) fmt.Println(s)
} }
@ -17,7 +18,8 @@ func ExampleTimestampTime() {
tz, _ := time.LoadLocation("America/New_York") tz, _ := time.LoadLocation("America/New_York")
ts := time.Date(2009, time.January, 20, 12, 5, 0, 0, tz) ts := time.Date(2009, time.January, 20, 12, 5, 0, 0, tz)
gen := nomino.Timestamp(nomino.TimestampTime(ts)) gen := nomino.Timestamp(nomino.TimestampTime(ts))
s, _ := gen.Make() conf := nomino.NewConfig(nomino.WithGenerator(gen))
s, _ := nomino.Make(conf)
fmt.Println(s) fmt.Println(s)
// Output: 2009-01-20T12-05-00-0500.txt // Output: 2009-01-20T12-05-00-0500.txt
} }
@ -26,7 +28,8 @@ func ExampleTimestampFormat() {
tz, _ := time.LoadLocation("America/New_York") tz, _ := time.LoadLocation("America/New_York")
ts := time.Date(2009, time.January, 20, 12, 5, 0, 0, tz) 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")) gen := nomino.Timestamp(nomino.TimestampTime(ts), nomino.TimestampFormat("2006#01#02<>15|04|05-0700"))
s, _ := gen.Make() conf := nomino.NewConfig(nomino.WithGenerator(gen))
s, _ := nomino.Make(conf)
fmt.Println(s) fmt.Println(s)
// Output: 2009#01#20<>12|05|00-0500.txt // Output: 2009#01#20<>12|05|00-0500.txt
} }
@ -35,7 +38,8 @@ func ExampleTimestampUTC() {
tz, _ := time.LoadLocation("America/New_York") tz, _ := time.LoadLocation("America/New_York")
ts := time.Date(2009, time.January, 20, 12, 5, 0, 0, tz) ts := time.Date(2009, time.January, 20, 12, 5, 0, 0, tz)
gen := nomino.Timestamp(nomino.TimestampTime(ts), nomino.TimestampUTC()) gen := nomino.Timestamp(nomino.TimestampTime(ts), nomino.TimestampUTC())
s, _ := gen.Make() conf := nomino.NewConfig(nomino.WithGenerator(gen))
s, _ := nomino.Make(conf)
fmt.Println(s) fmt.Println(s)
// Output: 2009-01-20T17-05-00.txt // Output: 2009-01-20T17-05-00.txt
} }

View file

@ -10,25 +10,20 @@ import (
// for example. // for example.
type Generator func(conf *Config) (string, error) 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) { func (g Generator) Make(opts ...Option) (string, error) {
return g.MakeWithConfig(NewConfig(opts...)) opts = append(opts, WithGenerator(g))
return Make(NewConfig(opts...))
} }
// MakeWithConfig allows you to generate a new string directly from a [Generator] // WithGenerator sets the specified 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].
func WithGenerator(g Generator) Option { func WithGenerator(g Generator) Option {
return func(c *Config) { return func(c *Config) {
c.generator = g 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") var ErrMissingGenerators = errors.New("no generators supplied")
func missingGen(*Config) (string, error) { func missingGen(*Config) (string, error) {
@ -36,7 +31,7 @@ func missingGen(*Config) (string, error) {
} }
// MultiGeneratorInOrder allows the use of multiple generators. Each new invokation will use the next generator in turn. // 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 { func MultiGeneratorInOrder(gens ...Generator) Generator {
if len(gens) == 0 { if len(gens) == 0 {
return missingGen return missingGen
@ -55,7 +50,7 @@ func MultiGeneratorInOrder(gens ...Generator) Generator {
} }
// MultiGeneratorRandomOrder allows the use of multiple generators. Each new invokation will use one of the generators randomly. // 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 { func MultiGeneratorRandomOrder(gens ...Generator) Generator {
if len(gens) == 0 { if len(gens) == 0 {
return missingGen return missingGen
@ -66,7 +61,6 @@ func MultiGeneratorRandomOrder(gens ...Generator) Generator {
} }
return func(c *Config) (string, error) { 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) idx := rand.Int() % len(gens)
return gens[idx](c) return gens[idx](c)
} }

View file

@ -37,7 +37,7 @@ func ExampleGenerator_Make() {
func ExampleMultiGeneratorInOrder() { func ExampleMultiGeneratorInOrder() {
gen1 := func(*nomino.Config) (string, error) { gen1 := func(*nomino.Config) (string, error) {
return "bonjour", nil return "hello", nil
} }
gen2 := func(*nomino.Config) (string, error) { gen2 := func(*nomino.Config) (string, error) {
return "goodbye", nil return "goodbye", nil
@ -54,17 +54,17 @@ func ExampleMultiGeneratorInOrder() {
fmt.Println(str) fmt.Println(str)
// Output: // Output:
// bonjour.txt // hello.txt
// goodbye.txt // goodbye.txt
// bonjour.txt // hello.txt
} }
func ExampleMultiGeneratorRandomOrder() { func ExampleMultiGeneratorRandomOrder() {
gen1 := func(*nomino.Config) (string, error) { gen1 := func(*nomino.Config) (string, error) {
return "guten-tag", nil return "hello", nil
} }
gen2 := func(*nomino.Config) (string, error) { gen2 := func(*nomino.Config) (string, error) {
return "wiedersehen", nil return "goodbye", nil
} }
gen := nomino.MultiGeneratorRandomOrder(gen1, gen2) gen := nomino.MultiGeneratorRandomOrder(gen1, gen2)

View file

@ -7,6 +7,15 @@ import (
"github.com/stretchr/testify/assert" "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 ( const (
out1 string = "abc" out1 string = "abc"
out2 string = "def" out2 string = "def"
@ -21,15 +30,6 @@ var (
gens = []Generator{gen1, gen2, gen3} 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) { func TestMultiGeneratorInOrder(t *testing.T) {
g := MultiGeneratorInOrder(gens...) g := MultiGeneratorInOrder(gens...)
st, err := g(nil) st, err := g(nil)
@ -47,15 +47,16 @@ func TestMultiGeneratorInOrder(t *testing.T) {
} }
func TestMultiGeneratorInOrderOne(t *testing.T) { 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) g := MultiGeneratorInOrder(g1)
st, err := g(nil) st, err := g(nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, out1, st) assert.Equal(t, st1, st)
st, err = g(nil) st, err = g(nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, out1, st) assert.Equal(t, st1, st)
} }
func TestMultiGeneratorInOrderMissing(t *testing.T) { func TestMultiGeneratorInOrderMissing(t *testing.T) {

View file

@ -7,8 +7,6 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
var errTest = errors.New("sorry")
func TestMake(t *testing.T) { func TestMake(t *testing.T) {
genOpt := WithGenerator(func(*Config) (string, error) { return "abc", nil }) genOpt := WithGenerator(func(*Config) (string, error) { return "abc", nil })
testcases := []struct { testcases := []struct {
@ -50,10 +48,11 @@ func TestMake(t *testing.T) {
} }
func TestMakeErr(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) st, err := Make(conf)
assert.Zero(t, st) assert.Zero(t, st)
assert.ErrorIs(t, err, errTest) assert.ErrorIs(t, err, retErr)
} }
func TestMakeDoesntChangeConf(t *testing.T) { func TestMakeDoesntChangeConf(t *testing.T) {

View file

@ -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

View file

@ -6,7 +6,7 @@ import (
"github.com/gosimple/slug" "github.com/gosimple/slug"
) )
// Option sets configuration parameters for [Config]. // Option sets configuration parameters for Config.
type Option func(c *Config) type Option func(c *Config)
// WithOriginal sets the original filename. // WithOriginal sets the original filename.
@ -18,7 +18,7 @@ func WithOriginal(o string) Option {
} }
// WithOriginal sets the original filename as a slug. // 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 { func WithOriginalSlug(o string) Option {
return func(c *Config) { return func(c *Config) {
c.original = slug.Make(o) 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. // 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 { func WithOriginalSlugLang(o, lang string) Option {
return func(c *Config) { return func(c *Config) {
c.original = slug.MakeLang(o, lang) 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 { func WithoutExtension() Option {
return func(c *Config) { return func(c *Config) {
c.extension = "" c.extension = ""