Remove BPA from Makefile
[icn.git] / cmd / bpa-operator / vendor / github.com / markbates / inflect / inflect.go
1 package inflect
2
3 import (
4         "bytes"
5         "encoding/json"
6         "fmt"
7         "io"
8         "io/ioutil"
9         "os"
10         "path/filepath"
11         "regexp"
12         "strconv"
13         "strings"
14         "unicode"
15         "unicode/utf8"
16 )
17
18 // baseAcronyms comes from https://en.wikipedia.org/wiki/List_of_information_technology_acronymss
19 const baseAcronyms = `JSON,JWT,ID,UUID,SQL,ACK,ACL,ADSL,AES,ANSI,API,ARP,ATM,BGP,BSS,CAT,CCITT,CHAP,CIDR,CIR,CLI,CPE,CPU,CRC,CRT,CSMA,CMOS,DCE,DEC,DES,DHCP,DNS,DRAM,DSL,DSLAM,DTE,DMI,EHA,EIA,EIGRP,EOF,ESS,FCC,FCS,FDDI,FTP,GBIC,gbps,GEPOF,HDLC,HTTP,HTTPS,IANA,ICMP,IDF,IDS,IEEE,IETF,IMAP,IP,IPS,ISDN,ISP,kbps,LACP,LAN,LAPB,LAPF,LLC,MAC,MAN,Mbps,MC,MDF,MIB,MoCA,MPLS,MTU,NAC,NAT,NBMA,NIC,NRZ,NRZI,NVRAM,OSI,OSPF,OUI,PAP,PAT,PC,PIM,PIM,PCM,PDU,POP3,POP,POTS,PPP,PPTP,PTT,PVST,RADIUS,RAM,RARP,RFC,RIP,RLL,ROM,RSTP,RTP,RCP,SDLC,SFD,SFP,SLARP,SLIP,SMTP,SNA,SNAP,SNMP,SOF,SRAM,SSH,SSID,STP,SYN,TDM,TFTP,TIA,TOFU,UDP,URL,URI,USB,UTP,VC,VLAN,VLSM,VPN,W3C,WAN,WEP,WiFi,WPA,WWW`
20
21 // Rule used by rulesets
22 type Rule struct {
23         suffix      string
24         replacement string
25         exact       bool
26 }
27
28 // Ruleset a Ruleset is the config of pluralization rules
29 // you can extend the rules with the Add* methods
30 type Ruleset struct {
31         uncountables map[string]bool
32         plurals      []*Rule
33         singulars    []*Rule
34         humans       []*Rule
35         acronyms     []*Rule
36 }
37
38 // NewRuleset creates a blank ruleset. Unless you are going to
39 // build your own rules from scratch you probably
40 // won't need this and can just use the defaultRuleset
41 // via the global inflect.* methods
42 func NewRuleset() *Ruleset {
43         rs := new(Ruleset)
44         rs.uncountables = make(map[string]bool)
45         rs.plurals = make([]*Rule, 0)
46         rs.singulars = make([]*Rule, 0)
47         rs.humans = make([]*Rule, 0)
48         rs.acronyms = make([]*Rule, 0)
49         return rs
50 }
51
52 // NewDefaultRuleset creates a new ruleset and load it with the default
53 // set of common English pluralization rules
54 func NewDefaultRuleset() *Ruleset {
55         rs := NewRuleset()
56         rs.AddPlural("movie", "movies")
57         rs.AddPlural("s", "s")
58         rs.AddPlural("testis", "testes")
59         rs.AddPlural("axis", "axes")
60         rs.AddPlural("octopus", "octopi")
61         rs.AddPlural("virus", "viri")
62         rs.AddPlural("octopi", "octopi")
63         rs.AddPlural("viri", "viri")
64         rs.AddPlural("alias", "aliases")
65         rs.AddPlural("status", "statuses")
66         rs.AddPlural("Status", "Statuses")
67         rs.AddPlural("campus", "campuses")
68         rs.AddPlural("bus", "buses")
69         rs.AddPlural("buffalo", "buffaloes")
70         rs.AddPlural("tomato", "tomatoes")
71         rs.AddPlural("tum", "ta")
72         rs.AddPlural("ium", "ia")
73         rs.AddPlural("ta", "ta")
74         rs.AddPlural("ia", "ia")
75         rs.AddPlural("sis", "ses")
76         rs.AddPlural("lf", "lves")
77         rs.AddPlural("rf", "rves")
78         rs.AddPlural("afe", "aves")
79         rs.AddPlural("bfe", "bves")
80         rs.AddPlural("cfe", "cves")
81         rs.AddPlural("dfe", "dves")
82         rs.AddPlural("efe", "eves")
83         rs.AddPlural("gfe", "gves")
84         rs.AddPlural("hfe", "hves")
85         rs.AddPlural("ife", "ives")
86         rs.AddPlural("jfe", "jves")
87         rs.AddPlural("kfe", "kves")
88         rs.AddPlural("lfe", "lves")
89         rs.AddPlural("mfe", "mves")
90         rs.AddPlural("nfe", "nves")
91         rs.AddPlural("ofe", "oves")
92         rs.AddPlural("pfe", "pves")
93         rs.AddPlural("qfe", "qves")
94         rs.AddPlural("rfe", "rves")
95         rs.AddPlural("sfe", "sves")
96         rs.AddPlural("tfe", "tves")
97         rs.AddPlural("ufe", "uves")
98         rs.AddPlural("vfe", "vves")
99         rs.AddPlural("wfe", "wves")
100         rs.AddPlural("xfe", "xves")
101         rs.AddPlural("yfe", "yves")
102         rs.AddPlural("zfe", "zves")
103         rs.AddPlural("hive", "hives")
104         rs.AddPlural("quy", "quies")
105         rs.AddPlural("by", "bies")
106         rs.AddPlural("cy", "cies")
107         rs.AddPlural("dy", "dies")
108         rs.AddPlural("fy", "fies")
109         rs.AddPlural("gy", "gies")
110         rs.AddPlural("hy", "hies")
111         rs.AddPlural("jy", "jies")
112         rs.AddPlural("ky", "kies")
113         rs.AddPlural("ly", "lies")
114         rs.AddPlural("my", "mies")
115         rs.AddPlural("ny", "nies")
116         rs.AddPlural("py", "pies")
117         rs.AddPlural("qy", "qies")
118         rs.AddPlural("ry", "ries")
119         rs.AddPlural("sy", "sies")
120         rs.AddPlural("ty", "ties")
121         rs.AddPlural("vy", "vies")
122         rs.AddPlural("wy", "wies")
123         rs.AddPlural("xy", "xies")
124         rs.AddPlural("zy", "zies")
125         rs.AddPlural("x", "xes")
126         rs.AddPlural("ch", "ches")
127         rs.AddPlural("ss", "sses")
128         rs.AddPlural("sh", "shes")
129         rs.AddPlural("matrix", "matrices")
130         rs.AddPlural("vertix", "vertices")
131         rs.AddPlural("indix", "indices")
132         rs.AddPlural("matrex", "matrices")
133         rs.AddPlural("vertex", "vertices")
134         rs.AddPlural("index", "indices")
135         rs.AddPlural("mouse", "mice")
136         rs.AddPlural("louse", "lice")
137         rs.AddPlural("mice", "mice")
138         rs.AddPlural("lice", "lice")
139         rs.AddPlural("ress", "resses")
140         rs.AddPluralExact("ox", "oxen", true)
141         rs.AddPluralExact("oxen", "oxen", true)
142         rs.AddPluralExact("quiz", "quizzes", true)
143         rs.AddSingular("s", "")
144         rs.AddSingular("ss", "ss")
145         rs.AddSingular("news", "news")
146         rs.AddSingular("ta", "tum")
147         rs.AddSingular("ia", "ium")
148         rs.AddSingular("analyses", "analysis")
149         rs.AddSingular("bases", "basis")
150         rs.AddSingularExact("basis", "basis", true)
151         rs.AddSingular("diagnoses", "diagnosis")
152         rs.AddSingularExact("diagnosis", "diagnosis", true)
153         rs.AddSingular("parentheses", "parenthesis")
154         rs.AddSingular("prognoses", "prognosis")
155         rs.AddSingular("synopses", "synopsis")
156         rs.AddSingular("theses", "thesis")
157         rs.AddSingular("analyses", "analysis")
158         rs.AddSingularExact("analysis", "analysis", true)
159         rs.AddSingular("ovies", "ovie")
160         rs.AddSingular("aves", "afe")
161         rs.AddSingular("bves", "bfe")
162         rs.AddSingular("cves", "cfe")
163         rs.AddSingular("dves", "dfe")
164         rs.AddSingular("eves", "efe")
165         rs.AddSingular("gves", "gfe")
166         rs.AddSingular("hves", "hfe")
167         rs.AddSingular("ives", "ife")
168         rs.AddSingular("jves", "jfe")
169         rs.AddSingular("kves", "kfe")
170         rs.AddSingular("lves", "lfe")
171         rs.AddSingular("mves", "mfe")
172         rs.AddSingular("nves", "nfe")
173         rs.AddSingular("oves", "ofe")
174         rs.AddSingular("pves", "pfe")
175         rs.AddSingular("qves", "qfe")
176         rs.AddSingular("rves", "rfe")
177         rs.AddSingular("sves", "sfe")
178         rs.AddSingular("tves", "tfe")
179         rs.AddSingular("uves", "ufe")
180         rs.AddSingular("vves", "vfe")
181         rs.AddSingular("wves", "wfe")
182         rs.AddSingular("xves", "xfe")
183         rs.AddSingular("yves", "yfe")
184         rs.AddSingular("zves", "zfe")
185         rs.AddSingular("hives", "hive")
186         rs.AddSingular("tives", "tive")
187         rs.AddSingular("lves", "lf")
188         rs.AddSingular("rves", "rf")
189         rs.AddSingular("quies", "quy")
190         rs.AddSingular("bies", "by")
191         rs.AddSingular("cies", "cy")
192         rs.AddSingular("dies", "dy")
193         rs.AddSingular("fies", "fy")
194         rs.AddSingular("gies", "gy")
195         rs.AddSingular("hies", "hy")
196         rs.AddSingular("jies", "jy")
197         rs.AddSingular("kies", "ky")
198         rs.AddSingular("lies", "ly")
199         rs.AddSingular("mies", "my")
200         rs.AddSingular("nies", "ny")
201         rs.AddSingular("pies", "py")
202         rs.AddSingular("qies", "qy")
203         rs.AddSingular("ries", "ry")
204         rs.AddSingular("sies", "sy")
205         rs.AddSingular("ties", "ty")
206         // rs.AddSingular("vies", "vy")
207         rs.AddSingular("wies", "wy")
208         rs.AddSingular("xies", "xy")
209         rs.AddSingular("zies", "zy")
210         rs.AddSingular("series", "series")
211         rs.AddSingular("xes", "x")
212         rs.AddSingular("ches", "ch")
213         rs.AddSingular("sses", "ss")
214         rs.AddSingular("shes", "sh")
215         rs.AddSingular("mice", "mouse")
216         rs.AddSingular("lice", "louse")
217         rs.AddSingular("buses", "bus")
218         rs.AddSingularExact("bus", "bus", true)
219         rs.AddSingular("oes", "o")
220         rs.AddSingular("shoes", "shoe")
221         rs.AddSingular("crises", "crisis")
222         rs.AddSingularExact("crisis", "crisis", true)
223         rs.AddSingular("axes", "axis")
224         rs.AddSingularExact("axis", "axis", true)
225         rs.AddSingular("testes", "testis")
226         rs.AddSingularExact("testis", "testis", true)
227         rs.AddSingular("octopi", "octopus")
228         rs.AddSingularExact("octopus", "octopus", true)
229         rs.AddSingular("viri", "virus")
230         rs.AddSingularExact("virus", "virus", true)
231         rs.AddSingular("statuses", "status")
232         rs.AddSingular("Statuses", "Status")
233         rs.AddSingular("campuses", "campus")
234         rs.AddSingularExact("status", "status", true)
235         rs.AddSingularExact("Status", "Status", true)
236         rs.AddSingularExact("campus", "campus", true)
237         rs.AddSingular("aliases", "alias")
238         rs.AddSingularExact("alias", "alias", true)
239         rs.AddSingularExact("oxen", "ox", true)
240         rs.AddSingular("vertices", "vertex")
241         rs.AddSingular("indices", "index")
242         rs.AddSingular("matrices", "matrix")
243         rs.AddSingularExact("quizzes", "quiz", true)
244         rs.AddSingular("databases", "database")
245         rs.AddSingular("resses", "ress")
246         rs.AddSingular("ress", "ress")
247         rs.AddIrregular("person", "people")
248         rs.AddIrregular("man", "men")
249         rs.AddIrregular("child", "children")
250         rs.AddIrregular("sex", "sexes")
251         rs.AddIrregular("move", "moves")
252         rs.AddIrregular("zombie", "zombies")
253         rs.AddIrregular("Status", "Statuses")
254         rs.AddIrregular("status", "statuses")
255         rs.AddIrregular("campus", "campuses")
256         rs.AddIrregular("human", "humans")
257         rs.AddUncountable("equipment")
258         rs.AddUncountable("information")
259         rs.AddUncountable("rice")
260         rs.AddUncountable("money")
261         rs.AddUncountable("species")
262         rs.AddUncountable("series")
263         rs.AddUncountable("fish")
264         rs.AddUncountable("sheep")
265         rs.AddUncountable("jeans")
266         rs.AddUncountable("police")
267
268         acronyms := strings.Split(baseAcronyms, ",")
269         for _, acr := range acronyms {
270                 rs.AddAcronym(acr)
271         }
272
273         return rs
274 }
275
276 // Uncountables returns a map of uncountables in the ruleset
277 func (rs *Ruleset) Uncountables() map[string]bool {
278         return rs.uncountables
279 }
280
281 // AddPlural add a pluralization rule
282 func (rs *Ruleset) AddPlural(suffix, replacement string) {
283         rs.AddPluralExact(suffix, replacement, false)
284 }
285
286 // AddPluralExact add a pluralization rule with full string match
287 func (rs *Ruleset) AddPluralExact(suffix, replacement string, exact bool) {
288         // remove uncountable
289         delete(rs.uncountables, suffix)
290         // create rule
291         r := new(Rule)
292         r.suffix = suffix
293         r.replacement = replacement
294         r.exact = exact
295         // prepend
296         rs.plurals = append([]*Rule{r}, rs.plurals...)
297 }
298
299 // AddSingular add a singular rule
300 func (rs *Ruleset) AddSingular(suffix, replacement string) {
301         rs.AddSingularExact(suffix, replacement, false)
302 }
303
304 // AddSingularExact same as AddSingular but you can set `exact` to force
305 // a full string match
306 func (rs *Ruleset) AddSingularExact(suffix, replacement string, exact bool) {
307         // remove from uncountable
308         delete(rs.uncountables, suffix)
309         // create rule
310         r := new(Rule)
311         r.suffix = suffix
312         r.replacement = replacement
313         r.exact = exact
314         rs.singulars = append([]*Rule{r}, rs.singulars...)
315 }
316
317 // AddHuman Human rules are applied by humanize to show more friendly
318 // versions of words
319 func (rs *Ruleset) AddHuman(suffix, replacement string) {
320         r := new(Rule)
321         r.suffix = suffix
322         r.replacement = replacement
323         rs.humans = append([]*Rule{r}, rs.humans...)
324 }
325
326 // AddIrregular Add any inconsistent pluralizing/singularizing rules
327 // to the set here.
328 func (rs *Ruleset) AddIrregular(singular, plural string) {
329         delete(rs.uncountables, singular)
330         delete(rs.uncountables, plural)
331         rs.AddPlural(singular, plural)
332         rs.AddPlural(plural, plural)
333         rs.AddSingular(plural, singular)
334 }
335
336 // AddAcronym if you use acronym you may need to add them to the ruleset
337 // to prevent Underscored words of things like "HTML" coming out
338 // as "h_t_m_l"
339 func (rs *Ruleset) AddAcronym(word string) {
340         r := new(Rule)
341         r.suffix = word
342         r.replacement = rs.Titleize(strings.ToLower(word))
343         rs.acronyms = append(rs.acronyms, r)
344 }
345
346 // AddUncountable add a word to this ruleset that has the same singular and plural form
347 // for example: "rice"
348 func (rs *Ruleset) AddUncountable(word string) {
349         rs.uncountables[strings.ToLower(word)] = true
350 }
351
352 func (rs *Ruleset) isUncountable(word string) bool {
353         // handle multiple words by using the last one
354         words := strings.Split(word, " ")
355         if _, exists := rs.uncountables[strings.ToLower(words[len(words)-1])]; exists {
356                 return true
357         }
358         return false
359 }
360
361 //isAcronym returns if a word is acronym or not.
362 func (rs *Ruleset) isAcronym(word string) bool {
363         for _, rule := range rs.acronyms {
364                 if strings.ToUpper(rule.suffix) == strings.ToUpper(word) {
365                         return true
366                 }
367         }
368
369         return false
370 }
371
372 //PluralizeWithSize pluralize with taking number into account
373 func (rs *Ruleset) PluralizeWithSize(word string, size int) string {
374         if size == 1 {
375                 return rs.Singularize(word)
376         }
377         return rs.Pluralize(word)
378 }
379
380 // Pluralize returns the plural form of a singular word
381 func (rs *Ruleset) Pluralize(word string) string {
382         if len(word) == 0 {
383                 return word
384         }
385         lWord := strings.ToLower(word)
386         if rs.isUncountable(lWord) {
387                 return word
388         }
389
390         var candidate string
391         for _, rule := range rs.plurals {
392                 if rule.exact {
393                         if lWord == rule.suffix {
394                                 // Capitalized word
395                                 if lWord[0] != word[0] && lWord[1:] == word[1:] {
396                                         return rs.Capitalize(rule.replacement)
397                                 }
398                                 return rule.replacement
399                         }
400                         continue
401                 }
402
403                 if strings.EqualFold(word, rule.suffix) {
404                         candidate = rule.replacement
405                 }
406
407                 if strings.HasSuffix(word, rule.suffix) {
408                         return replaceLast(word, rule.suffix, rule.replacement)
409                 }
410         }
411
412         if candidate != "" {
413                 return candidate
414         }
415         return word + "s"
416 }
417
418 //Singularize returns the singular form of a plural word
419 func (rs *Ruleset) Singularize(word string) string {
420         if len(word) <= 1 {
421                 return word
422         }
423         lWord := strings.ToLower(word)
424         if rs.isUncountable(lWord) {
425                 return word
426         }
427
428         var candidate string
429
430         for _, rule := range rs.singulars {
431                 if rule.exact {
432                         if lWord == rule.suffix {
433                                 // Capitalized word
434                                 if lWord[0] != word[0] && lWord[1:] == word[1:] {
435                                         return rs.Capitalize(rule.replacement)
436                                 }
437                                 return rule.replacement
438                         }
439                         continue
440                 }
441
442                 if strings.EqualFold(word, rule.suffix) {
443                         candidate = rule.replacement
444                 }
445
446                 if strings.HasSuffix(word, rule.suffix) {
447                         return replaceLast(word, rule.suffix, rule.replacement)
448                 }
449         }
450
451         if candidate != "" {
452                 return candidate
453         }
454
455         return word
456 }
457
458 //Capitalize uppercase first character
459 func (rs *Ruleset) Capitalize(word string) string {
460         if rs.isAcronym(word) {
461                 return strings.ToUpper(word)
462         }
463         return strings.ToUpper(word[:1]) + word[1:]
464 }
465
466 //Camelize "dino_party" -> "DinoParty"
467 func (rs *Ruleset) Camelize(word string) string {
468         if rs.isAcronym(word) {
469                 return strings.ToUpper(word)
470         }
471         words := splitAtCaseChangeWithTitlecase(word)
472         return strings.Join(words, "")
473 }
474
475 //CamelizeDownFirst same as Camelcase but with first letter downcased
476 func (rs *Ruleset) CamelizeDownFirst(word string) string {
477         word = Camelize(word)
478         return strings.ToLower(word[:1]) + word[1:]
479 }
480
481 //Titleize Capitalize every word in sentence "hello there" -> "Hello There"
482 func (rs *Ruleset) Titleize(word string) string {
483         words := splitAtCaseChangeWithTitlecase(word)
484         result := strings.Join(words, " ")
485
486         var acronymWords []string
487         for index, word := range words {
488                 if len(word) == 1 {
489                         acronymWords = append(acronymWords, word)
490                 }
491
492                 if len(word) > 1 || index == len(words)-1 || len(acronymWords) > 1 {
493                         acronym := strings.Join(acronymWords, "")
494                         if !rs.isAcronym(acronym) {
495                                 acronymWords = acronymWords[:len(acronymWords)]
496                                 continue
497                         }
498
499                         result = strings.Replace(result, strings.Join(acronymWords, " "), acronym, 1)
500                         acronymWords = []string{}
501                 }
502         }
503
504         return result
505 }
506
507 func (rs *Ruleset) safeCaseAcronyms(word string) string {
508         // convert an acronym like HTML into Html
509         for _, rule := range rs.acronyms {
510                 word = strings.Replace(word, rule.suffix, rule.replacement, -1)
511         }
512         return word
513 }
514
515 func (rs *Ruleset) separatedWords(word, sep string) string {
516         word = rs.safeCaseAcronyms(word)
517         words := splitAtCaseChange(word)
518         return strings.Join(words, sep)
519 }
520
521 //Underscore lowercase underscore version "BigBen" -> "big_ben"
522 func (rs *Ruleset) Underscore(word string) string {
523         return rs.separatedWords(word, "_")
524 }
525
526 //Humanize First letter of sentence capitalized
527 // Uses custom friendly replacements via AddHuman()
528 func (rs *Ruleset) Humanize(word string) string {
529         word = replaceLast(word, "_id", "") // strip foreign key kinds
530         // replace and strings in humans list
531         for _, rule := range rs.humans {
532                 word = strings.Replace(word, rule.suffix, rule.replacement, -1)
533         }
534         sentence := rs.separatedWords(word, " ")
535
536         r, n := utf8.DecodeRuneInString(sentence)
537         return string(unicode.ToUpper(r)) + sentence[n:]
538 }
539
540 //ForeignKey an underscored foreign key name "Person" -> "person_id"
541 func (rs *Ruleset) ForeignKey(word string) string {
542         return rs.Underscore(rs.Singularize(word)) + "_id"
543 }
544
545 //ForeignKeyCondensed a foreign key (with an underscore) "Person" -> "personid"
546 func (rs *Ruleset) ForeignKeyCondensed(word string) string {
547         return rs.Underscore(word) + "id"
548 }
549
550 //Tableize Rails style pluralized table names: "SuperPerson" -> "super_people"
551 func (rs *Ruleset) Tableize(word string) string {
552         return rs.Pluralize(rs.Underscore(rs.Typeify(word)))
553 }
554
555 var notUrlSafe *regexp.Regexp = regexp.MustCompile(`[^\w\d\-_ ]`)
556
557 //Parameterize param safe dasherized names like "my-param"
558 func (rs *Ruleset) Parameterize(word string) string {
559         return ParameterizeJoin(word, "-")
560 }
561
562 //ParameterizeJoin param safe dasherized names with custom separator
563 func (rs *Ruleset) ParameterizeJoin(word, sep string) string {
564         word = strings.ToLower(word)
565         word = rs.Asciify(word)
566         word = notUrlSafe.ReplaceAllString(word, "")
567         word = strings.Replace(word, " ", sep, -1)
568         if len(sep) > 0 {
569                 squash, err := regexp.Compile(sep + "+")
570                 if err == nil {
571                         word = squash.ReplaceAllString(word, sep)
572                 }
573         }
574         word = strings.Trim(word, sep+" ")
575         return word
576 }
577
578 var lookalikes = map[string]*regexp.Regexp{
579         "A":  regexp.MustCompile(`À|Á|Â|Ã|Ä|Å`),
580         "AE": regexp.MustCompile(`Æ`),
581         "C":  regexp.MustCompile(`Ç`),
582         "E":  regexp.MustCompile(`È|É|Ê|Ë`),
583         "G":  regexp.MustCompile(`Ğ`),
584         "I":  regexp.MustCompile(`Ì|Í|Î|Ï|İ`),
585         "N":  regexp.MustCompile(`Ñ`),
586         "O":  regexp.MustCompile(`Ò|Ó|Ô|Õ|Ö|Ø`),
587         "S":  regexp.MustCompile(`Ş`),
588         "U":  regexp.MustCompile(`Ù|Ú|Û|Ü`),
589         "Y":  regexp.MustCompile(`Ý`),
590         "ss": regexp.MustCompile(`ß`),
591         "a":  regexp.MustCompile(`à|á|â|ã|ä|å`),
592         "ae": regexp.MustCompile(`æ`),
593         "c":  regexp.MustCompile(`ç`),
594         "e":  regexp.MustCompile(`è|é|ê|ë`),
595         "g":  regexp.MustCompile(`ğ`),
596         "i":  regexp.MustCompile(`ì|í|î|ï|ı`),
597         "n":  regexp.MustCompile(`ñ`),
598         "o":  regexp.MustCompile(`ò|ó|ô|õ|ö|ø`),
599         "s":  regexp.MustCompile(`ş`),
600         "u":  regexp.MustCompile(`ù|ú|û|ü|ũ|ū|ŭ|ů|ű|ų`),
601         "y":  regexp.MustCompile(`ý|ÿ`),
602 }
603
604 //Asciify transforms Latin characters like é -> e
605 func (rs *Ruleset) Asciify(word string) string {
606         for repl, regex := range lookalikes {
607                 word = regex.ReplaceAllString(word, repl)
608         }
609         return word
610 }
611
612 var tablePrefix = regexp.MustCompile(`^[^.]*\.`)
613
614 //Typeify "something_like_this" -> "SomethingLikeThis"
615 func (rs *Ruleset) Typeify(word string) string {
616         word = tablePrefix.ReplaceAllString(word, "")
617         return rs.Camelize(rs.Singularize(word))
618 }
619
620 //Dasherize "SomeText" -> "some-text"
621 func (rs *Ruleset) Dasherize(word string) string {
622         return rs.separatedWords(word, "-")
623 }
624
625 //Ordinalize "1031" -> "1031st"
626 func (rs *Ruleset) Ordinalize(str string) string {
627         number, err := strconv.Atoi(str)
628         if err != nil {
629                 return str
630         }
631         switch abs(number) % 100 {
632         case 11, 12, 13:
633                 return fmt.Sprintf("%dth", number)
634         default:
635                 switch abs(number) % 10 {
636                 case 1:
637                         return fmt.Sprintf("%dst", number)
638                 case 2:
639                         return fmt.Sprintf("%dnd", number)
640                 case 3:
641                         return fmt.Sprintf("%drd", number)
642                 }
643         }
644         return fmt.Sprintf("%dth", number)
645 }
646
647 //ForeignKeyToAttribute returns the attribute name from the foreign key
648 func (rs *Ruleset) ForeignKeyToAttribute(str string) string {
649         w := rs.Camelize(str)
650         if strings.HasSuffix(w, "Id") {
651                 return strings.TrimSuffix(w, "Id") + "ID"
652         }
653         return w
654 }
655
656 //LoadReader loads rules from io.Reader param
657 func (rs *Ruleset) LoadReader(r io.Reader) error {
658         m := map[string]string{}
659         err := json.NewDecoder(r).Decode(&m)
660         if err != nil {
661                 return fmt.Errorf("could not decode inflection JSON from reader: %s", err)
662         }
663         for s, p := range m {
664                 defaultRuleset.AddIrregular(s, p)
665         }
666         return nil
667 }
668
669 /////////////////////////////////////////
670 // the default global ruleset
671 //////////////////////////////////////////
672
673 var defaultRuleset *Ruleset
674
675 //LoadReader loads rules from io.Reader param
676 func LoadReader(r io.Reader) error {
677         return defaultRuleset.LoadReader(r)
678 }
679
680 func init() {
681         defaultRuleset = NewDefaultRuleset()
682
683         pwd, _ := os.Getwd()
684         cfg := filepath.Join(pwd, "inflections.json")
685         if p := os.Getenv("INFLECT_PATH"); p != "" {
686                 cfg = p
687         }
688         if _, err := os.Stat(cfg); err == nil {
689                 b, err := ioutil.ReadFile(cfg)
690                 if err != nil {
691                         fmt.Printf("could not read inflection file %s (%s)\n", cfg, err)
692                         return
693                 }
694                 if err = defaultRuleset.LoadReader(bytes.NewReader(b)); err != nil {
695                         fmt.Println(err)
696                 }
697         }
698 }
699
700 //Uncountables returns a list of uncountables rules
701 func Uncountables() map[string]bool {
702         return defaultRuleset.Uncountables()
703 }
704
705 //AddPlural adds plural to the ruleset
706 func AddPlural(suffix, replacement string) {
707         defaultRuleset.AddPlural(suffix, replacement)
708 }
709
710 //AddSingular adds singular to the ruleset
711 func AddSingular(suffix, replacement string) {
712         defaultRuleset.AddSingular(suffix, replacement)
713 }
714
715 //AddHuman adds human
716 func AddHuman(suffix, replacement string) {
717         defaultRuleset.AddHuman(suffix, replacement)
718 }
719
720 func AddIrregular(singular, plural string) {
721         defaultRuleset.AddIrregular(singular, plural)
722 }
723
724 func AddAcronym(word string) {
725         defaultRuleset.AddAcronym(word)
726 }
727
728 func AddUncountable(word string) {
729         defaultRuleset.AddUncountable(word)
730 }
731
732 func Pluralize(word string) string {
733         return defaultRuleset.Pluralize(word)
734 }
735
736 func PluralizeWithSize(word string, size int) string {
737         return defaultRuleset.PluralizeWithSize(word, size)
738 }
739
740 func Singularize(word string) string {
741         return defaultRuleset.Singularize(word)
742 }
743
744 func Capitalize(word string) string {
745         return defaultRuleset.Capitalize(word)
746 }
747
748 func Camelize(word string) string {
749         return defaultRuleset.Camelize(word)
750 }
751
752 func CamelizeDownFirst(word string) string {
753         return defaultRuleset.CamelizeDownFirst(word)
754 }
755
756 func Titleize(word string) string {
757         return defaultRuleset.Titleize(word)
758 }
759
760 func Underscore(word string) string {
761         return defaultRuleset.Underscore(word)
762 }
763
764 func Humanize(word string) string {
765         return defaultRuleset.Humanize(word)
766 }
767
768 func ForeignKey(word string) string {
769         return defaultRuleset.ForeignKey(word)
770 }
771
772 func ForeignKeyCondensed(word string) string {
773         return defaultRuleset.ForeignKeyCondensed(word)
774 }
775
776 func Tableize(word string) string {
777         return defaultRuleset.Tableize(word)
778 }
779
780 func Parameterize(word string) string {
781         return defaultRuleset.Parameterize(word)
782 }
783
784 func ParameterizeJoin(word, sep string) string {
785         return defaultRuleset.ParameterizeJoin(word, sep)
786 }
787
788 func Typeify(word string) string {
789         return defaultRuleset.Typeify(word)
790 }
791
792 func Dasherize(word string) string {
793         return defaultRuleset.Dasherize(word)
794 }
795
796 func Ordinalize(word string) string {
797         return defaultRuleset.Ordinalize(word)
798 }
799
800 func Asciify(word string) string {
801         return defaultRuleset.Asciify(word)
802 }
803
804 func ForeignKeyToAttribute(word string) string {
805         return defaultRuleset.ForeignKeyToAttribute(word)
806 }
807
808 // helper funcs
809
810 func reverse(s string) string {
811         o := make([]rune, utf8.RuneCountInString(s))
812         i := len(o)
813         for _, c := range s {
814                 i--
815                 o[i] = c
816         }
817         return string(o)
818 }
819
820 func isSpacerChar(c rune) bool {
821         switch {
822         case c == rune("_"[0]):
823                 return true
824         case c == rune(" "[0]):
825                 return true
826         case c == rune(":"[0]):
827                 return true
828         case c == rune("-"[0]):
829                 return true
830         }
831         return false
832 }
833
834 func splitAtCaseChange(s string) []string {
835         words := make([]string, 0)
836         word := make([]rune, 0)
837         for _, c := range s {
838                 spacer := isSpacerChar(c)
839                 if len(word) > 0 {
840                         if unicode.IsUpper(c) || spacer {
841                                 words = append(words, string(word))
842                                 word = make([]rune, 0)
843                         }
844                 }
845                 if !spacer {
846                         word = append(word, unicode.ToLower(c))
847                 }
848         }
849         words = append(words, string(word))
850         return words
851 }
852
853 func splitAtCaseChangeWithTitlecase(s string) []string {
854         words := make([]string, 0)
855         word := make([]rune, 0)
856
857         for _, c := range s {
858                 spacer := isSpacerChar(c)
859                 if len(word) > 0 {
860                         if unicode.IsUpper(c) || spacer {
861                                 words = append(words, string(word))
862                                 word = make([]rune, 0)
863                         }
864                 }
865                 if !spacer {
866                         if len(word) > 0 {
867                                 word = append(word, unicode.ToLower(c))
868                         } else {
869                                 word = append(word, unicode.ToUpper(c))
870                         }
871                 }
872         }
873
874         words = append(words, string(word))
875         return words
876 }
877
878 func replaceLast(s, match, repl string) string {
879         // reverse strings
880         srev := reverse(s)
881         mrev := reverse(match)
882         rrev := reverse(repl)
883         // match first and reverse back
884         return reverse(strings.Replace(srev, mrev, rrev, 1))
885 }
886
887 func abs(x int) int {
888         if x < 0 {
889                 return -x
890         }
891         return x
892 }