|
84 | 84 | # normal rule means a number is specified |
85 | 85 |
|
86 | 86 |
|
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 | + |
91 | 106 |
|
92 | 107 | TokenInfo = collections.namedtuple('TokenInfo', 'type reference optional') |
93 | 108 |
|
@@ -221,52 +236,65 @@ def available_rulesets(self): |
221 | 236 | """list available public rulesets""" |
222 | 237 | return [r.name for r in self.rulesets if not r.private] |
223 | 238 |
|
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): |
239 | 251 | """ |
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" |
255 | 281 |
|
| 282 | + return self.match_ruleset(ruleset).apply(number, self) |
256 | 283 |
|
257 | 284 | def get_ruleset(self, name): |
258 | 285 | for r in self.rulesets: |
259 | 286 | if r.name == name: |
260 | 287 | return r |
261 | 288 |
|
262 | | - |
263 | 289 | @classmethod |
264 | 290 | def negotiate(cls, locale): |
265 | 291 | """ |
266 | 292 | Negotiate proper RBNF rules based on global data item `rbnf_locales` |
267 | 293 | Caching is not necessary the Locale object does that pretty well |
268 | 294 | """ |
269 | 295 | 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) |
270 | 298 | return cls(loc) |
271 | 299 |
|
272 | 300 |
|
|
0 commit comments