pax_global_header00006660000000000000000000000064137666672620014537gustar00rootroot0000000000000052 comment=2837f13b90d4cad725675bd82ac3173ecd7a205f plural-1.3.0/000077500000000000000000000000001376666726200130375ustar00rootroot00000000000000plural-1.3.0/.gitignore000066400000000000000000000004321376666726200150260ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj/ _test/ vendor/ # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof *.out plural-1.3.0/.travis.yml000066400000000000000000000001721376666726200151500ustar00rootroot00000000000000language: go go: - tip install: - go get -t -v . - go get github.com/mattn/goveralls script: - ./build+test.sh plural-1.3.0/LICENSE000066400000000000000000000027021376666726200140450ustar00rootroot00000000000000Copyright (c) 2016, Rick Beton All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of plural nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. plural-1.3.0/README.md000066400000000000000000000032521376666726200143200ustar00rootroot00000000000000# plural - Simple Go API for Pluralisation. [![GoDoc](https://img.shields.io/badge/api-Godoc-blue.svg?style=flat-square)](https://godoc.org/github.com/rickb777/plural) [![Build Status](https://travis-ci.org/rickb777/plural.svg?branch=master)](https://travis-ci.org/rickb777/plural/builds) [![Coverage Status](https://coveralls.io/repos/github/rickb777/plural/badge.svg?branch=master&service=github)](https://coveralls.io/github/rickb777/plural?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/rickb777/plural)](https://goreportcard.com/report/github.com/rickb777/plural) [![Issues](https://img.shields.io/github/issues/rickb777/plural.svg)](https://github.com/rickb777/plural/issues) Package plural provides simple support for localising plurals in a flexible range of different styles. There are considerable differences around the world in the way plurals are handled. This is a simple but competent API for catering with these differences when presenting to people formatted text with numbers. This package is able to format **countable things** and **continuous values**. It can handle integers and floating point numbers equally and this allows you to decide to what extent each is appropriate. For example, `2 cars` might weigh `1.6 tonnes`; both categories are covered. This API is deliberately simple; it doesn't address the full gamut of internationalisation. If that's what you need, you should consider products such as https://github.com/nicksnyder/go-i18n instead. ## Installation go get -u github.com/rickb777/plural ## Status This library has been in reliable production use for some time. Versioning follows the well-known semantic version pattern. plural-1.3.0/build+test.sh000077500000000000000000000005751376666726200154570ustar00rootroot00000000000000#!/bin/bash -e cd "$(dirname $0)" PATH=$HOME/go/bin:$PATH if ! type -p goveralls; then echo go install github.com/mattn/goveralls go install github.com/mattn/goveralls fi echo date... go test -v -covermode=count -coverprofile=date.out . go tool cover -func=date.out [ -z "$COVERALLS_TOKEN" ] || goveralls -coverprofile=date.out -service=travis-ci -repotoken $COVERALLS_TOKEN plural-1.3.0/doc.go000066400000000000000000000020441376666726200141330ustar00rootroot00000000000000// Copyright 2016 Rick Beton. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package plural provides simple support for localising plurals in a flexible range of different styles. // // There are considerable differences around the world in the way plurals are handled. This is // a simple but competent API for catering with these differences when presenting to people formatted text with numbers. // // This package is able to format countable things and continuous values. It can handle integers // and floating point numbers equally and this allows you to decide to what extent each is appropriate. // // For example, "2 cars" might weigh "1.6 tonnes"; both categories are covered. // // This API is deliberately simple; it doesn't address the full gamut of internationalisation. If that's // what you need, you should consider products such as https://github.com/nicksnyder/go-i18n instead. // // Please see the examples and associated api documentation. // package plural plural-1.3.0/go.mod000066400000000000000000000000531376666726200141430ustar00rootroot00000000000000module github.com/rickb777/plural go 1.15 plural-1.3.0/plural.go000066400000000000000000000144361376666726200146750ustar00rootroot00000000000000package plural import ( "fmt" "strings" ) // Case is the inner element of this API and describes one case. When the number to be described // matches the number here, the corresponding format string will be used. If the format string // includes '%', then fmt.Sprintf will be used. Otherwise the format string will be returned verbatim. type Case struct { Number int Format string } // Plurals provides a list of plural cases in the order they will be searched. // For plurals of continuous ranges (e.g. weight), the cases must be in ascending number order. // For plurals of discrete ranges (i.e. integers), the cases can be in any order you require, // but will conventionally be in ascending number order. // If no match is found, the last case will be used. type Plurals []Case // Format searches through the plural cases for the first match. If none is found, the last // case is used. The value passed in can be any number type, or pointer to a number type, except // complex numbers are not supported. The value will be converted to an int in order to // find the first case that matches. // The only possible error arises if value has a type that is not numeric. // It panics if 'plurals' is empty. func (plurals Plurals) Format(value interface{}) (string, error) { switch x := value.(type) { case int: return plurals.FormatInt(x), nil case int8: return plurals.FormatInt(int(x)), nil case int16: return plurals.FormatInt(int(x)), nil case int32: return plurals.FormatInt(int(x)), nil case int64: return plurals.FormatInt(int(x)), nil case uint8: return plurals.FormatInt(int(x)), nil case uint16: return plurals.FormatInt(int(x)), nil case uint32: return plurals.FormatInt(int(x)), nil case uint64: return plurals.FormatInt(int(x)), nil case float32: return plurals.FormatFloat(x), nil case float64: return plurals.FormatFloat(float32(x)), nil case *int: return plurals.FormatInt(*x), nil case *int8: return plurals.FormatInt(int(*x)), nil case *int16: return plurals.FormatInt(int(*x)), nil case *int32: return plurals.FormatInt(int(*x)), nil case *int64: return plurals.FormatInt(int(*x)), nil case *uint: return plurals.FormatInt(int(*x)), nil case *uint8: return plurals.FormatInt(int(*x)), nil case *uint16: return plurals.FormatInt(int(*x)), nil case *uint32: return plurals.FormatInt(int(*x)), nil case *uint64: return plurals.FormatInt(int(*x)), nil case *float32: return plurals.FormatFloat(*x), nil case *float64: return plurals.FormatFloat(float32(*x)), nil case nil: return "", fmt.Errorf("Unexpected nil value for %s", plurals) default: return "", fmt.Errorf("Unexpected type %T for %v", x, value) } } // FormatInt expresses an int in plural form. It panics if 'plurals' is empty. func (plurals Plurals) FormatInt(value int) string { for _, c := range plurals { if value == c.Number { return c.FormatInt(value) } } c := plurals[len(plurals)-1] return c.FormatInt(value) } // FormatFloat expresses a float32 in plural form. It panics if 'plurals' is empty. func (plurals Plurals) FormatFloat(value float32) string { for _, c := range plurals { if value <= float32(c.Number) { return c.FormatFloat(value) } } c := plurals[len(plurals)-1] return c.FormatFloat(value) } // FormatInt renders a specific case with a given value. func (c Case) FormatInt(value int) string { if strings.IndexByte(c.Format, '%') < 0 { return c.Format } return fmt.Sprintf(c.Format, value) } // FormatFloat renders a specific case with a given value. func (c Case) FormatFloat(value float32) string { if strings.IndexByte(c.Format, '%') < 0 { return c.Format } return fmt.Sprintf(c.Format, value) } //------------------------------------------------------------------------------------------------- // String implements io.Stringer. func (plurals Plurals) String() string { ss := make([]string, 0, len(plurals)) for _, c := range plurals { ss = append(ss, c.String()) } return fmt.Sprintf("Plurals(%s)", strings.Join(ss, ", ")) } // String implements io.Stringer. func (c Case) String() string { return fmt.Sprintf("{%v -> %q}", c.Number, c.Format) } //------------------------------------------------------------------------------------------------- // ByOrdinal constructs a simple set of cases using small ordinals (0, 1, 2, 3 etc), which is a // common requirement. It is an alias for FromZero. func ByOrdinal(zeroth string, rest ...string) Plurals { return FromZero(zeroth, rest...) } // FromZero constructs a simple set of cases using small ordinals (0, 1, 2, 3 etc), which is a // common requirement. It prevents creation of a Plurals list that is empty, which would be invalid. // // The 'zeroth' string becomes Case{0, first}. The rest are appended similarly. Notice that the // counting starts from zero. // // So // // FromZero("nothing", "%v thing", "%v things") // // is simply a shorthand for // // Plurals{Case{0, "nothing"}, Case{1, "%v thing"}, Case{2, "%v things"}} // // which would also be valid but a little more verbose. // // This helper function is less flexible than constructing Plurals directly, but covers many common // situations. func FromZero(zeroth string, rest ...string) Plurals { p := make(Plurals, 0, len(rest)+1) p = append(p, Case{0, zeroth}) for i, c := range rest { p = append(p, Case{i+1, c}) } return p } // FromOne constructs a simple set of cases using small positive numbers (1, 2, 3 etc), which is a // common requirement. It prevents creation of a Plurals list that is empty, which would be invalid. // // The 'first' string becomes Case{1, first}. The rest are appended similarly. Notice that the // counting starts from one. // // So // // FromOne("%v thing", "%v things") // // is simply a shorthand for // // Plurals{Case{1, "%v thing"}, Case{2, "%v things"}} // // which would also be valid but a little more verbose. // // Note the behaviour of formatting when the count is zero. As a consequence of Format evaluating // the cases in order, FromOne(...).FormatInt(0) will pick the last case you provide, not the first. // // This helper function is less flexible than constructing Plurals directly, but covers many common // situations. func FromOne(first string, rest ...string) Plurals { p := make(Plurals, 0, len(rest)+1) p = append(p, Case{1, first}) for i, c := range rest { p = append(p, Case{i+2, c}) } return p } plural-1.3.0/plural_test.go000066400000000000000000000124241376666726200157270ustar00rootroot00000000000000package plural import ( "fmt" "testing" ) func TestPluralFormatIntEnglish(t *testing.T) { p012 := Plurals{Case{0, "nothing"}, Case{1, "%v thing"}, Case{2, "%v things"}} cases := []struct { n interface{} expect string }{ {0, "nothing"}, {1, "1 thing"}, {2, "2 things"}, {3, "3 things"}, {400, "400 things"}, {int8(0), "nothing"}, {int16(1), "1 thing"}, {int32(2), "2 things"}, {int64(3), "3 things"}, {uint8(0), "nothing"}, {uint16(1), "1 thing"}, {uint32(2), "2 things"}, {uint64(3), "3 things"}, {float32(2), "2 things"}, {float32(0), "nothing"}, {float32(0.1), "0.1 thing"}, {float32(2.1), "2.1 things"}, {float64(3), "3 things"}, {float64(3.00001), "3.00001 things"}, {fp32(3), "3 things"}, {fp64(3), "3 things"}, {ip(0), "nothing"}, {ip(1), "1 thing"}, {ip(2), "2 things"}, {ip(3), "3 things"}, {ip8(3), "3 things"}, {ip16(3), "3 things"}, {ip32(3), "3 things"}, {ip64(3), "3 things"}, {uip(3), "3 things"}, {uip8(3), "3 things"}, {uip16(3), "3 things"}, {uip32(3), "3 things"}, {uip64(3), "3 things"}, } for _, c := range cases { s, err := p012.Format(c.n) if err != nil { t.Errorf("Format(%d) => %v, want %s", c.n, err, c.expect) } else if s != c.expect { t.Errorf("Format(%d) == %s, want %s", c.n, s, c.expect) } } } func TestByOrdinalFromZero(t *testing.T) { p012 := ByOrdinal("nothing", "%d thing", "%d things") cases := []struct { n interface{} expect string }{ {0, "nothing"}, {1, "1 thing"}, {2, "2 things"}, {3, "3 things"}, {400, "400 things"}, } for _, c := range cases { s, err := p012.Format(c.n) if err != nil { t.Errorf("Format(%d) => %v, want %s", c.n, err, c.expect) } else if s != c.expect { t.Errorf("Format(%d) == %s, want %s", c.n, s, c.expect) } } } func TestFromOne(t *testing.T) { p012 := FromOne("one thing", "two things", "%d things") cases := []struct { n interface{} expect string }{ {0, "0 things"}, {1, "one thing"}, {2, "two things"}, {3, "3 things"}, {400, "400 things"}, } for _, c := range cases { s, err := p012.Format(c.n) if err != nil { t.Errorf("Format(%d) => %v, want %s", c.n, err, c.expect) } else if s != c.expect { t.Errorf("Format(%d) == %s, want %s", c.n, s, c.expect) } } } func TestWithoutPlaceholders(t *testing.T) { plurals := ByOrdinal("nothing", "one", "some", "many") cases := []struct { n interface{} expect string }{ {0, "nothing"}, {1, "one"}, {2, "some"}, {3, "many"}, {400, "many"}, {4.1, "many"}, } for _, c := range cases { s, err := plurals.Format(c.n) if err != nil { t.Errorf("Format(%d) => %v, want %s", c.n, err, c.expect) } else if s != c.expect { t.Errorf("Format(%d) == %s, want %s", c.n, s, c.expect) } } } func TestErrorCase(t *testing.T) { plurals := Plurals{Case{0, "nothing"}, Case{1, "%v thing"}, Case{2, "%v things"}} cases := []struct { n interface{} expect string }{ {"foo", "Unexpected type string for foo"}, {nil, `Unexpected nil value for Plurals({0 -> "nothing"}, {1 -> "%v thing"}, {2 -> "%v things"})`}, } for _, c := range cases { _, err := plurals.Format(c.n) if err == nil { t.Errorf("Format(%#v) no error, want %s", c.n, c.expect) } else if err.Error() != c.expect { t.Errorf("Format(%v) == %s, want %s", c.n, err.Error(), c.expect) } } } func ip(v int) *int { return &v } func ip8(v int8) *int8 { return &v } func ip16(v int16) *int16 { return &v } func ip32(v int32) *int32 { return &v } func ip64(v int64) *int64 { return &v } func uip(v uint) *uint { return &v } func uip8(v uint8) *uint8 { return &v } func uip16(v uint16) *uint16 { return &v } func uip32(v uint32) *uint32 { return &v } func uip64(v uint64) *uint64 { return &v } func fp32(v float32) *float32 { return &v } func fp64(v float64) *float64 { return &v } func ExamplePlurals() { // Plurals{} holds a sequence of cardinal cases where the first match is used, otherwise the last one is used. // The last case will typically include a "%v" placeholder for the number. // carPlurals and weightPlurals provide English formatted cases for some number of cars and their weight. var carPlurals = Plurals{ Case{0, "no cars weigh"}, Case{1, "%v car weighs"}, Case{2, "%v cars weigh"}, } var weightPlurals = Plurals{ Case{0, "nothing"}, Case{1, "%1.1f tonne"}, Case{2, "%1.1f tonnes"}, } for d := 0; d < 4; d++ { s, _ := carPlurals.Format(d) w, _ := weightPlurals.Format(float32(d) * 0.6) fmt.Printf("%s %s\n", s, w) } // Output: no cars weigh nothing // 1 car weighs 0.6 tonne // 2 cars weigh 1.2 tonnes // 3 cars weigh 1.8 tonnes } func ExampleByOrdinal() { // ByOrdinal(...) builds simple common kinds of plurals using small ordinals (0, 1, 2, 3 etc). // Notice that the counting starts from zero. var carPlurals = ByOrdinal("no cars weigh", "%v car weighs", "%v cars weigh") // Note %g, %f etc should be chosen appropriately; both are used here for illustration var weightPlurals = ByOrdinal("nothing", "%g tonne", "%1.1f tonnes") for d := 0; d < 5; d++ { s, _ := carPlurals.Format(d) w, _ := weightPlurals.Format(float32(d) * 0.5) fmt.Printf("%s %s\n", s, w) } // Output: no cars weigh nothing // 1 car weighs 0.5 tonne // 2 cars weigh 1 tonne // 3 cars weigh 1.5 tonnes // 4 cars weigh 2.0 tonnes }