diff --git a/ChangeLog.md b/ChangeLog.md index 12fba308f..33110487f 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -12,6 +12,7 @@ All notable changes to this project will be documented in this file. ### Added +- function `str/align` - bytebuf allocation with random values - bytebuf allocation with a fill value - an example script comparing AES-256 CBC, AES-256 GCM, and AES-256 ZIP diff --git a/src/main/java/com/github/jlangch/venice/impl/docgen/cheatsheet/section/PrimitivesSection.java b/src/main/java/com/github/jlangch/venice/impl/docgen/cheatsheet/section/PrimitivesSection.java index 61b912458..41cff5c3a 100644 --- a/src/main/java/com/github/jlangch/venice/impl/docgen/cheatsheet/section/PrimitivesSection.java +++ b/src/main/java/com/github/jlangch/venice/impl/docgen/cheatsheet/section/PrimitivesSection.java @@ -196,6 +196,10 @@ public DocSection section() { trim.addItem(diBuilder.getDocItem("str/trim-left")); trim.addItem(diBuilder.getDocItem("str/trim-right")); + final DocSection align = new DocSection("Align", "primitives.strings.align"); + strings.addSection(align); + align.addItem(diBuilder.getDocItem("str/align")); + final DocSection hex = new DocSection("Hex", "primitives.strings.hex"); strings.addSection(hex); hex.addItem(diBuilder.getDocItem("str/hex-to-bytebuf")); diff --git a/src/main/java/com/github/jlangch/venice/impl/functions/StringFunctions.java b/src/main/java/com/github/jlangch/venice/impl/functions/StringFunctions.java index 9b91f9e4d..5774bfba7 100644 --- a/src/main/java/com/github/jlangch/venice/impl/functions/StringFunctions.java +++ b/src/main/java/com/github/jlangch/venice/impl/functions/StringFunctions.java @@ -303,6 +303,84 @@ else if (v1 != Nil || v2 != Nil) { private static final long serialVersionUID = -1848883965231344442L; }; + public static VncFunction str_align = + new VncFunction( + "str/align", + VncFunction + .meta() + .arglists( + "(str/align width align overflow-mode text)") + .doc( + "Aligns a text to width characters.\n\n" + + "align: :left, :center, :right\n\n" + + "overflow-mode: :clip-left, :clip-right, :ellipsis-left, :ellipsis-right") + .examples( + "(str/align 6 :left :clip-right \"abc\")", + "(str/align 6 :center :clip-right \"abc\")", + "(str/align 6 :right :clip-right \"abc\")", + "(str/align 6 :left :clip-left \"abcdefgh\")", + "(str/align 6 :left :ellipsis-left \"abcdefgh\")", + "(str/align 6 :left :ellipsis-right \"abcdefgh\")") + .seeAlso( + "str/trim-to-nil", "str/trim-left", "str/trim-right") + .build() + ) { + @Override + public VncVal apply(final VncList args) { + ArityExceptions.assertArity(this, args, 4); + + final int width = Coerce.toVncLong(args.first()).toJavaInteger(); + final String align = Coerce.toVncKeyword(args.second()).getSimpleName(); + final String overflow = Coerce.toVncKeyword(args.third()).getSimpleName(); + final String text = Coerce.toVncString(args.fourth()).getValue().trim(); + + final int textlen = text.length(); + + String justified; + + switch(overflow) { + case "clip-left": + justified = textlen >= width ? text.substring(textlen-width, textlen) : text; + break; + case "clip-right": + justified = textlen >= width ? text.substring(0, width) : text; + break; + case "ellipsis-left": + justified = textlen >= width-1 ? "…" + text.substring(textlen-width+1, textlen) : text; + break; + case "ellipsis-right": + justified = textlen >= width-1 ? text.substring(0, width-1) + "…" : text; + break; + default: + throw new VncException(String.format( + "Function 'str/align' got undefined overflow-mode :%s.", + overflow)); + } + + if (justified.length() < width) { + switch(align) { + case "left": + justified = StringUtil.padRight(justified, width); + break; + case "right": + justified = StringUtil.padLeft(justified, width); + break; + case "center": + justified = StringUtil.padCenter(justified, width); + break; + default: + throw new VncException(String.format( + "Function 'str/align' got invalid align mode :%s.", + align)); + } + } + + return new VncString(justified); + } + + private static final long serialVersionUID = -1848883965231344442L; + }; + public static VncFunction str_trim = new VncFunction( "str/trim", @@ -2617,6 +2695,7 @@ else if (Types.isVncSequence(locale)) { .add(str_trim_left) .add(str_trim_right) .add(str_trim_to_nil) + .add(str_align) .add(str_index_of) .add(str_last_index_of) .add(str_replace_first) diff --git a/src/main/java/com/github/jlangch/venice/impl/util/StringUtil.java b/src/main/java/com/github/jlangch/venice/impl/util/StringUtil.java index ec17c9489..f2898c0bc 100644 --- a/src/main/java/com/github/jlangch/venice/impl/util/StringUtil.java +++ b/src/main/java/com/github/jlangch/venice/impl/util/StringUtil.java @@ -548,9 +548,21 @@ public static String replaceLeadingSpaces(final String text, final char replaceC } } + public static String padLeft(final String s, final int len) { + final int padLen = len - s.length(); + return padLen > 0 ? repeat(' ', padLen) + s : s; + } + public static String padRight(final String s, final int len) { final int padLen = len - s.length(); - return padLen > 0 ? s + StringUtil.repeat(' ', padLen) : s; + return padLen > 0 ? s + repeat(' ', padLen) : s; + } + + public static String padCenter(final String s, final int len) { + final int padLen = len - s.length(); + final int padRight = padLen / 2; + final int padLeft = padLen - padRight; + return padLen > 0 ? repeat(' ', padLeft) + s + repeat(' ', padRight) : s; } public static String toEscapedUnicode(final char ch) { diff --git a/src/test/java/com/github/jlangch/venice/impl/functions/StringFunctionsTest.java b/src/test/java/com/github/jlangch/venice/impl/functions/StringFunctionsTest.java index c9eb6301b..3b93f7fa9 100644 --- a/src/test/java/com/github/jlangch/venice/impl/functions/StringFunctionsTest.java +++ b/src/test/java/com/github/jlangch/venice/impl/functions/StringFunctionsTest.java @@ -790,6 +790,49 @@ public void test_str_trim_right() { assertEquals("", venice.eval("(str/trim-right \" \f\t\r\n \")")); } + @Test + public void test_str_align() { + final Venice venice = new Venice(); + + assertEquals(" ", venice.eval("(str/align 6 :left :clip-right \"\")")); + assertEquals(" ", venice.eval("(str/align 6 :center :clip-right \"\")")); + assertEquals(" ", venice.eval("(str/align 6 :right :clip-right \"\")")); + + assertEquals("a ", venice.eval("(str/align 6 :left :clip-right \"a\")")); + assertEquals(" a ", venice.eval("(str/align 6 :center :clip-right \"a\")")); + assertEquals(" a", venice.eval("(str/align 6 :right :clip-right \"a\")")); + + assertEquals("ab ", venice.eval("(str/align 6 :left :clip-right \"ab\")")); + assertEquals(" ab ", venice.eval("(str/align 6 :center :clip-right \"ab\")")); + assertEquals(" ab", venice.eval("(str/align 6 :right :clip-right \"ab\")")); + + assertEquals("abc ", venice.eval("(str/align 6 :left :clip-right \"abc\")")); + assertEquals(" abc ", venice.eval("(str/align 6 :center :clip-right \"abc\")")); + assertEquals(" abc", venice.eval("(str/align 6 :right :clip-right \"abc\")")); + + assertEquals("abcd ", venice.eval("(str/align 6 :left :clip-right \"abcd\")")); + assertEquals(" abcd ", venice.eval("(str/align 6 :center :clip-right \"abcd\")")); + assertEquals(" abcd", venice.eval("(str/align 6 :right :clip-right \"abcd\")")); + + assertEquals("abcde ", venice.eval("(str/align 6 :left :clip-right \"abcde\")")); + assertEquals(" abcde", venice.eval("(str/align 6 :center :clip-right \"abcde\")")); + assertEquals(" abcde", venice.eval("(str/align 6 :right :clip-right \"abcde\")")); + + assertEquals("abcdef", venice.eval("(str/align 6 :left :clip-right \"abcdef\")")); + assertEquals("abcdef", venice.eval("(str/align 6 :center :clip-right \"abcdef\")")); + assertEquals("abcdef", venice.eval("(str/align 6 :right :clip-right \"abcdef\")")); + + assertEquals("abcdef", venice.eval("(str/align 6 :left :clip-right \"abcdefg\")")); + assertEquals("bcdefg", venice.eval("(str/align 6 :left :clip-left \"abcdefg\")")); + assertEquals("abcde…", venice.eval("(str/align 6 :left :ellipsis-right \"abcdefg\")")); + assertEquals("…cdefg", venice.eval("(str/align 6 :left :ellipsis-left \"abcdefg\")")); + + assertEquals("abcdef", venice.eval("(str/align 6 :left :clip-right \"abcdefgh\")")); + assertEquals("cdefgh", venice.eval("(str/align 6 :left :clip-left \"abcdefgh\")")); + assertEquals("abcde…", venice.eval("(str/align 6 :left :ellipsis-right \"abcdefgh\")")); + assertEquals("…defgh", venice.eval("(str/align 6 :left :ellipsis-left \"abcdefgh\")")); + } + @Test public void test_str_truncate() { final Venice venice = new Venice();