pax_global_header00006660000000000000000000000064143761506130014520gustar00rootroot0000000000000052 comment=6ae77794e62b2755d42b9dbdf27ec7f69ac5a864 flect-1.0.2/000077500000000000000000000000001437615061300126155ustar00rootroot00000000000000flect-1.0.2/.github/000077500000000000000000000000001437615061300141555ustar00rootroot00000000000000flect-1.0.2/.github/FUNDING.yml000066400000000000000000000001221437615061300157650ustar00rootroot00000000000000# These are supported funding model platforms github: markbates patreon: buffalo flect-1.0.2/.github/workflows/000077500000000000000000000000001437615061300162125ustar00rootroot00000000000000flect-1.0.2/.github/workflows/standard-go-test.yml000066400000000000000000000003061437615061300221140ustar00rootroot00000000000000name: Standard Test on: push: branches: [ main ] pull_request: jobs: call-standard-test: name: Test uses: gobuffalo/.github/.github/workflows/go-test.yml@v1 secrets: inherit flect-1.0.2/.github/workflows/standard-stale.yml000066400000000000000000000003141437615061300216410ustar00rootroot00000000000000name: Standard Autocloser on: schedule: - cron: "30 1 * * *" jobs: call-standard-autocloser: name: Autocloser uses: gobuffalo/.github/.github/workflows/stale.yml@v1 secrets: inherit flect-1.0.2/.gitignore000066400000000000000000000003761437615061300146130ustar00rootroot00000000000000*.log .DS_Store doc tmp pkg *.gem *.pid coverage coverage.data build/* *.pbxuser *.mode1v3 .svn profile .console_history .sass-cache/* .rake_tasks~ *.log.lck solr/ .jhw-cache/ jhw.* *.sublime* node_modules/ dist/ generated/ .vendor/ bin/* gin-bin .idea/ flect-1.0.2/.gometalinter.json000066400000000000000000000002221437615061300162540ustar00rootroot00000000000000{ "Enable": ["vet", "golint", "goimports", "deadcode", "gotype", "ineffassign", "misspell", "nakedret", "unconvert", "megacheck", "varcheck"] } flect-1.0.2/LICENSE000066400000000000000000000020651437615061300136250ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2019 Mark Bates Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. flect-1.0.2/Makefile000066400000000000000000000016011437615061300142530ustar00rootroot00000000000000TAGS ?= "" GO_BIN ?= "go" install: $(GO_BIN) install -tags ${TAGS} -v . make tidy tidy: ifeq ($(GO111MODULE),on) $(GO_BIN) mod tidy else echo skipping go mod tidy endif deps: $(GO_BIN) get -tags ${TAGS} -t ./... make tidy build: $(GO_BIN) build -v . make tidy test: $(GO_BIN) test -cover -tags ${TAGS} ./... make tidy ci-deps: $(GO_BIN) get -tags ${TAGS} -t ./... ci-test: $(GO_BIN) test -tags ${TAGS} -race ./... lint: go get github.com/golangci/golangci-lint/cmd/golangci-lint golangci-lint run --enable-all make tidy update: ifeq ($(GO111MODULE),on) rm go.* $(GO_BIN) mod init $(GO_BIN) mod tidy else $(GO_BIN) get -u -tags ${TAGS} endif make test make install make tidy release-test: $(GO_BIN) test -tags ${TAGS} -race ./... make tidy release: $(GO_BIN) get github.com/gobuffalo/release make tidy release -y -f version.go --skip-packr make tidy flect-1.0.2/README.md000066400000000000000000000063651437615061300141060ustar00rootroot00000000000000# Flect [![Go Reference](https://pkg.go.dev/badge/github.com/gobuffalo/flect.svg)](https://pkg.go.dev/github.com/gobuffalo/flect) [![Standard Test](https://github.com/gobuffalo/flect/actions/workflows/standard-go-test.yml/badge.svg)](https://github.com/gobuffalo/flect/actions/workflows/standard-go-test.yml) [![Go Report Card](https://goreportcard.com/badge/github.com/gobuffalo/flect)](https://goreportcard.com/report/github.com/gobuffalo/flect) This is a new inflection engine to replace [https://github.com/markbates/inflect](https://github.com/markbates/inflect) designed to be more modular, more readable, and easier to fix issues on than the original. Flect provides word inflection features such as `Singularize` and `Pluralize` for English nouns and text utility features such as `Camelize`, `Capitalize`, `Humanize`, and more. Due to the flexibly-complex nature of English noun inflection, it is almost impossible to cover all exceptions (such as identical/irregular plural). With this reason along with the main purpose of Flect, which is to make it easy to develop web application in Go, Flect has limitations with its own rules. * It covers regular rule (adding -s or -es and of the word) * It covers well-known irregular rules (such as -is to -es, -f to -ves, etc) * https://en.wiktionary.org/wiki/Appendix:English_irregular_nouns#Rules * It covers well-known irregular words (such as children, men, etc) * If a word can be countable and uncountable like milk or time, it will be treated as countable. * If a word has more than one plural forms, which means it has at least one irregular plural, we tried to find most popular one. (The selected plural could be odd to you, please feel free to open an issue with back data) * For example, we selected "stadiums" over "stadia", "dwarfs" over "dwarves" * One or combination of en.wiktionary.org, britannica.com, and trends.google.com are used to check the recent usage trends. * However, we cannot cover all cases and some of our cases could not fit with your situation. You can override the default with functions such as `InsertPlural()`, `InsertSingular()`, or `LoadInfrections()`. * If you have a json file named `inflections.json` in your application root, the file will be automatically loaded as your custom inflection dictionary. ## Installation ```console $ go get github.com/gobuffalo/flect ``` ## Packages ### `github.com/gobuffalo/flect` The `github.com/gobuffalo/flect` package contains "basic" inflection tools, like pluralization, singularization, etc... #### The `Ident` Type In addition to helpful methods that take in a `string` and return a `string`, there is an `Ident` type that can be used to create new, custom, inflection rules. The `Ident` type contains two fields. * `Original` - This is the original `string` that was used to create the `Ident` * `Parts` - This is a `[]string` that represents all of the "parts" of the string, that have been split apart, making the segments easier to work with Examples of creating new inflection rules using `Ident` can be found in the `github.com/gobuffalo/flect/name` package. ### `github.com/gobuffalo/flect/name` The `github.com/gobuffalo/flect/name` package contains more "business" inflection rules like creating proper names, table names, etc... flect-1.0.2/SHOULDERS.md000066400000000000000000000014571437615061300145160ustar00rootroot00000000000000# Flect Stands on the Shoulders of Giants Flect does not try to reinvent the wheel! Instead, it uses the already great wheels developed by the Go community and puts them all together in the best way possible. Without these giants, this project would not be possible. Please make sure to check them out and thank them for all of their hard work. Thank you to the following **GIANTS**: * [github.com/davecgh/go-spew](https://godoc.org/github.com/davecgh/go-spew) * [github.com/pmezard/go-difflib](https://godoc.org/github.com/pmezard/go-difflib) * [github.com/stretchr/objx](https://godoc.org/github.com/stretchr/objx) * [github.com/stretchr/testify](https://godoc.org/github.com/stretchr/testify) * [gopkg.in/check.v1](https://godoc.org/gopkg.in/check.v1) * [gopkg.in/yaml.v3](https://godoc.org/gopkg.in/yaml.v3) flect-1.0.2/acronyms.go000066400000000000000000000045501437615061300150030ustar00rootroot00000000000000package flect import "sync" var acronymsMoot = &sync.RWMutex{} var baseAcronyms = map[string]bool{ "OK": true, "UTF8": true, "HTML": true, "JSON": true, "JWT": true, "ID": true, "UUID": true, "SQL": true, "ACK": true, "ACL": true, "ADSL": true, "AES": true, "ANSI": true, "API": true, "ARP": true, "ATM": true, "BGP": true, "BSS": true, "CCITT": true, "CHAP": true, "CIDR": true, "CIR": true, "CLI": true, "CPE": true, "CPU": true, "CRC": true, "CRT": true, "CSMA": true, "CMOS": true, "DCE": true, "DEC": true, "DES": true, "DHCP": true, "DNS": true, "DRAM": true, "DSL": true, "DSLAM": true, "DTE": true, "DMI": true, "EHA": true, "EIA": true, "EIGRP": true, "EOF": true, "ESS": true, "FCC": true, "FCS": true, "FDDI": true, "FTP": true, "GBIC": true, "gbps": true, "GEPOF": true, "HDLC": true, "HTTP": true, "HTTPS": true, "IANA": true, "ICMP": true, "IDF": true, "IDS": true, "IEEE": true, "IETF": true, "IMAP": true, "IP": true, "IPS": true, "ISDN": true, "ISP": true, "kbps": true, "LACP": true, "LAN": true, "LAPB": true, "LAPF": true, "LLC": true, "MAC": true, "Mbps": true, "MC": true, "MDF": true, "MIB": true, "MoCA": true, "MPLS": true, "MTU": true, "NAC": true, "NAT": true, "NBMA": true, "NIC": true, "NRZ": true, "NRZI": true, "NVRAM": true, "OSI": true, "OSPF": true, "OUI": true, "PAP": true, "PAT": true, "PC": true, "PIM": true, "PCM": true, "PDU": true, "POP3": true, "POTS": true, "PPP": true, "PPTP": true, "PTT": true, "PVST": true, "RAM": true, "RARP": true, "RFC": true, "RIP": true, "RLL": true, "ROM": true, "RSTP": true, "RTP": true, "RCP": true, "SDLC": true, "SFD": true, "SFP": true, "SLARP": true, "SLIP": true, "SMTP": true, "SNA": true, "SNAP": true, "SNMP": true, "SOF": true, "SRAM": true, "SSH": true, "SSID": true, "STP": true, "SYN": true, "TDM": true, "TFTP": true, "TIA": true, "TOFU": true, "UDP": true, "URL": true, "URI": true, "USB": true, "UTP": true, "VC": true, "VLAN": true, "VLSM": true, "VPN": true, "W3C": true, "WAN": true, "WEP": true, "WiFi": true, "WPA": true, "WWW": true, } flect-1.0.2/camelize.go000066400000000000000000000015031437615061300147340ustar00rootroot00000000000000package flect import ( "strings" "unicode" ) // Camelize returns a camelize version of a string // bob dylan = bobDylan // widget_id = widgetID // WidgetID = widgetID func Camelize(s string) string { return New(s).Camelize().String() } // Camelize returns a camelize version of a string // bob dylan = bobDylan // widget_id = widgetID // WidgetID = widgetID func (i Ident) Camelize() Ident { var out []string for i, part := range i.Parts { var x string var capped bool for _, c := range part { if unicode.IsLetter(c) || unicode.IsDigit(c) { if i == 0 { x += string(unicode.ToLower(c)) continue } if !capped { capped = true x += string(unicode.ToUpper(c)) continue } x += string(c) } } if x != "" { out = append(out, x) } } return New(strings.Join(out, "")) } flect-1.0.2/camelize_test.go000066400000000000000000000017271437615061300160030ustar00rootroot00000000000000package flect import ( "testing" "github.com/stretchr/testify/require" ) func Test_Camelize(t *testing.T) { table := []tt{ {"", ""}, {"bob dylan", "bobDylan"}, {"id", "id"}, {"ID", "id"}, {"widgetID", "widgetID"}, {"widget_ID", "widgetID"}, {"Widget_ID", "widgetID"}, {"Widget_Id", "widgetID"}, {"Widget_id", "widgetID"}, {"Nice to see you!", "niceToSeeYou"}, {"*hello*", "hello"}, {"i've read a book! have you?", "iveReadABookHaveYou"}, {"This is `code` ok", "thisIsCodeOK"}, {"foo_bar", "fooBar"}, {"admin/widget", "adminWidget"}, {"widget", "widget"}, {"widgets", "widgets"}, {"status", "status"}, {"Statuses", "statuses"}, {"statuses", "statuses"}, {"People", "people"}, {"people", "people"}, {"ip_address", "ipAddress"}, {"some_url", "someURL"}, } for _, tt := range table { t.Run(tt.act, func(st *testing.T) { r := require.New(st) r.Equal(tt.exp, Camelize(tt.act)) r.Equal(tt.exp, Camelize(tt.exp)) }) } } flect-1.0.2/capitalize.go000066400000000000000000000010021437615061300152620ustar00rootroot00000000000000package flect import "unicode" // Capitalize will cap the first letter of string // user = User // bob dylan = Bob dylan // widget_id = Widget_id func Capitalize(s string) string { return New(s).Capitalize().String() } // Capitalize will cap the first letter of string // user = User // bob dylan = Bob dylan // widget_id = Widget_id func (i Ident) Capitalize() Ident { if len(i.Parts) == 0 { return New("") } runes := []rune(i.Original) runes[0] = unicode.ToTitle(runes[0]) return New(string(runes)) } flect-1.0.2/capitalize_test.go000066400000000000000000000010511437615061300163250ustar00rootroot00000000000000package flect import ( "testing" "github.com/stretchr/testify/require" ) func Test_Capitalize(t *testing.T) { table := []tt{ {"", ""}, {"foo", "Foo"}, {"bob dylan", "Bob dylan"}, {"WidgetID", "WidgetID"}, {"widget_id", "Widget_id"}, {"widget_ID", "Widget_ID"}, {"widget ID", "Widget ID"}, {"гофер", "Гофер"}, // it's "gopher" in Ukrainian } for _, tt := range table { t.Run(tt.act, func(st *testing.T) { r := require.New(st) r.Equal(tt.exp, Capitalize(tt.act)) r.Equal(tt.exp, Capitalize(tt.exp)) }) } } flect-1.0.2/custom_data.go000066400000000000000000000036451437615061300154570ustar00rootroot00000000000000package flect import ( "bytes" "encoding/json" "fmt" "io" "io/ioutil" "os" "path/filepath" "strings" ) func init() { loadCustomData("inflections.json", "INFLECT_PATH", "could not read inflection file", LoadInflections) loadCustomData("acronyms.json", "ACRONYMS_PATH", "could not read acronyms file", LoadAcronyms) } //CustomDataParser are functions that parse data like acronyms or //plurals in the shape of a io.Reader it receives. type CustomDataParser func(io.Reader) error func loadCustomData(defaultFile, env, readErrorMessage string, parser CustomDataParser) { pwd, _ := os.Getwd() path, found := os.LookupEnv(env) if !found { path = filepath.Join(pwd, defaultFile) } if _, err := os.Stat(path); err != nil { return } b, err := ioutil.ReadFile(path) if err != nil { fmt.Printf("%s %s (%s)\n", readErrorMessage, path, err) return } if err = parser(bytes.NewReader(b)); err != nil { fmt.Println(err) } } //LoadAcronyms loads rules from io.Reader param func LoadAcronyms(r io.Reader) error { m := []string{} err := json.NewDecoder(r).Decode(&m) if err != nil { return fmt.Errorf("could not decode acronyms JSON from reader: %s", err) } acronymsMoot.Lock() defer acronymsMoot.Unlock() for _, acronym := range m { baseAcronyms[acronym] = true } return nil } //LoadInflections loads rules from io.Reader param func LoadInflections(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) } pluralMoot.Lock() defer pluralMoot.Unlock() singularMoot.Lock() defer singularMoot.Unlock() for s, p := range m { if strings.Contains(s, " ") || strings.Contains(p, " ") { // flect works with parts, so multi-words should not be allowed return fmt.Errorf("inflection elements should be a single word") } singleToPlural[s] = p pluralToSingle[p] = s } return nil } flect-1.0.2/dasherize.go000066400000000000000000000013751437615061300151300ustar00rootroot00000000000000package flect import ( "strings" "unicode" ) // Dasherize returns an alphanumeric, lowercased, dashed string // Donald E. Knuth = donald-e-knuth // Test with + sign = test-with-sign // admin/WidgetID = admin-widget-id func Dasherize(s string) string { return New(s).Dasherize().String() } // Dasherize returns an alphanumeric, lowercased, dashed string // Donald E. Knuth = donald-e-knuth // Test with + sign = test-with-sign // admin/WidgetID = admin-widget-id func (i Ident) Dasherize() Ident { var parts []string for _, part := range i.Parts { var x string for _, c := range part { if unicode.IsLetter(c) || unicode.IsDigit(c) { x += string(c) } } parts = xappend(parts, x) } return New(strings.ToLower(strings.Join(parts, "-"))) } flect-1.0.2/dasherize_test.go000066400000000000000000000013671437615061300161700ustar00rootroot00000000000000package flect import ( "testing" "github.com/stretchr/testify/require" ) func Test_Dasherize(t *testing.T) { table := []tt{ {"", ""}, {"admin/WidgetID", "admin-widget-id"}, {"Donald E. Knuth", "donald-e-knuth"}, {"Random text with *(bad)* characters", "random-text-with-bad-characters"}, {"Trailing bad characters!@#", "trailing-bad-characters"}, {"!@#Leading bad characters", "leading-bad-characters"}, {"Squeeze separators", "squeeze-separators"}, {"Test with + sign", "test-with-sign"}, {"Test with malformed utf8 \251", "test-with-malformed-utf8"}, } for _, tt := range table { t.Run(tt.act, func(st *testing.T) { r := require.New(st) r.Equal(tt.exp, Dasherize(tt.act)) r.Equal(tt.exp, Dasherize(tt.exp)) }) } } flect-1.0.2/flect.go000066400000000000000000000014521437615061300142430ustar00rootroot00000000000000/* Package flect is a new inflection engine to replace [https://github.com/markbates/inflect](https://github.com/markbates/inflect) designed to be more modular, more readable, and easier to fix issues on than the original. */ package flect import ( "strings" "unicode" ) var spaces = []rune{'_', ' ', ':', '-', '/'} func isSpace(c rune) bool { for _, r := range spaces { if r == c { return true } } return unicode.IsSpace(c) } func xappend(a []string, ss ...string) []string { for _, s := range ss { s = strings.TrimSpace(s) for _, x := range spaces { s = strings.Trim(s, string(x)) } if _, ok := baseAcronyms[strings.ToUpper(s)]; ok { s = strings.ToUpper(s) } if s != "" { a = append(a, s) } } return a } func abs(x int) int { if x < 0 { return -x } return x } flect-1.0.2/flect_test.go000066400000000000000000000265271437615061300153140ustar00rootroot00000000000000package flect import ( "bytes" "encoding/json" "testing" "github.com/stretchr/testify/require" ) type tt struct { act string exp string } func Test_LoadInflections(t *testing.T) { r := require.New(t) m := map[string]string{ "baby": "bebe", "xyz": "zyx", } b, err := json.Marshal(m) r.NoError(err) r.NoError(LoadInflections(bytes.NewReader(b))) for k, v := range m { r.Equal(v, Pluralize(k)) r.Equal(v, Pluralize(v)) r.Equal(k, Singularize(k)) r.Equal(k, Singularize(v)) } } func Test_LoadInflectionsWrongSingular(t *testing.T) { r := require.New(t) m := map[string]string{ "a file": "files", } b, err := json.Marshal(m) r.NoError(err) r.Error(LoadInflections(bytes.NewReader(b))) } func Test_LoadInflectionsWrongPlural(t *testing.T) { r := require.New(t) m := map[string]string{ "beatle": "the beatles", } b, err := json.Marshal(m) r.NoError(err) r.Error(LoadInflections(bytes.NewReader(b))) } func Test_LoadAcronyms(t *testing.T) { r := require.New(t) m := []string{ "ACC", "TLC", "LSA", } b, err := json.Marshal(m) r.NoError(err) r.NoError(LoadAcronyms(bytes.NewReader(b))) for _, acronym := range m { r.True(baseAcronyms[acronym]) } } type dict struct { singular string plural string doSingularizeTest bool doPluralizeTest bool } var singlePluralAssertions = []dict{ {"", "", true, true}, {"Car", "Cars", true, true}, {"Boy", "Boys", true, true}, {"GoodBoy", "GoodBoys", true, true}, {"Axis", "Axes", true, true}, {"Child", "Children", true, true}, {"GoodChild", "GoodChildren", true, true}, {"node_child", "node_children", true, true}, {"SmartPerson", "SmartPeople", true, true}, {"great_person", "great_people", true, true}, {"salesperson", "salespeople", true, true}, {"custom_field", "custom_fields", true, true}, {"funky jeans", "funky jeans", true, true}, {"payment_information", "payment_information", true, true}, {"sportsEquipment", "sportsEquipment", true, true}, {"status_code", "status_codes", true, true}, {"user_custom_field", "user_custom_fields", true, true}, {"SuperbOx", "SuperbOxen", true, true}, {"WildOx", "WildOxen", true, true}, {"wild_ox", "wild_oxen", true, true}, {"box", "boxes", true, true}, {"fox", "foxes", true, true}, {"comment", "comments", true, true}, {"edge", "edges", true, true}, {"equipment", "equipment", true, true}, {"experience", "experiences", true, true}, {"fleet", "fleets", true, true}, {"foobar", "foobars", true, true}, {"mouse", "mice", true, true}, {"newsletter", "newsletters", true, true}, {"stack", "stacks", true, true}, {"user", "users", true, true}, {"woman", "women", true, true}, {"human", "humans", true, true}, {"spokesman", "spokesmen", true, true}, // Words that end in -f or -fe change -f or -fe to -ves // https://en.wiktionary.org/wiki/Category:English_irregular_plurals_ending_in_"-ves" {"calf", "calves", true, true}, {"dwarf", "dwarves", true, false}, // dwarfs looks popular than dwarves {"dwarf", "dwarfs", true, true}, {"elf", "elves", true, true}, {"half", "halves", true, true}, {"knife", "knives", true, true}, {"leaf", "leaves", true, true}, {"life", "lives", true, true}, {"loaf", "loaves", true, true}, {"safe", "saves", true, true}, {"scarf", "scarves", true, true}, {"self", "selves", true, true}, {"sheaf", "sheaves", true, true}, {"shelf", "shelves", true, true}, {"thief", "thieves", true, true}, {"wharf", "wharves", true, true}, {"wife", "wives", true, true}, {"wolf", "wolves", true, true}, {"beef", "beef", true, true}, {"belief", "beliefs", true, true}, {"chef", "chefs", true, true}, {"chief", "chiefs", true, true}, {"hoof", "hoofs", true, true}, {"kerchief", "kerchiefs", true, true}, {"roof", "roofs", true, true}, {"archive", "archives", true, true}, {"perspective", "perspectives", true, true}, // Words that end in -y preceded by a consonant change -y to -ies {"day", "days", true, true}, {"hobby", "hobbies", true, true}, {"agency", "agencies", true, true}, {"body", "bodies", true, true}, {"abbey", "abbeys", true, true}, {"jiffy", "jiffies", true, true}, {"geology", "geologies", true, true}, {"geography", "geographies", true, true}, {"cookie", "cookies", true, true}, {"sky", "skies", true, true}, {"supply", "supplies", true, true}, {"academy", "academies", true, true}, {"ebony", "ebonies", true, true}, {"boy", "boys", true, true}, {"copy", "copies", true, true}, {"category", "categories", true, true}, {"embassy", "embassies", true, true}, {"entity", "entities", true, true}, {"guy", "guys", true, true}, {"obsequy", "obsequies", true, true}, {"navy", "navies", true, true}, {"proxy", "proxies", true, true}, {"crazy", "crazies", true, true}, // Words from French that end in -u add an x {"aboideau", "aboideaux", true, true}, {"beau", "beaux", true, true}, {"château", "châteaux", true, true}, {"chateau", "chateaux", true, true}, {"fabliau", "fabliaux", true, true}, {"tableau", "tableaux", true, true}, {"bureau", "bureaus", true, true}, {"adieu", "adieux", true, true}, // Words from Greek that end in -on change -on to -a {"tetrahedron", "tetrahedra", true, true}, // Words from Latin that end in -um change -um to -a {"stadium", "stadiums", true, true}, {"stadium", "stadia", true, false}, {"aquarium", "aquaria", true, true}, {"auditorium", "auditoria", true, true}, {"bacterium", "bacteria", true, true}, {"pretorium", "pretoriums", true, true}, {"symposium", "symposia", true, true}, {"symposium", "symposiums", true, false}, {"amoebaeum", "amoebaea", true, true}, {"coliseum", "coliseums", true, true}, {"museum", "museums", true, true}, {"agenda", "agendas", true, true}, {"curriculum", "curriculums", true, true}, {"collum", "colla", true, true}, {"datum", "data", true, true}, {"erratum", "errata", true, true}, {"maximum", "maxima", true, true}, {"platinum", "platinums", true, true}, {"serum", "sera", true, true}, {"spectrum", "spectra", true, true}, // Words from Latin that end in -us change -us to -i or -era {"opera", "operas", true, true}, // Words from Latin that end in -a change -a to -ae {"alumna", "alumnae", true, true}, {"larva", "larvae", true, true}, {"minutia", "minutiae", true, true}, {"nebula", "nebulae", true, true}, {"vertebra", "vertebrae", true, true}, {"vita", "vitae", true, true}, {"antenna", "antennas", true, true}, {"formula", "formulas", true, true}, {"tuna", "tuna", true, true}, {"quota", "quotas", true, true}, {"vedalia", "vedalias", true, true}, {"media", "media", true, true}, // instead of mediae, popular case {"multimedia", "multimedia", true, true}, // Words that end in -ch, -o, -s, -sh, -x, -z {"lunch", "lunches", true, true}, {"search", "searches", true, true}, {"switch", "switches", true, true}, {"headache", "headaches", true, true}, // ch vs. che {"marsh", "marshes", true, true}, {"wish", "wishes", true, true}, {"bus", "buses", true, true}, // end with u + s, but no -us rule {"campus", "campuses", true, true}, {"caucus", "caucuses", true, true}, {"circus", "circuses", true, true}, {"plus", "pluses", true, true}, {"prometheus", "prometheuses", true, true}, {"status", "statuses", true, true}, {"virus", "viruses", true, true}, {"use", "uses", true, true}, // -use {"fuse", "fuses", true, true}, {"house", "houses", true, true}, {"spouse", "spouses", true, true}, {"quiz", "quizzes", true, true}, {"buzz", "buzzes", true, true}, {"blitz", "blitzes", true, true}, {"quartz", "quartzes", true, true}, {"topaz", "topazes", true, true}, {"waltz", "waltzes", true, true}, {"prize", "prizes", true, true}, {"access", "accesses", true, true}, {"process", "processes", true, true}, {"address", "addresses", true, true}, {"case", "cases", true, true}, {"database", "databases", true, true}, {"glimpse", "glimpses", true, true}, {"horse", "horses", true, true}, {"lapse", "lapses", true, true}, {"collapse", "collapses", true, true}, {"truss", "trusses", true, true}, {"portfolio", "portfolios", true, true}, // -o -os {"piano", "pianos", true, true}, // -ano -anos {"hello", "hellos", true, true}, // -lo -los {"buffalo", "buffaloes", true, true}, // -lo -loes {"photo", "photos", true, true}, // -to -tos {"potato", "potatoes", true, true}, // exception of -to -tos {"tomato", "tomatoes", true, true}, {"graffiti", "graffiti", true, true}, {"foo", "foos", true, true}, {"zoo", "zoos", true, true}, // Words from Latin that end in -ex change -ex to -ices // Words from Latin that end in -ix change -ix to -ices {"appendix", "appendices", true, true}, // -dix {"codex", "codices", true, true}, // -dex {"index", "indices", true, true}, {"bodice", "bodices", true, true}, // -dice {"helix", "helices", true, true}, // -lix {"complex", "complexes", true, true}, // -lex {"duplex", "duplexes", true, true}, {"accomplice", "accomplices", true, true}, // -lice {"slice", "slices", true, true}, {"matrix", "matrices", true, true}, // -trix {"justice", "justices", true, true}, // -tice {"lattice", "lattices", true, true}, {"notice", "notices", true, true}, {"apex", "apices", true, true}, // -pex {"spice", "spices", true, true}, // -pice {"device", "devices", true, true}, // -vice {"service", "services", true, true}, {"fix", "fixes", true, true}, // -ix {"sex", "sexes", true, true}, // -ex {"invoice", "invoices", true, true}, // gobuffalo/flect#61 {"voice", "voices", true, true}, {"choice", "choices", true, true}, // Words from Latin that end in -is change -is to -es {"axis", "axes", true, true}, {"tax", "taxes", true, true}, // not taxis {"eclipse", "eclipses", true, true}, {"ellipse", "ellipses", true, true}, {"ellipsis", "ellipses", false, true}, // pluralize only {"oasis", "oases", true, true}, {"thesis", "theses", true, true}, // word thesis {"hypothesis", "hypotheses", true, true}, {"parenthesis", "parentheses", true, true}, {"analysis", "analyses", true, true}, // suffix lysis {"antithesis", "antitheses", true, true}, {"diagnosis", "diagnoses", true, true}, // suffix gnosis {"prognosis", "prognoses", true, true}, {"synopsis", "synopses", true, true}, // suffix opsis {"synapse", "synapses", true, true}, {"waste", "wastes", true, true}, {"psi", "psis", true, true}, {"pepsi", "pepsis", true, true}, // Acronyms {"widget_uuid", "widget_uuids", true, true}, {"WidgetUUID", "WidgetUUIDs", true, true}, {"widgetUUID", "widgetUUIDs", true, true}, {"widgetUuid", "widgetUuids", true, true}, {"widget_UUID", "widget_UUIDs", true, true}, {"ID", "IDs", true, true}, {"IDS", "IDSes", true, true}, // id to ids (ID), ids to idses (IDS) is not supported {"api", "apis", true, true}, {"API", "APIs", true, true}, {"html", "htmls", true, true}, {"HTML", "HTMLs", true, true}, {"FYI", "FYIs", true, true}, {"LAN", "LANs", true, true}, {"ssh", "sshs", true, true}, // sh {"SSH", "SSHs", true, true}, {"eia", "eias", true, true}, // ia {"EIA", "EIAs", true, true}, {"DNS", "DNSes", true, true}, } func init() { for _, wd := range dictionary { if wd.uncountable && wd.plural == "" { wd.plural = wd.singular } singlePluralAssertions = append(singlePluralAssertions, dict{ singular: wd.singular, plural: wd.plural, doSingularizeTest: !wd.unidirectional, }) if wd.alternative != "" { singlePluralAssertions = append(singlePluralAssertions, dict{ singular: wd.singular, plural: wd.alternative, doPluralizeTest: false, }) } } } flect-1.0.2/go.mod000066400000000000000000000002351437615061300137230ustar00rootroot00000000000000module github.com/gobuffalo/flect go 1.16 exclude github.com/stretchr/testify v1.7.1 require github.com/stretchr/testify v1.8.1 retract [v1.0.0, v1.0.1] flect-1.0.2/go.sum000066400000000000000000000021711437615061300137510ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= flect-1.0.2/humanize.go000066400000000000000000000013311437615061300147620ustar00rootroot00000000000000package flect import ( "strings" ) // Humanize returns first letter of sentence capitalized. // Common acronyms are capitalized as well. // Other capital letters in string are left as provided. // employee_salary = Employee salary // employee_id = employee ID // employee_mobile_number = Employee mobile number // first_Name = First Name // firstName = First Name func Humanize(s string) string { return New(s).Humanize().String() } // Humanize First letter of sentence capitalized func (i Ident) Humanize() Ident { if len(i.Original) == 0 { return New("") } parts := xappend([]string{}, Titleize(i.Parts[0])) if len(i.Parts) > 1 { parts = xappend(parts, i.Parts[1:]...) } return New(strings.Join(parts, " ")) } flect-1.0.2/humanize_test.go000066400000000000000000000013121437615061300160200ustar00rootroot00000000000000package flect import ( "testing" "github.com/stretchr/testify/require" ) func Test_Humanize(t *testing.T) { table := []tt{ {"", ""}, {"id", "ID"}, {"url", "URL"}, {"IBM", "IBM"}, {"CAUTION! CAPs are CAPs!", "CAUTION! CAPs are CAPs!"}, {"employee_mobile_number", "Employee mobile number"}, {"employee_salary", "Employee salary"}, {"employee_id", "Employee ID"}, {"employee_ID", "Employee ID"}, {"first_name", "First name"}, {"first_Name", "First Name"}, {"firstName", "First Name"}, {"óbito", "Óbito"}, } for _, tt := range table { t.Run(tt.act, func(st *testing.T) { r := require.New(st) r.Equal(tt.exp, Humanize(tt.act)) r.Equal(tt.exp, Humanize(tt.exp)) }) } } flect-1.0.2/ident.go000066400000000000000000000055731437615061300142610ustar00rootroot00000000000000package flect import ( "encoding" "strings" "unicode" "unicode/utf8" ) // Ident represents the string and it's parts type Ident struct { Original string Parts []string } // String implements fmt.Stringer and returns the original string func (i Ident) String() string { return i.Original } // New creates a new Ident from the string func New(s string) Ident { i := Ident{ Original: s, Parts: toParts(s), } return i } func toParts(s string) []string { parts := []string{} s = strings.TrimSpace(s) if len(s) == 0 { return parts } if _, ok := baseAcronyms[strings.ToUpper(s)]; ok { return []string{strings.ToUpper(s)} } var prev rune var x strings.Builder x.Grow(len(s)) for _, c := range s { // fmt.Println("### cs ->", cs) // fmt.Println("### unicode.IsControl(c) ->", unicode.IsControl(c)) // fmt.Println("### unicode.IsDigit(c) ->", unicode.IsDigit(c)) // fmt.Println("### unicode.IsGraphic(c) ->", unicode.IsGraphic(c)) // fmt.Println("### unicode.IsLetter(c) ->", unicode.IsLetter(c)) // fmt.Println("### unicode.IsLower(c) ->", unicode.IsLower(c)) // fmt.Println("### unicode.IsMark(c) ->", unicode.IsMark(c)) // fmt.Println("### unicode.IsPrint(c) ->", unicode.IsPrint(c)) // fmt.Println("### unicode.IsPunct(c) ->", unicode.IsPunct(c)) // fmt.Println("### unicode.IsSpace(c) ->", unicode.IsSpace(c)) // fmt.Println("### unicode.IsTitle(c) ->", unicode.IsTitle(c)) // fmt.Println("### unicode.IsUpper(c) ->", unicode.IsUpper(c)) if !utf8.ValidRune(c) { continue } if isSpace(c) { parts = xappend(parts, x.String()) x.Reset() x.WriteRune(c) prev = c continue } if unicode.IsUpper(c) && !unicode.IsUpper(prev) { parts = xappend(parts, x.String()) x.Reset() x.WriteRune(c) prev = c continue } if unicode.IsUpper(c) && baseAcronyms[strings.ToUpper(x.String())] { parts = xappend(parts, x.String()) x.Reset() x.WriteRune(c) prev = c continue } if unicode.IsLetter(c) || unicode.IsDigit(c) || unicode.IsPunct(c) || c == '`' { prev = c x.WriteRune(c) continue } parts = xappend(parts, x.String()) x.Reset() prev = c } parts = xappend(parts, x.String()) return parts } var _ encoding.TextUnmarshaler = &Ident{} var _ encoding.TextMarshaler = &Ident{} // LastPart returns the last part/word of the original string func (i *Ident) LastPart() string { if len(i.Parts) == 0 { return "" } return i.Parts[len(i.Parts)-1] } // ReplaceSuffix creates a new Ident with the original suffix replaced by new func (i Ident) ReplaceSuffix(orig, new string) Ident { return New(strings.TrimSuffix(i.Original, orig) + new) } //UnmarshalText unmarshalls byte array into the Ident func (i *Ident) UnmarshalText(data []byte) error { (*i) = New(string(data)) return nil } //MarshalText marshals Ident into byte array func (i Ident) MarshalText() ([]byte, error) { return []byte(i.Original), nil } flect-1.0.2/ident_test.go000066400000000000000000000053031437615061300153070ustar00rootroot00000000000000package flect import ( "testing" "github.com/stretchr/testify/require" ) func Test_New(t *testing.T) { table := []Ident{ {"", []string{}}, {"widget", []string{"widget"}}, {"widget_id", []string{"widget", "ID"}}, {"WidgetID", []string{"Widget", "ID"}}, {"Widget_ID", []string{"Widget", "ID"}}, {"widget_ID", []string{"widget", "ID"}}, {"widget/ID", []string{"widget", "ID"}}, {"widgetID", []string{"widget", "ID"}}, {"widgetName", []string{"widget", "Name"}}, {"JWTName", []string{"JWT", "Name"}}, {"JWTname", []string{"JWTname"}}, {"jwtname", []string{"jwtname"}}, {"sql", []string{"SQL"}}, {"sQl", []string{"SQL"}}, {"id", []string{"ID"}}, {"Id", []string{"ID"}}, {"iD", []string{"ID"}}, {"html", []string{"HTML"}}, {"Html", []string{"HTML"}}, {"HTML", []string{"HTML"}}, {"with `code` inside", []string{"with", "`code`", "inside"}}, {"Donald E. Knuth", []string{"Donald", "E.", "Knuth"}}, {"Random text with *(bad)* characters", []string{"Random", "text", "with", "*(bad)*", "characters"}}, {"Allow_Under_Scores", []string{"Allow", "Under", "Scores"}}, {"Trailing bad characters!@#", []string{"Trailing", "bad", "characters!@#"}}, {"!@#Leading bad characters", []string{"!@#", "Leading", "bad", "characters"}}, {"Squeeze separators", []string{"Squeeze", "separators"}}, {"Test with + sign", []string{"Test", "with", "sign"}}, {"Malmö", []string{"Malmö"}}, {"Garçons", []string{"Garçons"}}, {"Opsů", []string{"Opsů"}}, {"Ærøskøbing", []string{"Ærøskøbing"}}, {"Aßlar", []string{"Aßlar"}}, {"Japanese: 日本語", []string{"Japanese", "日本語"}}, } for _, tt := range table { t.Run(tt.Original, func(st *testing.T) { r := require.New(st) i := New(tt.Original) r.Equal(tt.Original, i.Original) r.Equal(tt.Parts, i.Parts) }) } } func Test_MarshalText(t *testing.T) { r := require.New(t) n := New("mark") b, err := n.MarshalText() r.NoError(err) r.Equal("mark", string(b)) r.NoError((&n).UnmarshalText([]byte("bates"))) r.Equal("bates", n.String()) } func Benchmark_New(b *testing.B) { table := []string{ "", "widget", "widget_id", "WidgetID", "Widget_ID", "widget_ID", "widget/ID", "widgetID", "widgetName", "JWTName", "JWTname", "jwtname", "sql", "sQl", "id", "Id", "iD", "html", "Html", "HTML", "with `code` inside", "Donald E. Knuth", "Random text with *(bad)* characters", "Allow_Under_Scores", "Trailing bad characters!@#", "!@#Leading bad characters", "Squeeze separators", "Test with + sign", "Malmö", "Garçons", "Opsů", "Ærøskøbing", "Aßlar", "Japanese: 日本語", } for n := 0; n < b.N; n++ { for i := range table { New(table[i]) } } } flect-1.0.2/lower_upper.go000066400000000000000000000004461437615061300155130ustar00rootroot00000000000000package flect import "strings" // ToUpper is a convience wrapper for strings.ToUpper func (i Ident) ToUpper() Ident { return New(strings.ToUpper(i.Original)) } // ToLower is a convience wrapper for strings.ToLower func (i Ident) ToLower() Ident { return New(strings.ToLower(i.Original)) } flect-1.0.2/lower_upper_test.go000066400000000000000000000000161437615061300165430ustar00rootroot00000000000000package flect flect-1.0.2/name/000077500000000000000000000000001437615061300135355ustar00rootroot00000000000000flect-1.0.2/name/char.go000066400000000000000000000006541437615061300150060ustar00rootroot00000000000000package name import "unicode" // Char returns the first letter, lowered // "" = "x" // "foo" = "f" // "123d456" = "d" func Char(s string) string { return New(s).Char().String() } // Char returns the first letter, lowered // "" = "x" // "foo" = "f" // "123d456" = "d" func (i Ident) Char() Ident { for _, c := range i.Original { if unicode.IsLetter(c) { return New(string(unicode.ToLower(c))) } } return New("x") } flect-1.0.2/name/char_test.go000066400000000000000000000006121437615061300160370ustar00rootroot00000000000000package name import ( "testing" "github.com/stretchr/testify/require" ) func Test_Char(t *testing.T) { table := []tt{ {"", "x"}, {"foo_bar", "f"}, {"admin/widget", "a"}, {"123d4545", "d"}, {"!@#$%^&*", "x"}, } for _, tt := range table { t.Run(tt.act, func(st *testing.T) { r := require.New(st) r.Equal(tt.exp, Char(tt.act)) r.Equal(tt.exp, Char(tt.exp)) }) } } flect-1.0.2/name/file.go000066400000000000000000000011321437615061300150000ustar00rootroot00000000000000package name import ( "strings" "github.com/gobuffalo/flect" ) // File creates a suitable file name // admin/widget = admin/widget // foo_bar = foo_bar // U$ser = u_ser func File(s string, exts ...string) string { return New(s).File(exts...).String() } // File creates a suitable file name // admin/widget = admin/widget // foo_bar = foo_bar // U$ser = u_ser func (i Ident) File(exts ...string) Ident { var parts []string for _, part := range strings.Split(i.Original, "/") { parts = append(parts, flect.Underscore(part)) } return New(strings.Join(parts, "/") + strings.Join(exts, "")) } flect-1.0.2/name/file_test.go000066400000000000000000000010441437615061300160410ustar00rootroot00000000000000package name import ( "testing" "github.com/stretchr/testify/require" ) func Test_File(t *testing.T) { table := []tt{ {"", ""}, {"foo_bar", "foo_bar"}, {"admin/widget", "admin/widget"}, {"admin/widgets", "admin/widgets"}, {"widget", "widget"}, {"widgets", "widgets"}, {"User", "user"}, {"U$er", "u_er"}, } for _, tt := range table { t.Run(tt.act, func(st *testing.T) { r := require.New(st) r.Equal(tt.exp, File(tt.act)) r.Equal(tt.exp, File(tt.exp)) r.Equal(tt.exp+".a.b", File(tt.act, ".a", ".b")) }) } } flect-1.0.2/name/folder.go000066400000000000000000000020731437615061300153410ustar00rootroot00000000000000package name import ( "regexp" "strings" "github.com/gobuffalo/flect" ) var alphanum = regexp.MustCompile(`[^a-zA-Z0-9_]+`) // Folder returns a suitable folder name. It removes any special characters // from the given string `s` and returns a string consists of alpha-numeric // characters. // admin/widget --> admin/widget // adminUser --> admin_user // foo_bar --> foo_bar // Admin --> admin // U$ser --> u_ser func Folder(s string, exts ...string) string { return New(s).Folder(exts...).String() } // Folder returns a suitable folder name. It removes any special characters // from the given string `s` and returns a string consists of alpha-numeric // characters. // admin/widget --> admin/widget // adminUser --> admin_user // foo_bar --> foo_bar // Admin --> admin // U$ser --> u_ser func (i Ident) Folder(exts ...string) Ident { var parts []string for _, part := range strings.Split(i.Original, "/") { part = alphanum.ReplaceAllString(flect.Underscore(part), "") parts = append(parts, part) } return New(strings.Join(parts, "/") + strings.Join(exts, "")) } flect-1.0.2/name/folder_test.go000066400000000000000000000013141437615061300163750ustar00rootroot00000000000000package name import ( "testing" "github.com/stretchr/testify/require" ) func Test_Folder(t *testing.T) { table := []tt{ {"", ""}, {"admin/widget", "admin/widget"}, {"admin/widgets", "admin/widgets"}, {"widget", "widget"}, {"widgets", "widgets"}, {"User", "user"}, {"U$er", "u_er"}, {"adminuser", "adminuser"}, {"Adminuser", "adminuser"}, {"AdminUser", "admin_user"}, {"adminUser", "admin_user"}, {"admin-user", "admin_user"}, {"admin_user", "admin_user"}, } for _, tt := range table { t.Run(tt.act, func(st *testing.T) { r := require.New(st) r.Equal(tt.exp, Folder(tt.act)) r.Equal(tt.exp, Folder(tt.exp)) r.Equal(tt.exp+".a.b", Folder(tt.act, ".a", ".b")) }) } } flect-1.0.2/name/ident.go000066400000000000000000000003511437615061300151660ustar00rootroot00000000000000package name import "github.com/gobuffalo/flect" // Ident represents the string and it's parts type Ident struct { flect.Ident } // New creates a new Ident from the string func New(s string) Ident { return Ident{flect.New(s)} } flect-1.0.2/name/interface.go000066400000000000000000000007721437615061300160320ustar00rootroot00000000000000package name import ( "fmt" "reflect" ) func Interface(x interface{}) (Ident, error) { switch t := x.(type) { case string: return New(t), nil default: rv := reflect.Indirect(reflect.ValueOf(x)) to := rv.Type() if len(to.Name()) > 0 { return New(to.Name()), nil } k := to.Kind() switch k { case reflect.Slice, reflect.Array: e := to.Elem() n := New(e.Name()) return New(n.Pluralize().String()), nil } } return New(""), fmt.Errorf("could not convert %T to Ident", x) } flect-1.0.2/name/interface_test.go000066400000000000000000000011021437615061300170550ustar00rootroot00000000000000package name import ( "fmt" "testing" "github.com/stretchr/testify/require" ) type car struct{} func Test_Interface(t *testing.T) { table := []struct { in interface{} out string err bool }{ {"foo", "foo", false}, {car{}, "car", false}, {&car{}, "car", false}, {[]car{}, "cars", false}, {false, "bool", false}, } for _, tt := range table { t.Run(fmt.Sprint(tt.in), func(st *testing.T) { r := require.New(st) n, err := Interface(tt.in) if tt.err { r.Error(err) return } r.NoError(err) r.Equal(tt.out, n.String()) }) } } flect-1.0.2/name/join.go000066400000000000000000000006201437615061300150210ustar00rootroot00000000000000package name import "path/filepath" func FilePathJoin(names ...string) string { var ni = make([]Ident, len(names)) for i, n := range names { ni[i] = New(n) } base := New("") return base.FilePathJoin(ni...).String() } func (i Ident) FilePathJoin(ni ...Ident) Ident { var s = make([]string, len(ni)) for i, n := range ni { s[i] = n.OsPath().String() } return New(filepath.Join(s...)) } flect-1.0.2/name/join_test.go000066400000000000000000000011751437615061300160660ustar00rootroot00000000000000package name import ( "runtime" "testing" "github.com/stretchr/testify/require" ) func Test_Ident_FilePathJoin(t *testing.T) { table := map[string]string{ "foo/bar/baz": "foo/bar/baz/boo", "foo\\bar\\baz": "foo/bar/baz/boo", } if runtime.GOOS == "windows" { table = ident_FilePathJoin_Windows_Table() } for in, out := range table { t.Run(in, func(st *testing.T) { r := require.New(st) r.Equal(out, FilePathJoin(in, "boo")) }) } } func ident_FilePathJoin_Windows_Table() map[string]string { return map[string]string{ "foo/bar/baz": "foo\\bar\\baz\\boo", "foo\\bar\\baz": "foo\\bar\\baz\\boo", } } flect-1.0.2/name/key.go000066400000000000000000000003221437615061300146510ustar00rootroot00000000000000package name import ( "strings" ) func Key(s string) string { return New(s).Key().String() } func (i Ident) Key() Ident { s := strings.Replace(i.String(), "\\", "/", -1) return New(strings.ToLower(s)) } flect-1.0.2/name/key_test.go000066400000000000000000000005211437615061300157110ustar00rootroot00000000000000package name import ( "testing" "github.com/stretchr/testify/require" ) func Test_Ident_Key(t *testing.T) { table := map[string]string{ "Foo/bar/baz": "foo/bar/baz", "Foo\\bar\\baz": "foo/bar/baz", } for in, out := range table { t.Run(in, func(st *testing.T) { r := require.New(st) r.Equal(out, Key(in)) }) } } flect-1.0.2/name/name.go000066400000000000000000000025221437615061300150050ustar00rootroot00000000000000package name import ( "encoding" "strings" "github.com/gobuffalo/flect" ) // Proper pascalizes and singularizes the string // person = Person // foo_bar = FooBar // admin/widgets = AdminWidget func Proper(s string) string { return New(s).Proper().String() } // Proper pascalizes and singularizes the string // person = Person // foo_bar = FooBar // admin/widgets = AdminWidget func (i Ident) Proper() Ident { return Ident{i.Singularize().Pascalize()} } // Group pascalizes and pluralizes the string // person = People // foo_bar = FooBars // admin/widget = AdminWidgets func Group(s string) string { return New(s).Group().String() } // Group pascalizes and pluralizes the string // person = People // foo_bar = FooBars // admin/widget = AdminWidgets func (i Ident) Group() Ident { var parts []string if len(i.Original) == 0 { return i } last := i.Parts[len(i.Parts)-1] for _, part := range i.Parts[:len(i.Parts)-1] { parts = append(parts, flect.Pascalize(part)) } last = New(last).Pluralize().Pascalize().String() parts = append(parts, last) return New(strings.Join(parts, "")) } var _ encoding.TextUnmarshaler = &Ident{} var _ encoding.TextMarshaler = &Ident{} func (i *Ident) UnmarshalText(data []byte) error { (*i) = New(string(data)) return nil } func (i Ident) MarshalText() ([]byte, error) { return []byte(i.Original), nil } flect-1.0.2/name/name_test.go000066400000000000000000000033421437615061300160450ustar00rootroot00000000000000package name import ( "testing" "github.com/stretchr/testify/require" ) type tt struct { act string exp string } func Test_Name(t *testing.T) { table := []tt{ {"", ""}, {"bob dylan", "BobDylan"}, {"widgetID", "WidgetID"}, {"widget_ID", "WidgetID"}, {"Widget_ID", "WidgetID"}, {"Widget_Id", "WidgetID"}, {"Widget_id", "WidgetID"}, {"Nice to see you today!", "NiceToSeeYouToday"}, {"*hello*", "Hello"}, {"i've read a book! have you read it?", "IveReadABookHaveYouReadIt"}, {"This is `code` ok", "ThisIsCodeOK"}, {"foo_bar", "FooBar"}, {"admin/widget", "AdminWidget"}, {"admin/widgets", "AdminWidget"}, {"widget", "Widget"}, {"widgets", "Widget"}, {"status", "Status"}, {"Statuses", "Status"}, {"statuses", "Status"}, {"People", "Person"}, {"people", "Person"}, } for _, tt := range table { t.Run(tt.act, func(st *testing.T) { r := require.New(st) r.Equal(tt.exp, Proper(tt.act)) r.Equal(tt.exp, Proper(tt.exp)) }) } } func Test_Group(t *testing.T) { table := []tt{ {"", ""}, {"Person", "People"}, {"foo_bar", "FooBars"}, {"admin/widget", "AdminWidgets"}, {"widget", "Widgets"}, {"widgets", "Widgets"}, {"greatPerson", "GreatPeople"}, {"great/person", "GreatPeople"}, {"status", "Statuses"}, {"Status", "Statuses"}, {"Statuses", "Statuses"}, {"statuses", "Statuses"}, } for _, tt := range table { t.Run(tt.act, func(st *testing.T) { r := require.New(st) r.Equal(tt.exp, Group(tt.act)) r.Equal(tt.exp, Group(tt.exp)) }) } } func Test_MarshalText(t *testing.T) { r := require.New(t) n := New("mark") b, err := n.MarshalText() r.NoError(err) r.Equal("mark", string(b)) r.NoError((&n).UnmarshalText([]byte("bates"))) r.Equal("bates", n.String()) } flect-1.0.2/name/os_path.go000066400000000000000000000005601437615061300155220ustar00rootroot00000000000000package name import ( "path/filepath" "runtime" "strings" ) func OsPath(s string) string { return New(s).OsPath().String() } func (i Ident) OsPath() Ident { s := i.String() if runtime.GOOS == "windows" { s = strings.Replace(s, "/", string(filepath.Separator), -1) } else { s = strings.Replace(s, "\\", string(filepath.Separator), -1) } return New(s) } flect-1.0.2/name/os_path_test.go000066400000000000000000000011141437615061300165550ustar00rootroot00000000000000package name import ( "runtime" "testing" "github.com/stretchr/testify/require" ) func Test_Ident_OsPath(t *testing.T) { table := map[string]string{ "foo/bar/baz": "foo/bar/baz", "foo\\bar\\baz": "foo/bar/baz", } if runtime.GOOS == "windows" { table = ident_OsPath_Windows_Table() } for in, out := range table { t.Run(in, func(st *testing.T) { r := require.New(st) r.Equal(out, OsPath(in)) }) } } func ident_OsPath_Windows_Table() map[string]string { return map[string]string{ "foo/bar/baz": "foo\\bar\\baz", "foo\\bar\\baz": "foo\\bar\\baz", } } flect-1.0.2/name/package.go000066400000000000000000000014721437615061300154630ustar00rootroot00000000000000package name import ( "go/build" "path/filepath" "strings" ) // Package will attempt to return a package version of the name // $GOPATH/src/foo/bar = foo/bar // $GOPATH\src\foo\bar = foo/bar // foo/bar = foo/bar func Package(s string) string { return New(s).Package().String() } // Package will attempt to return a package version of the name // $GOPATH/src/foo/bar = foo/bar // $GOPATH\src\foo\bar = foo/bar // foo/bar = foo/bar func (i Ident) Package() Ident { c := build.Default s := i.Original for _, src := range c.SrcDirs() { s = strings.TrimPrefix(s, src) s = strings.TrimPrefix(s, filepath.Dir(src)) // encase there's no /src prefix } s = strings.TrimPrefix(s, string(filepath.Separator)) s = strings.Replace(s, "\\", "/", -1) s = strings.Replace(s, "_", "", -1) return Ident{New(s).ToLower()} } flect-1.0.2/name/package_test.go000066400000000000000000000015571437615061300165260ustar00rootroot00000000000000package name import ( "go/build" "path/filepath" "testing" "github.com/stretchr/testify/require" ) func Test_Package(t *testing.T) { table := []tt{ {"Foo", "foo"}, {"Foo/Foo", "foo/foo"}, {"Foo_Foo", "foofoo"}, {"create_table", "createtable"}, {"admin/widget", "admin/widget"}, {"admin\\widget", "admin/widget"}, } c := build.Default for _, src := range c.SrcDirs() { adds := []tt{ {filepath.Join(src, "admin/widget"), "admin/widget"}, {filepath.Join(src, "admin\\widget"), "admin/widget"}, {filepath.Join(filepath.Dir(src), "admin/widget"), "admin/widget"}, {filepath.Join(filepath.Dir(src), "admin\\widget"), "admin/widget"}, } table = append(table, adds...) } for _, tt := range table { t.Run(tt.act, func(st *testing.T) { r := require.New(st) r.Equal(tt.exp, Package(tt.act)) r.Equal(tt.exp, Package(tt.exp)) }) } } flect-1.0.2/name/param_id.go000066400000000000000000000010371437615061300156410ustar00rootroot00000000000000package name import "strings" // ParamID returns the string as parameter with _id added // user = user_id // UserID = user_id // admin/widgets = admin_widgets_id func ParamID(s string) string { return New(s).ParamID().String() } // ParamID returns the string as parameter with _id added // user = user_id // UserID = user_id // admin/widgets = admin_widget_id func (i Ident) ParamID() Ident { s := i.Singularize().Underscore().String() s = strings.ToLower(s) if strings.HasSuffix(s, "_id") { return New(s) } return New(s + "_id") } flect-1.0.2/name/param_id_test.go000066400000000000000000000007751437615061300167100ustar00rootroot00000000000000package name import ( "testing" "github.com/stretchr/testify/require" ) func Test_ParamID(t *testing.T) { table := []tt{ {"foo_bar", "foo_bar_id"}, {"admin/widget", "admin_widget_id"}, {"admin/widgets", "admin_widget_id"}, {"widget", "widget_id"}, {"User", "user_id"}, {"user", "user_id"}, {"UserID", "user_id"}, } for _, tt := range table { t.Run(tt.act, func(st *testing.T) { r := require.New(st) r.Equal(tt.exp, ParamID(tt.act)) r.Equal(tt.exp, ParamID(tt.exp)) }) } } flect-1.0.2/name/resource.go000066400000000000000000000006271437615061300157200ustar00rootroot00000000000000package name import ( "strings" ) // Resource version of a name func (n Ident) Resource() Ident { name := n.Underscore().String() x := strings.FieldsFunc(name, func(r rune) bool { return r == '_' || r == '/' }) for i, w := range x { if i == len(x)-1 { x[i] = New(w).Pluralize().Pascalize().String() continue } x[i] = New(w).Pascalize().String() } return New(strings.Join(x, "")) } flect-1.0.2/name/resource_test.go000066400000000000000000000012101437615061300167440ustar00rootroot00000000000000package name import ( "testing" "github.com/stretchr/testify/require" ) func Test_Name_Resource(t *testing.T) { r := require.New(t) table := []struct { V string E string }{ {V: "Person", E: "People"}, {V: "foo_bar", E: "FooBars"}, {V: "admin/widget", E: "AdminWidgets"}, {V: "widget", E: "Widgets"}, {V: "widgets", E: "Widgets"}, {V: "greatPerson", E: "GreatPeople"}, {V: "great/person", E: "GreatPeople"}, {V: "status", E: "Statuses"}, {V: "Status", E: "Statuses"}, {V: "Statuses", E: "Statuses"}, {V: "statuses", E: "Statuses"}, } for _, tt := range table { r.Equal(tt.E, New(tt.V).Resource().String()) } } flect-1.0.2/name/tablize.go000066400000000000000000000006251437615061300155210ustar00rootroot00000000000000package name // Tableize returns an underscore, pluralized string // User = users // Person = persons // Admin/Widget = admin_widgets func Tableize(s string) string { return New(s).Tableize().String() } // Tableize returns an underscore, pluralized string // User = users // Person = persons // Admin/Widget = admin_widgets func (i Ident) Tableize() Ident { return Ident{i.Underscore().Pluralize()} } flect-1.0.2/name/tablize_test.go000066400000000000000000000014751437615061300165640ustar00rootroot00000000000000package name import ( "testing" "github.com/stretchr/testify/require" ) func Test_Tableize(t *testing.T) { table := []tt{ {"", ""}, {"bob dylan", "bob_dylans"}, {"Nice to see you!", "nice_to_see_you"}, {"*hello*", "hellos"}, {"i've read a book! have you?", "ive_read_a_book_have_you"}, {"This is `code` ok", "this_is_code_oks"}, {"foo_bar", "foo_bars"}, {"admin/widget", "admin_widgets"}, {"widget", "widgets"}, {"widgets", "widgets"}, {"status", "statuses"}, {"Statuses", "statuses"}, {"statuses", "statuses"}, {"People", "people"}, {"people", "people"}, {"BigPerson", "big_people"}, {"Wild Ox", "wild_oxen"}, } for _, tt := range table { t.Run(tt.act, func(st *testing.T) { r := require.New(st) r.Equal(tt.exp, Tableize(tt.act)) r.Equal(tt.exp, Tableize(tt.exp)) }) } } flect-1.0.2/name/url.go000066400000000000000000000001211437615061300146600ustar00rootroot00000000000000package name func (n Ident) URL() Ident { return Ident{n.File().Pluralize()} } flect-1.0.2/name/url_test.go000066400000000000000000000010061437615061300157220ustar00rootroot00000000000000package name import ( "testing" "github.com/stretchr/testify/require" ) func Test_URL(t *testing.T) { table := []struct { in string out string }{ {"User", "users"}, {"widget", "widgets"}, {"AdminUser", "admin_users"}, {"Admin/User", "admin/users"}, {"Admin/Users", "admin/users"}, {"/Admin/Users", "/admin/users"}, } for _, tt := range table { t.Run(tt.in, func(st *testing.T) { r := require.New(st) n := New(tt.in) r.Equal(tt.out, n.URL().String(), "URL of %v", tt.in) }) } } flect-1.0.2/name/var_case.go000066400000000000000000000021101437615061300156410ustar00rootroot00000000000000package name // VarCaseSingle version of a name. // foo_bar = fooBar // admin/widget = adminWidget // User = users func VarCaseSingle(s string) string { return New(s).VarCaseSingle().String() } // VarCaseSingle version of a name. // foo_bar = fooBar // admin/widget = adminWidget // User = users func (i Ident) VarCaseSingle() Ident { return Ident{i.Group().Singularize().Camelize()} } // VarCasePlural version of a name. // foo_bar = fooBars // admin/widget = adminWidgets // User = users func VarCasePlural(s string) string { return New(s).VarCasePlural().String() } // VarCasePlural version of a name. // foo_bar = fooBars // admin/widget = adminWidgets // User = users func (i Ident) VarCasePlural() Ident { return Ident{i.Group().Pluralize().Camelize()} } // VarCase version of a name. // foo_bar = fooBar // admin/widget = adminWidget // Users = users func (i Ident) VarCase() Ident { return Ident{i.Camelize()} } // VarCase version of a name. // foo_bar = fooBar // admin/widget = adminWidget // Users = users func VarCase(s string) string { return New(s).VarCase().String() } flect-1.0.2/name/var_case_test.go000066400000000000000000000030751437615061300167130ustar00rootroot00000000000000package name import ( "testing" "github.com/stretchr/testify/require" ) func Test_VarCaseSingle(t *testing.T) { table := []tt{ {"foo_bar", "fooBar"}, {"admin/widget", "adminWidget"}, {"widget", "widget"}, {"widgets", "widget"}, {"User", "user"}, {"FooBar", "fooBar"}, {"status", "status"}, {"statuses", "status"}, {"Status", "status"}, {"Statuses", "status"}, } for _, tt := range table { t.Run(tt.act, func(st *testing.T) { r := require.New(st) r.Equal(tt.exp, VarCaseSingle(tt.act)) r.Equal(tt.exp, VarCaseSingle(tt.exp)) }) } } func Test_VarCasePlural(t *testing.T) { table := []tt{ {"foo_bar", "fooBars"}, {"admin/widget", "adminWidgets"}, {"widget", "widgets"}, {"widgets", "widgets"}, {"User", "users"}, {"FooBar", "fooBars"}, {"status", "statuses"}, {"statuses", "statuses"}, {"Status", "statuses"}, {"Statuses", "statuses"}, } for _, tt := range table { t.Run(tt.act, func(st *testing.T) { r := require.New(st) r.Equal(tt.exp, VarCasePlural(tt.act)) r.Equal(tt.exp, VarCasePlural(tt.exp)) }) } } func Test_VarCase(t *testing.T) { table := []tt{ {"foo_bar", "fooBar"}, {"admin/widget", "adminWidget"}, {"widget", "widget"}, {"widgets", "widgets"}, {"User", "user"}, {"FooBar", "fooBar"}, {"FooBars", "fooBars"}, {"status", "status"}, {"statuses", "statuses"}, {"Status", "status"}, {"Statuses", "statuses"}, } for _, tt := range table { t.Run(tt.act, func(st *testing.T) { r := require.New(st) r.Equal(tt.exp, VarCase(tt.act)) r.Equal(tt.exp, VarCase(tt.exp)) }) } } flect-1.0.2/ordinalize.go000066400000000000000000000013701437615061300153050ustar00rootroot00000000000000package flect import ( "fmt" "strconv" ) // Ordinalize converts a number to an ordinal version // 42 = 42nd // 45 = 45th // 1 = 1st func Ordinalize(s string) string { return New(s).Ordinalize().String() } // Ordinalize converts a number to an ordinal version // 42 = 42nd // 45 = 45th // 1 = 1st func (i Ident) Ordinalize() Ident { number, err := strconv.Atoi(i.Original) if err != nil { return i } var s string switch abs(number) % 100 { case 11, 12, 13: s = fmt.Sprintf("%dth", number) default: switch abs(number) % 10 { case 1: s = fmt.Sprintf("%dst", number) case 2: s = fmt.Sprintf("%dnd", number) case 3: s = fmt.Sprintf("%drd", number) } } if s != "" { return New(s) } return New(fmt.Sprintf("%dth", number)) } flect-1.0.2/ordinalize_test.go000066400000000000000000000027151437615061300163500ustar00rootroot00000000000000package flect import ( "testing" "github.com/stretchr/testify/require" ) func Test_Ordinalize(t *testing.T) { table := []tt{ {"-1", "-1st"}, {"-2", "-2nd"}, {"-3", "-3rd"}, {"-4", "-4th"}, {"-5", "-5th"}, {"-6", "-6th"}, {"-7", "-7th"}, {"-8", "-8th"}, {"-9", "-9th"}, {"-10", "-10th"}, {"-11", "-11th"}, {"-12", "-12th"}, {"-13", "-13th"}, {"-14", "-14th"}, {"-20", "-20th"}, {"-21", "-21st"}, {"-22", "-22nd"}, {"-23", "-23rd"}, {"-24", "-24th"}, {"-100", "-100th"}, {"-101", "-101st"}, {"-102", "-102nd"}, {"-103", "-103rd"}, {"-104", "-104th"}, {"-110", "-110th"}, {"-111", "-111th"}, {"-112", "-112th"}, {"-113", "-113th"}, {"-1000", "-1000th"}, {"-1001", "-1001st"}, {"0", "0th"}, {"1", "1st"}, {"2", "2nd"}, {"3", "3rd"}, {"4", "4th"}, {"5", "5th"}, {"6", "6th"}, {"7", "7th"}, {"8", "8th"}, {"9", "9th"}, {"10", "10th"}, {"11", "11th"}, {"12", "12th"}, {"13", "13th"}, {"14", "14th"}, {"20", "20th"}, {"21", "21st"}, {"22", "22nd"}, {"23", "23rd"}, {"24", "24th"}, {"100", "100th"}, {"101", "101st"}, {"102", "102nd"}, {"103", "103rd"}, {"104", "104th"}, {"110", "110th"}, {"111", "111th"}, {"112", "112th"}, {"113", "113th"}, {"1000", "1000th"}, {"1001", "1001st"}, } for _, tt := range table { t.Run(tt.act, func(st *testing.T) { r := require.New(st) r.Equal(tt.exp, Ordinalize(tt.act)) r.Equal(tt.exp, Ordinalize(tt.exp)) }) } } flect-1.0.2/pascalize.go000066400000000000000000000012451437615061300151210ustar00rootroot00000000000000package flect import ( "strings" ) // Pascalize returns a string with each segment capitalized // user = User // bob dylan = BobDylan // widget_id = WidgetID func Pascalize(s string) string { return New(s).Pascalize().String() } // Pascalize returns a string with each segment capitalized // user = User // bob dylan = BobDylan // widget_id = WidgetID func (i Ident) Pascalize() Ident { c := i.Camelize() if len(c.String()) == 0 { return c } if len(i.Parts) == 0 { return i } capLen := 1 if _, ok := baseAcronyms[strings.ToUpper(i.Parts[0])]; ok { capLen = len(i.Parts[0]) } return New(string(strings.ToUpper(c.Original[0:capLen])) + c.Original[capLen:]) } flect-1.0.2/pascalize_test.go000066400000000000000000000013021437615061300161520ustar00rootroot00000000000000package flect import ( "testing" "github.com/stretchr/testify/require" ) func Test_Pascalize(t *testing.T) { table := []tt{ {"", ""}, {"bob dylan", "BobDylan"}, {"ID", "ID"}, {"id", "ID"}, {"widgetID", "WidgetID"}, {"widget_ID", "WidgetID"}, {"Widget_ID", "WidgetID"}, {"Nice to see you!", "NiceToSeeYou"}, {"*hello*", "Hello"}, {"i've read a book! have you?", "IveReadABookHaveYou"}, {"This is `code` ok", "ThisIsCodeOK"}, {"id", "ID"}, {"ip_address", "IPAddress"}, {"some_url", "SomeURL"}, } for _, tt := range table { t.Run(tt.act, func(st *testing.T) { r := require.New(st) r.Equal(tt.exp, Pascalize(tt.act)) r.Equal(tt.exp, Pascalize(tt.exp)) }) } } flect-1.0.2/plural_rules.go000066400000000000000000000361211437615061300156600ustar00rootroot00000000000000package flect import "fmt" var pluralRules = []rule{} // AddPlural adds a rule that will replace the given suffix with the replacement suffix. // The name is confusing. This function will be deprecated in the next release. func AddPlural(suffix string, repl string) { InsertPluralRule(suffix, repl) } // InsertPluralRule inserts a rule that will replace the given suffix with // the repl(acement) at the begining of the list of the pluralize rules. func InsertPluralRule(suffix, repl string) { pluralMoot.Lock() defer pluralMoot.Unlock() pluralRules = append([]rule{{ suffix: suffix, fn: simpleRuleFunc(suffix, repl), }}, pluralRules...) pluralRules = append([]rule{{ suffix: repl, fn: noop, }}, pluralRules...) } type word struct { singular string plural string alternative string unidirectional bool // plural to singular is not possible (or bad) uncountable bool exact bool } // dictionary is the main table for singularize and pluralize. // All words in the dictionary will be added to singleToPlural, pluralToSingle // and singlePluralAssertions by init() functions. var dictionary = []word{ // identicals https://en.wikipedia.org/wiki/English_plurals#Nouns_with_identical_singular_and_plural {singular: "aircraft", plural: "aircraft"}, {singular: "beef", plural: "beef", alternative: "beefs"}, {singular: "bison", plural: "bison"}, {singular: "blues", plural: "blues", unidirectional: true}, {singular: "chassis", plural: "chassis"}, {singular: "deer", plural: "deer"}, {singular: "fish", plural: "fish", alternative: "fishes"}, {singular: "moose", plural: "moose"}, {singular: "police", plural: "police"}, {singular: "salmon", plural: "salmon", alternative: "salmons"}, {singular: "series", plural: "series"}, {singular: "sheep", plural: "sheep"}, {singular: "shrimp", plural: "shrimp", alternative: "shrimps"}, {singular: "species", plural: "species"}, {singular: "swine", plural: "swine", alternative: "swines"}, {singular: "trout", plural: "trout", alternative: "trouts"}, {singular: "tuna", plural: "tuna", alternative: "tunas"}, {singular: "you", plural: "you"}, // -en https://en.wikipedia.org/wiki/English_plurals#Plurals_in_-(e)n {singular: "child", plural: "children"}, {singular: "ox", plural: "oxen", exact: true}, // apophonic https://en.wikipedia.org/wiki/English_plurals#Apophonic_plurals {singular: "foot", plural: "feet"}, {singular: "goose", plural: "geese"}, {singular: "man", plural: "men"}, {singular: "human", plural: "humans"}, // not humen {singular: "louse", plural: "lice", exact: true}, {singular: "mouse", plural: "mice"}, {singular: "tooth", plural: "teeth"}, {singular: "woman", plural: "women"}, // misc https://en.wikipedia.org/wiki/English_plurals#Miscellaneous_irregular_plurals {singular: "die", plural: "dice", exact: true}, {singular: "person", plural: "people"}, // Words from French that end in -u add an x; in addition to eau to eaux rule {singular: "adieu", plural: "adieux", alternative: "adieus"}, {singular: "fabliau", plural: "fabliaux"}, {singular: "bureau", plural: "bureaus", alternative: "bureaux"}, // popular // Words from Greek that end in -on change -on to -a; in addition to hedron rule {singular: "criterion", plural: "criteria"}, {singular: "ganglion", plural: "ganglia", alternative: "ganglions"}, {singular: "lexicon", plural: "lexica", alternative: "lexicons"}, {singular: "mitochondrion", plural: "mitochondria", alternative: "mitochondrions"}, {singular: "noumenon", plural: "noumena"}, {singular: "phenomenon", plural: "phenomena"}, {singular: "taxon", plural: "taxa"}, // Words from Latin that end in -um change -um to -a; in addition to some rules {singular: "media", plural: "media"}, // popular case: media -> media {singular: "medium", plural: "media", alternative: "mediums", unidirectional: true}, {singular: "stadium", plural: "stadiums", alternative: "stadia"}, {singular: "aquarium", plural: "aquaria", alternative: "aquariums"}, {singular: "auditorium", plural: "auditoria", alternative: "auditoriums"}, {singular: "symposium", plural: "symposia", alternative: "symposiums"}, {singular: "curriculum", plural: "curriculums", alternative: "curricula"}, // ulum {singular: "quota", plural: "quotas"}, // Words from Latin that end in -us change -us to -i or -era {singular: "alumnus", plural: "alumni", alternative: "alumnuses"}, // -i {singular: "bacillus", plural: "bacilli"}, {singular: "cactus", plural: "cacti", alternative: "cactuses"}, {singular: "coccus", plural: "cocci"}, {singular: "focus", plural: "foci", alternative: "focuses"}, {singular: "locus", plural: "loci", alternative: "locuses"}, {singular: "nucleus", plural: "nuclei", alternative: "nucleuses"}, {singular: "octopus", plural: "octupuses", alternative: "octopi"}, {singular: "radius", plural: "radii", alternative: "radiuses"}, {singular: "syllabus", plural: "syllabi"}, {singular: "corpus", plural: "corpora", alternative: "corpuses"}, // -ra {singular: "genus", plural: "genera"}, // Words from Latin that end in -a change -a to -ae {singular: "alumna", plural: "alumnae"}, {singular: "vertebra", plural: "vertebrae"}, {singular: "differentia", plural: "differentiae"}, // -tia {singular: "minutia", plural: "minutiae"}, {singular: "vita", plural: "vitae"}, // -ita {singular: "larva", plural: "larvae"}, // -va {singular: "postcava", plural: "postcavae"}, {singular: "praecava", plural: "praecavae"}, {singular: "uva", plural: "uvae"}, // Words from Latin that end in -ex change -ex to -ices {singular: "apex", plural: "apices", alternative: "apexes"}, {singular: "codex", plural: "codices", alternative: "codexes"}, {singular: "index", plural: "indices", alternative: "indexes"}, {singular: "latex", plural: "latices", alternative: "latexes"}, {singular: "vertex", plural: "vertices", alternative: "vertexes"}, {singular: "vortex", plural: "vortices", alternative: "vortexes"}, // Words from Latin that end in -ix change -ix to -ices (eg, matrix becomes matrices) {singular: "appendix", plural: "appendices", alternative: "appendixes"}, {singular: "radix", plural: "radices", alternative: "radixes"}, {singular: "helix", plural: "helices", alternative: "helixes"}, // Words from Latin that end in -is change -is to -es {singular: "axis", plural: "axes", exact: true}, {singular: "crisis", plural: "crises"}, {singular: "ellipsis", plural: "ellipses", unidirectional: true}, // ellipse {singular: "genesis", plural: "geneses"}, {singular: "oasis", plural: "oases"}, {singular: "thesis", plural: "theses"}, {singular: "testis", plural: "testes"}, {singular: "base", plural: "bases"}, // popular case {singular: "basis", plural: "bases", unidirectional: true}, {singular: "alias", plural: "aliases", exact: true}, // no alia, no aliasis {singular: "vedalia", plural: "vedalias"}, // no vedalium, no vedaliases // Words that end in -ch, -o, -s, -sh, -x, -z (can be conflict with the others) {singular: "use", plural: "uses", exact: true}, // us vs use {singular: "abuse", plural: "abuses"}, {singular: "cause", plural: "causes"}, {singular: "clause", plural: "clauses"}, {singular: "cruse", plural: "cruses"}, {singular: "excuse", plural: "excuses"}, {singular: "fuse", plural: "fuses"}, {singular: "house", plural: "houses"}, {singular: "misuse", plural: "misuses"}, {singular: "muse", plural: "muses"}, {singular: "pause", plural: "pauses"}, {singular: "ache", plural: "aches"}, {singular: "topaz", plural: "topazes"}, {singular: "buffalo", plural: "buffaloes", alternative: "buffalos"}, {singular: "potato", plural: "potatoes"}, {singular: "tomato", plural: "tomatoes"}, // uncountables {singular: "equipment", uncountable: true}, {singular: "information", uncountable: true}, {singular: "jeans", uncountable: true}, {singular: "money", uncountable: true}, {singular: "news", uncountable: true}, {singular: "rice", uncountable: true}, // exceptions: -f to -ves, not -fe {singular: "dwarf", plural: "dwarfs", alternative: "dwarves"}, {singular: "hoof", plural: "hoofs", alternative: "hooves"}, {singular: "thief", plural: "thieves"}, // exceptions: instead of -f(e) to -ves {singular: "chive", plural: "chives"}, {singular: "hive", plural: "hives"}, {singular: "move", plural: "moves"}, // exceptions: instead of -y to -ies {singular: "movie", plural: "movies"}, {singular: "cookie", plural: "cookies"}, // exceptions: instead of -um to -a {singular: "pretorium", plural: "pretoriums"}, {singular: "agenda", plural: "agendas"}, // instead of plural of agendum // exceptions: instead of -um to -a (chemical element names) // Words from Latin that end in -a change -a to -ae {singular: "formula", plural: "formulas", alternative: "formulae"}, // also -um/-a // exceptions: instead of -o to -oes {singular: "shoe", plural: "shoes"}, {singular: "toe", plural: "toes", exact: true}, {singular: "graffiti", plural: "graffiti"}, // abbreviations {singular: "ID", plural: "IDs", exact: true}, } // singleToPlural is the highest priority map for Pluralize(). // singularToPluralSuffixList is used to build pluralRules for suffixes and // compound words. var singleToPlural = map[string]string{} // pluralToSingle is the highest priority map for Singularize(). // singularToPluralSuffixList is used to build singularRules for suffixes and // compound words. var pluralToSingle = map[string]string{} // NOTE: This map should not be built as reverse map of singleToPlural since // there are words that has the same plurals. // build singleToPlural and pluralToSingle with dictionary func init() { for _, wd := range dictionary { if singleToPlural[wd.singular] != "" { panic(fmt.Errorf("map singleToPlural already has an entry for %s", wd.singular)) } if wd.uncountable && wd.plural == "" { wd.plural = wd.singular } if wd.plural == "" { panic(fmt.Errorf("plural for %s is not provided", wd.singular)) } singleToPlural[wd.singular] = wd.plural if !wd.unidirectional { if pluralToSingle[wd.plural] != "" { panic(fmt.Errorf("map pluralToSingle already has an entry for %s", wd.plural)) } pluralToSingle[wd.plural] = wd.singular if wd.alternative != "" { if pluralToSingle[wd.alternative] != "" { panic(fmt.Errorf("map pluralToSingle already has an entry for %s", wd.alternative)) } pluralToSingle[wd.alternative] = wd.singular } } } } type singularToPluralSuffix struct { singular string plural string } // singularToPluralSuffixList is a list of "bidirectional" suffix rules for // the irregular plurals follow such rules. // // NOTE: IMPORTANT! The order of items in this list is the rule priority, not // alphabet order. The first match will be used to inflect. var singularToPluralSuffixList = []singularToPluralSuffix{ // https://en.wiktionary.org/wiki/Appendix:English_irregular_nouns#Rules // Words that end in -f or -fe change -f or -fe to -ves {"tive", "tives"}, // exception {"eaf", "eaves"}, {"oaf", "oaves"}, {"afe", "aves"}, {"arf", "arves"}, {"rfe", "rves"}, {"rf", "rves"}, {"lf", "lves"}, {"fe", "ves"}, // previously '[a-eg-km-z]fe' TODO: regex support // Words that end in -y preceded by a consonant change -y to -ies {"ay", "ays"}, {"ey", "eys"}, {"oy", "oys"}, {"quy", "quies"}, {"uy", "uys"}, {"y", "ies"}, // '[^aeiou]y' // Words from French that end in -u add an x (eg, château becomes châteaux) {"eau", "eaux"}, // it seems like 'eau' is the most popular form of this rule // Words from Latin that end in -a change -a to -ae; before -on to -a and -um to -a {"bula", "bulae"}, {"dula", "bulae"}, {"lula", "bulae"}, {"nula", "bulae"}, {"vula", "bulae"}, // Words from Greek that end in -on change -on to -a (eg, polyhedron becomes polyhedra) // https://en.wiktionary.org/wiki/Category:English_irregular_plurals_ending_in_"-a" {"hedron", "hedra"}, // Words from Latin that end in -um change -um to -a (eg, minimum becomes minima) // https://en.wiktionary.org/wiki/Category:English_irregular_plurals_ending_in_"-a" {"ium", "ia"}, // some exceptions especially chemical element names {"seum", "seums"}, {"eum", "ea"}, {"oum", "oa"}, {"stracum", "straca"}, {"dum", "da"}, {"elum", "ela"}, {"ilum", "ila"}, {"olum", "ola"}, {"ulum", "ula"}, {"llum", "lla"}, {"ylum", "yla"}, {"imum", "ima"}, {"ernum", "erna"}, {"gnum", "gna"}, {"brum", "bra"}, {"crum", "cra"}, {"terum", "tera"}, {"serum", "sera"}, {"trum", "tra"}, {"antum", "anta"}, {"atum", "ata"}, {"entum", "enta"}, {"etum", "eta"}, {"itum", "ita"}, {"otum", "ota"}, {"utum", "uta"}, {"ctum", "cta"}, {"ovum", "ova"}, // Words from Latin that end in -us change -us to -i or -era // not easy to make a simple rule. just add them all to the dictionary // Words from Latin that end in -ex change -ex to -ices (eg, vortex becomes vortices) // Words from Latin that end in -ix change -ix to -ices (eg, matrix becomes matrices) // for example, -dix, -dex, and -dice will have the same plural form so // making a simple rule is not possible for them {"trix", "trices"}, // ignore a few words end in trice // Words from Latin that end in -is change -is to -es (eg, thesis becomes theses) // -sis and -se has the same plural -ses so making a rule is not easy too. {"iasis", "iases"}, {"mesis", "meses"}, {"kinesis", "kineses"}, {"resis", "reses"}, {"gnosis", "gnoses"}, // e.g. diagnosis {"opsis", "opses"}, // e.g. synopsis {"ysis", "yses"}, // e.g. analysis // Words that end in -ch, -o, -s, -sh, -x, -z {"ouse", "ouses"}, {"lause", "lauses"}, {"us", "uses"}, // use/uses is in the dictionary {"ch", "ches"}, {"io", "ios"}, {"sh", "shes"}, {"ss", "sses"}, {"ez", "ezzes"}, {"iz", "izzes"}, {"tz", "tzes"}, {"zz", "zzes"}, {"ano", "anos"}, {"lo", "los"}, {"to", "tos"}, {"oo", "oos"}, {"o", "oes"}, {"x", "xes"}, // for abbreviations {"S", "Ses"}, // excluded rules: seems rare // Words from Hebrew that add -im or -ot (eg, cherub becomes cherubim) // - cherub (cherubs or cherubim), seraph (seraphs or seraphim) // Words from Greek that end in -ma change -ma to -mata // - The most of words end in -ma are in this category but it looks like // just adding -s is more popular. // Words from Latin that end in -nx change -nx to -nges // - The most of words end in -nx are in this category but it looks like // just adding -es is more popular. (sphinxes) // excluded rules: don't care at least for now: // Words that end in -ful that add an s after the -ful // Words that end in -s or -ese denoting a national of a particular country // Symbols or letters, which often add -'s } func init() { for i := len(singularToPluralSuffixList) - 1; i >= 0; i-- { InsertPluralRule(singularToPluralSuffixList[i].singular, singularToPluralSuffixList[i].plural) InsertSingularRule(singularToPluralSuffixList[i].plural, singularToPluralSuffixList[i].singular) } // build pluralRule and singularRule with dictionary for compound words for _, wd := range dictionary { if wd.exact { continue } if wd.uncountable && wd.plural == "" { wd.plural = wd.singular } InsertPluralRule(wd.singular, wd.plural) if !wd.unidirectional { InsertSingularRule(wd.plural, wd.singular) if wd.alternative != "" { InsertSingularRule(wd.alternative, wd.singular) } } } } flect-1.0.2/pluralize.go000066400000000000000000000026211437615061300151540ustar00rootroot00000000000000package flect import ( "strings" "sync" ) var pluralMoot = &sync.RWMutex{} // Pluralize returns a plural version of the string // user = users // person = people // datum = data func Pluralize(s string) string { return New(s).Pluralize().String() } // PluralizeWithSize will pluralize a string taking a number number into account. // PluralizeWithSize("user", 1) = user // PluralizeWithSize("user", 2) = users func PluralizeWithSize(s string, i int) string { if i == 1 || i == -1 { return New(s).Singularize().String() } return New(s).Pluralize().String() } // Pluralize returns a plural version of the string // user = users // person = people // datum = data func (i Ident) Pluralize() Ident { s := i.LastPart() if len(s) == 0 { return New("") } pluralMoot.RLock() defer pluralMoot.RUnlock() // check if the Original has an explicit entry in the map if p, ok := singleToPlural[i.Original]; ok { return i.ReplaceSuffix(i.Original, p) } if _, ok := pluralToSingle[i.Original]; ok { return i } ls := strings.ToLower(s) if _, ok := pluralToSingle[ls]; ok { return i } if p, ok := singleToPlural[ls]; ok { if s == Capitalize(s) { p = Capitalize(p) } return i.ReplaceSuffix(s, p) } for _, r := range pluralRules { if strings.HasSuffix(s, r.suffix) { return i.ReplaceSuffix(s, r.fn(s)) } } if strings.HasSuffix(ls, "s") { return i } return New(i.String() + "s") } flect-1.0.2/pluralize_test.go000066400000000000000000000033701437615061300162150ustar00rootroot00000000000000package flect import ( "testing" "github.com/stretchr/testify/require" ) func Test_Pluralize(t *testing.T) { for _, tt := range singlePluralAssertions { if tt.doPluralizeTest { t.Run(tt.singular, func(st *testing.T) { r := require.New(st) r.Equal(tt.plural, Pluralize(tt.singular), "pluralize %s", tt.singular) r.Equal(tt.plural, Pluralize(tt.plural), "pluralize %s", tt.plural) }) } } } func Test_PluralizeWithSize(t *testing.T) { for _, tt := range singlePluralAssertions { t.Run(tt.singular, func(st *testing.T) { r := require.New(st) if tt.doSingularizeTest { r.Equal(tt.singular, PluralizeWithSize(tt.singular, -1), "pluralize %d %s", -1, tt.singular) r.Equal(tt.singular, PluralizeWithSize(tt.plural, -1), "pluralize %d %s", -1, tt.plural) r.Equal(tt.singular, PluralizeWithSize(tt.singular, 1), "pluralize %d %s", 1, tt.singular) r.Equal(tt.singular, PluralizeWithSize(tt.plural, 1), "pluralize %d %s", 1, tt.plural) } if tt.doPluralizeTest { r.Equal(tt.plural, PluralizeWithSize(tt.singular, -2), "pluralize %d %s", -2, tt.singular) r.Equal(tt.plural, PluralizeWithSize(tt.plural, -2), "pluralize %d %s", -2, tt.plural) r.Equal(tt.plural, PluralizeWithSize(tt.singular, 0), "pluralize %d %s", 0, tt.singular) r.Equal(tt.plural, PluralizeWithSize(tt.plural, 0), "pluralize %d %s", 0, tt.plural) r.Equal(tt.plural, PluralizeWithSize(tt.singular, 2), "pluralize %d %s", 2, tt.singular) r.Equal(tt.plural, PluralizeWithSize(tt.plural, 2), "pluralize %d %s", 2, tt.plural) } }) } } func BenchmarkPluralize(b *testing.B) { for n := 0; n < b.N; n++ { for _, tt := range singlePluralAssertions { if tt.doPluralizeTest { Pluralize(tt.singular) Pluralize(tt.plural) } } } } flect-1.0.2/rule.go000066400000000000000000000004401437615061300141110ustar00rootroot00000000000000package flect type ruleFn func(string) string type rule struct { suffix string fn ruleFn } func simpleRuleFunc(suffix, repl string) func(string) string { return func(s string) string { s = s[:len(s)-len(suffix)] return s + repl } } func noop(s string) string { return s } flect-1.0.2/singular_rules.go000066400000000000000000000013561437615061300162070ustar00rootroot00000000000000package flect var singularRules = []rule{} // AddSingular adds a rule that will replace the given suffix with the replacement suffix. // The name is confusing. This function will be deprecated in the next release. func AddSingular(ext string, repl string) { InsertSingularRule(ext, repl) } // InsertSingularRule inserts a rule that will replace the given suffix with // the repl(acement) at the beginning of the list of the singularize rules. func InsertSingularRule(suffix, repl string) { singularMoot.Lock() defer singularMoot.Unlock() singularRules = append([]rule{{ suffix: suffix, fn: simpleRuleFunc(suffix, repl), }}, singularRules...) singularRules = append([]rule{{ suffix: repl, fn: noop, }}, singularRules...) } flect-1.0.2/singularize.go000066400000000000000000000025441437615061300155050ustar00rootroot00000000000000package flect import ( "strings" "sync" ) var singularMoot = &sync.RWMutex{} // Singularize returns a singular version of the string // users = user // data = datum // people = person func Singularize(s string) string { return New(s).Singularize().String() } // SingularizeWithSize will singular a string taking a number number into account. // SingularizeWithSize("user", 1) = user // SingularizeWithSize("user", 2) = users func SingularizeWithSize(s string, i int) string { return PluralizeWithSize(s, i) } // Singularize returns a singular version of the string // users = user // data = datum // people = person func (i Ident) Singularize() Ident { s := i.LastPart() if len(s) == 0 { return i } singularMoot.RLock() defer singularMoot.RUnlock() // check if the Original has an explicit entry in the map if p, ok := pluralToSingle[i.Original]; ok { return i.ReplaceSuffix(i.Original, p) } if _, ok := singleToPlural[i.Original]; ok { return i } ls := strings.ToLower(s) if p, ok := pluralToSingle[ls]; ok { if s == Capitalize(s) { p = Capitalize(p) } return i.ReplaceSuffix(s, p) } if _, ok := singleToPlural[ls]; ok { return i } for _, r := range singularRules { if strings.HasSuffix(s, r.suffix) { return i.ReplaceSuffix(s, r.fn(s)) } } if strings.HasSuffix(s, "s") { return i.ReplaceSuffix("s", "") } return i } flect-1.0.2/singularize_test.go000066400000000000000000000034661437615061300165500ustar00rootroot00000000000000package flect import ( "testing" "github.com/stretchr/testify/require" ) func Test_Singularize(t *testing.T) { for _, tt := range singlePluralAssertions { if tt.doSingularizeTest { t.Run(tt.plural, func(st *testing.T) { r := require.New(st) r.Equal(tt.singular, Singularize(tt.plural), "singularize %s", tt.plural) r.Equal(tt.singular, Singularize(tt.singular), "singularize %s", tt.singular) }) } } } func Test_SingularizeWithSize(t *testing.T) { for _, tt := range singlePluralAssertions { t.Run(tt.plural, func(st *testing.T) { r := require.New(st) if tt.doSingularizeTest { r.Equal(tt.singular, SingularizeWithSize(tt.plural, -1), "singularize %d %s", -1, tt.plural) r.Equal(tt.singular, SingularizeWithSize(tt.singular, -1), "singularize %d %s", -1, tt.singular) r.Equal(tt.singular, SingularizeWithSize(tt.plural, 1), "singularize %d %s", 1, tt.plural) r.Equal(tt.singular, SingularizeWithSize(tt.singular, 1), "singularize %d %s", 1, tt.singular) } if tt.doPluralizeTest { r.Equal(tt.plural, SingularizeWithSize(tt.plural, -2), "singularize %d %s", -2, tt.plural) r.Equal(tt.plural, SingularizeWithSize(tt.singular, -2), "singularize %d %s", -2, tt.singular) r.Equal(tt.plural, SingularizeWithSize(tt.plural, 0), "singularize %d %s", 0, tt.plural) r.Equal(tt.plural, SingularizeWithSize(tt.singular, 0), "singularize %d %s", 0, tt.singular) r.Equal(tt.plural, SingularizeWithSize(tt.plural, 2), "singularize %d %s", 2, tt.plural) r.Equal(tt.plural, SingularizeWithSize(tt.singular, 2), "singularize %d %s", 2, tt.singular) } }) } } func BenchmarkSingularize(b *testing.B) { for n := 0; n < b.N; n++ { for _, tt := range singlePluralAssertions { if tt.doSingularizeTest { Singularize(tt.singular) Singularize(tt.plural) } } } } flect-1.0.2/titleize.go000066400000000000000000000021361437615061300147770ustar00rootroot00000000000000package flect import ( "strings" "unicode" ) // Titleize will capitalize the start of each part // "Nice to see you!" = "Nice To See You!" // "i've read a book! have you?" = "I've Read A Book! Have You?" // "This is `code` ok" = "This Is `code` OK" func Titleize(s string) string { return New(s).Titleize().String() } // Titleize will capitalize the start of each part // "Nice to see you!" = "Nice To See You!" // "i've read a book! have you?" = "I've Read A Book! Have You?" // "This is `code` ok" = "This Is `code` OK" func (i Ident) Titleize() Ident { var parts []string // TODO: we need to reconsider the design. // this approach preserves inline code block as is but it also // preserves the other words start with a special character. // I would prefer: "*wonderful* world" to be "*Wonderful* World" for _, part := range i.Parts { // CAUTION: in unicode, []rune(str)[0] is not rune(str[0]) runes := []rune(part) x := string(unicode.ToTitle(runes[0])) if len(runes) > 1 { x += string(runes[1:]) } parts = append(parts, x) } return New(strings.Join(parts, " ")) } flect-1.0.2/titleize_test.go000066400000000000000000000013061437615061300160340ustar00rootroot00000000000000package flect import ( "testing" "github.com/stretchr/testify/require" ) func Test_Titleize(t *testing.T) { table := []tt{ {"", ""}, {"bob dylan", "Bob Dylan"}, {"Nice to see you!", "Nice To See You!"}, {"*hello*", "*hello*"}, {"hello *wonderful* world!", "Hello *wonderful* World!"}, // CHKME {"i've read a book! have you?", "I've Read A Book! Have You?"}, {"This is `code` ok", "This Is `code` OK"}, {"foo_bar", "Foo Bar"}, {"admin/widget", "Admin Widget"}, {"widget", "Widget"}, {"óbito", "Óbito"}, } for _, tt := range table { t.Run(tt.act, func(st *testing.T) { r := require.New(st) r.Equal(tt.exp, Titleize(tt.act)) r.Equal(tt.exp, Titleize(tt.exp)) }) } } flect-1.0.2/underscore.go000066400000000000000000000013271437615061300153200ustar00rootroot00000000000000package flect import ( "strings" "unicode" ) // Underscore a string // bob dylan --> bob_dylan // Nice to see you! --> nice_to_see_you // widgetID --> widget_id func Underscore(s string) string { return New(s).Underscore().String() } // Underscore a string // bob dylan --> bob_dylan // Nice to see you! --> nice_to_see_you // widgetID --> widget_id func (i Ident) Underscore() Ident { out := make([]string, 0, len(i.Parts)) for _, part := range i.Parts { var x strings.Builder x.Grow(len(part)) for _, c := range part { if unicode.IsLetter(c) || unicode.IsDigit(c) { x.WriteRune(c) } } if x.Len() > 0 { out = append(out, x.String()) } } return New(strings.ToLower(strings.Join(out, "_"))) } flect-1.0.2/underscore_test.go000066400000000000000000000015401437615061300163540ustar00rootroot00000000000000package flect import ( "testing" "github.com/stretchr/testify/require" ) func Test_Underscore(t *testing.T) { baseAcronyms["TLC"] = true table := []tt{ {"", ""}, {"bob dylan", "bob_dylan"}, {"Nice to see you!", "nice_to_see_you"}, {"*hello*", "hello"}, {"i've read a book! have you?", "ive_read_a_book_have_you"}, {"This is `code` ok", "this_is_code_ok"}, {"TLCForm", "tlc_form"}, } for _, tt := range table { t.Run(tt.act, func(st *testing.T) { r := require.New(st) r.Equal(tt.exp, Underscore(tt.act)) r.Equal(tt.exp, Underscore(tt.exp)) }) } } func Benchmark_Underscore(b *testing.B) { table := []string{ "", "bob dylan", "Nice to see you!", "*hello*", "i've read a book! have you?", "This is `code` ok", "TLCForm", } for n := 0; n < b.N; n++ { for i := range table { Underscore(table[i]) } } } flect-1.0.2/version.go000066400000000000000000000001151437615061300146260ustar00rootroot00000000000000package flect //Version holds Flect version number const Version = "v1.0.0"