Files
5etools-mirror-2.github.io/node/util-prettify-data.js
TheGiddyLimit d2f2daa34e v1.207.0
2024-05-21 22:49:09 +01:00

402 lines
8.7 KiB
JavaScript

import * as fs from "fs";
import * as ut from "./util.js";
import "../js/parser.js";
import "../js/utils.js";
import "../js/utils-proporder.js";
const FILE_BLOCKLIST = new Set([
"loot.json",
"msbcr.json",
"monsterfeatures.json",
"index.json",
"life.json",
"makecards.json",
"renderdemo.json",
"makebrew-creature.json",
"sources.json",
"fluff-index.json",
"changelog.json",
"index-meta.json",
"index-props.json",
"index-sources.json",
"index-timestamps.json",
"package.json",
"package-lock.json",
]);
const _FILE_PROP_ORDER = [
"$schema",
"_meta",
// region Player options
"class",
"foundryClass",
"classFluff",
"subclass",
"foundrySubclass",
"subclassFluff",
"classFeature",
"foundryClassFeature",
"subclassFeature",
"foundrySubclassFeature",
"optionalfeature",
"optionalfeatureFluff",
"foundryOptionalfeature",
"background",
"backgroundFeature",
"backgroundFluff",
"race",
"subrace",
"foundryRace",
"foundryRaceFeature",
"raceFluff",
"raceFluffMeta",
"feat",
"foundryFeat",
"featFluff",
"reward",
"rewardFluff",
"charoption",
"charoptionFluff",
// endregion
// region General entities
"spell",
"spellFluff",
"foundrySpell",
"spellList",
"baseitem",
"item",
"itemGroup",
"magicvariant",
"itemFluff",
"itemProperty",
"reducedItemProperty",
"itemType",
"itemTypeAdditionalEntries",
"reducedItemType",
"itemEntry",
"itemMastery",
"linkedLootTables",
"deck",
"card",
"deity",
"language",
"languageFluff",
// endregion
// region GM-specific
"monster",
"monsterFluff",
"foundryMonster",
"legendaryGroup",
"object",
"objectFluff",
"vehicle",
"vehicleUpgrade",
"vehicleFluff",
"cult",
"boon",
"trap",
"trapFluff",
"hazard",
"hazardFluff",
"encounter",
"name",
// endregion
// region Rules
"variantrule",
"table",
"condition",
"conditionFluff",
"disease",
"status",
"action",
"skill",
"sense",
"citation",
"adventure",
"adventureData",
"book",
"bookData",
// endregion
// region Other
"recipe",
"recipeFluff",
// endregion
// region Legacy content
"psionic",
"psionicDisciplineFocus",
"psionicDisciplineActive",
// endregion
// region Tooling
"makebrewCreatureTrait",
"makebrewCreatureAction",
"monsterfeatures",
// endregion
// region Roll20-specific
"roll20Spell",
// endregion
// region Non-brew data
"blocklist",
// endregion
];
const KEY_BLOCKLIST = new Set([
"data",
"itemTypeAdditionalEntries",
"itemType",
"itemProperty",
"itemEntry",
"raceFluffMeta",
"linkedLootTables",
]);
const PRIMITIVE_TYPES = new Set([
"boolean",
"number",
"string",
]);
const PROPS_TO_UNHANDLED_KEYS = {};
function getFnListSort (prop) {
switch (prop) {
case "spell":
case "roll20Spell":
case "foundrySpell":
case "spellList":
case "monster":
case "foundryMonster":
case "monsterFluff":
case "monsterTemplate":
case "makebrewCreatureTrait":
case "makebrewCreatureAction":
case "action":
case "foundryAction":
case "background":
case "legendaryGroup":
case "language":
case "languageScript":
case "name":
case "condition":
case "disease":
case "status":
case "cult":
case "boon":
case "feat":
case "foundryFeat":
case "vehicle":
case "vehicleUpgrade":
case "foundryVehicleUpgrade":
case "backgroundFluff":
case "featFluff":
case "optionalfeatureFluff":
case "conditionFluff":
case "spellFluff":
case "itemFluff":
case "languageFluff":
case "vehicleFluff":
case "objectFluff":
case "raceFluff":
case "item":
case "foundryItem":
case "baseitem":
case "magicvariant":
case "foundryMagicvariant":
case "itemGroup":
case "itemMastery":
case "object":
case "optionalfeature":
case "foundryOptionalfeature":
case "psionic":
case "reward":
case "foundryReward":
case "rewardFluff":
case "variantrule":
case "race":
case "foundryRace":
case "foundryRaceFeature":
case "table":
case "trap":
case "trapFluff":
case "hazard":
case "hazardFluff":
case "charoption":
case "charoptionFluff":
case "recipe":
case "recipeFluff":
case "sense":
case "skill":
case "deck":
case "citation":
case "foundryMap":
return SortUtil.ascSortGenericEntity.bind(SortUtil);
case "deity":
return SortUtil.ascSortDeity.bind(SortUtil);
case "card":
return SortUtil.ascSortCard.bind(SortUtil);
case "class":
case "classFluff":
case "foundryClass":
return (a, b) => SortUtil.ascSortDateString(Parser.sourceJsonToDate(b.source), Parser.sourceJsonToDate(a.source)) || SortUtil.ascSortLower(a.name, b.name) || SortUtil.ascSortLower(a.source, b.source);
case "subclass":
case "subclassFluff":
case "foundrySubclass":
return (a, b) => SortUtil.ascSortDateString(Parser.sourceJsonToDate(b.source), Parser.sourceJsonToDate(a.source)) || SortUtil.ascSortLower(a.name, b.name);
case "classFeature":
case "foundryClassFeature":
return (a, b) => SortUtil.ascSortLower(a.classSource, b.classSource)
|| SortUtil.ascSortLower(a.className, b.className)
|| SortUtil.ascSort(a.level, b.level)
|| SortUtil.ascSortLower(a.name, b.name)
|| SortUtil.ascSortLower(a.source, b.source);
case "subclassFeature":
case "foundrySubclassFeature":
return (a, b) => SortUtil.ascSortLower(a.classSource, b.classSource)
|| SortUtil.ascSortLower(a.className, b.className)
|| SortUtil.ascSortLower(a.subclassSource, b.subclassSource)
|| SortUtil.ascSortLower(a.subclassShortName, b.subclassShortName)
|| SortUtil.ascSort(a.level, b.level)
|| SortUtil.ascSort(a.header || 0, b.header || 0)
|| SortUtil.ascSortLower(a.name, b.name)
|| SortUtil.ascSortLower(a.source, b.source);
case "subrace": return (a, b) => SortUtil.ascSortLower(a.raceName, b.raceName)
|| SortUtil.ascSortLower(a.raceSource, b.raceSource)
|| SortUtil.ascSortLower(a.name || "", b.name || "")
|| SortUtil.ascSortLower(a.source, b.source);
case "encounter":
return SortUtil.ascSortEncounter.bind(SortUtil);
case "adventure": return SortUtil.ascSortAdventure.bind(SortUtil);
case "book": return SortUtil.ascSortBook.bind(SortUtil);
case "adventureData":
case "bookData":
return SortUtil.ascSortBookData.bind(SortUtil);
default: throw new Error(`Unhandled prop "${prop}"`);
}
}
export const getPrettified = (json, {isFoundryPrefixKeys = false} = {}) => {
let isModified = false;
// region Sort keys within entities
Object.entries(json)
.filter(([k]) => !KEY_BLOCKLIST.has(k))
.forEach(([k, v]) => {
if (v == null || PRIMITIVE_TYPES.has(typeof v)) return;
const kMod = isFoundryPrefixKeys && !k.startsWith("_") ? `foundry${k.uppercaseFirst()}` : k;
if (!PropOrder.hasOrder(kMod)) {
console.warn(`\t\tUnhandled property: "${kMod}"`);
return;
}
PROPS_TO_UNHANDLED_KEYS[kMod] = PROPS_TO_UNHANDLED_KEYS[kMod] || new Set();
if (json[k] instanceof Array) {
json[k] = v.map(it => PropOrder.getOrdered(
it,
kMod,
{
fnUnhandledKey: uk => {
PROPS_TO_UNHANDLED_KEYS[kMod].add(uk);
},
},
));
json[k].sort(getFnListSort(kMod));
} else {
json[k] = PropOrder.getOrdered(
v,
kMod,
{
fnUnhandledKey: uk => {
PROPS_TO_UNHANDLED_KEYS[kMod].add(uk);
},
},
);
}
isModified = true;
});
// endregion
// region Sort file-level properties
const keyOrder = Object.keys(json)
.sort((a, b) => {
const ixA = _FILE_PROP_ORDER.indexOf(a);
const ixB = _FILE_PROP_ORDER.indexOf(b);
return SortUtil.ascSort(~ixA ? ixA : Number.MAX_SAFE_INTEGER, ~ixB ? ixB : Number.MAX_SAFE_INTEGER);
});
const numUnhandledKeys = Object.keys(json).filter(it => !~_FILE_PROP_ORDER.indexOf(it));
if (numUnhandledKeys > 1) console.warn(`\t\tUnhandled file-level properties: "${numUnhandledKeys}"`);
if (!CollectionUtil.deepEquals(Object.keys(json), keyOrder)) {
const nxt = {};
keyOrder.forEach(k => nxt[k] = json[k]);
json = nxt;
isModified = true;
}
// endregion
return {json, isModified};
};
export const prettifyFile = file => {
console.log(`\tPrettifying ${file}...`);
const json = ut.readJson(file);
const {json: jsonPrettified, isModified} = getPrettified(
json,
{
isFoundryPrefixKeys: file.includes("foundry.json") || file.split("/").last().startsWith("foundry-"),
},
);
if (isModified) fs.writeFileSync(file, CleanUtil.getCleanJson(jsonPrettified), "utf-8");
};
export const prettifyFolder = folder => {
console.log(`Prettifying directory ${folder}...`);
const files = ut.listFiles({dir: folder});
files
.filter(file => file.endsWith(".json") && !FILE_BLOCKLIST.has(file.split("/").last()))
.forEach(file => prettifyFile(file));
Object.entries(PROPS_TO_UNHANDLED_KEYS)
.filter(([, set]) => set.size)
.forEach(([prop, set]) => {
console.warn(`Unhandled keys for data property "${prop}":`);
set.forEach(k => console.warn(`\t${k}`));
});
};