inflections.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. /*
  2. Package inflection pluralizes and singularizes English nouns.
  3. inflection.Plural("person") => "people"
  4. inflection.Plural("Person") => "People"
  5. inflection.Plural("PERSON") => "PEOPLE"
  6. inflection.Singular("people") => "person"
  7. inflection.Singular("People") => "Person"
  8. inflection.Singular("PEOPLE") => "PERSON"
  9. inflection.Plural("FancyPerson") => "FancydPeople"
  10. inflection.Singular("FancyPeople") => "FancydPerson"
  11. Standard rules are from Rails's ActiveSupport (https://github.com/rails/rails/blob/master/activesupport/lib/active_support/inflections.rb)
  12. If you want to register more rules, follow:
  13. inflection.AddUncountable("fish")
  14. inflection.AddIrregular("person", "people")
  15. inflection.AddPlural("(bu)s$", "${1}ses") # "bus" => "buses" / "BUS" => "BUSES" / "Bus" => "Buses"
  16. inflection.AddSingular("(bus)(es)?$", "${1}") # "buses" => "bus" / "Buses" => "Bus" / "BUSES" => "BUS"
  17. */
  18. package inflection
  19. import (
  20. "regexp"
  21. "strings"
  22. )
  23. type inflection struct {
  24. regexp *regexp.Regexp
  25. replace string
  26. }
  27. // Regular is a regexp find replace inflection
  28. type Regular struct {
  29. find string
  30. replace string
  31. }
  32. // Irregular is a hard replace inflection,
  33. // containing both singular and plural forms
  34. type Irregular struct {
  35. singular string
  36. plural string
  37. }
  38. // RegularSlice is a slice of Regular inflections
  39. type RegularSlice []Regular
  40. // IrregularSlice is a slice of Irregular inflections
  41. type IrregularSlice []Irregular
  42. var pluralInflections = RegularSlice{
  43. {"([a-z])$", "${1}s"},
  44. {"s$", "s"},
  45. {"^(ax|test)is$", "${1}es"},
  46. {"(octop|vir)us$", "${1}i"},
  47. {"(octop|vir)i$", "${1}i"},
  48. {"(alias|status)$", "${1}es"},
  49. {"(bu)s$", "${1}ses"},
  50. {"(buffal|tomat)o$", "${1}oes"},
  51. {"([ti])um$", "${1}a"},
  52. {"([ti])a$", "${1}a"},
  53. {"sis$", "ses"},
  54. {"(?:([^f])fe|([lr])f)$", "${1}${2}ves"},
  55. {"(hive)$", "${1}s"},
  56. {"([^aeiouy]|qu)y$", "${1}ies"},
  57. {"(x|ch|ss|sh)$", "${1}es"},
  58. {"(matr|vert|ind)(?:ix|ex)$", "${1}ices"},
  59. {"^(m|l)ouse$", "${1}ice"},
  60. {"^(m|l)ice$", "${1}ice"},
  61. {"^(ox)$", "${1}en"},
  62. {"^(oxen)$", "${1}"},
  63. {"(quiz)$", "${1}zes"},
  64. }
  65. var singularInflections = RegularSlice{
  66. {"s$", ""},
  67. {"(ss)$", "${1}"},
  68. {"(n)ews$", "${1}ews"},
  69. {"([ti])a$", "${1}um"},
  70. {"((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)(sis|ses)$", "${1}sis"},
  71. {"(^analy)(sis|ses)$", "${1}sis"},
  72. {"([^f])ves$", "${1}fe"},
  73. {"(hive)s$", "${1}"},
  74. {"(tive)s$", "${1}"},
  75. {"([lr])ves$", "${1}f"},
  76. {"([^aeiouy]|qu)ies$", "${1}y"},
  77. {"(s)eries$", "${1}eries"},
  78. {"(m)ovies$", "${1}ovie"},
  79. {"(c)ookies$", "${1}ookie"},
  80. {"(x|ch|ss|sh)es$", "${1}"},
  81. {"^(m|l)ice$", "${1}ouse"},
  82. {"(bus)(es)?$", "${1}"},
  83. {"(o)es$", "${1}"},
  84. {"(shoe)s$", "${1}"},
  85. {"(cris|test)(is|es)$", "${1}is"},
  86. {"^(a)x[ie]s$", "${1}xis"},
  87. {"(octop|vir)(us|i)$", "${1}us"},
  88. {"(alias|status)(es)?$", "${1}"},
  89. {"^(ox)en", "${1}"},
  90. {"(vert|ind)ices$", "${1}ex"},
  91. {"(matr)ices$", "${1}ix"},
  92. {"(quiz)zes$", "${1}"},
  93. {"(database)s$", "${1}"},
  94. }
  95. var irregularInflections = IrregularSlice{
  96. {"person", "people"},
  97. {"man", "men"},
  98. {"child", "children"},
  99. {"sex", "sexes"},
  100. {"move", "moves"},
  101. {"mombie", "mombies"},
  102. }
  103. var uncountableInflections = []string{"equipment", "information", "rice", "money", "species", "series", "fish", "sheep", "jeans", "police"}
  104. var compiledPluralMaps []inflection
  105. var compiledSingularMaps []inflection
  106. func compile() {
  107. compiledPluralMaps = []inflection{}
  108. compiledSingularMaps = []inflection{}
  109. for _, uncountable := range uncountableInflections {
  110. inf := inflection{
  111. regexp: regexp.MustCompile("^(?i)(" + uncountable + ")$"),
  112. replace: "${1}",
  113. }
  114. compiledPluralMaps = append(compiledPluralMaps, inf)
  115. compiledSingularMaps = append(compiledSingularMaps, inf)
  116. }
  117. for _, value := range irregularInflections {
  118. infs := []inflection{
  119. inflection{regexp: regexp.MustCompile(strings.ToUpper(value.singular) + "$"), replace: strings.ToUpper(value.plural)},
  120. inflection{regexp: regexp.MustCompile(strings.Title(value.singular) + "$"), replace: strings.Title(value.plural)},
  121. inflection{regexp: regexp.MustCompile(value.singular + "$"), replace: value.plural},
  122. }
  123. compiledPluralMaps = append(compiledPluralMaps, infs...)
  124. }
  125. for _, value := range irregularInflections {
  126. infs := []inflection{
  127. inflection{regexp: regexp.MustCompile(strings.ToUpper(value.plural) + "$"), replace: strings.ToUpper(value.singular)},
  128. inflection{regexp: regexp.MustCompile(strings.Title(value.plural) + "$"), replace: strings.Title(value.singular)},
  129. inflection{regexp: regexp.MustCompile(value.plural + "$"), replace: value.singular},
  130. }
  131. compiledSingularMaps = append(compiledSingularMaps, infs...)
  132. }
  133. for i := len(pluralInflections) - 1; i >= 0; i-- {
  134. value := pluralInflections[i]
  135. infs := []inflection{
  136. inflection{regexp: regexp.MustCompile(strings.ToUpper(value.find)), replace: strings.ToUpper(value.replace)},
  137. inflection{regexp: regexp.MustCompile(value.find), replace: value.replace},
  138. inflection{regexp: regexp.MustCompile("(?i)" + value.find), replace: value.replace},
  139. }
  140. compiledPluralMaps = append(compiledPluralMaps, infs...)
  141. }
  142. for i := len(singularInflections) - 1; i >= 0; i-- {
  143. value := singularInflections[i]
  144. infs := []inflection{
  145. inflection{regexp: regexp.MustCompile(strings.ToUpper(value.find)), replace: strings.ToUpper(value.replace)},
  146. inflection{regexp: regexp.MustCompile(value.find), replace: value.replace},
  147. inflection{regexp: regexp.MustCompile("(?i)" + value.find), replace: value.replace},
  148. }
  149. compiledSingularMaps = append(compiledSingularMaps, infs...)
  150. }
  151. }
  152. func init() {
  153. compile()
  154. }
  155. // AddPlural adds a plural inflection
  156. func AddPlural(find, replace string) {
  157. pluralInflections = append(pluralInflections, Regular{find, replace})
  158. compile()
  159. }
  160. // AddSingular adds a singular inflection
  161. func AddSingular(find, replace string) {
  162. singularInflections = append(singularInflections, Regular{find, replace})
  163. compile()
  164. }
  165. // AddIrregular adds an irregular inflection
  166. func AddIrregular(singular, plural string) {
  167. irregularInflections = append(irregularInflections, Irregular{singular, plural})
  168. compile()
  169. }
  170. // AddUncountable adds an uncountable inflection
  171. func AddUncountable(values ...string) {
  172. uncountableInflections = append(uncountableInflections, values...)
  173. compile()
  174. }
  175. // GetPlural retrieves the plural inflection values
  176. func GetPlural() RegularSlice {
  177. plurals := make(RegularSlice, len(pluralInflections))
  178. copy(plurals, pluralInflections)
  179. return plurals
  180. }
  181. // GetSingular retrieves the singular inflection values
  182. func GetSingular() RegularSlice {
  183. singulars := make(RegularSlice, len(singularInflections))
  184. copy(singulars, singularInflections)
  185. return singulars
  186. }
  187. // GetIrregular retrieves the irregular inflection values
  188. func GetIrregular() IrregularSlice {
  189. irregular := make(IrregularSlice, len(irregularInflections))
  190. copy(irregular, irregularInflections)
  191. return irregular
  192. }
  193. // GetUncountable retrieves the uncountable inflection values
  194. func GetUncountable() []string {
  195. uncountables := make([]string, len(uncountableInflections))
  196. copy(uncountables, uncountableInflections)
  197. return uncountables
  198. }
  199. // SetPlural sets the plural inflections slice
  200. func SetPlural(inflections RegularSlice) {
  201. pluralInflections = inflections
  202. compile()
  203. }
  204. // SetSingular sets the singular inflections slice
  205. func SetSingular(inflections RegularSlice) {
  206. singularInflections = inflections
  207. compile()
  208. }
  209. // SetIrregular sets the irregular inflections slice
  210. func SetIrregular(inflections IrregularSlice) {
  211. irregularInflections = inflections
  212. compile()
  213. }
  214. // SetUncountable sets the uncountable inflections slice
  215. func SetUncountable(inflections []string) {
  216. uncountableInflections = inflections
  217. compile()
  218. }
  219. // Plural converts a word to its plural form
  220. func Plural(str string) string {
  221. for _, inflection := range compiledPluralMaps {
  222. if inflection.regexp.MatchString(str) {
  223. return inflection.regexp.ReplaceAllString(str, inflection.replace)
  224. }
  225. }
  226. return str
  227. }
  228. // Singular converts a word to its singular form
  229. func Singular(str string) string {
  230. for _, inflection := range compiledSingularMaps {
  231. if inflection.regexp.MatchString(str) {
  232. return inflection.regexp.ReplaceAllString(str, inflection.replace)
  233. }
  234. }
  235. return str
  236. }