422 lines
11 KiB
Go
422 lines
11 KiB
Go
package gofakeit
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"math/rand"
|
|
"regexp/syntax"
|
|
"strings"
|
|
)
|
|
|
|
// Generate fake information from given string.
|
|
// Replaceable values should be within {}
|
|
//
|
|
// Functions
|
|
// Ex: {firstname} - billy
|
|
// Ex: {sentence:3} - Record river mind.
|
|
// Ex: {number:1,10} - 4
|
|
// Ex: {uuid} - 590c1440-9888-45b0-bd51-a817ee07c3f2
|
|
//
|
|
// Letters/Numbers
|
|
// Ex: ### - 481 - random numbers
|
|
// Ex: ??? - fda - random letters
|
|
//
|
|
// For a complete list of runnable functions use FuncsLookup
|
|
func Generate(dataVal string) string { return generate(globalFaker.Rand, dataVal) }
|
|
|
|
// Generate fake information from given string.
|
|
// Replaceable values should be within {}
|
|
//
|
|
// Functions
|
|
// Ex: {firstname} - billy
|
|
// Ex: {sentence:3} - Record river mind.
|
|
// Ex: {number:1,10} - 4
|
|
// Ex: {uuid} - 590c1440-9888-45b0-bd51-a817ee07c3f2
|
|
//
|
|
// Letters/Numbers
|
|
// Ex: ### - 481 - random numbers
|
|
// Ex: ??? - fda - random letters
|
|
//
|
|
// For a complete list of runnable functions use FuncsLookup
|
|
func (f *Faker) Generate(dataVal string) string { return generate(f.Rand, dataVal) }
|
|
|
|
func generate(r *rand.Rand, dataVal string) string {
|
|
// Replace # with numbers and ? with letters
|
|
dataVal = replaceWithNumbers(r, dataVal)
|
|
dataVal = replaceWithLetters(r, dataVal)
|
|
|
|
// Check if string has any replaceable values
|
|
if !strings.Contains(dataVal, "{") && !strings.Contains(dataVal, "}") {
|
|
return dataVal
|
|
}
|
|
|
|
// Variables to identify the index in which it exists
|
|
startCurly := -1
|
|
startCurlyIgnore := []int{}
|
|
endCurly := -1
|
|
endCurlyIgnore := []int{}
|
|
|
|
// Loop through string characters
|
|
for i := 0; i < len(dataVal); i++ {
|
|
// Check for ignores if equal skip
|
|
shouldSkip := false
|
|
for _, igs := range startCurlyIgnore {
|
|
if i == igs {
|
|
shouldSkip = true
|
|
}
|
|
}
|
|
for _, ige := range endCurlyIgnore {
|
|
if i == ige {
|
|
shouldSkip = true
|
|
}
|
|
}
|
|
if shouldSkip {
|
|
continue
|
|
}
|
|
|
|
// Identify items between brackets. Ex: {firstname}
|
|
if string(dataVal[i]) == "{" {
|
|
startCurly = i
|
|
continue
|
|
}
|
|
if startCurly != -1 && string(dataVal[i]) == "}" {
|
|
endCurly = i
|
|
}
|
|
if startCurly == -1 || endCurly == -1 {
|
|
continue
|
|
}
|
|
|
|
// Get the value between brackets
|
|
fParts := dataVal[startCurly+1 : endCurly]
|
|
|
|
// Check if has params separated by :
|
|
fNameSplit := strings.SplitN(fParts, ":", 2)
|
|
fName := ""
|
|
fParams := ""
|
|
if len(fNameSplit) >= 1 {
|
|
fName = fNameSplit[0]
|
|
}
|
|
if len(fNameSplit) >= 2 {
|
|
fParams = fNameSplit[1]
|
|
}
|
|
|
|
// Check to see if its a replaceable lookup function
|
|
if info := GetFuncLookup(fName); info != nil {
|
|
// Get parameters, make sure params and the split both have values
|
|
mapParams := NewMapParams()
|
|
paramsLen := len(info.Params)
|
|
|
|
// If just one param and its a string simply just pass it
|
|
if paramsLen == 1 && info.Params[0].Type == "string" {
|
|
mapParams.Add(info.Params[0].Field, fParams)
|
|
} else if paramsLen > 0 && fParams != "" {
|
|
splitVals := funcLookupSplit(fParams)
|
|
mapParams = addSplitValsToMapParams(splitVals, info, mapParams)
|
|
}
|
|
if mapParams.Size() == 0 {
|
|
mapParams = nil
|
|
}
|
|
|
|
// Call function
|
|
fValue, err := info.Generate(r, mapParams, info)
|
|
if err != nil {
|
|
// If we came across an error just dont replace value
|
|
dataVal = strings.Replace(dataVal, "{"+fParts+"}", err.Error(), 1)
|
|
} else {
|
|
// Successfully found, run replace with new value
|
|
dataVal = strings.Replace(dataVal, "{"+fParts+"}", fmt.Sprintf("%v", fValue), 1)
|
|
}
|
|
|
|
// Reset the curly index back to -1 and reset ignores
|
|
startCurly = -1
|
|
startCurlyIgnore = []int{}
|
|
endCurly = -1
|
|
endCurlyIgnore = []int{}
|
|
i = -1 // Reset back to the start of the string
|
|
continue
|
|
}
|
|
|
|
// Couldnt find anything - mark curly brackets to skip and rerun
|
|
startCurlyIgnore = append(startCurlyIgnore, startCurly)
|
|
endCurlyIgnore = append(endCurlyIgnore, endCurly)
|
|
|
|
// Reset the curly index back to -1
|
|
startCurly = -1
|
|
endCurly = -1
|
|
i = -1 // Reset back to the start of the string
|
|
continue
|
|
}
|
|
|
|
return dataVal
|
|
}
|
|
|
|
// Regex will generate a string based upon a RE2 syntax
|
|
func Regex(regexStr string) string { return regex(globalFaker.Rand, regexStr) }
|
|
|
|
// Regex will generate a string based upon a RE2 syntax
|
|
func (f *Faker) Regex(regexStr string) string { return regex(f.Rand, regexStr) }
|
|
|
|
func regex(r *rand.Rand, regexStr string) (gen string) {
|
|
re, err := syntax.Parse(regexStr, syntax.Perl)
|
|
if err != nil {
|
|
return "Could not parse regex string"
|
|
}
|
|
|
|
// Panic catch
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
gen = fmt.Sprint(r)
|
|
return
|
|
|
|
}
|
|
}()
|
|
|
|
return regexGenerate(r, re, len(regexStr)*100)
|
|
}
|
|
|
|
func regexGenerate(ra *rand.Rand, re *syntax.Regexp, limit int) string {
|
|
if limit <= 0 {
|
|
panic("Length limit reached when generating output")
|
|
}
|
|
|
|
op := re.Op
|
|
switch op {
|
|
case syntax.OpNoMatch: // matches no strings
|
|
// Do Nothing
|
|
case syntax.OpEmptyMatch: // matches empty string
|
|
return ""
|
|
case syntax.OpLiteral: // matches Runes sequence
|
|
var b strings.Builder
|
|
for _, ru := range re.Rune {
|
|
b.WriteRune(ru)
|
|
}
|
|
return b.String()
|
|
case syntax.OpCharClass: // matches Runes interpreted as range pair list
|
|
// number of possible chars
|
|
sum := 0
|
|
for i := 0; i < len(re.Rune); i += 2 {
|
|
sum += int(re.Rune[i+1]-re.Rune[i]) + 1
|
|
if re.Rune[i+1] == 0x10ffff { // rune range end
|
|
sum = -1
|
|
break
|
|
}
|
|
}
|
|
|
|
// pick random char in range (inverse match group)
|
|
if sum == -1 {
|
|
chars := []uint8{}
|
|
for j := 0; j < len(allStr); j++ {
|
|
c := allStr[j]
|
|
|
|
// Check c in range
|
|
for i := 0; i < len(re.Rune); i += 2 {
|
|
if rune(c) >= re.Rune[i] && rune(c) <= re.Rune[i+1] {
|
|
chars = append(chars, c)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
if len(chars) > 0 {
|
|
return string([]byte{chars[ra.Intn(len(chars))]})
|
|
}
|
|
}
|
|
|
|
r := ra.Intn(int(sum))
|
|
var ru rune
|
|
sum = 0
|
|
for i := 0; i < len(re.Rune); i += 2 {
|
|
gap := int(re.Rune[i+1]-re.Rune[i]) + 1
|
|
if sum+gap > r {
|
|
ru = re.Rune[i] + rune(r-sum)
|
|
break
|
|
}
|
|
sum += gap
|
|
}
|
|
|
|
return string(ru)
|
|
case syntax.OpAnyCharNotNL, syntax.OpAnyChar: // matches any character(and except newline)
|
|
return randCharacter(ra, allStr)
|
|
case syntax.OpBeginLine: // matches empty string at beginning of line
|
|
case syntax.OpEndLine: // matches empty string at end of line
|
|
case syntax.OpBeginText: // matches empty string at beginning of text
|
|
case syntax.OpEndText: // matches empty string at end of text
|
|
case syntax.OpWordBoundary: // matches word boundary `\b`
|
|
case syntax.OpNoWordBoundary: // matches word non-boundary `\B`
|
|
case syntax.OpCapture: // capturing subexpression with index Cap, optional name Name
|
|
return regexGenerate(ra, re.Sub0[0], limit)
|
|
case syntax.OpStar: // matches Sub[0] zero or more times
|
|
var b strings.Builder
|
|
for i := 0; i < number(ra, 0, 10); i++ {
|
|
for _, rs := range re.Sub {
|
|
b.WriteString(regexGenerate(ra, rs, limit-b.Len()))
|
|
}
|
|
}
|
|
return b.String()
|
|
case syntax.OpPlus: // matches Sub[0] one or more times
|
|
var b strings.Builder
|
|
for i := 0; i < number(ra, 1, 10); i++ {
|
|
for _, rs := range re.Sub {
|
|
b.WriteString(regexGenerate(ra, rs, limit-b.Len()))
|
|
}
|
|
}
|
|
return b.String()
|
|
case syntax.OpQuest: // matches Sub[0] zero or one times
|
|
var b strings.Builder
|
|
for i := 0; i < number(ra, 0, 1); i++ {
|
|
for _, rs := range re.Sub {
|
|
b.WriteString(regexGenerate(ra, rs, limit-b.Len()))
|
|
}
|
|
}
|
|
return b.String()
|
|
case syntax.OpRepeat: // matches Sub[0] at least Min times, at most Max (Max == -1 is no limit)
|
|
var b strings.Builder
|
|
count := 0
|
|
re.Max = int(math.Min(float64(re.Max), float64(10)))
|
|
if re.Max > re.Min {
|
|
count = ra.Intn(re.Max - re.Min + 1)
|
|
}
|
|
for i := 0; i < re.Min || i < (re.Min+count); i++ {
|
|
for _, rs := range re.Sub {
|
|
b.WriteString(regexGenerate(ra, rs, limit-b.Len()))
|
|
}
|
|
}
|
|
return b.String()
|
|
case syntax.OpConcat: // matches concatenation of Subs
|
|
var b strings.Builder
|
|
for _, rs := range re.Sub {
|
|
b.WriteString(regexGenerate(ra, rs, limit-b.Len()))
|
|
}
|
|
return b.String()
|
|
case syntax.OpAlternate: // matches alternation of Subs
|
|
return regexGenerate(ra, re.Sub[number(ra, 0, len(re.Sub)-1)], limit)
|
|
}
|
|
|
|
return ""
|
|
}
|
|
|
|
// Map will generate a random set of map data
|
|
func Map() map[string]interface{} { return mapFunc(globalFaker.Rand) }
|
|
|
|
// Map will generate a random set of map data
|
|
func (f *Faker) Map() map[string]interface{} { return mapFunc(f.Rand) }
|
|
|
|
func mapFunc(r *rand.Rand) map[string]interface{} {
|
|
m := map[string]interface{}{}
|
|
|
|
randWordType := func() string {
|
|
s := randomString(r, []string{"lorem", "bs", "job", "name", "address"})
|
|
switch s {
|
|
case "bs":
|
|
return bs(r)
|
|
case "job":
|
|
return jobTitle(r)
|
|
case "name":
|
|
return name(r)
|
|
case "address":
|
|
return street(r) + ", " + city(r) + ", " + state(r) + " " + zip(r)
|
|
}
|
|
return word(r)
|
|
}
|
|
|
|
randSlice := func() []string {
|
|
var sl []string
|
|
for ii := 0; ii < number(r, 3, 10); ii++ {
|
|
sl = append(sl, word(r))
|
|
}
|
|
return sl
|
|
}
|
|
|
|
for i := 0; i < number(r, 3, 10); i++ {
|
|
t := randomString(r, []string{"string", "int", "float", "slice", "map"})
|
|
switch t {
|
|
case "string":
|
|
m[word(r)] = randWordType()
|
|
case "int":
|
|
m[word(r)] = number(r, 1, 10000000)
|
|
case "float":
|
|
m[word(r)] = float32Range(r, 1, 1000000)
|
|
case "slice":
|
|
m[word(r)] = randSlice()
|
|
case "map":
|
|
mm := map[string]interface{}{}
|
|
tt := randomString(r, []string{"string", "int", "float", "slice"})
|
|
switch tt {
|
|
case "string":
|
|
mm[word(r)] = randWordType()
|
|
case "int":
|
|
mm[word(r)] = number(r, 1, 10000000)
|
|
case "float":
|
|
mm[word(r)] = float32Range(r, 1, 1000000)
|
|
case "slice":
|
|
mm[word(r)] = randSlice()
|
|
}
|
|
m[word(r)] = mm
|
|
}
|
|
}
|
|
|
|
return m
|
|
}
|
|
|
|
func addGenerateLookup() {
|
|
AddFuncLookup("generate", Info{
|
|
Display: "Generate",
|
|
Category: "generate",
|
|
Description: "Random string generated from string value based upon available data sets",
|
|
Example: "{firstname} {lastname} {email} - Markus Moen markusmoen@pagac.net",
|
|
Output: "string",
|
|
Params: []Param{
|
|
{Field: "str", Display: "String", Type: "string", Description: "String value to generate from"},
|
|
},
|
|
Generate: func(r *rand.Rand, m *MapParams, info *Info) (interface{}, error) {
|
|
str, err := info.GetString(m, "str")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Limit the length of the string passed
|
|
if len(str) > 1000 {
|
|
return nil, errors.New("string length is too large. Limit to 1000 characters")
|
|
}
|
|
|
|
return generate(r, str), nil
|
|
},
|
|
})
|
|
|
|
AddFuncLookup("regex", Info{
|
|
Display: "Regex",
|
|
Category: "generate",
|
|
Description: "Random string generated from regex RE2 syntax string",
|
|
Example: "[abcdef]{5} - affec",
|
|
Output: "string",
|
|
Params: []Param{
|
|
{Field: "str", Display: "String", Type: "string", Description: "Regex RE2 syntax string"},
|
|
},
|
|
Generate: func(r *rand.Rand, m *MapParams, info *Info) (interface{}, error) {
|
|
str, err := info.GetString(m, "str")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Limit the length of the string passed
|
|
if len(str) > 500 {
|
|
return nil, errors.New("string length is too large. Limit to 500 characters")
|
|
}
|
|
|
|
return regex(r, str), nil
|
|
},
|
|
})
|
|
|
|
AddFuncLookup("map", Info{
|
|
Display: "Map",
|
|
Category: "generate",
|
|
Description: "Random map of generated data",
|
|
Example: `map[consult:respond context:9285735]`,
|
|
Output: "map[string]interface{}",
|
|
ContentType: "application/json",
|
|
Generate: func(r *rand.Rand, m *MapParams, info *Info) (interface{}, error) {
|
|
return mapFunc(r), nil
|
|
},
|
|
})
|
|
}
|