Compare commits

..

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

30 changed files with 466 additions and 1211 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,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!

View file

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

View file

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

View file

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

View file

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

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

1
go.mod
View file

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

2
go.sum
View file

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

20
make.go
View file

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

View file

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

View file

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

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"
)
// 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 = ""

View file

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

48
options_test.go Normal file
View file

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