Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@

**Note:** Entries marked with "🔥" indicate crucial or breaking changes that might affect your current setup.

## 3.3.0 Reference-based filtering and split rules

- ✨ Filter table notes by reference: only emit tables linked from included content (`onlyReferencedTables`); resolves #862
- ✨ Filter legendary groups by monster references; resolves #718
- ✨ Option to break rules into separate documents (`splitRules`); resolves #781
- 🐛 Use HTML to render complex tables; resolves #811
- 🐛 Resolve reprint aliases before adding spell references; resolves #857
- 🐛 Fix reprinted subclass spell list merging and wrong qualifiers; resolves #859
- 🐛 Resolve reprinted class source in subclass link generation; refs #779
- 🐛 Render psionic focus and modes in template; resolves #839
- 🐛 Fix image floating (center, left); resolves #853
- 🐛 Fix duplicated item properties
- 🐛 Remove length limit for captions; resolves #762

## 3.2.5 Races or species?

- ✨ Add option to emit races as species
Expand Down
16 changes: 16 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ This guide introduces you to configuring data transformations using the Command
- [Reprint behavior](#reprint-behavior)
- [Troubleshooting reprint behavior](#troubleshooting-reprint-behavior)
- [Races as species](#races-as-species)
- [Only emit referenced tables](#only-emit-referenced-tables)
- [Use the dice roller plugin](#use-the-dice-roller-plugin)
- [Render with Fantasy Statblocks](#render-with-fantasy-statblocks)
- [Tag prefix](#tag-prefix)
Expand Down Expand Up @@ -412,6 +413,21 @@ If you prefer the term "species" over "race" (as used in newer D&D editions), se

This changes the output directory from `races/` to `species/`, tags from `race/...` to `species/...`, and the CSS class from `json5e-race` to `json5e-species`. It does not affect internal indexing or source data processing.

## Only emit referenced tables

By default, the CLI emits all table notes from included sources. Set `onlyReferencedTables` to `true` to restrict output to tables that are actually linked from other included content (monsters, spells, adventures, etc.).

``` json
"onlyReferencedTables": true
```

When enabled, a table note is written only if:

- something in the rendered output links to it (via a `{@table}` tag or inline table matching), **or**
- it is explicitly named in an [`include` filter](#including-specific-content-with-include).

Unreferenced tables are logged as `(drop | unreferenced)` when run with `--log`.

## Use the dice roller plugin

The CLI can generate notes that include inline dice rolls. To enable this feature, set the `useDiceRoller` attribute to `true`.
Expand Down
3 changes: 3 additions & 0 deletions examples/config/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@
"type" : "string"
}
},
"onlyReferencedTables" : {
"type" : "boolean"
},
"paths" : {
"type" : "object",
"properties" : {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ static DiceRoller fromAttributes(Boolean useDiceRoller, Boolean yamlStatblocks)
ReprintBehavior reprintBehavior = ReprintBehavior.newest;
boolean racesAsSpecies = false;
boolean splitRules = false;
boolean onlyReferencedTables = false;
final Set<String> allowedSources = new HashSet<>();
final Set<String> includedKeys = new HashSet<>();
final Set<String> includedGroups = new HashSet<>();
Expand Down Expand Up @@ -135,6 +136,10 @@ public boolean splitRules() {
return splitRules;
}

public boolean onlyReferencedTables() {
return onlyReferencedTables;
}

public boolean allSources() {
return allSources;
}
Expand Down Expand Up @@ -442,6 +447,10 @@ private void readConfig(CompendiumConfig config, JsonNode node) {
if (input.splitRules != null && input.splitRules) {
config.splitRules = true;
}

if (input.onlyReferencedTables != null && input.onlyReferencedTables) {
config.onlyReferencedTables = true;
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions src/main/java/dev/ebullient/convert/config/UserConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public class UserConfig {
Boolean yamlStatblocks = null;
Boolean racesAsSpecies = null;
Boolean splitRules = null;
Boolean onlyReferencedTables = null;

String tagPrefix = "";

Expand Down Expand Up @@ -75,6 +76,7 @@ enum ConfigKeys {
sources(List.of("fullSource", "full-source", "convert")),
tagPrefix,
template,
onlyReferencedTables,
racesAsSpecies,
splitRules,
useDiceRoller,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ public static boolean isSrdBasicOnly() {
// Legendary group key -> monster keys that reference it
private final Map<String, Set<String>> legendaryGroupMonsters = new HashMap<>();

// Table keys that are actually linked from included (non-table) content
private final Set<String> referencedTableKeys = new HashSet<>();

private final Set<String> unresolvableKeys = new TreeSet<>();
private final Map<String, SkillOrAbility> resolvedSkills = new HashMap<>();

Expand Down Expand Up @@ -1234,6 +1237,14 @@ public boolean isExcluded(String key) {
return !isIncluded(key);
}

public void recordTableReference(String key) {
referencedTableKeys.add(key);
}

public boolean isTableReferenced(String key) {
return referencedTableKeys.contains(key);
}

public boolean differentSource(Tools5eSources sources, String source) {
String primarySource = sources == null ? null : sources.primarySource();
if (primarySource == null || source == null) {
Expand Down Expand Up @@ -1362,6 +1373,7 @@ public void cleanup() {
subraces.clear();
tableIndex.clear();
legendaryGroupMonsters.clear();
referencedTableKeys.clear();

if (filteredIndex != null) {
filteredIndex.clear();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,15 @@ private String createLink(String linkText, String key, Tools5eSources linkSource
case deity -> linkDeity(linkText, key);
case subclass -> linkSubclass(linkText, key);
case variantrule -> linkVariantRules(linkText, key);
case table, tableGroup -> {
if (index.isIncluded(key)) {
index.recordTableReference(key);
}
JsonNode node = index.getNode(key);
yield linkOrText(linkText, key,
getRelativePath(type),
fixFileName(decoratedName(type, node), linkSource.primarySource(), type));
}
default -> {
JsonNode node = index.getNode(key);
yield linkOrText(linkText, key,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,22 +60,24 @@ public Tools5eMarkdownConverter writeFiles(List<? extends IndexType> types) {
index.tui().verbosef("Converting data: %s", types);

WritingQueue queue = new WritingQueue();
for (var entry : index.includedEntries()) {
final String key = entry.getKey();
final JsonNode jsonSource = entry.getValue();

Tools5eIndexType nodeType = Tools5eIndexType.getTypeFromKey(key);
if (types.contains(Tools5eIndexType.race) && nodeType == Tools5eIndexType.subrace) {
// include subrace with race
} else if (!types.contains(nodeType)) {
continue;
}
boolean hasTables = types.stream()
.anyMatch(t -> t == Tools5eIndexType.table || t == Tools5eIndexType.tableGroup);
boolean hasNonTables = types.stream()
.anyMatch(t -> t != Tools5eIndexType.table && t != Tools5eIndexType.tableGroup);

if (nodeType.writeFile()) {
writeQuteBaseFiles(nodeType, key, jsonSource, queue);
} else if (nodeType.isOutputType() && nodeType.useQuteNote()) {
writeQuteNoteFiles(nodeType, key, jsonSource, queue);
}
if (hasTables && hasNonTables && index.cfg().onlyReferencedTables()) {
// Non-tables first: renders content and populates referencedTableKeys
_writeFiles(types.stream()
.filter(t -> t != Tools5eIndexType.table && t != Tools5eIndexType.tableGroup)
.toList(), queue, false);
// Tables second: only write those actually linked from included content
_writeFiles(types.stream()
.filter(t -> t == Tools5eIndexType.table || t == Tools5eIndexType.tableGroup)
.toList(), queue, true);
} else {
// Config not set, only tables, or only non-tables — single pass, no filtering
_writeFiles(types, queue, false);
}

writer.writeFiles(index.compendiumFilePath(), queue.baseCompendium);
Expand Down Expand Up @@ -105,6 +107,39 @@ public Tools5eMarkdownConverter writeFiles(List<? extends IndexType> types) {
return this;
}

private void _writeFiles(List<? extends IndexType> types, WritingQueue queue, boolean filterTables) {
for (var entry : index.includedEntries()) {
final String key = entry.getKey();
final JsonNode jsonSource = entry.getValue();

Tools5eIndexType nodeType = Tools5eIndexType.getTypeFromKey(key);
if (types.contains(Tools5eIndexType.race) && nodeType == Tools5eIndexType.subrace) {
// include subrace with race
} else if (!types.contains(nodeType)) {
continue;
}

if (nodeType.writeFile()) {
writeQuteBaseFiles(nodeType, key, jsonSource, queue);
} else if (nodeType.isOutputType() && nodeType.useQuteNote()) {
if (filterTables && (nodeType == Tools5eIndexType.table || nodeType == Tools5eIndexType.tableGroup)) {
Tools5eSources sources = Tools5eSources.findSources(key);
// Write if linked from rendered content, or explicitly targeted by a filter rule
boolean explicitlyIncluded = sources != null
&& sources.filterRuleApplied()
&& sources.includedByConfig();
if (index.isTableReferenced(key) || explicitlyIncluded) {
writeQuteNoteFiles(nodeType, key, jsonSource, queue);
} else {
index.tui().logf(Msg.FILTER, "(drop | unreferenced) %s", key);
}
} else {
writeQuteNoteFiles(nodeType, key, jsonSource, queue);
}
}
}
}

private void writeQuteBaseFiles(Tools5eIndexType type, String key, JsonNode jsonSource, WritingQueue queue) {
var compendium = queue.baseCompendium;
var rules = queue.baseRules;
Expand Down
3 changes: 2 additions & 1 deletion src/test/resources/5e/sample.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ sources:
- AAG
- AI
- BAM
- DMG
- DoD
- EGW
- FTD
Expand All @@ -42,6 +41,7 @@ sources:
- XGE
reference:
- AWM
- DMG
- EEPC
- ESK
- TftYP
Expand Down Expand Up @@ -91,3 +91,4 @@ images:

useDiceRoller: true
yamlStatblocks: false
onlyReferencedTables: true
Loading