Last active
December 28, 2015 08:09
-
-
Save jvmvik/7469465 to your computer and use it in GitHub Desktop.
Very basic template engine... I wrote this to replace the horrible Groovy Template Engine. This is not designed to solve a large scope of problem.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.nio.charset.Charset | |
import java.nio.file.Files | |
import java.nio.file.Path | |
import java.nio.file.StandardOpenOption | |
/** | |
* Simple template parser. | |
* | |
* This parser offer to: | |
* - Replace $variable by the value | |
* - Replace ${variable} by the value | |
* - Disable the parsing of commented line | |
* - Store line parsed in memory during the processing when inMemory = true | |
* - Avoid memory starvation by keeping 1000 lines in memory max | |
* | |
* @author jvmvik@gmail.com | |
*/ | |
class SimpleTemplate | |
{ | |
// Optimisation settings | |
// Enables to write the content of the output buffer in the file line by line. | |
boolean inMemory = true | |
// Ignore line starting by a comment. | |
// Comment supported: # or // | |
boolean ignoreComment = false | |
void parse(Path source, Path target, Map<String, String> mapping) | |
throws SyntaxException | |
{ | |
def writer, reader | |
try | |
{ | |
writer = Files.newBufferedWriter(target, Charset.defaultCharset(), StandardOpenOption.CREATE) | |
reader = Files.newBufferedReader(source, Charset.defaultCharset()); | |
String s | |
int j = 0 | |
while ((s = reader.readLine()) != null) | |
{ | |
writer.println(parseLine(s, mapping, j++, source)) | |
if (!inMemory || // Use RAM | |
(j >= 1000 && j % 1000 == 0)) // Avoid memory starvation | |
writer.flush() | |
} | |
writer.flush() // flush only at the end for faster I/O performance | |
} | |
catch (Exception e) | |
{ | |
writer.close() | |
reader.close() | |
Files.delete(target) | |
e.printStackTrace() | |
throw e | |
} | |
finally | |
{ | |
writer.flush() | |
writer.close() | |
reader.close() | |
} | |
} | |
/*** | |
* Parse a single line, and replace variables. | |
* | |
* @param s string to process | |
* @param mapping key:value pairs that should be replaced | |
* @param lineNumber current line read in the file | |
* @param file path of the template file which is read | |
* @return line where key are replaced by their value | |
* @throws SyntaxException | |
*/ | |
String parseLine(String s, Map<String, String> mapping, int lineNumber, Path file) | |
throws SyntaxException | |
{ | |
int i = 0 | |
int offset, k | |
if (!s.contains('$')) | |
return s | |
// Find matches | |
String key | |
while ((i = s.indexOf('$', i)) > -1) | |
{ | |
i++ | |
if (!(ignoreComment | |
&& (s ==~ /$\s+#.*/ || s ==~ /$\s+\/\/.*/))) | |
{ | |
if (s[i + 1] ==~ /\s+/) | |
{ | |
throw new SyntaxException("\$ must not be follow by a white space", lineNumber, i + 1, file) | |
} | |
if ('{'.equals(s[i])) | |
{ | |
if ((k = s.indexOf('}')) > -1) | |
key = s.substring(i + 1, k).trim() | |
else | |
throw new SyntaxException("\${ must end with }", lineNumber, i + 1, file) | |
offset = 2 | |
} | |
else | |
{ | |
if ((k = s.indexOf(' ', i)) > -1) | |
key = s.substring(i, k).trim() | |
else | |
key = s.substring(i).trim() | |
offset = 0 | |
} | |
if (key.contains(' ')) | |
throw new SyntaxException("key must not contain blank", lineNumber, i, file) | |
//Get value | |
String v | |
try | |
{ | |
v = mapping.get(key) | |
if (!v) | |
throw new SyntaxException("Empty value for key: $key", lineNumber, i, file) | |
} | |
catch (NullPointerException ex) | |
{ | |
throw new SyntaxException("Key is not found: $key", lineNumber, i, file) | |
} | |
// Replace | |
s = s.substring(0, i - 1) + v + s.substring(i + offset + key.length(), s.length()) | |
} | |
} | |
return s | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import com.arm.pipd.scf.exception.SyntaxException | |
import org.junit.Before | |
import org.junit.Test | |
import java.nio.file.Paths | |
import static groovy.test.GroovyAssert.shouldFail | |
/** | |
* @author jvmvik@gmail.com | |
*/ | |
class SimpleTemplateTest | |
{ | |
SimpleTemplate tpl | |
@Before | |
void setUp() | |
{ | |
tpl = new SimpleTemplate() | |
} | |
@Test | |
void parse() | |
{ | |
def i = File.createTempFile('scf-input','tmp') | |
i.withPrintWriter | |
{ writer -> | |
writer.println('line1: $key1 $key2') | |
writer.println('## \'line2: ${key3} ') | |
} | |
def o = File.createTempFile('scf-output','tmp') | |
def m = [key1: "hello", key2: "word", key3: 'this is fun to do..'] | |
tpl.parse(i.toPath(), o.toPath(), m) | |
assert "line1: $m.key1 $m.key2" == o.readLines()[0] | |
assert "## 'line2: ${m.key3} " == o.readLines()[1] | |
o.delete() | |
i.delete() | |
} | |
@Test | |
void parseLine() | |
{ | |
def f = Paths.get('none.txt') | |
assert 'hello world' == tpl.parseLine('hello $key', [key:'world'], 0, f) | |
assert 'hello world' == tpl.parseLine('hello ${key}', [key:'world'], 0, f) | |
assert 'hello \n world' == tpl.parseLine('hello \n ${key}', [key:'world'], 0, f) | |
assert 'hello \n world' == tpl.parseLine('${key1} \n ${key2}', [key1:'hello',key2:'world'], 0, f) | |
assert 'hello \n world' == tpl.parseLine('$key1 \n $key2', [key1:'hello',key2:'world'], 0, f) | |
} | |
@Test | |
void parseLineFail() | |
{ | |
def f = Paths.get('none.txt') | |
shouldFail(SyntaxException, { | |
tpl.parseLine('hello $ key', [key:'world'], 0, f) | |
}) | |
shouldFail(SyntaxException, { | |
tpl.parseLine('hello ${key', [key:'world'], 0, f) | |
}) | |
shouldFail(SyntaxException, { | |
tpl.parseLine('hello ${ key }', [key:'world'], 0, f) | |
}) | |
shouldFail(SyntaxException, { | |
tpl.parseLine('hello ${ key', [key:'world'], 0, f) | |
}) | |
} | |
@Test | |
void parseIgnoreCommentAndNoInMemory() | |
{ | |
tpl.ignoreComment = true | |
tpl.inMemory = false | |
def i = File.createTempFile('scf-input','tmp') | |
i.withPrintWriter | |
{ writer -> | |
writer.println('line1: $key1 $key2') | |
writer.println(' ## ${key3} ') | |
writer.println(' // ${key3} ') | |
} | |
def o = File.createTempFile('scf-output','tmp') | |
def m = [key1: "hello", key2: "word", key3: 'this is fun to do..'] | |
tpl.parse(i.toPath(), o.toPath(), m) | |
assert "line1: $m.key1 $m.key2" == o.readLines()[0] | |
assert " ## ${m.key3} " == o.readLines()[1] | |
assert " // ${m.key3} " == o.readLines()[2] | |
o.delete() | |
i.delete() | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.nio.file.Path | |
/** | |
* Syntax Exception | |
* | |
* Purpose | |
* - Show clear error message if a configuration file cannot be parse by ConfigSlurper | |
* | |
* @author jvmvik@gmail.com | |
*/ | |
class SyntaxException extends ScfException | |
{ | |
public SyntaxException(String message) | |
{ | |
super(message) | |
} | |
public SyntaxException(String message, int line, int colNumber, Path path) | |
{ | |
super("(${line}, ${colNumber}) ${message} in ${path}") | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment