tind/config.go

141 lines
3.2 KiB
Go
Raw Normal View History

package tind
import (
"crypto/rand"
"errors"
"log"
)
// Config is a configuration for generating and working with TinD id's of
// possibly varying sizes and runesets
type Config struct {
size int
runes []rune
runesize int
mod byte
}
const (
defaultRunes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
defaultSize = 4
)
// NewTinDConfig starts off a factory pattern to build a TinD
// with either the default values, or setting custom values
// using the `With` functions
// Example:
//
// Get a default TinD id
// `id := New().Get()`
//
// Get a TinD with 8 bytes but the default runeset
// `id := New().WithSize(8).Get()`
//
// Get a TinD of default size with a special runeset
// `id := New().WithRuneset("😃😡🕰🧰BGD").Get()`
//
// You can always define the configuration and create new TinD's
// using it.
// Example:
//
// myNumberOnlyConfig := New().WithSize(8).WithRuneset("0123456789")
func NewTinDConfig() *Config {
return &Config{
size: defaultSize,
runes: []rune(defaultRunes),
runesize: len(defaultRunes),
mod: byte(len(defaultRunes) - 1),
}
}
// WithRuneset requires a string as input, where each character must be unique.
// The string can be comprised of any unique unicode character.
// Will filter out duplicate runes before storing it
//
// This does slow down initialization, but the final product still runs fast to
// generate ids
func (c *Config) WithRuneset(r []rune) *Config {
c.runes = ensureUniqueRunes(r)
c.runesize = len(c.runes)
c.mod = byte(c.runesize - 1)
return c
}
func ensureUniqueRunes(r []rune) []rune {
unq := make(map[rune]int)
var index int
// loop through the runes to find duplicates
for _, v := range r {
// if the current rune hasn't been seen yet, assign it
if _, ok := unq[v]; !ok {
unq[v] = index
index++
}
}
rr := make([]rune, len(unq))
for k, n := range unq {
rr[n] = k
}
return rr
}
// WithSize sets the number of bytes in the TinD ID
func (c *Config) WithSize(s int) *Config {
c.size = s
return c
}
// Zero returns a zero value TinD ID of the configured size
func (c *Config) Zero() TinD {
t := TinD{
bytes: make([]byte, c.size),
config: c,
}
t.String() // set the string so it's faster to recall
return t
}
// Gen generates a TinD with the given configuration
func (c *Config) Gen() TinD {
bytes := make([]byte, c.size)
runes := make([]rune, c.size)
_, err := rand.Read(bytes)
if err != nil {
log.Fatalln(err)
}
// Make sure each byte fits the rune so it can be encoded and decoded
for i := 0; i < c.size; i++ {
bytes[i] = bytes[i] & c.mod
runes[i] = c.runes[bytes[i]]
}
t := TinD{
bytes: bytes,
runes: runes,
config: c,
}
return t
}
// FromString takes a string representation of a TinD id and returns
// the actual TinD as bytes
func (c *Config) FromString(in string) (TinD, error) {
// Make sure the number of characters matches what's needed
if len(in) != c.size {
return c.Zero(), errors.New("given id isn't the same size as the configuration")
}
t := TinD{
bytes: make([]byte, c.size),
config: c,
}
r := []rune(in)
for i := 0; i < c.size; i++ {
for j := 0; j < c.runesize; j++ {
if r[i] == c.runes[j] {
t.bytes[i] = byte(j)
break
}
}
}
return TinD(t), nil
}