Initial commit
Not tested, only written code. Tests will come next to verify that it works
This commit is contained in:
commit
d00ac81a29
133
config.go
Normal file
133
config.go
Normal file
@ -0,0 +1,133 @@
|
||||
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 string) *Config {
|
||||
c.runes = ensureUniqueRunes(r)
|
||||
c.runesize = len(c.runes)
|
||||
c.mod = byte(c.runesize - 1)
|
||||
return c
|
||||
}
|
||||
|
||||
func ensureUniqueRunes(r string) []rune {
|
||||
unq := make(map[rune]int)
|
||||
var index int
|
||||
// loop through the runes to find duplicates
|
||||
for _, v := range []rune(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 {
|
||||
t := TinD{
|
||||
bytes: make([]byte, c.size),
|
||||
config: c,
|
||||
}
|
||||
_, err := rand.Read(t.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++ {
|
||||
t.bytes[i] = t.bytes[i] & c.mod
|
||||
}
|
||||
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 := make([]byte, c.size)
|
||||
r := []rune(in)
|
||||
for i := 0; i < l; i++ {
|
||||
for j := 0; j < c.runesize; j++ {
|
||||
if r[i] == alphabet[j] {
|
||||
t[i] = byte(j)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return TinD(t), nil
|
||||
}
|
58
tind.go
Normal file
58
tind.go
Normal file
@ -0,0 +1,58 @@
|
||||
package tind
|
||||
|
||||
// TinD: Because a UUID is too big for a lot of people
|
||||
// a TinD is a defaults to a 4 byte ID made up of ONLY Alphabetical characters.
|
||||
// To ensure more randomness, lower case and upper case letters are used
|
||||
// providing up to 52 differenct characters for use in each single position.
|
||||
// Arbitrary TinD sizes can also be used, by calling
|
||||
// `id := NewTinDOfLength(<number of bytes>)`
|
||||
//
|
||||
// TinD is at it's core a byte slice. It's default alphabet can be replaced with
|
||||
// whatever unicode characters you want, as the alphabet it uses is just a slice
|
||||
// of runes
|
||||
import (
|
||||
"crypto/rand"
|
||||
"errors"
|
||||
"log"
|
||||
)
|
||||
|
||||
// defaults are just a call to NewConfig()
|
||||
var defaults = NewTinDConfig()
|
||||
|
||||
// TinD represents a very simple `size` byte unique ID that is generated
|
||||
// at random. TinD stands for Tiny iDentifier, and is pronouced "tind"
|
||||
// Each byte can be the value 0 -> len(alphabet) that is used, to ensure
|
||||
// values can be encoded and decoded reliably. The longer the alphabet
|
||||
// used, the more randomness can be done.
|
||||
type TinD struct{
|
||||
bytes []byte
|
||||
str string
|
||||
config *Config
|
||||
}
|
||||
|
||||
// Gen generates a new TinD id using default values
|
||||
func Gen() TinD {
|
||||
return defaults.Gen()
|
||||
}
|
||||
|
||||
// String returns the Human Readable form of the TinD
|
||||
func (t TinD) String() string {
|
||||
if len(t.str) == t.config.size {
|
||||
return t.str
|
||||
}
|
||||
t.str = makeString(t.bytes, t.config.size, t.config.runset)
|
||||
return t.str
|
||||
}
|
||||
|
||||
// Bytes returns the raw bytes of the TinD
|
||||
func (t TinD) Bytes() []byte {
|
||||
return t.bytes
|
||||
}
|
||||
|
||||
func makeString(bytes []byte, size int, runeset []rune) string {
|
||||
r := make([]rune, size)
|
||||
for i := 0; i < size; i++ {
|
||||
r[i] = runeset[bytes[i]]
|
||||
}
|
||||
return string(r)
|
||||
}
|
Loading…
Reference in New Issue
Block a user