Skip to content

Commit

Permalink
Merge pull request #118 from iliclaey/join-function
Browse files Browse the repository at this point in the history
Built-in join function
  • Loading branch information
jrha authored Dec 6, 2016
2 parents d991bf6 + 859448b commit 177e124
Show file tree
Hide file tree
Showing 19 changed files with 403 additions and 1 deletion.
3 changes: 3 additions & 0 deletions panc-docs/source/pan-book/pan-book.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1906,6 +1906,9 @@ Table: Type Conversion Functions
| | can either be a variable reference, path, or template |
| | name. |
+--------------------+-----------------------------------------------------------+
| :ref:`join` | Returns a string with the arguments joined, using the |
| | the passed delimeter. |
+--------------------+-----------------------------------------------------------+
| :ref:`path_exists` | Return true if the given path exists. The argument must |
| | be an absolute or external path. |
+--------------------+-----------------------------------------------------------+
Expand Down
36 changes: 35 additions & 1 deletion panc-docs/source/standard-functions/standard-functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1030,6 +1030,7 @@ Description
The ``is_string`` function will return ``true`` if the argument is a
string value; it will return ``false`` otherwise.


.. _is_valid:

is_valid
Expand All @@ -1043,7 +1044,7 @@ is_valid -- checks if an element meets the requirements of a certain type
Synopsis
--------

boolean **is_valid** (type, *type*, element *el*)
boolean **is_valid** (type *type*, element *el*)

Description
-----------
Expand All @@ -1063,6 +1064,39 @@ follows:

In this case ``'/result'`` will be of type boolean and hold ``true`` as a value.

.. _join:

join
====

Name
----

join -- joins the passed arguments

Synposis
--------

string **join** (string *delimeter*, list *resource*)

string **join** (string *delimeter*, string *arg1*, string *arg2*, ...)

Description
-----------
This function takes a delimeter and a list of strings, or each of the strings
individually, and joins them with the given delimeter. Only (a list of) strings
can be passed as arguments.

::

# joining a list
'/x' = list("a", "b", "c");
'/rx' = join("-", value('/x')); # This will return "a-b-c"

# joining individual arguments
'/rx' = join("-", "a", "b", "c"); # This will also return "a-b-c"


.. _key:

key
Expand Down
20 changes: 20 additions & 0 deletions panc/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,26 @@

<plugins>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<executions>
<execution>
<id>enforce-java</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireJavaVersion>
<version>[1.8.0,) </version>
</requireJavaVersion>
</rules>
</configuration>
</execution>
</executions>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
Expand Down
127 changes: 127 additions & 0 deletions panc/src/main/java/org/quattor/pan/dml/functions/Join.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package org.quattor.pan.dml.functions;

import org.quattor.pan.dml.Operation;
import org.quattor.pan.dml.data.*;
import org.quattor.pan.exceptions.EvaluationException;
import org.quattor.pan.exceptions.SyntaxException;
import org.quattor.pan.template.Context;
import org.quattor.pan.template.SourceRange;

import java.util.ArrayList;

import static org.quattor.pan.utils.MessageUtils.MSG_SECOND_ARG_LIST_OR_VARIABLE_REF;
import static org.quattor.pan.utils.MessageUtils.MSG_VALUE_CANNOT_BE_NULL;
import static org.quattor.pan.utils.MessageUtils.MSG_FIRST_STRING_ARG_REQ;
import static org.quattor.pan.utils.MessageUtils.MSG_TWO_OR_MORE_ARG_REQ;

