Created
August 10, 2014 11:20
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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