Skip to content

Commit 08709d1

Browse files
committed
🎨 ✨ Template methods for 2024 template
1 parent a693989 commit 08709d1

File tree

5 files changed

+292
-41
lines changed

5 files changed

+292
-41
lines changed

docs/templates/TtrpgTemplateExtension.md

Lines changed: 33 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,42 +6,67 @@ Use these functions to help render TTRPG data in Qute templates.
66

77
## Attributes
88

9-
[asBonus](#asbonus), [capitalized](#capitalized), [join](#join), [joinConjunct](#joinconjunct), [jsonString](#jsonstring), [pluralizeLabel](#pluralizelabel), [prefixSpace](#prefixspace)
9+
[asBonus](#asbonus), [capitalized](#capitalized), [capitalizedList](#capitalizedlist), [join](#join), [joinConjunct](#joinconjunct), [jsonString](#jsonstring), [lowercase](#lowercase), [pluralizeLabel](#pluralizelabel), [prefixSpace](#prefixspace), [uppercaseFirst](#uppercasefirst)
1010

1111
### asBonus
1212

13-
Return the value formatted with a bonus with a +/- prefix. Example: `{perception.asBonus}`
13+
Return the value formatted with a bonus with a +/- prefix.
14+
15+
Usage: `{perception.asBonus}`
1416

1517
### capitalized
1618

17-
Return the string capitalized. Example: `{resource.name.capitalized}`
19+
Return a Title Case form of this string, capitalizing the first word.
20+
Does not transform the contents of parenthesis (like markdown URLs).
21+
22+
Usage: `{resource.languages.capitalized}`
23+
24+
### capitalizedList
25+
26+
Return a capitalized form of this string, capitalizing the first word of each clause.
27+
Clauses are separated by commas or semicolons. Ignores conjunctions and parenthetical content.
28+
29+
Usage: `{resource.languages.capitalizedList}`
1830

1931
### join
2032

2133
Return the given collection converted into a string and joined using the specified joiner.
2234

23-
Example: `{resource.components.join(", ")}`
35+
Usage: `{resource.components.join(", ")}`
2436

2537
### joinConjunct
2638

2739
Return the given list joined into a single string, using a different delimiter for the last element.
2840

29-
Example: `{resource.components.joinConjunct(", ", " or ")}`
41+
Usage: `{resource.components.joinConjunct(", ", " or ")}`
3042

3143
### jsonString
3244

3345
Return the object as a JSON string
3446

35-
Example: `{resource.components.getJsonString(resource)}`
47+
Usage: `{resource.components.getJsonString(resource)}`
48+
49+
### lowercase
50+
51+
Return the lowercase form of this string.
52+
Does not transform the contents of parenthesis (like markdown URLs).
53+
54+
Usage: `{resource.name.lowercase}`
3655

3756
### pluralizeLabel
3857

3958
Return the string pluralized based on the size of the collection.
4059

41-
Example: `{resource.name.pluralized(resource.components)}`
60+
Usage: `{resource.name.pluralized(resource.components)}`
4261

4362
### prefixSpace
4463

4564
Return the given object as a string, with a space prepended if it's non-empty and non-null.
4665

47-
Example: `{resource.name.prefixSpace}`
66+
Usage: `{resource.name.prefixSpace}`
67+
68+
### uppercaseFirst
69+
70+
Return the text with a capitalized first letter (ignoring punctuation like '[')
71+
72+
Usage: `{resource.name.uppercaseFirst}`

examples/templates/tools5e/monster2md-2024.txt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ conditionImmunities:
2626
```ad-statblock
2727
title: {resource.name}{#if resource.token}
2828
![{resource.token.title}]({resource.token.vaultPath}#token){/if}
29-
*{resource.size} {resource.fullType}, {resource.alignment}*
29+
*{resource.size.capitalized} {resource.fullType.capitalized}, {resource.alignment.capitalized}*
3030

3131
- **Armor Class** {#if resource.ac }{resource.ac} {/if}{#if resource.acText }({resource.acText}){/if}
3232
- **Hit Points** {resource.hp} {#if resource.hitDice }({resource.hitDice}){/if} {#if resource.hpText }({resource.hpText}){/if}
@@ -47,21 +47,21 @@ title: {resource.name}{#if resource.token}
4747

4848

4949
- **Proficiency Bonus** {resource.pb}
50-
- **Saving Throws** {#if resource.savingThrows }{resource.savingThrows}{#else}⏤{/if}
51-
- **Skills** {#if resource.skills }{resource.skills}{#else}⏤{/if}
50+
- **Saving Throws** {#if resource.savingThrows }{resource.savingThrows.capitalizedList}{#else}⏤{/if}
51+
- **Skills** {#if resource.skills }{resource.skills.capitalizedList}{#else}⏤{/if}
5252
{#if resource.vulnerable }
53-
- **Damage Vulnerabilities** {resource.vulnerable}
53+
- **Damage Vulnerabilities** {resource.vulnerable.capitalizedList}
5454
{/if}{#if resource.resist}
55-
- **Damage Resistances** {resource.resist}
55+
- **Damage Resistances** {resource.resist.capitalizedList}
5656
{/if}{#if resource.immune}
57-
- **Damage Immunities** {resource.immune}
57+
- **Damage Immunities** {resource.immune.capitalizedList}
5858
{/if}{#if resource.conditionImmune}
59-
- **Condition Immunities** {resource.conditionImmune}
59+
- **Condition Immunities** {resource.conditionImmune.capitalizedList}
6060
{/if}{#if resource.gear}
6161
- **Gear** {resource.gear.join(", ")}
6262
{/if}
63-
- **Senses** {#if resource.senses }{resource.senses}, {/if}passive Perception {resource.passive}
64-
- **Languages** {#if resource.languages }{resource.languages}{#else}—{/if}
63+
- **Senses** {#if resource.senses }{resource.senses.capitalizedList}, {/if}Passive Perception {resource.passive}
64+
- **Languages** {#if resource.languages }{resource.languages.capitalizedList}{#else}—{/if}
6565
- **Challenge** {resource.cr}
6666
{#if resource.trait}
6767

src/main/java/dev/ebullient/convert/StringUtil.java

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@
1111
import java.util.function.BinaryOperator;
1212
import java.util.function.Function;
1313
import java.util.function.Supplier;
14+
import java.util.regex.Matcher;
15+
import java.util.regex.Pattern;
1416
import java.util.stream.Collector;
15-
import java.util.stream.Collectors;
1617

1718
/**
1819
* Holds common, generic string utiltity methods.
@@ -24,6 +25,12 @@
2425
* </p>
2526
*/
2627
public class StringUtil {
28+
final static Set<String> lowercaseWords = Set.of(
29+
"a", "an", "the", "at", "by", "for", "in", "of", "on", "to", "with",
30+
"but", "nor", "so", "yet", "and", "or");
31+
32+
final static Set<String> conjunctionArticle = Set.of(
33+
"a", "an", "the", "and", "or");
2734

2835
/**
2936
* Return {@code formatString} formatted with {@code o} as the first parameter.
@@ -44,7 +51,24 @@ public static String valueOrDefault(String[] parts, int index, String fallback)
4451
}
4552

4653
public static String uppercaseFirst(String value) {
47-
return value == null || value.isEmpty() ? value : Character.toUpperCase(value.charAt(0)) + value.substring(1);
54+
if (value == null || value.isEmpty()) {
55+
return value;
56+
}
57+
Pattern pattern = Pattern.compile("^(\\P{L}*)(.*)");
58+
Matcher matcher = pattern.matcher(value);
59+
60+
if (matcher.matches()) {
61+
String prefix = matcher.group(1); // Leading non-letters
62+
String rest = matcher.group(2); // Everything else
63+
64+
if (rest.isEmpty()) {
65+
return value; // No letters found
66+
}
67+
68+
return prefix + Character.toUpperCase(rest.charAt(0)) + rest.substring(1);
69+
}
70+
71+
return value;
4872
}
4973

5074
public static boolean equal(Object o1, Object o2) {
@@ -196,14 +220,43 @@ public static String joinConjunct(List<String> list, String joiner, String lastJ
196220

197221
/** Return the given text converted to title case, with the first letter of each word capitalized. */
198222
public static String toTitleCase(String text) {
223+
return toTitleCase(text, false);
224+
}
225+
226+
/**
227+
* Return the given text converted to title case, with the first letter of each word capitalized.
228+
*
229+
* @param text The text to convert to title case
230+
* @param midClause If true, then don't capitalize conjunctions and articles in the middle of a clause.
231+
*/
232+
public static String toTitleCase(String text, boolean midClause) {
199233
if (text == null || text.isEmpty()) {
200234
return text;
201235
}
202-
return Arrays.stream(text.split(" "))
203-
.map(word -> word.isEmpty()
204-
? word
205-
: Character.toTitleCase(word.charAt(0)) + word.substring(1).toLowerCase())
206-
.collect(Collectors.joining(" "));
236+
String[] words = text.split(" ", -1);
237+
for (int i = 0; i < words.length; i++) {
238+
String word = words[i];
239+
if (word.isEmpty() || word.matches("\\P{L}+")) {
240+
continue; // Skip empty words
241+
}
242+
var lowerWord = word.toLowerCase();
243+
244+
if (midClause && conjunctionArticle.contains(lowerWord)) {
245+
// Don't capitalize conjunctions
246+
words[i] = lowerWord;
247+
} else if (i == 0 || i == words.length - 1 || !lowercaseWords.contains(lowerWord)) {
248+
// // Capitalize; Handle markdown link display text: [word] -> [Word]
249+
// if (word.startsWith("[") && word.length() > 1) {
250+
// words[i] = "[" + Character.toTitleCase(word.charAt(1)) + word.substring(2).toLowerCase();
251+
// } else {
252+
// words[i] = Character.toTitleCase(word.charAt(0)) + word.substring(1).toLowerCase();
253+
// }
254+
words[i] = uppercaseFirst(word);
255+
} else {
256+
words[i] = lowerWord;
257+
}
258+
}
259+
return String.join(" ", words);
207260
}
208261

209262
/** Returns true if the given string is non-null and non-blank. */

0 commit comments

Comments
 (0)