From 20a578caa74a4b0a3bd86adb442902d0fb19f625 Mon Sep 17 00:00:00 2001 From: gbbocchini Date: Sat, 12 Sep 2026 17:16:05 -0300 Subject: [PATCH 1/3] Rewrite as go-randomstring (v1.0.0) Co-Authored-By: Claude Code --- .gitignore | 6 ++ README.md | 120 ++++++++++++++++++++---- charscollections.go | 8 -- charsets.go | 24 +++++ doc.go | 17 ++++ example_test.go | 49 ++++++++++ go.mod | 4 +- randomstring.go | 193 +++++++++++++++++++++++++++++++++++++++ randomstring_test.go | 160 ++++++++++++++++++++++++++++++++ stringrandomizer.go | 88 ------------------ stringrandomizer_test.go | 55 ----------- 11 files changed, 555 insertions(+), 169 deletions(-) delete mode 100644 charscollections.go create mode 100644 charsets.go create mode 100644 doc.go create mode 100644 example_test.go create mode 100644 randomstring.go create mode 100644 randomstring_test.go delete mode 100644 stringrandomizer.go delete mode 100644 stringrandomizer_test.go diff --git a/.gitignore b/.gitignore index 52ff022..1d3e43c 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,9 @@ # Dependency directories (remove the comment below to include it) # vendor/ + +# IDE +.idea/ + +# Claude Code local settings +.claude/settings.local.json diff --git a/README.md b/README.md index 069a5e0..21cf0cb 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,125 @@ -# go-string-randomizer +# go-randomstring -Go-string-randomizer is a simple but powerfull random strings generator for GO. -You can use it to generate random ids, passwords, etc. +> Fast, flexible random string generation for Go β€” ids, tokens, and passwords in one small, dependency-free package. -The package also includes a usefull collection of chars that can be used as argument LettersUniverse. +[![Go Reference](https://pkg.go.dev/badge/github.com/gbbocchini/go-randomstring.svg)](https://pkg.go.dev/github.com/gbbocchini/go-randomstring) +[![Go Report Card](https://goreportcard.com/badge/github.com/gbbocchini/go-randomstring)](https://goreportcard.com/report/github.com/gbbocchini/go-randomstring) +[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) + +## Features + +- πŸ”€ **Flexible character sets** β€” seven built-in universes, or bring your own (Unicode included). +- πŸ”’ **Crypto-secure mode** β€” draw from `crypto/rand` for passwords and security tokens. +- πŸ” **Unique batches** β€” generate thousands of guaranteed-distinct strings in one call. +- 🎲 **Deterministic output** β€” pin a `Seed` for reproducible results in tests. +- πŸš€ **Fast** β€” an ASCII fast path with zero dependencies beyond the standard library. ## Installation ```bash -go get github.com/gbbocchini/go-string-randomizer +go get github.com/gbbocchini/go-randomstring +``` + +## Quick start + +```go +package main + +import ( + "fmt" + + "github.com/gbbocchini/go-randomstring" +) + +func main() { + r := randomstring.Randomizer{ + Universe: randomstring.LowerUpperDigits, + Length: 13, + } + id, err := r.GenerateOne() + if err != nil { + panic(err) + } + fmt.Println(id) +} ``` ## Usage +### Basic generation + +```go +r := randomstring.Randomizer{ + Universe: randomstring.LowerLetters, + Length: 8, +} +id, err := r.GenerateOne() +``` + +### Unique batches + +Set `Unique` to guarantee every string in a batch is distinct: + ```go -import "github.com/gbbocchini/go-string-randomizer" +r := randomstring.Randomizer{ + Universe: randomstring.LowerUpperDigits, + Length: 8, + Unique: true, +} +ids, err := r.Generate(10_000) // 10,000 distinct ids +``` + +### Crypto-secure passwords -randomizer := StringRandomizer{ - LettersUniverse: LOWER_UPPER_LETTERS_NUMS, - GeneratedMaxLen: 13, - NoCollisions: true, - RandSeed: 123, +```go +r := randomstring.Randomizer{ + Universe: randomstring.LowerUpperDigitsSymbols, + Length: 32, + Secure: true, } +password, err := r.GenerateOne() +``` -one := randomizer.GenerateOne() +### Deterministic output (great for tests) -bulk := randomizer.GenerateBulk(1000000) +```go +r := randomstring.Randomizer{ + Universe: randomstring.LowerUpperDigits, + Length: 13, + Seed: 1, +} +id, _ := r.GenerateOne() // always "9g14r5YgIsx9v" ``` +### Custom universe + +```go +r := randomstring.Randomizer{ + Universe: "ABC123", // only these characters + Length: 6, +} +``` + +### Built-in character sets + +| Constant | Contents | +| --- | --- | +| `LowerLetters` | `a-z` | +| `UpperLetters` | `A-Z` | +| `Digits` | `0-9` | +| `Symbols` | `!@#$%&*()-_+={};:.,` | +| `LowerUpperLetters` | `a-z` + `A-Z` | +| `LowerUpperDigits` | `a-z` + `A-Z` + `0-9` | +| `LowerUpperDigitsSymbols` | `a-z` + `A-Z` + `0-9` + symbols | + +## Documentation + +Full API reference is available on [pkg.go.dev](https://pkg.go.dev/github.com/gbbocchini/go-randomstring). + ## Contributing -Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. -Please make sure to update tests as appropriate. +Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change, and update tests as appropriate. ## License -[MIT](https://choosealicense.com/licenses/mit/) \ No newline at end of file + +[MIT](LICENSE) Β© Gabriel Bocchini diff --git a/charscollections.go b/charscollections.go deleted file mode 100644 index 29d1831..0000000 --- a/charscollections.go +++ /dev/null @@ -1,8 +0,0 @@ -// Provides useful char collections to be used with the package -package go_string_randomizer - -const LOWER_LETTERS = "abcdefghijklmnopqrstuvwxyz" -const LOWER_UPPER_LETTERS = "abcdefghijklmnopqrstuvwxyzABCDEUFGHIJKLMNOPQRSTUVWXYZ" -const LOWER_LETTERS_NUMS = "abcdefghijklmnopqrstuvwxyz0123456789" -const LOWER_UPPER_LETTERS_NUMS = "abcdefghijklmnopqrstuvwxyzABCDEUFGHIJKLMNOPQRSTUVWXYZ0123456789" -const LOWER_UPPER_LETTERS_NUMS_SYMBOLS = "abcdefghijklmnopqrstuvwxyzABCDEUFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%&*()-_+={};:.," diff --git a/charsets.go b/charsets.go new file mode 100644 index 0000000..fac9640 --- /dev/null +++ b/charsets.go @@ -0,0 +1,24 @@ +package randomstring + +// LowerLetters contains the lowercase ASCII letters a-z. +const LowerLetters = "abcdefghijklmnopqrstuvwxyz" + +// UpperLetters contains the uppercase ASCII letters A-Z. +const UpperLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + +// Digits contains the decimal digits 0-9. +const Digits = "0123456789" + +// Symbols contains a set of common punctuation and symbol characters. +const Symbols = "!@#$%&*()-_+={};:.," + +// LowerUpperLetters contains all lowercase and uppercase ASCII letters. +const LowerUpperLetters = LowerLetters + UpperLetters + +// LowerUpperDigits contains all lowercase and uppercase ASCII letters plus +// digits. +const LowerUpperDigits = LowerLetters + UpperLetters + Digits + +// LowerUpperDigitsSymbols contains all lowercase and uppercase ASCII letters, +// digits, and symbols. +const LowerUpperDigitsSymbols = LowerUpperDigits + Symbols diff --git a/doc.go b/doc.go new file mode 100644 index 0000000..1e8ae85 --- /dev/null +++ b/doc.go @@ -0,0 +1,17 @@ +// Package randomstring generates random strings for use as identifiers, +// tokens, passwords, and anywhere else you need short, configurable random +// text. +// +// The core type is Randomizer, configured through exported struct fields: +// +// r := randomstring.Randomizer{ +// Universe: randomstring.LowerUpperDigits, +// Length: 13, +// Unique: true, +// } +// id, err := r.GenerateOne() +// +// The package also provides ready-made character sets (LowerLetters, +// UpperLetters, Digits, Symbols, and their common combinations) that can be +// passed as a Randomizer's Universe. +package randomstring diff --git a/example_test.go b/example_test.go new file mode 100644 index 0000000..bc659df --- /dev/null +++ b/example_test.go @@ -0,0 +1,49 @@ +package randomstring_test + +import ( + "fmt" + + "github.com/gbbocchini/go-randomstring" +) + +func ExampleRandomizer_GenerateOne() { + r := randomstring.Randomizer{ + Universe: randomstring.LowerUpperDigits, + Length: 13, + Seed: 1, + } + id, err := r.GenerateOne() + if err != nil { + panic(err) + } + fmt.Println(id) + // Output: 9g14r5YgIsx9v +} + +func ExampleRandomizer_Generate() { + r := randomstring.Randomizer{ + Universe: randomstring.LowerUpperDigits, + Length: 8, + Unique: true, + } + ids, err := r.Generate(1000) + if err != nil { + panic(err) + } + fmt.Println(len(ids)) + // Output: 1000 +} + +func ExampleRandomizer_GenerateOne_secure() { + r := randomstring.Randomizer{ + Universe: randomstring.LowerUpperDigitsSymbols, + Length: 32, + Secure: true, + } + token, err := r.GenerateOne() + if err != nil { + panic(err) + } + fmt.Println(len(token)) + // Output: 32 +} diff --git a/go.mod b/go.mod index fc175e9..dd5cd75 100644 --- a/go.mod +++ b/go.mod @@ -1,3 +1,3 @@ -module github.com/gbbocchini/go-string-randomizer +module github.com/gbbocchini/go-randomstring -go 1.18 +go 1.23 diff --git a/randomstring.go b/randomstring.go new file mode 100644 index 0000000..81c8d86 --- /dev/null +++ b/randomstring.go @@ -0,0 +1,193 @@ +package randomstring + +import ( + crand "crypto/rand" + "errors" + "fmt" + "math/big" + "unicode/utf8" + + mrand "math/rand/v2" +) + +// Randomizer generates random strings according to its configuration. +// +// A Randomizer value may be reused and shared across goroutines: all fields +// are read only during generation, and no mutable state is kept on the value. +type Randomizer struct { + // Universe is the set of runes from which generated strings are drawn. + // It must not be empty. Multi-byte (Unicode) runes are supported. + Universe string + + // Length is the number of runes in each generated string. It must be + // greater than zero. + Length int + + // Unique, when true, makes a single call to Generate return amount + // distinct strings. It has no effect on GenerateOne. + Unique bool + + // Secure, when true, draws randomness from crypto/rand, producing output + // suitable for passwords and other security-sensitive tokens. Secure + // generation ignores Seed. + Secure bool + + // Seed makes output deterministic: two Randomizers with the same + // configuration and the same non-zero Seed produce identical output. + // The zero value (the default) seeds from a random source. Seed is + // ignored when Secure is true. + Seed int64 +} + +// GenerateOne generates a single random string. +// +// It returns an error if Universe is empty or Length is less than one. +func (r Randomizer) GenerateOne() (string, error) { + if err := r.validate(); err != nil { + return "", err + } + return r.generate(r.newPicker()) +} + +// Generate generates amount random strings. +// +// If Unique is set, the returned strings are all distinct. Generate returns an +// error if Universe is empty, Length is less than one, amount is negative, or +// Unique is set and amount exceeds the number of possible permutations. +func (r Randomizer) Generate(amount int) ([]string, error) { + if err := r.validate(); err != nil { + return nil, err + } + if amount < 0 { + return nil, fmt.Errorf("randomstring: amount must not be negative, got %d", amount) + } + + pick := r.newPicker() + + if !r.Unique { + out := make([]string, amount) + for i := range out { + s, err := r.generate(pick) + if err != nil { + return nil, err + } + out[i] = s + } + return out, nil + } + + if max := r.UniquePermutations(); new(big.Int).SetInt64(int64(amount)).Cmp(max) > 0 { + return nil, fmt.Errorf("randomstring: cannot generate %d unique strings from only %s permutations", amount, max) + } + + out := make([]string, 0, amount) + seen := make(map[string]struct{}, amount) + for len(out) < amount { + s, err := r.generate(pick) + if err != nil { + return nil, err + } + if _, dup := seen[s]; dup { + continue + } + seen[s] = struct{}{} + out = append(out, s) + } + return out, nil +} + +// UniquePermutations returns the maximum number of distinct strings this +// Randomizer can produce, as a big.Int. +// +// It is the number of distinct runes in Universe raised to the power of +// Length. When Universe contains no duplicate runes this equals +// len(Universe)^Length. +func (r Randomizer) UniquePermutations() *big.Int { + base := big.NewInt(int64(distinctRunes(r.Universe))) + return new(big.Int).Exp(base, big.NewInt(int64(r.Length)), nil) +} + +// validate reports whether the Randomizer is configured correctly. +func (r Randomizer) validate() error { + if r.Universe == "" { + return errors.New("randomstring: Universe must not be empty") + } + if r.Length < 1 { + return fmt.Errorf("randomstring: Length must be greater than zero, got %d", r.Length) + } + return nil +} + +// picker returns an index in [0, n). +type picker func(n int) (int, error) + +// newPicker returns the random-index source for a single generation call, +// honoring Secure and Seed. The seeded source is constructed once so that a +// whole call advances through one deterministic sequence. +func (r Randomizer) newPicker() picker { + switch { + case r.Secure: + return func(n int) (int, error) { + v, err := crand.Int(crand.Reader, big.NewInt(int64(n))) + if err != nil { + return 0, err + } + return int(v.Int64()), nil + } + case r.Seed != 0: + src := mrand.New(mrand.NewPCG(uint64(r.Seed), uint64(r.Seed))) + return func(n int) (int, error) { + return src.IntN(n), nil + } + default: + return func(n int) (int, error) { + return mrand.IntN(n), nil + } + } +} + +// generate produces one random string using the given picker. +func (r Randomizer) generate(pick picker) (string, error) { + if isASCII(r.Universe) { + alphabet := []byte(r.Universe) + buf := make([]byte, r.Length) + for i := range buf { + idx, err := pick(len(alphabet)) + if err != nil { + return "", err + } + buf[i] = alphabet[idx] + } + return string(buf), nil + } + + alphabet := []rune(r.Universe) + buf := make([]rune, r.Length) + for i := range buf { + idx, err := pick(len(alphabet)) + if err != nil { + return "", err + } + buf[i] = alphabet[idx] + } + return string(buf), nil +} + +// isASCII reports whether s consists entirely of single-byte (ASCII) runes. +func isASCII(s string) bool { + for i := 0; i < len(s); i++ { + if s[i] >= utf8.RuneSelf { + return false + } + } + return true +} + +// distinctRunes reports the number of distinct runes in s. +func distinctRunes(s string) int { + seen := make(map[rune]struct{}) + for _, r := range s { + seen[r] = struct{}{} + } + return len(seen) +} diff --git a/randomstring_test.go b/randomstring_test.go new file mode 100644 index 0000000..0416bb6 --- /dev/null +++ b/randomstring_test.go @@ -0,0 +1,160 @@ +package randomstring_test + +import ( + "math/big" + "slices" + "strings" + "testing" + "unicode/utf8" + + "github.com/gbbocchini/go-randomstring" +) + +func TestGenerateOne(t *testing.T) { + r := randomstring.Randomizer{Universe: randomstring.LowerUpperDigits, Length: 13} + s, err := r.GenerateOne() + if err != nil { + t.Fatal(err) + } + if len(s) != 13 { + t.Fatalf("length = %d, want 13", len(s)) + } + for _, c := range s { + if !strings.ContainsRune(randomstring.LowerUpperDigits, c) { + t.Fatalf("unexpected rune %q", c) + } + } +} + +func TestGenerateOne_Deterministic(t *testing.T) { + r1 := randomstring.Randomizer{Universe: randomstring.LowerUpperDigits, Length: 12, Seed: 42} + r2 := randomstring.Randomizer{Universe: randomstring.LowerUpperDigits, Length: 12, Seed: 42} + a, err := r1.GenerateOne() + if err != nil { + t.Fatal(err) + } + b, err := r2.GenerateOne() + if err != nil { + t.Fatal(err) + } + if a != b { + t.Fatalf("expected deterministic output, got %q and %q", a, b) + } +} + +func TestGenerateOne_Unicode(t *testing.T) { + const universe = "γ“γ‚“γ«γ‘γ―δΈ–η•Œ" + r := randomstring.Randomizer{Universe: universe, Length: 10} + s, err := r.GenerateOne() + if err != nil { + t.Fatal(err) + } + if got := utf8.RuneCountInString(s); got != 10 { + t.Fatalf("rune count = %d, want 10", got) + } + for _, c := range s { + if !strings.ContainsRune(universe, c) { + t.Fatalf("unexpected rune %q", c) + } + } +} + +func TestGenerate(t *testing.T) { + r := randomstring.Randomizer{Universe: randomstring.LowerLetters, Length: 6} + got, err := r.Generate(100) + if err != nil { + t.Fatal(err) + } + if len(got) != 100 { + t.Fatalf("len = %d, want 100", len(got)) + } + for _, s := range got { + if len(s) != 6 { + t.Fatalf("word length = %d, want 6", len(s)) + } + } +} + +func TestGenerate_Unique(t *testing.T) { + r := randomstring.Randomizer{Universe: randomstring.LowerLetters, Length: 4, Unique: true} + got, err := r.Generate(5000) + if err != nil { + t.Fatal(err) + } + if len(got) != 5000 { + t.Fatalf("len = %d, want 5000", len(got)) + } + seen := make(map[string]struct{}, len(got)) + for _, s := range got { + if _, dup := seen[s]; dup { + t.Fatalf("duplicate string %q", s) + } + seen[s] = struct{}{} + } +} + +func TestGenerate_Deterministic(t *testing.T) { + r1 := randomstring.Randomizer{Universe: randomstring.LowerUpperDigits, Length: 8, Seed: 7} + r2 := randomstring.Randomizer{Universe: randomstring.LowerUpperDigits, Length: 8, Seed: 7} + a, err := r1.Generate(10) + if err != nil { + t.Fatal(err) + } + b, err := r2.Generate(10) + if err != nil { + t.Fatal(err) + } + if !slices.Equal(a, b) { + t.Fatalf("expected deterministic slices, got %v and %v", a, b) + } +} + +func TestGenerate_UniqueExceedsPermutations(t *testing.T) { + r := randomstring.Randomizer{Universe: "ab", Length: 2, Unique: true} + if _, err := r.Generate(5); err == nil { + t.Fatal("expected error when amount exceeds permutations") + } +} + +func TestGenerate_Errors(t *testing.T) { + bad := []randomstring.Randomizer{ + {Universe: "", Length: 5}, + {Universe: randomstring.LowerLetters, Length: 0}, + {Universe: randomstring.LowerLetters, Length: -1}, + } + for _, r := range bad { + if _, err := r.GenerateOne(); err == nil { + t.Errorf("expected error for %+v", r) + } + } + if _, err := (randomstring.Randomizer{Universe: randomstring.LowerLetters, Length: 5}).Generate(-1); err == nil { + t.Error("expected error for negative amount") + } +} + +func TestGenerate_Secure(t *testing.T) { + r := randomstring.Randomizer{Universe: randomstring.LowerUpperDigitsSymbols, Length: 32, Secure: true} + s, err := r.GenerateOne() + if err != nil { + t.Fatal(err) + } + if len(s) != 32 { + t.Fatalf("len = %d, want 32", len(s)) + } +} + +func TestUniquePermutations(t *testing.T) { + r := randomstring.Randomizer{Universe: randomstring.LowerLetters, Length: 3} + want := big.NewInt(26 * 26 * 26) + if got := r.UniquePermutations(); got.Cmp(want) != 0 { + t.Errorf("UniquePermutations = %s, want %s", got, want) + } +} + +func BenchmarkGenerate(b *testing.B) { + r := randomstring.Randomizer{Universe: randomstring.LowerUpperDigits, Length: 16} + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _, _ = r.GenerateOne() + } +} diff --git a/stringrandomizer.go b/stringrandomizer.go deleted file mode 100644 index 9468fe8..0000000 --- a/stringrandomizer.go +++ /dev/null @@ -1,88 +0,0 @@ -// Package go_string_randomizer Package provides small and optimized functions to generate -//random strings that can be used as passwords, control ids, etc. -package go_string_randomizer - -import ( - "fmt" - "math" - "math/rand" -) - -type StringRandomizer struct { - LettersUniverse string - GeneratedMaxLen int - NoCollisions bool - RandSeed int -} - -// GenerateOne - generates 1 randomized string based on the struct initialization vars -func (s StringRandomizer) GenerateOne() string { - rand.Seed(int64(s.RandSeed)) - id := make([]rune, s.GeneratedMaxLen) - letters := []rune(s.LettersUniverse) - for i := 0; i < s.GeneratedMaxLen; i++ { - id[i] = letters[rand.Intn(len(letters))] - } - return string(id) -} - -// GenerateBulk - generates amount arg of randomized string based on the struct initialization vars -func (s StringRandomizer) GenerateBulk(amount int) (error, []string) { - var err error - var result []string - - if s.NoCollisions { - err = s.validatePermutationsAmount(amount) - } - - if err != nil { - return err, result - } - - rand.Seed(int64(s.RandSeed)) - collisionControl := make(map[string]bool) - letters := []rune(s.LettersUniverse) - - for len(result) != amount { - id := make([]rune, s.GeneratedMaxLen) - for i := 0; i < s.GeneratedMaxLen; i++ { - id[i] = letters[rand.Intn(len(letters))] - } - - if s.NoCollisions { - if !s.isCollision(collisionControl, string(id)) { - result = append(result, string(id)) - } - } else { - result = append(result, string(id)) - } - } - return err, result -} - -// GetUniquePermutationsAmt Gets the max number of possible permutations given the struct initialization args -func (s StringRandomizer) GetUniquePermutationsAmt() float64 { - return math.Pow( - float64(len(s.LettersUniverse)), - float64(s.GeneratedMaxLen), - ) -} - -//helper function that checks collisions when attrib NoCollisions on the struct is set to true -func (s StringRandomizer) isCollision(controlMap map[string]bool, item string) bool { - if _, exists := controlMap[item]; !exists { - controlMap[item] = true - return false - } - return true -} - -//helper function that validates the max unique permutations amount -func (s StringRandomizer) validatePermutationsAmount(amountToGenerate int) error { - var err error - if float64(amountToGenerate) > s.GetUniquePermutationsAmt() { - err = fmt.Errorf("the amount desired for generation (%v) is higher than the possible combinations (%v)", - amountToGenerate, s.GetUniquePermutationsAmt()) - } - return err -} diff --git a/stringrandomizer_test.go b/stringrandomizer_test.go deleted file mode 100644 index a285fbb..0000000 --- a/stringrandomizer_test.go +++ /dev/null @@ -1,55 +0,0 @@ -package go_string_randomizer - -import "testing" - -func TestGenerateOne(t *testing.T) { - randomizer := StringRandomizer{ - LettersUniverse: LOWER_UPPER_LETTERS_NUMS, - GeneratedMaxLen: 7, - NoCollisions: true, - RandSeed: 123, - } - res := randomizer.GenerateOne() - if len(res) != 7 { - t.Errorf("Generated len should be 7") - } -} - -func TestGenerateBulk(t *testing.T) { - randomizer := StringRandomizer{ - LettersUniverse: LOWER_UPPER_LETTERS_NUMS, - GeneratedMaxLen: 10, - NoCollisions: true, - RandSeed: 123, - } - err, res := randomizer.GenerateBulk(1000) - - if len(res) != 1000 && err == nil { - t.Errorf("Generated slice len should be 1000") - } - - if len(res[0]) != 10 && err == nil { - t.Errorf("Generated word len should be 10") - } -} - -func TestIsCollision(t *testing.T) { - randomizer := StringRandomizer{ - LettersUniverse: LOWER_UPPER_LETTERS_NUMS, - GeneratedMaxLen: 10, - NoCollisions: true, - RandSeed: 123, - } - ctrl := map[string]bool{"A": true, "B": false} - res := randomizer.isCollision(ctrl, "A") - - if !res { - t.Errorf("A should return true") - } - - res = randomizer.isCollision(ctrl, "B") - - if !res { - t.Errorf("B should return true") - } -} From 7297eb78dcbc642c9a10143a5b38ffb8838a38e7 Mon Sep 17 00:00:00 2001 From: gbbocchini Date: Sat, 12 Sep 2026 17:18:35 -0300 Subject: [PATCH 2/3] Add CI workflow Co-Authored-By: Claude Code --- .github/workflows/ci.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5962827 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,32 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +jobs: + test: + name: Vet & Test + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + + - name: Verify formatting + run: test -z "$(gofmt -l .)" + + - name: Vet + run: go vet ./... + + - name: Test + run: go test ./... From e5e70af4e3ba242b0db723c1661bfb661a3db1e7 Mon Sep 17 00:00:00 2001 From: gbbocchini Date: Sat, 12 Sep 2026 17:20:29 -0300 Subject: [PATCH 3/3] Remove auto-generated Go workflow (superseded by CI) Co-Authored-By: Claude Code --- .github/workflows/go.yml | 28 ---------------------------- 1 file changed, 28 deletions(-) delete mode 100644 .github/workflows/go.yml diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml deleted file mode 100644 index 0b443f3..0000000 --- a/.github/workflows/go.yml +++ /dev/null @@ -1,28 +0,0 @@ -# This workflow will build a golang project -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go - -name: Go - -on: - push: - branches: [ "main" ] - pull_request: - branches: [ "main" ] - -jobs: - - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Set up Go - uses: actions/setup-go@v4 - with: - go-version: '1.20' - - - name: Build - run: go build -v ./... - - - name: Test - run: go test -v ./...