Skip to content

Instantly share code, notes, and snippets.

@morhekil morhekil/main.go
Created Aug 10, 2014

Embed
What would you like to do?
Go kata to generate random strings, like promotional codes. See http://speakmy.name/2014/08/10/go-kata-generating-random-strings/ for details
package main
import (
"errors"
"fmt"
"morhekil/training/randstr/generator"
"os"
"strconv"
)
// main function parses command line arguments to get the number of codes
// to generate, and invokes the generator
func main() {
var count int
var err error
task := generator.New()
if count, err = setup(&task); err != nil {
fmt.Println(err)
os.Exit(1)
}
for code := range task.Generate(count) {
fmt.Fprintf(os.Stderr, "%d\r", code.Index)
fmt.Fprintf(os.Stdout, "%s\n", code.Value)
}
}
// New parses command line arguments and return task configuration
func setup(task *generator.Task) (int, error) {
// Make sure we've got two CLI arguments, if we don't - print short help
if len(os.Args) < 4 {
return 0, errors.New("Usage: ./randstr <number_of_codes_to_generate> <length> <chars>")
}
// First argument should be an integer, goes for the number of codes
count, err := strconv.ParseInt(os.Args[1], 10, 0)
if err != nil {
return 0, errors.New("number of codes should be an integer")
}
// First argument should be an integer, goes for the number of codes
task.Length, err = strconv.ParseInt(os.Args[2], 10, 0)
if err != nil {
return 0, errors.New("code length should be an integer")
}
// Chars argument can be anything
task.Chars = os.Args[3]
return int(count), nil
}
//
// generator/task.go
//
package generator
import (
"github.com/dchest/uniuri"
)
// Task setup
type Task struct {
Chars string
Length int64
// using map of empty structs to keep track of unique codes
codes map[string]struct{}
}
// Code represents generated codes
type Code struct {
Index int
Value string
}
// New task structure
func New() Task {
return Task{
Chars: string(uniuri.StdChars),
Length: uniuri.StdLen,
codes: make(map[string]struct{}),
}
}
// Generate the given number of generated codes using the existing task setup.
// Codes are printed to stdout, and progress count is printed to stderr
func (task *Task) Generate(count int) (codes chan Code) {
codes = make(chan Code)
go task.generate(count, codes)
return
}
// generate the given number of codes, and write them to the channel
func (task *Task) generate(count int, codes chan Code) {
defer close(codes)
for i := 0; i < int(count); i++ {
codes <- Code{Index: i, Value: task.next()}
}
}
// generates next unique code according to the setup rules
func (task *Task) next() (code string) {
for dup := true; dup; {
code = uniuri.NewLenChars(int(task.Length), []byte(task.Chars))
_, dup = task.codes[code]
}
task.codes[code] = struct{}{}
return
}
//
// generator/task_test.go
//
package generator_test
import (
"morhekil/training/randstr/generator"
"regexp"
"testing"
)
func TestNew(t *testing.T) {
task := generator.New()
if task.Length == 0 {
t.Errorf("length does not have default value")
}
if task.Chars == "" {
t.Errorf("chars does not have default value")
}
}
func TestGenerate(t *testing.T) {
codes := make(map[string]struct{})
var n int
task := generator.New()
task.Chars = "0123456789"
task.Length = 5
for code := range task.Generate(100) {
// Index received in order
if n != code.Index {
t.Fatalf("index %d received, %d expected", code.Index, n)
}
n++
// Code value is unique
if _, dup := codes[code.Value]; dup {
t.Fatalf("duplicate code %s received", code.Value)
}
// Code value has the right length
if len(code.Value) != int(task.Length) {
t.Fatalf("code %s has wrong length", code.Value)
}
// Code contains allowed characters only
if match, _ := regexp.Match("^["+task.Chars+"]+$$", []byte(code.Value)); !match {
t.Fatalf("code %s contains invalid characters", code.Value)
}
codes[code.Value] = struct{}{}
}
// Expected number of codes has been received
if n != 100 {
t.Errorf("expected 100 codes to be generated, got %d", n)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.