Initial release: Go Jdenticon library v0.1.0

- Core library with SVG and PNG generation
- CLI tool with generate and batch commands
- Cross-platform path handling for Windows compatibility
- Comprehensive test suite with integration tests
This commit is contained in:
Kevin McIntyre
2026-01-02 23:56:48 -05:00
parent f84b511895
commit d9e84812ff
292 changed files with 19725 additions and 38884 deletions

View File

@@ -0,0 +1,103 @@
package engine
import (
"context"
"fmt"
"github.com/ungluedlabs/go-jdenticon/internal/util"
)
// Generate creates an identicon with the specified hash and size
// This method includes caching and singleflight to prevent duplicate work
func (g *Generator) Generate(ctx context.Context, hash string, size float64) (*Icon, error) {
// Basic validation
if hash == "" {
return nil, fmt.Errorf("jdenticon: engine: generation failed: hash cannot be empty")
}
if size <= 0 {
return nil, fmt.Errorf("jdenticon: engine: generation failed: invalid size: %f", size)
}
// Check icon size limits
if g.maxIconSize > 0 && int(size) > g.maxIconSize {
return nil, fmt.Errorf("jdenticon: engine: generation failed: icon size %d exceeds maximum allowed size %d", int(size), g.maxIconSize)
}
// Validate hash format
if !util.IsValidHash(hash) {
return nil, fmt.Errorf("jdenticon: engine: generation failed: invalid hash format: %s", hash)
}
// Check for context cancellation before proceeding
if err := ctx.Err(); err != nil {
return nil, err
}
// Generate cache key
key := g.cacheKey(hash, size)
// Check cache first (with read lock)
g.mu.RLock()
if cached, ok := g.cache.Get(key); ok {
g.mu.RUnlock()
g.metrics.recordHit()
return cached, nil
}
g.mu.RUnlock()
// Use singleflight to prevent multiple concurrent generations for the same key
result, err, _ := g.sf.Do(key, func() (interface{}, error) {
// Check cache again inside singleflight (another goroutine might have populated it)
g.mu.RLock()
if cached, ok := g.cache.Get(key); ok {
g.mu.RUnlock()
g.metrics.recordHit()
return cached, nil
}
g.mu.RUnlock()
// Generate the icon
icon, err := g.generateIcon(ctx, hash, size)
if err != nil {
return nil, err
}
// Store in cache (with write lock)
g.mu.Lock()
g.cache.Add(key, icon)
g.mu.Unlock()
g.metrics.recordMiss()
return icon, nil
})
if err != nil {
return nil, err
}
return result.(*Icon), nil
}
// GenerateWithoutCache creates an identicon without using cache
// This method is useful for testing or when caching is not desired
func (g *Generator) GenerateWithoutCache(ctx context.Context, hash string, size float64) (*Icon, error) {
// Basic validation
if hash == "" {
return nil, fmt.Errorf("jdenticon: engine: generation failed: hash cannot be empty")
}
if size <= 0 {
return nil, fmt.Errorf("jdenticon: engine: generation failed: invalid size: %f", size)
}
// Validate hash format
if !util.IsValidHash(hash) {
return nil, fmt.Errorf("jdenticon: engine: generation failed: invalid hash format: %s", hash)
}
// Check for context cancellation
if err := ctx.Err(); err != nil {
return nil, err
}
return g.generateIcon(ctx, hash, size)
}