jenkins-bot submitted this change.

View Change

Approvals: Xqt: Looks good to me, approved jenkins-bot: Verified
[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(-)

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 = {'&params;': 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')


To view, visit change 789191. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Change-Id: I811d85ab7205f43da798d42c72ff81283a5ccecd
Gerrit-Change-Number: 789191
Gerrit-PatchSet: 4
Gerrit-Owner: Xqt <info@gno.de>
Gerrit-Reviewer: JAn Dudík <jan.dudik@gmail.com>
Gerrit-Reviewer: Xqt <info@gno.de>
Gerrit-Reviewer: jenkins-bot
Gerrit-MessageType: merged