Write a Blade Templating Engine

5

Blade is a PHP templating engine. For this challenge, you only need to implement a modified version one feature -- text replacement.

Challenge Description

Given an input file and a map of keys to their replacements, write an interpreter that reads through the file to find double open braces ({{) followed by a key, followed by double closing braces (}}). Whitespace inside the braces should be ignored, unless it is inside of the key.

Requirements

If a key is not found in the map, your program should exit without outputting anything

Keys must be nestable. See the examples for a valid nesting example.

Keys may contain any character, but cannot start or end with a space. Whitespace should be trimmed from the start and end of the key.

Example input/output

Input blade file:

Hello, {{user_{{username }}}}! It is { NICE } to meet you!
My name is {{     my n@m3}}!

Input map:

{
'user_Joe': 'Joseph, user #1234',
'username': 'Joe',
'my n@m3': 'Henry'
}

Expected output:

Hello, Joseph, user#1234! It is { NICE } to meet you!
My name is Henry!

Marking:

Feel free to hard code input/output and exclude it from your byte count. However, if you implement the following features, take the corresponding value away from your byte count.

Read filename of blade template from stdin or arguments: 15 bytes

Read variable mapping as JSON from stdin or arguments: 20 bytes

Read variable mapping in any other format from stdin or arguments: 12 bytes

Bonus

Allow calculations (+, -, *, /, %, etc) in your blade script. Example input file:

{{a + b}}

Input map:

{ 'a': 1, 'b': 4 }

Expected output:

5

Brian Hannay

Posted 2015-11-09T01:27:26.250

Reputation: 153

Keys may contain any character including { and }? – feersum – 2015-11-09T01:29:57.547

True. in fact, the following key should be valid: '<?>#{dfd }E}{}3' – Brian Hannay – 2015-11-09T03:55:36.143

Then how are you supposed to decide if {{ or }} is part of a set of braces or part of the name of a key? – feersum – 2015-11-09T04:15:49.773

If there are two of the same brace in a row, it is safe to assume that it is part of a set. If there is only 1 in a row, it may be part of a key or part of the text left alone – Brian Hannay – 2015-11-09T04:25:19.983

Answers

3

Perl: 105 - 15 - 20 = 70

perl -MJSON -pE'sub f{$_[0]=~s#{{\s*((.*?(?R)?.*?)+)\s*}}#$h->{f($1)}#reg};BEGIN{$h=from_json shift};$_=f$_' 'JSON' <input

Usage: the JSON is given as a command line argument. The template source is passed in STDIN. Example:

$ perl ... '{"user_Joe":"Joseph, user #1234","username":"Joe","my n@m3":"Henry"}'
>Hello, {{user_{{username }}}}! It is { NICE } to meet you!
>My name is {{     my n@m3}}!

(where lines beginning with > are pasted/typed/piped in)

Restrictions:

  • can't do arithmetic.
  • keys cannot span multiple lines.
  • requires the JSON Perl module to be installed (non-standard).
  • requires a new-ish perl (tested on Perl5 v20, but should work on at least v16 or v14).

I am not sure about the scoring rules, so I excluded perl·, the quotes for the code, and included an extra space and two quotes needed to pass the JSON on the command line.

Explanation:

Perl regexes can be recursive, but substitutions can't. To overcome this, we first define a regex that matches keys that might contain keys again: {{\s* ((.*? (?R)? .*?)+) \s*}}. Here, (?R) recurses into the whole pattern. The key does not have to contain a template tag, so the recursion is optional. In that case, the pattern is a non-greedy “match anything”: .*?.*?. This will terminate given a double closing braces. Usually braces are metacharacters in regexes, e.g. a{10,20} (match 10 to 20 "a"s). Fortunately, the regex compiler recognizes that these braces are not used as quantifiers.

Once we have matched the (possibly recursive) tag contents, we pass it to the function f which again applies the substitution to this content. The result is then used as a key in a hash reference $h, which replaces the tag contents. This is done non-destructively /r and globally /g.

The $h hash reference is defined in the BEGIN phase to the json-decoded value of the first command line argument. Since the sub f definition and the BEGIN block are done before the main execution, the actual script is perl -pE'$_=f$_', which applies our substitution function f to each line.

Voilà, a template engine is born :)

amon

Posted 2015-11-09T01:27:26.250

Reputation: 309

It's beautiful. Thanks for your submission! – Brian Hannay – 2015-11-09T21:53:22.673