/**
* Created by iliclaey.
*
* This only allows strings to be passed as arguments.
* If you want to be able to pass other types, remove the check whether the arguments are strings.
* In execute, make sure the values are somehow converted to strings, since a list of strings is
* used to perform the join.
*/
final public class Join extends BuiltInFunction {

protected Join(SourceRange sourceRange, Operation... operations) {
super("join", sourceRange, operations);
}

public static Operation getInstance(SourceRange sourceRange, Operation... operations) throws SyntaxException {

if (operations.length < 2) {
throw SyntaxException.create(sourceRange, MSG_TWO_OR_MORE_ARG_REQ, "join");
}

// The first and second argument can't be null.
if (operations[0] instanceof Null || operations[1] instanceof Null) {
throw SyntaxException.create(sourceRange, MSG_VALUE_CANNOT_BE_NULL, "join");
}

// If there are only two arguments, the second argument needs to be a list.
if (operations.length == 2) {
if (operations[1] instanceof Element) {
if (!(operations[1] instanceof ListResource)) {
throw SyntaxException.create(sourceRange, MSG_SECOND_ARG_LIST_OR_VARIABLE_REF, "join");
}
}
}

return new Join(sourceRange, operations);
}

@Override
public Element execute(Context context) throws EvaluationException {

assert (ops.length != 1);

// Extract the first argument. This must be a string value.
String delimeter = null;
try {
delimeter = ((StringProperty) ops[0].execute(context)).getValue();
} catch (ClassCastException cce) {
throw new EvaluationException("first argument in join() must be a string", getSourceRange(), context);
}

// Create a list containing all elements we need to join.
ArrayList<String> result = new ArrayList<String>();

// If the second argument is a ListResource, add the strings to the result-list.
if (ops.length == 2) {

// Double check whether the second argument is actually a list.
Element list = ops[1].execute(context);

if (list instanceof ListResource) {

// Loop over the list items and convert them to strings.
Resource.Iterator it = ((ListResource) list).iterator();
while (it.hasNext()) {
Element e = it.next().getValue();

if (e instanceof Resource) {
throw new EvaluationException(
"join() doesn't accept nested elements in the passed list",
getSourceRange(), context);
}

if (!(e instanceof StringProperty)) {
throw new EvaluationException(
"join() only accepts strings as elements in the list",
getSourceRange(), context);
}

result.add(((StringProperty) e).getValue());
}

} else {
throw new EvaluationException(
"join() only accepts strings or a list of strings as input arguments",
getSourceRange(), context);
}

// If all arguments are passed individually, add them to a list.
} else {
int length = ops.length;

for (int i = 1; i < length; i++) {
Element e = ops[i].execute(context);

if (e instanceof ListResource) {
throw new EvaluationException(
"join() only accepts a single list as an argument or all the arguments individually",
getSourceRange(), context);
} else if (!(e instanceof StringProperty)) {
throw new EvaluationException(
"join() only accepts strings or a list of strings as input arguments",
getSourceRange(), context);
}

result.add(((StringProperty) e).getValue());
}
}

return StringProperty.getInstance(String.join(delimeter, result));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,9 @@ public class PanParserAstUtils {
fc.put("-suppress-traceback-",
(TracebackSuppressed.class).getDeclaredMethod("getInstance", SourceRange.class, Operation[].class));

fc.put("join",
(Join.class).getDeclaredMethod("getInstance", SourceRange.class, Operation[].class));

// Make the visible map invariant, just to make sure that no one
// makes unexpected changes.
functionConstructors = Collections.unmodifiableMap(fc);
Expand Down Expand Up @@ -399,6 +402,7 @@ static private Statement convertAstToAssignStatement(ASTStatement ast, Path pref
statement = AssignmentStatement
.createAssignmentStatement(ast.getSourceRange(), path, dml, ast.getConditionalFlag(),
!ast.getFinalFlag());

}

return statement;
Expand Down
4 changes: 4 additions & 0 deletions panc/src/main/java/org/quattor/pan/utils/MessageUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,8 @@ public class MessageUtils {

public final static String MSG_TWO_ARGS_REQ = "MSG_TWO_ARGS_REQ";

public final static String MSG_TWO_OR_MORE_ARG_REQ = "MSG_TWO_OR_MORE_ARG_REQ";

public final static String MSG_3_ARGS_REQ = "MSG_3_ARGS_REQ";

public final static String MSG_RESTRICTED_CONTEXT = "MSG_RESTRICTED_CONTEXT";
Expand Down Expand Up @@ -250,6 +252,8 @@ public class MessageUtils {

public final static String MSG_FIRST_ARG_LIST_OR_VARIABLE_REF = "MSG_FIRST_ARG_LIST_OR_VARIABLE_REF";

public final static String MSG_SECOND_ARG_LIST_OR_VARIABLE_REF = "MSG_SECOND_ARG_LIST_OR_VARIABLE_REF";

public final static String MSG_DEF_VALUE_NOT_CONSTANT = "MSG_DEF_VALUE_NOT_CONSTANT";

public final static String MSG_DEF_VALUE_CANNOT_BE_UNDEF = "MSG_DEF_VALUE_CANNOT_BE_UNDEF";
Expand Down
2 changes: 2 additions & 0 deletions panc/src/main/resources/org/quattor/pan/Messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ MSG_ONE_ARG_REQ={0}() requires exactly one argument
MSG_ONE_OR_TWO_ARGS_REQ={0}() requires exactly one or two arguments
MSG_ONE_OR_MORE_ARG_REQ={0}() requires at least one argument
MSG_TWO_ARGS_REQ={0}() requires exactly two arguments
MSG_TWO_OR_MORE_ARG_REQ={0}() requires two or more arguments
MSG_3_ARGS_REQ={0}() requires exactly three arguments
MSG_RESTRICTED_CONTEXT={0}() may not appear in a restricted context (either variable indices or function arguments)
MSG_EVEN_NUMBER_OF_ARGS=hash/nlist function must have an even number of arguments
Expand All @@ -115,6 +116,7 @@ MSG_FIELD_MUST_BE_VALID_TERM=field name ({0}) must be a valid term
MSG_FIELD_MUST_BE_VALID_KEY=field name ({0}) must be a valid key, not an index
MSG_FIRST_ARG_VARIABLE_REF=first argument of {0}() must be a variable reference
MSG_FIRST_ARG_LIST_OR_VARIABLE_REF=first argument of {0}() must be a variable reference or a list
MSG_SECOND_ARG_LIST_OR_VARIABLE_REF=second argument of {0}() must be a variable reference or a list
MSG_VARIABLE_REF_OR_UNDEF=argument must be a variable reference or undef
MSG_DEF_VALUE_NOT_CONSTANT=DML block for default value does not evaluate to a compile-time constant
MSG_DEF_VALUE_CANNOT_BE_UNDEF=undef is not a valid default value
Expand Down
112 changes: 112 additions & 0 deletions panc/src/test/java/org/quattor/pan/dml/functions/JoinTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package org.quattor.pan.dml.functions;

import org.junit.Test;
import org.quattor.pan.dml.data.*;
import org.quattor.pan.exceptions.EvaluationException;
import org.quattor.pan.exceptions.SyntaxException;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

/**
* Created by iliclaey.
*/
public class JoinTest extends BuiltInFunctionTestUtils {

@Test
public void checkGetInstance() {
checkClassRequirements(Join.class);
}

@Test(expected = SyntaxException.class)
public void testTooFewArguments() throws SyntaxException {
Join.getInstance(null);
}

@Test(expected = SyntaxException.class)
public void testNullValueFirstArg() throws SyntaxException {
Join.getInstance(null, Null.getInstance(), new ListResource());
}

@Test(expected = SyntaxException.class)
public void testNullValueSecondArg() throws SyntaxException {
Join.getInstance(null, StringProperty.getInstance(","), Null.getInstance());
}

@Test(expected = EvaluationException.class)
public void testInvalidFirstArg() throws SyntaxException {
runDml(Join.getInstance(null, LongProperty.getInstance(0L), new ListResource()));
}

@Test(expected = SyntaxException.class)
public void testInvalidSecondArg() throws SyntaxException {
runDml(Join.getInstance(null, StringProperty.getInstance(","), new HashResource()));
}

@Test(expected = EvaluationException.class)
public void testInvalidArgs1() throws SyntaxException {
runDml(Join.getInstance(null, StringProperty.getInstance(","),
StringProperty.getInstance("First"), LongProperty.getInstance(0L)));
}

@Test(expected = EvaluationException.class)
public void testInvalidArgs2() throws SyntaxException {
runDml(Join.getInstance(null, StringProperty.getInstance(","),
StringProperty.getInstance("a"), new ListResource()));
}

@Test(expected = EvaluationException.class)
public void testInvalidTypeSecondArg() throws SyntaxException {
Element[] args = {
LongProperty.getInstance(1),
LongProperty.getInstance(2),
LongProperty.getInstance(3)
};

runDml(Join.getInstance(null, StringProperty.getInstance(","), new ListResource(args)));
}

@Test(expected = EvaluationException.class)
public void testNestedElements() throws SyntaxException {
Element[] args = { new ListResource() };

runDml(Join.getInstance(null, StringProperty.getInstance("OK"), new ListResource(args)));
}

@Test
public void testJoinWithList() throws SyntaxException {
String expected = new String("a-b-c");

// Execute operation
Element[] args = {
StringProperty.getInstance("a"),
StringProperty.getInstance("b"),
StringProperty.getInstance("c")
};

Element e = runDml(Join.getInstance(null, StringProperty.getInstance("-"), new ListResource(args)));

// Check result
assertTrue(e instanceof StringProperty);
String result = ((StringProperty) e).getValue();
assertEquals(result, expected);
}

@Test
public void testJoinWithoutList() throws SyntaxException {
String expected = new String("a-b-c");

// Execute operation
Element e = runDml(Join.getInstance(null,
StringProperty.getInstance("-"),
StringProperty.getInstance("a"),
StringProperty.getInstance("b"),
StringProperty.getInstance("c")
));

// Check result
assertTrue(e instanceof StringProperty);
String result = ((StringProperty) e).getValue();
assertEquals(result, expected);
}
}
Loading

0 comments on commit 177e124

Please sign in to comment.