fake.go

  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}