Skip to content

Commit 0927acc

Browse files
committed
rbnf: make spell_number API less kwargsy
1 parent 192fecf commit 0927acc

3 files changed

Lines changed: 75 additions & 47 deletions

File tree

babel/numbers.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -663,15 +663,15 @@ def __init__(self, message, suggestions=None):
663663
self.suggestions = suggestions
664664

665665

666-
def spell_number(number, locale=LC_NUMERIC, **kwargs):
666+
def spell_number(number, locale=LC_NUMERIC, ruleset=None):
667667
"""Return value spelled out for a specific locale
668668
669669
:param number: the number to format
670670
:param locale: the `Locale` object or locale identifier
671-
:param kwargs: optional locale specific parameters
671+
:param ruleset: the ruleset to use; defaults to regular numbers.
672672
"""
673673
speller = RuleBasedNumberFormat.negotiate(locale)
674-
return speller.format(number, **kwargs)
674+
return speller.format(number, ruleset=ruleset)
675675

676676

677677
def get_rbnf_rules(locale=LC_NUMERIC):

babel/rbnf.py

Lines changed: 63 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,25 @@
8484
# normal rule means a number is specified
8585

8686

87-
class RBNFError(Exception): pass
88-
class TokenizationError(RBNFError): pass
89-
class RulesetNotFound(RBNFError): pass
90-
class RuleNotFound(RBNFError): pass
87+
class RBNFError(Exception):
88+
pass
89+
90+
91+
class TokenizationError(RBNFError):
92+
pass
93+
94+
95+
class RulesetNotFound(RBNFError):
96+
pass
97+
98+
99+
class RuleNotFound(RBNFError):
100+
pass
101+
102+
103+
class RulesetSubstitutionWarning(UserWarning):
104+
pass
105+
91106

92107
TokenInfo = collections.namedtuple('TokenInfo', 'type reference optional')
93108

@@ -221,52 +236,65 @@ def available_rulesets(self):
221236
"""list available public rulesets"""
222237
return [r.name for r in self.rulesets if not r.private]
223238

224-
225-
def format(self, number, ordinal=False, year=False, ruleset=None, **kwargs):
226-
"""spell an actual number (int/float/decimal)
227-
228-
Search available_rulesets for an entry point
229-
default is `spellout-numbering`.
230-
231-
If year is True: use spellout-numbering-year
232-
If ordinal is True: use spellout-ordinal
233-
If year and ordinal both True: raise error
234-
235-
TODO
236-
If no `spellout-ordinal`:
237-
if has `spellout-ordinal-*`: use first one, issue warning
238-
239+
def _find_matching_ruleset(self, prefix):
240+
available_rulesets = self.available_rulesets
241+
if prefix in available_rulesets:
242+
return (prefix, True)
243+
# Sorting here avoids use of more specific ("spellout-ordinal-sinokorean-count")
244+
# rulesets when a shorter one might be available.
245+
for ruleset in sorted(available_rulesets):
246+
if ruleset.startswith(prefix):
247+
return (ruleset, False)
248+
return (None, False)
249+
250+
def match_ruleset(self, ruleset):
239251
"""
240-
if ordinal and year:
241-
raise ValueError('both ordinal and year is not possible')
242-
if ordinal:
243-
search = ruleset or 'spellout-ordinal'
244-
elif year:
245-
search = ruleset or 'spellout-year'
246-
else:
247-
search = ruleset or 'spellout-numbering'
248-
249-
ruleset = self.get_ruleset(search)
250-
251-
if ruleset is None:
252-
raise RulesetNotFound(search)
253-
254-
return ruleset.apply(number, self)
252+
Try to find a matching ruleset given a ruleset name or alias ("year", "ordinal").
253+
"""
254+
if ruleset == "year":
255+
ruleset = "spellout-numbering-year"
256+
elif ruleset == "ordinal":
257+
ruleset, exact_match = self._find_matching_ruleset("spellout-ordinal")
258+
if not ruleset:
259+
raise RulesetNotFound("No ordinal ruleset is available for %s" % (
260+
self._locale,
261+
))
262+
if not exact_match:
263+
warnings.warn("Using non-specific ordinal ruleset %s" % ruleset, RulesetSubstitutionWarning)
264+
ruleset_obj = self.get_ruleset(ruleset)
265+
if not ruleset_obj:
266+
raise RulesetNotFound("Ruleset %r is not one of the ones available for %s: %r" % (
267+
ruleset,
268+
self._locale,
269+
self.available_rulesets,
270+
))
271+
return ruleset_obj
272+
273+
def format(self, number, ruleset=None):
274+
"""Format a number (int/float/decimal) with spelling rules.
275+
276+
Ruleset may be an actual ruleset name for the locale,
277+
or one of the aliases "year" or "ordinal".
278+
"""
279+
if not ruleset:
280+
ruleset = "spellout-numbering"
255281

282+
return self.match_ruleset(ruleset).apply(number, self)
256283

257284
def get_ruleset(self, name):
258285
for r in self.rulesets:
259286
if r.name == name:
260287
return r
261288

262-
263289
@classmethod
264290
def negotiate(cls, locale):
265291
"""
266292
Negotiate proper RBNF rules based on global data item `rbnf_locales`
267293
Caching is not necessary the Locale object does that pretty well
268294
"""
269295
loc = Locale.negotiate([str(Locale.parse(locale))], get_global('rbnf_locales'))
296+
if not loc:
297+
raise RulesetNotFound("No RBNF rules available for %s" % locale)
270298
return cls(loc)
271299

272300

tests/test_number_spelling.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ def test_basic(self):
2121

2222
def test_negotiation(self):
2323
for lid in locale_identifiers():
24-
loc = rbnf.RuleBasedNumberFormat.negotiate(lid)._locale
25-
if loc is None:
24+
try:
25+
loc = rbnf.RuleBasedNumberFormat.negotiate(lid)._locale
26+
except rbnf.RulesetNotFound:
2627
# generate warning if necessary
27-
pass
28-
else:
29-
# test groups
30-
for k in loc._data['rbnf_rules']:
31-
assert k in rbnf.RuleBasedNumberFormat.group_types
28+
continue
29+
# test groups
30+
for k in loc._data['rbnf_rules']:
31+
assert k in rbnf.RuleBasedNumberFormat.group_types
3232

3333
def test_tokenization(self):
3434

@@ -81,7 +81,7 @@ def _spell(x):
8181

8282
def test_hu_HU_ordinal(self):
8383
def _spell(x):
84-
return numbers.spell_number(x, locale='hu_HU', ordinal=True).replace(soft_hyphen, '')
84+
return numbers.spell_number(x, locale='hu_HU', ruleset="ordinal").replace(soft_hyphen, '')
8585

8686
assert _spell(0) == "nulla"
8787
# assert _spell(0) == "nulladik"
@@ -120,7 +120,7 @@ def _spell(x):
120120

121121
def test_en_GB_ordinal(self):
122122
def _spell(x):
123-
return numbers.spell_number(x, locale='en_GB', ordinal=True).replace(soft_hyphen, '')
123+
return numbers.spell_number(x, locale='en_GB', ruleset="ordinal").replace(soft_hyphen, '')
124124

125125
assert _spell(0) == "zeroth"
126126
assert _spell(1) == "first"

0 commit comments

Comments
 (0)