feat: add project files
This commit is contained in:
38
formatterlib-abstraction/build.gradle.kts
Normal file
38
formatterlib-abstraction/build.gradle.kts
Normal file
@@ -0,0 +1,38 @@
|
||||
plugins {
|
||||
id("java-library")
|
||||
id("maven-publish")
|
||||
}
|
||||
|
||||
group = "io.github.axolotsh"
|
||||
version = "1.0-SNAPSHOT"
|
||||
|
||||
java {
|
||||
toolchain.languageVersion = JavaLanguageVersion.of(25)
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
create<MavenPublication>("mavenJava") {
|
||||
from(components["java"])
|
||||
|
||||
groupId = project.group.toString()
|
||||
artifactId = project.name
|
||||
version = project.version.toString()
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
name = "Reposilite"
|
||||
|
||||
val releasesUrl = "https://maven.axolotsh.org/releases"
|
||||
val snapshotsUrl = "https://maven.axolotsh.org/snapshots"
|
||||
url = uri(if (version.toString().endsWith("SNAPSHOT")) snapshotsUrl else releasesUrl)
|
||||
|
||||
credentials {
|
||||
username = providers.gradleProperty("reposiliteUser").orNull ?: "admin"
|
||||
password = providers.gradleProperty("reposilitePassword").orNull ?: "password"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package io.github.axolotsh.formatterlib.abstraction;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
public interface IFormatter {
|
||||
public String content();
|
||||
|
||||
public Collection<IFormatterTag> tags();
|
||||
|
||||
public String format(String input, IFormatterContext context);
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package io.github.axolotsh.formatterlib.abstraction;
|
||||
|
||||
public interface IFormatterContext {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package io.github.axolotsh.formatterlib.abstraction;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface IFormatterTag {
|
||||
public String name();
|
||||
|
||||
public String apply(String content, Map<String, String> args, IFormatterContext context);
|
||||
}
|
||||
58
formatterlib/build.gradle.kts
Normal file
58
formatterlib/build.gradle.kts
Normal file
@@ -0,0 +1,58 @@
|
||||
plugins {
|
||||
id("java-library")
|
||||
id("maven-publish")
|
||||
}
|
||||
|
||||
group = "io.github.axolotsh"
|
||||
version = "1.0-SNAPSHOT"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("org.projectlombok:lombok:1.18.46")
|
||||
annotationProcessor("org.projectlombok:lombok:1.18.46")
|
||||
|
||||
compileOnly("org.jetbrains:annotations:26.1.0")
|
||||
|
||||
implementation(project(":formatterlib-abstraction"))
|
||||
|
||||
testImplementation(libs.junit.jupiter)
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain.languageVersion = JavaLanguageVersion.of(25)
|
||||
}
|
||||
|
||||
tasks.named<Test>("test") {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
create<MavenPublication>("mavenJava") {
|
||||
from(components["java"])
|
||||
|
||||
groupId = project.group.toString()
|
||||
artifactId = project.name
|
||||
version = project.version.toString()
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
maven {
|
||||
name = "Reposilite"
|
||||
|
||||
val releasesUrl = "https://maven.axolotsh.org/releases"
|
||||
val snapshotsUrl = "https://maven.axolotsh.org/snapshots"
|
||||
url = uri(if (version.toString().endsWith("SNAPSHOT")) snapshotsUrl else releasesUrl)
|
||||
|
||||
credentials {
|
||||
username = providers.gradleProperty("reposiliteUser").orNull ?: "admin"
|
||||
password = providers.gradleProperty("reposilitePassword").orNull ?: "password"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
package io.github.axolotsh.formatterlib;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import io.github.axolotsh.formatterlib.abstraction.IFormatter;
|
||||
import io.github.axolotsh.formatterlib.abstraction.IFormatterContext;
|
||||
import io.github.axolotsh.formatterlib.abstraction.IFormatterTag;
|
||||
import lombok.Builder;
|
||||
import lombok.Getter;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Getter
|
||||
@Accessors(chain = true, fluent = true)
|
||||
public class Formatter implements IFormatter {
|
||||
private static final Pattern TAG_PATTERN = Pattern.compile(
|
||||
"<([a-zA-Z0-9]+)([^>]*?)(?:/>|>([\\s\\S]*?)</\\1>|>)");
|
||||
private static final Pattern ATTR_PATTERN = Pattern.compile(
|
||||
"([a-zA-Z0-9_-]+)=\"([^\"]*)\"");
|
||||
private static final String CONTENT_ATTR = "content";
|
||||
|
||||
@Nullable
|
||||
private String content;
|
||||
|
||||
private Collection<IFormatterTag> tags;
|
||||
private final Map<String, IFormatterTag> storage = new HashMap<>();
|
||||
|
||||
public Formatter(String content, IFormatterTag... tags) {
|
||||
this(content, Set.of(tags));
|
||||
}
|
||||
|
||||
@Builder(toBuilder = true)
|
||||
Formatter(String content, Collection<IFormatterTag> tags) {
|
||||
this.content = content;
|
||||
this.tags = tags;
|
||||
|
||||
for (var tag : tags)
|
||||
storage.put(tag.name(), tag);
|
||||
}
|
||||
|
||||
public static class FormatterBuilder {
|
||||
public FormatterBuilder tag(String tag) {
|
||||
return tag(new ReplacementTag(tag));
|
||||
}
|
||||
|
||||
public FormatterBuilder tag(String... tags) {
|
||||
for (var tag : tags)
|
||||
tag(new ReplacementTag(tag));
|
||||
return this;
|
||||
}
|
||||
|
||||
public FormatterBuilder tag(String tag, String replacement) {
|
||||
return tag(new ReplacementTag(tag, replacement));
|
||||
}
|
||||
|
||||
public FormatterBuilder tag(String tag, Function<IFormatterContext, String> replacement) {
|
||||
return tag(new ReplacementTag(tag, replacement));
|
||||
}
|
||||
|
||||
public FormatterBuilder tag(String tag, IFormatter formatter) {
|
||||
return tag(tag, ctx -> formatter.format(formatter.content(), ctx));
|
||||
}
|
||||
|
||||
public FormatterBuilder tag(IFormatter formatter) {
|
||||
return tag(formatter.tags().toArray(ReplacementTag[]::new));
|
||||
}
|
||||
|
||||
public FormatterBuilder tag(IFormatterTag... tags) {
|
||||
for (var tag : tags)
|
||||
tag(tag);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public FormatterBuilder tag(IFormatterTag tag) {
|
||||
if (tags == null)
|
||||
tags = new HashSet<>();
|
||||
|
||||
tags.add(tag);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public String format() {
|
||||
return format(content);
|
||||
}
|
||||
|
||||
public String format(IFormatterContext context) {
|
||||
return format(content, context);
|
||||
}
|
||||
|
||||
public String format(String input) {
|
||||
return format(input, null);
|
||||
}
|
||||
|
||||
public String format(String input, IFormatterContext context) {
|
||||
if (input == null || input.isBlank())
|
||||
input = content;
|
||||
if (input == null || input.isBlank())
|
||||
return "";
|
||||
|
||||
return TAG_PATTERN.matcher(input).replaceAll(matchResult -> {
|
||||
var tagName = matchResult.group(1).toLowerCase();
|
||||
var rawArgs = matchResult.group(2);
|
||||
var content = matchResult.group(3);
|
||||
|
||||
var tag = storage.get(tagName);
|
||||
if (tag == null)
|
||||
return matchResult.group(0);
|
||||
|
||||
var args = parseAttributes(rawArgs);
|
||||
if (content == null && args.containsKey(CONTENT_ATTR)) {
|
||||
content = args.get(CONTENT_ATTR);
|
||||
args.remove(CONTENT_ATTR);
|
||||
}
|
||||
|
||||
return tag.apply(content, args, context);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return format();
|
||||
}
|
||||
|
||||
private static Map<String, String> parseAttributes(String rawArgs) {
|
||||
Map<String, String> argsMap = new HashMap<>();
|
||||
if (rawArgs == null || rawArgs.isBlank())
|
||||
return argsMap;
|
||||
|
||||
var matcher = ATTR_PATTERN.matcher(rawArgs);
|
||||
|
||||
while (matcher.find())
|
||||
argsMap.put(matcher.group(1), matcher.group(2));
|
||||
|
||||
return argsMap;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package io.github.axolotsh.formatterlib;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import io.github.axolotsh.formatterlib.abstraction.IFormatter;
|
||||
import io.github.axolotsh.formatterlib.abstraction.IFormatterContext;
|
||||
import io.github.axolotsh.formatterlib.abstraction.IFormatterTag;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Getter
|
||||
@Accessors(chain = true, fluent = true)
|
||||
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
|
||||
public class ReplacementTag implements IFormatterTag {
|
||||
@EqualsAndHashCode.Include
|
||||
private String name;
|
||||
@Nullable
|
||||
private Function<IFormatterContext, String> replacement;
|
||||
|
||||
public ReplacementTag(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public ReplacementTag(String name, String replacement) {
|
||||
this(name, _ -> replacement);
|
||||
}
|
||||
|
||||
public ReplacementTag(String name, Function<IFormatterContext, String> replacement) {
|
||||
this.name = name;
|
||||
this.replacement = replacement;
|
||||
}
|
||||
|
||||
public ReplacementTag(String name, IFormatter formatter) {
|
||||
this.name = name;
|
||||
this.replacement = ctx -> {
|
||||
return formatter.format(formatter.content(), ctx);
|
||||
};
|
||||
}
|
||||
|
||||
public String apply(String content, Map<String, String> args, IFormatterContext context) {
|
||||
return replacement.apply(context);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package io.github.axolotsh.formatterlib;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import io.github.axolotsh.formatterlib.abstraction.IFormatterContext;
|
||||
import io.github.axolotsh.formatterlib.abstraction.IFormatterTag;
|
||||
|
||||
public class FormatterLibTest {
|
||||
private static class TestContext implements IFormatterContext {
|
||||
private final String value;
|
||||
|
||||
public TestContext(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSingleTagReplacement() {
|
||||
Formatter formatter = new Formatter("Hello <name/>!", new ReplacementTag("name", "World"));
|
||||
assertEquals("Hello World!", formatter.format());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleTagsReplacement() {
|
||||
Formatter formatter = new Formatter("<greeting/>, <name/>!",
|
||||
new ReplacementTag("greeting", "Hello"),
|
||||
new ReplacementTag("name", "John"));
|
||||
assertEquals("Hello, John!", formatter.format());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnregisteredTagIsIgnored() {
|
||||
Formatter formatter = new Formatter("Hello <unknown/>!");
|
||||
assertEquals("Hello <unknown/>!", formatter.format());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTagWithContentExtraction() {
|
||||
IFormatterTag echoContentTag = new IFormatterTag() {
|
||||
@Override
|
||||
public String name() {
|
||||
return "echo";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String apply(String content, Map<String, String> args, IFormatterContext context) {
|
||||
return "Echo: " + content;
|
||||
}
|
||||
};
|
||||
|
||||
Formatter formatter = new Formatter("<echo>some inner text</echo>", echoContentTag);
|
||||
assertEquals("Echo: some inner text", formatter.format());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTagWithAttributesExtraction() {
|
||||
IFormatterTag attrTag = new IFormatterTag() {
|
||||
@Override
|
||||
public String name() {
|
||||
return "user";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String apply(String content, Map<String, String> args, IFormatterContext context) {
|
||||
return args.get("firstname") + " " + args.get("lastname");
|
||||
}
|
||||
};
|
||||
|
||||
Formatter formatter = new Formatter("User is <user firstname=\"John\" lastname=\"Doe\"/>", attrTag);
|
||||
assertEquals("User is John Doe", formatter.format());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContentAttributeFallback() {
|
||||
IFormatterTag contentAttrTag = new IFormatterTag() {
|
||||
@Override
|
||||
public String name() {
|
||||
return "msg";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String apply(String content, Map<String, String> args, IFormatterContext context) {
|
||||
return "Message: " + content;
|
||||
}
|
||||
};
|
||||
|
||||
Formatter formatter = new Formatter("<msg content=\"Hello from attribute\"/>", contentAttrTag);
|
||||
assertEquals("Message: Hello from attribute", formatter.format());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContextPassing() {
|
||||
Formatter formatter = Formatter.builder()
|
||||
.content("Context value: <ctx/>")
|
||||
.tag("ctx", ctx -> {
|
||||
if (ctx instanceof TestContext testCtx) {
|
||||
return testCtx.getValue();
|
||||
}
|
||||
return "null";
|
||||
})
|
||||
.build();
|
||||
|
||||
TestContext context = new TestContext("42");
|
||||
assertEquals("Context value: 42", formatter.format(context));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNullOrBlankInputHandling() {
|
||||
Formatter formatter = new Formatter("Default <tag/>", new ReplacementTag("tag", "Content"));
|
||||
|
||||
assertEquals("Default Content", formatter.format((String) null));
|
||||
assertEquals("Default Content", formatter.format(" "));
|
||||
|
||||
Formatter emptyFormatter = new Formatter(null);
|
||||
assertEquals("", emptyFormatter.format((String) null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFormatterBuilder() {
|
||||
Formatter formatter = Formatter.builder()
|
||||
.content("Build: <a/> and <b/>")
|
||||
.tag("a", "Alpha")
|
||||
.tag(new ReplacementTag("b", "Beta"))
|
||||
.build();
|
||||
|
||||
assertNotNull(formatter.tags());
|
||||
assertEquals(2, formatter.tags().size());
|
||||
assertEquals("Build: Alpha and Beta", formatter.format());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedFormatterUsingBuilder() {
|
||||
Formatter innerFormatter = Formatter.builder()
|
||||
.content("Inner <x/>")
|
||||
.tag("x", "Value")
|
||||
.build();
|
||||
|
||||
Formatter outerFormatter = Formatter.builder()
|
||||
.content("Outer -> <inner/>")
|
||||
.tag("inner", innerFormatter)
|
||||
.build();
|
||||
|
||||
assertEquals("Outer -> Inner Value", outerFormatter.format());
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
plugins {
|
||||
id("java-library")
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("org.projectlombok:lombok:1.18.46")
|
||||
annotationProcessor("org.projectlombok:lombok:1.18.46")
|
||||
|
||||
compileOnly("org.jetbrains:annotations:26.1.0")
|
||||
|
||||
testImplementation(libs.junit.jupiter)
|
||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||
}
|
||||
|
||||
java {
|
||||
toolchain.languageVersion = JavaLanguageVersion.of(25)
|
||||
}
|
||||
|
||||
tasks.named<Test>("test") {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
rootProject.name = "FormatterLib"
|
||||
include("lib")
|
||||
include("formatterlib")
|
||||
include("formatterlib-abstraction")
|
||||
|
||||
Reference in New Issue
Block a user