Using obsidian to add markdown formatted documentation
main v1
Matthew Stobbs 2024-02-23 21:47:26 -07:00
parent e2d0cb303e
commit 198d509789
6 changed files with 74 additions and 16 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.obsidian

View File

@ -26,13 +26,13 @@ const (
// Example:
//
// Get a default TinD id
// `id := New().Get()`
// `id := New().Gen()`
//
// Get a TinD with 8 bytes but the default runeset
// `id := New().WithSize(8).Get()`
// `id := New().WithSize(8).Gen()`
//
// Get a TinD of default size with a special runeset
// `id := New().WithRuneset("😃😡🕰🧰BGD").Get()`
// `id := New().WithRuneset("😃😡🕰🧰BGD").Gen()`
//
// You can always define the configuration and create new TinD's
// using it.
@ -48,12 +48,28 @@ func NewTinDConfig() *Config {
}
}
// NewConfigFrom will build a configuration from a byte slice. Because this is a
// byte slice, the provided input doesn't matter, it's more about the length,
// not how you use it 😉
func NewConfigFrom(in []byte) *Config {
return &Config{
size: len(in),
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
// Will filter out duplicate runes before storing it, meaning if you provide a
// runeset with duplicates, the size of the runeset will be reduced to ensure
// consistency. Dont' rely on len(runeset) of your custom runeset to be
// accurate.
//
// This does slow down initialization, but the final product still runs fast to
// generate ids
// When updating a runeset, the values of runesize and mod will be updated
func (c *Config) WithRuneset(r []rune) *Config {
c.runes = ensureUniqueRunes(r)
c.runesize = len(c.runes)
@ -138,6 +154,10 @@ func (c *Config) FromString(in string) (TinD, error) {
return TinD(t), nil
}
// Load imports a TinD byte string, or really any byte string, that fits within
// the constraints of the configuration. You can't load an 8byte slice into a
// 4byte TinD configuration.
// To generate a configuration from a given byte slice, use NewConfigFrom.
func (c *Config) Load(in []byte) (TinD, error) {
if len(in) != c.size {
return c.Zero(), errors.New("given id isn't the same size as the configuration")

View File

@ -0,0 +1,10 @@
A UUID is amazing. It's almost garunteed that it will always be unique, and makes sense in a lot of use cases, but doesn't lead to a very human readable identifier.
It used to be the case that using an auto incrementing integer based ID was the best way to go. It was simple, easy to understand, and hard to screw up.
But auto incrementing ID's have a lot of drawbacks when you want something to be unpredictable in order to prevent data leaks. Not only that, but when using sharding, partitioning, or even different databases in the same application, auto incrementing may just plain break your application.
In came UUID's to fix it, and fix it they did! A UUID is at it's core a 128bit long random identifier. This solves the issues of database partitioning and using different databases in general, but leads to a hard to read ID, not really suitable for things that shouldn't just be machine readable.
As an example, this is a UUID: `402b5a52-3751-4daa-bf33-f89b3822d595`. It's stored as a series of 16bytes, represented using hexidecimal characters `0123456789abcdef`, which hyphens to make it more *readable*.
TinD seeks to solves these issues, by having a customizable length of bytes, that then get's mapped to a list of `runes` or `characters`. An example of a default TinD generated ID is `CBzF`. Much easier to read, much easier to understand.
TinD doesn't seek to be the most unique identifier in the world. It will not compete at all with UUID's for uniqueness globally, but for a small application, like say invoicing for a small to medium sized company, it provides a balance of uniqueness to readability, with a completely customizable set of characters to use.

View File

@ -0,0 +1,3 @@
TinD is highly configurable, and defaults to an extremely simple set of values:
- Default size of 4 bytes, or 32bits
- Default character set of upper and lower case alphabet characters `abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ`

View File

@ -4,8 +4,6 @@ package tind
// 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

View File

@ -44,16 +44,7 @@ func Test_GenTinD_With_VaryingSizes(t *testing.T) {
}
}
//func Test_Generate_With_Collisions(t *testing.T) {
// log.Println("Test TinD for collisions, base on number of iterations and time")
//
// maxSize := 16
// for size := 4; size < maxSize; size++ {
// CheckCollisions(size)
// }
//}
func CheckCollisions(tindSize int) {
func checkCollisions(tindSize int) {
ids := make(map[string]struct{})
collided := false
tc := NewTinDConfig().WithSize(tindSize)
@ -76,3 +67,38 @@ func CheckCollisions(tindSize int) {
}
log.Printf("Collision found after %v and %d iterations with size of %d", time.Since(start), iters, tindSize)
}
func Test_GenerateConfigFromByteSlice(t *testing.T) {
tests := []struct {
name string
bytes []byte
expectedSize int
expectedMod byte
runeset []rune
}{
{
name: "Default runeset with size of 10",
bytes: []byte("0123456789"),
expectedSize: 10,
expectedMod: 51,
runeset: []rune(defaultRunes),
},
{
name: "Custom runeset of 8 runes with size of 14",
bytes: []byte("01234567890123"),
expectedSize: 14,
expectedMod: 7,
runeset: []rune("aBcDeFgH"),
},
}
for _, v := range tests {
t.Log(v.name)
tt := NewConfigFrom(v.bytes).WithRuneset(v.runeset)
if tt.mod != v.expectedMod {
t.Errorf("expected mod %d, but got %d", v.expectedMod, tt.mod)
}
if tt.size != v.expectedSize {
t.Errorf("expected size of %d, got %d", v.expectedSize, tt.size)
}
}
}