package generator import ( "fmt" "math/rand" "regexp" "strconv" "strings" ) // ExpressionValueGenerator implements Generator interface. It generates // random string based on the input expression. The input expression is // a string, which may contain "[a-zA-Z0-9]{length}" constructs, // defining range and length of the result random characters. // // Examples: // // from | value // ----------------------------- // "test[0-9]{1}x" | "test7x" // "[0-1]{8}" | "01001100" // "0x[A-F0-9]{4}" | "0xB3AF" // "[a-zA-Z0-9]{8}" | "hW4yQU5i" // // TODO: Support more regexp constructs. type ExpressionValueGenerator struct { seed *rand.Rand } const ( Alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" Numerals = "0123456789" Symbols = "~!@#$%^&*()-_+={}[]\\|<,>.?/\"';:`" ASCII = Alphabet + Numerals + Symbols ) var ( rangeExp = regexp.MustCompile(`([\\]?[a-zA-Z0-9]\-?[a-zA-Z0-9]?)`) generatorsExp = regexp.MustCompile(`\[([a-zA-Z0-9\-\\]+)\](\{([0-9]+)\})`) expressionExp = regexp.MustCompile(`\[(\\w|\\d|\\a|\\A)|([a-zA-Z0-9]\-[a-zA-Z0-9])+\]`) ) // NewExpressionValueGenerator creates new ExpressionValueGenerator. func NewExpressionValueGenerator(seed *rand.Rand) ExpressionValueGenerator { return ExpressionValueGenerator{seed: seed} } // GenerateValue generates random string based on the input expression. // The input expression is a pseudo-regex formatted string. See // ExpressionValueGenerator for more details. func (g ExpressionValueGenerator) GenerateValue(expression string) (interface{}, error) { for { r := generatorsExp.FindStringIndex(expression) if r == nil { break } ranges, length, err := rangesAndLength(expression[r[0]:r[1]]) if err != nil { return "", err } err = replaceWithGenerated( &expression, expression[r[0]:r[1]], findExpressionPos(ranges), length, g.seed, ) if err != nil { return "", err } } return expression, nil } // alphabetSlice produces a string slice that contains all characters within // a specified range. func alphabetSlice(from, to byte) (string, error) { leftPos := strings.Index(ASCII, string(from)) rightPos := strings.LastIndex(ASCII, string(to)) if leftPos > rightPos { return "", fmt.Errorf("invalid range specified: %s-%s", string(from), string(to)) } return ASCII[leftPos:rightPos], nil } // replaceWithGenerated replaces all occurrences of the given expression // in the string with random characters of the specified range and length. func replaceWithGenerated(s *string, expression string, ranges [][]byte, length int, seed *rand.Rand) error { var alphabet string for _, r := range ranges { switch string(r[0]) + string(r[1]) { case `\w`: alphabet += Alphabet + Numerals + "_" case `\d`: alphabet += Numerals case `\a`: alphabet += Alphabet + Numerals case `\A`: alphabet += Symbols default: slice, err := alphabetSlice(r[0], r[1]) if err != nil { return err } alphabet += slice } } result := make([]byte, length) alphabet = removeDuplicateChars(alphabet) for i := 0; i < length; i++ { result[i] = alphabet[seed.Intn(len(alphabet))] } *s = strings.Replace(*s, expression, string(result), 1) return nil } // removeDuplicateChars removes the duplicate characters from the data slice func removeDuplicateChars(input string) string { data := []byte(input) length := len(data) - 1 for i := 0; i < length; i++ { for j := i + 1; j <= length; j++ { if data[i] == data[j] { data[j] = data[length] data = data[0:length] length-- j-- } } } return string(data) } // findExpressionPos searches the given string for the valid expressions // and returns their corresponding indexes. func findExpressionPos(s string) [][]byte { matches := rangeExp.FindAllStringIndex(s, -1) result := make([][]byte, len(matches)) for i, r := range matches { result[i] = []byte{s[r[0]], s[r[1]-1]} } return result } // rangesAndLength extracts the expression ranges (eg. [A-Z0-9]) and length // (eg. {3}). This helper function also validates the expression syntax and // its length (must be within 1..255). func rangesAndLength(s string) (string, int, error) { expr := s[0:strings.LastIndex(s, "{")] if !expressionExp.MatchString(expr) { return "", 0, fmt.Errorf("malformed expresion syntax: %s", expr) } length, _ := strconv.Atoi(s[strings.LastIndex(s, "{")+1 : len(s)-1]) // TODO: We do need to set a better limit for the number of generated characters. if length > 0 && length <= 255 { return expr, length, nil } return "", 0, fmt.Errorf("range must be within [1-255] characters (%d)", length) }