podman
713 строк · 18.3 Кб
1package inflect
2
3import (
4"fmt"
5"regexp"
6"strconv"
7"strings"
8"unicode"
9"unicode/utf8"
10)
11
12// used by rulesets
13type Rule struct {
14suffix string
15replacement string
16exact bool
17}
18
19// a Ruleset is the config of pluralization rules
20// you can extend the rules with the Add* methods
21type Ruleset struct {
22uncountables map[string]bool
23plurals []*Rule
24singulars []*Rule
25humans []*Rule
26acronyms []*Rule
27acronymMatcher *regexp.Regexp
28}
29
30// create a blank ruleset. Unless you are going to
31// build your own rules from scratch you probably
32// won't need this and can just use the defaultRuleset
33// via the global inflect.* methods
34func NewRuleset() *Ruleset {
35rs := new(Ruleset)
36rs.uncountables = make(map[string]bool)
37rs.plurals = make([]*Rule, 0)
38rs.singulars = make([]*Rule, 0)
39rs.humans = make([]*Rule, 0)
40rs.acronyms = make([]*Rule, 0)
41return rs
42}
43
44// create a new ruleset and load it with the default
45// set of common English pluralization rules
46func NewDefaultRuleset() *Ruleset {
47rs := NewRuleset()
48rs.AddPlural("s", "s")
49rs.AddPlural("testis", "testes")
50rs.AddPlural("axis", "axes")
51rs.AddPlural("octopus", "octopi")
52rs.AddPlural("virus", "viri")
53rs.AddPlural("octopi", "octopi")
54rs.AddPlural("viri", "viri")
55rs.AddPlural("alias", "aliases")
56rs.AddPlural("status", "statuses")
57rs.AddPlural("bus", "buses")
58rs.AddPlural("buffalo", "buffaloes")
59rs.AddPlural("tomato", "tomatoes")
60rs.AddPlural("tum", "ta")
61rs.AddPlural("ium", "ia")
62rs.AddPlural("ta", "ta")
63rs.AddPlural("ia", "ia")
64rs.AddPlural("sis", "ses")
65rs.AddPlural("lf", "lves")
66rs.AddPlural("rf", "rves")
67rs.AddPlural("afe", "aves")
68rs.AddPlural("bfe", "bves")
69rs.AddPlural("cfe", "cves")
70rs.AddPlural("dfe", "dves")
71rs.AddPlural("efe", "eves")
72rs.AddPlural("gfe", "gves")
73rs.AddPlural("hfe", "hves")
74rs.AddPlural("ife", "ives")
75rs.AddPlural("jfe", "jves")
76rs.AddPlural("kfe", "kves")
77rs.AddPlural("lfe", "lves")
78rs.AddPlural("mfe", "mves")
79rs.AddPlural("nfe", "nves")
80rs.AddPlural("ofe", "oves")
81rs.AddPlural("pfe", "pves")
82rs.AddPlural("qfe", "qves")
83rs.AddPlural("rfe", "rves")
84rs.AddPlural("sfe", "sves")
85rs.AddPlural("tfe", "tves")
86rs.AddPlural("ufe", "uves")
87rs.AddPlural("vfe", "vves")
88rs.AddPlural("wfe", "wves")
89rs.AddPlural("xfe", "xves")
90rs.AddPlural("yfe", "yves")
91rs.AddPlural("zfe", "zves")
92rs.AddPlural("hive", "hives")
93rs.AddPlural("quy", "quies")
94rs.AddPlural("by", "bies")
95rs.AddPlural("cy", "cies")
96rs.AddPlural("dy", "dies")
97rs.AddPlural("fy", "fies")
98rs.AddPlural("gy", "gies")
99rs.AddPlural("hy", "hies")
100rs.AddPlural("jy", "jies")
101rs.AddPlural("ky", "kies")
102rs.AddPlural("ly", "lies")
103rs.AddPlural("my", "mies")
104rs.AddPlural("ny", "nies")
105rs.AddPlural("py", "pies")
106rs.AddPlural("qy", "qies")
107rs.AddPlural("ry", "ries")
108rs.AddPlural("sy", "sies")
109rs.AddPlural("ty", "ties")
110rs.AddPlural("vy", "vies")
111rs.AddPlural("wy", "wies")
112rs.AddPlural("xy", "xies")
113rs.AddPlural("zy", "zies")
114rs.AddPlural("x", "xes")
115rs.AddPlural("ch", "ches")
116rs.AddPlural("ss", "sses")
117rs.AddPlural("sh", "shes")
118rs.AddPlural("matrix", "matrices")
119rs.AddPlural("vertix", "vertices")
120rs.AddPlural("indix", "indices")
121rs.AddPlural("matrex", "matrices")
122rs.AddPlural("vertex", "vertices")
123rs.AddPlural("index", "indices")
124rs.AddPlural("mouse", "mice")
125rs.AddPlural("louse", "lice")
126rs.AddPlural("mice", "mice")
127rs.AddPlural("lice", "lice")
128rs.AddPluralExact("ox", "oxen", true)
129rs.AddPluralExact("oxen", "oxen", true)
130rs.AddPluralExact("quiz", "quizzes", true)
131rs.AddSingular("s", "")
132rs.AddSingular("news", "news")
133rs.AddSingular("ta", "tum")
134rs.AddSingular("ia", "ium")
135rs.AddSingular("analyses", "analysis")
136rs.AddSingular("bases", "basis")
137rs.AddSingular("diagnoses", "diagnosis")
138rs.AddSingular("parentheses", "parenthesis")
139rs.AddSingular("prognoses", "prognosis")
140rs.AddSingular("synopses", "synopsis")
141rs.AddSingular("theses", "thesis")
142rs.AddSingular("analyses", "analysis")
143rs.AddSingular("aves", "afe")
144rs.AddSingular("bves", "bfe")
145rs.AddSingular("cves", "cfe")
146rs.AddSingular("dves", "dfe")
147rs.AddSingular("eves", "efe")
148rs.AddSingular("gves", "gfe")
149rs.AddSingular("hves", "hfe")
150rs.AddSingular("ives", "ife")
151rs.AddSingular("jves", "jfe")
152rs.AddSingular("kves", "kfe")
153rs.AddSingular("lves", "lfe")
154rs.AddSingular("mves", "mfe")
155rs.AddSingular("nves", "nfe")
156rs.AddSingular("oves", "ofe")
157rs.AddSingular("pves", "pfe")
158rs.AddSingular("qves", "qfe")
159rs.AddSingular("rves", "rfe")
160rs.AddSingular("sves", "sfe")
161rs.AddSingular("tves", "tfe")
162rs.AddSingular("uves", "ufe")
163rs.AddSingular("vves", "vfe")
164rs.AddSingular("wves", "wfe")
165rs.AddSingular("xves", "xfe")
166rs.AddSingular("yves", "yfe")
167rs.AddSingular("zves", "zfe")
168rs.AddSingular("hives", "hive")
169rs.AddSingular("tives", "tive")
170rs.AddSingular("lves", "lf")
171rs.AddSingular("rves", "rf")
172rs.AddSingular("quies", "quy")
173rs.AddSingular("bies", "by")
174rs.AddSingular("cies", "cy")
175rs.AddSingular("dies", "dy")
176rs.AddSingular("fies", "fy")
177rs.AddSingular("gies", "gy")
178rs.AddSingular("hies", "hy")
179rs.AddSingular("jies", "jy")
180rs.AddSingular("kies", "ky")
181rs.AddSingular("lies", "ly")
182rs.AddSingular("mies", "my")
183rs.AddSingular("nies", "ny")
184rs.AddSingular("pies", "py")
185rs.AddSingular("qies", "qy")
186rs.AddSingular("ries", "ry")
187rs.AddSingular("sies", "sy")
188rs.AddSingular("ties", "ty")
189rs.AddSingular("vies", "vy")
190rs.AddSingular("wies", "wy")
191rs.AddSingular("xies", "xy")
192rs.AddSingular("zies", "zy")
193rs.AddSingular("series", "series")
194rs.AddSingular("movies", "movie")
195rs.AddSingular("xes", "x")
196rs.AddSingular("ches", "ch")
197rs.AddSingular("sses", "ss")
198rs.AddSingular("shes", "sh")
199rs.AddSingular("mice", "mouse")
200rs.AddSingular("lice", "louse")
201rs.AddSingular("buses", "bus")
202rs.AddSingular("oes", "o")
203rs.AddSingular("shoes", "shoe")
204rs.AddSingular("crises", "crisis")
205rs.AddSingular("axes", "axis")
206rs.AddSingular("testes", "testis")
207rs.AddSingular("octopi", "octopus")
208rs.AddSingular("viri", "virus")
209rs.AddSingular("statuses", "status")
210rs.AddSingular("aliases", "alias")
211rs.AddSingularExact("oxen", "ox", true)
212rs.AddSingular("vertices", "vertex")
213rs.AddSingular("indices", "index")
214rs.AddSingular("matrices", "matrix")
215rs.AddSingularExact("quizzes", "quiz", true)
216rs.AddSingular("databases", "database")
217rs.AddIrregular("person", "people")
218rs.AddIrregular("man", "men")
219rs.AddIrregular("child", "children")
220rs.AddIrregular("sex", "sexes")
221rs.AddIrregular("move", "moves")
222rs.AddIrregular("zombie", "zombies")
223rs.AddUncountable("equipment")
224rs.AddUncountable("information")
225rs.AddUncountable("rice")
226rs.AddUncountable("money")
227rs.AddUncountable("species")
228rs.AddUncountable("series")
229rs.AddUncountable("fish")
230rs.AddUncountable("sheep")
231rs.AddUncountable("jeans")
232rs.AddUncountable("police")
233return rs
234}
235
236func (rs *Ruleset) Uncountables() map[string]bool {
237return rs.uncountables
238}
239
240// add a pluralization rule
241func (rs *Ruleset) AddPlural(suffix, replacement string) {
242rs.AddPluralExact(suffix, replacement, false)
243}
244
245// add a pluralization rule with full string match
246func (rs *Ruleset) AddPluralExact(suffix, replacement string, exact bool) {
247// remove uncountable
248delete(rs.uncountables, suffix)
249// create rule
250r := new(Rule)
251r.suffix = suffix
252r.replacement = replacement
253r.exact = exact
254// prepend
255rs.plurals = append([]*Rule{r}, rs.plurals...)
256}
257
258// add a singular rule
259func (rs *Ruleset) AddSingular(suffix, replacement string) {
260rs.AddSingularExact(suffix, replacement, false)
261}
262
263// same as AddSingular but you can set `exact` to force
264// a full string match
265func (rs *Ruleset) AddSingularExact(suffix, replacement string, exact bool) {
266// remove from uncountable
267delete(rs.uncountables, suffix)
268// create rule
269r := new(Rule)
270r.suffix = suffix
271r.replacement = replacement
272r.exact = exact
273rs.singulars = append([]*Rule{r}, rs.singulars...)
274}
275
276// Human rules are applied by humanize to show more friendly
277// versions of words
278func (rs *Ruleset) AddHuman(suffix, replacement string) {
279r := new(Rule)
280r.suffix = suffix
281r.replacement = replacement
282rs.humans = append([]*Rule{r}, rs.humans...)
283}
284
285// Add any inconsistant pluralizing/sinularizing rules
286// to the set here.
287func (rs *Ruleset) AddIrregular(singular, plural string) {
288delete(rs.uncountables, singular)
289delete(rs.uncountables, plural)
290rs.AddPlural(singular, plural)
291rs.AddPlural(plural, plural)
292rs.AddSingular(plural, singular)
293}
294
295// if you use acronym you may need to add them to the ruleset
296// to prevent Underscored words of things like "HTML" coming out
297// as "h_t_m_l"
298func (rs *Ruleset) AddAcronym(word string) {
299r := new(Rule)
300r.suffix = word
301r.replacement = rs.Titleize(strings.ToLower(word))
302rs.acronyms = append(rs.acronyms, r)
303}
304
305// add a word to this ruleset that has the same singular and plural form
306// for example: "rice"
307func (rs *Ruleset) AddUncountable(word string) {
308rs.uncountables[strings.ToLower(word)] = true
309}
310
311func (rs *Ruleset) isUncountable(word string) bool {
312// handle multiple words by using the last one
313words := strings.Split(word, " ")
314if _, exists := rs.uncountables[strings.ToLower(words[len(words)-1])]; exists {
315return true
316}
317return false
318}
319
320// returns the plural form of a singular word
321func (rs *Ruleset) Pluralize(word string) string {
322if len(word) == 0 {
323return word
324}
325if rs.isUncountable(word) {
326return word
327}
328for _, rule := range rs.plurals {
329if rule.exact {
330if word == rule.suffix {
331return rule.replacement
332}
333} else {
334if strings.HasSuffix(word, rule.suffix) {
335return replaceLast(word, rule.suffix, rule.replacement)
336}
337}
338}
339return word + "s"
340}
341
342// returns the singular form of a plural word
343func (rs *Ruleset) Singularize(word string) string {
344if len(word) == 0 {
345return word
346}
347if rs.isUncountable(word) {
348return word
349}
350for _, rule := range rs.singulars {
351if rule.exact {
352if word == rule.suffix {
353return rule.replacement
354}
355} else {
356if strings.HasSuffix(word, rule.suffix) {
357return replaceLast(word, rule.suffix, rule.replacement)
358}
359}
360}
361return word
362}
363
364// uppercase first character
365func (rs *Ruleset) Capitalize(word string) string {
366return strings.ToUpper(word[:1]) + word[1:]
367}
368
369// "dino_party" -> "DinoParty"
370func (rs *Ruleset) Camelize(word string) string {
371words := splitAtCaseChangeWithTitlecase(word)
372return strings.Join(words, "")
373}
374
375// same as Camelcase but with first letter downcased
376func (rs *Ruleset) CamelizeDownFirst(word string) string {
377word = Camelize(word)
378return strings.ToLower(word[:1]) + word[1:]
379}
380
381// Captitilize every word in sentance "hello there" -> "Hello There"
382func (rs *Ruleset) Titleize(word string) string {
383words := splitAtCaseChangeWithTitlecase(word)
384return strings.Join(words, " ")
385}
386
387func (rs *Ruleset) safeCaseAcronyms(word string) string {
388// convert an acroymn like HTML into Html
389for _, rule := range rs.acronyms {
390word = strings.Replace(word, rule.suffix, rule.replacement, -1)
391}
392return word
393}
394
395func (rs *Ruleset) seperatedWords(word, sep string) string {
396word = rs.safeCaseAcronyms(word)
397words := splitAtCaseChange(word)
398return strings.Join(words, sep)
399}
400
401// lowercase underscore version "BigBen" -> "big_ben"
402func (rs *Ruleset) Underscore(word string) string {
403return rs.seperatedWords(word, "_")
404}
405
406// First letter of sentance captitilized
407// Uses custom friendly replacements via AddHuman()
408func (rs *Ruleset) Humanize(word string) string {
409word = replaceLast(word, "_id", "") // strip foreign key kinds
410// replace and strings in humans list
411for _, rule := range rs.humans {
412word = strings.Replace(word, rule.suffix, rule.replacement, -1)
413}
414sentance := rs.seperatedWords(word, " ")
415return strings.ToUpper(sentance[:1]) + sentance[1:]
416}
417
418// an underscored foreign key name "Person" -> "person_id"
419func (rs *Ruleset) ForeignKey(word string) string {
420return rs.Underscore(rs.Singularize(word)) + "_id"
421}
422
423// a foreign key (with an underscore) "Person" -> "personid"
424func (rs *Ruleset) ForeignKeyCondensed(word string) string {
425return rs.Underscore(word) + "id"
426}
427
428// Rails style pluralized table names: "SuperPerson" -> "super_people"
429func (rs *Ruleset) Tableize(word string) string {
430return rs.Pluralize(rs.Underscore(rs.Typeify(word)))
431}
432
433var notUrlSafe *regexp.Regexp = regexp.MustCompile(`[^\w\d\-_ ]`)
434
435// param safe dasherized names like "my-param"
436func (rs *Ruleset) Parameterize(word string) string {
437return ParameterizeJoin(word, "-")
438}
439
440// param safe dasherized names with custom seperator
441func (rs *Ruleset) ParameterizeJoin(word, sep string) string {
442word = strings.ToLower(word)
443word = rs.Asciify(word)
444word = notUrlSafe.ReplaceAllString(word, "")
445word = strings.Replace(word, " ", sep, -1)
446if len(sep) > 0 {
447squash, err := regexp.Compile(sep + "+")
448if err == nil {
449word = squash.ReplaceAllString(word, sep)
450}
451}
452word = strings.Trim(word, sep+" ")
453return word
454}
455
456var lookalikes map[string]*regexp.Regexp = map[string]*regexp.Regexp{
457"A": regexp.MustCompile(`À|Á|Â|Ã|Ä|Å`),
458"AE": regexp.MustCompile(`Æ`),
459"C": regexp.MustCompile(`Ç`),
460"E": regexp.MustCompile(`È|É|Ê|Ë`),
461"G": regexp.MustCompile(`Ğ`),
462"I": regexp.MustCompile(`Ì|Í|Î|Ï|İ`),
463"N": regexp.MustCompile(`Ñ`),
464"O": regexp.MustCompile(`Ò|Ó|Ô|Õ|Ö|Ø`),
465"S": regexp.MustCompile(`Ş`),
466"U": regexp.MustCompile(`Ù|Ú|Û|Ü`),
467"Y": regexp.MustCompile(`Ý`),
468"ss": regexp.MustCompile(`ß`),
469"a": regexp.MustCompile(`à|á|â|ã|ä|å`),
470"ae": regexp.MustCompile(`æ`),
471"c": regexp.MustCompile(`ç`),
472"e": regexp.MustCompile(`è|é|ê|ë`),
473"g": regexp.MustCompile(`ğ`),
474"i": regexp.MustCompile(`ì|í|î|ï|ı`),
475"n": regexp.MustCompile(`ñ`),
476"o": regexp.MustCompile(`ò|ó|ô|õ|ö|ø`),
477"s": regexp.MustCompile(`ş`),
478"u": regexp.MustCompile(`ù|ú|û|ü|ũ|ū|ŭ|ů|ű|ų`),
479"y": regexp.MustCompile(`ý|ÿ`),
480}
481
482// transforms latin characters like é -> e
483func (rs *Ruleset) Asciify(word string) string {
484for repl, regex := range lookalikes {
485word = regex.ReplaceAllString(word, repl)
486}
487return word
488}
489
490var tablePrefix *regexp.Regexp = regexp.MustCompile(`^[^.]*\.`)
491
492// "something_like_this" -> "SomethingLikeThis"
493func (rs *Ruleset) Typeify(word string) string {
494word = tablePrefix.ReplaceAllString(word, "")
495return rs.Camelize(rs.Singularize(word))
496}
497
498// "SomeText" -> "some-text"
499func (rs *Ruleset) Dasherize(word string) string {
500return rs.seperatedWords(word, "-")
501}
502
503// "1031" -> "1031st"
504func (rs *Ruleset) Ordinalize(str string) string {
505number, err := strconv.Atoi(str)
506if err != nil {
507return str
508}
509switch abs(number) % 100 {
510case 11, 12, 13:
511return fmt.Sprintf("%dth", number)
512default:
513switch abs(number) % 10 {
514case 1:
515return fmt.Sprintf("%dst", number)
516case 2:
517return fmt.Sprintf("%dnd", number)
518case 3:
519return fmt.Sprintf("%drd", number)
520}
521}
522return fmt.Sprintf("%dth", number)
523}
524
525/////////////////////////////////////////
526// the default global ruleset
527//////////////////////////////////////////
528
529var defaultRuleset *Ruleset
530
531func init() {
532defaultRuleset = NewDefaultRuleset()
533}
534
535func Uncountables() map[string]bool {
536return defaultRuleset.Uncountables()
537}
538
539func AddPlural(suffix, replacement string) {
540defaultRuleset.AddPlural(suffix, replacement)
541}
542
543func AddSingular(suffix, replacement string) {
544defaultRuleset.AddSingular(suffix, replacement)
545}
546
547func AddHuman(suffix, replacement string) {
548defaultRuleset.AddHuman(suffix, replacement)
549}
550
551func AddIrregular(singular, plural string) {
552defaultRuleset.AddIrregular(singular, plural)
553}
554
555func AddAcronym(word string) {
556defaultRuleset.AddAcronym(word)
557}
558
559func AddUncountable(word string) {
560defaultRuleset.AddUncountable(word)
561}
562
563func Pluralize(word string) string {
564return defaultRuleset.Pluralize(word)
565}
566
567func Singularize(word string) string {
568return defaultRuleset.Singularize(word)
569}
570
571func Capitalize(word string) string {
572return defaultRuleset.Capitalize(word)
573}
574
575func Camelize(word string) string {
576return defaultRuleset.Camelize(word)
577}
578
579func CamelizeDownFirst(word string) string {
580return defaultRuleset.CamelizeDownFirst(word)
581}
582
583func Titleize(word string) string {
584return defaultRuleset.Titleize(word)
585}
586
587func Underscore(word string) string {
588return defaultRuleset.Underscore(word)
589}
590
591func Humanize(word string) string {
592return defaultRuleset.Humanize(word)
593}
594
595func ForeignKey(word string) string {
596return defaultRuleset.ForeignKey(word)
597}
598
599func ForeignKeyCondensed(word string) string {
600return defaultRuleset.ForeignKeyCondensed(word)
601}
602
603func Tableize(word string) string {
604return defaultRuleset.Tableize(word)
605}
606
607func Parameterize(word string) string {
608return defaultRuleset.Parameterize(word)
609}
610
611func ParameterizeJoin(word, sep string) string {
612return defaultRuleset.ParameterizeJoin(word, sep)
613}
614
615func Typeify(word string) string {
616return defaultRuleset.Typeify(word)
617}
618
619func Dasherize(word string) string {
620return defaultRuleset.Dasherize(word)
621}
622
623func Ordinalize(word string) string {
624return defaultRuleset.Ordinalize(word)
625}
626
627func Asciify(word string) string {
628return defaultRuleset.Asciify(word)
629}
630
631// helper funcs
632
633func reverse(s string) string {
634o := make([]rune, utf8.RuneCountInString(s))
635i := len(o)
636for _, c := range s {
637i--
638o[i] = c
639}
640return string(o)
641}
642
643func isSpacerChar(c rune) bool {
644switch {
645case c == rune("_"[0]):
646return true
647case c == rune(" "[0]):
648return true
649case c == rune(":"[0]):
650return true
651case c == rune("-"[0]):
652return true
653}
654return false
655}
656
657func splitAtCaseChange(s string) []string {
658words := make([]string, 0)
659word := make([]rune, 0)
660for _, c := range s {
661spacer := isSpacerChar(c)
662if len(word) > 0 {
663if unicode.IsUpper(c) || spacer {
664words = append(words, string(word))
665word = make([]rune, 0)
666}
667}
668if !spacer {
669word = append(word, unicode.ToLower(c))
670}
671}
672words = append(words, string(word))
673return words
674}
675
676func splitAtCaseChangeWithTitlecase(s string) []string {
677words := make([]string, 0)
678word := make([]rune, 0)
679for _, c := range s {
680spacer := isSpacerChar(c)
681if len(word) > 0 {
682if unicode.IsUpper(c) || spacer {
683words = append(words, string(word))
684word = make([]rune, 0)
685}
686}
687if !spacer {
688if len(word) > 0 {
689word = append(word, unicode.ToLower(c))
690} else {
691word = append(word, unicode.ToUpper(c))
692}
693}
694}
695words = append(words, string(word))
696return words
697}
698
699func replaceLast(s, match, repl string) string {
700// reverse strings
701srev := reverse(s)
702mrev := reverse(match)
703rrev := reverse(repl)
704// match first and reverse back
705return reverse(strings.Replace(srev, mrev, rrev, 1))
706}
707
708func abs(x int) int {
709if x < 0 {
710return -x
711}
712return x
713}
714