aboutsummaryrefslogtreecommitdiffstats
path: root/node-admin
diff options
context:
space:
mode:
authorHåkon Hallingstad <hakon@yahooinc.com>2022-01-10 19:23:35 +0100
committerHåkon Hallingstad <hakon@yahooinc.com>2022-01-11 13:20:03 +0100
commit27386c0e1e6f592ac8e7ceaee33eb40ac4aa9985 (patch)
tree8cfc8ef215121a144b6a438c6fd4409bde8eeb20 /node-admin
parent32f7e0d2b655a823a9e5a6d03310b54db1286ef0 (diff)
Add if section
Diffstat (limited to 'node-admin')
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/Form.java33
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/FormBuilder.java89
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/IfSection.java57
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/LiteralSection.java5
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/NoSuchNameTemplateException.java6
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/NotBooleanValueTemplateException.java11
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/Section.java2
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/SectionList.java44
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/SubformSection.java29
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/Template.java27
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/TemplateBuilder.java48
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/TemplateNameNotSetException.java2
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/TemplateParser.java30
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/VariableSection.java6
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/template/TemplateFileTest.java37
15 files changed, 273 insertions, 153 deletions
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/Form.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/Form.java
index 00e2e87fc5a..c95885d7753 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/Form.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/Form.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.node.admin.task.util.template;
import com.yahoo.vespa.hosted.node.admin.task.util.text.CursorRange;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -13,25 +14,25 @@ import java.util.Optional;
* @see Template
* @author hakonhall
*/
-public class Form extends Section {
- private final Form parent;
+public class Form {
+ private Form parent = null;
+ private final CursorRange range;
private final List<Section> sections;
- private final Map<String, String> variables;
+ private final Map<String, String> values = new HashMap<>();
private final Map<String, SubformSection> subforms;
- Form(Form parent, CursorRange range, List<Section> sections, Map<String, String> variables,
- Map<String, SubformSection> subforms) {
- super(range);
- this.parent = parent;
+ Form(CursorRange range, List<Section> sections, Map<String, SubformSection> subforms) {
+ this.range = new CursorRange(range);
this.sections = List.copyOf(sections);
- this.variables = variables; // Mutable and referenced by the variable sections
this.subforms = Map.copyOf(subforms);
}
+ void setParent(Form parent) { this.parent = parent; }
+
/** Set the value of a variable expression, e.g. %{=color}. */
public Form set(String name, String value) {
- variables.put(name, value);
+ values.put(name, value);
return this;
}
@@ -52,24 +53,30 @@ public class Form extends Section {
public Form add(String name) {
var section = subforms.get(name);
if (section == null) {
- throw new NoSuchNameTemplateException(this, name);
+ throw new NoSuchNameTemplateException(range, name);
}
return section.add();
}
public String render() {
- var buffer = new StringBuilder((int) (range().length() * 1.2 + 128));
+ var buffer = new StringBuilder((int) (range.length() * 1.2 + 128));
appendTo(buffer);
return buffer.toString();
}
- @Override
public void appendTo(StringBuilder buffer) {
sections.forEach(section -> section.appendTo(buffer));
}
+ /** Returns a deep copy of this. No changes to this affects the returned form, and vice versa. */
+ Form copy() {
+ var builder = new FormBuilder(range.start());
+ sections.forEach(section -> section.appendCopyTo(builder.topLevelSectionList()));
+ return builder.build();
+ }
+
Optional<String> getVariableValue(String name) {
- String value = variables.get(name);
+ String value = values.get(name);
if (value != null) return Optional.of(value);
if (parent != null) {
return parent.getVariableValue(name);
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/FormBuilder.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/FormBuilder.java
index d0274d7777a..833e5916fce 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/FormBuilder.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/FormBuilder.java
@@ -2,67 +2,80 @@
package com.yahoo.vespa.hosted.node.admin.task.util.template;
import com.yahoo.vespa.hosted.node.admin.task.util.text.Cursor;
-import com.yahoo.vespa.hosted.node.admin.task.util.text.CursorRange;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.function.Consumer;
/**
* @author hakonhall
*/
class FormBuilder {
- private final List<Section> sections = new ArrayList<>();
- private final Map<String, String> variables = new HashMap<>();
+ /** The top-level section list in this form. */
+ private final SectionList sectionList;
+ private final List<Section> allSections = new ArrayList<>();
+ private final Map<String, VariableSection> sampleVariables = new HashMap<>();
+ private final Map<String, IfSection> sampleIfSections = new HashMap<>();
private final Map<String, SubformSection> subforms = new HashMap<>();
- private final Form parent;
- private final CursorRange range;
- static Form build(Form parent, CursorRange range, List<Consumer<FormBuilder>> sections) {
- var builder = new FormBuilder(parent, range);
- sections.forEach(section -> section.accept(builder));
- return builder.build();
+ FormBuilder(Cursor start) {
+ this.sectionList = new SectionList(start, this);
}
- private FormBuilder(Form parent, CursorRange range) {
- this.parent = parent;
- this.range = new CursorRange(range);
- }
+ SectionList topLevelSectionList() { return sectionList; }
- FormBuilder addLiteralSection(CursorRange range) {
- sections.add(new LiteralSection(range));
- return this;
+ void addLiteralSection(LiteralSection section) {
+ allSections.add(section);
}
- FormBuilder addVariableSection(CursorRange range, String name, Cursor nameOffset) {
- var section = new VariableSection(range, name, nameOffset);
- sections.add(section);
- return this;
- }
+ void addVariableSection(VariableSection section) {
+ // It's OK if the same name is used in an if-directive (as long as the value is boolean,
+ // determined when set on a form).
+
+ SubformSection existing = subforms.get(section.name());
+ if (existing != null)
+ throw new NameAlreadyExistsTemplateException(section.name(), existing.nameOffset(),
+ section.nameOffset());
- FormBuilder addSubformSection(CursorRange range, String name, Template body) {
- checkNameIsAvailable(name, range);
- var section = new SubformSection(range, name, body);
- sections.add(section);
- subforms.put(section.name(), section);
- return this;
+ sampleVariables.put(section.name(), section);
+ allSections.add(section);
}
- private Form build() {
- var form = new Form(parent, range, sections, variables, subforms);
- sections.forEach(section -> section.setForm(form));
- return form;
+ void addIfSection(IfSection section) {
+ // It's OK if the same name is used in a variable section (as long as the value is boolean,
+ // determined when set on a form).
+
+ SubformSection subform = subforms.get(section.name());
+ if (subform != null)
+ throw new NameAlreadyExistsTemplateException(section.name(), subform.nameOffset(),
+ section.nameOffset());
+
+ sampleIfSections.put(section.name(), section);
+ allSections.add(section);
}
- private void checkNameIsAvailable(String name, CursorRange range) {
- if (nameIsDefined(name)) {
- throw new NameAlreadyExistsTemplateException(name, range);
- }
+ void addSubformSection(SubformSection section) {
+ VariableSection variableSection = sampleVariables.get(section.name());
+ if (variableSection != null)
+ throw new NameAlreadyExistsTemplateException(section.name(), variableSection.nameOffset(),
+ section.nameOffset());
+
+ IfSection ifSection = sampleIfSections.get(section.name());
+ if (ifSection != null)
+ throw new NameAlreadyExistsTemplateException(section.name(), ifSection.nameOffset(),
+ section.nameOffset());
+
+ SubformSection previous = subforms.put(section.name(), section);
+ if (previous != null)
+ throw new NameAlreadyExistsTemplateException(section.name(), previous.nameOffset(),
+ section.nameOffset());
+ allSections.add(section);
}
- private boolean nameIsDefined(String name) {
- return variables.containsKey(name) || subforms.containsKey(name);
+ Form build() {
+ var form = new Form(sectionList.range(), sectionList.sections(), subforms);
+ allSections.forEach(section -> section.setForm(form));
+ return form;
}
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/IfSection.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/IfSection.java
new file mode 100644
index 00000000000..33e62750f69
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/IfSection.java
@@ -0,0 +1,57 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.node.admin.task.util.template;
+
+import com.yahoo.vespa.hosted.node.admin.task.util.text.Cursor;
+import com.yahoo.vespa.hosted.node.admin.task.util.text.CursorRange;
+
+import java.util.Optional;
+
+/**
+ * @author hakonhall
+ */
+public class IfSection extends Section {
+ private final boolean negated;
+ private final String name;
+ private final Cursor nameOffset;
+ private final SectionList ifSections;
+
+ public IfSection(CursorRange range, boolean negated, String name, Cursor nameOffset,
+ SectionList ifSections) {
+ super(range);
+ this.negated = negated;
+ this.name = name;
+ this.nameOffset = nameOffset;
+ this.ifSections = ifSections;
+ }
+
+ String name() { return name; }
+ Cursor nameOffset() { return nameOffset; }
+
+ @Override
+ void appendTo(StringBuilder buffer) {
+ Optional<String> stringValue = form().getVariableValue(name);
+ if (stringValue.isEmpty())
+ throw new TemplateNameNotSetException(name, nameOffset);
+
+ final boolean value;
+ if (stringValue.get().equals("true")) {
+ value = true;
+ } else if (stringValue.get().equals("false")) {
+ value = false;
+ } else {
+ throw new NotBooleanValueTemplateException(name);
+ }
+
+ boolean condition = negated ? !value : value;
+ if (condition) {
+ ifSections.sections().forEach(section -> section.appendTo(buffer));
+ }
+ }
+
+ @Override
+ void appendCopyTo(SectionList sectionList) {
+ SectionList ifSectionCopy = new SectionList(ifSections.range().start(), sectionList.formBuilder());
+ ifSections.sections().forEach(section -> section.appendCopyTo(ifSectionCopy));
+ sectionList.appendIfSection(negated, name, nameOffset, range().end(), ifSectionCopy);
+ }
+}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/LiteralSection.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/LiteralSection.java
index 852056fa283..c03653253af 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/LiteralSection.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/LiteralSection.java
@@ -18,4 +18,9 @@ class LiteralSection extends Section {
void appendTo(StringBuilder buffer) {
range().appendTo(buffer);
}
+
+ @Override
+ void appendCopyTo(SectionList sectionList) {
+ sectionList.appendLiteralSection(range().end());
+ }
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/NoSuchNameTemplateException.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/NoSuchNameTemplateException.java
index f6d630846a2..706d347d39d 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/NoSuchNameTemplateException.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/NoSuchNameTemplateException.java
@@ -1,11 +1,13 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.node.admin.task.util.template;
+import com.yahoo.vespa.hosted.node.admin.task.util.text.CursorRange;
+
/**
* @author hakonhall
*/
public class NoSuchNameTemplateException extends TemplateException {
- public NoSuchNameTemplateException(Section section, String name) {
- super("No such element '" + name + "' in the " + describeSection(section.range()));
+ public NoSuchNameTemplateException(CursorRange range, String name) {
+ super("No such element '" + name + "' in the " + describeSection(range));
}
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/NotBooleanValueTemplateException.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/NotBooleanValueTemplateException.java
new file mode 100644
index 00000000000..6c6d157bb47
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/NotBooleanValueTemplateException.java
@@ -0,0 +1,11 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.node.admin.task.util.template;
+
+/**
+ * @author hakonhall
+ */
+public class NotBooleanValueTemplateException extends TemplateException {
+ public NotBooleanValueTemplateException(String name) {
+ super(name + " was set to a non-boolean value: must be true or false");
+ }
+}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/Section.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/Section.java
index f12dfb8d8b7..234915770f8 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/Section.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/Section.java
@@ -25,4 +25,6 @@ abstract class Section {
protected CursorRange range() { return range; }
abstract void appendTo(StringBuilder buffer);
+
+ abstract void appendCopyTo(SectionList sectionList);
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/SectionList.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/SectionList.java
index b5017246f31..679888678bf 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/SectionList.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/SectionList.java
@@ -6,7 +6,6 @@ import com.yahoo.vespa.hosted.node.admin.task.util.text.CursorRange;
import java.util.ArrayList;
import java.util.List;
-import java.util.function.Consumer;
/**
* A mutable list of sections at the same level that can be used to build a form, e.g. the if-body.
@@ -14,42 +13,51 @@ import java.util.function.Consumer;
* @author hakonhall
*/
class SectionList {
- private TemplateBuilder templateBuilder;
-
private final Cursor start;
private final Cursor end;
+ private final FormBuilder formBuilder;
- private final List<Consumer<FormBuilder>> sections = new ArrayList<>();
+ private final List<Section> sections = new ArrayList<>();
- SectionList(Cursor start) {
+ SectionList(Cursor start, FormBuilder formBuilder) {
this.start = new Cursor(start);
this.end = new Cursor(start);
+ this.formBuilder = formBuilder;
}
- /** Must be invoked once before any other method. */
- void setTemplateBuilder(TemplateBuilder templateBuilder) {
- this.templateBuilder = templateBuilder;
- }
+ CursorRange range() { return new CursorRange(start, end); }
+ FormBuilder formBuilder() { return formBuilder; }
+ List<Section> sections() { return List.copyOf(sections); }
void appendLiteralSection(Cursor end) {
CursorRange range = verifyAndUpdateEnd(end);
- sections.add((FormBuilder builder) -> builder.addLiteralSection(range));
+ var section = new LiteralSection(range);
+ formBuilder.addLiteralSection(section);
+ sections.add(section);
}
- void appendVariableSection(String name, Cursor nameOffset, Cursor end) {
+ VariableSection appendVariableSection(String name, Cursor nameOffset, Cursor end) {
CursorRange range = verifyAndUpdateEnd(end);
- templateBuilder.addVariable(name, nameOffset);
- sections.add(formBuilder -> formBuilder.addVariableSection(range, name, nameOffset));
+ var section = new VariableSection(range, name, nameOffset);
+ formBuilder.addVariableSection(section);
+ sections.add(section);
+ return section;
}
- void appendSubformSection(String name, Cursor nameCursor, Cursor end, Template body) {
+ void appendIfSection(boolean negated, String name, Cursor nameOffset, Cursor end,
+ SectionList ifSections) {
CursorRange range = verifyAndUpdateEnd(end);
- templateBuilder.addSubform(name, nameCursor);
- sections.add(formBuilder -> formBuilder.addSubformSection(range, name, body));
+ var section = new IfSection(range, negated, name, nameOffset, ifSections);
+ formBuilder.addIfSection(section);
+ sections.add(section);
}
- CursorRange range() { return new CursorRange(start, end); }
- List<Consumer<FormBuilder>> sections() { return List.copyOf(sections); }
+ void appendSubformSection(String name, Cursor nameOffset, Cursor end, Form body) {
+ CursorRange range = verifyAndUpdateEnd(end);
+ var section = new SubformSection(range, name, nameOffset, body);
+ formBuilder.addSubformSection(section);
+ sections.add(section);
+ }
private CursorRange verifyAndUpdateEnd(Cursor newEnd) {
var range = new CursorRange(this.end, newEnd);
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/SubformSection.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/SubformSection.java
index 360a37964b2..fa65589d7d5 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/SubformSection.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/SubformSection.java
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.node.admin.task.util.template;
+import com.yahoo.vespa.hosted.node.admin.task.util.text.Cursor;
import com.yahoo.vespa.hosted.node.admin.task.util.text.CursorRange;
import java.util.ArrayList;
@@ -13,26 +14,44 @@ import java.util.List;
* @author hakonhall
*/
class SubformSection extends Section {
- private final Template body;
private final String name;
+ private final Cursor nameOffset;
+ private final Form body;
private final List<Form> elements = new ArrayList<>();
- SubformSection(CursorRange range, String name, Template body) {
+ SubformSection(CursorRange range, String name, Cursor nameOffset, Form body) {
super(range);
this.name = name;
+ this.nameOffset = new Cursor(nameOffset);
this.body = body;
}
String name() { return name; }
+ Cursor nameOffset() { return new Cursor(nameOffset); }
+
+ @Override
+ void setForm(Form form) {
+ super.setForm(form);
+ body.setParent(form);
+ }
Form add() {
- var form = body.instantiate(form());
- elements.add(form);
- return form;
+ Form element = body.copy();
+ element.setParent(form());
+ elements.add(element);
+ return element;
}
@Override
void appendTo(StringBuilder buffer) {
elements.forEach(form -> form.appendTo(buffer));
}
+
+ @Override
+ void appendCopyTo(SectionList sectionList) {
+ // avoid copying elements for now
+ // Optimization: Reuse body in copy, since it is only used for copying.
+
+ sectionList.appendSubformSection(name, nameOffset, range().end(), body);
+ }
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/Template.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/Template.java
index 47ff28b2d6b..a3ffcf46bdf 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/Template.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/Template.java
@@ -1,13 +1,6 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.node.admin.task.util.template;
-import com.yahoo.vespa.hosted.node.admin.task.util.text.Cursor;
-import com.yahoo.vespa.hosted.node.admin.task.util.text.CursorRange;
-
-import java.util.List;
-import java.util.Map;
-import java.util.function.Consumer;
-
/**
* The Java representation of a template text.
*
@@ -18,6 +11,7 @@ import java.util.function.Consumer;
* section: literal | variable | subform
* literal: plain text not containing %{
* variable: %{=identifier}
+ * if: %{if [!]identifier}template[%{else}template]%{end}
* subform: %{form identifier}template%{end}
* identifier: a valid Java identifier
* </pre>
@@ -38,18 +32,7 @@ import java.util.function.Consumer;
* @author hakonhall
*/
public class Template {
- private final CursorRange range;
- private final List<Consumer<FormBuilder>> sections;
- private final Map<String, Cursor> variables;
- private final Map<String, Cursor> subforms;
-
- public Template(CursorRange range, List<Consumer<FormBuilder>> sections,
- Map<String, Cursor> variables, Map<String, Cursor> subforms) {
- this.range = new CursorRange(range);
- this.sections = List.copyOf(sections);
- this.variables = Map.copyOf(variables);
- this.subforms = Map.copyOf(subforms);
- }
+ private final Form form;
public static Template from(String text) { return from(text, new TemplateDescriptor()); }
@@ -57,7 +40,9 @@ public class Template {
return TemplateParser.parse(text, descriptor).template();
}
- public Form instantiate() { return instantiate(null); }
+ Template(Form form) {
+ this.form = form;
+ }
- Form instantiate(Form parent) { return FormBuilder.build(parent, range, sections); }
+ public Form instantiate() { return form.copy(); }
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/TemplateBuilder.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/TemplateBuilder.java
deleted file mode 100644
index d63ed7c9e70..00000000000
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/TemplateBuilder.java
+++ /dev/null
@@ -1,48 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.node.admin.task.util.template;
-
-import com.yahoo.vespa.hosted.node.admin.task.util.text.Cursor;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/**
- * @author hakonhall
- */
-class TemplateBuilder {
- private final SectionList sections;
-
- /** Example location (value) of a variable name (key). */
- private final Map<String, Cursor> variables = new HashMap<>();
-
- /** Location of the name of each subform. */
- private final Map<String, Cursor> subforms = new HashMap<>();
-
- TemplateBuilder(Cursor start) {
- sections = new SectionList(start);
- sections.setTemplateBuilder(this);
- }
-
- SectionList sectionList() { return sections; }
-
- void addVariable(String name, Cursor nameOffset) {
- Cursor existing = subforms.get(name);
- if (existing != null)
- throw new NameAlreadyExistsTemplateException(name, existing, nameOffset);
- variables.put(name, new Cursor(nameOffset));
- }
-
- void addSubform(String name, Cursor nameCursor) {
- Cursor existing = variables.get(name);
- if (existing != null)
- throw new NameAlreadyExistsTemplateException(name, existing, nameCursor);
-
- existing = subforms.put(name, nameCursor);
- if (existing != null)
- throw new NameAlreadyExistsTemplateException(name, existing, nameCursor);
- }
-
- Template build() {
- return new Template(sections.range(), sections.sections(), variables, subforms);
- }
-}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/TemplateNameNotSetException.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/TemplateNameNotSetException.java
index 0c715df2efd..d65c2a7c4d6 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/TemplateNameNotSetException.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/TemplateNameNotSetException.java
@@ -8,6 +8,6 @@ import com.yahoo.vespa.hosted.node.admin.task.util.text.Cursor;
*/
public class TemplateNameNotSetException extends TemplateException {
public TemplateNameNotSetException(String name, Cursor nameOffset) {
- super(name + " at " + nameOffset.calculateLocation().lineAndColumnText() + " has not been set");
+ super("Variable at " + nameOffset.calculateLocation().lineAndColumnText() + " has not been set: " + name);
}
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/TemplateParser.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/TemplateParser.java
index b97745df3a6..ac38a23d4ad 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/TemplateParser.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/TemplateParser.java
@@ -15,7 +15,7 @@ class TemplateParser {
private final TemplateDescriptor descriptor;
private final Cursor start;
private final Cursor current;
- private final TemplateBuilder templateBuilder;
+ private final FormBuilder formBuilder;
static TemplateParser parse(String text, TemplateDescriptor descriptor) {
return parse(new TemplateDescriptor(descriptor), new Cursor(text), EnumSet.of(Sentinel.EOT));
@@ -23,20 +23,20 @@ class TemplateParser {
private static TemplateParser parse(TemplateDescriptor descriptor, Cursor start, EnumSet<Sentinel> sentinel) {
var parser = new TemplateParser(descriptor, start);
- parser.parse(parser.templateBuilder.sectionList(), sentinel);
+ parser.parse(parser.formBuilder.topLevelSectionList(), sentinel);
return parser;
}
private enum Sentinel { END, EOT }
- TemplateParser(TemplateDescriptor descriptor, Cursor start) {
+ private TemplateParser(TemplateDescriptor descriptor, Cursor start) {
this.descriptor = descriptor;
this.start = new Cursor(start);
this.current = new Cursor(start);
- this.templateBuilder = new TemplateBuilder(start);
+ this.formBuilder = new FormBuilder(start);
}
- Template template() { return templateBuilder.build(); }
+ Template template() { return new Template(formBuilder.build()); }
private Sentinel parse(SectionList sectionList, EnumSet<Sentinel> sentinels) {
do {
@@ -60,7 +60,6 @@ class TemplateParser {
}
private Optional<Sentinel> parseSection(SectionList sectionList, EnumSet<Sentinel> sentinels) {
- var startOfDirective = new Cursor(current);
current.skip(descriptor.startDelimiter());
if (current.skip(Token.VARIABLE_DIRECTIVE_CHAR)) {
@@ -78,6 +77,9 @@ class TemplateParser {
case "form":
parseSubformSection(sectionList);
break;
+ case "if":
+ parseIfSection(sectionList);
+ break;
default:
throw new BadTemplateException(startOfType, "Unknown section '" + type + "'");
}
@@ -106,7 +108,21 @@ class TemplateParser {
TemplateParser bodyParser = parse(descriptor, current, EnumSet.of(Sentinel.END));
current.set(bodyParser.current);
- sectionList.appendSubformSection(name, startOfName, current, bodyParser.template());
+ sectionList.appendSubformSection(name, startOfName, current, bodyParser.formBuilder.build());
+ }
+
+ private void parseIfSection(SectionList sectionList) {
+ skipRequiredWhitespaces();
+ boolean negated = current.skip(Token.NEGATE_CHAR);
+ current.skipWhitespaces();
+ var startOfName = new Cursor(current);
+ String name = parseId();
+ parseEndDelimiter(true);
+
+ SectionList ifSectionList = new SectionList(current, formBuilder);
+ parse(ifSectionList, EnumSet.of(Sentinel.END));
+
+ sectionList.appendIfSection(negated, name, startOfName, current, ifSectionList);
}
private void skipRequiredWhitespaces() {
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/VariableSection.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/VariableSection.java
index b384a2deb5a..bf211a01190 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/VariableSection.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/template/VariableSection.java
@@ -21,6 +21,7 @@ class VariableSection extends Section {
}
String name() { return name; }
+ Cursor nameOffset() { return new Cursor(nameOffset); }
@Override
void appendTo(StringBuilder buffer) {
@@ -28,4 +29,9 @@ class VariableSection extends Section {
.orElseThrow(() -> new TemplateNameNotSetException(name, nameOffset));
buffer.append(value);
}
+
+ @Override
+ void appendCopyTo(SectionList sectionList) {
+ sectionList.appendVariableSection(name, nameOffset, range().end());
+ }
}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/template/TemplateFileTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/template/TemplateFileTest.java
index c316d4efe86..121d4fd8d25 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/template/TemplateFileTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/template/TemplateFileTest.java
@@ -83,6 +83,43 @@ class TemplateFileTest {
form.render());
}
+ @Test
+ void verifyIfSection() {
+ Template template = Template.from("Hello%{if cond} world%{end}!");
+ assertEquals("Hello world!", template.instantiate().set("cond", true).render());
+ assertEquals("Hello!", template.instantiate().set("cond", false).render());
+ }
+
+ @Test
+ void verifyComplexIfSection() {
+ Template template = Template.from("%{if cond|}\n" +
+ "var: %{=varname}\n" +
+ "if: %{if !inner}inner is false%{end}\n" +
+ "subform: %{form formname}subform%{end}\n" +
+ "%{end|}\n");
+
+ assertEquals("", template.instantiate().set("cond", false).render());
+
+ assertEquals("var: varvalue\n" +
+ "if: \n" +
+ "subform: \n",
+ template.instantiate()
+ .set("cond", true)
+ .set("varname", "varvalue")
+ .set("inner", true)
+ .render());
+
+ Form form = template.instantiate()
+ .set("cond", true)
+ .set("varname", "varvalue")
+ .set("inner", false);
+ form.add("formname");
+
+ assertEquals("var: varvalue\n" +
+ "if: inner is false\n" +
+ "subform: subform\n", form.render());
+ }
+
private Form getForm(String filename) {
return TemplateFile.read(Path.of("src/test/resources/" + filename)).instantiate();
}