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