comparison babel/support.py @ 353:cd8702e96f45

Added `validate_format helper function to `babel.support`.
author aronacher
date Tue, 17 Jun 2008 19:32:09 +0000
parents e5db561bf70e
children 249aab27c4b3
comparison
equal deleted inserted replaced
352:90849c44c531 353:cd8702e96f45
18 """ 18 """
19 19
20 from datetime import date, datetime, time 20 from datetime import date, datetime, time
21 import gettext 21 import gettext
22 22
23 try:
24 set
25 except NameError:
26 from sets import set
27
23 from babel.core import Locale 28 from babel.core import Locale
24 from babel.dates import format_date, format_datetime, format_time, LC_TIME 29 from babel.dates import format_date, format_datetime, format_time, LC_TIME
25 from babel.numbers import format_number, format_decimal, format_currency, \ 30 from babel.numbers import format_number, format_decimal, format_currency, \
26 format_percent, format_scientific, LC_NUMERIC 31 format_percent, format_scientific, LC_NUMERIC
27 from babel.util import UTC 32 from babel.util import UTC, PYTHON_FORMAT
28 33
29 __all__ = ['Format', 'LazyProxy', 'Translations'] 34 __all__ = ['Format', 'LazyProxy', 'Translations', 'validate_format']
30 __docformat__ = 'restructuredtext en' 35 __docformat__ = 'restructuredtext en'
31 36
32 37
33 class Format(object): 38 class Format(object):
34 """Wrapper class providing the various date and number formatting functions 39 """Wrapper class providing the various date and number formatting functions
317 self.files.extend(translations.files) 322 self.files.extend(translations.files)
318 return self 323 return self
319 324
320 def __repr__(self): 325 def __repr__(self):
321 return "<%s>" % (type(self).__name__) 326 return "<%s>" % (type(self).__name__)
327
328
329 #: list of format chars that are compatible to each other
330 _string_format_compatibilities = [
331 set(['i', 'd', 'u']),
332 set(['x', 'X']),
333 set(['f', 'F', 'g', 'G'])
334 ]
335
336
337 def validate_format(format, alternative):
338 """Test format string `alternative` against `format`. `format` can be the
339 msgid of a message and `alternative` one of the `msgstr`\s. The two
340 arguments are not interchangeable as `alternative` may contain less
341 placeholders if `format` uses named placeholders.
342
343 If `format` does not use string formatting a `TypeError` is raised.
344
345 If the string formatting of `alternative` is compatible to `format` the
346 function returns `None`, otherwise a `ValueError` is raised.
347
348 Examples for compatible format strings:
349
350 >>> validate_format('Hello %s!', 'Hallo %s!')
351 >>> validate_format('Hello %i!', 'Hallo %d!')
352
353 Example for an incompatible format strings:
354
355 >>> validate_format('Hello %(name)s!', 'Hallo %s!')
356 Traceback (most recent call last):
357 ...
358 TypeError: the format strings are of different kinds
359
360 :param format: The original format string
361 :param alternative: The alternative format string that should be checked
362 against format
363 :return: None on success
364 :raises ValueError: on an formatting error
365 """
366
367 def _parse(string):
368 result = []
369 for match in PYTHON_FORMAT.finditer(string):
370 name, format, typechar = match.groups()
371 if typechar == '%' and name is not None:
372 continue
373 result.append((name, typechar))
374 return result
375
376 def _compatible(a, b):
377 if a == b:
378 return True
379 for set in _string_format_compatibilities:
380 if a in set and b in set:
381 return True
382 return False
383
384 def _check_positional(results):
385 positional = None
386 for name, char in results:
387 if positional is None:
388 positional = name is None
389 else:
390 if (name is None) != positional:
391 raise ValueError('format string mixes positional '
392 'and named placeholders')
393 return bool(positional)
394
395 a, b = map(_parse, (format, alternative))
396
397 # if a does not use string formattings, we are dealing with invalid
398 # input data. This function only works if the first string provided
399 # does contain string format chars
400 if not a:
401 raise TypeError('original string provided does not use string '
402 'formatting.')
403
404 # now check if both strings are positional or named
405 a_positional, b_positional = map(_check_positional, (a, b))
406 if a_positional and not b_positional and not b:
407 raise ValueError('placeholders are incompatible')
408 elif a_positional != b_positional:
409 raise TypeError('the format strings are of different kinds')
410
411 # if we are operating on positional strings both must have the
412 # same number of format chars and those must be compatible
413 if a_positional:
414 if len(a) != len(b):
415 raise ValueError('positional format placeholders unbalanced')
416 for idx, ((_, first), (_, second)) in enumerate(zip(a, b)):
417 if not _compatible(first, second):
418 raise ValueError('incompatible format for placeholder %d: '
419 '%r and %r are not compatible' %
420 (idx + 1, first, second))
421
422 # otherwise the second string must not have names the first one
423 # doesn't have and the types of those included must be compatible
424 else:
425 type_map = dict(a)
426 for name, typechar in b:
427 if name not in type_map:
428 raise ValueError('unknown named placeholder %r' % name)
429 elif not _compatible(typechar, type_map[name]):
430 raise ValueError('incompatible format for placeholder %r: '
431 '%r and %r are not compatible' %
432 (name, typechar, type_map[name]))
Copyright (C) 2012-2017 Edgewall Software