From af9e4a2da2c0246683ad5dfb1c6d9127499dc515 Mon Sep 17 00:00:00 2001 From: AXOLOTsh <96595812+AXOLOTsh@users.noreply.github.com> Date: Thu, 4 Jun 2026 19:24:07 +0300 Subject: [PATCH] feat: add project files --- formatterlib-abstraction/build.gradle.kts | 38 +++++ .../formatterlib/abstraction/IFormatter.java | 11 ++ .../abstraction/IFormatterContext.java | 5 + .../abstraction/IFormatterTag.java | 9 + formatterlib/build.gradle.kts | 58 +++++++ .../axolotsh/formatterlib/Formatter.java | 145 +++++++++++++++++ .../axolotsh/formatterlib/ReplacementTag.java | 47 ++++++ .../formatterlib/FormatterLibTest.java | 154 ++++++++++++++++++ lib/build.gradle.kts | 25 --- settings.gradle.kts | 3 +- 10 files changed, 469 insertions(+), 26 deletions(-) create mode 100644 formatterlib-abstraction/build.gradle.kts create mode 100644 formatterlib-abstraction/src/main/java/io/github/axolotsh/formatterlib/abstraction/IFormatter.java create mode 100755 formatterlib-abstraction/src/main/java/io/github/axolotsh/formatterlib/abstraction/IFormatterContext.java create mode 100755 formatterlib-abstraction/src/main/java/io/github/axolotsh/formatterlib/abstraction/IFormatterTag.java create mode 100644 formatterlib/build.gradle.kts create mode 100644 formatterlib/src/main/java/io/github/axolotsh/formatterlib/Formatter.java create mode 100755 formatterlib/src/main/java/io/github/axolotsh/formatterlib/ReplacementTag.java create mode 100644 formatterlib/src/test/java/io/github/axolotsh/formatterlib/FormatterLibTest.java delete mode 100644 lib/build.gradle.kts diff --git a/formatterlib-abstraction/build.gradle.kts b/formatterlib-abstraction/build.gradle.kts new file mode 100644 index 0000000..20aa057 --- /dev/null +++ b/formatterlib-abstraction/build.gradle.kts @@ -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("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" + } + } + } +} \ No newline at end of file diff --git a/formatterlib-abstraction/src/main/java/io/github/axolotsh/formatterlib/abstraction/IFormatter.java b/formatterlib-abstraction/src/main/java/io/github/axolotsh/formatterlib/abstraction/IFormatter.java new file mode 100644 index 0000000..f16a3e1 --- /dev/null +++ b/formatterlib-abstraction/src/main/java/io/github/axolotsh/formatterlib/abstraction/IFormatter.java @@ -0,0 +1,11 @@ +package io.github.axolotsh.formatterlib.abstraction; + +import java.util.Collection; + +public interface IFormatter { + public String content(); + + public Collection tags(); + + public String format(String input, IFormatterContext context); +} diff --git a/formatterlib-abstraction/src/main/java/io/github/axolotsh/formatterlib/abstraction/IFormatterContext.java b/formatterlib-abstraction/src/main/java/io/github/axolotsh/formatterlib/abstraction/IFormatterContext.java new file mode 100755 index 0000000..7764032 --- /dev/null +++ b/formatterlib-abstraction/src/main/java/io/github/axolotsh/formatterlib/abstraction/IFormatterContext.java @@ -0,0 +1,5 @@ +package io.github.axolotsh.formatterlib.abstraction; + +public interface IFormatterContext { + +} diff --git a/formatterlib-abstraction/src/main/java/io/github/axolotsh/formatterlib/abstraction/IFormatterTag.java b/formatterlib-abstraction/src/main/java/io/github/axolotsh/formatterlib/abstraction/IFormatterTag.java new file mode 100755 index 0000000..59ea97f --- /dev/null +++ b/formatterlib-abstraction/src/main/java/io/github/axolotsh/formatterlib/abstraction/IFormatterTag.java @@ -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 args, IFormatterContext context); +} diff --git a/formatterlib/build.gradle.kts b/formatterlib/build.gradle.kts new file mode 100644 index 0000000..0394454 --- /dev/null +++ b/formatterlib/build.gradle.kts @@ -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") { + useJUnitPlatform() +} + +publishing { + publications { + create("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" + } + } + } +} diff --git a/formatterlib/src/main/java/io/github/axolotsh/formatterlib/Formatter.java b/formatterlib/src/main/java/io/github/axolotsh/formatterlib/Formatter.java new file mode 100644 index 0000000..644839e --- /dev/null +++ b/formatterlib/src/main/java/io/github/axolotsh/formatterlib/Formatter.java @@ -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]*?)|>)"); + 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 tags; + private final Map storage = new HashMap<>(); + + public Formatter(String content, IFormatterTag... tags) { + this(content, Set.of(tags)); + } + + @Builder(toBuilder = true) + Formatter(String content, Collection 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 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 parseAttributes(String rawArgs) { + Map 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; + } +} diff --git a/formatterlib/src/main/java/io/github/axolotsh/formatterlib/ReplacementTag.java b/formatterlib/src/main/java/io/github/axolotsh/formatterlib/ReplacementTag.java new file mode 100755 index 0000000..4b75dc1 --- /dev/null +++ b/formatterlib/src/main/java/io/github/axolotsh/formatterlib/ReplacementTag.java @@ -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 replacement; + + public ReplacementTag(String name) { + this.name = name; + } + + public ReplacementTag(String name, String replacement) { + this(name, _ -> replacement); + } + + public ReplacementTag(String name, Function 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 args, IFormatterContext context) { + return replacement.apply(context); + } +} \ No newline at end of file diff --git a/formatterlib/src/test/java/io/github/axolotsh/formatterlib/FormatterLibTest.java b/formatterlib/src/test/java/io/github/axolotsh/formatterlib/FormatterLibTest.java new file mode 100644 index 0000000..1c4a100 --- /dev/null +++ b/formatterlib/src/test/java/io/github/axolotsh/formatterlib/FormatterLibTest.java @@ -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 !", new ReplacementTag("name", "World")); + assertEquals("Hello World!", formatter.format()); + } + + @Test + public void testMultipleTagsReplacement() { + Formatter formatter = new Formatter(", !", + new ReplacementTag("greeting", "Hello"), + new ReplacementTag("name", "John")); + assertEquals("Hello, John!", formatter.format()); + } + + @Test + public void testUnregisteredTagIsIgnored() { + Formatter formatter = new Formatter("Hello !"); + assertEquals("Hello !", formatter.format()); + } + + @Test + public void testTagWithContentExtraction() { + IFormatterTag echoContentTag = new IFormatterTag() { + @Override + public String name() { + return "echo"; + } + + @Override + public String apply(String content, Map args, IFormatterContext context) { + return "Echo: " + content; + } + }; + + Formatter formatter = new Formatter("some inner text", 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 args, IFormatterContext context) { + return args.get("firstname") + " " + args.get("lastname"); + } + }; + + Formatter formatter = new Formatter("User is ", 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 args, IFormatterContext context) { + return "Message: " + content; + } + }; + + Formatter formatter = new Formatter("", contentAttrTag); + assertEquals("Message: Hello from attribute", formatter.format()); + } + + @Test + public void testContextPassing() { + Formatter formatter = Formatter.builder() + .content("Context value: ") + .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 ", 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: and ") + .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 ") + .tag("x", "Value") + .build(); + + Formatter outerFormatter = Formatter.builder() + .content("Outer -> ") + .tag("inner", innerFormatter) + .build(); + + assertEquals("Outer -> Inner Value", outerFormatter.format()); + } +} \ No newline at end of file diff --git a/lib/build.gradle.kts b/lib/build.gradle.kts deleted file mode 100644 index 14096aa..0000000 --- a/lib/build.gradle.kts +++ /dev/null @@ -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") { - useJUnitPlatform() -} diff --git a/settings.gradle.kts b/settings.gradle.kts index bae0c90..0904c88 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,2 +1,3 @@ rootProject.name = "FormatterLib" -include("lib") +include("formatterlib") +include("formatterlib-abstraction")