Compare commits

...

6 commits

Author SHA1 Message Date
4b1312e293 🔀 Merge branch 'release/0.5.1' into stable 2025-03-19 18:46:42 -05:00
797c616447 📝 Add package level example 2025-03-19 18:45:36 -05:00
8f02956ecd 🚨 A bunch of small improvements from linter 2025-03-19 18:05:16 -05:00
480e36763f 🛠 Replace all linting with golangci-lint 2025-03-19 17:48:23 -05:00
8072ae267a ✏️ Extra backtick 2025-03-19 16:16:00 -05:00
4faf3a5d2f 🔀 Merge tag 'v0.5.0' into develop
🔖 v0.5.0:  More UUIDs
2025-03-19 14:39:59 -05:00
14 changed files with 153 additions and 104 deletions

39
.golangci.yaml Normal file
View file

@ -0,0 +1,39 @@
linters:
enable:
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- unused
- copyloopvar
- dupl
- err113
- errname
- exptostd
- fatcontext
- funlen
- gocognit
- goconst
- gocritic
- gocyclo
- godot
- godox
- gosec
- perfsprint
- testifylint
linters-settings:
testifylint:
enable-all: true
disable:
- require-error
gocognit:
min-complexity: 5
gocyclo:
min-complexity: 5
gocritic:
enable-all: true
settings:
hugeParam:
sizeThreshold: 255

View file

