├── LICENSE ├── tests └── tinytest.R ├── .gitignore ├── CRAN-RELEASE ├── .Rbuildignore ├── .travis.yml ├── NAMESPACE ├── R ├── zzz.R ├── pluralize-package.R ├── is.R ├── pluralize.r ├── singularize.R └── rules.R ├── cran-comments.md ├── pluralize.Rproj ├── NEWS.md ├── man ├── is_plural.Rd ├── add_plural_rule.Rd ├── pluralize-package.Rd ├── add_uncountable_rule.Rd ├── pluralize-function.Rd ├── add_singular_rule.Rd ├── add_irregular_rule.Rd └── singularize.Rd ├── inst ├── tinytest │ └── test_pluralize.R └── js │ └── pluralize.js ├── DESCRIPTION ├── CONDUCT.md ├── README.Rmd ├── vignettes └── Why-pluralize.Rmd └── README.md /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2016 2 | COPYRIGHT HOLDER: Bob Rudis 3 | -------------------------------------------------------------------------------- /tests/tinytest.R: -------------------------------------------------------------------------------- 1 | 2 | if ( requireNamespace("tinytest", quietly=TRUE) ){ 3 | tinytest::test_package("pluralize") 4 | } 5 | 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | .Rproj 5 | src/*.o 6 | src/*.so 7 | src/*.dll 8 | inst/doc 9 | doc 10 | Meta 11 | -------------------------------------------------------------------------------- /CRAN-RELEASE: -------------------------------------------------------------------------------- 1 | This package was submitted to CRAN on 2020-06-03. 2 | Once it is accepted, delete this file and tag the release (commit 77962cb936). 3 | -------------------------------------------------------------------------------- /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^\.travis\.yml$ 4 | ^README\.md$ 5 | ^v8\.png$ 6 | ^README\.html$ 7 | ^CONDUCT\.md$ 8 | ^doc$ 9 | ^Meta$ 10 | ^README\.Rmd$ 11 | ^cran-comments\.md$ 12 | ^CRAN-RELEASE$ 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: R 2 | sudo: false 3 | cache: packages 4 | latex: false 5 | fortran: false 6 | 7 | addons: 8 | apt: 9 | packages: 10 | - libv8-dev 11 | 12 | after_success: 13 | - Rscript -e 'covr::codecov()' 14 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(add_irregular_rule) 4 | export(add_plural_rule) 5 | export(add_singular_rule) 6 | export(add_uncountable_rule) 7 | export(is_plural) 8 | export(is_singular) 9 | export(pluralize) 10 | export(singularize) 11 | import(V8) 12 | -------------------------------------------------------------------------------- /R/zzz.R: -------------------------------------------------------------------------------- 1 | # holding place for things we want to keep around but not visible 2 | .pkgenv <- new.env(parent=emptyenv()) 3 | 4 | .onLoad <- function(...) { 5 | 6 | # read in the suffixes using V8 7 | ct <- v8() 8 | assign("ct", ct, envir=.pkgenv) 9 | 10 | ct$source(system.file("js/pluralize.js", package="pluralize")) 11 | 12 | } 13 | -------------------------------------------------------------------------------- /cran-comments.md: -------------------------------------------------------------------------------- 1 | ## Test environments 2 | * local R installation, R 4.0.1 3 | * ubuntu 16.04 (on travis-ci), R 4.0.1 4 | * win-builder (devel) 5 | 6 | ## R CMD check results 7 | 8 | 0 errors | 0 warnings | 1 note 9 | 10 | * This is a new release. 11 | 12 | ARGH! My apologies for that stupid typo'd word. 13 | 14 | Please ignore the previous submission. 15 | -------------------------------------------------------------------------------- /pluralize.Rproj: -------------------------------------------------------------------------------- 1 | Version: 1.0 2 | 3 | RestoreWorkspace: Default 4 | SaveWorkspace: Default 5 | AlwaysSaveHistory: Default 6 | 7 | EnableCodeIndexing: Yes 8 | UseSpacesForTab: Yes 9 | NumSpacesForTab: 2 10 | Encoding: UTF-8 11 | 12 | RnwWeave: Sweave 13 | LaTeX: pdfLaTeX 14 | 15 | StripTrailingWhitespace: Yes 16 | 17 | BuildType: Package 18 | PackageUseDevtools: Yes 19 | PackageInstallArgs: --no-multiarch --with-keep.source 20 | PackageBuildArgs: --resave-data 21 | PackageRoxygenize: rd,collate,namespace 22 | -------------------------------------------------------------------------------- /R/pluralize-package.R: -------------------------------------------------------------------------------- 1 | #' Pluralize and Singularize Any Word 2 | #' 3 | #' Tools are provided to create plural, singular and regular forms of 4 | #' English words along with tools to augment the built-in rules to fit 5 | #' specializied needs. Core functionality is based on a JavaScript library, 6 | #' , by Blake Embrey. 7 | #' 8 | #' @name pluralize-package 9 | #' @docType package 10 | #' @author Bob Rudis (@@hrbrmstr) 11 | #' @keywords internal 12 | #' @import V8 13 | NULL 14 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | # pluralize 0.2.1 2 | 3 | * Switched out `.onAttach()` for `onLoad()` so that importing to packages 4 | (either using `@import` or by declaring with `pluralize::`) now works 5 | 6 | # pluralize 0.2.0 7 | 8 | * Added a `NEWS.md` file to track changes to the package. 9 | * Normalized README to fit other package write 10 | * Updated pluralize.js 11 | * Swithed to {tinytest} 12 | * Updated vignette 13 | * Broke out the previous monolith pluralize.R into separate files. 14 | * Removed {purrr} dependency 15 | * Added "is_*" functions 16 | -------------------------------------------------------------------------------- /R/is.R: -------------------------------------------------------------------------------- 1 | #' Test plural state of a word 2 | #' 3 | #' @param x vector of words to test 4 | #' @return logical vector 5 | #' @export 6 | #' @examples 7 | #' is_singular(c("boats", "house", "cats", "river")) 8 | #' is_plural(c("boats", "house", "cats", "river")) 9 | is_plural <- function(x) { 10 | unname(sapply(x, function(y) { .pkgenv$ct$call("pluralize.isPlural", y)})) 11 | } 12 | 13 | #' @rdname is_plural 14 | #' @export 15 | is_singular <- function(x) { 16 | unname(sapply(x, function(y) { .pkgenv$ct$call("pluralize.isSingular", y)})) 17 | } 18 | -------------------------------------------------------------------------------- /man/is_plural.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/is.R 3 | \name{is_plural} 4 | \alias{is_plural} 5 | \alias{is_singular} 6 | \title{Test plural state of a word} 7 | \usage{ 8 | is_plural(x) 9 | 10 | is_singular(x) 11 | } 12 | \arguments{ 13 | \item{x}{vector of words to test} 14 | } 15 | \value{ 16 | logical vector 17 | } 18 | \description{ 19 | Test plural state of a word 20 | } 21 | \examples{ 22 | is_singular(c("boats", "house", "cats", "river")) 23 | is_plural(c("boats", "house", "cats", "river")) 24 | } 25 | -------------------------------------------------------------------------------- /man/add_plural_rule.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rules.R 3 | \name{add_plural_rule} 4 | \alias{add_plural_rule} 5 | \title{Add a custom rule for making a word plural} 6 | \usage{ 7 | add_plural_rule(singular_word, plural_word) 8 | } 9 | \arguments{ 10 | \item{singular_word}{desired singular form of a word} 11 | 12 | \item{plural_word}{plural form of a word} 13 | } 14 | \description{ 15 | Add a custom rule for making a word plural 16 | } 17 | \examples{ 18 | add_plural_rule("gex", "gexii") 19 | pluralize('regex') 20 | singularize('singles') 21 | } 22 | -------------------------------------------------------------------------------- /man/pluralize-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pluralize-package.R 3 | \docType{package} 4 | \name{pluralize-package} 5 | \alias{pluralize-package} 6 | \title{Pluralize and Singularize Any Word} 7 | \description{ 8 | Tools are provided to create plural, singular and regular forms of 9 | English words along with tools to augment the built-in rules to fit 10 | specializied needs. Core functionality is based on a JavaScript library, 11 | , by Blake Embrey. 12 | } 13 | \author{ 14 | Bob Rudis (@hrbrmstr) 15 | } 16 | \keyword{internal} 17 | -------------------------------------------------------------------------------- /man/add_uncountable_rule.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rules.R 3 | \name{add_uncountable_rule} 4 | \alias{add_uncountable_rule} 5 | \title{Make a word "uncountable"} 6 | \usage{ 7 | add_uncountable_rule(uncountable_word) 8 | } 9 | \arguments{ 10 | \item{uncountable_word}{the word that's, well, uncountable} 11 | } 12 | \value{ 13 | nothing (function produces a side effect of modifying 14 | in-memory internal package envrionment) 15 | } 16 | \description{ 17 | Sometimes you don't want to turn "\code{paper}" into "\code{papers}". You 18 | can use this function to make a word "uncountable". 19 | } 20 | \examples{ 21 | add_uncountable_rule("paper") 22 | pluralize("paper") 23 | } 24 | -------------------------------------------------------------------------------- /R/pluralize.r: -------------------------------------------------------------------------------- 1 | #' Pluralize a word 2 | #' 3 | #' If the built-in rules are not sufficient, use \code{add_plural_rule()} to 4 | #' customize the behavior. 5 | #' 6 | #' @param x character vector of words to make plural 7 | #' @param n amount of plural (some plurals change by amount and this also impacts 8 | #' the \code{prepend} functionality) 9 | #' @param prepend should we prepend the value of \code{n} to the output? Default: \code{FALSE} 10 | #' @return character vector of modified pluralized words 11 | #' @export 12 | #' @rdname pluralize-function 13 | #' @examples 14 | #' pluralize('test') 15 | #' pluralize('test', 5) 16 | #' pluralize('test', 5, TRUE) 17 | #' pluralize('regex') 18 | pluralize <- function(x, n=2, prepend=FALSE) { 19 | unname(sapply(x, function(y) { 20 | .pkgenv$ct$call("pluralize", y, n, prepend) 21 | })) 22 | } 23 | 24 | -------------------------------------------------------------------------------- /man/pluralize-function.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/pluralize.r 3 | \name{pluralize} 4 | \alias{pluralize} 5 | \title{Pluralize a word} 6 | \usage{ 7 | pluralize(x, n = 2, prepend = FALSE) 8 | } 9 | \arguments{ 10 | \item{x}{character vector of words to make plural} 11 | 12 | \item{n}{amount of plural (some plurals change by amount and this also impacts 13 | the \code{prepend} functionality)} 14 | 15 | \item{prepend}{should we prepend the value of \code{n} to the output? Default: \code{FALSE}} 16 | } 17 | \value{ 18 | character vector of modified pluralized words 19 | } 20 | \description{ 21 | If the built-in rules are not sufficient, use \code{add_plural_rule()} to 22 | customize the behavior. 23 | } 24 | \examples{ 25 | pluralize('test') 26 | pluralize('test', 5) 27 | pluralize('test', 5, TRUE) 28 | pluralize('regex') 29 | } 30 | -------------------------------------------------------------------------------- /man/add_singular_rule.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rules.R 3 | \name{add_singular_rule} 4 | \alias{add_singular_rule} 5 | \title{Add a custom rule for making a word singular} 6 | \usage{ 7 | add_singular_rule(plural_word, singular_word) 8 | } 9 | \arguments{ 10 | \item{plural_word}{plural form of a word} 11 | 12 | \item{singular_word}{desired singular form of a word} 13 | } 14 | \value{ 15 | nothing (function produces a side effect of modifying 16 | in-memory internal package envrionment) 17 | } 18 | \description{ 19 | Add a custom rule for making a word singular 20 | } 21 | \examples{ 22 | add_singular_rule("singles", "singular") 23 | singularize('singles') 24 | pluralize("irregular") 25 | pluralize(c("woman", "man", "child", "tooth", "foot", "person", "leaf")) 26 | singularize(c("woman", "man", "child", "tooth", "foot", "person", "leaf")) 27 | } 28 | -------------------------------------------------------------------------------- /man/add_irregular_rule.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/rules.R 3 | \name{add_irregular_rule} 4 | \alias{add_irregular_rule} 5 | \title{Add a custom rule for making "deregularizing" a word} 6 | \usage{ 7 | add_irregular_rule(irregular_word, regular_word) 8 | } 9 | \arguments{ 10 | \item{irregular_word}{the irregular form of the word} 11 | 12 | \item{regular_word}{regular form of a word} 13 | } 14 | \value{ 15 | nothing (function produces a side effect of modifying 16 | in-memory internal package envrionment) 17 | } 18 | \description{ 19 | "\code{regular}" is the "deregularized" form of "\code{irregular}". 20 | Calling \code{plural("irregular")} will return "\code{regular}" if you 21 | define a rule such as \code{add_irregular_rule("irregular", "regular")}. 22 | } 23 | \examples{ 24 | add_irregular_rule("irregular", "regular") 25 | pluralize("irregular") 26 | pluralize("paper") 27 | } 28 | -------------------------------------------------------------------------------- /R/singularize.R: -------------------------------------------------------------------------------- 1 | #' Singularize a word 2 | #' 3 | #' If the built-in rules are not sufficient, use \code{add_singular_rule()} to 4 | #' customize the behavior. 5 | #' 6 | #' @param x vector of words to make singular 7 | #' @return modified character vector of singularized words 8 | #' @export 9 | #' @examples 10 | #' singularize('test') 11 | #' singularize(c("boats", "houses", "cats", "rivers")) 12 | #' pluralize(singularize(c("boats", "houses", "cats", "rivers"))) 13 | #' singularize(c("buses", "wishes", "pitches", "boxexs")) 14 | #' pluralize(singularize(c("buses", "wishes", "pitches", "boxexs"))) 15 | #' singularize(c("pennies", "spies", "babies", "cities", "daisies")) 16 | #' pluralize(singularize(c("pennies", "spies", "babies", "cities", "daisies"))) 17 | #' singularize(c("sheep", "fish", "deer", "species", "aircraft")) 18 | #' pluralize(singularize(c("sheep", "fish", "deer", "species", "aircraft"))) 19 | singularize <- function(x) { 20 | unname(sapply(x, function(y) { .pkgenv$ct$call("pluralize", y, 1)})) 21 | } 22 | 23 | 24 | -------------------------------------------------------------------------------- /inst/tinytest/test_pluralize.R: -------------------------------------------------------------------------------- 1 | library(pluralize) 2 | 3 | expect_equal(pluralize('test'), "tests") 4 | expect_equal(pluralize('test', 5), "tests") 5 | expect_equal(pluralize('test', 5, TRUE), "5 tests") 6 | expect_equal(pluralize('regex'), "regexes") 7 | 8 | expect_equal(singularize('test'), "test") 9 | 10 | expect_equal( 11 | singularize(c("boats", "houses", "cats", "rivers")), 12 | c("boat", "house", "cat", "river") 13 | ) 14 | 15 | expect_equal( 16 | pluralize(singularize(c("boats", "houses", "cats", "rivers"))), 17 | c("boats", "houses", "cats", "rivers") 18 | ) 19 | 20 | expect_equal( 21 | singularize(c("buses", "wishes", "pitches", "boxexs")), 22 | c("bus", "wish", "pitch", "boxex") 23 | ) 24 | 25 | add_plural_rule("gex", "gexii") 26 | expect_equal(pluralize('regex'), "regexii") 27 | 28 | add_singular_rule("singles", "singular") 29 | expect_equal(singularize('singles'), "singular") 30 | 31 | add_irregular_rule("irregular", "regular") 32 | expect_equal(pluralize("irregular"), "regular") 33 | 34 | add_uncountable_rule("paper") 35 | expect_equal(pluralize("paper"), "paper") 36 | -------------------------------------------------------------------------------- /man/singularize.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/singularize.R 3 | \name{singularize} 4 | \alias{singularize} 5 | \title{Singularize a word} 6 | \usage{ 7 | singularize(x) 8 | } 9 | \arguments{ 10 | \item{x}{vector of words to make singular} 11 | } 12 | \value{ 13 | modified character vector of singularized words 14 | } 15 | \description{ 16 | If the built-in rules are not sufficient, use \code{add_singular_rule()} to 17 | customize the behavior. 18 | } 19 | \examples{ 20 | singularize('test') 21 | singularize(c("boats", "houses", "cats", "rivers")) 22 | pluralize(singularize(c("boats", "houses", "cats", "rivers"))) 23 | singularize(c("buses", "wishes", "pitches", "boxexs")) 24 | pluralize(singularize(c("buses", "wishes", "pitches", "boxexs"))) 25 | singularize(c("pennies", "spies", "babies", "cities", "daisies")) 26 | pluralize(singularize(c("pennies", "spies", "babies", "cities", "daisies"))) 27 | singularize(c("sheep", "fish", "deer", "species", "aircraft")) 28 | pluralize(singularize(c("sheep", "fish", "deer", "species", "aircraft"))) 29 | } 30 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: pluralize 2 | Type: Package 3 | Title: Pluralize and 'Singularize' Any (English) Word 4 | Version: 0.2.1 5 | Date: 2020-06-03 6 | Authors@R: c( 7 | person( 8 | "Bob", "Rudis", email = "bob@rud.is", 9 | role = c("aut", "cre"), 10 | comment = c(ORCID = "0000-0001-5670-2640") 11 | ), 12 | person( 13 | "Blake", "Embrey", email = "hello@blakeembrey.com", 14 | role = c("aut", "ctb"), 15 | comment = "pluralize.js " 16 | ) 17 | ) 18 | Maintainer: Bob Rudis 19 | Description: Tools are provided to create plural, singular and regular forms of 20 | English words along with tools to augment the built-in rules to fit 21 | specialized needs. Core functionality is based on a JavaScript library, 22 | . 23 | URL: http://gitlab.com/hrbrmstr/pluralize 24 | BugReports: https://gitlab.com/hrbrmstr/pluralize/issues 25 | License: MIT + file LICENSE 26 | Encoding: UTF-8 27 | Suggests: 28 | tinytest, 29 | knitr, 30 | rmarkdown 31 | Depends: 32 | R (>= 3.6.0) 33 | Imports: 34 | V8 35 | RoxygenNote: 7.1.1 36 | VignetteBuilder: knitr 37 | -------------------------------------------------------------------------------- /CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, we pledge to respect all people who 4 | contribute through reporting issues, posting feature requests, updating documentation, 5 | submitting pull requests or patches, and other activities. 6 | 7 | We are committed to making participation in this project a harassment-free experience for 8 | everyone, regardless of level of experience, gender, gender identity and expression, 9 | sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion. 10 | 11 | Examples of unacceptable behavior by participants include the use of sexual language or 12 | imagery, derogatory comments or personal attacks, trolling, public or private harassment, 13 | insults, or other unprofessional conduct. 14 | 15 | Project maintainers have the right and responsibility to remove, edit, or reject comments, 16 | commits, code, wiki edits, issues, and other contributions that are not aligned to this 17 | Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed 18 | from the project team. 19 | 20 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by 21 | opening an issue or contacting one or more of the project maintainers. 22 | 23 | This Code of Conduct is adapted from the Contributor Covenant 24 | (http:contributor-covenant.org), version 1.0.0, available at 25 | http://contributor-covenant.org/version/1/0/0/ 26 | -------------------------------------------------------------------------------- /README.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | output: rmarkdown::github_document 3 | editor_options: 4 | chunk_output_type: console 5 | --- 6 | ```{r pkg-knitr-opts, include=FALSE} 7 | hrbrpkghelpr::global_opts() 8 | ``` 9 | 10 | ```{r badges, results='asis', echo=FALSE, cache=FALSE} 11 | hrbrpkghelpr::stinking_badges() 12 | ``` 13 | 14 | ```{r description, results='asis', echo=FALSE, cache=FALSE} 15 | hrbrpkghelpr::yank_title_and_description() 16 | ``` 17 | 18 | ## What's Inside The Tin 19 | 20 | The following functions are implemented: 21 | 22 | ```{r ingredients, results='asis', echo=FALSE, cache=FALSE} 23 | hrbrpkghelpr::describe_ingredients() 24 | ``` 25 | 26 | ## Installation 27 | 28 | ```{r install-ex, results='asis', echo=FALSE, cache=FALSE} 29 | hrbrpkghelpr::install_block() 30 | ``` 31 | 32 | ## Usage 33 | 34 | ```{r lib-ex} 35 | library(pluralize) 36 | 37 | # current version 38 | packageVersion("pluralize") 39 | 40 | ``` 41 | 42 | ```{r ex-01} 43 | pluralize('test') 44 | 45 | singularize('test') 46 | 47 | singularize(c("boats", "houses", "cats", "rivers")) 48 | 49 | pluralize(singularize(c("boats", "houses", "cats", "rivers"))) 50 | 51 | singularize(c("buses", "wishes", "pitches", "boxexs")) 52 | 53 | pluralize(singularize(c("buses", "wishes", "pitches", "boxexs"))) 54 | 55 | singularize(c("pennies", "spies", "babies", "cities", "daisies")) 56 | 57 | pluralize(singularize(c("pennies", "spies", "babies", "cities", "daisies"))) 58 | 59 | singularize(c("sheep", "fish", "deer", "species", "aircraft")) 60 | 61 | pluralize(singularize(c("sheep", "fish", "deer", "species", "aircraft"))) 62 | 63 | pluralize('test', 5) 64 | 65 | pluralize('test', 5, TRUE) 66 | 67 | pluralize('regex') 68 | 69 | add_plural_rule("gex", "gexii") 70 | 71 | pluralize('regex') 72 | 73 | singularize('singles') 74 | 75 | add_singular_rule("singles", "singular") 76 | 77 | singularize('singles') 78 | 79 | pluralize("irregular") 80 | 81 | pluralize(c("woman", "man", "child", "tooth", "foot", "person", "leaf")) 82 | 83 | singularize(c("woman", "man", "child", "tooth", "foot", "person", "leaf")) 84 | 85 | add_irregular_rule("irregular", "regular") 86 | 87 | pluralize("irregular") 88 | 89 | pluralize("paper") 90 | 91 | add_uncountable_rule("paper") 92 | 93 | pluralize("paper") 94 | ``` 95 | 96 | ### pluralize Metrics 97 | 98 | ```{r cloc, echo=FALSE} 99 | cloc::cloc_pkg_md() 100 | ``` 101 | 102 | ## Code of Conduct 103 | 104 | Please note that this project is released with a Contributor Code of Conduct. 105 | By participating in this project you agree to abide by its terms. 106 | -------------------------------------------------------------------------------- /R/rules.R: -------------------------------------------------------------------------------- 1 | #' Add a custom rule for making a word singular 2 | #' 3 | #' @param plural_word plural form of a word 4 | #' @param singular_word desired singular form of a word 5 | #' @return nothing (function produces a side effect of modifying 6 | #' in-memory internal package envrionment) 7 | #' @export 8 | #' @examples 9 | #' add_singular_rule("singles", "singular") 10 | #' singularize('singles') 11 | #' pluralize("irregular") 12 | #' pluralize(c("woman", "man", "child", "tooth", "foot", "person", "leaf")) 13 | #' singularize(c("woman", "man", "child", "tooth", "foot", "person", "leaf")) 14 | add_singular_rule <- function(plural_word, singular_word) { 15 | .pkgenv$ct$call("pluralize.addSingularRule", 16 | JS(sprintf("/%s/i", plural_word)), 17 | singular_word) 18 | } 19 | 20 | #' Add a custom rule for making a word plural 21 | #' 22 | #' @param singular_word desired singular form of a word 23 | #' @param plural_word plural form of a word 24 | #' @export 25 | #' @examples 26 | #' add_plural_rule("gex", "gexii") 27 | #' pluralize('regex') 28 | #' singularize('singles') 29 | add_plural_rule <- function(singular_word, plural_word) { 30 | .pkgenv$ct$call("pluralize.addPluralRule", 31 | JS(sprintf("/%s/i", singular_word)), 32 | plural_word) 33 | } 34 | 35 | #' Add a custom rule for making "deregularizing" a word 36 | #' 37 | #' "\code{regular}" is the "deregularized" form of "\code{irregular}". 38 | #' Calling \code{plural("irregular")} will return "\code{regular}" if you 39 | #' define a rule such as \code{add_irregular_rule("irregular", "regular")}. 40 | #' 41 | #' @param irregular_word the irregular form of the word 42 | #' @param regular_word regular form of a word 43 | #' @export 44 | #' @return nothing (function produces a side effect of modifying 45 | #' in-memory internal package envrionment) 46 | #' @examples 47 | #' add_irregular_rule("irregular", "regular") 48 | #' pluralize("irregular") 49 | #' pluralize("paper") 50 | add_irregular_rule <- function(irregular_word, regular_word) { 51 | .pkgenv$ct$call("pluralize.addPluralRule", irregular_word, regular_word) 52 | } 53 | 54 | #' Make a word "uncountable" 55 | #' 56 | #' Sometimes you don't want to turn "\code{paper}" into "\code{papers}". You 57 | #' can use this function to make a word "uncountable". 58 | #' 59 | #' @param uncountable_word the word that's, well, uncountable 60 | #' @return nothing (function produces a side effect of modifying 61 | #' in-memory internal package envrionment) 62 | #' @export 63 | #' @examples 64 | #' add_uncountable_rule("paper") 65 | #' pluralize("paper") 66 | add_uncountable_rule <- function(uncountable_word) { 67 | .pkgenv$ct$call("pluralize.addUncountableRule", uncountable_word) 68 | } 69 | -------------------------------------------------------------------------------- /vignettes/Why-pluralize.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Why 'pluralize'\\?" 3 | author: "Bob Rudis" 4 | date: "`r Sys.Date()`" 5 | output: rmarkdown::html_vignette 6 | vignette: > 7 | %\VignetteIndexEntry{Why pluralize\\?} 8 | %\VignetteEngine{knitr::rmarkdown} 9 | %\VignetteEncoding{UTF-8} 10 | --- 11 | There are times when one needs to do some lightweight text mining and need to normalize words without the baggage that comes along with dependencies such as `tm` or `OpenNLP`. 12 | 13 | One of the steps in this normalization process that is often helpful is to take plural words and make them singular vs "stem" them (which often leaves _interesting_ prefixes that work but aren't exactly "pretty".) 14 | 15 | The English language makes this a bit more gnarly at times and there are also cases where one might want to bend the rules a bit (i.e. discipline-specific terminology that may not abide by the rules). 16 | 17 | While many words can be made plural by just adding an "`s`" there are cases where one has to add "es", "ies" depending on the ending consonant or vowel. 18 | 19 | There are further cases, like "`knife`", where there are special rules that force you to do things like make that "`knives`" to get the plural form. 20 | 21 | Then, there are _irregular_ plurals like "`fish`" => "`fish`" and "`person`" => "`people`" and others like "`cactus`" => "`cacti`" or "`erratum`" => "`errata`". 22 | 23 | However, you may need to override normal behavior. For instance, "`paper`" would normally turn into "`papers`" but you may need it to stay "`paper`". 24 | 25 | This package is based on a javascript library that has encoded the core pluralization (and singularization) rules and exposed various functions to aid in maniuplating strings, including the ability to add special case rules (like the "`paper`" example). 26 | 27 | Here are some examples to show the functionality: 28 | 29 | Basic pluralization 30 | 31 | ```{r} 32 | library(pluralize) 33 | 34 | (plurals <- pluralize(c("snake", "window", "box", "boy", "lorry", 35 | "potato", "knife"))) 36 | ``` 37 | 38 | and the reverse: 39 | 40 | ```{r} 41 | singularize(plurals) 42 | ``` 43 | 44 | ending "`o`": 45 | 46 | ```{r} 47 | (plurals <- pluralize(c("echo", "embargo", "hero", "potato", "tomato", 48 | "torpedo", "veto"))) 49 | 50 | singularize(plurals) 51 | ``` 52 | 53 | others ending "`o`": 54 | 55 | ```{r} 56 | (plurals <- pluralize(c("auto", "kangaroo", "kilo", "memo", "photo", "piano", 57 | "pimento", "pro", "solo", "soprano", "studio", "tattoo", 58 | "video", "zoo"))) 59 | 60 | singularize(plurals) 61 | ``` 62 | 63 | Some special nouns: 64 | 65 | ```{r} 66 | (plurals <- pluralize(c("knife", "leaf", "hoof", "life", "self", "elf"))) 67 | 68 | singularize(plurals) 69 | ``` 70 | 71 | Some irregular nouns: 72 | 73 | ```{r} 74 | (plurals <- pluralize(c("fish", "sheep", "foot", "tooth", "goose", 75 | "child", "man", "woman", "person", "mouse"))) 76 | 77 | singularize(plurals) 78 | ``` 79 | 80 | Again, you may want to do something special for certain words: 81 | 82 | ```{r} 83 | add_plural_rule("corpus", "corpora") 84 | add_plural_rule("formula", "formulae") 85 | add_plural_rule("memoranda", "memorandums") 86 | 87 | add_singular_rule("corpora", "corpus") 88 | add_singular_rule("formulae", "formula") 89 | add_singular_rule("memorandums", "memoranda") 90 | 91 | (plurals <- pluralize(c("corpus", "formula", "memoranda"))) 92 | 93 | singularize(plurals) 94 | ``` 95 | 96 | Since this package is based on a javascript library - [pluralize.js](https://github.com/blakeembrey/pluralize) by Blake Embrey - and uses the V8 package it won't be as lightning fast as a pure C/C++-backed package (it takes ~10 seconds to pluralize/singularize 10,000 words which is kinda horrible but sufficient for this project I'm working on), but it wouldn't take much to translate the rules built into `pluralize.js` over to C++. 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | [![Project Status: Active – The project has reached a stable, usable 3 | state and is being actively 4 | developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active) 5 | [![Signed 6 | by](https://img.shields.io/badge/Keybase-Verified-brightgreen.svg)](https://keybase.io/hrbrmstr) 7 | ![Signed commit 8 | %](https://img.shields.io/badge/Signed_Commits-100%25-lightgrey.svg) 9 | [![Linux build 10 | Status](https://travis-ci.org/hrbrmstr/pluralize.svg?branch=master)](https://travis-ci.org/hrbrmstr/pluralize) 11 | [![Coverage 12 | Status](https://codecov.io/gh/hrbrmstr/pluralize/branch/master/graph/badge.svg)](https://codecov.io/gh/hrbrmstr/pluralize) 13 | ![Minimal R 14 | Version](https://img.shields.io/badge/R%3E%3D-3.6.0-blue.svg) 15 | ![License](https://img.shields.io/badge/License-MIT-blue.svg) 16 | 17 | # pluralize 18 | 19 | Pluralize and ‘Singularize’ Any (English) Word 20 | 21 | ## Description 22 | 23 | Tools are provided to create plural, singular and regular forms of 24 | English words along with tools to augment the built-in rules to fit 25 | specializied needs. Core functionality is based on a JavaScript library, 26 | . 27 | 28 | ## What’s Inside The Tin 29 | 30 | The following functions are implemented: 31 | 32 | - `add_irregular_rule`: Add a custom rule for making “deregularizing” 33 | a word 34 | - `add_plural_rule`: Add a custom rule for making a word plural 35 | - `add_singular_rule`: Add a custom rule for making a word singular 36 | - `add_uncountable_rule`: Make a word “uncountable” 37 | - `is_plural`: Test plural state of a word 38 | - `pluralize`: Pluralize a word 39 | - `singularize`: Singularize a word 40 | 41 | ## Installation 42 | 43 | ``` r 44 | remotes::install_gitlab("hrbrmstr/pluralize") 45 | # or 46 | remotes::install_github("hrbrmstr/pluralize") 47 | ``` 48 | 49 | NOTE: To use the ‘remotes’ install options you will need to have the 50 | [{remotes} package](https://github.com/r-lib/remotes) installed. 51 | 52 | ## Usage 53 | 54 | ``` r 55 | library(pluralize) 56 | 57 | # current version 58 | packageVersion("pluralize") 59 | ## [1] '0.2.0' 60 | ``` 61 | 62 | ``` r 63 | pluralize('test') 64 | ## [1] "tests" 65 | 66 | singularize('test') 67 | ## [1] "test" 68 | 69 | singularize(c("boats", "houses", "cats", "rivers")) 70 | ## [1] "boat" "house" "cat" "river" 71 | 72 | pluralize(singularize(c("boats", "houses", "cats", "rivers"))) 73 | ## [1] "boats" "houses" "cats" "rivers" 74 | 75 | singularize(c("buses", "wishes", "pitches", "boxexs")) 76 | ## [1] "bus" "wish" "pitch" "boxex" 77 | 78 | pluralize(singularize(c("buses", "wishes", "pitches", "boxexs"))) 79 | ## [1] "buses" "wishes" "pitches" "boxexes" 80 | 81 | singularize(c("pennies", "spies", "babies", "cities", "daisies")) 82 | ## [1] "penny" "spy" "baby" "city" "daisie" 83 | 84 | pluralize(singularize(c("pennies", "spies", "babies", "cities", "daisies"))) 85 | ## [1] "pennies" "spies" "babies" "cities" "daisies" 86 | 87 | singularize(c("sheep", "fish", "deer", "species", "aircraft")) 88 | ## [1] "sheep" "fish" "deer" "specie" "aircraft" 89 | 90 | pluralize(singularize(c("sheep", "fish", "deer", "species", "aircraft"))) 91 | ## [1] "sheep" "fish" "deer" "species" "aircraft" 92 | 93 | pluralize('test', 5) 94 | ## [1] "tests" 95 | 96 | pluralize('test', 5, TRUE) 97 | ## [1] "5 tests" 98 | 99 | pluralize('regex') 100 | ## [1] "regexes" 101 | 102 | add_plural_rule("gex", "gexii") 103 | 104 | pluralize('regex') 105 | ## [1] "regexii" 106 | 107 | singularize('singles') 108 | ## [1] "single" 109 | 110 | add_singular_rule("singles", "singular") 111 | 112 | singularize('singles') 113 | ## [1] "singular" 114 | 115 | pluralize("irregular") 116 | ## [1] "irregulars" 117 | 118 | pluralize(c("woman", "man", "child", "tooth", "foot", "person", "leaf")) 119 | ## [1] "women" "men" "children" "teeth" "feet" "people" "leaves" 120 | 121 | singularize(c("woman", "man", "child", "tooth", "foot", "person", "leaf")) 122 | ## [1] "woman" "man" "child" "tooth" "foot" "person" "leaf" 123 | 124 | add_irregular_rule("irregular", "regular") 125 | 126 | pluralize("irregular") 127 | ## [1] "regular" 128 | 129 | pluralize("paper") 130 | ## [1] "papers" 131 | 132 | add_uncountable_rule("paper") 133 | 134 | pluralize("paper") 135 | ## [1] "paper" 136 | ``` 137 | 138 | ### pluralize Metrics 139 | 140 | | Lang | \# Files | (%) | LoC | (%) | Blank lines | (%) | \# Lines | (%) | 141 | | :--- | -------: | --: | --: | ---: | ----------: | ---: | -------: | ---: | 142 | | HTML | 1 | 0.1 | 367 | 0.79 | 33 | 0.28 | 2 | 0.01 | 143 | | Rmd | 2 | 0.2 | 58 | 0.12 | 72 | 0.61 | 71 | 0.39 | 144 | | R | 7 | 0.7 | 40 | 0.09 | 13 | 0.11 | 108 | 0.60 | 145 | 146 | ## Code of Conduct 147 | 148 | Please note that this project is released with a Contributor Code of 149 | Conduct. By participating in this project you agree to abide by its 150 | terms. 151 | -------------------------------------------------------------------------------- /inst/js/pluralize.js: -------------------------------------------------------------------------------- 1 | /* global define */ 2 | 3 | (function (root, pluralize) { 4 | /* istanbul ignore else */ 5 | if (typeof require === 'function' && typeof exports === 'object' && typeof module === 'object') { 6 | // Node. 7 | module.exports = pluralize(); 8 | } else if (typeof define === 'function' && define.amd) { 9 | // AMD, registers as an anonymous module. 10 | define(function () { 11 | return pluralize(); 12 | }); 13 | } else { 14 | // Browser global. 15 | root.pluralize = pluralize(); 16 | } 17 | })(this, function () { 18 | // Rule storage - pluralize and singularize need to be run sequentially, 19 | // while other rules can be optimized using an object for instant lookups. 20 | var pluralRules = []; 21 | var singularRules = []; 22 | var uncountables = {}; 23 | var irregularPlurals = {}; 24 | var irregularSingles = {}; 25 | 26 | /** 27 | * Sanitize a pluralization rule to a usable regular expression. 28 | * 29 | * @param {(RegExp|string)} rule 30 | * @return {RegExp} 31 | */ 32 | function sanitizeRule (rule) { 33 | if (typeof rule === 'string') { 34 | return new RegExp('^' + rule + '$', 'i'); 35 | } 36 | 37 | return rule; 38 | } 39 | 40 | /** 41 | * Pass in a word token to produce a function that can replicate the case on 42 | * another word. 43 | * 44 | * @param {string} word 45 | * @param {string} token 46 | * @return {Function} 47 | */ 48 | function restoreCase (word, token) { 49 | // Tokens are an exact match. 50 | if (word === token) return token; 51 | 52 | // Lower cased words. E.g. "hello". 53 | if (word === word.toLowerCase()) return token.toLowerCase(); 54 | 55 | // Upper cased words. E.g. "WHISKY". 56 | if (word === word.toUpperCase()) return token.toUpperCase(); 57 | 58 | // Title cased words. E.g. "Title". 59 | if (word[0] === word[0].toUpperCase()) { 60 | return token.charAt(0).toUpperCase() + token.substr(1).toLowerCase(); 61 | } 62 | 63 | // Lower cased words. E.g. "test". 64 | return token.toLowerCase(); 65 | } 66 | 67 | /** 68 | * Interpolate a regexp string. 69 | * 70 | * @param {string} str 71 | * @param {Array} args 72 | * @return {string} 73 | */ 74 | function interpolate (str, args) { 75 | return str.replace(/\$(\d{1,2})/g, function (match, index) { 76 | return args[index] || ''; 77 | }); 78 | } 79 | 80 | /** 81 | * Replace a word using a rule. 82 | * 83 | * @param {string} word 84 | * @param {Array} rule 85 | * @return {string} 86 | */ 87 | function replace (word, rule) { 88 | return word.replace(rule[0], function (match, index) { 89 | var result = interpolate(rule[1], arguments); 90 | 91 | if (match === '') { 92 | return restoreCase(word[index - 1], result); 93 | } 94 | 95 | return restoreCase(match, result); 96 | }); 97 | } 98 | 99 | /** 100 | * Sanitize a word by passing in the word and sanitization rules. 101 | * 102 | * @param {string} token 103 | * @param {string} word 104 | * @param {Array} rules 105 | * @return {string} 106 | */ 107 | function sanitizeWord (token, word, rules) { 108 | // Empty string or doesn't need fixing. 109 | if (!token.length || uncountables.hasOwnProperty(token)) { 110 | return word; 111 | } 112 | 113 | var len = rules.length; 114 | 115 | // Iterate over the sanitization rules and use the first one to match. 116 | while (len--) { 117 | var rule = rules[len]; 118 | 119 | if (rule[0].test(word)) return replace(word, rule); 120 | } 121 | 122 | return word; 123 | } 124 | 125 | /** 126 | * Replace a word with the updated word. 127 | * 128 | * @param {Object} replaceMap 129 | * @param {Object} keepMap 130 | * @param {Array} rules 131 | * @return {Function} 132 | */ 133 | function replaceWord (replaceMap, keepMap, rules) { 134 | return function (word) { 135 | // Get the correct token and case restoration functions. 136 | var token = word.toLowerCase(); 137 | 138 | // Check against the keep object map. 139 | if (keepMap.hasOwnProperty(token)) { 140 | return restoreCase(word, token); 141 | } 142 | 143 | // Check against the replacement map for a direct word replacement. 144 | if (replaceMap.hasOwnProperty(token)) { 145 | return restoreCase(word, replaceMap[token]); 146 | } 147 | 148 | // Run all the rules against the word. 149 | return sanitizeWord(token, word, rules); 150 | }; 151 | } 152 | 153 | /** 154 | * Check if a word is part of the map. 155 | */ 156 | function checkWord (replaceMap, keepMap, rules, bool) { 157 | return function (word) { 158 | var token = word.toLowerCase(); 159 | 160 | if (keepMap.hasOwnProperty(token)) return true; 161 | if (replaceMap.hasOwnProperty(token)) return false; 162 | 163 | return sanitizeWord(token, token, rules) === token; 164 | }; 165 | } 166 | 167 | /** 168 | * Pluralize or singularize a word based on the passed in count. 169 | * 170 | * @param {string} word The word to pluralize 171 | * @param {number} count How many of the word exist 172 | * @param {boolean} inclusive Whether to prefix with the number (e.g. 3 ducks) 173 | * @return {string} 174 | */ 175 | function pluralize (word, count, inclusive) { 176 | var pluralized = count === 1 177 | ? pluralize.singular(word) : pluralize.plural(word); 178 | 179 | return (inclusive ? count + ' ' : '') + pluralized; 180 | } 181 | 182 | /** 183 | * Pluralize a word. 184 | * 185 | * @type {Function} 186 | */ 187 | pluralize.plural = replaceWord( 188 | irregularSingles, irregularPlurals, pluralRules 189 | ); 190 | 191 | /** 192 | * Check if a word is plural. 193 | * 194 | * @type {Function} 195 | */ 196 | pluralize.isPlural = checkWord( 197 | irregularSingles, irregularPlurals, pluralRules 198 | ); 199 | 200 | /** 201 | * Singularize a word. 202 | * 203 | * @type {Function} 204 | */ 205 | pluralize.singular = replaceWord( 206 | irregularPlurals, irregularSingles, singularRules 207 | ); 208 | 209 | /** 210 | * Check if a word is singular. 211 | * 212 | * @type {Function} 213 | */ 214 | pluralize.isSingular = checkWord( 215 | irregularPlurals, irregularSingles, singularRules 216 | ); 217 | 218 | /** 219 | * Add a pluralization rule to the collection. 220 | * 221 | * @param {(string|RegExp)} rule 222 | * @param {string} replacement 223 | */ 224 | pluralize.addPluralRule = function (rule, replacement) { 225 | pluralRules.push([sanitizeRule(rule), replacement]); 226 | }; 227 | 228 | /** 229 | * Add a singularization rule to the collection. 230 | * 231 | * @param {(string|RegExp)} rule 232 | * @param {string} replacement 233 | */ 234 | pluralize.addSingularRule = function (rule, replacement) { 235 | singularRules.push([sanitizeRule(rule), replacement]); 236 | }; 237 | 238 | /** 239 | * Add an uncountable word rule. 240 | * 241 | * @param {(string|RegExp)} word 242 | */ 243 | pluralize.addUncountableRule = function (word) { 244 | if (typeof word === 'string') { 245 | uncountables[word.toLowerCase()] = true; 246 | return; 247 | } 248 | 249 | // Set singular and plural references for the word. 250 | pluralize.addPluralRule(word, '$0'); 251 | pluralize.addSingularRule(word, '$0'); 252 | }; 253 | 254 | /** 255 | * Add an irregular word definition. 256 | * 257 | * @param {string} single 258 | * @param {string} plural 259 | */ 260 | pluralize.addIrregularRule = function (single, plural) { 261 | plural = plural.toLowerCase(); 262 | single = single.toLowerCase(); 263 | 264 | irregularSingles[single] = plural; 265 | irregularPlurals[plural] = single; 266 | }; 267 | 268 | /** 269 | * Irregular rules. 270 | */ 271 | [ 272 | // Pronouns. 273 | ['I', 'we'], 274 | ['me', 'us'], 275 | ['he', 'they'], 276 | ['she', 'they'], 277 | ['them', 'them'], 278 | ['myself', 'ourselves'], 279 | ['yourself', 'yourselves'], 280 | ['itself', 'themselves'], 281 | ['herself', 'themselves'], 282 | ['himself', 'themselves'], 283 | ['themself', 'themselves'], 284 | ['is', 'are'], 285 | ['was', 'were'], 286 | ['has', 'have'], 287 | ['this', 'these'], 288 | ['that', 'those'], 289 | // Words ending in with a consonant and `o`. 290 | ['echo', 'echoes'], 291 | ['dingo', 'dingoes'], 292 | ['volcano', 'volcanoes'], 293 | ['tornado', 'tornadoes'], 294 | ['torpedo', 'torpedoes'], 295 | // Ends with `us`. 296 | ['genus', 'genera'], 297 | ['viscus', 'viscera'], 298 | // Ends with `ma`. 299 | ['stigma', 'stigmata'], 300 | ['stoma', 'stomata'], 301 | ['dogma', 'dogmata'], 302 | ['lemma', 'lemmata'], 303 | ['schema', 'schemata'], 304 | ['anathema', 'anathemata'], 305 | // Other irregular rules. 306 | ['ox', 'oxen'], 307 | ['axe', 'axes'], 308 | ['die', 'dice'], 309 | ['yes', 'yeses'], 310 | ['foot', 'feet'], 311 | ['eave', 'eaves'], 312 | ['goose', 'geese'], 313 | ['tooth', 'teeth'], 314 | ['quiz', 'quizzes'], 315 | ['human', 'humans'], 316 | ['proof', 'proofs'], 317 | ['carve', 'carves'], 318 | ['valve', 'valves'], 319 | ['looey', 'looies'], 320 | ['thief', 'thieves'], 321 | ['groove', 'grooves'], 322 | ['pickaxe', 'pickaxes'], 323 | ['passerby', 'passersby'] 324 | ].forEach(function (rule) { 325 | return pluralize.addIrregularRule(rule[0], rule[1]); 326 | }); 327 | 328 | /** 329 | * Pluralization rules. 330 | */ 331 | [ 332 | [/s?$/i, 's'], 333 | [/[^\u0000-\u007F]$/i, '$0'], 334 | [/([^aeiou]ese)$/i, '$1'], 335 | [/(ax|test)is$/i, '$1es'], 336 | [/(alias|[^aou]us|t[lm]as|gas|ris)$/i, '$1es'], 337 | [/(e[mn]u)s?$/i, '$1s'], 338 | [/([^l]ias|[aeiou]las|[ejzr]as|[iu]am)$/i, '$1'], 339 | [/(alumn|syllab|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i, '$1i'], 340 | [/(alumn|alg|vertebr)(?:a|ae)$/i, '$1ae'], 341 | [/(seraph|cherub)(?:im)?$/i, '$1im'], 342 | [/(her|at|gr)o$/i, '$1oes'], 343 | [/(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|automat|quor)(?:a|um)$/i, '$1a'], 344 | [/(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)(?:a|on)$/i, '$1a'], 345 | [/sis$/i, 'ses'], 346 | [/(?:(kni|wi|li)fe|(ar|l|ea|eo|oa|hoo)f)$/i, '$1$2ves'], 347 | [/([^aeiouy]|qu)y$/i, '$1ies'], 348 | [/([^ch][ieo][ln])ey$/i, '$1ies'], 349 | [/(x|ch|ss|sh|zz)$/i, '$1es'], 350 | [/(matr|cod|mur|sil|vert|ind|append)(?:ix|ex)$/i, '$1ices'], 351 | [/\b((?:tit)?m|l)(?:ice|ouse)$/i, '$1ice'], 352 | [/(pe)(?:rson|ople)$/i, '$1ople'], 353 | [/(child)(?:ren)?$/i, '$1ren'], 354 | [/eaux$/i, '$0'], 355 | [/m[ae]n$/i, 'men'], 356 | ['thou', 'you'] 357 | ].forEach(function (rule) { 358 | return pluralize.addPluralRule(rule[0], rule[1]); 359 | }); 360 | 361 | /** 362 | * Singularization rules. 363 | */ 364 | [ 365 | [/s$/i, ''], 366 | [/(ss)$/i, '$1'], 367 | [/(wi|kni|(?:after|half|high|low|mid|non|night|[^\w]|^)li)ves$/i, '$1fe'], 368 | [/(ar|(?:wo|[ae])l|[eo][ao])ves$/i, '$1f'], 369 | [/ies$/i, 'y'], 370 | [/(dg|ss|ois|lk|ok|wn|mb|th|ch|ec|oal|is|ck|ix|sser|ts|wb)ies$/i, '$1ie'], 371 | [/\b(l|(?:neck|cross|hog|aun)?t|coll|faer|food|gen|goon|group|hipp|junk|vegg|(?:pork)?p|charl|calor|cut)ies$/i, '$1ie'], 372 | [/\b(mon|smil)ies$/i, '$1ey'], 373 | [/\b((?:tit)?m|l)ice$/i, '$1ouse'], 374 | [/(seraph|cherub)im$/i, '$1'], 375 | [/(x|ch|ss|sh|zz|tto|go|cho|alias|[^aou]us|t[lm]as|gas|(?:her|at|gr)o|[aeiou]ris)(?:es)?$/i, '$1'], 376 | [/(analy|diagno|parenthe|progno|synop|the|empha|cri|ne)(?:sis|ses)$/i, '$1sis'], 377 | [/(movie|twelve|abuse|e[mn]u)s$/i, '$1'], 378 | [/(test)(?:is|es)$/i, '$1is'], 379 | [/(alumn|syllab|vir|radi|nucle|fung|cact|stimul|termin|bacill|foc|uter|loc|strat)(?:us|i)$/i, '$1us'], 380 | [/(agend|addend|millenni|dat|extrem|bacteri|desiderat|strat|candelabr|errat|ov|symposi|curricul|quor)a$/i, '$1um'], 381 | [/(apheli|hyperbat|periheli|asyndet|noumen|phenomen|criteri|organ|prolegomen|hedr|automat)a$/i, '$1on'], 382 | [/(alumn|alg|vertebr)ae$/i, '$1a'], 383 | [/(cod|mur|sil|vert|ind)ices$/i, '$1ex'], 384 | [/(matr|append)ices$/i, '$1ix'], 385 | [/(pe)(rson|ople)$/i, '$1rson'], 386 | [/(child)ren$/i, '$1'], 387 | [/(eau)x?$/i, '$1'], 388 | [/men$/i, 'man'] 389 | ].forEach(function (rule) { 390 | return pluralize.addSingularRule(rule[0], rule[1]); 391 | }); 392 | 393 | /** 394 | * Uncountable rules. 395 | */ 396 | [ 397 | // Singular words with no plurals. 398 | 'adulthood', 399 | 'advice', 400 | 'agenda', 401 | 'aid', 402 | 'aircraft', 403 | 'alcohol', 404 | 'ammo', 405 | 'analytics', 406 | 'anime', 407 | 'athletics', 408 | 'audio', 409 | 'bison', 410 | 'blood', 411 | 'bream', 412 | 'buffalo', 413 | 'butter', 414 | 'carp', 415 | 'cash', 416 | 'chassis', 417 | 'chess', 418 | 'clothing', 419 | 'cod', 420 | 'commerce', 421 | 'cooperation', 422 | 'corps', 423 | 'debris', 424 | 'diabetes', 425 | 'digestion', 426 | 'elk', 427 | 'energy', 428 | 'equipment', 429 | 'excretion', 430 | 'expertise', 431 | 'firmware', 432 | 'flounder', 433 | 'fun', 434 | 'gallows', 435 | 'garbage', 436 | 'graffiti', 437 | 'hardware', 438 | 'headquarters', 439 | 'health', 440 | 'herpes', 441 | 'highjinks', 442 | 'homework', 443 | 'housework', 444 | 'information', 445 | 'jeans', 446 | 'justice', 447 | 'kudos', 448 | 'labour', 449 | 'literature', 450 | 'machinery', 451 | 'mackerel', 452 | 'mail', 453 | 'media', 454 | 'mews', 455 | 'moose', 456 | 'music', 457 | 'mud', 458 | 'manga', 459 | 'news', 460 | 'only', 461 | 'personnel', 462 | 'pike', 463 | 'plankton', 464 | 'pliers', 465 | 'police', 466 | 'pollution', 467 | 'premises', 468 | 'rain', 469 | 'research', 470 | 'rice', 471 | 'salmon', 472 | 'scissors', 473 | 'series', 474 | 'sewage', 475 | 'shambles', 476 | 'shrimp', 477 | 'software', 478 | 'staff', 479 | 'swine', 480 | 'tennis', 481 | 'traffic', 482 | 'transportation', 483 | 'trout', 484 | 'tuna', 485 | 'wealth', 486 | 'welfare', 487 | 'whiting', 488 | 'wildebeest', 489 | 'wildlife', 490 | 'you', 491 | /pok[eé]mon$/i, 492 | // Regexes. 493 | /[^aeiou]ese$/i, // "chinese", "japanese" 494 | /deer$/i, // "deer", "reindeer" 495 | /fish$/i, // "fish", "blowfish", "angelfish" 496 | /measles$/i, 497 | /o[iu]s$/i, // "carnivorous" 498 | /pox$/i, // "chickpox", "smallpox" 499 | /sheep$/i 500 | ].forEach(pluralize.addUncountableRule); 501 | 502 | return pluralize; 503 | }); 504 | --------------------------------------------------------------------------------