Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add functions to modify the placeholder values #337

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,32 @@ You can configure the version and properties adjustments for specific branches a
e.g `${env.BUILD_NUMBER:-0}` or `${env.BUILD_NUMBER:-local}`

ℹ define placeholder overwrite value (placeholder is defined) like this `${name:+OVERWRITE_VALUE}`<br>
e.g `${dirty:-SNAPSHOT}` resolves to `-SNAPSHOT` instead of `-DIRTY`
e.g `${dirty:+-SNAPSHOT}` resolves to `-SNAPSHOT` instead of `-DIRTY`

ℹ placeholder default values and overwrite values containing the `:` character can escape it by doubling it
in case the value matches with a function name. For example, `${env.VAR:-hello::slug}` resolves to `hello:slug`
if `env.VAR` is not defined.

###### Placeholders functions
Placeholders can contain functions with the form: `${key:functionname}` where the function `functionname` is applied to
the value referenced by `key`.

They can be combined with format placeholders and combined, e.g. `${key:-abc/def/42:slug:uppercase:next}` will give
`ABD-DEF-43` if key is absent.

Defined functions:
- `slug`: replaces all sequences of characters that are not alphanumeric or underscore or hyphen with and hyphen and
eliminate duplicated hyphens from the value.
- `slug+dot`: does the same thing as `slug` but does not replace `.` characters.
- `slug+hyphen`: does the same thing as `slug` but replaces also `_` with `-`.
- `slug+hyphen+dot`: does the same thing as `slug+hyphen` but does not replace `.` characters.
- `word`: does the same thing as `slug` but replaces `-` and all non-alphanumeric characters with `_`.
- `word+dot`: does the same thing as `word` but does not replace `.` characters.
- `uppercase`: transform the value to uppercase.
- `lowercase`: transform the value to lowercase.
- `next`: if the value ends with a number, it is incremented, else it appends `.1` at the end of the value.
- `incrementlast`: if the value contains a number, increments it, else does nothing, e.g. if `x` has value `rc.1-something`
`${x:incrementlast}` returns `rc.2-something`.

###### Placeholders

Expand Down
63 changes: 57 additions & 6 deletions src/main/java/me/qoomon/gitversioning/commons/StringUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,62 @@

