Skip to content

Instantly share code, notes, and snippets.

@morhekil
Created August 10, 2014 11:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save morhekil/d96c922dd7fe000d518a to your computer and use it in GitHub Desktop.
Save morhekil/d96c922dd7fe000d518a to your computer and use it in GitHub Desktop.
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