Match rules¶
Match rules are used to check if the files match the expected files. Multiple match rules can be defined and will be processed in order.
Rule syntax¶
A match rule contains a file selector, file class and action to take if a file name matches the selector.
rule ::= [type]filter[ "["nameclass"]" ] "=>"action[message] type ::= "+" | "-" filter ::= ["!"] ["^"]glob[ "{"countrange"}" ] nameclass ::=class[ "#"name] | "#"nameaction ::= "pass" | "pass-secret" | "pass-hidden" | "fail" countrange ::=from_count[ "," [to_count] ] | ","to_count
Type¶
If the rule starts with a -, the matched files will be consumed (default), if the rule
starts with a + they will not be consumed and remain available to match in later rules.
Filter¶
The filter is a Unix shell style glob pattern applied to the name of the file.
*matches in exactly one path segment**matches in multiple path segments (including none)?matches any single character[seq]matches any character in seq[!seq]matches any char not in seq
Path segments (separated with a forward slash / ) can be used. A * will match a single path segment, whereas
a ** will match any number of path segments, i.e. * matches any character except the forward slash, whereas
** matches all characters. See the examples below for more details.
The filter has the following modifications to a normal glob pattern:
if the filter starts with a
!, the rule is inverted, i.e. it will match if no files match the filter.if the filter starts with a
^, the file glob is case-insensitive. In case both!and^are needed,!should be specified first.the filter can be followed by a
{x,y}count to indicate that the filter should only match if at least x files match, and match at most y files. Both x and y can be empty indicating 0 and infinity respectively.{x}is identical to{x,x}, i.e. there must be exactly x files matching the selector.
Files matched by a rule will be removed from the list of files for the next rule,
unless the rule type is +.
Name and class¶
The file selector can be followed with a file class and (optionally) new file name between square brackets ([]).
The class and name parts are separated by a hash sign (#), e.g. [class#name.txt].
Action¶
The action to take if the selector matches can be specified. The action can be one of:
pass, pass-secret, pass-hidden, fail. These will generate a corresponding
outcome. The pass-secret and pass-hidden outcomes will be marked as secret (not visible to learners,
but visible to reviewers) and hidden (not visible to anyone) respectively.
Message¶
If a message is specified, it will be included in the report if the rule matches.
For positive match rules, the message can contain the following placeholders:
{#}is replaced with the number of matched files.{=x|y}is replaced with the string x if 1 file matched, y if more than 1 file matched. Either string can be empty.{*}is replaced with a comma separated list of matched file names,{𝒾}, with 𝒾 an integer index ≥ 0, is replaced with the (𝒾+1)th matched file, i.e.{0}is replaced with the first matched file,{1}with the second, etc.
To include a literal { in the message, use \{.
Placeholders can only be used with positive matche rules, as negative matches (using the ! prefix) do
not have files matched.
Examples¶
-*.py{2,} => fail Multiple Python files detected, expected no more than one.This rule will generate a fail outcome with an error message
Multiple Python files detected, expected no more than one.if more than one*.pyfile is detected.If a none or a single
*.pyfile is detected, the optional file will be consumed and the next rule will be processed.!*.py{1} => fail Expected exactly one Python file.This rule is similar to the previous one, with the difference that it will also generate a fail outcome if no
*.pyfile is detected.No files will be consumed because due to the
!the rule matches if no files match it, so the next rule will be processed with the same set of files.+*.py [python] => pass Python detectedThis rule will generate a pass outcome if any
*.pyfile is detected. The file will not be consumed and will be available for the next rule. The file will be tagged with apythonfile class.!Main.java => fail Main.java not submittedThis rule will generate a fail outcome if no
Main.javafile is detected.!Library.java => pass WARNING: Expected Library.java file not submittedThis will generate a warning message in the report if no
Library.javafile is detected.*/Main.java [main#source/Main.java] => pass-secret**/Main.java [main#source/Main.java] => pass-secret*/**/Main.java [main#source/Main.java] => pass-secretThese rules look for a
Main.javafile in one or more sub-directories. If it is found, it will be tagged with amainfile class and renamed tosource/Main.java. Also, the step will be marked as secret.The first version will only match if
Main.javais in a single sub-directory, the second version will match if it is anywhere in the project, either the root or any sub-directory level deep, and the third version will match if it is in at least one sub-directory level deep.* => fail Rejected {#} unexpected file{=|s}: {*}This “catch-all” rule is useful to use as the last rule in a set of rules. It will generate a fail outcome if any file is detected that was not matched and consumed by any of the previous rules.
The error message will contain the number of unexpected files and a comma separated list of their names.