1/*
2Package fake is the fake data generatror for go (Golang), heavily inspired by forgery and ffaker Ruby gems
3
4Most data and methods are ported from forgery/ffaker Ruby gems.
5
6Currently english and russian languages are available.
7
8For the list of available methods please look at https://godoc.org/github.com/icrowley/fake.
9
10Fake embeds samples data files unless you call UseExternalData(true) in order to be able to work without external files dependencies when compiled, so, if you add new data files or make changes to existing ones don't forget to regenerate data.go file using github.com/mjibson/esc tool and esc -o data.go -pkg fake data command (or you can just use go generate command if you are using Go 1.4 or later).
11
12Examples:
13 name := fake.FirstName()
14 fullname = := fake.FullName()
15 product := fake.Product()
16
17Changing language:
18 err := fake.SetLang("ru")
19 if err != nil {
20 panic(err)
21 }
22 password := fake.SimplePassword()
23
24Using english fallback:
25 err := fake.SetLang("ru")
26 if err != nil {
27 panic(err)
28 }
29 fake.EnFallback(true)
30 password := fake.Paragraph()
31
32Using external data:
33 fake.UseExternalData(true)
34 password := fake.Paragraph()
35*/
36package fake
37
38import (
39 "fmt"
40 "io/ioutil"
41 "math/rand"
42 "strconv"
43 "strings"
44 "sync"
45 "time"
46)
47
48//go:generate go get github.com/mjibson/esc
49//go:generate esc -o data.go -pkg fake data
50
51// cat/subcat/lang/samples
52type samplesTree map[string]map[string][]string
53
54var samplesLock sync.Mutex
55var samplesCache = make(samplesTree)
56var r = rand.New(&rndSrc{src: rand.NewSource(time.Now().UnixNano())})
57var lang = "en"
58var useExternalData = false
59var enFallback = true
60var availLangs = GetLangs()
61
62var (
63 // ErrNoLanguageFn is the error that indicates that given language is not available
64 ErrNoLanguageFn = func(lang string) error { return fmt.Errorf("The language passed (%s) is not available", lang) }
65 // ErrNoSamplesFn is the error that indicates that there are no samples for the given language
66 ErrNoSamplesFn = func(lang string) error { return fmt.Errorf("No samples found for language: %s", lang) }
67)
68
69// Seed uses the provided seed value to initialize the internal PRNG to a
70// deterministic state.
71func Seed(seed int64) {
72 r.Seed(seed)
73}
74
75type rndSrc struct {
76 mtx sync.Mutex
77 src rand.Source
78}
79
80func (s *rndSrc) Int63() int64 {
81 s.mtx.Lock()
82 n := s.src.Int63()
83 s.mtx.Unlock()
84 return n
85}
86
87func (s *rndSrc) Seed(n int64) {
88 s.mtx.Lock()
89 s.src.Seed(n)
90 s.mtx.Unlock()
91}
92
93// GetLangs returns a slice of available languages
94func GetLangs() []string {
95 var langs []string
96 for k, v := range data {
97 if v.isDir && k != "/" && k != "/data" {
98 langs = append(langs, strings.Replace(k, "/data/", "", 1))
99 }
100 }
101 return langs
102}
103
104// SetLang sets the language in which the data should be generated
105// returns error if passed language is not available
106func SetLang(newLang string) error {
107 found := false
108 for _, l := range availLangs {
109 if newLang == l {
110 found = true
111 break
112 }
113 }
114 if !found {
115 return ErrNoLanguageFn(newLang)
116 }
117 lang = newLang
118 return nil
119}
120
121// UseExternalData sets the flag that allows using of external files as data providers (fake uses embedded ones by default)
122func UseExternalData(flag bool) {
123 useExternalData = flag
124}
125
126// EnFallback sets the flag that allows fake to fallback to englsh samples if the ones for the used languaged are not available
127func EnFallback(flag bool) {
128 enFallback = flag
129}
130
131func (st samplesTree) hasKeyPath(lang, cat string) bool {
132 if _, ok := st[lang]; ok {
133 if _, ok = st[lang][cat]; ok {
134 return true
135 }
136 }
137 return false
138}
139
140func join(parts ...string) string {
141 var filtered []string
142 for _, part := range parts {
143 if part != "" {
144 filtered = append(filtered, part)
145 }
146 }
147 return strings.Join(filtered, " ")
148}
149
150func generate(lang, cat string, fallback bool) string {
151 format := lookup(lang, cat+"_format", fallback)
152 var result string
153 for _, ru := range format {
154 if ru != '#' {
155 result += string(ru)
156 } else {
157 result += strconv.Itoa(r.Intn(10))
158 }
159 }
160 return result
161}
162
163func lookup(lang, cat string, fallback bool) string {
164 samplesLock.Lock()
165 s := _lookup(lang, cat, fallback)
166 samplesLock.Unlock()
167 return s
168}
169
170func _lookup(lang, cat string, fallback bool) string {
171 var samples []string
172
173 if samplesCache.hasKeyPath(lang, cat) {
174 samples = samplesCache[lang][cat]
175 } else {
176 var err error
177 samples, err = populateSamples(lang, cat)
178 if err != nil {
179 if lang != "en" && fallback && enFallback && err.Error() == ErrNoSamplesFn(lang).Error() {
180 return _lookup("en", cat, false)
181 }
182 return ""
183 }
184 }
185
186 return samples[r.Intn(len(samples))]
187}
188
189func populateSamples(lang, cat string) ([]string, error) {
190 data, err := readFile(lang, cat)
191 if err != nil {
192 return nil, err
193 }
194
195 if _, ok := samplesCache[lang]; !ok {
196 samplesCache[lang] = make(map[string][]string)
197 }
198
199 samples := strings.Split(strings.TrimSpace(string(data)), "\n")
200
201 samplesCache[lang][cat] = samples
202 return samples, nil
203}
204
205func readFile(lang, cat string) ([]byte, error) {
206 fullpath := fmt.Sprintf("/data/%s/%s", lang, cat)
207 file, err := FS(useExternalData).Open(fullpath)
208 if err != nil {
209 return nil, ErrNoSamplesFn(lang)
210 }
211 defer file.Close()
212
213 return ioutil.ReadAll(file)
214}