Modified: bloodhound/branches/trac-1.0.2-integration/trac/trac/util/tests/datefmt.py URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/trac/util/tests/datefmt.py?rev=1639823&r1=1639822&r2=1639823&view=diff ============================================================================== --- bloodhound/branches/trac-1.0.2-integration/trac/trac/util/tests/datefmt.py (original) +++ bloodhound/branches/trac-1.0.2-integration/trac/trac/util/tests/datefmt.py Sat Nov 15 01:14:46 2014 @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2007-2009 Edgewall Software +# Copyright (C) 2007-2013 Edgewall Software # Copyright (C) 2007 Matt Good <t...@matt-good.net> # All rights reserved. # @@ -15,23 +15,21 @@ # Author: Matt Good <t...@matt-good.net> import datetime +import locale import os import time import unittest +import trac.tests.compat from trac.core import TracError -from trac.util import datefmt, translation +from trac.util import datefmt try: - import pytz -except ImportError: - pytz = None -try: from babel import Locale except ImportError: Locale = None -if pytz is None: +if datefmt.pytz is None: PytzTestCase = None else: class PytzTestCase(unittest.TestCase): @@ -99,19 +97,6 @@ else: dt = datefmt.to_datetime(t, tz) self.assertEqual(datetime.timedelta(0, 7200), dt.utcoffset()) - def test_parse_date_across_dst_boundary(self): - tz = datefmt.get_timezone('Europe/Zurich') - # DST start - 31 March, 02:00 - format = '%Y-%m-%d %H:%M:%S %Z%z' - expected = '2002-03-31 03:30:00 CEST+0200' - # iso8601 - t = datefmt.parse_date('2002-03-31T02:30:00', tz) - self.assertEqual(expected, t.strftime(format)) - # strptime - t = datetime.datetime(2002, 3, 31, 2, 30) - t = datefmt.parse_date(t.strftime('%x %X'), tz) - self.assertEqual(expected, t.strftime(format)) - def test_to_datetime_astimezone(self): tz = datefmt.get_timezone('Europe/Paris') t = datetime.datetime(2012, 3, 25, 2, 15, tzinfo=datefmt.utc) @@ -121,11 +106,11 @@ else: def test_to_datetime_tz_from_naive_datetime_is_localtz(self): t = datetime.datetime(2012, 3, 25, 2, 15) dt = datefmt.to_datetime(t) - self.assert_(isinstance(dt.tzinfo, datefmt.LocalTimezone)) + self.assertIsInstance(dt.tzinfo, datefmt.LocalTimezone) def test_to_datetime_tz_from_now_is_localtz(self): dt = datefmt.to_datetime(None) - self.assert_(isinstance(dt.tzinfo, datefmt.LocalTimezone)) + self.assertIsInstance(dt.tzinfo, datefmt.LocalTimezone) class ParseISO8601TestCase(unittest.TestCase): @@ -205,7 +190,7 @@ class ParseISO8601TestCase(unittest.Test t = datetime.datetime(2012, 10, 11, 2, 40, 57, 0, datefmt.localtz) dt = datefmt.parse_date('2012-10-11T02:40:57') self.assertEqual(t, dt) - self.assert_(isinstance(dt.tzinfo, datefmt.LocalTimezone)) + self.assertIsInstance(dt.tzinfo, datefmt.LocalTimezone) def test_iso8601_naive_tz_used_tzinfo_arg(self): tz = datefmt.timezone('GMT +1:00') @@ -221,7 +206,7 @@ class ParseISO8601TestCase(unittest.Test self.assertEqual(datetime.timedelta(hours=-9, minutes=-30), dt.utcoffset()) - if pytz: + if datefmt.pytz: def test_iso8601_naive_tz_normalize_non_existent_time(self): t = datetime.datetime(2012, 3, 25, 1, 15, 57, 0, datefmt.utc) tz = datefmt.timezone('Europe/Paris') @@ -238,6 +223,128 @@ class ParseISO8601TestCase(unittest.Test self.assertEqual(2, dt.hour) self.assertEqual(datetime.timedelta(hours=1), dt.utcoffset()) + def test_hint_iso8601(self): + def validate(locale=None): + try: + datefmt.parse_date('2001-0a-01', locale=locale, hint='iso8601') + raise self.failureException('TracError not raised') + except TracError, e: + self.assertIn(u'"YYYY-MM-DDThh:mm:ss±hh:mm"', unicode(e)) + + validate(locale=None) + validate(locale='iso8601') + if Locale: + validate(locale=Locale.parse('en_US')) + + +class ParseDateWithoutBabelTestCase(unittest.TestCase): + + if os.name != 'nt': + locales = {} + else: + # LCID: http://msdn.microsoft.com/en-us/goglobal/bb964664.aspx + # NLS: http://msdn.microsoft.com/en-us/goglobal/bb896001.aspx + ref_time = time.gmtime(123456) + locales = { + 'en_US.UTF8': ('English_United States', '1/2/1970 10:17:36 AM'), + 'en_GB.UTF8': ('English_United Kingdom', '02/01/1970 10:17:36'), + 'fr_FR.UTF8': ('French_France', '02/01/1970 10:17:36'), + 'ja_JP.UTF8': ('Japanese_Japan', '1970/01/02 10:17:36'), + 'zh_CN.UTF8': ("Chinese_People's Republic of China", + '1970/1/2 10:17:36') + } + + def setUp(self): + rv = locale.getlocale(locale.LC_TIME) + self._orig_locale = rv if rv[0] else 'C' + + def tearDown(self): + locale.setlocale(locale.LC_ALL, self._orig_locale) + + def _setlocale(self, id): + try: + mapped, ref_strftime = self.locales.get(id, (id, None)) + locale.setlocale(locale.LC_ALL, mapped) + return (ref_strftime is None or + ref_strftime == time.strftime('%x %X', self.ref_time)) + except locale.Error: + return False + + def test_parse_date_libc(self): + tz = datefmt.timezone('GMT +2:00') + expected = datetime.datetime(2010, 8, 28, 13, 45, 56, 0, tz) + expected_minute = datetime.datetime(2010, 8, 28, 13, 45, 0, 0, tz) + expected_date = datetime.datetime(2010, 8, 28, 0, 0, 0, 0, tz) + + self.assertTrue(self._setlocale('C')) + self.assertEqual(expected, + datefmt.parse_date('08/28/10 13:45:56', tz)) + self.assertEqual(expected_minute, + datefmt.parse_date('08/28/10 13:45', tz)) + self.assertEqual(expected_date, datefmt.parse_date('08/28/10', tz)) + self.assertEqual(expected_minute, + datefmt.parse_date('28 Aug 2010 1:45 pm', tz)) + + if self._setlocale('en_US.UTF8'): + self.assertEqual(expected, + datefmt.parse_date('Aug 28, 2010 1:45:56 PM', tz)) + self.assertEqual(expected, + datefmt.parse_date('8 28, 2010 1:45:56 PM', tz)) + self.assertEqual(expected, + datefmt.parse_date('28 Aug 2010 1:45:56 PM', tz)) + self.assertEqual(expected, + datefmt.parse_date('28 Aug 2010 PM 1:45:56', tz)) + self.assertEqual(expected, + datefmt.parse_date('28 Aug 2010 13:45:56', tz)) + self.assertEqual(expected_minute, + datefmt.parse_date('28 Aug 2010 PM 1:45', tz)) + self.assertEqual(expected_date, + datefmt.parse_date('28 Aug 2010', tz)) + + if self._setlocale('en_GB.UTF8'): + self.assertEqual(expected, + datefmt.parse_date('28 Aug 2010 13:45:56', tz)) + self.assertEqual(expected_minute, + datefmt.parse_date('28 Aug 2010 PM 1:45', tz)) + self.assertEqual(expected_date, + datefmt.parse_date('28 Aug 2010', tz)) + + if self._setlocale('fr_FR.UTF8'): + self.assertEqual(expected, + datefmt.parse_date(u'28 août 2010 13:45:56', tz)) + self.assertEqual(expected, + datefmt.parse_date(u'août 28 2010 13:45:56', tz)) + self.assertEqual(expected_minute, + datefmt.parse_date(u'août 28 2010 13:45', tz)) + self.assertEqual(expected_date, + datefmt.parse_date(u'août 28 2010', tz)) + self.assertEqual(expected_minute, + datefmt.parse_date('Aug 28 2010 1:45 pm', tz)) + + if self._setlocale('ja_JP.UTF8'): + self.assertEqual(expected, + datefmt.parse_date('2010/08/28 13:45:56', tz)) + self.assertEqual(expected_minute, + datefmt.parse_date('2010/08/28 13:45', tz)) + self.assertEqual(expected_date, + datefmt.parse_date('2010/08/28', tz)) + self.assertEqual(expected_minute, + datefmt.parse_date('2010/Aug/28 1:45 pm', tz)) + + if self._setlocale('zh_CN.UTF8'): + self.assertEqual(expected, + datefmt.parse_date(u'2010-8-28 ä¸å01:45:56', tz)) + self.assertEqual(expected, + datefmt.parse_date(u'2010-8-28 01:45:56ä¸å', tz)) + self.assertEqual(expected_minute, + datefmt.parse_date(u'2010-8-28 ä¸å01:45', tz)) + self.assertEqual(expected_minute, + datefmt.parse_date(u'2010-8-28 01:45ä¸å', tz)) + self.assertEqual(expected_date, + datefmt.parse_date('2010-8-28', tz)) + self.assertEqual(expected_minute, + datefmt.parse_date('2010-Aug-28 01:45 pm', tz)) + class ParseRelativeDateTestCase(unittest.TestCase): @@ -417,7 +524,7 @@ class ParseRelativeDateTestCase(unittest self.assertEqual(datetime.datetime(2012, 3, 25, 3, 14, 59, tzinfo=tz), datefmt._parse_relative_time('last second', tz, now)) - if pytz: + if datefmt.pytz: def test_time_interval_across_dst(self): tz = datefmt.timezone('Europe/Paris') now = datefmt.to_datetime(datetime.datetime(2012, 3, 25, 3, 0, 41), @@ -458,9 +565,9 @@ class ParseDateValidRangeTestCase(unitte datefmt.parse_date('2038-01-19T03:14:07Z') try: datefmt.parse_date('9999-12-31T23:59:59-12:00') - raise AssertionError('TracError not raised') + raise self.failureException('TracError not raised') except TracError, e: - self.assert_('is outside valid range' in unicode(e)) + self.assertIn('is outside valid range', unicode(e)) def test_min_timestamp(self): if os.name != 'nt': @@ -472,9 +579,9 @@ class ParseDateValidRangeTestCase(unitte datefmt.parse_date('1970-01-01T00:00:00Z') try: datefmt.parse_date('0001-01-01T00:00:00+14:00') - raise AssertionError('TracError not raised') + raise self.failureException('TracError not raised') except TracError, e: - self.assert_('is outside valid range' in unicode(e)) + self.assertIn('is outside valid range', unicode(e)) class DateFormatTestCase(unittest.TestCase): @@ -523,6 +630,10 @@ class DateFormatTestCase(unittest.TestCa self.assertEqual(datefmt.to_datetime(23L, tz), expected) self.assertEqual(datefmt.to_datetime(23.0, tz), expected) + def test_to_datetime_typeerror(self): + self.assertRaises(TypeError, datefmt.to_datetime, 'blah') + self.assertRaises(TypeError, datefmt.to_datetime, u'bl\xe1h') + def test_format_datetime_utc(self): t = datetime.datetime(1970, 1, 1, 1, 0, 23, 0, datefmt.utc) expected = '1970-01-01T01:00:23Z' @@ -559,6 +670,21 @@ class DateFormatTestCase(unittest.TestCa self.assertEqual(datefmt.format_time(t, 'iso8601', gmt01), expected.split('T')[1]) + def test_format_iso8601_before_1900(self): + t = datetime.datetime(1899, 12, 30, 23, 58, 59, 123456, datefmt.utc) + self.assertEqual('1899-12-30T23:58:59Z', + datefmt.format_datetime(t, 'iso8601', datefmt.utc)) + self.assertEqual('1899-12-30', + datefmt.format_datetime(t, 'iso8601date', + datefmt.utc)) + self.assertEqual('1899-12-30', + datefmt.format_date(t, 'iso8601', datefmt.utc)) + self.assertEqual('23:58:59Z', + datefmt.format_datetime(t, 'iso8601time', + datefmt.utc)) + self.assertEqual('23:58:59Z', + datefmt.format_time(t, 'iso8601', datefmt.utc)) + def test_format_date_accepts_date_instances(self): a_date = datetime.date(2009, 8, 20) self.assertEqual('2009-08-20', @@ -660,38 +786,68 @@ class ISO8601TestCase(unittest.TestCase) datefmt.format_time(t, 'medium', tz, 'iso8601')) self.assertEqual('2010-08-28T11:45:56', datefmt.format_datetime(t, 'medium', tz, 'iso8601')) - for f in ('long', 'full'): - self.assertEqual('11:45:56+02:00', - datefmt.format_time(t, f, tz, 'iso8601')) - self.assertEqual('2010-08-28T11:45:56+02:00', - datefmt.format_datetime(t, f, tz, 'iso8601')) + self.assertEqual('11:45:56+02:00', + datefmt.format_time(t, 'long', tz, 'iso8601')) + self.assertEqual('2010-08-28T11:45:56+02:00', + datefmt.format_datetime(t, 'long', tz, 'iso8601')) + self.assertEqual('11:45:56.123456+02:00', + datefmt.format_time(t, 'full', tz, 'iso8601')) + self.assertEqual('2010-08-28T11:45:56.123456+02:00', + datefmt.format_datetime(t, 'full', tz, 'iso8601')) + + def test_with_babel_format_before_1900(self): + tz = datefmt.timezone('GMT +2:00') + t = datetime.datetime(1899, 8, 28, 11, 45, 56, 123456, tz) + for f in ('short', 'medium', 'long', 'full'): + self.assertEqual('1899-08-28', + datefmt.format_date(t, f, tz, 'iso8601')) + self.assertEqual('11:45', + datefmt.format_time(t, 'short', tz, 'iso8601')) + self.assertEqual('1899-08-28T11:45', + datefmt.format_datetime(t, 'short', tz, 'iso8601')) + self.assertEqual('11:45:56', + datefmt.format_time(t, 'medium', tz, 'iso8601')) + self.assertEqual('1899-08-28T11:45:56', + datefmt.format_datetime(t, 'medium', tz, 'iso8601')) + self.assertEqual('11:45:56+02:00', + datefmt.format_time(t, 'long', tz, 'iso8601')) + self.assertEqual('1899-08-28T11:45:56+02:00', + datefmt.format_datetime(t, 'long', tz, 'iso8601')) + self.assertEqual('11:45:56.123456+02:00', + datefmt.format_time(t, 'full', tz, 'iso8601')) + self.assertEqual('1899-08-28T11:45:56.123456+02:00', + datefmt.format_datetime(t, 'full', tz, 'iso8601')) def test_hint(self): try: datefmt.parse_date('***', locale='iso8601', hint='date') + raise self.failureException('TracError not raised') except TracError, e: - self.assert_('"YYYY-MM-DD"' in unicode(e)) + self.assertIn('"YYYY-MM-DD"', unicode(e)) try: datefmt.parse_date('***', locale='iso8601', hint='datetime') + raise self.failureException('TracError not raised') except TracError, e: - self.assert_(u'"YYYY-MM-DDThh:mm:ss±hh:mm"' in unicode(e)) + self.assertIn(u'"YYYY-MM-DDThh:mm:ss±hh:mm"', unicode(e)) try: datefmt.parse_date('***', locale='iso8601', hint='foobar') + raise self.failureException('TracError not raised') except TracError, e: - self.assert_('"foobar"' in unicode(e)) + self.assertIn('"foobar"', unicode(e)) if Locale is None: I18nDateFormatTestCase = None else: class I18nDateFormatTestCase(unittest.TestCase): + def test_i18n_format_datetime(self): tz = datefmt.timezone('GMT +2:00') t = datetime.datetime(2010, 8, 28, 11, 45, 56, 123456, datefmt.utc) en_US = Locale.parse('en_US') - self.assertEqual('Aug 28, 2010 1:45:56 PM', - datefmt.format_datetime(t, tzinfo=tz, - locale=en_US)) + self.assertIn(datefmt.format_datetime(t, tzinfo=tz, locale=en_US), + ('Aug 28, 2010 1:45:56 PM', + 'Aug 28, 2010, 1:45:56 PM')) # CLDR 23 en_GB = Locale.parse('en_GB') self.assertEqual('28 Aug 2010 13:45:56', datefmt.format_datetime(t, tzinfo=tz, @@ -706,9 +862,9 @@ else: self.assertEqual(u'13:45:56 28-08-2010', datefmt.format_datetime(t, tzinfo=tz, locale=vi)) zh_CN = Locale.parse('zh_CN') - self.assertEqual(u'2010-8-28 ä¸å01:45:56', - datefmt.format_datetime(t, tzinfo=tz, - locale=zh_CN)) + self.assertIn(datefmt.format_datetime(t, tzinfo=tz, locale=zh_CN), + (u'2010-8-28 ä¸å01:45:56', + u'2010å¹´8æ28æ¥ ä¸å1:45:56')) def test_i18n_format_date(self): tz = datefmt.timezone('GMT +2:00') @@ -729,8 +885,8 @@ else: self.assertEqual(u'07-08-2010', datefmt.format_date(t, tzinfo=tz, locale=vi)) zh_CN = Locale.parse('zh_CN') - self.assertEqual(u'2010-8-7', - datefmt.format_date(t, tzinfo=tz, locale=zh_CN)) + self.assertIn(datefmt.format_date(t, tzinfo=tz, locale=zh_CN), + (u'2010-8-7', u'2010å¹´8æ7æ¥')) def test_i18n_format_time(self): tz = datefmt.timezone('GMT +2:00') @@ -752,8 +908,8 @@ else: datefmt.format_time(t, tzinfo=tz, locale=ja)) self.assertEqual('13:45:56', datefmt.format_time(t, tzinfo=tz, locale=vi)) - self.assertEqual(u'ä¸å01:45:56', - datefmt.format_time(t, tzinfo=tz, locale=zh_CN)) + self.assertIn(datefmt.format_time(t, tzinfo=tz, locale=zh_CN), + (u'ä¸å01:45:56', u'ä¸å1:45:56')) def test_i18n_datetime_hint(self): en_US = Locale.parse('en_US') @@ -763,18 +919,19 @@ else: vi = Locale.parse('vi') zh_CN = Locale.parse('zh_CN') - self.assert_(datefmt.get_datetime_format_hint(en_US) - in ('MMM d, yyyy h:mm:ss a', 'MMM d, y h:mm:ss a')) - self.assert_(datefmt.get_datetime_format_hint(en_GB) - in ('d MMM yyyy HH:mm:ss', 'd MMM y HH:mm:ss')) - self.assert_(datefmt.get_datetime_format_hint(fr) - in ('d MMM yyyy HH:mm:ss', 'd MMM y HH:mm:ss')) - self.assertEqual('yyyy/MM/dd H:mm:ss', - datefmt.get_datetime_format_hint(ja)) - self.assertEqual('HH:mm:ss dd-MM-yyyy', - datefmt.get_datetime_format_hint(vi)) - self.assertEqual('yyyy-M-d ahh:mm:ss', - datefmt.get_datetime_format_hint(zh_CN)) + self.assertIn(datefmt.get_datetime_format_hint(en_US), + ('MMM d, yyyy h:mm:ss a', 'MMM d, y h:mm:ss a', + 'MMM d, y, h:mm:ss a')) + self.assertIn(datefmt.get_datetime_format_hint(en_GB), + ('d MMM yyyy HH:mm:ss', 'd MMM y HH:mm:ss')) + self.assertIn(datefmt.get_datetime_format_hint(fr), + ('d MMM yyyy HH:mm:ss', 'd MMM y HH:mm:ss')) + self.assertIn(datefmt.get_datetime_format_hint(ja), + ('yyyy/MM/dd H:mm:ss', 'y/MM/dd H:mm:ss')) + self.assertIn(datefmt.get_datetime_format_hint(vi), + ('HH:mm:ss dd-MM-yyyy', 'HH:mm:ss dd-MM-y')) + self.assertIn(datefmt.get_datetime_format_hint(zh_CN), + ('yyyy-M-d ahh:mm:ss', u'yå¹´Mædæ¥ ah:mm:ss')) def test_i18n_date_hint(self): en_US = Locale.parse('en_US') @@ -784,18 +941,18 @@ else: vi = Locale.parse('vi') zh_CN = Locale.parse('zh_CN') - self.assert_(datefmt.get_date_format_hint(en_US) - in ('MMM d, yyyy', 'MMM d, y')) - self.assert_(datefmt.get_date_format_hint(en_GB) - in ('d MMM yyyy', 'd MMM y')) - self.assert_(datefmt.get_date_format_hint(fr) - in ('d MMM yyyy', 'd MMM y')) - self.assertEqual('yyyy/MM/dd', - datefmt.get_date_format_hint(ja)) - self.assertEqual('dd-MM-yyyy', - datefmt.get_date_format_hint(vi)) - self.assertEqual('yyyy-M-d', - datefmt.get_date_format_hint(zh_CN)) + self.assertIn(datefmt.get_date_format_hint(en_US), + ('MMM d, yyyy', 'MMM d, y')) + self.assertIn(datefmt.get_date_format_hint(en_GB), + ('d MMM yyyy', 'd MMM y')) + self.assertIn(datefmt.get_date_format_hint(fr), + ('d MMM yyyy', 'd MMM y')) + self.assertIn(datefmt.get_date_format_hint(ja), + ('yyyy/MM/dd', 'y/MM/dd')) + self.assertIn(datefmt.get_date_format_hint(vi), + ('dd-MM-yyyy', 'dd-MM-y')) + self.assertIn(datefmt.get_date_format_hint(zh_CN), + ('yyyy-M-d', u'yå¹´Mædæ¥')) def test_i18n_parse_date_iso8609(self): tz = datefmt.timezone('GMT +2:00') @@ -864,16 +1021,22 @@ else: self.assertEqual(expected_minute, datefmt.parse_date(u'août 28 2010 13:45', tz, fr)) + self.assertEqual(expected_minute, + datefmt.parse_date('Aug 28 2010 1:45 PM', tz, fr)) self.assertEqual(expected, datefmt.parse_date('2010/08/28 13:45:56', tz, ja)) self.assertEqual(expected_minute, datefmt.parse_date('2010/08/28 13:45', tz, ja)) + self.assertEqual(expected_minute, + datefmt.parse_date('2010/Aug/28 1:45 PM', tz, ja)) self.assertEqual(expected, datefmt.parse_date('13:45:56 28-08-2010', tz, vi)) self.assertEqual(expected_minute, datefmt.parse_date('13:45 28-08-2010', tz, vi)) + self.assertEqual(expected_minute, + datefmt.parse_date('1:45PM 28-Aug-2010', tz, vi)) self.assertEqual(expected, datefmt.parse_date(u'2010-8-28 ä¸å01:45:56', @@ -887,6 +1050,9 @@ else: self.assertEqual(expected_minute, datefmt.parse_date(u'2010-8-28 01:45ä¸å', tz, zh_CN)) + self.assertEqual(expected_minute, + datefmt.parse_date('2010-Aug-28 01:45PM', tz, + zh_CN)) def test_i18n_parse_date_datetime_meridiem(self): tz = datefmt.timezone('GMT +2:00') @@ -945,20 +1111,27 @@ else: datefmt.parse_date(u'2010-8-28', tz, zh_CN)) def test_i18n_parse_date_roundtrip(self): + from pkg_resources import resource_listdir + locales = sorted(dirname + for dirname in resource_listdir('trac', 'locale') + if '.' not in dirname) + tz = datefmt.timezone('GMT +2:00') t = datetime.datetime(2010, 8, 28, 11, 45, 56, 123456, datefmt.utc) - expected = datetime.datetime(2010, 8, 28, 13, 45, 56, 0, tz) + tz_t = datetime.datetime(2010, 8, 28, 13, 45, 56, 0, tz) - for locale in translation.get_available_locales(): + for locale in locales: locale = Locale.parse(locale) formatted = datefmt.format_datetime(t, tzinfo=tz, locale=locale) actual = datefmt.parse_date(formatted, tz, locale) - self.assertEqual(expected, actual, - '%r != %r (%r)' % (expected, actual, locale)) + self.assertEqual(tz_t, actual, + '%r != %r (%r %r)' % (tz_t, actual, formatted, + locale)) + self.assertEqual(tz_t.isoformat(), actual.isoformat()) - actual = datefmt.format_datetime(expected, tzinfo=tz, + actual = datefmt.format_datetime(tz_t, tzinfo=tz, locale=locale) self.assertEqual(formatted, actual, '%r != %r (%r)' % (formatted, actual, locale)) @@ -966,12 +1139,12 @@ else: def test_format_compatibility(self): tz = datefmt.timezone('GMT +2:00') t = datetime.datetime(2010, 8, 28, 11, 45, 56, 123456, datefmt.utc) - tz_t = datetime.datetime(2010, 8, 28, 13, 45, 56, 123456, tz) en_US = Locale.parse('en_US') # Converting default format to babel's format - self.assertEqual('Aug 28, 2010 1:45:56 PM', - datefmt.format_datetime(t, '%x %X', tz, en_US)) + self.assertIn(datefmt.format_datetime(t, '%x %X', tz, en_US), + ('Aug 28, 2010 1:45:56 PM', + 'Aug 28, 2010, 1:45:56 PM')) # CLDR 23 self.assertEqual('Aug 28, 2010', datefmt.format_datetime(t, '%x', tz, en_US)) self.assertEqual('1:45:56 PM', @@ -1162,6 +1335,29 @@ class LocalTimezoneTestCase(unittest.Tes self.assertEqual('2011-10-30T02:45:42.123456+01:00', dt.astimezone(datefmt.localtz).isoformat()) + def test_astimezone_invalid_range_on_gmt01(self): + self._tzset('GMT-1') + + # 1899-12-30T23:59:58+00:00 is -0x83ac4e92 for time_t, out of range + # for 32-bit signed integer + dt = datetime.datetime(1899, 12, 30, 23, 59, 58, 123456, datefmt.utc) + self.assertEqual('1899-12-31T00:59:58.123456+01:00', + dt.astimezone(datefmt.localtz).isoformat()) + dt = datetime.datetime(1899, 12, 30, 23, 59, 58, 123456, + datefmt.localtz) + self.assertEqual('1899-12-30T22:59:58.123456+00:00', + dt.astimezone(datefmt.utc).isoformat()) + + # 2040-12-31T23:59:58+00:00 is 0x858c84ee for time_t, out of range for + # 32-bit signed integer + dt = datetime.datetime(2040, 12, 31, 23, 59, 58, 123456, datefmt.utc) + self.assertEqual('2041-01-01T00:59:58.123456+01:00', + dt.astimezone(datefmt.localtz).isoformat()) + dt = datetime.datetime(2040, 12, 31, 23, 59, 58, 123456, + datefmt.localtz) + self.assertEqual('2040-12-31T22:59:58.123456+00:00', + dt.astimezone(datefmt.utc).isoformat()) + def test_arithmetic_localized_non_existent_time(self): self._tzset('Europe/Paris') t = datetime.datetime(2012, 3, 25, 1, 15, 42, 123456) @@ -1231,7 +1427,6 @@ class LocalTimezoneTestCase(unittest.Tes def test_arithmetic_not_localized_normalized_non_existent_time(self): self._tzset('Europe/Paris') t = datetime.datetime(2012, 3, 25, 1, 15, 42, 123456, datefmt.localtz) - t_utc = t.replace(tzinfo=datefmt.utc) t1 = t self.assertEqual('2012-03-25T01:15:42.123456+01:00', t1.isoformat()) t2 = datefmt.localtz.normalize(t1 + datetime.timedelta(hours=1)) @@ -1245,7 +1440,6 @@ class LocalTimezoneTestCase(unittest.Tes def test_arithmetic_not_localized_normalized_ambiguous_time(self): self._tzset('Europe/Paris') t = datetime.datetime(2011, 10, 30, 1, 45, 42, 123456, datefmt.localtz) - t_utc = t.replace(tzinfo=datefmt.utc) t1 = t self.assertEqual('2011-10-30T01:45:42.123456+02:00', t1.isoformat()) t2 = datefmt.localtz.normalize(t1 + datetime.timedelta(hours=1)) @@ -1259,6 +1453,235 @@ class LocalTimezoneTestCase(unittest.Tes self.assertEqual(datetime.timedelta(hours=1), t3 - t2) self.assertEqual(datetime.timedelta(hours=1), t4 - t3) + def test_london_between_1968_and_1971(self): + self._tzset('Europe/London') + # -1:00 (DST end) at 1967-10-29 03:00 + ts = datefmt.to_timestamp(datetime.datetime(1967, 10, 30, + tzinfo=datefmt.utc)) + self.assertEqual('1967-10-30T00:00:00+00:00', + datefmt.to_datetime(ts, datefmt.localtz).isoformat()) + # +1:00 (DST start) at 1968-02-18 02:00 + ts = datefmt.to_timestamp(datetime.datetime(1968, 2, 19, + tzinfo=datefmt.utc)) + self.assertEqual('1968-02-19T01:00:00+01:00', + datefmt.to_datetime(ts, datefmt.localtz).isoformat()) + # No DST between 1968-02-18 02:00 and 1971-10-31 03:00 + ts = datefmt.to_timestamp(datetime.datetime(1970, 1, 1, 0, 0, 23, + tzinfo=datefmt.utc)) + self.assertEqual('1970-01-01T01:00:23+01:00', + datefmt.to_datetime(ts, datefmt.localtz).isoformat()) + # -1:00 (TZ change) at 1971-10-31 03:00 + t = datefmt.to_datetime(datetime.datetime(1971, 10, 31, 1, 30), + datefmt.localtz) + delta = datetime.timedelta(hours=1) + self.assertEqual('1971-10-31T01:30:00+01:00', t.isoformat()) + t = datefmt.to_datetime(t + delta, datefmt.localtz) + self.assertEqual('1971-10-31T02:30:00+01:00', t.isoformat()) + t = datefmt.to_datetime(t + delta, datefmt.localtz) + self.assertEqual('1971-10-31T02:30:00+00:00', t.isoformat()) + t = datefmt.to_datetime(t + delta, datefmt.localtz) + self.assertEqual('1971-10-31T03:30:00+00:00', t.isoformat()) + + ts = datefmt.to_timestamp(datetime.datetime(1971, 11, 1, + tzinfo=datefmt.utc)) + self.assertEqual('1971-11-01T00:00:00+00:00', + datefmt.to_datetime(ts, datefmt.localtz).isoformat()) + + def test_guatemala_dst_in_2006(self): + self._tzset('America/Guatemala') + # No DST before 2006-04-30 00:00 + ts = datefmt.to_timestamp(datetime.datetime(2006, 4, 29, + tzinfo=datefmt.utc)) + self.assertEqual('2006-04-28T18:00:00-06:00', + datefmt.to_datetime(ts, datefmt.localtz).isoformat()) + # +1:00 (DST start) at 2006-04-30 00:00 + ts = datefmt.to_timestamp(datetime.datetime(2006, 8, 1, + tzinfo=datefmt.utc)) + self.assertEqual('2006-07-31T19:00:00-05:00', + datefmt.to_datetime(ts, datefmt.localtz).isoformat()) + # -1:00 (DST end) at 2006-10-01 00:00 + ts = datefmt.to_timestamp(datetime.datetime(2006, 10, 2, + tzinfo=datefmt.utc)) + self.assertEqual('2006-10-01T18:00:00-06:00', + datefmt.to_datetime(ts, datefmt.localtz).isoformat()) + # No DST after 2006-10-01 00:00 + + def test_venezuela_in_2007(self): + self._tzset('America/Caracas') + ts = datefmt.to_timestamp(datetime.datetime(2007, 12, 8, + tzinfo=datefmt.utc)) + self.assertEqual('2007-12-07T20:00:00-04:00', + datefmt.to_datetime(ts, datefmt.localtz).isoformat()) + # -0:30 (TZ change) at 2007-12-09 03:00 + ts = datefmt.to_timestamp(datetime.datetime(2007, 12, 10, + tzinfo=datefmt.utc)) + self.assertEqual('2007-12-09T19:30:00-04:30', + datefmt.to_datetime(ts, datefmt.localtz).isoformat()) + + def test_lord_howe_island_in_198x(self): + self._tzset('Australia/Lord_Howe') + ts = datefmt.to_timestamp(datetime.datetime(1985, 3, 1, + tzinfo=datefmt.utc)) + self.assertEqual('1985-03-01T11:30:00+11:30', + datefmt.to_datetime(ts, datefmt.localtz).isoformat()) + # -1:00 (DST end) at 1985-03-03 02:00 + ts = datefmt.to_timestamp(datetime.datetime(1985, 8, 1, + tzinfo=datefmt.utc)) + self.assertEqual('1985-08-01T10:30:00+10:30', + datefmt.to_datetime(ts, datefmt.localtz).isoformat()) + ts = datefmt.to_timestamp(datetime.datetime(1985, 11, 1, + tzinfo=datefmt.utc)) + # +0:30 (DST start) at 1985-10-27 02:00 + self.assertEqual('1985-11-01T11:00:00+11:00', + datefmt.to_datetime(ts, datefmt.localtz).isoformat()) + + def _compare_pytz_arithmetic(self, tz, dt_naive): + """Compare arithmetic timezone-aware datetime between localtz and + pytz's timezone""" + localtz = datefmt.localtz + delta = datetime.timedelta(minutes=20) + n = datetime.timedelta(hours=3).seconds / delta.seconds + # create timezone-aware datetime instances + dt_localtz = datefmt.to_datetime(dt_naive - delta * n, localtz) + dt_tz = datefmt.to_datetime(dt_naive - delta * n, tz) + # compare datetime instances between -3 hours and +3 hours + for i in xrange(n * 2 + 1): + self.assertEqual(dt_tz, dt_localtz) + self.assertEqual(dt_tz.isoformat(), dt_localtz.isoformat()) + dt_localtz = datefmt.to_datetime(dt_localtz + delta, localtz) + dt_tz = datefmt.to_datetime(dt_tz + delta, tz) + + def _compare_pytz_localize_and_normalize(self, tz, dt_naive): + """Compare localize() and normalize() of LocalTimezone and pytz's + timezone""" + localtz = datefmt.localtz + delta = datetime.timedelta(minutes=20) + n = datetime.timedelta(hours=3).seconds / delta.seconds + dt_naive -= delta * n + # compare localize and normalize with naive datetime + # between -3 hours and +3 hours + for i in xrange(n * 2 + 1): + dt_localtz = localtz.localize(dt_naive) + dt_tz = tz.localize(dt_naive) + self.assertEqual(dt_tz, dt_localtz, + '%r != %r (%r)' % (dt_tz, dt_localtz, dt_naive)) + self.assertEqual(dt_tz.isoformat(), dt_localtz.isoformat(), + '%r != %r (%r)' % (dt_tz.isoformat(), + dt_localtz.isoformat(), + dt_naive)) + dt_localtz = localtz.normalize(localtz.localize(dt_naive)) + dt_tz = tz.normalize(tz.localize(dt_naive)) + self.assertEqual(dt_tz, dt_localtz, + '%r != %r (%r)' % (dt_tz, dt_localtz, dt_naive)) + self.assertEqual(dt_tz.isoformat(), dt_localtz.isoformat(), + '%r != %r (%r)' % (dt_tz.isoformat(), + dt_localtz.isoformat(), + dt_naive)) + dt_naive += delta + + def _compare_pytz(self, tz, value, localize=True): + if isinstance(value, basestring): + value = datefmt.parse_date(value + 'Z', datefmt.utc) + dt_naive = value.replace(tzinfo=None) + self._compare_pytz_arithmetic(tz, dt_naive) + # `localize()` differs one of pytz's timezone when backward timezone + # change + if localize: + self._compare_pytz_localize_and_normalize(tz, dt_naive) + + if datefmt.pytz: + def test_pytz_choibalsan(self): + tz = datefmt.timezone('Asia/Choibalsan') + self._tzset('Asia/Choibalsan') + self._compare_pytz(tz, '1977-01-01T00:00') # No DST + self._compare_pytz(tz, '1978-01-01T01:00') # +1:00 (TZ change) + self._compare_pytz(tz, '1978-01-01T02:00') # (TZ change) + self._compare_pytz(tz, '1982-04-01T00:00') # No DST + self._compare_pytz(tz, '1983-04-01T00:00') # +2:00 (TZ change) + self._compare_pytz(tz, '1983-04-01T02:00') # (TZ change) + self._compare_pytz(tz, '1983-10-01T00:00') # -1:00 (DST end) + self._compare_pytz(tz, '2006-03-25T02:00') # +1:00 (DST start) + self._compare_pytz(tz, '2006-09-30T02:00') # -1:00 (DST end) + self._compare_pytz(tz, '2007-07-01T00:00') # No DST in 2007 + self._compare_pytz(tz, '2008-03-30T23:00', # (TZ change) + localize=False) + self._compare_pytz(tz, '2008-03-31T00:00', # -1:00 (TZ change) + localize=False) + self._compare_pytz(tz, '2009-07-01T00:00') # No DST + + def test_pytz_guatemala(self): + tz = datefmt.timezone('America/Guatemala') + self._tzset('America/Guatemala') + self._compare_pytz(tz, '2005-07-01T00:00') # No DST + self._compare_pytz(tz, '2006-04-30T00:00') # +1:00 (DST start) + self._compare_pytz(tz, '2006-10-01T00:00') # -1:00 (DST end) + self._compare_pytz(tz, '2007-07-01T00:00') # No DST + + def test_pytz_london(self): + tz = datefmt.timezone('Europe/London') + self._tzset('Europe/London') + self._compare_pytz(tz, '1968-02-18T02:00') # +1:00 (DST start) + self._compare_pytz(tz, '1971-10-31T02:00', # (TZ change) + localize=False) + self._compare_pytz(tz, '1971-10-31T03:00', # -1:00 (TZ change) + localize=False) + self._compare_pytz(tz, '1972-03-19T02:00') # +1:00 (DST start) + self._compare_pytz(tz, '1972-10-29T03:00') # -1:00 (DST end) + + def test_pytz_lord_howe_island(self): + tz = datefmt.timezone('Australia/Lord_Howe') + self._tzset('Australia/Lord_Howe') + self._compare_pytz(tz, '1980-07-01T00:00') # No DST + self._compare_pytz(tz, '1981-03-01T00:00') # +0:30 (TZ change) + self._compare_pytz(tz, '1981-03-01T00:30') # (TZ change) + self._compare_pytz(tz, '1981-10-25T02:00') # +1:00 (DST start) + self._compare_pytz(tz, '1985-03-03T02:00') # -1:00 (DST end) + self._compare_pytz(tz, '1985-10-27T02:00') # +0:30 (DST start) + self._compare_pytz(tz, '1986-03-16T02:00') # -0:30 (DST end) + + def test_pytz_moscow(self): + tz = datefmt.timezone('Europe/Moscow') + self._tzset('Europe/Moscow') + self._compare_pytz(tz, '1991-09-29T03:00') # -1:00 (DST end) + self._compare_pytz(tz, '1992-01-19T02:00') # +1:00 (TZ change) + self._compare_pytz(tz, '1992-01-19T03:00') # (TZ change) + self._compare_pytz(tz, '1992-03-28T23:00') # +1:00 (DST start) + self._compare_pytz(tz, '1992-09-26T23:00') # -1:00 (DST end) + self._compare_pytz(tz, '2010-03-28T02:00') # +1:00 (DST start) + self._compare_pytz(tz, '2010-10-31T03:00') # -1:00 (DST end) + self._compare_pytz(tz, '2011-03-27T02:00') # +1:00 (TZ change) + self._compare_pytz(tz, '2011-03-27T03:00') # (TZ change) + self._compare_pytz(tz, '2011-10-31T03:00') # No DST + + def test_pytz_paris(self): + tz = datefmt.timezone('Europe/Paris') + self._tzset('Europe/Paris') + self._compare_pytz(tz, '1975-07-01T01:00') # No DST + self._compare_pytz(tz, '1976-03-28T01:00') # +1:00 (DST start) + self._compare_pytz(tz, '1976-09-26T01:00') # -1:00 (DST end) + self._compare_pytz(tz, '2009-03-29T02:00') # +1:00 (DST start) + self._compare_pytz(tz, '2009-10-25T03:00') # -1:00 (DST end) + + def test_pytz_tokyo(self): + tz = datefmt.timezone('Asia/Tokyo') + self._tzset('Asia/Tokyo') + self._compare_pytz(tz, '1947-07-01T02:00') # No DST + self._compare_pytz(tz, '1948-05-02T02:00') # +1:00 (DST start) + self._compare_pytz(tz, '1948-09-11T02:00') # -1:00 (DST end) + self._compare_pytz(tz, '1949-04-03T02:00') # +1:00 (DST start) + self._compare_pytz(tz, '1949-09-10T02:00') # -1:00 (DST end) + self._compare_pytz(tz, '1950-07-01T02:00') # No DST + + def test_pytz_venezuela(self): + tz = datefmt.timezone('America/Caracas') + self._tzset('America/Caracas') + self._compare_pytz(tz, '2006-07-01T00:00') # No DST + self._compare_pytz(tz, '2007-12-09T02:30', # (TZ change) + localize=False) + self._compare_pytz(tz, '2007-12-09T03:00', # -0:30 (TZ change) + localize=False) + self._compare_pytz(tz, '2008-07-01T00:00') # No DST + class LocalTimezoneStrTestCase(unittest.TestCase): @@ -1282,17 +1705,18 @@ class LocalTimezoneStrTestCase(unittest. def suite(): suite = unittest.TestSuite() if PytzTestCase: - suite.addTest(unittest.makeSuite(PytzTestCase, 'test')) + suite.addTest(unittest.makeSuite(PytzTestCase)) else: print "SKIP: utils/tests/datefmt.py (no pytz installed)" suite.addTest(unittest.makeSuite(DateFormatTestCase)) suite.addTest(unittest.makeSuite(UTimestampTestCase)) suite.addTest(unittest.makeSuite(ISO8601TestCase)) if I18nDateFormatTestCase: - suite.addTest(unittest.makeSuite(I18nDateFormatTestCase, 'test')) + suite.addTest(unittest.makeSuite(I18nDateFormatTestCase)) else: print "SKIP: utils/tests/datefmt.py (no babel installed)" suite.addTest(unittest.makeSuite(ParseISO8601TestCase)) + suite.addTest(unittest.makeSuite(ParseDateWithoutBabelTestCase)) suite.addTest(unittest.makeSuite(ParseRelativeDateTestCase)) suite.addTest(unittest.makeSuite(ParseDateValidRangeTestCase)) suite.addTest(unittest.makeSuite(HttpDateTestCase))
Modified: bloodhound/branches/trac-1.0.2-integration/trac/trac/util/tests/html.py URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/trac/util/tests/html.py?rev=1639823&r1=1639822&r2=1639823&view=diff ============================================================================== --- bloodhound/branches/trac-1.0.2-integration/trac/trac/util/tests/html.py (original) +++ bloodhound/branches/trac-1.0.2-integration/trac/trac/util/tests/html.py Sat Nov 15 01:14:46 2014 @@ -1,9 +1,24 @@ # -*- coding: utf-8 -*- +# +# Copyright (C) 2007-2013 Edgewall Software +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://trac.edgewall.org/wiki/TracLicense. +# +# This software consists of voluntary contributions made by many +# individuals. For the exact contribution history, see the revision +# history and logs, available at http://trac.edgewall.org/log/. import unittest +from genshi.builder import Element, Fragment, tag from genshi.input import HTML -from trac.util.html import TracHTMLSanitizer +import trac.tests.compat +from trac.core import TracError +from trac.util.html import TracHTMLSanitizer, find_element, to_fragment +from trac.util.translation import gettext, tgettext class TracHTMLSanitizerTestCase(unittest.TestCase): @@ -141,9 +156,95 @@ class TracHTMLSanitizerTestCase(unittest self.assertEqual('<div>XSS</div>', unicode(html | TracHTMLSanitizer())) +class FindElementTestCase(unittest.TestCase): + def test_find_element_with_tag(self): + frag = tag(tag.p('Paragraph with a ', + tag.a('link', href='http://www.edgewall.org'), + ' and some ', tag.strong('strong text'))) + self.assertIsNotNone(find_element(frag, tag='p')) + self.assertIsNotNone(find_element(frag, tag='a')) + self.assertIsNotNone(find_element(frag, tag='strong')) + self.assertIsNone(find_element(frag, tag='input')) + self.assertIsNone(find_element(frag, tag='textarea')) + + +class ToFragmentTestCase(unittest.TestCase): + + def test_unicode(self): + rv = to_fragment('blah') + self.assertEqual(Fragment, type(rv)) + self.assertEqual('blah', unicode(rv)) + + def test_fragment(self): + rv = to_fragment(tag('blah')) + self.assertEqual(Fragment, type(rv)) + self.assertEqual('blah', unicode(rv)) + + def test_element(self): + rv = to_fragment(tag.p('blah')) + self.assertEqual(Element, type(rv)) + self.assertEqual('<p>blah</p>', unicode(rv)) + + def test_tracerror(self): + rv = to_fragment(TracError('blah')) + self.assertEqual(Fragment, type(rv)) + self.assertEqual('blah', unicode(rv)) + + def test_tracerror_with_fragment(self): + message = tag('Powered by ', + tag.a('Trac', href='http://trac.edgewall.org/')) + rv = to_fragment(TracError(message)) + self.assertEqual(Fragment, type(rv)) + self.assertEqual('Powered by <a href="http://trac.edgewall.org/">Trac' + '</a>', unicode(rv)) + + def test_tracerror_with_element(self): + message = tag.p('Powered by ', + tag.a('Trac', href='http://trac.edgewall.org/')) + rv = to_fragment(TracError(message)) + self.assertEqual(Element, type(rv)) + self.assertEqual('<p>Powered by <a href="http://trac.edgewall.org/">' + 'Trac</a></p>', unicode(rv)) + + def test_error(self): + rv = to_fragment(ValueError('invalid literal for int(): blah')) + self.assertEqual(Fragment, type(rv)) + self.assertEqual('invalid literal for int(): blah', unicode(rv)) + + def test_gettext(self): + rv = to_fragment(gettext('%(size)s bytes', size=0)) + self.assertEqual(Fragment, type(rv)) + self.assertEqual('0 bytes', unicode(rv)) + + def test_tgettext(self): + rv = to_fragment(tgettext('Back to %(parent)s', + parent=tag.a('WikiStart', + href='http://localhost/'))) + self.assertEqual(Fragment, type(rv)) + self.assertEqual('Back to <a href="http://localhost/">WikiStart</a>', + unicode(rv)) + + def test_tracerror_with_gettext(self): + e = TracError(gettext('%(size)s bytes', size=0)) + rv = to_fragment(e) + self.assertEqual(Fragment, type(rv)) + self.assertEqual('0 bytes', unicode(rv)) + + def test_tracerror_with_tgettext(self): + e = TracError(tgettext('Back to %(parent)s', + parent=tag.a('WikiStart', + href='http://localhost/'))) + rv = to_fragment(e) + self.assertEqual(Fragment, type(rv)) + self.assertEqual('Back to <a href="http://localhost/">WikiStart</a>', + unicode(rv)) + + def suite(): suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(TracHTMLSanitizerTestCase, 'test')) + suite.addTest(unittest.makeSuite(TracHTMLSanitizerTestCase)) + suite.addTest(unittest.makeSuite(FindElementTestCase)) + suite.addTest(unittest.makeSuite(ToFragmentTestCase)) return suite Modified: bloodhound/branches/trac-1.0.2-integration/trac/trac/util/tests/presentation.py URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/trac/util/tests/presentation.py?rev=1639823&r1=1639822&r2=1639823&view=diff ============================================================================== --- bloodhound/branches/trac-1.0.2-integration/trac/trac/util/tests/presentation.py (original) +++ bloodhound/branches/trac-1.0.2-integration/trac/trac/util/tests/presentation.py Sat Nov 15 01:14:46 2014 @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C)2006-2009 Edgewall Software +# Copyright (C) 2006-2013 Edgewall Software # Copyright (C) 2006 Christopher Lenz <cml...@gmx.de> # All rights reserved. # @@ -32,16 +32,19 @@ class ToJsonTestCase(unittest.TestCase): presentation.to_json("a ' single quote")) self.assertEqual(r'"\u003cb\u003e\u0026\u003c/b\u003e"', presentation.to_json('<b>&</b>')) + self.assertEqual(r'"\n\r\u2028\u2029"', + presentation.to_json(u'\x0a\x0d\u2028\u2029')) def test_compound_types(self): self.assertEqual('[1,2,[true,false]]', presentation.to_json([1, 2, [True, False]])) self.assertEqual(r'{"one":1,"other":[null,0],' r'''"three":[3,"\u0026\u003c\u003e'"],''' - r'"two":2}', + r'"two":2,"\u2028\n":"\u2029\r"}', presentation.to_json({"one": 1, "two": 2, "other": [None, 0], - "three": [3, "&<>'"]})) + "three": [3, "&<>'"], + u"\u2028\x0a": u"\u2029\x0d"})) def suite(): Modified: bloodhound/branches/trac-1.0.2-integration/trac/trac/util/tests/text.py URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/trac/util/tests/text.py?rev=1639823&r1=1639822&r2=1639823&view=diff ============================================================================== --- bloodhound/branches/trac-1.0.2-integration/trac/trac/util/tests/text.py (original) +++ bloodhound/branches/trac-1.0.2-integration/trac/trac/util/tests/text.py Sat Nov 15 01:14:46 2014 @@ -1,31 +1,46 @@ # -*- coding: utf-8 -*- +# +# Copyright (C) 2006-2013 Edgewall Software +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://trac.edgewall.org/wiki/TracLicense. +# +# This software consists of voluntary contributions made by many +# individuals. For the exact contribution history, see the revision +# history and logs, available at http://trac.edgewall.org/log/. +import os +import socket import unittest from StringIO import StringIO +import trac.tests.compat from trac.util.text import empty, expandtabs, fix_eol, javascript_quote, \ - to_js_string, normalize_whitespace, to_unicode, \ - text_width, print_table, unicode_quote, \ - unicode_quote_plus, unicode_unquote, \ - unicode_urlencode, wrap, quote_query_string, \ - unicode_to_base64, unicode_from_base64, \ - strip_line_ws, stripws, levenshtein_distance + levenshtein_distance, normalize_whitespace, \ + print_table, quote_query_string, shorten_line, \ + strip_line_ws, stripws, text_width, \ + to_js_string, to_unicode, unicode_from_base64, \ + unicode_quote, unicode_quote_plus, \ + unicode_to_base64, unicode_unquote, \ + unicode_urlencode, wrap class ToUnicodeTestCase(unittest.TestCase): def test_explicit_charset(self): uc = to_unicode('\xc3\xa7', 'utf-8') - assert isinstance(uc, unicode) + self.assertIsInstance(uc, unicode) self.assertEquals(u'\xe7', uc) def test_explicit_charset_with_replace(self): uc = to_unicode('\xc3', 'utf-8') - assert isinstance(uc, unicode) + self.assertIsInstance(uc, unicode) self.assertEquals(u'\xc3', uc) def test_implicit_charset(self): uc = to_unicode('\xc3\xa7') - assert isinstance(uc, unicode) + self.assertIsInstance(uc, unicode) self.assertEquals(u'\xe7', uc) def test_from_exception_using_unicode_args(self): @@ -33,26 +48,52 @@ class ToUnicodeTestCase(unittest.TestCas try: raise ValueError, '%s is not a number.' % u except ValueError, e: - self.assertEquals(u'\uB144 is not a number.', to_unicode(e)) + self.assertEqual(u'\uB144 is not a number.', to_unicode(e)) def test_from_exception_using_str_args(self): u = u'Das Ger\xe4t oder die Ressource ist belegt' try: raise ValueError, u.encode('utf-8') except ValueError, e: - self.assertEquals(u, to_unicode(e)) + self.assertEqual(u, to_unicode(e)) + + def test_from_windows_error(self): + try: + os.stat('non/existent/file.txt') + except OSError, e: + uc = to_unicode(e) + self.assertIsInstance(uc, unicode, uc) + self.assertTrue(uc.startswith('[Error '), uc) + self.assertIn(e.strerror.decode('mbcs'), uc) + + def test_from_socket_error(self): + for res in socket.getaddrinfo('127.0.0.1', 65536, 0, + socket.SOCK_STREAM): + af, socktype, proto, canonname, sa = res + s = socket.socket(af, socktype, proto) + try: + s.connect(sa) + except socket.error, e: + uc = to_unicode(e) + self.assertIsInstance(uc, unicode, uc) + if hasattr(e, 'strerror'): + self.assertIn(e.strerror.decode('mbcs'), uc) + + if os.name != 'nt': + del test_from_windows_error + del test_from_socket_error class ExpandtabsTestCase(unittest.TestCase): def test_empty(self): x = expandtabs('', ignoring='\0') - self.assertEquals('', x) + self.assertEqual('', x) def test_ingoring(self): x = expandtabs('\0\t', ignoring='\0') - self.assertEquals('\0 ', x) + self.assertEqual('\0 ', x) def test_tabstops(self): - self.assertEquals(' ', expandtabs(' \t')) - self.assertEquals(' ', expandtabs('\t\t')) + self.assertEqual(' ', expandtabs(' \t')) + self.assertEqual(' ', expandtabs('\t\t')) class JavascriptQuoteTestCase(unittest.TestCase): @@ -65,6 +106,8 @@ class JavascriptQuoteTestCase(unittest.T javascript_quote('\x02\x1e')) self.assertEqual(r'\u0026\u003c\u003e', javascript_quote('&<>')) + self.assertEqual(r'\u2028\u2029', + javascript_quote(u'\u2028\u2029')) class ToJsStringTestCase(unittest.TestCase): @@ -81,6 +124,8 @@ class ToJsStringTestCase(unittest.TestCa to_js_string('')) self.assertEqual('""', to_js_string(None)) + self.assertEqual(r'"\u2028\u2029"', + to_js_string(u'\u2028\u2029')) class UnicodeQuoteTestCase(unittest.TestCase): @@ -310,17 +355,17 @@ class UnicodeBase64TestCase(unittest.Tes class StripwsTestCase(unittest.TestCase): def test_stripws(self): - self.assertEquals(u'stripws', - stripws(u' \u200b\t\u3000stripws \u200b\t\u2008')) - self.assertEquals(u'stripws \u3000\t', - stripws(u'\u200b\t\u2008 stripws \u3000\t', - trailing=False)) - self.assertEquals(u' \t\u3000stripws', - stripws(u' \t\u3000stripws \u200b\t\u2008', - leading=False)) - self.assertEquals(u' \t\u3000stripws \u200b\t\u2008', - stripws(u' \t\u3000stripws \u200b\t\u2008', - leading=False, trailing=False)) + self.assertEqual(u'stripws', + stripws(u' \u200b\t\u3000stripws \u200b\t\u2008')) + self.assertEqual(u'stripws \u3000\t', + stripws(u'\u200b\t\u2008 stripws \u3000\t', + trailing=False)) + self.assertEqual(u' \t\u3000stripws', + stripws(u' \t\u3000stripws \u200b\t\u2008', + leading=False)) + self.assertEqual(u' \t\u3000stripws \u200b\t\u2008', + stripws(u' \t\u3000stripws \u200b\t\u2008', + leading=False, trailing=False)) @@ -333,22 +378,41 @@ class LevenshteinDistanceTestCase(unitte self.assertEqual(0, levenshtein_distance('milestone', 'milestone')) +class ShortenLineTestCase(unittest.TestCase): + + def test_less_than_maxlen(self): + text = '123456789' + self.assertEqual(text, shorten_line(text, 10)) + + def test_equalto_maxlen(self): + text = '1234567890' + self.assertEqual(text, shorten_line(text, 10)) + + def test_greater_than_maxlen(self): + text = 'word word word word' + self.assertEqual('word word ...', shorten_line(text, 15)) + text = 'abcdefghij' + self.assertEqual('abcde ...', shorten_line(text, 9)) + + + def suite(): suite = unittest.TestSuite() - suite.addTest(unittest.makeSuite(ToUnicodeTestCase, 'test')) - suite.addTest(unittest.makeSuite(ExpandtabsTestCase, 'test')) - suite.addTest(unittest.makeSuite(UnicodeQuoteTestCase, 'test')) - suite.addTest(unittest.makeSuite(JavascriptQuoteTestCase, 'test')) - suite.addTest(unittest.makeSuite(ToJsStringTestCase, 'test')) - suite.addTest(unittest.makeSuite(QuoteQueryStringTestCase, 'test')) - suite.addTest(unittest.makeSuite(WhitespaceTestCase, 'test')) - suite.addTest(unittest.makeSuite(TextWidthTestCase, 'test')) - suite.addTest(unittest.makeSuite(PrintTableTestCase, 'test')) - suite.addTest(unittest.makeSuite(WrapTestCase, 'test')) - suite.addTest(unittest.makeSuite(FixEolTestCase, 'test')) - suite.addTest(unittest.makeSuite(UnicodeBase64TestCase, 'test')) - suite.addTest(unittest.makeSuite(StripwsTestCase, 'test')) - suite.addTest(unittest.makeSuite(LevenshteinDistanceTestCase, 'test')) + suite.addTest(unittest.makeSuite(ToUnicodeTestCase)) + suite.addTest(unittest.makeSuite(ExpandtabsTestCase)) + suite.addTest(unittest.makeSuite(UnicodeQuoteTestCase)) + suite.addTest(unittest.makeSuite(JavascriptQuoteTestCase)) + suite.addTest(unittest.makeSuite(ToJsStringTestCase)) + suite.addTest(unittest.makeSuite(QuoteQueryStringTestCase)) + suite.addTest(unittest.makeSuite(WhitespaceTestCase)) + suite.addTest(unittest.makeSuite(TextWidthTestCase)) + suite.addTest(unittest.makeSuite(PrintTableTestCase)) + suite.addTest(unittest.makeSuite(WrapTestCase)) + suite.addTest(unittest.makeSuite(FixEolTestCase)) + suite.addTest(unittest.makeSuite(UnicodeBase64TestCase)) + suite.addTest(unittest.makeSuite(StripwsTestCase)) + suite.addTest(unittest.makeSuite(LevenshteinDistanceTestCase)) + suite.addTest(unittest.makeSuite(ShortenLineTestCase)) return suite if __name__ == '__main__': Modified: bloodhound/branches/trac-1.0.2-integration/trac/trac/util/text.py URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/trac/util/text.py?rev=1639823&r1=1639822&r2=1639823&view=diff ============================================================================== --- bloodhound/branches/trac-1.0.2-integration/trac/trac/util/text.py (original) +++ bloodhound/branches/trac-1.0.2-integration/trac/trac/util/text.py Sat Nov 15 01:14:46 2014 @@ -61,6 +61,13 @@ def to_unicode(text, charset=None): except UnicodeDecodeError: return unicode(text, 'latin1') elif isinstance(text, Exception): + if os.name == 'nt' and isinstance(text, (OSError, IOError)): + # the exception might have a localized error string encoded with + # ANSI codepage if OSError and IOError on Windows + try: + return unicode(str(text), 'mbcs') + except UnicodeError: + pass # two possibilities for storing unicode strings in exception data: try: # custom __str__ method on the exception (e.g. PermissionError) @@ -131,10 +138,10 @@ def strip_line_ws(text, leading=True, tr _js_quote = {'\\': '\\\\', '"': '\\"', '\b': '\\b', '\f': '\\f', '\n': '\\n', '\r': '\\r', '\t': '\\t', "'": "\\'"} -for i in range(0x20) + [ord(c) for c in '&<>']: - _js_quote.setdefault(chr(i), '\\u%04x' % i) -_js_quote_re = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t\'&<>]') -_js_string_re = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t&<>]') +for i in range(0x20) + [ord(c) for c in u'&<>\u2028\u2029']: + _js_quote.setdefault(unichr(i), '\\u%04x' % i) +_js_quote_re = re.compile(ur'[\x00-\x1f\\"\b\f\n\r\t\'&<>\u2028\u2029]') +_js_string_re = re.compile(ur'[\x00-\x1f\\"\b\f\n\r\t&<>\u2028\u2029]') def javascript_quote(text): @@ -221,24 +228,27 @@ def quote_query_string(text): def to_utf8(text, charset='latin1'): - """Convert a string to UTF-8, assuming the encoding is either UTF-8, ISO - Latin-1, or as specified by the optional `charset` parameter. + """Convert a string to an UTF-8 `str` object. - .. deprecated :: 0.10 - You should use `unicode` strings only. - """ - try: - # Do nothing if it's already utf-8 - u = unicode(text, 'utf-8') - return text - except UnicodeError: + If the input is not an `unicode` object, we assume the encoding is + already UTF-8, ISO Latin-1, or as specified by the optional + *charset* parameter. + """ + if isinstance(text, unicode): + u = text + else: try: - # Use the user supplied charset if possible - u = unicode(text, charset) + # Do nothing if it's already utf-8 + u = unicode(text, 'utf-8') + return text except UnicodeError: - # This should always work - u = unicode(text, 'latin1') - return u.encode('utf-8') + try: + # Use the user supplied charset if possible + u = unicode(text, charset) + except UnicodeError: + # This should always work + u = unicode(text, 'latin1') + return u.encode('utf-8') class unicode_passwd(unicode): @@ -402,17 +412,19 @@ def print_table(data, headers=None, sep= def shorten_line(text, maxlen=75): - """Truncates content to at most `maxlen` characters. + """Truncates `text` to length less than or equal to `maxlen` characters. This tries to be (a bit) clever and attempts to find a proper word boundary for doing so. """ - if len(text or '') < maxlen: + if len(text or '') <= maxlen: return text - cut = max(text.rfind(' ', 0, maxlen), text.rfind('\n', 0, maxlen)) + suffix = ' ...' + maxtextlen = maxlen - len(suffix) + cut = max(text.rfind(' ', 0, maxtextlen), text.rfind('\n', 0, maxtextlen)) if cut < 0: - cut = maxlen - return text[:cut] + ' ...' + cut = maxtextlen + return text[:cut] + suffix class UnicodeTextWrapper(textwrap.TextWrapper): Modified: bloodhound/branches/trac-1.0.2-integration/trac/trac/util/translation.py URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/trac/util/translation.py?rev=1639823&r1=1639822&r2=1639823&view=diff ============================================================================== --- bloodhound/branches/trac-1.0.2-integration/trac/trac/util/translation.py (original) +++ bloodhound/branches/trac-1.0.2-integration/trac/trac/util/translation.py Sat Nov 15 01:14:46 2014 @@ -146,17 +146,18 @@ try: self._activate_failed = True return t = Translations.load(locale_dir, locale or 'en_US') - if not t or t.__class__ is NullTranslations: + if not isinstance(t, Translations): t = self._null_translations else: - t.add(Translations.load(locale_dir, locale or 'en_US', - 'tracini')) + self._add(t, Translations.load(locale_dir, locale or 'en_US', + 'tracini')) if env_path: with self._plugin_domains_lock: domains = self._plugin_domains.get(env_path, {}) domains = domains.items() for domain, dirname in domains: - t.add(Translations.load(dirname, locale, domain)) + self._add(t, Translations.load(dirname, locale, + domain)) self._current.translations = t self._activate_failed = False @@ -184,6 +185,12 @@ try: return self._current.translations is not None \ or self._activate_failed + # Internal methods + + def _add(self, t, translations): + if isinstance(translations, Translations): + t.add(translations) + # Delegated methods def __getattr__(self, name): @@ -335,21 +342,37 @@ try: translations are available. """ try: - return [dirname for dirname - in pkg_resources.resource_listdir('trac', 'locale') - if '.' not in dirname] + locales = [dirname for dirname + in pkg_resources.resource_listdir('trac', 'locale') + if '.' not in dirname + and pkg_resources.resource_exists( + 'trac', 'locale/%s/LC_MESSAGES/messages.mo' % dirname)] + return locales except Exception: return [] def get_negotiated_locale(preferred_locales): def normalize(locale_ids): return [id.replace('-', '_') for id in locale_ids if id] - return Locale.negotiate(normalize(preferred_locales), - normalize(get_available_locales())) + available_locales = get_available_locales() + if 'en_US' not in available_locales: + available_locales.append('en_US') + locale = Locale.negotiate(normalize(preferred_locales), + normalize(available_locales)) + if locale and str(locale) not in available_locales: + # The list of get_available_locales() must include locale + # identifier from str(locale), but zh_* don't be included after + # Babel 1.0. Avoid expanding zh_* to zh_Hans_CN and zh_Hant_TW + # to clear "script" property of Locale instance. See #11258. + locale._data # load localedata before clear script property + locale.script = None + assert str(locale) in available_locales + return locale has_babel = True except ImportError: # fall back on 0.11 behavior, i18n functions are no-ops + Locale = None gettext = _ = gettext_noop dgettext = dgettext_noop ngettext = ngettext_noop Modified: bloodhound/branches/trac-1.0.2-integration/trac/trac/versioncontrol/__init__.py URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/trac/versioncontrol/__init__.py?rev=1639823&r1=1639822&r2=1639823&view=diff ============================================================================== --- bloodhound/branches/trac-1.0.2-integration/trac/trac/versioncontrol/__init__.py (original) +++ bloodhound/branches/trac-1.0.2-integration/trac/trac/versioncontrol/__init__.py Sat Nov 15 01:14:46 2014 @@ -1 +1,14 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2005-2013 Edgewall Software +# All rights reserved. +# +# This software is licensed as described in the file COPYING, which +# you should have received as part of this distribution. The terms +# are also available at http://trac.edgewall.com/license.html. +# +# This software consists of voluntary contributions made by many +# individuals. For the exact contribution history, see the revision +# history and logs, available at http://trac.edgewall.org/. + from trac.versioncontrol.api import * Modified: bloodhound/branches/trac-1.0.2-integration/trac/trac/versioncontrol/admin.py URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/trac/versioncontrol/admin.py?rev=1639823&r1=1639822&r2=1639823&view=diff ============================================================================== --- bloodhound/branches/trac-1.0.2-integration/trac/trac/versioncontrol/admin.py (original) +++ bloodhound/branches/trac-1.0.2-integration/trac/trac/versioncontrol/admin.py Sat Nov 15 01:14:46 2014 @@ -176,13 +176,11 @@ class RepositoryAdminPanel(Component): # IAdminPanelProvider methods def get_admin_panels(self, req): - if 'VERSIONCONTROL_ADMIN' in req.perm: + if 'VERSIONCONTROL_ADMIN' in req.perm('admin', 'versioncontrol/repository'): yield ('versioncontrol', _('Version Control'), 'repository', _('Repositories')) def render_admin_panel(self, req, category, page, path_info): - req.perm.require('VERSIONCONTROL_ADMIN') - # Retrieve info for all repositories rm = RepositoryManager(self.env) all_repos = rm.get_all_repositories() @@ -202,39 +200,42 @@ class RepositoryAdminPanel(Component): elif db_provider and req.args.get('save'): # Modify repository changes = {} + valid = True for field in db_provider.repository_attrs: value = normalize_whitespace(req.args.get(field)) if (value is not None or field == 'hidden') \ and value != info.get(field): changes[field] = value - if 'dir' in changes \ - and not self._check_dir(req, changes['dir']): - changes = {} - if changes: + if 'dir' in changes and not \ + self._check_dir(req, changes['dir']): + valid = False + if valid and changes: db_provider.modify_repository(reponame, changes) add_notice(req, _('Your changes have been saved.')) - name = req.args.get('name') - resync = tag.tt('trac-admin $ENV repository resync "%s"' - % (name or '(default)')) - if 'dir' in changes: - msg = tag_('You should now run %(resync)s to ' - 'synchronize Trac with the repository.', - resync=resync) - add_notice(req, msg) - elif 'type' in changes: - msg = tag_('You may have to run %(resync)s to ' - 'synchronize Trac with the repository.', - resync=resync) - add_notice(req, msg) - if name and name != path_info and not 'alias' in info: - cset_added = tag.tt('trac-admin $ENV changeset ' - 'added "%s" $REV' - % (name or '(default)')) - msg = tag_('You will need to update your post-commit ' - 'hook to call %(cset_added)s with the new ' - 'repository name.', cset_added=cset_added) - add_notice(req, msg) - if changes: + name = req.args.get('name') + resync = tag.tt('trac-admin $ENV repository resync ' + '"%s"' % (name or '(default)')) + if 'dir' in changes: + msg = tag_('You should now run %(resync)s to ' + 'synchronize Trac with the repository.', + resync=resync) + add_notice(req, msg) + elif 'type' in changes: + msg = tag_('You may have to run %(resync)s to ' + 'synchronize Trac with the repository.', + resync=resync) + add_notice(req, msg) + if name and name != path_info and not 'alias' in info: + cset_added = tag.tt('trac-admin $ENV changeset ' + 'added "%s" $REV' + % (name or '(default)')) + msg = tag_('You will need to update your ' + 'post-commit hook to call ' + '%(cset_added)s with the new ' + 'repository name.', + cset_added=cset_added) + add_notice(req, msg) + if valid: req.redirect(req.href.admin(category, page)) Chrome(self.env).add_wiki_toolbars(req) @@ -253,7 +254,12 @@ class RepositoryAdminPanel(Component): add_warning(req, _('Missing arguments to add a ' 'repository.')) elif self._check_dir(req, dir): - db_provider.add_repository(name, dir, type_) + try: + db_provider.add_repository(name, dir, type_) + except self.env.db_exc.IntegrityError: + name = name or '(default)' + raise TracError(_('The repository "%(name)s" ' + 'already exists.', name=name)) name = name or '(default)' add_notice(req, _('The repository "%(name)s" has been ' 'added.', name=name)) @@ -277,7 +283,12 @@ class RepositoryAdminPanel(Component): name = req.args.get('name') alias = req.args.get('alias') if name is not None and alias is not None: - db_provider.add_alias(name, alias) + try: + db_provider.add_alias(name, alias) + except self.env.db_exc.IntegrityError: + raise TracError(_('The alias "%(name)s" already ' + 'exists.', + name=name or '(default)')) add_notice(req, _('The alias "%(name)s" has been ' 'added.', name=name or '(default)')) req.redirect(req.href.admin(category, page)) Modified: bloodhound/branches/trac-1.0.2-integration/trac/trac/versioncontrol/api.py URL: http://svn.apache.org/viewvc/bloodhound/branches/trac-1.0.2-integration/trac/trac/versioncontrol/api.py?rev=1639823&r1=1639822&r2=1639823&view=diff ============================================================================== --- bloodhound/branches/trac-1.0.2-integration/trac/trac/versioncontrol/api.py (original) +++ bloodhound/branches/trac-1.0.2-integration/trac/trac/versioncontrol/api.py Sat Nov 15 01:14:46 2014 @@ -24,7 +24,7 @@ from trac.config import ConfigSection, L from trac.core import * from trac.resource import IResourceManager, Resource, ResourceNotFound from trac.util.concurrency import threading -from trac.util.text import printout, to_unicode +from trac.util.text import printout, to_unicode, exception_to_unicode from trac.util.translation import _ from trac.web.api import IRequestFilter @@ -250,9 +250,18 @@ class DbRepositoryProvider(Component): """Modify attributes of a repository.""" if is_default(reponame): reponame = '' + new_reponame = changes.get('name', reponame) + if is_default(new_reponame): + new_reponame = '' rm = RepositoryManager(self.env) with self.env.db_transaction as db: id = rm.get_repository_id(reponame) + if reponame != new_reponame: + if db("""SELECT id FROM repository WHERE name='name' AND + value=%s""", (new_reponame,)): + raise TracError(_('The repository "%(name)s" already ' + 'exists.', + name=new_reponame or '(default)')) for (k, v) in changes.iteritems(): if k not in self.repository_attrs: continue @@ -353,7 +362,22 @@ class RepositoryManager(Component): _("Can't synchronize with repository \"%(name)s\" " "(%(error)s). Look in the Trac log for more " "information.", name=reponame or '(default)', - error=to_unicode(e.message))) + error=to_unicode(e))) + except Exception, e: + add_warning(req, + _("Failed to sync with repository \"%(name)s\": " + "%(error)s; repository information may be out of " + "date. Look in the Trac log for more information " + "including mitigation strategies.", + name=reponame or '(default)', error=to_unicode(e))) + self.log.error( + "Failed to sync with repository \"%s\"; You may be " + "able to reduce the impact of this issue by " + "configuring [trac] repository_sync_per_request; see " + "http://trac.edgewall.org/wiki/TracRepositoryAdmin" + "#ExplicitSync for more detail: %s", + reponame or '(default)', + exception_to_unicode(e, traceback=True)) self.log.info("Synchronized '%s' repository in %0.2f seconds", reponame or '(default)', time.time() - start) return handler @@ -400,6 +424,8 @@ class RepositoryManager(Component): return _('%(kind)s %(id)s%(at_version)s%(in_repo)s', kind=kind, id=id, at_version=version, in_repo=in_repo) elif resource.realm == 'repository': + if not resource.id: + return _("Default repository") return _("Repository %(repo)s", repo=resource.id) def get_resource_url(self, resource, href, **kwargs): @@ -502,8 +528,8 @@ class RepositoryManager(Component): This will create and save a new id if none is found. - \note: this should probably be renamed as we're dealing - exclusively with *db* repository ids here. + Note: this should probably be renamed as we're dealing + exclusively with *db* repository ids here. """ with self.env.db_transaction as db: for id, in db( @@ -631,8 +657,8 @@ class RepositoryManager(Component): The supported events are the names of the methods defined in the `IRepositoryChangeListener` interface. """ - self.log.debug("Event %s on %s for changesets %r", - event, reponame, revs) + self.log.debug("Event %s on repository '%s' for changesets %r", + event, reponame or '(default)', revs) # Notify a repository by name, and all repositories with the same # base, or all repositories by base or by repository dir @@ -667,8 +693,12 @@ class RepositoryManager(Component): repos.sync_changeset(rev) changeset = repos.get_changeset(rev) except NoSuchChangeset: + self.log.debug( + "No changeset '%s' found in repository '%s'. " + "Skipping subscribers for event %s", + rev, repos.reponame or '(default)', event) continue - self.log.debug("Event %s on %s for revision %s", + self.log.debug("Event %s on repository '%s' for revision '%s'", event, repos.reponame or '(default)', rev) for listener in self.change_listeners: getattr(listener, event)(repos, changeset, *args) @@ -998,6 +1028,23 @@ class Node(object): """ raise NotImplementedError + def get_processed_content(self, keyword_substitution=True, eol_hint=None): + """Return a stream for reading the content of the node, with some + standard processing applied. + + :param keyword_substitution: if `True`, meta-data keywords + present in the content like ``$Rev$`` are substituted + (which keyword are substituted and how they are + substituted is backend specific) + + :param eol_hint: which style of line ending is expected if + `None` was explicitly specified for the file itself in + the version control backend (for example in Subversion, + if it was set to ``'native'``). It can be `None`, + ``'LF'``, ``'CR'`` or ``'CRLF'``. + """ + return self.get_content() + def get_entries(self): """Generator that yields the immediate child entries of a directory.