Add playground dialog for post-processing variables/functions and update the documentation

This commit is contained in:
0xb00bface 2023-03-04 15:14:59 +01:00
parent e63107cd93
commit f2df8deb0c
24 changed files with 432 additions and 82 deletions

View File

@ -278,7 +278,8 @@ public class SettingsTab extends Tab implements TabSelectionListener {
Group.of("Post-Processing",
Setting.of("Threads", postProcessingThreads),
Setting.of("Steps", postProcessingStepPanel),
Setting.of("", createHelpButton("Post-Processing Help", "http://localhost:5689/docs/PostProcessing.md")))),
Setting.of("", createHelpButton("Post-Processing Help", "http://localhost:5689/docs/PostProcessing.md")),
Setting.of("", createVariablePlayGroundButton()))),
Category.of("Events & Actions", new ActionSettingsPanel(recorder)), Category.of("Ignore List", ignoreList),
Category.of("Sites", siteCategories.toArray(new Category[0])),
Category.of("Proxy",
@ -377,6 +378,12 @@ public class SettingsTab extends Tab implements TabSelectionListener {
return postProcessingHelpButton;
}
private Button createVariablePlayGroundButton() {
var postProcessingHelpButton = new Button("Variable Playground");
postProcessingHelpButton.setOnAction(e -> new VariablePlayGroundDialogFactory().openDialog(this.getTabPane().getScene(), config, recorder));
return postProcessingHelpButton;
}
private void bindEnabledProperty(Setting s, BooleanExpression bindTo) {
try {
s.getGui().disableProperty().bind(bindTo);

View File

@ -0,0 +1,76 @@
package ctbrec.ui.settings;
import ctbrec.Config;
import ctbrec.Recording;
import ctbrec.StringUtil;
import ctbrec.UnknownModel;
import ctbrec.recorder.Recorder;
import ctbrec.recorder.postprocessing.PostProcessingContext;
import ctbrec.sites.chaturbate.Chaturbate;
import ctbrec.ui.controls.Dialogs;
import ctbrec.ui.tabs.DownloadPostprocessor;
import ctbrec.variableexpansion.functions.AntlrSyntacErrorAdapter;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;
import java.nio.file.Paths;
import java.time.Instant;
public class VariablePlayGroundDialogFactory {
public void openDialog(Scene parent, Config config, Recorder recorder) {
Chaturbate chaturbate = new Chaturbate();
UnknownModel unknownModel = new UnknownModel();
unknownModel.setName("Pussy_Galore");
unknownModel.setDisplayName("Pussy Galore");
unknownModel.setSite(chaturbate);
Recording recording = new Recording();
recording.setAbsoluteFile(Paths.get("ctbrec", "recs", "pussy_galore", "2023-02-26_14-05-56").toFile());
recording.setStartDate(Instant.now());
recording.setStatus(Recording.State.POST_PROCESSING);
recording.setNote("notes about the recording");
recording.setModel(unknownModel);
PostProcessingContext ctx = new PostProcessingContext();
ctx.setConfig(config);
ctx.setRecorder(recorder);
ctx.setRecording(recording);
DownloadPostprocessor postprocessor = new DownloadPostprocessor();
GridPane pane = new GridPane();
Label result = new Label();
Label error = new Label();
pane.add(new Label("Expression"), 0, 0);
TextField input = new TextField();
input.setMinWidth(600);
pane.add(input, 1, 0);
GridPane.setHgrow(input, Priority.ALWAYS);
input.setOnKeyTyped(evt -> {
String r = postprocessor.fillInPlaceHolders(input.getText(), ctx, new AntlrSyntacErrorAdapter() {
@Override
public void syntaxError(Recognizer<?, ?> recognizer, Object o, int line, int pos, String s, RecognitionException e) {
error.setText(String.format("Syntax error at %d:%d %s", line, pos, s));
}
});
result.setText(r);
if (StringUtil.isNotBlank(r)) {
error.setText("");
}
});
pane.add(error, 0, 1);
GridPane.setColumnSpan(error, 2);
pane.add(result, 0, 2);
GridPane.setColumnSpan(result, 2);
pane.setHgap(5);
pane.vgapProperty().bind(pane.hgapProperty());
Dialogs.showCustomInput(parent, "Playground", pane, (obs, oldV, newV) -> {
});
}
}

View File

@ -1,8 +1,10 @@
#### Post-Processing
The post-processing gives you the possibility to execute different actions after a recording has finished. You can use that to convert
the files to another format, create preview images, rename / move the file etc.
##### Available Steps
- **create a copy** - Creates a copy of the original recording. All following post-processing steps are executed on the copy, not on the
original recording. This means, that the post-processing can be rerun in case a step failed, because the original recording is still
available.
@ -21,17 +23,20 @@ the files to another format, create preview images, rename / move the file etc.
- **create contactsheet** - create a contact sheet with preview images of the recording
#### Planned for future releases
- **call a webhook** - call a URL once a recording is finished
- **create timeline thumbnails** - create a small thumbnail for every second or every few seconds, which can be used to very fast
scan through a recording
#### How to configure the server to do post-processing
There is currently no user interface to configure the post-processing for the server. It has to be added manually to the server config.
I suggest to start the app and configure the post-processing steps in the settings. Afterwards you close the app and copy the
post-processing section from the settings.json to your server.json file. To find out, where these files are on your system, read
[Configuration File](ConfigurationFile.md).
The part you have to copy is
```
postProcessors: [
...
@ -43,16 +48,13 @@ The part you have to copy is
<a id="variables" />
###### Available variables:
- **${modelName}** - the name of the recorded model
- **${modelDisplayName}** - the name of the recorded model, which is shown on the webpage. Might be the same as
- **${modelDisplayName}** - the name of the recorded model, which is shown on the webpage. Might be the same as
${modelName}
- **${modelSanitizedName}** - sanitized name of the model. The following characters are replaced by an underscore:
\\, /, ', " and space
- **${modelGroupName}** - name of the model group, if the model is part of a group
- **${modelGroupId}** - the unique ID of the model group, if the model is part of a group
- **${siteName}** - the name of the cam site, the model streams on
- **${siteSanitizedName}** - sanitized name of the site. The following characters are replaced by an underscore:
\\, /, ', " and space
- **${fileSuffix}** - the file extension of the recording. E.g. ts or mp4. In case of a standard server recording,
this will be empty
- **${epochSecond}** - timestamp of the recording in seconds since 1970-01-01 (unixtime)
@ -60,16 +62,31 @@ The part you have to copy is
\\, /, ', " and space
- **${recordingNotes}** - sanitized recording notes. The following characters are replaced by an underscore:
\\, /, ', " and space. Useful for the download of recordings from the server.
- **${recordingsDir}** - the base directory of all recordings. Same as Recordings Directory in the Recorder settings
- **${recordingsDir}** - the base directory of all recordings. Same as Recordings Directory in the Recorder settings
section.
- **${absolutePath}** - the absolute path in the filesystem to the recording file (or the recording directory in case of
a server recording)
- **${absoluteParentPath}** - the absolute path to the parent directory of the recording in the filesystem (or the
- **${absoluteParentPath}** - the absolute path to the parent directory of the recording in the filesystem (or the
recording dir in case of a server recording)
- **${utcDateTime}** and **${localDateTime}** - the timestamp of the recording in the UTC or your local timezone. If no
pattern is given, the default ```yyyy-MM-dd_HH-mm-ss``` is used. You can also define your own pattern using the following
symbols. For example ```${localDateTime(yyyyMMdd-HHmmss)}``` would lead to 20200928-173605.
- **${utcDateTime}** and **${localDateTime}** - the timestamp of the recording in the UTC or your local timezone
#### Functions
- **$trim** - removes all leading and trailing space - `$trim( hello world )` becomes `hello world`
- **$upper** - converts all of the characters to upper case - `$upper(hello world)` becomes `HELLO WORLD`
- **$lower** - converts all of the characters to lower case - `$lower(hElLo WORLD)` becomes `hello world`
- **$capitalize** - capitalizes words changing the first character to upper case - `$capitalize(hElLo WorLD)` becomes `HElLo WorLD`
- **$sanitize** - removes problematic characters - `$sanitize(hEl'Lo / WO"RLD)` becomes `hEl_Lo___WO_RLD`. The following characters are replaced by an
underscore:
\\, /, ', " and space
- **$orElse** - provide an alternative in case a variable is not set - `$orElse(${variable},someValue)` - becomes `${variable}`, if it is set or `someValue`, if
`${variable}` is not set
- **$format** - formats a date, can be used with a pattern or without.
Examples:
- `$format(${localDateTime})` - becomes something like `2023-02-26_12-23-15`
- `$format(${localDateTime},yyyyMMdd-HHmmss)` would lead to `20200928-173605`
<table class="table-striped">
<thead>
<tr><th scope="col">Symbol</th> <th scope="col">Meaning</th> <th scope="col">Presentation</th> <th scope="col">Examples</th>
</tr></thead>
@ -118,16 +135,10 @@ The part you have to copy is
</tr></tbody>
</table>
For more information see: [DateTimeFormatter](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/format/DateTimeFormatter.html)
###### Fallback values:
You can define a fallback value for each variable in case there is no value available for the variable. The syntax is
${placeholder?foobar}
Let's for example say you have created some model groups. For models, which are part of a group, you want to use the group name. But for models, which
are not part of a group you want to use the sanitized name. You can achieve that by using the following expression:
For more information see: [DateTimeFormatter](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/time/format/DateTimeFormatter.html)
${modelGroupName?${modelSanitizedName}}
#### Full Example
It can be read like "use the modelGroupName, but if that is not available use modelSanitizedName".
`$orElse(${modelGroupName},$sanitize(${modelName}))_$format(${localDateTime})_${recordingNotes}`

View File

@ -66,7 +66,7 @@ public class Settings {
public int defaultPriority = 50;
public boolean determineResolution = false;
public List<String> disabledSites = new ArrayList<>();
public String downloadFilename = "${modelSanitizedName}-$format(${localDateTime})";
public String downloadFilename = "$sanitize(${modelName})_$format(${localDateTime})";
public List<EventHandlerConfiguration> eventHandlers = new ArrayList<>();
public boolean eventsSuspended = false;
public boolean fastScrollSpeed = true;

View File

@ -10,7 +10,9 @@ import ctbrec.variableexpansion.antlr.PostProcessingLexer;
import ctbrec.variableexpansion.antlr.PostProcessingParser;
import ctbrec.variableexpansion.functions.AntlrSyntacErrorAdapter;
import lombok.extern.slf4j.Slf4j;
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.CharStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.tree.ParseTree;
import java.io.IOException;
@ -28,6 +30,10 @@ import static java.util.Optional.ofNullable;
public abstract class AbstractPlaceholderAwarePostProcessor extends AbstractPostProcessor {
public String fillInPlaceHolders(String input, PostProcessingContext ctx) {
return this.fillInPlaceHolders(input, ctx, null);
}
public String fillInPlaceHolders(String input, PostProcessingContext ctx, AntlrSyntacErrorAdapter errorListener) {
Recording rec = ctx.getRecording();
Config config = ctx.getConfig();
@ -42,21 +48,16 @@ public abstract class AbstractPlaceholderAwarePostProcessor extends AbstractPost
variables.put("utcDateTime", getUtcDateTime(rec));
variables.put("localDateTime", getLocalDateTime(rec));
return fillInPlaceHolders(input, variables);
return fillInPlaceHolders(input, variables, errorListener);
}
private String fillInPlaceHolders(String input, Map<String, Optional<Object>> variables) {
private String fillInPlaceHolders(String input, Map<String, Optional<Object>> variables, AntlrSyntacErrorAdapter errorListener) {
try (StringReader reader = new StringReader(input)) {
CharStream s = CharStreams.fromReader(reader);
PostProcessingLexer lexer = new PostProcessingLexer(s);
CommonTokenStream tokens = new CommonTokenStream(lexer);
PostProcessingParser parser = new PostProcessingParser(tokens);
parser.addErrorListener(new AntlrSyntacErrorAdapter() {
@Override
public void syntaxError(Recognizer<?, ?> recognizer, Object o, int line, int pos, String s, RecognitionException e) {
log.warn("Syntax error at {}:{} {}", line, pos, s);
}
});
Optional.ofNullable(errorListener).ifPresent(parser::addErrorListener);
ParseTree parseTree = parser.line();
ParserVisitor visitor = new ParserVisitor(variables);
return visitor.visit(parseTree);

View File

@ -16,7 +16,7 @@ public class Move extends AbstractPlaceholderAwarePostProcessor {
private static final Logger LOG = LoggerFactory.getLogger(Move.class);
public static final String PATH_TEMPLATE = "path.template";
public static final String DEFAULT = "${modelSanitizedName}" + File.separatorChar + "$format(${localDateTime})";
public static final String DEFAULT = "$sanitize(${modelName})" + File.separatorChar + "$format(${localDateTime})";
@Override
public String getName() {

View File

@ -13,8 +13,8 @@ public class Rename extends AbstractPlaceholderAwarePostProcessor {
private static final Logger LOG = LoggerFactory.getLogger(Rename.class);
public static final String FILE_NAME_TEMPLATE = "filename.template";
public static final String DEFAULT = "${modelSanitizedName}_$format(${localDateTime}).${fileSuffix}";
public static final String DEFAULT_DIR = "${modelSanitizedName}_$format(${localDateTime})";
public static final String DEFAULT = "$sanitize(${modelName})_$format(${localDateTime}).${fileSuffix}";
public static final String DEFAULT_DIR = "$sanitize(${modelName})_$format(${localDateTime})";
@Override
public String getName() {

View File

@ -18,10 +18,8 @@ public class ModelVariableExpander extends AbstractVariableExpander {
Optional<ModelGroup> modelGroup = Optional.ofNullable(recorder).flatMap(r -> r.getModelGroup(model));
placeholderValueSuppliers.put("modelName", ofNullable(model.getName()));
placeholderValueSuppliers.put("modelDisplayName", ofNullable(model.getDisplayName()));
placeholderValueSuppliers.put("modelSanitizedName",getSanitizedName(model));
placeholderValueSuppliers.put("modelNotes",getSanitizedModelNotes(config, model));
placeholderValueSuppliers.put("modelNotes", getSanitizedModelNotes(config, model));
placeholderValueSuppliers.put("siteName", ofNullable(model).map(Model::getSite).map(Site::getName));
placeholderValueSuppliers.put("siteSanitizedName",getSanitizedSiteName(model));
placeholderValueSuppliers.put("modelGroupName", modelGroup.map(ModelGroup::getName));
placeholderValueSuppliers.put("modelGroupId", modelGroup.map(ModelGroup::getId).map(UUID::toString));
}
@ -30,19 +28,6 @@ public class ModelVariableExpander extends AbstractVariableExpander {
return fillInPlaceHolders(input, placeholderValueSuppliers);
}
private Optional<Object> getSanitizedName(Model model) {
String name = model.getSanitizedNamed();
if (StringUtil.isBlank(name)) {
return Optional.empty();
} else {
return Optional.of(name);
}
}
private Optional<Object> getSanitizedSiteName(Model model) {
return ofNullable(model).map(Model::getSite).map(Site::getName).map(StringUtil::sanitize);
}
private Optional<Object> getSanitizedModelNotes(Config config, Model m) {
return ofNullable(config.getModelNotes(m)).map(StringUtil::sanitize);
}

View File

@ -7,7 +7,10 @@ import java.util.Optional;
public class Capitalize implements VarArgsFunction<Object, String> {
@Override
public String apply(Object[] params) {
public String apply(Object... params) {
if (params == null || params.length == 0) {
return "";
}
return Optional.ofNullable(params).map(p -> p[0]).map(String.class::cast).map(StringUtil::capitalize).orElse("");
}
}

View File

@ -9,23 +9,30 @@ import java.util.Optional;
public class Format implements VarArgsFunction<Object, String> {
@Override
public String apply(Object[] params) {
public String apply(Object... params) {
return Optional.ofNullable(params).map(p -> {
String format = "yyyy-MM-dd_HH-mm-ss";
if (p.length > 1) {
format = (String) p[1];
}
ZonedDateTime zdt;
if (p[0] instanceof String date) {
try {
zdt = ZonedDateTime.parse(date);
} catch (DateTimeParseException e) {
try {
if (p.length == 0) {
return "";
}
} else {
zdt = (ZonedDateTime) p[0];
String format = "yyyy-MM-dd_HH-mm-ss";
if (p.length > 1) {
format = (String) p[1];
}
ZonedDateTime zdt;
if (p != null && p.length > 0 && p[0] instanceof String date) {
try {
zdt = ZonedDateTime.parse(date);
} catch (DateTimeParseException e) {
return "";
}
} else {
zdt = (ZonedDateTime) p[0];
}
return DateTimeFormatter.ofPattern(format).format(zdt);
} catch (Exception e) {
return "";
}
return DateTimeFormatter.ofPattern(format).format(zdt);
})
.orElse("");
}

View File

@ -6,7 +6,10 @@ import java.util.Optional;
public class Lower implements VarArgsFunction<Object, String> {
@Override
public String apply(Object[] params) {
public String apply(Object... params) {
if (params == null || params.length == 0) {
return "";
}
return Optional.ofNullable(params).map(sa -> sa[0]).map(String.class::cast).map(String::toLowerCase).orElse("");
}
}

View File

@ -8,15 +8,19 @@ import java.util.Optional;
public class OrElse implements VarArgsFunction<Object, String> {
@Override
public String apply(Object... params) {
if (params.length < 2) {
throw new IllegalArgumentException("OrElse needs two parameters");
if (params == null || params.length < 1) {
return "";
}
String fallback = "";
if (params.length >= 2) {
fallback = String.valueOf(params[1]);
}
Optional<String> result = Optional.ofNullable(params[0]).map(String::valueOf);
if (result.isPresent() && StringUtil.isNotBlank(result.get())) {
return result.get();
} else {
return String.valueOf(params[1]);
return fallback;
}
}
}

View File

@ -7,7 +7,10 @@ import java.util.Optional;
public class Sanitize implements VarArgsFunction<Object, String> {
@Override
public String apply(Object[] params) {
public String apply(Object... params) {
if (params == null || params.length == 0) {
return "";
}
return Optional.ofNullable(params).map(p -> p[0]).map(String.class::cast).map(StringUtil::sanitize).orElse("");
}
}

View File

@ -6,7 +6,10 @@ import java.util.Optional;
public class Trim implements VarArgsFunction<Object, String> {
@Override
public String apply(Object[] params) {
public String apply(Object... params) {
if (params == null || params.length == 0) {
return "";
}
return Optional.ofNullable(params).map(sa -> sa[0]).map(String.class::cast).map(String::trim).orElse("");
}
}

View File

@ -6,7 +6,10 @@ import java.util.Optional;
public class Upper implements VarArgsFunction<Object, String> {
@Override
public String apply(Object[] params) {
public String apply(Object... params) {
if (params == null || params.length == 0) {
return "";
}
return Optional.ofNullable(params).map(sa -> sa[0]).map(String.class::cast).map(String::toUpperCase).orElse("");
}
}

View File

@ -42,7 +42,7 @@ class AbstractPlaceholderAwarePostProcessorTest extends AbstractPpTest {
assertEquals("asdf_Mockita Boobilicious_asdf", placeHolderAwarePp.fillInPlaceHolders(input, createPostProcessingContext(rec, null, config)));
input = "asdf_${modelDisplayName}_asdf";
assertEquals("asdf_Mockita Boobilicious_asdf", placeHolderAwarePp.fillInPlaceHolders(input, createPostProcessingContext(rec, null, config)));
input = "asdf_${modelSanitizedName}_asdf";
input = "asdf_$sanitize(${modelName})_asdf";
assertEquals("asdf_Mockita_Boobilicious_asdf", placeHolderAwarePp.fillInPlaceHolders(input, createPostProcessingContext(rec, null, config)));
}
@ -50,7 +50,7 @@ class AbstractPlaceholderAwarePostProcessorTest extends AbstractPpTest {
void testSiteNameReplacement() {
String input = "asdf_${siteName}_asdf";
assertEquals("asdf_Chaturbate_asdf", placeHolderAwarePp.fillInPlaceHolders(input, createPostProcessingContext(rec, null, config)));
input = "asdf_${siteSanitizedName}_asdf";
input = "asdf_$sanitize(${siteName})_asdf";
assertEquals("asdf_Chaturbate_asdf", placeHolderAwarePp.fillInPlaceHolders(input, createPostProcessingContext(rec, null, config)));
}
@ -137,7 +137,7 @@ class AbstractPlaceholderAwarePostProcessorTest extends AbstractPpTest {
@Test
void testPlaceholderDefaultValues() {
String input = "asdf_$orElse(${modelGroupName},$orElse(${modelSanitizedName},anonymous))_asdf";
String input = "asdf_$orElse(${modelGroupName},$orElse($sanitize(${modelName}),anonymous))_asdf";
PostProcessingContext ctx = createPostProcessingContext(rec, null, config);
ctx.getRecording().getModel().setName(null);
assertEquals("asdf_anonymous_asdf", placeHolderAwarePp.fillInPlaceHolders(input, ctx));

View File

@ -1,19 +1,18 @@
package ctbrec.recorder.postprocessing;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.Recording;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.nio.file.Files;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.junit.jupiter.api.Test;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.Recording;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
class RenameDirectoryTest extends AbstractPpTest {
@ -29,7 +28,7 @@ class RenameDirectoryTest extends AbstractPpTest {
pp.postprocess(createPostProcessingContext(rec, recordingManager, config));
Matcher m = Pattern.compile("Mockita_Boobilicious_\\d{4}-\\d{2}-\\d{2}_\\d{2}-\\d{2}-\\d{2}").matcher(rec.getAbsoluteFile().getName());
assertTrue(m.matches());
assertTrue(m.matches(), () -> rec.getAbsoluteFile().getName() + " does not match");
assertEquals(rec.getAbsoluteFile(), rec.getPostProcessedFile());
assertNotEquals(rec.getAbsoluteFile(), original);
}

View File

@ -0,0 +1,31 @@
package ctbrec.variableexpansion.functions;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class CapitalizeTest {
Capitalize capitalize = new Capitalize();
@Test
void testNullParams() {
assertEquals("", capitalize.apply((Object[]) null));
}
@Test
void testEmptyParams() {
assertEquals("", capitalize.apply());
}
@Test
void testNormalStrings() {
assertEquals("Hello World", capitalize.apply("hello world"));
assertEquals("HElLo WoRlD", capitalize.apply("hElLo woRlD"));
assertEquals("HELLO WORLD", capitalize.apply("HELLO WORLD"));
}
@Test
void testSpecialCharacters() {
assertEquals("He{LLo Wo\n$rlD", capitalize.apply("he{LLo wo\n$rlD"));
}
}

View File

@ -0,0 +1,55 @@
package ctbrec.variableexpansion.functions;
import org.junit.jupiter.api.Test;
import java.time.ZonedDateTime;
import static org.junit.jupiter.api.Assertions.assertEquals;
class FormatTest {
Format format = new Format();
@Test
void testNullParams() {
assertEquals("", format.apply((Object[]) null));
}
@Test
void testEmptyParams() {
assertEquals("", format.apply());
}
@Test
void testZonedDateTimeParameter() {
String expected = "2023-02-26_17-11-23";
String zdt = "2023-02-26T17:11:23.363270467+01:00[Europe/Berlin]";
assertEquals(expected, format.apply(ZonedDateTime.parse(zdt)));
}
@Test
void testStringParameter() {
String expected = "2023-02-26_17-11-23";
String zdt = "2023-02-26T17:11:23.363270467+01:00[Europe/Berlin]";
assertEquals(expected, format.apply(zdt));
}
@Test
void testInvalidStringParameter() {
assertEquals("", format.apply("bullshit"));
}
@Test
void testIStringParameterWithPattern() {
String expected = "20230226-171123";
String zdt = "2023-02-26T17:11:23.363270467+01:00[Europe/Berlin]";
assertEquals(expected, format.apply(zdt, "yyyyMMdd-HHmmss"));
}
@Test
void testIStringParameterWithInvalidPattern() {
String expected = "";
String zdt = "2023-02-26T17:11:23.363270467+01:00[Europe/Berlin]";
assertEquals(expected, format.apply(zdt, "asdft"));
}
}

View File

@ -0,0 +1,31 @@
package ctbrec.variableexpansion.functions;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class LowerTest {
Lower lower = new Lower();
@Test
void testNullParams() {
assertEquals("", lower.apply((Object[]) null));
}
@Test
void testEmptyParams() {
assertEquals("", lower.apply());
}
@Test
void testNormalStrings() {
assertEquals("hello world", lower.apply("hello world"));
assertEquals("hello world", lower.apply("hElLo woRlD"));
assertEquals("hello world", lower.apply("HELLO WORLD"));
}
@Test
void testSpecialCharacters() {
assertEquals("he{llo\nwo$rld", lower.apply("he{LLo\nwo$rlD"));
}
}

View File

@ -0,0 +1,29 @@
package ctbrec.variableexpansion.functions;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class OrElseTest {
OrElse orElse = new OrElse();
@Test
void testNullParams() {
assertEquals("", orElse.apply((Object[]) null));
}
@Test
void testEmptyParams() {
assertEquals("", orElse.apply());
}
@Test
void testHappyPath() {
assertEquals("world", orElse.apply("world", "hello"));
}
@Test
void testFallback() {
assertEquals("hello", orElse.apply("", "hello"));
}
}

View File

@ -0,0 +1,35 @@
package ctbrec.variableexpansion.functions;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class SanitizeTest {
Sanitize sanitize = new Sanitize();
@Test
void testNullParams() {
assertEquals("", sanitize.apply((Object[]) null));
}
@Test
void testEmptyParams() {
assertEquals("", sanitize.apply());
}
@Test
void testNormalStrings() {
assertEquals("hel!lo", sanitize.apply("hel!lo"));
assertEquals("hE$lLo", sanitize.apply("hE$lLo"));
assertEquals("HELL%O", sanitize.apply("HELL%O"));
}
@Test
void testSpecialCharacters() {
assertEquals("asdf_asdf", sanitize.apply("asdf asdf"));
assertEquals("asdf_asdf", sanitize.apply("asdf\"asdf"));
assertEquals("asdf_asdf", sanitize.apply("asdf'asdf"));
assertEquals("asdf_asdf", sanitize.apply("asdf\\asdf"));
assertEquals("asdf_asdf", sanitize.apply("asdf/asdf"));
}
}

View File

@ -0,0 +1,33 @@
package ctbrec.variableexpansion.functions;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class TrimTest {
Trim trim = new Trim();
@Test
void testNullParams() {
assertEquals("", trim.apply((Object[]) null));
}
@Test
void testEmptyParams() {
assertEquals("", trim.apply());
}
@Test
void testNormalStrings() {
assertEquals("hello", trim.apply("hello"));
assertEquals("hello", trim.apply(" hello "));
assertEquals("hello", trim.apply(" hello "));
assertEquals("hello", trim.apply("\thello\t"));
assertEquals("hello", trim.apply("\nhello\n"));
}
@Test
void testSpecialCharacters() {
assertEquals("he{LLo\nwo $rlD", trim.apply("he{LLo\nwo $rlD"));
}
}

View File

@ -0,0 +1,31 @@
package ctbrec.variableexpansion.functions;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class UpperTest {
Upper upper = new Upper();
@Test
void testNullParams() {
assertEquals("", upper.apply((Object[]) null));
}
@Test
void testEmptyParams() {
assertEquals("", upper.apply());
}
@Test
void testNormalStrings() {
assertEquals("HELLO WORLD", upper.apply("hello world"));
assertEquals("HELLO WORLD", upper.apply("hElLo woRlD"));
assertEquals("HELLO WORLD", upper.apply("HELLO WORLD"));
}
@Test
void testSpecialCharacters() {
assertEquals("HE{LLO\nWO$RLD", upper.apply("he{LLo\nwo$rlD"));
}
}