| package inflect |
| |
| import ( |
| "bytes" |
| "encoding/json" |
| "fmt" |
| "io" |
| "io/ioutil" |
| "os" |
| "path/filepath" |
| "regexp" |
| "strconv" |
| "strings" |
| "unicode" |
| "unicode/utf8" |
| ) |
| |
| // baseAcronyms comes from https://en.wikipedia.org/wiki/List_of_information_technology_acronymss |
| 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` |
| |
| // Rule used by rulesets |
| type Rule struct { |
| suffix string |
| replacement string |
| exact bool |
| } |
| |
| // Ruleset a Ruleset is the config of pluralization rules |
| // you can extend the rules with the Add* methods |
| type Ruleset struct { |
| uncountables map[string]bool |
| plurals []*Rule |
| singulars []*Rule |
| humans []*Rule |
| acronyms []*Rule |
| } |
| |
| // NewRuleset creates a blank ruleset. Unless you are going to |
| // build your own rules from scratch you probably |
| // won't need this and can just use the defaultRuleset |
| // via the global inflect.* methods |
| func NewRuleset() *Ruleset { |
| rs := new(Ruleset) |
| rs.uncountables = make(map[string]bool) |
| rs.plurals = make([]*Rule, 0) |
| rs.singulars = make([]*Rule, 0) |
| rs.humans = make([]*Rule, 0) |
| rs.acronyms = make([]*Rule, 0) |
| return rs |
| } |
| |
| // NewDefaultRuleset creates a new ruleset and load it with the default |
| // set of common English pluralization rules |
| func NewDefaultRuleset() *Ruleset { |
| rs := NewRuleset() |
| rs.AddPlural("movie", "movies") |
| rs.AddPlural("s", "s") |
| rs.AddPlural("testis", "testes") |
| rs.AddPlural("axis", "axes") |
| rs.AddPlural("octopus", "octopi") |
| rs.AddPlural("virus", "viri") |
| rs.AddPlural("octopi", "octopi") |
| rs.AddPlural("viri", "viri") |
| rs.AddPlural("alias", "aliases") |
| rs.AddPlural("status", "statuses") |
| rs.AddPlural("Status", "Statuses") |
| rs.AddPlural("campus", "campuses") |
| rs.AddPlural("bus", "buses") |
| rs.AddPlural("buffalo", "buffaloes") |
| rs.AddPlural("tomato", "tomatoes") |
| rs.AddPlural("tum", "ta") |
| rs.AddPlural("ium", "ia") |
| rs.AddPlural("ta", "ta") |
| rs.AddPlural("ia", "ia") |
| rs.AddPlural("sis", "ses") |
| rs.AddPlural("lf", "lves") |
| rs.AddPlural("rf", "rves") |
| rs.AddPlural("afe", "aves") |
| rs.AddPlural("bfe", "bves") |
| rs.AddPlural("cfe", "cves") |
| rs.AddPlural("dfe", "dves") |
| rs.AddPlural("efe", "eves") |
| rs.AddPlural("gfe", "gves") |
| rs.AddPlural("hfe", "hves") |
| rs.AddPlural("ife", "ives") |
| rs.AddPlural("jfe", "jves") |
| rs.AddPlural("kfe", "kves") |
| rs.AddPlural("lfe", "lves") |
| rs.AddPlural("mfe", "mves") |
| rs.AddPlural("nfe", "nves") |
| rs.AddPlural("ofe", "oves") |
| rs.AddPlural("pfe", "pves") |
| rs.AddPlural("qfe", "qves") |
| rs.AddPlural("rfe", "rves") |
| rs.AddPlural("sfe", "sves") |
| rs.AddPlural("tfe", "tves") |
| rs.AddPlural("ufe", "uves") |
| rs.AddPlural("vfe", "vves") |
| rs.AddPlural("wfe", "wves") |
| rs.AddPlural("xfe", "xves") |
| rs.AddPlural("yfe", "yves") |
| rs.AddPlural("zfe", "zves") |
| rs.AddPlural("hive", "hives") |
| rs.AddPlural("quy", "quies") |
| rs.AddPlural("by", "bies") |
| rs.AddPlural("cy", "cies") |
| rs.AddPlural("dy", "dies") |
| rs.AddPlural("fy", "fies") |
| rs.AddPlural("gy", "gies") |
| rs.AddPlural("hy", "hies") |
| rs.AddPlural("jy", "jies") |
| rs.AddPlural("ky", "kies") |
| rs.AddPlural("ly", "lies") |
| rs.AddPlural("my", "mies") |
| rs.AddPlural("ny", "nies") |
| rs.AddPlural("py", "pies") |
| rs.AddPlural("qy", "qies") |
| rs.AddPlural("ry", "ries") |
| rs.AddPlural("sy", "sies") |
| rs.AddPlural("ty", "ties") |
| rs.AddPlural("vy", "vies") |
| rs.AddPlural("wy", "wies") |
| rs.AddPlural("xy", "xies") |
| rs.AddPlural("zy", "zies") |
| rs.AddPlural("x", "xes") |
| rs.AddPlural("ch", "ches") |
| rs.AddPlural("ss", "sses") |
| rs.AddPlural("sh", "shes") |
| rs.AddPlural("matrix", "matrices") |
| rs.AddPlural("vertix", "vertices") |
| rs.AddPlural("indix", "indices") |
| rs.AddPlural("matrex", "matrices") |
| rs.AddPlural("vertex", "vertices") |
| rs.AddPlural("index", "indices") |
| rs.AddPlural("mouse", "mice") |
| rs.AddPlural("louse", "lice") |
| rs.AddPlural("mice", "mice") |
| rs.AddPlural("lice", "lice") |
| rs.AddPlural("ress", "resses") |
| rs.AddPluralExact("ox", "oxen", true) |
| rs.AddPluralExact("oxen", "oxen", true) |
| rs.AddPluralExact("quiz", "quizzes", true) |
| rs.AddSingular("s", "") |
| rs.AddSingular("ss", "ss") |
| rs.AddSingular("news", "news") |
| rs.AddSingular("ta", "tum") |
| rs.AddSingular("ia", "ium") |
| rs.AddSingular("analyses", "analysis") |
| rs.AddSingular("bases", "basis") |
| rs.AddSingularExact("basis", "basis", true) |
| rs.AddSingular("diagnoses", "diagnosis") |
| rs.AddSingularExact("diagnosis", "diagnosis", true) |
| rs.AddSingular("parentheses", "parenthesis") |
| rs.AddSingular("prognoses", "prognosis") |
| rs.AddSingular("synopses", "synopsis") |
| rs.AddSingular("theses", "thesis") |
| rs.AddSingular("analyses", "analysis") |
| rs.AddSingularExact("analysis", "analysis", true) |
| rs.AddSingular("ovies", "ovie") |
| rs.AddSingular("aves", "afe") |
| rs.AddSingular("bves", "bfe") |
| rs.AddSingular("cves", "cfe") |
| rs.AddSingular("dves", "dfe") |
| rs.AddSingular("eves", "efe") |
| rs.AddSingular("gves", "gfe") |
| rs.AddSingular("hves", "hfe") |
| rs.AddSingular("ives", "ife") |
| rs.AddSingular("jves", "jfe") |
| rs.AddSingular("kves", "kfe") |
| rs.AddSingular("lves", "lfe") |
| rs.AddSingular("mves", "mfe") |
| rs.AddSingular("nves", "nfe") |
| rs.AddSingular("oves", "ofe") |
| rs.AddSingular("pves", "pfe") |
| rs.AddSingular("qves", "qfe") |
| rs.AddSingular("rves", "rfe") |
| rs.AddSingular("sves", "sfe") |
| rs.AddSingular("tves", "tfe") |
| rs.AddSingular("uves", "ufe") |
| rs.AddSingular("vves", "vfe") |
| rs.AddSingular("wves", "wfe") |
| rs.AddSingular("xves", "xfe") |
| rs.AddSingular("yves", "yfe") |
| rs.AddSingular("zves", "zfe") |
| rs.AddSingular("hives", "hive") |
| rs.AddSingular("tives", "tive") |
| rs.AddSingular("lves", "lf") |
| rs.AddSingular("rves", "rf") |
| rs.AddSingular("quies", "quy") |
| rs.AddSingular("bies", "by") |
| rs.AddSingular("cies", "cy") |
| rs.AddSingular("dies", "dy") |
| rs.AddSingular("fies", "fy") |
| rs.AddSingular("gies", "gy") |
| rs.AddSingular("hies", "hy") |
| rs.AddSingular("jies", "jy") |
| rs.AddSingular("kies", "ky") |
| rs.AddSingular("lies", "ly") |
| rs.AddSingular("mies", "my") |
| rs.AddSingular("nies", "ny") |
| rs.AddSingular("pies", "py") |
| rs.AddSingular("qies", "qy") |
| rs.AddSingular("ries", "ry") |
| rs.AddSingular("sies", "sy") |
| rs.AddSingular("ties", "ty") |
| // rs.AddSingular("vies", "vy") |
| rs.AddSingular("wies", "wy") |
| rs.AddSingular("xies", "xy") |
| rs.AddSingular("zies", "zy") |
| rs.AddSingular("series", "series") |
| rs.AddSingular("xes", "x") |
| rs.AddSingular("ches", "ch") |
| rs.AddSingular("sses", "ss") |
| rs.AddSingular("shes", "sh") |
| rs.AddSingular("mice", "mouse") |
| rs.AddSingular("lice", "louse") |
| rs.AddSingular("buses", "bus") |
| rs.AddSingularExact("bus", "bus", true) |
| rs.AddSingular("oes", "o") |
| rs.AddSingular("shoes", "shoe") |
| rs.AddSingular("crises", "crisis") |
| rs.AddSingularExact("crisis", "crisis", true) |
| rs.AddSingular("axes", "axis") |
| rs.AddSingularExact("axis", "axis", true) |
| rs.AddSingular("testes", "testis") |
| rs.AddSingularExact("testis", "testis", true) |
| rs.AddSingular("octopi", "octopus") |
| rs.AddSingularExact("octopus", "octopus", true) |
| rs.AddSingular("viri", "virus") |
| rs.AddSingularExact("virus", "virus", true) |
| rs.AddSingular("statuses", "status") |
| rs.AddSingular("Statuses", "Status") |
| rs.AddSingular("campuses", "campus") |
| rs.AddSingularExact("status", "status", true) |
| rs.AddSingularExact("Status", "Status", true) |
| rs.AddSingularExact("campus", "campus", true) |
| rs.AddSingular("aliases", "alias") |
| rs.AddSingularExact("alias", "alias", true) |
| rs.AddSingularExact("oxen", "ox", true) |
| rs.AddSingular("vertices", "vertex") |
| rs.AddSingular("indices", "index") |
| rs.AddSingular("matrices", "matrix") |
| rs.AddSingularExact("quizzes", "quiz", true) |
| rs.AddSingular("databases", "database") |
| rs.AddSingular("resses", "ress") |
| rs.AddSingular("ress", "ress") |
| rs.AddIrregular("person", "people") |
| rs.AddIrregular("man", "men") |
| rs.AddIrregular("child", "children") |
| rs.AddIrregular("sex", "sexes") |
| rs.AddIrregular("move", "moves") |
| rs.AddIrregular("zombie", "zombies") |
| rs.AddIrregular("Status", "Statuses") |
| rs.AddIrregular("status", "statuses") |
| rs.AddIrregular("campus", "campuses") |
| rs.AddIrregular("human", "humans") |
| rs.AddUncountable("equipment") |
| rs.AddUncountable("information") |
| rs.AddUncountable("rice") |
| rs.AddUncountable("money") |
| rs.AddUncountable("species") |
| rs.AddUncountable("series") |
| rs.AddUncountable("fish") |
| rs.AddUncountable("sheep") |
| rs.AddUncountable("jeans") |
| rs.AddUncountable("police") |
| |
| acronyms := strings.Split(baseAcronyms, ",") |
| for _, acr := range acronyms { |
| rs.AddAcronym(acr) |
| } |
| |
| return rs |
| } |
| |
| // Uncountables returns a map of uncountables in the ruleset |
| func (rs *Ruleset) Uncountables() map[string]bool { |
| return rs.uncountables |
| } |
| |
| // AddPlural add a pluralization rule |
| func (rs *Ruleset) AddPlural(suffix, replacement string) { |
| rs.AddPluralExact(suffix, replacement, false) |
| } |
| |
| // AddPluralExact add a pluralization rule with full string match |
| func (rs *Ruleset) AddPluralExact(suffix, replacement string, exact bool) { |
| // remove uncountable |
| delete(rs.uncountables, suffix) |
| // create rule |
| r := new(Rule) |
| r.suffix = suffix |
| r.replacement = replacement |
| r.exact = exact |
| // prepend |
| rs.plurals = append([]*Rule{r}, rs.plurals...) |
| } |
| |
| // AddSingular add a singular rule |
| func (rs *Ruleset) AddSingular(suffix, replacement string) { |
| rs.AddSingularExact(suffix, replacement, false) |
| } |
| |
| // AddSingularExact same as AddSingular but you can set `exact` to force |
| // a full string match |
| func (rs *Ruleset) AddSingularExact(suffix, replacement string, exact bool) { |
| // remove from uncountable |
| delete(rs.uncountables, suffix) |
| // create rule |
| r := new(Rule) |
| r.suffix = suffix |
| r.replacement = replacement |
| r.exact = exact |
| rs.singulars = append([]*Rule{r}, rs.singulars...) |
| } |
| |
| // AddHuman Human rules are applied by humanize to show more friendly |
| // versions of words |
| func (rs *Ruleset) AddHuman(suffix, replacement string) { |
| r := new(Rule) |
| r.suffix = suffix |
| r.replacement = replacement |
| rs.humans = append([]*Rule{r}, rs.humans...) |
| } |
| |
| // AddIrregular Add any inconsistent pluralizing/singularizing rules |
| // to the set here. |
| func (rs *Ruleset) AddIrregular(singular, plural string) { |
| delete(rs.uncountables, singular) |
| delete(rs.uncountables, plural) |
| rs.AddPlural(singular, plural) |
| rs.AddPlural(plural, plural) |
| rs.AddSingular(plural, singular) |
| } |
| |
| // AddAcronym if you use acronym you may need to add them to the ruleset |
| // to prevent Underscored words of things like "HTML" coming out |
| // as "h_t_m_l" |
| func (rs *Ruleset) AddAcronym(word string) { |
| r := new(Rule) |
| r.suffix = word |
| r.replacement = rs.Titleize(strings.ToLower(word)) |
| rs.acronyms = append(rs.acronyms, r) |
| } |
| |
| // AddUncountable add a word to this ruleset that has the same singular and plural form |
| // for example: "rice" |
| func (rs *Ruleset) AddUncountable(word string) { |
| rs.uncountables[strings.ToLower(word)] = true |
| } |
| |
| func (rs *Ruleset) isUncountable(word string) bool { |
| // handle multiple words by using the last one |
| words := strings.Split(word, " ") |
| if _, exists := rs.uncountables[strings.ToLower(words[len(words)-1])]; exists { |
| return true |
| } |
| return false |
| } |
| |
| //isAcronym returns if a word is acronym or not. |
| func (rs *Ruleset) isAcronym(word string) bool { |
| for _, rule := range rs.acronyms { |
| if strings.ToUpper(rule.suffix) == strings.ToUpper(word) { |
| return true |
| } |
| } |
| |
| return false |
| } |
| |
| //PluralizeWithSize pluralize with taking number into account |
| func (rs *Ruleset) PluralizeWithSize(word string, size int) string { |
| if size == 1 { |
| return rs.Singularize(word) |
| } |
| return rs.Pluralize(word) |
| } |
| |
| // Pluralize returns the plural form of a singular word |
| func (rs *Ruleset) Pluralize(word string) string { |
| if len(word) == 0 { |
| return word |
| } |
| lWord := strings.ToLower(word) |
| if rs.isUncountable(lWord) { |
| return word |
| } |
| |
| var candidate string |
| for _, rule := range rs.plurals { |
| if rule.exact { |
| if lWord == rule.suffix { |
| // Capitalized word |
| if lWord[0] != word[0] && lWord[1:] == word[1:] { |
| return rs.Capitalize(rule.replacement) |
| } |
| return rule.replacement |
| } |
| continue |
| } |
| |
| if strings.EqualFold(word, rule.suffix) { |
| candidate = rule.replacement |
| } |
| |
| if strings.HasSuffix(word, rule.suffix) { |
| return replaceLast(word, rule.suffix, rule.replacement) |
| } |
| } |
| |
| if candidate != "" { |
| return candidate |
| } |
| return word + "s" |
| } |
| |
| //Singularize returns the singular form of a plural word |
| func (rs *Ruleset) Singularize(word string) string { |
| if len(word) <= 1 { |
| return word |
| } |
| lWord := strings.ToLower(word) |
| if rs.isUncountable(lWord) { |
| return word |
| } |
| |
| var candidate string |
| |
| for _, rule := range rs.singulars { |
| if rule.exact { |
| if lWord == rule.suffix { |
| // Capitalized word |
| if lWord[0] != word[0] && lWord[1:] == word[1:] { |
| return rs.Capitalize(rule.replacement) |
| } |
| return rule.replacement |
| } |
| continue |
| } |
| |
| if strings.EqualFold(word, rule.suffix) { |
| candidate = rule.replacement |
| } |
| |
| if strings.HasSuffix(word, rule.suffix) { |
| return replaceLast(word, rule.suffix, rule.replacement) |
| } |
| } |
| |
| if candidate != "" { |
| return candidate |
| } |
| |
| return word |
| } |
| |
| //Capitalize uppercase first character |
| func (rs *Ruleset) Capitalize(word string) string { |
| if rs.isAcronym(word) { |
| return strings.ToUpper(word) |
| } |
| return strings.ToUpper(word[:1]) + word[1:] |
| } |
| |
| //Camelize "dino_party" -> "DinoParty" |
| func (rs *Ruleset) Camelize(word string) string { |
| if rs.isAcronym(word) { |
| return strings.ToUpper(word) |
| } |
| words := splitAtCaseChangeWithTitlecase(word) |
| return strings.Join(words, "") |
| } |
| |
| //CamelizeDownFirst same as Camelcase but with first letter downcased |
| func (rs *Ruleset) CamelizeDownFirst(word string) string { |
| word = Camelize(word) |
| return strings.ToLower(word[:1]) + word[1:] |
| } |
| |
| //Titleize Capitalize every word in sentence "hello there" -> "Hello There" |
| func (rs *Ruleset) Titleize(word string) string { |
| words := splitAtCaseChangeWithTitlecase(word) |
| result := strings.Join(words, " ") |
| |
| var acronymWords []string |
| for index, word := range words { |
| if len(word) == 1 { |
| acronymWords = append(acronymWords, word) |
| } |
| |
| if len(word) > 1 || index == len(words)-1 || len(acronymWords) > 1 { |
| acronym := strings.Join(acronymWords, "") |
| if !rs.isAcronym(acronym) { |
| acronymWords = acronymWords[:len(acronymWords)] |
| continue |
| } |
| |
| result = strings.Replace(result, strings.Join(acronymWords, " "), acronym, 1) |
| acronymWords = []string{} |
| } |
| } |
| |
| return result |
| } |
| |
| func (rs *Ruleset) safeCaseAcronyms(word string) string { |
| // convert an acronym like HTML into Html |
| for _, rule := range rs.acronyms { |
| word = strings.Replace(word, rule.suffix, rule.replacement, -1) |
| } |
| return word |
| } |
| |
| func (rs *Ruleset) separatedWords(word, sep string) string { |
| word = rs.safeCaseAcronyms(word) |
| words := splitAtCaseChange(word) |
| return strings.Join(words, sep) |
| } |
| |
| //Underscore lowercase underscore version "BigBen" -> "big_ben" |
| func (rs *Ruleset) Underscore(word string) string { |
| return rs.separatedWords(word, "_") |
| } |
| |
| //Humanize First letter of sentence capitalized |
| // Uses custom friendly replacements via AddHuman() |
| func (rs *Ruleset) Humanize(word string) string { |
| word = replaceLast(word, "_id", "") // strip foreign key kinds |
| // replace and strings in humans list |
| for _, rule := range rs.humans { |
| word = strings.Replace(word, rule.suffix, rule.replacement, -1) |
| } |
| sentence := rs.separatedWords(word, " ") |
| |
| r, n := utf8.DecodeRuneInString(sentence) |
| return string(unicode.ToUpper(r)) + sentence[n:] |
| } |
| |
| //ForeignKey an underscored foreign key name "Person" -> "person_id" |
| func (rs *Ruleset) ForeignKey(word string) string { |
| return rs.Underscore(rs.Singularize(word)) + "_id" |
| } |
| |
| //ForeignKeyCondensed a foreign key (with an underscore) "Person" -> "personid" |
| func (rs *Ruleset) ForeignKeyCondensed(word string) string { |
| return rs.Underscore(word) + "id" |
| } |
| |
| //Tableize Rails style pluralized table names: "SuperPerson" -> "super_people" |
| func (rs *Ruleset) Tableize(word string) string { |
| return rs.Pluralize(rs.Underscore(rs.Typeify(word))) |
| } |
| |
| var notUrlSafe *regexp.Regexp = regexp.MustCompile(`[^\w\d\-_ ]`) |
| |
| //Parameterize param safe dasherized names like "my-param" |
| func (rs *Ruleset) Parameterize(word string) string { |
| return ParameterizeJoin(word, "-") |
| } |
| |
| //ParameterizeJoin param safe dasherized names with custom separator |
| func (rs *Ruleset) ParameterizeJoin(word, sep string) string { |
| word = strings.ToLower(word) |
| word = rs.Asciify(word) |
| word = notUrlSafe.ReplaceAllString(word, "") |
| word = strings.Replace(word, " ", sep, -1) |
| if len(sep) > 0 { |
| squash, err := regexp.Compile(sep + "+") |
| if err == nil { |
| word = squash.ReplaceAllString(word, sep) |
| } |
| } |
| word = strings.Trim(word, sep+" ") |
| return word |
| } |
| |
| var lookalikes = map[string]*regexp.Regexp{ |
| "A": regexp.MustCompile(`À|Á|Â|Ã|Ä|Å`), |
| "AE": regexp.MustCompile(`Æ`), |
| "C": regexp.MustCompile(`Ç`), |
| "E": regexp.MustCompile(`È|É|Ê|Ë`), |
| "G": regexp.MustCompile(`Ğ`), |
| "I": regexp.MustCompile(`Ì|Í|Î|Ï|İ`), |
| "N": regexp.MustCompile(`Ñ`), |
| "O": regexp.MustCompile(`Ò|Ó|Ô|Õ|Ö|Ø`), |
| "S": regexp.MustCompile(`Ş`), |
| "U": regexp.MustCompile(`Ù|Ú|Û|Ü`), |
| "Y": regexp.MustCompile(`Ý`), |
| "ss": regexp.MustCompile(`ß`), |
| "a": regexp.MustCompile(`à|á|â|ã|ä|å`), |
| "ae": regexp.MustCompile(`æ`), |
| "c": regexp.MustCompile(`ç`), |
| "e": regexp.MustCompile(`è|é|ê|ë`), |
| "g": regexp.MustCompile(`ğ`), |
| "i": regexp.MustCompile(`ì|í|î|ï|ı`), |
| "n": regexp.MustCompile(`ñ`), |
| "o": regexp.MustCompile(`ò|ó|ô|õ|ö|ø`), |
| "s": regexp.MustCompile(`ş`), |
| "u": regexp.MustCompile(`ù|ú|û|ü|ũ|ū|ŭ|ů|ű|ų`), |
| "y": regexp.MustCompile(`ý|ÿ`), |
| } |
| |
| //Asciify transforms Latin characters like é -> e |
| func (rs *Ruleset) Asciify(word string) string { |
| for repl, regex := range lookalikes { |
| word = regex.ReplaceAllString(word, repl) |
| } |
| return word |
| } |
| |
| var tablePrefix = regexp.MustCompile(`^[^.]*\.`) |
| |
| //Typeify "something_like_this" -> "SomethingLikeThis" |
| func (rs *Ruleset) Typeify(word string) string { |
| word = tablePrefix.ReplaceAllString(word, "") |
| return rs.Camelize(rs.Singularize(word)) |
| } |
| |
| //Dasherize "SomeText" -> "some-text" |
| func (rs *Ruleset) Dasherize(word string) string { |
| return rs.separatedWords(word, "-") |
| } |
| |
| //Ordinalize "1031" -> "1031st" |
| func (rs *Ruleset) Ordinalize(str string) string { |
| number, err := strconv.Atoi(str) |
| if err != nil { |
| return str |
| } |
| switch abs(number) % 100 { |
| case 11, 12, 13: |
| return fmt.Sprintf("%dth", number) |
| default: |
| switch abs(number) % 10 { |
| case 1: |
| return fmt.Sprintf("%dst", number) |
| case 2: |
| return fmt.Sprintf("%dnd", number) |
| case 3: |
| return fmt.Sprintf("%drd", number) |
| } |
| } |
| return fmt.Sprintf("%dth", number) |
| } |
| |
| //ForeignKeyToAttribute returns the attribute name from the foreign key |
| func (rs *Ruleset) ForeignKeyToAttribute(str string) string { |
| w := rs.Camelize(str) |
| if strings.HasSuffix(w, "Id") { |
| return strings.TrimSuffix(w, "Id") + "ID" |
| } |
| return w |
| } |
| |
| //LoadReader loads rules from io.Reader param |
| func (rs *Ruleset) LoadReader(r io.Reader) error { |
| m := map[string]string{} |
| err := json.NewDecoder(r).Decode(&m) |
| if err != nil { |
| return fmt.Errorf("could not decode inflection JSON from reader: %s", err) |
| } |
| for s, p := range m { |
| defaultRuleset.AddIrregular(s, p) |
| } |
| return nil |
| } |
| |
| ///////////////////////////////////////// |
| // the default global ruleset |
| ////////////////////////////////////////// |
| |
| var defaultRuleset *Ruleset |
| |
| //LoadReader loads rules from io.Reader param |
| func LoadReader(r io.Reader) error { |
| return defaultRuleset.LoadReader(r) |
| } |
| |
| func init() { |
| defaultRuleset = NewDefaultRuleset() |
| |
| pwd, _ := os.Getwd() |
| cfg := filepath.Join(pwd, "inflections.json") |
| if p := os.Getenv("INFLECT_PATH"); p != "" { |
| cfg = p |
| } |
| if _, err := os.Stat(cfg); err == nil { |
| b, err := ioutil.ReadFile(cfg) |
| if err != nil { |
| fmt.Printf("could not read inflection file %s (%s)\n", cfg, err) |
| return |
| } |
| if err = defaultRuleset.LoadReader(bytes.NewReader(b)); err != nil { |
| fmt.Println(err) |
| } |
| } |
| } |
| |
| //Uncountables returns a list of uncountables rules |
| func Uncountables() map[string]bool { |
| return defaultRuleset.Uncountables() |
| } |
| |
| //AddPlural adds plural to the ruleset |
| func AddPlural(suffix, replacement string) { |
| defaultRuleset.AddPlural(suffix, replacement) |
| } |
| |
| //AddSingular adds singular to the ruleset |
| func AddSingular(suffix, replacement string) { |
| defaultRuleset.AddSingular(suffix, replacement) |
| } |
| |
| //AddHuman adds human |
| func AddHuman(suffix, replacement string) { |
| defaultRuleset.AddHuman(suffix, replacement) |
| } |
| |
| func AddIrregular(singular, plural string) { |
| defaultRuleset.AddIrregular(singular, plural) |
| } |
| |
| func AddAcronym(word string) { |
| defaultRuleset.AddAcronym(word) |
| } |
| |
| func AddUncountable(word string) { |
| defaultRuleset.AddUncountable(word) |
| } |
| |
| func Pluralize(word string) string { |
| return defaultRuleset.Pluralize(word) |
| } |
| |
| func PluralizeWithSize(word string, size int) string { |
| return defaultRuleset.PluralizeWithSize(word, size) |
| } |
| |
| func Singularize(word string) string { |
| return defaultRuleset.Singularize(word) |
| } |
| |
| func Capitalize(word string) string { |
| return defaultRuleset.Capitalize(word) |
| } |
| |
| func Camelize(word string) string { |
| return defaultRuleset.Camelize(word) |
| } |
| |
| func CamelizeDownFirst(word string) string { |
| return defaultRuleset.CamelizeDownFirst(word) |
| } |
| |
| func Titleize(word string) string { |
| return defaultRuleset.Titleize(word) |
| } |
| |
| func Underscore(word string) string { |
| return defaultRuleset.Underscore(word) |
| } |
| |
| func Humanize(word string) string { |
| return defaultRuleset.Humanize(word) |
| } |
| |
| func ForeignKey(word string) string { |
| return defaultRuleset.ForeignKey(word) |
| } |
| |
| func ForeignKeyCondensed(word string) string { |
| return defaultRuleset.ForeignKeyCondensed(word) |
| } |
| |
| func Tableize(word string) string { |
| return defaultRuleset.Tableize(word) |
| } |
| |
| func Parameterize(word string) string { |
| return defaultRuleset.Parameterize(word) |
| } |
| |
| func ParameterizeJoin(word, sep string) string { |
| return defaultRuleset.ParameterizeJoin(word, sep) |
| } |
| |
| func Typeify(word string) string { |
| return defaultRuleset.Typeify(word) |
| } |
| |
| func Dasherize(word string) string { |
| return defaultRuleset.Dasherize(word) |
| } |
| |
| func Ordinalize(word string) string { |
| return defaultRuleset.Ordinalize(word) |
| } |
| |
| func Asciify(word string) string { |
| return defaultRuleset.Asciify(word) |
| } |
| |
| func ForeignKeyToAttribute(word string) string { |
| return defaultRuleset.ForeignKeyToAttribute(word) |
| } |
| |
| // helper funcs |
| |
| func reverse(s string) string { |
| o := make([]rune, utf8.RuneCountInString(s)) |
| i := len(o) |
| for _, c := range s { |
| i-- |
| o[i] = c |
| } |
| return string(o) |
| } |
| |
| func isSpacerChar(c rune) bool { |
| switch { |
| case c == rune("_"[0]): |
| return true |
| case c == rune(" "[0]): |
| return true |
| case c == rune(":"[0]): |
| return true |
| case c == rune("-"[0]): |
| return true |
| } |
| return false |
| } |
| |
| func splitAtCaseChange(s string) []string { |
| words := make([]string, 0) |
| word := make([]rune, 0) |
| for _, c := range s { |
| spacer := isSpacerChar(c) |
| if len(word) > 0 { |
| if unicode.IsUpper(c) || spacer { |
| words = append(words, string(word)) |
| word = make([]rune, 0) |
| } |
| } |
| if !spacer { |
| word = append(word, unicode.ToLower(c)) |
| } |
| } |
| words = append(words, string(word)) |
| return words |
| } |
| |
| func splitAtCaseChangeWithTitlecase(s string) []string { |
| words := make([]string, 0) |
| word := make([]rune, 0) |
| |
| for _, c := range s { |
| spacer := isSpacerChar(c) |
| if len(word) > 0 { |
| if unicode.IsUpper(c) || spacer { |
| words = append(words, string(word)) |
| word = make([]rune, 0) |
| } |
| } |
| if !spacer { |
| if len(word) > 0 { |
| word = append(word, unicode.ToLower(c)) |
| } else { |
| word = append(word, unicode.ToUpper(c)) |
| } |
| } |
| } |
| |
| words = append(words, string(word)) |
| return words |
| } |
| |
| func replaceLast(s, match, repl string) string { |
| // reverse strings |
| srev := reverse(s) |
| mrev := reverse(match) |
| rrev := reverse(repl) |
| // match first and reverse back |
| return reverse(strings.Replace(srev, mrev, rrev, 1)) |
| } |
| |
| func abs(x int) int { |
| if x < 0 { |
| return -x |
| } |
| return x |
| } |