jenkins-bot has submitted this change. ( https://gerrit.wikimedia.org/r/c/pywikibot/core/+/789191 )
Change subject: [bugfix] do not use old color format for transliterated chars ......................................................................
[bugfix] do not use old color format for transliterated chars
- do not use old color format for transliterated chars - fix other occurences of old color format - deprecate the old color format. There are to many problems if keeping both variants. - use new color format with deprecated color_format function - update tests accordingly - remove unused _ColorFormatter class
Bug: T307550 Change-Id: I811d85ab7205f43da798d42c72ff81283a5ccecd --- M pywikibot/bot_choice.py M pywikibot/tools/formatter.py M pywikibot/userinterfaces/terminal_interface_base.py M scripts/commons_information.py M scripts/listpages.py M tests/tools_formatter_tests.py M tests/ui_tests.py 7 files changed, 72 insertions(+), 105 deletions(-)
Approvals: Xqt: Looks good to me, approved jenkins-bot: Verified
diff --git a/pywikibot/bot_choice.py b/pywikibot/bot_choice.py index a1b3ac6..8e4e7b3 100644 --- a/pywikibot/bot_choice.py +++ b/pywikibot/bot_choice.py @@ -59,7 +59,7 @@ formatted_options.append(option.format(default=default)) # remove color highlights before fill function text = '{} ({})'.format(text, ', '.join(formatted_options)) - pattern = '\03{[a-z]+}' + pattern = '<<[a-z]+>>' highlights = re.findall(pattern, text) return fill(re.sub(pattern, '{}', text), width=77).format(*highlights)
diff --git a/pywikibot/tools/formatter.py b/pywikibot/tools/formatter.py index 6a4bf2f..80f207b 100644 --- a/pywikibot/tools/formatter.py +++ b/pywikibot/tools/formatter.py @@ -5,13 +5,11 @@ # Distributed under the terms of the MIT license. # import math -import platform -from string import Formatter -from typing import Any, Mapping, Sequence +import re
from pywikibot.logging import output from pywikibot.tools import deprecated -from pywikibot.userinterfaces.terminal_interface_base import colors +from pywikibot.userinterfaces import terminal_interface_base
class SequenceOutputter: @@ -65,64 +63,6 @@ output(self.out)
-class _ColorFormatter(Formatter): - - """Special string formatter which skips colors.""" - - colors = set(colors) - # Dot.product of colors to create all possible combinations of foreground - # and background colors. - colors |= {'{};{}'.format(c1, c2) for c1 in colors for c2 in colors} - - def get_value(self, key, args, kwargs): - """Get value, filling in 'color' when it is a valid color.""" - if key == 'color' and kwargs.get('color') in self.colors: - return '\03{{{}}}'.format(kwargs[key]) - return super().get_value(key, args, kwargs) - - def parse(self, format_string: str): - """Yield results similar to parse but skip colors.""" - previous_literal = '' - for literal, field, spec, conv in super().parse(format_string): - if field in self.colors: - if spec: - raise ValueError( - 'Color field "{}" in "{}" uses format spec ' - 'information "{}"'.format(field, format_string, spec)) - if conv: - raise ValueError( - 'Color field "{}" in "{}" uses conversion ' - 'information "{}"'.format(field, format_string, conv)) - if not literal or literal[-1] != '\03': - literal += '\03' - if '\03' in literal[:-1]: - raise ValueError(r'Literal text in {} contains ' - r'\03'.format(format_string)) - previous_literal += literal + '{' + field + '}' - else: - if '\03' in literal: - raise ValueError(r'Literal text in {} contains ' - r'\03'.format(format_string)) - yield previous_literal + literal, field, spec, conv - previous_literal = '' - if previous_literal: - yield previous_literal, None, None, None - - def vformat(self, format_string: str, args: Sequence, - kwargs: Mapping[str, Any]) -> str: - """Return the format result but verify no colors are keywords. - - :param format_string: The format template string - :param args: The positional field values - :param kwargs: The named field values - :return: The formatted string - """ - if self.colors.intersection(kwargs): # kwargs use colors - raise ValueError('Keyword argument(s) use valid color(s): ' - + '", "'.join(self.colors.intersection(kwargs))) - return super().vformat(format_string, args, kwargs) - - @deprecated('New color format pattern like <<color>>colored text<<default>>', since='7.2.0') def color_format(text: str, *args, **kwargs) -> str: @@ -142,8 +82,29 @@
:param text: The format template string :return: The formatted string + :raises ValueError: Wrong format string or wrong keywords """ - if platform.python_implementation() == 'PyPy' \ - and isinstance(text, bytes): # T296830 - raise TypeError("'text' parameter must be a str not bytes") - return _ColorFormatter().format(text, *args, **kwargs) + colors = set(terminal_interface_base.colors) + # Dot.product of colors to create all possible combinations of foreground + # and background colors. + colors |= {'{};{}'.format(c1, c2) for c1 in colors for c2 in colors} + col_pat = '|'.join(colors) + text = re.sub('(?:\03)?{{({})}}'.format(col_pat), r'<<\1>>', text) + replace_color = kwargs.get('color') + if replace_color in colors: + text = text.replace('{color}', '<<{}>>'.format(replace_color)) + if '\03' in text: + raise ValueError('\03 pattern found in color format') + intersect = colors.intersection(kwargs) # kwargs use colors + if intersect: + raise ValueError('Keyword argument(s) use valid color(s): ' + + '", "'.join(intersect)) + try: + text = text.format(*args, **kwargs) + except KeyError as e: + if str(e).strip("'") in colors: + raise ValueError( + 'Color field "{}" in "{}" uses conversion information or ' + 'format spec'.format(e, text)) + raise + return text diff --git a/pywikibot/userinterfaces/terminal_interface_base.py b/pywikibot/userinterfaces/terminal_interface_base.py index 0444206..c49b3d0 100644 --- a/pywikibot/userinterfaces/terminal_interface_base.py +++ b/pywikibot/userinterfaces/terminal_interface_base.py @@ -22,7 +22,7 @@ StandardOption, ) from pywikibot.logging import INFO, INPUT, STDOUT, VERBOSE, WARNING -from pywikibot.tools import RLock +from pywikibot.tools import issue_deprecation_warning, RLock from pywikibot.userinterfaces import transliteration from pywikibot.userinterfaces._interface_base import ABUIC
@@ -201,7 +201,14 @@ raise ValueError('Old color format must not be mixed with new ' 'color format. Found:\n' + text.replace('\03', '\03')) - text_parts = old_parts if len(old_parts) > 1 else new_parts + if len(old_parts) > 1: + issue_deprecation_warning( + 'old color format variant like \03{color}', + 'new color format like <<color>>', + since='7.3.0') + text_parts = old_parts + else: + text_parts = new_parts text_parts += ['default'] # match.split() includes every regex group; for each matched color # fg_col:b_col, fg_col and bg_col are added to the resulting list. @@ -311,9 +318,9 @@ # could consist of multiple letters. # mark the transliterated letters in yellow. transliteratedText = ''.join((transliteratedText, - '\03{lightyellow}', + '<<lightyellow>>', transliterated, - '\03{previous}')) + '<<previous>>')) # memorize if we replaced a single letter by multiple # letters. if transliterated: diff --git a/scripts/commons_information.py b/scripts/commons_information.py index a17f134..5402092 100755 --- a/scripts/commons_information.py +++ b/scripts/commons_information.py @@ -69,10 +69,10 @@ tmp_page2 = pywikibot.Page(self.site, langs[0].lang, ns=10) if tmp_page2 != tmp_page: pywikibot.output( - '\03{{lightblue}}The language template {before!r} ' + '<<lightblue>>The language template {before!r} ' 'was found, but langdetect thinks {after!r} is the ' 'most appropriate with a probability of {prob}:' - '\03{{default}}\n{text}' + '<<default>>\n{text}' .format(before=tmp_page.title(with_ns=False), after=tmp_page2.title(with_ns=False), prob=langs[0].prob, diff --git a/scripts/listpages.py b/scripts/listpages.py index 0c5e9b2..f206e25 100755 --- a/scripts/listpages.py +++ b/scripts/listpages.py @@ -24,7 +24,7 @@ 4 - '[[{page.title}]]' --> [[PageTitle]]
- 5 - '{num:4d} \03{{lightred}}{page.loc_title:<40}\03{{default}}' + 5 - '{num:4d} <<lightred>>{page.loc_title:<40}<<default>>' --> 10 localised_Namespace:PageTitle (colorised in lightred)
6 - '{num:4d} {page.loc_title:<40} {page.can_title:<40}' @@ -96,8 +96,9 @@
import pywikibot from pywikibot import config, i18n -from pywikibot.exceptions import Error +from pywikibot.exceptions import ArgumentDeprecationWarning, Error from pywikibot.pagegenerators import GeneratorFactory, parameterHelp +from pywikibot.tools import issue_deprecation_warning
docuReplacements = {'¶ms;': parameterHelp} # noqa: N816 @@ -112,7 +113,7 @@ '2': '{num:4d} [[{page.title}]]', '3': '{page.title}', '4': '[[{page.title}]]', - '5': '{num:4d} \03{{lightred}}{page.loc_title:<40}\03{{default}}', + '5': '{num:4d} <<lightred>>{page.loc_title:<40}<<default>>', '6': '{num:4d} {page.loc_title:<40} {page.can_title:<40}', '7': '{num:4d} {page.loc_title:<40} {page.trs_title:<40}', } @@ -193,7 +194,13 @@ if option == '-notitle': notitle = True elif option == '-format': - fmt = value.replace('\03{{', '\03{{') + if '\03{{' in value: + fmt = value.replace('\03{{', '\03{{') + issue_deprecation_warning( + 'old color format variant like \03{color}', + 'new color format like <<color>>', + warning_class=ArgumentDeprecationWarning, + since='7.3.0') if not fmt.strip(): notitle = True elif option == '-outputlang': diff --git a/tests/tools_formatter_tests.py b/tests/tools_formatter_tests.py index 82c0a57..5345d80 100755 --- a/tests/tools_formatter_tests.py +++ b/tests/tools_formatter_tests.py @@ -60,29 +60,21 @@
def test_colors(self): """Test with colors in template string.""" - self.assert_format('{0}{black}', '42\03{black}', 42) - self.assert_format('{ans}{black}', '42\03{black}', ans=42) - with self.assertRaisesRegex( - ValueError, - r'.*conversion.*'): + self.assert_format('{0}{black}', '42<<black>>', 42) + self.assert_format('{ans}{black}', '42<<black>>', ans=42) + with self.assertRaisesRegex(ValueError, r'.*conversion.*'): formatter.color_format('{0}{black!r}', 42) - with self.assertRaisesRegex( - ValueError, - r'.*format spec.*'): + with self.assertRaisesRegex(ValueError, r'.*format spec.*'): formatter.color_format('{0}{black:03}', 42)
def test_marker(self): r"""Test that the \03 marker is only allowed in front of colors.""" - self.assert_format('{0}\03{black}', '42\03{black}', 42) + self.assert_format('{0}\03{black}', '42<<black>>', 42) # literal before a normal field - with self.assertRaisesRegex( - ValueError, - r'.*\03'): + with self.assertRaisesRegex(ValueError, r'.*\03'): formatter.color_format('\03{0}{black}', 42) # literal before a color field - with self.assertRaisesRegex( - ValueError, - r'.*\03'): + with self.assertRaisesRegex(ValueError, r'.*\03'): formatter.color_format('{0}\03before{black}', 42)
def test_color_kwargs(self): @@ -93,9 +85,9 @@ def test_non_ascii(self): """Test non-ASCII replacements.""" self.assert_format('{0}', 'ä', 'ä') - self.assert_format('{black}{0}', '\03{black}ä', 'ä') + self.assert_format('{black}{0}', '<<black>>ä', 'ä') self.assert_format('{0}', 'ä', self.DummyUnicode()) - self.assert_format('{black}{0}', '\03{black}ä', self.DummyUnicode()) + self.assert_format('{black}{0}', '<<black>>ä', self.DummyUnicode())
def test_bytes_format(self): """Test that using `bytes` is not allowed.""" @@ -106,8 +98,8 @@
def test_variant_colors(self): """Test variant colors with {color} parameter.""" - self.assert_format('{0}{color}', '42\03{black}', 42, color='black') - self.assert_format('{ans}{color}', '42\03{black}', ans=42, + self.assert_format('{0}{color}', '42<<black>>', 42, color='black') + self.assert_format('{ans}{color}', '42<<black>>', ans=42, color='black') self.assert_format('{color}', '42', color=42)
diff --git a/tests/ui_tests.py b/tests/ui_tests.py index 3c81000..57654eb 100755 --- a/tests/ui_tests.py +++ b/tests/ui_tests.py @@ -281,7 +281,7 @@
"""Terminal output color tests."""
- str1 = 'text \03{lightpurple}light purple text\03{default} text' + str1 = 'text <<lightpurple>>light purple text<<default>> text'
def testOutputColorizedText(self): pywikibot.output(self.str1) @@ -298,9 +298,9 @@ self.strerr.getvalue(), 'text light purple text text ***\n')
- str2 = ('normal text \03{lightpurple} light purple ' - '\03{lightblue} light blue \03{previous} light purple ' - '\03{default} normal text') + str2 = ('normal text <<lightpurple>> light purple ' + '<<lightblue>> light blue <<previous>> light purple ' + '<<default>> normal text')
def testOutputColorCascade_incorrect(self): """Test incorrect behavior of testOutputColorCascade.""" @@ -420,7 +420,7 @@ """Test a string using one color.""" self._colors = (('red', 6), ('default', 10)) with redirect_stdout(self.redirect) as f: - self.ui_obj._print('Hello \03{red}world you!', self.ui_obj.stdout) + self.ui_obj._print('Hello <<red>>world you!', self.ui_obj.stdout) self.assertEqual(f.getvalue(), self.expected)
def test_flat_color(self): @@ -429,7 +429,7 @@ ('default', 1)) with redirect_stdout(self.redirect) as f: self.ui_obj._print( - 'Hello \03{red}world \03{default}you\03{yellow}!', + 'Hello <<red>>world <<default>>you<<yellow>>!', self.ui_obj.stdout) self.assertEqual(f.getvalue(), self.expected)
@@ -438,7 +438,7 @@ self._colors = (('red', 6), ('yellow', 6), ('red', 3), ('default', 1)) with redirect_stdout(self.redirect) as f: self.ui_obj._print( - 'Hello \03{red}world \03{yellow}you\03{previous}!', + 'Hello <<red>>world <<yellow>>you<<previous>>!', self.ui_obj.stdout) self.assertEqual(f.getvalue(), self.expected)
@@ -446,7 +446,7 @@ """Test using stacked colors without popping any.""" self._colors = (('red', 6), ('yellow', 6), ('default', 4)) with redirect_stdout(self.redirect) as f: - self.ui_obj._print('Hello \03{red}world \03{yellow}you!', + self.ui_obj._print('Hello <<red>>world <<yellow>>you!', self.ui_obj.stdout) self.assertEqual(f.getvalue(), self.expected)
@@ -454,7 +454,7 @@ """Test with trailing new line and one color.""" self._colors = (('red', 6), ('default', 11)) with redirect_stdout(self.redirect) as f: - self.ui_obj._print('Hello \03{red}world you!\n', + self.ui_obj._print('Hello <<red>>world you!\n', self.ui_obj.stdout) self.assertEqual(f.getvalue(), self.expected + '\n')
pywikibot-commits@lists.wikimedia.org