tind/config.go
Matthew Stobbs 79c235de31 Added basic testing
Started doing all comparisons in Runes
string cound emojis as 4 runes instead of just the 1 it is
2024-02-02 01:04:48 -07:00

141 lines
3.2 KiB
Go
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}