@ -10,7 +10,7 @@
#### Support #### Support
- 📝 Some better examples` - 📝 Some better examples
### [0.4.0] - 2025-03-15 - ✨ More Generators, and `Generator.Make` method ### [0.4.0] - 2025-03-15 - ✨ More Generators, and `Generator.Make` method

View file

@ -5,7 +5,7 @@ tasks:
cmds: cmds:
- task: fmt - task: fmt
- task: test - task: test
- task: build - task: lint
fmt: fmt:
desc: Format go code desc: Format go code
@ -22,66 +22,16 @@ tasks:
cmds: cmds:
- go generate ./... - go generate ./...
vet:
desc: Vet go code
sources:
- '**/*.go'
cmds:
- go vet ./...
critic:
desc: Critique go code
sources:
- '**/*.go'
cmds:
- gocritic check ./...
staticcheck:
desc: Static check go code
sources:
- '**/*.go'
cmds:
- staticcheck ./...
cog-complex:
desc: Calculate cognitive complexity
sources:
- '**/*.go'
cmds:
- gocognit -over 5 .
cyc-complex:
desc: Calculate cyclomatic complexity
sources:
- '**/*.go'
cmds:
- gocyclo -over 5 .
complex:
desc: Calculate complexities
deps:
- cog-complex
- cyc-complex
vuln:
desc: Check for vulnerabilities
sources:
- '**/*.go'
cmds:
- govulncheck ./...
lint: lint:
desc: Do static analysis desc: Do static analysis
deps: sources:
- vet - '**/*.go'
- critic cmds:
- staticcheck - golangci-lint run
- complex
- vuln
test: test:
desc: Run unit tests desc: Run unit tests
deps: [fmt, vet] deps: [fmt]
sources: sources:
- '**/*.go' - '**/*.go'
generates: generates:

56
examples_test.go Normal file
View file

@ -0,0 +1,56 @@
package nomino_test
import (
"fmt"
"codeberg.org/danjones000/nomino"
)
// Define a global Config.
func NominoConfig() nomino.Config {
return nomino.NewConfig(
nomino.WithSeparator("-"),
nomino.WithPrefix("upload"),
nomino.WithGenerator(nomino.UUID(
nomino.UUIDv7,
)),
)
}
// / HandleImgUploads generates new filenames for images with a png extension.
func HandleImgUploads(orig string) string {
newName, _ := nomino.Make(
NominoConfig(),
nomino.WithExtension("png"),
nomino.WithOriginalSlug(orig),
)
return newName
}
// HandleVidUploads generates a new filename for videos.
// We ignore the original filename and use a timestamp for the generated part
// with a webm extension.
func HandleVidUploads() string {
newName, _ := nomino.Timestamp(nomino.TimestampUTC()).
MakeWithConfig(NominoConfig().AddOptions(
nomino.WithExtension("webm"),
))
return newName
}
// Example shows how to use nomino.
func Example() {
// Pretend we have an image upload
filename := "George"
uploadImgName := HandleImgUploads(filename)
// Upload to storage
fmt.Println(uploadImgName)
// New Video Upload
uploadVidName := HandleVidUploads()
// Upload to storage
fmt.Println(uploadVidName)
}

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,10 +37,10 @@ func Slug(lang ...string) Generator {
} }
} }
// HashingFunc is a function that generates a hash.Hash // HashingFunc is a function that generates a hash.Hash.
type HashingFunc func() hash.Hash 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()
} }
@ -51,7 +51,7 @@ type Hasher interface {
New() hash.Hash New() hash.Hash
} }
// ErrInvalidHash is returned by the Hash generator when an invalid HashType is passed // ErrInvalidHash is returned by the Hash generator when an invalid HashType is passed.
var ErrInvalidHash = errors.New("invalid hash type") 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 fmt.Sprintf("%x", hs.Sum(nil)), nil return hex.EncodeToString(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,7 +30,7 @@ func Incremental(opts ...IncrementalOption) Generator {
} }
} }
// IncrementalStart sets the starting integer for Incremental // IncrementalStart sets the starting integer for Incremental.
func IncrementalStart(start int) IncrementalOption { func IncrementalStart(start int) IncrementalOption {
return func(c *incConf) { return func(c *incConf) {
c.start = start c.start = start
@ -45,7 +45,7 @@ func IncrementalStep(step int) IncrementalOption {
} }
// 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

@ -52,10 +52,10 @@ 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,11 +64,17 @@ 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) {
for buff.Len() < length {
buff.Write(getRandomBytes(length - buff.Len()))
}
}
// Random generates a random string containing the characters [A-Za-z0-9]. // Random generates a random string containing the characters [A-Za-z0-9].
// By default, it will be eight characters long. // By default, it will be eight characters long.
func Random(opts ...RandomOption) Generator { func Random(opts ...RandomOption) Generator {
@ -79,9 +85,7 @@ 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)
for buff.Len() < c.length { fillBuffer(&buff, c.length)
buff.Write(getRandomBytes(c.length - buff.Len()))
}
return buff.String(), nil return buff.String(), nil
} }
} }

View file

@ -1,7 +1,6 @@
package nomino package nomino
import ( import (
"errors"
"testing" "testing"
"github.com/google/uuid" "github.com/google/uuid"
@ -18,7 +17,7 @@ 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, errors.New("sorry") return 0, errTest
} }
func TestUUIDFail(t *testing.T) { func TestUUIDFail(t *testing.T) {
@ -26,7 +25,7 @@ func TestUUIDFail(t *testing.T) {
defer uuid.SetRand(nil) defer uuid.SetRand(nil)
_, err := UUID(nil)(nil) _, err := UUID(nil)(nil)
assert.Equal(t, errors.New("sorry"), err) assert.ErrorIs(t, err, errTest)
} }
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 WithTimestamp and WithTime // 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 for WithTimestampUTC and WithTimeUTC // 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 {

View file

@ -21,7 +21,7 @@ func (g Generator) MakeWithConfig(c Config) (string, error) {
return Make(c.AddOptions(WithGenerator(g))) return Make(c.AddOptions(WithGenerator(g)))
} }
// WithGenerator sets the specified generator // 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
@ -66,6 +66,7 @@ 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 "hello", nil return "bonjour", 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:
// hello.txt // bonjour.txt
// goodbye.txt // goodbye.txt
// hello.txt // bonjour.txt
} }
func ExampleMultiGeneratorRandomOrder() { func ExampleMultiGeneratorRandomOrder() {
gen1 := func(*nomino.Config) (string, error) { gen1 := func(*nomino.Config) (string, error) {
return "hello", nil return "guten-tag", nil
} }
gen2 := func(*nomino.Config) (string, error) { gen2 := func(*nomino.Config) (string, error) {
return "goodbye", nil return "wiedersehen", nil
} }
gen := nomino.MultiGeneratorRandomOrder(gen1, gen2) gen := nomino.MultiGeneratorRandomOrder(gen1, gen2)

View file

@ -7,15 +7,6 @@ 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"
@ -30,6 +21,15 @@ 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,16 +47,15 @@ func TestMultiGeneratorInOrder(t *testing.T) {
} }
func TestMultiGeneratorInOrderOne(t *testing.T) { func TestMultiGeneratorInOrderOne(t *testing.T) {
st1 := "abc" g1 := func(*Config) (string, error) { return out1, nil }
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, st1, st) assert.Equal(t, out1, st)
st, err = g(nil) st, err = g(nil)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, st1, st) assert.Equal(t, out1, st)
} }
func TestMultiGeneratorInOrderMissing(t *testing.T) { func TestMultiGeneratorInOrderMissing(t *testing.T) {

View file

@ -7,6 +7,8 @@ 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 {
@ -48,11 +50,10 @@ func TestMake(t *testing.T) {
} }
func TestMakeErr(t *testing.T) { func TestMakeErr(t *testing.T) {
retErr := errors.New("oops") conf := NewConfig(WithGenerator(func(*Config) (string, error) { return "foobar", errTest }))
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, retErr) assert.ErrorIs(t, err, errTest)
} }
func TestMakeDoesntChangeConf(t *testing.T) { func TestMakeDoesntChangeConf(t *testing.T) {

View file

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