Skip to content

Commit

Permalink
#10 range: over keys of a map
Browse files Browse the repository at this point in the history
  • Loading branch information
birchb1024 committed Jun 12, 2019
1 parent 4c82cad commit 4dddd79
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 12 deletions.
24 changes: 23 additions & 1 deletion README.asciidoc
Expand Up @@ -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]
----
Expand All @@ -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:
Expand Down
25 changes: 23 additions & 2 deletions doc/README.html
Expand Up @@ -1523,14 +1523,35 @@ <h3 id="_looping_with_code_repeat_code">Looping with <code>repeat</code></h3>
</div>
<div class="sect2">
<h3 id="_looping_with_code_range_code">Looping with <code>range</code></h3>
<div class="paragraph"><p>The <code>range</code> macro substitutes a list of numbers that can be used in <code>repeat</code> macros. (Or anywhere else a list of numbers is needed). The start and end values are passed as a list argument.</p></div>
<div class="paragraph"><p>The <code>range</code> macro substitutes a list of numbers that can be used in <code>repeat</code> 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.</p></div>
<div class="listingblock">
<div class="content"><!-- Generator: GNU source-highlight 3.1.8
by Lorenzo Bettini
http://www.lorenzobettini.it
http://www.gnu.org/software/src-highlite -->
<pre><tt><span style="color: #009900">range</span><span style="color: #990000">:</span> [<span style="color: #993399">3</span>,<span style="color: #993399">5</span>]</tt></pre></div></div>
<div class="paragraph"><p>Produces <code>[3,4,5]</code></p></div>
<div class="paragraph"><p><code>range</code> also accepts a map object, in which case it expands the sequence of map keys. For example</p></div>
<div class="listingblock">
<div class="content"><!-- Generator: GNU source-highlight 3.1.8
by Lorenzo Bettini
http://www.lorenzobettini.it
http://www.gnu.org/software/src-highlite -->
<pre><tt><span style="color: #990000">- </span><span style="color: #009900">define</span><span style="color: #990000">:</span> {map: {ra: <span style="color: #993399">879</span>, rb: <span style="color: #993399">662</span>}}
<span style="color: #990000">- </span><span style="color: #009900">range</span><span style="color: #990000">:</span> map</tt></pre></div></div>
<div class="paragraph"><p>Produces <code>[ra, rb]</code>. 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 <code>ra</code> then <code>rb</code> which <code>map.keyz</code> resolves to <code>879</code> and <code>662</code>:</p></div>
<div class="listingblock">
<div class="content"><!-- Generator: GNU source-highlight 3.1.8
by Lorenzo Bettini
http://www.lorenzobettini.it
http://www.gnu.org/software/src-highlite -->
<pre><tt><span style="color: #009900">repeat</span><span style="color: #990000">:</span>
<span style="color: #990000"> </span><span style="color: #009900">for</span><span style="color: #990000">:</span> keyz
<span style="color: #990000"> </span><span style="color: #009900">in</span><span style="color: #990000">:</span> {range: map}
<span style="color: #990000"> </span><span style="color: #009900">body</span><span style="color: #990000">:</span>
map.keyz</tt></pre></div></div>
<div class="paragraph"><p>Be aware that map keys in data (such as <code>ra</code>) might conflict with already defined variables.</p></div>
</div>
<div class="sect2">
<h3 id="_combining_lists_with_code_flatten_code">Combining Lists with <code>flatten</code></h3>
Expand Down Expand Up @@ -1895,7 +1916,7 @@ <h3 id="_updating_this_document">Updating This Document</h3>
<div id="footer-text">
Version 0.2<br />
Last updated
2019-06-11 15:34:25 AEST
2019-06-12 12:21:14 AEST
</div>
</div>
</body>
Expand Down
40 changes: 32 additions & 8 deletions src/yamp.py
Expand Up @@ -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):
"""
Expand Down
1 change: 1 addition & 0 deletions test/all-examples.yaml
Expand Up @@ -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
Expand Down
25 changes: 25 additions & 0 deletions test/fixtures/all-examples.output.yaml
Expand Up @@ -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
Expand Down
24 changes: 23 additions & 1 deletion test/test_expand_01.py
Expand Up @@ -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()
Expand Down

0 comments on commit 4dddd79

Please sign in to comment.