import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public final class StringUtil {

private static final Pattern END_NUMBERS = Pattern.compile("\\d+$");
private static final Pattern LAST_NUMBERS = Pattern.compile("(\\d+)(?=\\D*$)");
private static final Pattern PLACEHOLDER_PATTERN;
private static final Map<String, UnaryOperator<String>> FUNCTIONS;

static {
final Map<String, UnaryOperator<String>> functions = new HashMap<>();
functions.put("slug", str -> str.replaceAll("[^\\w-]+", "-").replaceAll("-{2,}", "-"));
functions.put("slug+dot", str -> str.replaceAll("[^\\w.-]+", "-").replaceAll("-{2,}", "-"));
functions.put("slug+hyphen", str -> str.replaceAll("[^a-zA-Z0-9-]+", "-").replaceAll("-{2,}", "-"));
functions.put("slug+hyphen+dot", str -> str.replaceAll("[^a-zA-Z0-9.-]+", "-").replaceAll("-{2,}", "-"));
functions.put("next", StringUtil::next);
functions.put("incrementlast", StringUtil::incrementLast);
functions.put("uppercase", str -> str.toUpperCase(Locale.ROOT));
functions.put("lowercase", str -> str.toLowerCase(Locale.ROOT));
functions.put("word", str -> str.replaceAll("\\W+", "_").replaceAll("_{2,}", "_"));
functions.put("word+dot", str -> str.replaceAll("[^\\w.]+", "_").replaceAll("_{2,}", "_"));
FUNCTIONS = functions.entrySet().stream().collect(Collectors.toUnmodifiableMap(
Map.Entry::getKey,
e -> str -> str == null ? null : e.getValue().apply(str))
);
final String functionsAlternatives = FUNCTIONS.keySet().stream().map(Pattern::quote).collect(Collectors.joining("|:", "(?<functions>(?::", ")+)?"));
PLACEHOLDER_PATTERN = Pattern.compile("\\$\\{(?<key>[^}:]+)(?::(?<modifier>[-+])(?<value>(?:::|[^:}])*))?" + functionsAlternatives + "}");
}

public static String substituteText(String text, Map<String, Supplier<String>> replacements) {
StringBuffer result = new StringBuffer();
Pattern placeholderPattern = Pattern.compile("\\$\\{(?<key>[^}:]+)(?::(?<modifier>[-+])(?<value>[^}]*))?}");
Matcher placeholderMatcher = placeholderPattern.matcher(text);
Matcher placeholderMatcher = PLACEHOLDER_PATTERN.matcher(text);
while (placeholderMatcher.find()) {
String placeholderKey = placeholderMatcher.group("key");
Supplier<String> replacementSupplier = replacements.get(placeholderKey);
String replacement = replacementSupplier != null ? replacementSupplier.get() : null;
String placeholderModifier = placeholderMatcher.group("modifier");
if(placeholderModifier != null){
if (placeholderModifier != null) {
if (placeholderModifier.equals("-") && replacement == null) {
replacement = placeholderMatcher.group("value");
replacement = placeholderMatcher.group("value").replace("::", ":");
}
if (placeholderModifier.equals("+") && replacement != null) {
replacement = placeholderMatcher.group("value");
replacement = placeholderMatcher.group("value").replace("::", ":");
}
}
String functionNames = placeholderMatcher.group("functions");
if (functionNames != null) {
for (String functionName : functionNames.substring(1).split(":")) {
replacement = FUNCTIONS.get(functionName).apply(replacement);
}
}
if (replacement != null) {
Expand All @@ -39,7 +72,7 @@ public static String substituteText(String text, Map<String, Supplier<String>> r

/**
* @param pattern pattern
* @param text to parse
* @param text to parse
* @return a map of group-index and group-name to matching value
*/
public static Map<String, String> patternGroupValues(Pattern pattern, String text) {
Expand Down Expand Up @@ -91,4 +124,22 @@ public static Set<String> patternGroupNames(Pattern pattern) {
return groups;
}

public static String next(String value) {
final Matcher matcher = END_NUMBERS.matcher(value);
if (matcher.find()) {
return matcher.replaceAll(matchResult -> String.valueOf(Long.parseLong(matchResult.group()) + 1L));
}
return value + ".1";
}

public static String incrementLast(String value) {
final Matcher matcher = LAST_NUMBERS.matcher(value);
if (matcher.find()) {
return matcher.replaceFirst(matchResult -> String.valueOf(Long.parseLong(matchResult.group()) + 1L));
}
return value;
}

private StringUtil() {
}
}
210 changes: 210 additions & 0 deletions src/test/java/me/qoomon/gitversioning/commons/StringUtilTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,216 @@ void substituteText_overwrite_value() {
assertThat(outputText).isEqualTo("xxx");
}

@Test
void substituteText_function_combination_slug_uppercase() {

// Given
String givenText = "${foo:+a/b:slug:uppercase}";
Map<String, Supplier<String>> givenSubstitutionMap = new HashMap<>();
givenSubstitutionMap.put("foo", () -> "aaa");

// When
String outputText = StringUtil.substituteText(givenText, givenSubstitutionMap);

// Then
assertThat(outputText).isEqualTo("A-B");
}

@Test
void substituteText_function_word_lowercase() {

// Given
String givenText = "${foo:word:lowercase}";
Map<String, Supplier<String>> givenSubstitutionMap = new HashMap<>();
givenSubstitutionMap.put("foo", () -> "PR-56+/ii");

// When
String outputText = StringUtil.substituteText(givenText, givenSubstitutionMap);

// Then
assertThat(outputText).isEqualTo("pr_56_ii");
}

@Test
void substituteText_function_slug() {

// Given
String givenText = "${foo:slug}";
Map<String, Supplier<String>> givenSubstitutionMap = new HashMap<>();
givenSubstitutionMap.put("foo", () -> "PR-56+/ii_7");

// When
String outputText = StringUtil.substituteText(givenText, givenSubstitutionMap);

// Then
assertThat(outputText).isEqualTo("PR-56-ii_7");
}

@Test
void substituteText_function_slug_dot() {

// Given
String givenText = "${foo:slug+dot}";
Map<String, Supplier<String>> givenSubstitutionMap = new HashMap<>();
givenSubstitutionMap.put("foo", () -> "my-release/2.5");

// When
String outputText = StringUtil.substituteText(givenText, givenSubstitutionMap);

// Then
assertThat(outputText).isEqualTo("my-release-2.5");
}

@Test
void substituteText_function_slug_hyphen() {

// Given
String givenText = "${foo:slug+hyphen}";
Map<String, Supplier<String>> givenSubstitutionMap = new HashMap<>();
givenSubstitutionMap.put("foo", () -> "my_release/2.5");

// When
String outputText = StringUtil.substituteText(givenText, givenSubstitutionMap);

// Then
assertThat(outputText).isEqualTo("my-release-2-5");
}

@Test
void substituteText_function_slug_hyphen_dot() {

// Given
String givenText = "${foo:slug+hyphen+dot}";
Map<String, Supplier<String>> givenSubstitutionMap = new HashMap<>();
givenSubstitutionMap.put("foo", () -> "my_release/2.5");

// When
String outputText = StringUtil.substituteText(givenText, givenSubstitutionMap);

// Then
assertThat(outputText).isEqualTo("my-release-2.5");
}

@Test
void substituteText_function_word_dot() {

// Given
String givenText = "${foo:word+dot}";
Map<String, Supplier<String>> givenSubstitutionMap = new HashMap<>();
givenSubstitutionMap.put("foo", () -> "release/2.5");

// When
String outputText = StringUtil.substituteText(givenText, givenSubstitutionMap);

// Then
assertThat(outputText).isEqualTo("release_2.5");
}

@Test
void substituteText_function_next() {

// Given
String givenText = "${foo:next}";
Map<String, Supplier<String>> givenSubstitutionMap = new HashMap<>();
givenSubstitutionMap.put("foo", () -> "alpha.56-rc.12");

// When
String outputText = StringUtil.substituteText(givenText, givenSubstitutionMap);

// Then
assertThat(outputText).isEqualTo("alpha.56-rc.13");
}

@Test
void substituteText_function_next_without_number_adds_dot_1() {

// Given
String givenText = "${foo:next}";
Map<String, Supplier<String>> givenSubstitutionMap = new HashMap<>();
givenSubstitutionMap.put("foo", () -> "alpha.56-rc.12-abc");

// When
String outputText = StringUtil.substituteText(givenText, givenSubstitutionMap);

// Then
assertThat(outputText).isEqualTo("alpha.56-rc.12-abc.1");
}

@Test
void substituteText_function_incrementlast() {

// Given
String givenText = "${foo:incrementlast}";
Map<String, Supplier<String>> givenSubstitutionMap = new HashMap<>();
givenSubstitutionMap.put("foo", () -> "alpha.56-rc.12");

// When
String outputText = StringUtil.substituteText(givenText, givenSubstitutionMap);

// Then
assertThat(outputText).isEqualTo("alpha.56-rc.13");
}

@Test
void substituteText_function_incrementlast_without_number_does_nothing() {

// Given
String givenText = "${foo:incrementlast}";
Map<String, Supplier<String>> givenSubstitutionMap = new HashMap<>();
givenSubstitutionMap.put("foo", () -> "alpha");

// When
String outputText = StringUtil.substituteText(givenText, givenSubstitutionMap);

// Then
assertThat(outputText).isEqualTo("alpha");
}

@Test
void substituteText_function_incrementlast_without_number_at_end() {

// Given
String givenText = "${foo:incrementlast}";
Map<String, Supplier<String>> givenSubstitutionMap = new HashMap<>();
givenSubstitutionMap.put("foo", () -> "alpha.9-special");

// When
String outputText = StringUtil.substituteText(givenText, givenSubstitutionMap);

// Then
assertThat(outputText).isEqualTo("alpha.10-special");
}

@Test
void substituteText_function_value_escaping() {

// Given
String givenText = "${foo:+word::lowercase}";
Map<String, Supplier<String>> givenSubstitutionMap = new HashMap<>();
givenSubstitutionMap.put("foo", () -> "PR-56+/ii");

// When
String outputText = StringUtil.substituteText(givenText, givenSubstitutionMap);

// Then
assertThat(outputText).isEqualTo("word:lowercase");
}

@Test
void substituteText_function_value_escaping_with_function() {

// Given
String givenText = "${foo:+WORD:::lowercase}";
Map<String, Supplier<String>> givenSubstitutionMap = new HashMap<>();
givenSubstitutionMap.put("foo", () -> "word");

// When
String outputText = StringUtil.substituteText(givenText, givenSubstitutionMap);

// Then
assertThat(outputText).isEqualTo("word:");
}


@Test
void valueGroupMap() {
Expand Down