Sfoglia il codice sorgente

Add a range checker for all int fields (max is now 20 answers)

e.g. `-n -1`, `-p -2`, `-p 40`, `-n 35`
Benjamin Gleitzman 4 anni fa
parent
commit
f688c4f5f9
2 ha cambiato i file con 56 aggiunte e 4 eliminazioni
  1. 28 2
      howdoi/howdoi.py
  2. 28 2
      test_howdoi.py

+ 28 - 2
howdoi/howdoi.py

@@ -131,6 +131,31 @@ class BlockError(RuntimeError):
     pass
 
 
+class IntRange:
+    def __init__(self, imin=None, imax=None):
+        self.imin = imin
+        self.imax = imax
+
+    def __call__(self, arg):
+        try:
+            value = int(arg)
+        except ValueError as value_error:
+            raise self.exception() from value_error
+        if (self.imin is not None and value < self.imin) or (self.imax is not None and value > self.imax):
+            raise self.exception()
+        return value
+
+    def exception(self):
+        if self.imin is not None and self.imax is not None:
+            return argparse.ArgumentTypeError('Must be an integer in the range [{imin}, {imax}]'.format(
+                imin=self.imin, imax=self.imax))
+        if self.imin is not None:
+            return argparse.ArgumentTypeError('Must be an integer >= {imin}'.format(imin=self.imin))
+        if self.imax is not None:
+            return argparse.ArgumentTypeError('Must be an integer <= {imax}'.format(imax=self.imax))
+        return argparse.ArgumentTypeError('Must be an integer')
+
+
 def _random_int(width):
     bres = os.urandom(width)
     if sys.version < '3':
@@ -571,11 +596,12 @@ def howdoi(raw_query):
 def get_parser():
     parser = argparse.ArgumentParser(description='instant coding answers via the command line')
     parser.add_argument('query', metavar='QUERY', type=str, nargs='*', help='the question to answer')
-    parser.add_argument('-p', '--pos', help='select answer in specified position (default: 1)', default=1, type=int)
+    parser.add_argument('-p', '--pos', help='select answer in specified position (default: 1)',
+                        default=1, type=IntRange(1, 20))
     parser.add_argument('-a', '--all', help='display the full text of the answer', action='store_true')
     parser.add_argument('-l', '--link', help='display only the answer link', action='store_true')
     parser.add_argument('-c', '--color', help='enable colorized output', action='store_true')
-    parser.add_argument('-n', '--num-answers', help='number of answers to return', default=1, type=int)
+    parser.add_argument('-n', '--num-answers', help='number of answers to return', default=1, type=IntRange(1, 20))
     parser.add_argument('-C', '--clear-cache', help='clear the cache',
                         action='store_true')
     parser.add_argument('-j', '--json-output', help='return answers in raw json format',

+ 28 - 2
test_howdoi.py

@@ -31,6 +31,22 @@ class HowdoiTestCase(unittest.TestCase):
                 f.write(bytes(page_content, encoding='utf-8'))
                 return page_content
 
+    def _negative_number_query(self):
+        query = self.queries[0]
+        self.call_howdoi(query + ' -n -1')
+
+    def _high_positive_number_query(self):
+        query = self.queries[0]
+        self.call_howdoi(query + ' -n 21')
+
+    def _negative_position_query(self):
+        query = self.queries[0]
+        self.call_howdoi(query + ' -p -2')
+
+    def _high_positive_position_query(self):
+        query = self.queries[0]
+        self.call_howdoi(query + ' -p 40')
+
     def setUp(self):
         self.original_get_result = howdoi._get_result
         howdoi._get_result = self._get_result_mock
@@ -156,8 +172,8 @@ class HowdoiTestCase(unittest.TestCase):
         query = self.queries[0]
         normal = self.call_howdoi(query)
         colorized = self.call_howdoi('-c ' + query)
-        self.assertTrue(normal.find('[39;') is -1)
-        self.assertTrue(colorized.find('[39;') is not -1)
+        self.assertTrue(normal.find('[39;') == -1)
+        self.assertTrue(colorized.find('[39;') != -1)
 
     def test_get_text_without_links(self):
         html = '''\n  <p>The halting problem is basically a\n  formal way of asking if you can tell\n  whether or not an arbitrary program\n  will eventually halt.</p>\n  \n  <p>In other words, can you write a\n  program called a halting oracle,\n  HaltingOracle(program, input), which\n  returns true if program(input) would\n  eventually halt, and which returns\n  false if it wouldn't?</p>\n  \n  <p>The answer is: no, you can't.</p>\n'''
@@ -242,6 +258,16 @@ class HowdoiTestCase(unittest.TestCase):
         for query in bad_help_queries:
             self.assertFalse(howdoi._is_help_query(query))
 
+    def test_negative_and_high_positive_int_values_rejected(self):
+        with self.assertRaises(SystemExit):
+            self._negative_number_query()
+        with self.assertRaises(SystemExit):
+            self._negative_position_query()
+        with self.assertRaises(SystemExit):
+            self._high_positive_position_query()
+        with self.assertRaises(SystemExit):
+            self._high_positive_number_query()
+
 
 class HowdoiTestCaseEnvProxies(unittest.TestCase):