diff --git a/README.asciidoc b/README.asciidoc index 1270daf..508302c 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -714,7 +714,7 @@ Produces: === Looping with `range` -The `range` macro substitutes a list of numbers that can be used in `repeat` macros. (Or anywhere else a list of numbers is needed). The start and end values are passed as a list argument. +The `range` macro substitutes a list of numbers that can be used in `repeat` macros. (Or anywhere else a list of numbers is needed). The start and end values are passed as a list argument. The range can count up or down, always by one. [source, YAML] ---- @@ -723,6 +723,28 @@ range: [3,5] Produces `[3,4,5]` +`range` also accepts a map object, in which case it expands the sequence of map keys. For example + +[source, YAML] +---- +- define: {map: {ra: 879, rb: 662}} +- range: map +---- + +Produces `[ra, rb]`. This can then be used in repeat to loop over the items in a map. Dot notation is used to expand individual members of the map. +For example here the loop variable is set to `ra` then `rb` which `map.keyz` resolves to `879` and `662`: + +[source, YAML] +---- +repeat: + for: keyz + in: {range: map} + body: + map.keyz +---- + +Be aware that map keys in data (such as `ra`) might conflict with already defined variables. + === Combining Lists with `flatten` Sometimes you need to combine lists, perhaps from different macro expansions. The `flatten` macro combines multiple lists into a single, flat, list. The flattening is recursive. Syntax: diff --git a/doc/README.html b/doc/README.html index 2c397cc..0e2c6f6 100644 --- a/doc/README.html +++ b/doc/README.html @@ -1523,7 +1523,7 @@

Looping with repeat

Looping with range

-

The range macro substitutes a list of numbers that can be used in repeat macros. (Or anywhere else a list of numbers is needed). The start and end values are passed as a list argument.

+

The range macro substitutes a list of numbers that can be used in repeat macros. (Or anywhere else a list of numbers is needed). The start and end values are passed as a list argument. The range can count up or down, always by one.

range: [3,5]

Produces [3,4,5]

+

range also accepts a map object, in which case it expands the sequence of map keys. For example

+
+
+
- define: {map: {ra: 879, rb: 662}}
+- range: map
+

Produces [ra, rb]. This can then be used in repeat to loop over the items in a map. Dot notation is used to expand individual members of the map. +For example here the loop variable is set to ra then rb which map.keyz resolves to 879 and 662:

+
+
+
repeat:
+  for: keyz
+  in: {range: map}
+  body:
+    map.keyz
+

Be aware that map keys in data (such as ra) might conflict with already defined variables.

Combining Lists with flatten

@@ -1895,7 +1916,7 @@

Updating This Document

diff --git a/src/yamp.py b/src/yamp.py index 37bdcb9..77c4c90 100755 --- a/src/yamp.py +++ b/src/yamp.py @@ -396,19 +396,43 @@ def plus_builtin(tree, args, bindings): sum += item return sum +def str_2_int(x): + """ + Because idigit() doesnt deal with +- + """ + if type(x) != str and type(x) != type(1): + return None, False + try: + x = int(x) + return x, True + except ValueError: + return None, False + def range_builtin(tree, statement, bindings): """ :return: a list from statement[0] to statement[1] """ if not statement: - raise(YampException('Syntax error was expecting integers list in {}, got {}'.format(tree, statement))) - validate_params(tree, {'range': None}, statement, [1,2]) - start = str(expand(statement[0], bindings)) - end = str(expand(statement[1], bindings)) - for item in [start, end]: - if not item.isdigit(): - raise(YampException('Syntax error was expecting integer range in {}, got {}'.format(tree, item))) - return list(range(int(start), int(end)+1)) + raise(YampException('range: was expecting map or integer sequence in {}'.format(tree))) + if type(statement) == list: + if len(statement) != 2: + raise(YampException('range: {} is not a sequence of two'.format(tree))) + start, sok = str_2_int(statement[0]) + end, eok = str_2_int(statement[1]) + if not sok or not eok: + raise(YampException('range: {} is not an integer in {}'.format(statement, tree))) + if start < end: + result = list(range(start, end+1)) + else: + result = list(range(start, end-1, -1)) + if len(result) == 0: + raise(YampException('range: empty range in {}'.format(tree))) + return result + elif type(statement) == dict: + return list(statement.keys()) + else: + raise(YampException('Syntax error was expecting map or integer sequence in {}, got {}'.format(tree, statement))) + def flatten_builtin(tree, args, bindings): """ diff --git a/test/all-examples.yaml b/test/all-examples.yaml index 2955690..962862b 100644 --- a/test/all-examples.yaml +++ b/test/all-examples.yaml @@ -31,6 +31,7 @@ include: - ../examples/multi_define.yaml - ../examples/python.yaml - ../examples/quote.yaml + - ../examples/range.yaml - ../examples/readme.yaml - ../examples/recursive.yaml - ../examples/repeat.yaml diff --git a/test/fixtures/all-examples.output.yaml b/test/fixtures/all-examples.output.yaml index 6f45e34..ad52343 100644 --- a/test/fixtures/all-examples.output.yaml +++ b/test/fixtures/all-examples.output.yaml @@ -295,6 +295,31 @@ webserver: - - 333 - 22 --- +- '*************** File: range.yaml *****************' +--- +- - 1 + - 2 +- - 1 + - 2 +- - -2 + - -1 +--- +- - 1 + - 0 +- - 1 + - 0 +- - -1 + - 0 +--- +ra: 879 +rb: 662 +--- +- ra +- rb +--- +- - 879 +- - 662 +--- - '*************** File: readme.yaml *****************' --- - Hello: World diff --git a/test/test_expand_01.py b/test/test_expand_01.py index d9e9ccb..19217cd 100644 --- a/test/test_expand_01.py +++ b/test/test_expand_01.py @@ -665,7 +665,29 @@ def testValidateKeys(self): self.try_validate_keys_error("Unexpected", [('a',)], {'a': None, 'b': None}) self.try_validate_keys_error("Unexpected", [('a',)], {'b': None}) - + def teststr_2_int_OK(self): + self.assertEquals((2, True), (str_2_int(2))) + self.assertEquals((2, True), (str_2_int("2"))) + self.assertEquals((2, True), (str_2_int("+2"))) + self.assertEquals((-2, True), (str_2_int("-2"))) + + def teststr_2_int_bad(self): + self.assertEquals((None, False), (str_2_int((2,)))) + self.assertEquals((None, False), (str_2_int([2]))) + self.assertEquals((None, False), (str_2_int({"x": 3}))) + self.assertEquals((None, False), (str_2_int(2.3))) + self.assertEquals((None, False), (str_2_int("x"))) + + def test_range_builtin_ok(self): + self.assertEquals([3,2,1],range_builtin({ "range:" : [3,1]}, [3,1], {})) + + def test_range_builtin_bad(self): + with self.assertRaises(Exception) as context: + range_builtin({ "range:" : None}, None, {}) + with self.assertRaises(Exception) as context: + range_builtin({ "range:" : [1]}, [1], {}) + with self.assertRaises(Exception) as context: + range_builtin({ "range:" :[1, "x"]}, [1, "x"], {}) def runFileRegression(self, file_to_test, fixture): tempout = tempfile.mkstemp()