Skip to content

Commit

Permalink
Merge pull request #18127 from mkouba/qute-loop-else-block
Browse files Browse the repository at this point in the history
Qute - loop section - introduce the {#else} block
  • Loading branch information
mkouba authored Jun 25, 2021
2 parents bd6eee1 + 5d0a869 commit d3a042a
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 13 deletions.
25 changes: 19 additions & 6 deletions docs/src/main/asciidoc/qute-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -522,19 +522,19 @@ A section helper that defines the logic of a section can "execute" any of the bl
[[loop_section]]
==== Loop Section

The loop section makes it possible to iterate over an instance of `Iterable`, `Map` 's entry set, `Stream` and an `Integer`.
The loop section makes it possible to iterate over an instance of `Iterable`, `Iterator`, array, `Map` 's entry set, `Stream` and an `Integer`.
It has two flavors.
The first one is using the `each` name alias.
The first one is using the `each` name and `it` is an implicit alias for the iteration element.

[source]
----
{#each items}
{it.name} <1>
{/each}
----
<1> `it` is an implicit alias. `name` is resolved against the current iteration element.
<1> `name` is resolved against the current iteration element.

The other form is using the `for` name alias and can specify the alias used to reference the iteration element:
The other form is using the `for` name and can specify the alias used to reference the iteration element:

[source]
----
Expand All @@ -551,7 +551,9 @@ It's also possible to access the iteration metadata inside the loop:
{count}. {it.name} <1>
{/each}
----
<1> `count` represents one-based index. Metadata also include zero-based `index`, `hasNext`, `odd` and `even`.
<1> `count` represents one-based index.

TIP: The iteration metadata also include zero-based `index`, `hasNext`, `odd` and `even` properties.

The `for` statement also works with integers, starting from 1. In the example below, considering that `total = 3`:

Expand All @@ -562,13 +564,24 @@ The `for` statement also works with integers, starting from 1. In the example be
{/for}
----

The output will be:
And the output will be:

[source]
----
1:2:3:
----

A loop section may define the `{#else}` block that is executed when there are no items to iterate:

[source]
----
{#for item in items}
{item.name}
{#else}
No items.
{/for}
----

[[if_section]]
==== If Section

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

import static io.quarkus.qute.Parameter.EMPTY;

import io.quarkus.qute.SectionHelperFactory.SectionInitContext;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
Expand All @@ -20,13 +22,18 @@
public class LoopSectionHelper implements SectionHelper {

private static final String DEFAULT_ALIAS = "it";
private static final String ELSE = "else";
private static final String ALIAS = "alias";
private static final String ITERABLE = "iterable";

private final String alias;
private final Expression iterable;
private final SectionBlock elseBlock;

LoopSectionHelper(String alias, Expression iterable) {
this.alias = Parameter.EMPTY.equals(alias) ? DEFAULT_ALIAS : alias;
this.iterable = Objects.requireNonNull(iterable);
LoopSectionHelper(SectionInitContext context) {
this.alias = context.getParameterOrDefault(ALIAS, DEFAULT_ALIAS);
this.iterable = Objects.requireNonNull(context.getExpression(ITERABLE));
this.elseBlock = context.getBlock(ELSE);
}

@SuppressWarnings("unchecked")
Expand All @@ -47,7 +54,12 @@ public CompletionStage<ResultNode> resolve(SectionResolutionContext context) {
results.add(nextElement(iterator.next(), idx++, iterator.hasNext(), context));
}
if (results.isEmpty()) {
return ResultNode.NOOP;
// Execute the {#else} block if present
if (elseBlock != null) {
return context.execute(elseBlock, context.resolutionContext());
} else {
return ResultNode.NOOP;
}
}
if (results.size() == 1) {
return results.get(0);
Expand Down Expand Up @@ -129,9 +141,7 @@ public static class Factory implements SectionHelperFactory<LoopSectionHelper> {

public static final String HINT_ELEMENT = "<loop-element>";
public static final String HINT_PREFIX = "<loop#";
private static final String ALIAS = "alias";
private static final String IN = "in";
private static final String ITERABLE = "iterable";

@Override
public List<String> getDefaultAliases() {
Expand All @@ -147,9 +157,13 @@ public ParametersInfo getParameters() {
.build();
}

public List<String> getBlockLabels() {
return Collections.singletonList(ELSE);
}

@Override
public LoopSectionHelper initialize(SectionInitContext context) {
return new LoopSectionHelper(context.getParameter(ALIAS), context.getExpression(ITERABLE));
return new LoopSectionHelper(context);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,14 +116,34 @@ default public Map<String, String> getParameters() {
return getBlocks().get(0).parameters;
}

/**
*
* @return {@code true} if the main block declares a parameter of the given name
*/
default public boolean hasParameter(String name) {
return getParameters().containsKey(name);
}

/**
*
* @return the parameter, or null/{@link Parameter.EMPTY} if the main block does not declare a parameter of the given
* name
*/
default public String getParameter(String name) {
return getParameters().get(name);
}

/**
*
* @param name
* @param defaultValue
* @return the param or the default value if not specified
*/
default public String getParameterOrDefault(String name, String defaultValue) {
String param = getParameter(name);
return param == null || Parameter.EMPTY.equals(param) ? defaultValue : param;
}

/**
* Note that the expression must be registered in the {@link SectionHelperFactory#initializeBlock(Scope, BlockInfo)}
* first.
Expand All @@ -144,6 +164,20 @@ default public String getParameter(String name) {

public List<SectionBlock> getBlocks();

/**
*
* @param label
* @return the first block with the given label, or {code null} if no such exists
*/
default SectionBlock getBlock(String label) {
for (SectionBlock block : getBlocks()) {
if (label.equals(block.label)) {
return block;
}
}
return null;
}

public Engine getEngine();

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -184,4 +185,11 @@ void testScope() {
assertTrue(result.contains("hellllllo"), result);
}

@Test
public void testElseBlock() {
Engine engine = Engine.builder().addDefaults().build();
assertEquals("No items.",
engine.parse("{#for i in items}{item}{#else}No items.{/for}").data("items", Collections.emptyList()).render());
}

}

0 comments on commit d3a042a

Please sign in to comment.