jenkins-bot has submitted this change and it was merged.
Change subject: [bugfix] Enable prefixed ListOption
......................................................................
[bugfix] Enable prefixed ListOption
IntegerOption changes:
- minimum and maximum became properties to be easily overwritten by subclass
- test method does not parse the value anymore. The parse method does it.
- minimum may be equal to maximum
- constructor may fail for non-integer values with ValueError exception
- show default value in Option.format
ListOption changes:
- inherit ListOption from IntegerOption
- overwrite maximum property
- fix for result method
Other changes:
- Add some Test for choice options
- Add ui_options_tests.py and bot_choice.py to flake8-docstrings-mandatory
Bug: T105581
Change-Id: Iba46e2e6c2198678838af493ab89d345077447fd
---
M pywikibot/bot_choice.py
A tests/ui_options_tests.py
M tox.ini
3 files changed, 172 insertions(+), 25 deletions(-)
Approvals:
XZise: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/bot_choice.py b/pywikibot/bot_choice.py
index 95c1808..3db1958 100755
--- a/pywikibot/bot_choice.py
+++ b/pywikibot/bot_choice.py
@@ -166,16 +166,18 @@
def __init__(self, minimum=1, maximum=None, prefix=''):
"""Constructor."""
super(IntegerOption, self).__init__()
- if minimum is not None and maximum is not None and minimum >= maximum:
+ if not ((minimum is None or isinstance(minimum, int)) and
+ (maximum is None or isinstance(maximum, int))):
+ raise ValueError(
+ 'The minimum and maximum parameters must be int or None.')
+ if minimum is not None and maximum is not None and minimum > maximum:
raise ValueError('The minimum must be lower than the maximum.')
- self.minimum = minimum
- self.maximum = maximum
+ self._min = minimum
+ self._max = maximum
self.prefix = prefix
def test(self, value):
"""Return whether the value is an int and in the specified
range."""
- if not value.lower().startswith(self.prefix.lower()):
- return False
try:
value = self.parse(value)
except ValueError:
@@ -184,15 +186,43 @@
return ((self.minimum is None or value >= self.minimum) and
(self.maximum is None or value <= self.maximum))
+ @property
+ def minimum(self):
+ """return the minimum value."""
+ return self._min
+
+ @property
+ def maximum(self):
+ """return the maximum value."""
+ return self._max
+
def format(self, default):
"""Return a formatted string showing the range."""
- if self.minimum is not None or self.maximum is not None:
- _min = '' if self.minimum is None else str(self.minimum)
- _max = '' if self.maximum is None else str(self.maximum)
- rng = _min + '-' + _max
+ if default is not None and self.test(default):
+ value = self.parse(default)
+ default = '[{0}]'.format(value)
else:
- rng = 'any'
- return self.prefix + '<number> [' + rng + ']'
+ value = None
+ default = ''
+ if self.minimum is not None or self.maximum is not None:
+ if default and value == self.minimum:
+ minimum = default
+ default = ''
+ else:
+ minimum = '' if self.minimum is None else str(self.minimum)
+ if default and value == self.maximum:
+ maximum = default
+ default = ''
+ else:
+ maximum = '' if self.maximum is None else str(self.maximum)
+ default = '-{0}-'.format(default) if default else '-'
+ if self.minimum == self.maximum:
+ rng = minimum
+ else:
+ rng = minimum + default + maximum
+ else:
+ rng = 'any' + default
+ return '{0}<number> [{1}]'.format(self.prefix, rng)
def parse(self, value):
"""Return integer from value with prefix
removed."""
@@ -235,32 +265,34 @@
pywikibot.output(self.text[start_context:end_context])
-class ListOption(Option):
+class ListOption(IntegerOption):
"""An option to select something from a list."""
- def __init__(self, sequence, prefix):
+ def __init__(self, sequence, prefix=''):
"""Constructor."""
- super(ListOption, self).__init__()
self._list = sequence
- self._prefix = prefix
+ try:
+ super(ListOption, self).__init__(1, self.maximum, prefix)
+ except ValueError:
+ raise ValueError('The sequence is empty.')
+ del self._max
def format(self, default):
"""Return a string showing the range."""
- return '<number> [0-{0}]'.format(len(self._list) - 1)
-
- def test(self, value):
- """Test if the value is an int and in range."""
- try:
- value = int(value)
- except ValueError:
- return False
+ if not self._list:
+ raise ValueError('The sequence is empty.')
else:
- return 0 <= value < len(self._list)
+ return super(ListOption, self).format(default)
+
+ @property
+ def maximum(self):
+ """return the maximum value."""
+ return len(self._list)
def result(self, value):
"""Return a tuple with the prefix and selected
value."""
- return (self._prefix, self._list[int(value)])
+ return (self.prefix, self._list[self.parse(value) - 1])
class HighlightContextOption(ContextOption):
diff --git a/tests/ui_options_tests.py b/tests/ui_options_tests.py
new file mode 100644
index 0000000..0246ed9
--- /dev/null
+++ b/tests/ui_options_tests.py
@@ -0,0 +1,113 @@
+# -*- coding: utf-8 -*-
+"""Bot tests for input_choice options."""
+#
+# (C) Pywikibot team, 2015
+#
+# Distributed under the terms of the MIT license.
+#
+from __future__ import unicode_literals
+
+__version__ = '$Id$'
+#
+from pywikibot import bot, bot_choice
+
+from tests.aspects import unittest, TestCase
+
+message = bot.Option.formatted
+
+
+class TestChoiceOptions(TestCase):
+
+ """Test cases for input_choice Option."""
+
+ net = False
+
+ def test_formatted(self):
+ """Test static method Option.formatted."""
+ self.assertEqual(message('Question:', [], None), 'Question: ()')
+
+ def test_output(self):
+ """Test OutputOption."""
+ option = bot_choice.OutputOption()
+ with self.assertRaises(NotImplementedError):
+ message('?', [option], None)
+
+ def test_standard(self):
+ """Test StandardOption."""
+ option = bot.StandardOption('Test', 'T')
+ self.assertEqual(option.option, 'Test')
+ self.assertEqual(option.shortcut, 't')
+ self.assertEqual(option.shortcut, option.result(None))
+ self.assertEqual(option.format(None), '[t]est')
+ self.assertEqual(option.format('t'), '[T]est')
+ self.assertTrue(option.test('Test'))
+ self.assertTrue(option.test('t'))
+ self.assertTrue(option.test('T'))
+ self.assertFalse(option.test('?'))
+ self.assertIs(option.handled('T'), option)
+ self.assertIsNone(option.handled('?'))
+ self.assertEqual(message('?', [option], None), '? ([t]est)')
+ self.assertEqual(message('?', [option], 't'), '?
([T]est)')
+
+ def test_Nested(self):
+ """Test NestedOption."""
+ standard = bot.StandardOption('Test', 'T')
+ option = bot.NestedOption('Next', 'x', 'Nested:',
[standard])
+ self.assertEqual(option.format('x'), 'Ne[X]t')
+ self.assertEqual(option._output, 'Nested: ([t]est)')
+ self.assertEqual(message('?', [option], 't'), '?
(Ne[x]t)')
+ self.assertIs(standard.handled('t'), standard)
+ self.assertIs(option.handled('x'), option)
+ self.assertIs(option.handled('t'), standard)
+
+ def test_Integer(self):
+ """Test IntegerOption."""
+ option = bot.IntegerOption(maximum=5, prefix='r')
+ self.assertEqual(option.format('2'), 'r<number> [1-5]')
+ self.assertEqual(option.format('r2'), 'r<number>
[1-[2]-5]')
+ self.assertEqual(message('?', [option], None), '? (r<number>
[1-5])')
+ self.assertEqual(message('?', [option], 'r3'), '?
(r<number> [1-[3]-5])')
+ self.assertRaises(AttributeError, option.test, 1)
+ self.assertFalse(option.test('0'))
+ self.assertFalse(option.test('r0'))
+ self.assertFalse(option.test('r6'))
+ self.assertIsNone(option.handled('r6'))
+ for i in range(1, 6):
+ self.assertTrue(option.test('r%d' % i))
+ self.assertEqual(option.handled('r%d' % i), option)
+ self.assertEqual(option.result('r%d' % i), ('r', i))
+
+ def test_List(self):
+ """Test ListOption."""
+ self.assertRaises(ValueError, bot.ListOption, [])
+ options = ['foo', 'bar']
+ option = bot.ListOption(options)
+ self.assertEqual(message('?', [option], None), '? (<number>
[1-2])')
+ self.assertEqual(message('?', [option], '2'), '?
(<number> [1-[2]])')
+ options.pop()
+ self.assertEqual(message('?', [option], None), '? (<number>
[1])')
+ self.assertEqual(message('?', [option], '1'), '?
(<number> [[1]])')
+ options.pop()
+ self.assertRaises(ValueError, option.format, None)
+ self.assertFalse(option.test('0'))
+ options += ['baz', 'quux', 'norf']
+ self.assertEqual(message('?', [option], None), '? (<number>
[1-3])')
+ for prefix in ('', 'r', 'st'):
+ option = bot.ListOption(options, prefix=prefix)
+ self.assertEqual(message('?', [option], None),
+ '? (%s<number> [1-3])' % prefix)
+ for i, elem in enumerate(options, 1):
+ self.assertTrue(option.test('%s%d' % (prefix, i)))
+ self.assertIs(option.handled('%s%d' % (prefix, i)), option)
+ self.assertEqual(option.result('%s%d' % (prefix, i)),
+ (prefix, elem))
+ self.assertFalse(option.test('%s%d' % (prefix, len(options) + 1)))
+ self.assertIsNone(option.handled('%s%d'
+ % (prefix, len(options) + 1)))
+
+
+if __name__ == '__main__':
+ try:
+ unittest.main()
+ except SystemExit:
+ pass
diff --git a/tox.ini b/tox.ini
index 2650718..ae7e9c2 100644
--- a/tox.ini
+++ b/tox.ini
@@ -37,6 +37,7 @@
pywikibot/__init__.py \
pywikibot/backports.py \
pywikibot/bot.py \
+ pywikibot/bot_choice.py \
pywikibot/comms/__init__.py \
pywikibot/comms/http.py \
pywikibot/comms/rcstream.py \
@@ -131,6 +132,7 @@
tests/tools_chars_tests.py \
tests/tools_ip_tests.py \
tests/tools_tests.py \
+ tests/ui_options_tests.py \
tests/upload_tests.py \
tests/wikidataquery_tests.py
--
To view, visit
https://gerrit.wikimedia.org/r/224226
To unsubscribe, visit
https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Iba46e2e6c2198678838af493ab89d345077447fd
Gerrit-PatchSet: 15
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: Xqt <info(a)gno.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: Ricordisamoa <ricordisamoa(a)openmailbox.org>
Gerrit-Reviewer: XZise <CommodoreFabianus(a)gmx.de>
Gerrit-Reviewer: Xqt <info(a)gno.de>
Gerrit-Reviewer: jenkins-bot <>