jenkins-bot has submitted this change and it was merged.
Change subject: [FEAT] color_format to ignore valid colors
......................................................................
[FEAT] color_format to ignore valid colors
This allows to add colors to strings which will be filled using format. Instead
of calling 'str'.format(…), color_format('str', …) is called. That uses
the
`_ColorFormatter` class which parses the text differently and skips color
fields.
It also checks that the color fields are used without conversion and format
spec. The `\03` marker becomes optional and is added if missing and if it may
not be present anywhere else than in front of color field.
Change-Id: Ib354fb80180b0e421a18474cc67d7ee10cda3745
---
M pywikibot/bot.py
M pywikibot/tools/formatter.py
M tests/tools_formatter_tests.py
3 files changed, 113 insertions(+), 5 deletions(-)
Approvals:
John Vandenberg: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/bot.py b/pywikibot/bot.py
index c0a119c..25ed2b0 100644
--- a/pywikibot/bot.py
+++ b/pywikibot/bot.py
@@ -101,6 +101,7 @@
LoggingFormatter as _LoggingFormatter,
RotatingFileHandler,
)
+from pywikibot.tools.formatter import color_format
if not PY2:
unicode = str
@@ -742,17 +743,16 @@
'\03{default}' + text[rng[1]: rng[1] +
self.context])
question = 'Should the link '
else:
- question = 'Should the link \03{{lightred}}{0}\03{{default}} '
+ question = 'Should the link {lightred}{0}{default} '
if self._new is False:
question += 'be unlinked?'
else:
- question += ('target to '
- '\03{{{{lightpurple}}}}{0}\03{{{{default}}}}?').format(
- self._new.canonical_title())
+ question += color_format('target to {lightpurple}{0}{default}?',
+ self._new.canonical_title())
choice = pywikibot.input_choice(
- question.format(self._old.canonical_title()),
+ color_format(question, self._old.canonical_title()),
choices, default=self._default, automatic_quit=self._quit)
return self.handle_answer(choice)
diff --git a/pywikibot/tools/formatter.py b/pywikibot/tools/formatter.py
index d2f15e2..151e4d9 100644
--- a/pywikibot/tools/formatter.py
+++ b/pywikibot/tools/formatter.py
@@ -9,9 +9,13 @@
__version__ = '$Id$'
+import inspect
import math
+from string import Formatter
+
from pywikibot.logging import output
+from pywikibot.userinterfaces.terminal_interface_base import colors
class SequenceOutputter(object):
@@ -56,3 +60,63 @@
def output(self):
"""Output the text of the current sequence."""
output(self.format_list())
+
+
+class _ColorFormatter(Formatter):
+
+ """Special string formatter which skips colors."""
+
+ colors = set(colors)
+
+ def __init__(self):
+ """Create new instance and store the stack
depth."""
+ super(_ColorFormatter, self).__init__()
+ self._depth = len(inspect.stack())
+
+ def parse(self, format_string):
+ """Yield results similar to parse but skip
colors."""
+ previous_literal = ''
+ for literal, field, spec, conv in super(_ColorFormatter, self).parse(
+ format_string):
+ if field in self.colors:
+ if spec:
+ raise ValueError(
+ 'Color field "{0}" in "{1}" uses format
spec '
+ 'information "{2}"'.format(field,
format_string, spec))
+ elif conv:
+ raise ValueError(
+ 'Color field "{0}" in "{1}" uses
conversion '
+ 'information "{2}"'.format(field,
format_string, conv))
+ else:
+ if not literal or literal[-1] != '\03':
+ literal += '\03'
+ if '\03' in literal[:-1]:
+ raise ValueError(r'Literal text in {0} contains '
+ r'\03'.format(format_string))
+ previous_literal += literal + '{' + field + '}'
+ else:
+ if '\03' in literal:
+ raise ValueError(r'Literal text in {0} 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, args, kwargs):
+ """Return the normal format result but verify no colors are
keywords."""
+ if self.colors.intersection(kwargs): # kwargs use colors
+ raise ValueError('Keyword argument(s) use valid color(s): ' +
+ '",
"'.join(self.colors.intersection(kwargs)))
+ return super(_ColorFormatter, self).vformat(format_string, args,
+ kwargs)
+
+
+def color_format(text, *args, **kwargs):
+ r"""
+ Do C{str.format} without having to worry about colors.
+
+ It is automatically adding \03 in front of color fields so it's
+ unnecessary to add them manually. Any other \03 in the text is disallowed.
+ """
+ return _ColorFormatter().format(text, *args, **kwargs)
diff --git a/tests/tools_formatter_tests.py b/tests/tools_formatter_tests.py
index 74d9768..a441e35 100644
--- a/tests/tools_formatter_tests.py
+++ b/tests/tools_formatter_tests.py
@@ -31,6 +31,50 @@
self.assertEqual(outputter.format_list(), '\nfoo\nbar\n')
+class TestColorFormat(TestCase):
+
+ """Test color_format function in bot module."""
+
+ net = False
+
+ def test_no_colors(self):
+ """Test without colors in template string."""
+ self.assertEqual(formatter.color_format('42'), '42')
+ self.assertEqual(formatter.color_format('{0}', 42), '42')
+ self.assertEqual(formatter.color_format('{ans}', ans=42), '42')
+
+ def test_colors(self):
+ """Test with colors in template string."""
+ self.assertEqual(formatter.color_format('{0}{black}', 42),
+ '42\03{black}')
+ self.assertEqual(formatter.color_format('{ans}{black}', ans=42),
+ '42\03{black}')
+ self.assertRaisesRegex(
+ ValueError, r'.*conversion.*', formatter.color_format,
+ '{0}{black!r}', 42)
+ 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.assertEqual(formatter.color_format('{0}\03{black}', 42),
+ '42\03{black}')
+ # literal before a normal field
+ self.assertRaisesRegex(
+ ValueError, r'.*\\03', formatter.color_format,
+ '\03{0}{black}', 42)
+ # literal before a color field
+ self.assertRaisesRegex(
+ ValueError, r'.*\\03', formatter.color_format,
+ '{0}\03before{black}', 42)
+
+ def test_color_kwargs(self):
+ """Test with a color as keyword argument."""
+ self.assertRaises(ValueError,
+ formatter.color_format, '{aqua}{black}', aqua=42)
+
+
if __name__ == '__main__':
try:
unittest.main()
--
To view, visit
https://gerrit.wikimedia.org/r/222907
To unsubscribe, visit
https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Ib354fb80180b0e421a18474cc67d7ee10cda3745
Gerrit-PatchSet: 5
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: XZise <CommodoreFabianus(a)gmx.de>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Ladsgroup <ladsgroup(a)gmail.com>
Gerrit-Reviewer: Merlijn van Deen <valhallasw(a)arctus.nl>
Gerrit-Reviewer: Mpaa <mpaa.wiki(a)gmail.com>
Gerrit-Reviewer: XZise <CommodoreFabianus(a)gmx.de>
Gerrit-Reviewer: jenkins-bot <>