|
1 | 1 | package de.mannodermaus.gradle.plugins.junit5.util.projects |
2 | 2 |
|
3 | | -import kotlin.math.max |
4 | | - |
5 | | -// Known tokens |
6 | | -private val GET_MATCHER = Regex("//\\\$GET\\{(.*)}") |
7 | | -private val IF_MATCHER = Regex("//\\\$IF\\{(.*)}") |
8 | | -private val ELSE_MATCHER = Regex("//\\\$ELSE") |
9 | | -private val IFGRADLE_MATCHER = Regex("//\\\$IFGRADLE\\{(.*)}") |
10 | | -private val FOREACH_MATCHER = Regex("//\\\$FOREACH\\{(.+)}") |
11 | | -private val END_MATCHER = Regex("//\\\$END") |
| 3 | +import com.soywiz.korte.TeFunction |
| 4 | +import com.soywiz.korte.TemplateConfig |
| 5 | +import com.soywiz.korte.TemplateProvider |
| 6 | +import com.soywiz.korte.Templates |
| 7 | +import kotlinx.coroutines.runBlocking |
| 8 | +import java.io.File |
12 | 9 |
|
13 | 10 | /** |
14 | 11 | * Processor class for virtual build script files, used by Functional Tests. |
15 | | - * It utilizes a very, VERY crude token API for placeholders, which are dynamically |
| 12 | + * It utilizes a template engine to customize the processed output for the build scripts |
16 | 13 | * injected into the virtual projects, based around template files located within src/test/resources. |
17 | 14 | */ |
18 | | -class BuildScriptTemplateProcessor(private val targetGradleVersion: String?, |
19 | | - private val replacements: Map<String, Any>) { |
20 | | - |
21 | | - fun process(rawText: String): String { |
22 | | - var ignoredBlockCount = 0 |
23 | | - var loopBlockCount = 0 |
24 | | - |
25 | | - // Replace GET tokens first |
26 | | - val text1 = rawText.replace(GET_MATCHER) { result -> |
27 | | - val key = result.groupValues.last() |
28 | | - if (key in replacements) { |
29 | | - "\"${replacements[key].toString()}\"" |
30 | | - } else { |
31 | | - "\"(missing: '$key')\"" |
32 | | - } |
33 | | - } |
34 | | - |
35 | | - // Iterate over the result and exclude all non-matching IF token blocks |
36 | | - val text2 = StringBuilder() |
37 | | - for (line in text1.lines()) { |
38 | | - // Check if any conditional marker is included in the line |
39 | | - val ifMatch = IF_MATCHER.find(line) |
40 | | - val elseMatch = ELSE_MATCHER.find(line) |
41 | | - val ifgradleMatch = IFGRADLE_MATCHER.find(line) |
42 | | - val foreachMatch = FOREACH_MATCHER.find(line) |
43 | | - val endMatch = END_MATCHER.find(line) |
44 | | - |
45 | | - if (ignoredBlockCount == 0 && ifMatch != null) { |
46 | | - val ifKey = ifMatch.groupValues.last() |
47 | | - val conditionValue = replacements[ifKey]?.toString() |
48 | | - val conditionEnabled = conditionValue != "false" |
49 | | - && (conditionValue == "true" || conditionValue != null) |
50 | | - |
51 | | - if (!conditionEnabled) { |
52 | | - // Ignore this block |
53 | | - ignoredBlockCount++ |
54 | | - } |
55 | | - } |
56 | | - |
57 | | - if (ignoredBlockCount == 0 && ifgradleMatch != null) { |
58 | | - val ifgradleExpression = ifgradleMatch.groupValues.last() |
59 | | - |
60 | | - // When the given Gradle requirement is null, or if the requirement |
61 | | - // is not fulfilled by the block, ignore it |
62 | | - if (shouldIgnoreIfgradleBlock(ifgradleExpression)) { |
63 | | - // Ignore this block |
64 | | - ignoredBlockCount++ |
| 15 | +class BuildScriptTemplateProcessor( |
| 16 | + folder: File, |
| 17 | + private val replacements: Map<String, Any>, |
| 18 | + private val agpVersion: String, |
| 19 | + private val gradleVersion: String |
| 20 | +) { |
| 21 | + |
| 22 | + private val renderer = Templates( |
| 23 | + root = FileReadingTemplateProvider(folder), |
| 24 | + cache = true, |
| 25 | + config = TemplateConfig( |
| 26 | + // Allow checking for AGP & Gradle versions inside the templates |
| 27 | + extraFunctions = listOf( |
| 28 | + TeFunction("atLeastAgp") { args -> |
| 29 | + isVersionAtLeast(agpVersion, args[0].toDynamicString()) |
| 30 | + }, |
| 31 | + TeFunction("atLeastGradle") { args -> |
| 32 | + isVersionAtLeast(gradleVersion, args[0].toDynamicString()) |
65 | 33 | } |
66 | | - } |
| 34 | + ) |
| 35 | + ) |
| 36 | + ) |
67 | 37 |
|
68 | | - if (ignoredBlockCount == 0 && foreachMatch != null) { |
69 | | - val foreachKey = foreachMatch.groupValues.last() |
70 | | - |
71 | | - // Started a loop. Find the associated list of elements first |
72 | | - val value = (replacements[foreachKey] as? List<Any?>) |
73 | | - ?.joinToString(separator = ",") { "\"$it\""} |
74 | | - ?: throw IllegalArgumentException("FOREACH replacement value should be a list, but got: ${replacements[foreachKey]}") |
75 | | - |
76 | | - // Emit the beginning of a foreach loop and continue onward. |
77 | | - // As soon as the next END marker is detected, the loop will be closed again |
78 | | - if (value.isNotEmpty()) { |
79 | | - text2.append("listOf($value).forEach { it ->").appendln() |
80 | | - loopBlockCount++ |
81 | | - } else { |
82 | | - // Empty list == ignore the entire block and don't emit it |
83 | | - ignoredBlockCount++ |
84 | | - } |
85 | | - } |
86 | | - |
87 | | - if (elseMatch != null) { |
88 | | - ignoredBlockCount = if (ignoredBlockCount == 0) { |
89 | | - 1 |
90 | | - } else { |
91 | | - max(0, ignoredBlockCount - 1) |
92 | | - } |
93 | | - } |
| 38 | + fun process(fileName: String): String = runBlocking { |
| 39 | + renderer.render(fileName, replacements) |
| 40 | + } |
94 | 41 |
|
95 | | - // Lines with a marker shouldn't be appended in the first place; |
96 | | - // normal lines are appended only if we are not excluding a previously false conditional |
97 | | - val isNormalLine = ifMatch == null && ifgradleMatch == null && elseMatch == null && endMatch == null |
98 | | - if (isNormalLine && ignoredBlockCount == 0) { |
99 | | - text2.append(line).appendln() |
100 | | - } |
| 42 | + /* Private */ |
101 | 43 |
|
102 | | - if (endMatch != null) { |
103 | | - if (loopBlockCount > 0) { |
104 | | - // Emit the end of a prior loop |
105 | | - text2.append("}").appendln() |
106 | | - loopBlockCount = max(0, loopBlockCount - 1) |
107 | | - } else { |
108 | | - ignoredBlockCount = max(0, ignoredBlockCount - 1) |
109 | | - } |
110 | | - } |
111 | | - } |
112 | | - return text2.toString() |
| 44 | + private fun isVersionAtLeast(actual: String, required: String): Boolean { |
| 45 | + val actualVersion = SemanticVersion(actual) |
| 46 | + val requiredVersion = SemanticVersion(required) |
| 47 | + return actualVersion >= requiredVersion |
113 | 48 | } |
114 | 49 |
|
115 | | - private fun shouldIgnoreIfgradleBlock(version: String): Boolean { |
116 | | - if (targetGradleVersion == null) { |
117 | | - return true |
| 50 | + private class FileReadingTemplateProvider(private val folder: File) : TemplateProvider { |
| 51 | + override suspend fun get(template: String): String { |
| 52 | + return File(folder, template).readText() |
118 | 53 | } |
119 | | - |
120 | | - val targetValue = SemanticVersion(targetGradleVersion) |
121 | | - val compareValue = SemanticVersion(version) |
122 | | - return compareValue > targetValue |
123 | 54 | } |
124 | 55 | } |
0 commit comments