Consolidate writer logic to a single package

This commit is contained in:
Andrew Miner
2025-10-09 18:15:27 -06:00
parent cc72fa1fbb
commit 320e9fb240
44 changed files with 758 additions and 230 deletions
-12
View File
@@ -1,12 +0,0 @@
from mcpacker.model.core.world import World
# Class ############################################################################################
class Writer:
def __init__(self, world:World):
self.world = world
def write(self):
raise NotImplementedError()
-7
View File
@@ -1,7 +0,0 @@
import mcpacker.emit.datapack.writer
# Tests ############################################################################################
def test_syntax():
pass
@@ -1,4 +0,0 @@
class SpawnerRuleEmitter:
def __init__(self):
pass
View File
-8
View File
@@ -1,8 +0,0 @@
import mcpacker.emit.markdown.mobreport
# Tests ############################################################################################
def test_syntax():
pass
View File
+41 -6
View File
@@ -5,6 +5,11 @@ from json import loads
from typing import TypeAlias from typing import TypeAlias
# Constants ########################################################################################
INDENT = " "
# Type Support ##################################################################################### # Type Support #####################################################################################
JsonBlob:TypeAlias = dict[str, "JsonBlob"] | list["JsonBlob"] | str | int | float | bool | None JsonBlob:TypeAlias = dict[str, "JsonBlob"] | list["JsonBlob"] | str | int | float | bool | None
@@ -13,12 +18,42 @@ JsonBlob:TypeAlias = dict[str, "JsonBlob"] | list["JsonBlob"] | str | int | floa
# Functions ######################################################################################## # Functions ########################################################################################
def removeNoneValues(blob:JsonBlob) -> JsonBlob: def removeNoneValues(blob:JsonBlob) -> JsonBlob:
if not isinstance(blob, dict): return blob if isinstance(blob, dict):
for key in list(blob.keys()):
if blob[key] == None:
del blob[key]
else:
blob[key] = removeNoneValues(blob[key])
for key in list(blob.keys()): if isinstance(blob, list):
if blob[key] == None: result:list[JsonBlob] = []
del blob[key] for value in blob:
else: if value != None:
removeNoneValues(blob[key]) result.append(removeNoneValues(value))
return result
return blob
def removeEmptyObjects(blob:JsonBlob) -> JsonBlob:
if isinstance(blob, dict):
for key in list(blob.keys()):
value = blob[key]
if isinstance(value, dict):
if len(value) == 0:
del blob[key]
else:
blob[key] = removeEmptyObjects(value)
return blob
if isinstance(blob, list):
result:list[JsonBlob] = []
for value in blob:
if isinstance(value, dict):
if len(value) > 0:
result.append(removeEmptyObjects(value))
return result
return blob return blob
+51
View File
@@ -0,0 +1,51 @@
from mcpacker.json import JsonBlob
from pytest import fixture
from typing import cast
import mcpacker.json as json
# Fixtures #########################################################################################
@fixture(name="blob")
def createBlob():
yield {
"alpha": "a",
"bravo": 12,
"charlie": True,
"delta": [
"foxtrot",
13,
False,
[ "golf" ],
{ "hotel": "india" },
None,
],
"juliette": {
"kilo": "lima",
"mike": {},
"november": None
},
"oscar": None,
"papa": [],
}
@fixture(name="noNone")
def removeNoneValues(blob:JsonBlob):
yield json.removeNoneValues(blob)
@fixture(name="noEmpty")
def remmoveEmptyObjects(blob:JsonBlob):
yield json.removeEmptyObjects(blob)
# Tests ############################################################################################
def test_removeNoneValues(noNone:JsonBlob):
assert len(cast(dict[str,list[str]], noNone)["delta"]) == 5
assert "november" not in cast(dict[str,dict[str,JsonBlob]], noNone)["juliette"]
assert "oscar" not in cast(dict[str,JsonBlob], noNone)
def test_removeEmptyObjects(noEmpty:JsonBlob):
assert "mike" not in cast(dict[str,dict[str,JsonBlob]], noEmpty)["juliette"]
assert "papa" in cast(dict[str,JsonBlob], noEmpty)
+1 -5
View File
@@ -7,8 +7,4 @@ from typing import Iterator
# Classes ########################################################################################## # Classes ##########################################################################################
class BiomeCatalog(Catalog[Biome]): class BiomeCatalog(Catalog[Biome]):
pass
def matching(self, biomeFilter:BiomeFilter) -> Iterator[Biome]:
for biome in self:
if biomeFilter.accepts(biome):
yield biome
@@ -43,4 +43,4 @@ def createCatalog():
# Tests ############################################################################################ # Tests ############################################################################################
def test_findJungles(catalog, jungles): def test_findJungles(catalog, jungles):
assert [b.city for b in catalog.matching(jungles)] == ["singapore"] assert [b.city for b in catalog.filter(lambda b: jungles.accepts(b))] == ["singapore"]
+3 -6
View File
@@ -12,15 +12,12 @@ class Mob:
""" """
def __init__(self, gameId:str, active:Active|Iterable[Active]=AC.DIURNAL): def __init__(self, gameId:str, active:Active|Iterable[Active]=AC.DIURNAL):
if isinstance(active, Active):
active = [active]
self.gameId = gameId self.gameId = gameId
self.active = active self.active = active
if not self.active:
self.active = AC.DIURNAL
if isinstance(self.active, Active):
self.active = (self.active,)
def __eq__(self, other) -> bool: def __eq__(self, other) -> bool:
if type(self) != type(other): return False if type(self) != type(other): return False
if self.gameId != other.gameId: return False if self.gameId != other.gameId: return False
+1 -1
View File
@@ -36,4 +36,4 @@ def test_str(squid):
assert str(squid) == "minecraft:squid" assert str(squid) == "minecraft:squid"
def test_repr(cow): def test_repr(cow):
assert repr(cow) == "Mob<minecraft:cow>{active:(Active<day>{start:0, end:12000},)}" assert repr(cow) == "Mob<minecraft:cow>{active:[Active<day>{start:0, end:12000}]}"
-38
View File
@@ -1,38 +0,0 @@
from mcpacker.json import JsonBlob
from mcpacker.model.incontrol.extracondition import ExtraCondition
from mcpacker.model.core.dimension import Dimension
import mcpacker.json as json
import mcpacker.model.core.altitude as A
import mcpacker.model.core.dimension as D
# Class ############################################################################################
class Condition:
def __init__(
self,
dimension:Dimension=D.OVERWORLD,
heightMin:int|None=None,
heightMax:int|None=None,
thisMax:int|None=None,
inWater:bool|None=None,
andCondition:ExtraCondition|None=None,
):
self.dimension = dimension
self.heightMin = heightMin
self.heightMax = heightMax
self.thisMax = thisMax
self.inWater = inWater
self.andCondition = andCondition
def asJsonBlob(self) -> JsonBlob:
return json.removeNoneValues({
"dimension": self.dimension.name,
"heightmin": self.heightMin,
"heightmax": self.heightMax,
"maxthis": self.thisMax,
"inwater": self.inWater,
"and": None if not self.andCondition else self.andCondition.asJsonBlob(),
})
@@ -1,44 +0,0 @@
from collections.abc import Iterable
from mcpacker.json import JsonBlob
import mcpacker.json as json
# Class ############################################################################################
class ExtraCondition:
def __init__(
self,
autumn:bool|None=None,
biome:Iterable[str]|None=None,
cave:bool|None=None,
seeSky:bool|None=None,
spring:bool|None=None,
summer:bool|None=None,
timeMax:int|None=None,
timeMin:int|None=None,
winter:bool|None=None,
):
self.autumn = autumn
self.biome = biome
self.cave = cave
self.seeSky = seeSky
self.spring = spring
self.summer = summer
self.timeMax = timeMax
self.timeMin = timeMin
self.winter = winter
def asJsonBlob(self) -> JsonBlob:
return json.removeNoneValues({
"autumn": self.autumn,
"biome": None if not self.biome else "[" + ", ".join(self.biome) + "]",
"cave": self.cave,
"seesky": self.seeSky,
"spring": self.spring,
"summer": self.summer,
"maxtime": self.timeMax,
"mintime": self.timeMin,
"winter": self.winter,
})
-41
View File
@@ -1,41 +0,0 @@
from mcpacker.json import JsonBlob
from mcpacker.model.core.fauna.mobspawn import MobSpawn
from mcpacker.model.incontrol.condition import Condition
# Class ############################################################################################
class SpawnerRule:
def __init__(
self,
mobGameId:str,
condition:Condition,
amountMin:int=1,
amountMax:int=1,
rate:float=1.0,
attempts:int=1,
weight:int=1,
):
self.mobGameId = mobGameId
self.amountMin = amountMin
self.amountMax = amountMax
self.rate = rate
self.attempts = attempts
self.weight = weight
self.condition = condition
def asJsonBlob(self) -> JsonBlob:
return {
"mob": [self.mobGameId],
"amount": {
"minimum": self.amountMin,
"maximum": self.amountMax,
"groupdistance": 4,
},
"persecond": self.rate,
"attempts": self.attempts,
"weights": [self.weight],
"conditions": self.condition.asJsonBlob(),
}
+7
View File
@@ -0,0 +1,7 @@
# Constants ########################################################################################
TICK = 1
SEC = 20 * TICK
MIN = 60 * SEC
HOUR = 60 * MIN
DAY = 24 * HOUR
@@ -1,4 +1,4 @@
import mcpacker.emit.modpack import mcpacker.model.time
# Tests ############################################################################################ # Tests ############################################################################################
+22 -12
View File
@@ -1,8 +1,11 @@
from mcpacker.emit.markdown.reportwriter import ReportWriter from mcpacker.model.modpack import ModPack
from mcpacker.emit.markdown.biomereport import BiomeReport from mcpacker.write.incontrol.spawnerwriter import SpawnerWriter
from mcpacker.emit.markdown.mineralreport import MineralReport from mcpacker.write.markdown.biomereport import BiomeReport
from mcpacker.emit.markdown.mobreport import MobReport from mcpacker.write.markdown.mineralreport import MineralReport
from mcpacker.model.modpack import ModPack from mcpacker.write.markdown.mobspawnreport import MobSpawnReport
from mcpacker.write.markdown.reportwriter import ReportWriter
from mcpacker.write.writer import CompositeWriter
from pathlib import Path
import inspect import inspect
import os import os
@@ -11,15 +14,16 @@ import sys
# Constants ######################################################################################## # Constants ########################################################################################
OUTPUT_PATH = "output" OUTPUT_PATH = Path("output")
# Class ############################################################################################ # Class ############################################################################################
class Runner: class Runner:
def __init__(self, pack:ModPack|None=None): def __init__(self, pack:ModPack|None=None, outputDir:Path=OUTPUT_PATH):
self.pack = pack or ModPack("untitled") self.pack = pack or ModPack("untitled")
self.outputDir = outputDir
def abort(self, message:str, status:int=-1): def abort(self, message:str, status:int=-1):
print(message) print(message)
@@ -40,9 +44,15 @@ class Runner:
# Commands ################################################################# # Commands #################################################################
def _command_writeReports(self): def _command_writeReports(self):
writer = ReportWriter([ writer = CompositeWriter(self.pack, self.outputDir, [
BiomeReport(self.pack), BiomeReport,
MineralReport(self.pack), MineralReport,
MobReport(self.pack), MobSpawnReport,
]) ])
writer.write(os.path.join(OUTPUT_PATH, self.pack.name, "markdown")) writer.write()
def _command_writeModPack(self):
writer = CompositeWriter(self.pack, self.outputDir, [
SpawnerWriter,
])
writer.write()
+94 -3
View File
@@ -1,8 +1,99 @@
import mcpacker.ui.runner from mcpacker.model.core.ecology.biome import Biome
from mcpacker.model.core.ecology.biomefilter import BiomeFilter as BF
from mcpacker.model.core.fauna.mob import Mob
from mcpacker.model.core.fauna.mobspawn import MobSpawn
from mcpacker.model.core.geology.mineral import Mineral
from mcpacker.model.core.geology.replacement import Replacement
from mcpacker.model.core.habitat import Habitat
from mcpacker.model.modpack import ModPack
from mcpacker.ui.runner import Runner
from mcpacker.write.incontrol.spawnerwriter import SpawnerWriter
from pathlib import Path
from pytest import fixture
import mcpacker.json as json
import mcpacker.model.core.altitude as AL
import mcpacker.model.core.ecology.flora as FL
import mcpacker.model.core.ecology.geology as GE
import mcpacker.model.core.ecology.heat as HE
import mcpacker.model.core.ecology.humidity as HU
import mcpacker.model.core.ecology.soil as SO
import mcpacker.model.core.ecology.water as WA
import mcpacker.model.core.fauna.active as AC
import mcpacker.model.core.fauna.group as GR
import mcpacker.model.core.fauna.location as LO
import mcpacker.model.core.scarcity as SC
import mcpacker.model.core.season as SE
import textwrap
# Fixtures #########################################################################################
@fixture(name="addBiomes")
def defineAddBiomes():
def addBiomes(pack:ModPack):
pack.world.biomes.add(Biome("singapore", "minecraft:jungle",
FL.CANOPY, GE.SEDIMENTARY, HE.TROPICAL, HU.WET, SO.ACIDIC, WA.INLAND
))
yield addBiomes
@fixture(name="addMobs")
def defineAddMobs():
def addMobs(pack:ModPack):
pack.world.mobs.add(Mob("minecraft:chicken", AC.DIURNAL))
yield addMobs
@fixture(name="addMobSpawns")
def defineAddMobSpawnss():
def addMobSpawns(pack:ModPack):
mobs = pack.world.mobs
spawns = pack.world.mobSpawns
pack.world.mobSpawns.add(
MobSpawn(mobs["minecraft:chicken"],
Habitat(
altitude = AL.span(AL.LOWLANDS, AL.UPLANDS),
biomeFilter = BF([HE.TROPICAL, HU.WET, FL.within(FL.CANOPY, FL.CLEARING)]),
seasons = SE.SUMMER,
group = GR.TROUP,
scarcity = SC.COMMON,
).derive(
seasons = SE.exclude(SE.SUMMER),
scarcity = SC.UNCOMMON
)
)
)
yield addMobSpawns
@fixture(name="pack")
def createPack(addBiomes, addMobs, addMobSpawns):
pack = ModPack("testModPack")
pack.augment(addBiomes)
pack.augment(addMobs)
pack.augment(addMobSpawns)
yield pack
@fixture(name="modPackRunner")
def createModPackRunner(tmp_path:Path):
runner = Runner(ModPack("testModPack"), tmp_path)
runner._command_writeModPack()
yield runner
@fixture(name="reportRunner")
def createReportRunner(tmp_path:Path):
runner = Runner(ModPack("testModPack"), tmp_path)
runner._command_writeReports()
yield runner
# Tests ############################################################################################ # Tests ############################################################################################
def test_syntax(): def test_writeModPack(tmp_path:Path, modPackRunner:Runner):
pass assert (tmp_path/"testModPack"/"config"/"incontrol"/"spawner.json").exists()
def test_writeReports(tmp_path:Path, reportRunner:Runner):
assert (tmp_path/"testModPack"/"reports"/"biomes.md").exists()
assert (tmp_path/"testModPack"/"reports"/"minerals.md").exists()
assert (tmp_path/"testModPack"/"reports"/"mobspawns.md").exists()
@@ -1,4 +1,4 @@
import mcpacker.emit.datapack.locator import mcpacker.write.datapack.locator
# Tests ############################################################################################ # Tests ############################################################################################
+7
View File
@@ -0,0 +1,7 @@
from pathlib import Path
# Constants ########################################################################################
INCONTROL_CONFIG_DIR = Path("config") / "incontrol"
+108
View File
@@ -0,0 +1,108 @@
from mcpacker.json import JsonBlob
from mcpacker.model.core.ecology.biomefilter import BiomeFilter
from mcpacker.model.core.fauna.active import Active
from mcpacker.model.core.fauna.mobspawn import MobSpawn
from mcpacker.model.core.habitat import Habitat
from mcpacker.model.core.scarcity import Scarcity
from mcpacker.model.core.season import Season
from mcpacker.model.modpack import ModPack
from mcpacker.write.incontrol import INCONTROL_CONFIG_DIR
from mcpacker.write.writer import Writer
from pathlib import Path
import mcpacker.json as json
import mcpacker.model.core.dimension as DI
import mcpacker.model.core.fauna.location as LO
import mcpacker.model.core.scarcity as SC
import mcpacker.model.time as TI
# Class ############################################################################################
class SpawnerWriter(Writer):
def __init__(self, pack:ModPack, outputDir:Path):
super().__init__(pack, outputDir)
def write(self):
path = self.outputDir / self.pack.name / INCONTROL_CONFIG_DIR / "spawner.json"
path.parent.mkdir(parents=True)
path.write_text(json.dumps(self._makeAllRules(), indent=json.INDENT))
# Private Functions ########################################################
def _computeBiomeNames(self, biomeFilter:BiomeFilter) -> JsonBlob:
return list(
b.gameId for b in self.pack.world.biomes.filter(
lambda b: biomeFilter.accepts(b)
)
)
def _computeSpawnRate(self, scarcity:Scarcity) -> float:
base = {
SC.ABSENT: 24 * TI.HOUR,
SC.RARE: 10 * TI.MIN,
SC.UNUSUAL: 5 * TI.MIN,
SC.SPARSE: 1 * TI.MIN,
SC.UNCOMMON: 15 * TI.SEC,
SC.COMMON: 5 * TI.SEC,
SC.CARPET: 1 * TI.SEC,
}[scarcity]
return 1.0 / (base / 20)
def _computeWeight(self, scarcity:Scarcity) -> int:
return {
SC.ABSENT: 0,
SC.RARE: 1,
SC.UNUSUAL: 2,
SC.SPARSE: 4,
SC.UNCOMMON: 8,
SC.COMMON: 16,
SC.CARPET: 32,
}[scarcity]
def _makeAndCondition(self, habitat:Habitat, season:Season, active:Active) -> JsonBlob:
return {
"biome": self._computeBiomeNames(habitat.biomeFilter),
season.name: True,
"seesky": True if LO.OUTSIDE == habitat.location else None,
"cave": True if LO.CAVE == habitat.location else None,
"mintime": active.start,
"maxtime": active.end,
}
def _makeAllRules(self) -> JsonBlob:
result:list[JsonBlob] = []
for spawn in self.pack.world.mobSpawns:
for habitat in spawn.habitats:
for season in habitat.seasons:
for active in spawn.mob.active:
result.append(self._makeRule(spawn, habitat, season, active))
return json.removeEmptyObjects(json.removeNoneValues(result))
def _makeCondition(self, habitat:Habitat, season:Season, active:Active) -> JsonBlob:
return {
"dimension": DI.OVERWORLD.name,
"minheight": habitat.altitude.bottom,
"maxheight": habitat.altitude.top,
"maxthis": habitat.group.largest * 2,
"inwater": LO.WATER == habitat.location,
"and": self._makeAndCondition(habitat, season, active),
}
def _makeRule(self, spawn:MobSpawn, habitat:Habitat, season:Season, active:Active,) -> JsonBlob:
return {
"mob": spawn.mob.gameId,
"amount": {
"minimum": habitat.group.smallest,
"maximum": habitat.group.largest,
"groupdistance": 4,
},
"persecond": self._computeSpawnRate(habitat.scarcity),
"attempts": habitat.group.largest * 4,
"weights": [ self._computeWeight(habitat.scarcity) ],
"conditions": self._makeCondition(habitat, season, active),
}
@@ -0,0 +1,105 @@
from mcpacker.model.core.ecology.biome import Biome
from mcpacker.model.core.ecology.biomefilter import BiomeFilter as BF
from mcpacker.model.core.fauna.mob import Mob
from mcpacker.model.core.fauna.mobspawn import MobSpawn
from mcpacker.model.core.geology.mineral import Mineral
from mcpacker.model.core.geology.replacement import Replacement
from mcpacker.model.core.habitat import Habitat
from mcpacker.model.modpack import ModPack
from mcpacker.write.incontrol.spawnerwriter import SpawnerWriter
from pathlib import Path
from pytest import fixture
import mcpacker.json as json
import mcpacker.model.core.altitude as AL
import mcpacker.model.core.ecology.flora as FL
import mcpacker.model.core.ecology.geology as GE
import mcpacker.model.core.ecology.heat as HE
import mcpacker.model.core.ecology.humidity as HU
import mcpacker.model.core.ecology.soil as SO
import mcpacker.model.core.ecology.water as WA
import mcpacker.model.core.fauna.active as AC
import mcpacker.model.core.fauna.group as GR
import mcpacker.model.core.fauna.location as LO
import mcpacker.model.core.scarcity as SC
import mcpacker.model.core.season as SE
import textwrap
# Fixtures #########################################################################################
@fixture(name="addBiomes")
def defineAddBiomes():
def addBiomes(pack:ModPack):
pack.world.biomes.add(Biome("singapore", "minecraft:jungle",
FL.CANOPY, GE.SEDIMENTARY, HE.TROPICAL, HU.WET, SO.ACIDIC, WA.INLAND
))
yield addBiomes
@fixture(name="addMobs")
def defineAddMobs():
def addMobs(pack:ModPack):
pack.world.mobs.add(Mob("minecraft:chicken", AC.DIURNAL))
yield addMobs
@fixture(name="addMobSpawns")
def defineAddMobSpawnss():
def addMobSpawns(pack:ModPack):
mobs = pack.world.mobs
spawns = pack.world.mobSpawns
pack.world.mobSpawns.add(
MobSpawn(mobs["minecraft:chicken"],
Habitat(
altitude = AL.span(AL.LOWLANDS, AL.UPLANDS),
biomeFilter = BF([HE.TROPICAL, HU.WET, FL.within(FL.CANOPY, FL.CLEARING)]),
seasons = SE.SUMMER,
group = GR.TROUP,
scarcity = SC.COMMON,
).derive(
seasons = SE.exclude(SE.SUMMER),
scarcity = SC.UNCOMMON
)
)
)
yield addMobSpawns
@fixture(name="pack")
def createPack(addBiomes, addMobs, addMobSpawns):
pack = ModPack("testModPack")
pack.augment(addBiomes)
pack.augment(addMobs)
pack.augment(addMobSpawns)
yield pack
@fixture(name="writer")
def createWriter(pack, tmp_path):
writer = SpawnerWriter(pack, tmp_path)
writer.write()
yield writer
# Tests ############################################################################################
def test_write(writer:SpawnerWriter, tmp_path:Path):
path = tmp_path / "testModPack" / "config" / "incontrol" / "spawner.json"
text = path.read_text()
data = json.loads(text)
assert len(data) == 4
rule = data[0]
assert rule["mob"] == "minecraft:chicken"
assert rule["amount"]["maximum"] == 6
assert abs(rule["persecond"] - 0.2) < 0.001
assert rule["conditions"]["and"]["biome"] == ["minecraft:jungle"]
assert rule["conditions"]["and"]["summer"]
rule = data[1]
assert abs(rule["persecond"] - 0.066) < 0.001
assert rule["conditions"]["and"]["spring"]
@@ -1,15 +1,16 @@
from mcpacker.emit.markdown.report import Report from mcpacker.write.markdown.markdownwriter import MarkdownWriter
from mcpacker.model.modpack import ModPack from mcpacker.model.modpack import ModPack
from pathlib import Path
# Class ############################################################################################ # Class ############################################################################################
class BiomeReport(Report): class BiomeReport(MarkdownWriter):
def __init__(self, pack:ModPack): def __init__(self, pack:ModPack, outputDir:Path):
super().__init__("biomes.md", pack) super().__init__("biomes.md", pack, outputDir)
def build(self): def compose(self):
for biome in self.pack.world.biomes: for biome in self.pack.world.biomes:
self.line(f"# Biome: {biome.gameId} ({biome.city})") self.line(f"# Biome: {biome.gameId} ({biome.city})")
self.line() self.line()
@@ -1,4 +1,4 @@
from mcpacker.emit.markdown.biomereport import BiomeReport from mcpacker.write.markdown.biomereport import BiomeReport
from mcpacker.model.core.ecology.biome import Biome from mcpacker.model.core.ecology.biome import Biome
from mcpacker.model.modpack import ModPack from mcpacker.model.modpack import ModPack
from pytest import fixture from pytest import fixture
@@ -33,20 +33,21 @@ def defineAddBiomes():
@fixture(name="pack") @fixture(name="pack")
def createPack(addBiomes): def createPack(addBiomes):
pack = ModPack("test") pack = ModPack("testModPack")
pack.augment(addBiomes) pack.augment(addBiomes)
yield pack yield pack
@fixture(name="report") @fixture(name="report")
def createReport(pack): def createReport(pack, tmp_path):
report = BiomeReport(pack) report = BiomeReport(pack, tmp_path)
report.build() report.write()
yield report yield report
# Tests ############################################################################################ # Tests ############################################################################################
def test_report(report): def test_report(tmp_path, report):
assert str(report) == textwrap.dedent(""" path = tmp_path / "testModPack" / "reports" / "biomes.md"
assert path.read_text() == textwrap.dedent("""
# Biome: minecraft:plains (kansascity) # Biome: minecraft:plains (kansascity)
* Flora: field * Flora: field
@@ -64,4 +65,5 @@ def test_report(report):
* Humidity: dry * Humidity: dry
* Soil: sandy * Soil: sandy
* Water: inland * Water: inland
""").strip()
""").lstrip()
+71
View File
@@ -0,0 +1,71 @@
from mcpacker.write.writer import Writer
from mcpacker.model.modpack import ModPack
from pathlib import Path
from typing import TextIO
import os
import shutil
# Constants ########################################################################################
INDENT = " "
REPORT_DIR_PATH = "reports"
# Class ############################################################################################
class MarkdownWriter(Writer):
def __init__(self, name:str, pack:ModPack, outputDir:Path):
super().__init__(pack, outputDir)
self._file:TextIO|None = None
self._name = name
self._indent = 0
self._lineBuffer:list[str] = []
def write(self):
path = self.outputDir / self.pack.name / REPORT_DIR_PATH / self._name
path.parent.mkdir(exist_ok=True, parents=True)
with path.open("w") as file:
try:
self._file = file
self.compose()
finally:
self._file = None
def compose(self) -> str:
raise NotImplementedError()
# Helper Methods ###########################################################
def indent(self) -> "MarkdownWriter":
self._indent += 1
self._lineBuffer.insert(0, INDENT)
return self
def line(self, text:str="") -> "MarkdownWriter":
assert self._file != None
self.text(text)
self._file.write("".join(self._lineBuffer) + "\n")
self._lineBuffer = []
while len(self._lineBuffer) < self._indent:
self._lineBuffer.append(INDENT)
return self
def outdent(self) -> "MarkdownWriter":
self._indent = max(0, self._indent - 1)
if self._lineBuffer:
if self._lineBuffer[0] == INDENT:
self._lineBuffer.pop()
return self
def text(self, text:str) -> "MarkdownWriter":
self._lineBuffer.append(str(text))
return self
@@ -0,0 +1,43 @@
from mcpacker.model.modpack import ModPack
from mcpacker.write.markdown.markdownwriter import MarkdownWriter
from pathlib import Path
from pytest import fixture
import textwrap
# Helpers ##########################################################################################
class SampleWriter(MarkdownWriter):
def compose(self):
(self.text("alpha")
.line("bravo")
.indent()
.line("charlie")
.indent()
.line("delta")
.outdent()
.line("echo")
)
# Fixtures #########################################################################################
@fixture(name="writer")
def createWriter(tmp_path:Path):
writer = SampleWriter("testReport.md", ModPack("testModPack"), tmp_path)
writer.write()
yield writer
# Tests ############################################################################################
def test_write(tmp_path, writer):
path = tmp_path / "testModPack" / "reports" / "testReport.md"
assert path.read_text() == textwrap.dedent("""
alphabravo
charlie
delta
echo
""").lstrip()
@@ -1,15 +1,16 @@
from mcpacker.emit.markdown.report import Report from mcpacker.write.markdown.markdownwriter import MarkdownWriter
from mcpacker.model.modpack import ModPack from mcpacker.model.modpack import ModPack
from pathlib import Path
# Class ############################################################################################ # Class ############################################################################################
class MineralReport(Report): class MineralReport(MarkdownWriter):
def __init__(self, pack:ModPack): def __init__(self, pack:ModPack, outputDir:Path):
super().__init__("minerals.md", pack) super().__init__("minerals.md", pack, outputDir)
def build(self): def compose(self):
for mineral in self.pack.world.minerals: for mineral in self.pack.world.minerals:
self.line(f"# Mineral: {mineral.name}") self.line(f"# Mineral: {mineral.name}")
self.line() self.line()
@@ -1,4 +1,4 @@
from mcpacker.emit.markdown.mineralreport import MineralReport from mcpacker.write.markdown.mineralreport import MineralReport
from mcpacker.model.core.geology.mineral import Mineral from mcpacker.model.core.geology.mineral import Mineral
from mcpacker.model.core.geology.replacement import Replacement from mcpacker.model.core.geology.replacement import Replacement
from mcpacker.model.modpack import ModPack from mcpacker.model.modpack import ModPack
@@ -26,21 +26,23 @@ def defineAddMinerals():
@fixture(name="pack") @fixture(name="pack")
def createPack(addMinerals): def createPack(addMinerals):
pack = ModPack("test") pack = ModPack("testModPack")
pack.augment(addMinerals) pack.augment(addMinerals)
yield pack yield pack
@fixture(name="report") @fixture(name="report")
def createReport(pack): def createReport(pack, tmp_path):
report = MineralReport(pack) report = MineralReport(pack, tmp_path)
report.build() report.write()
yield report yield report
# Tests ############################################################################################ # Tests ############################################################################################
def test_report(report): def test_report(tmp_path, report):
assert str(report) == textwrap.dedent(""" path = tmp_path / "testModPack" / "reports" / "minerals.md"
assert path.read_text() == textwrap.dedent("""
# Mineral: copper # Mineral: copper
* #minecraft:stone_replaceables => minecraft:copper_ore * #minecraft:stone_replaceables => minecraft:copper_ore
@@ -50,4 +52,5 @@ def test_report(report):
* #minecraft:stone_replaceables => minecraft:iron_ore * #minecraft:stone_replaceables => minecraft:iron_ore
* #minecraft:deepslate_replaceables => minecraft:deepslate_iron_ore * #minecraft:deepslate_replaceables => minecraft:deepslate_iron_ore
""").strip()
""").lstrip()
@@ -1,22 +1,23 @@
from mcpacker.emit.markdown.report import Report
from mcpacker.model.core.ecology.biometrait import BiomeTrait from mcpacker.model.core.ecology.biometrait import BiomeTrait
from mcpacker.model.modpack import ModPack from mcpacker.model.modpack import ModPack
from mcpacker.write.markdown.markdownwriter import MarkdownWriter
from pathlib import Path
# Class ############################################################################################ # Class ############################################################################################
class MobReport(Report): class MobSpawnReport(MarkdownWriter):
def __init__(self, pack:ModPack): def __init__(self, pack:ModPack, outputDir:Path):
super().__init__("mobs.md", pack) super().__init__("mobspawns.md", pack, outputDir)
def build(self): def compose(self):
for placement in self.pack.world.mobs: for spawn in self.pack.world.mobSpawns:
self.line(f"# Mob: {placement.gameId}") self.line(f"# Mob: {spawn.gameId}")
self.line() self.line()
self.indent() self.indent()
for index, habitat in enumerate(placement.habitats, start=1): for index, habitat in enumerate(spawn.habitats, start=1):
self.text("* habitat ").line(index) self.text("* habitat ").line(index)
self.indent() self.indent()
self.text("* altitude: ").line(habitat.altitude) self.text("* altitude: ").line(habitat.altitude)
@@ -0,0 +1,99 @@
from mcpacker.model.core.ecology.biomefilter import BiomeFilter as BF
from mcpacker.model.core.fauna.mob import Mob
from mcpacker.model.core.fauna.mobspawn import MobSpawn
from mcpacker.model.core.geology.mineral import Mineral
from mcpacker.model.core.geology.replacement import Replacement
from mcpacker.model.core.habitat import Habitat
from mcpacker.model.modpack import ModPack
from mcpacker.write.markdown.mobspawnreport import MobSpawnReport
from pytest import fixture
import mcpacker.model.core.altitude as AL
import mcpacker.model.core.ecology.flora as FL
import mcpacker.model.core.ecology.geology as GE
import mcpacker.model.core.ecology.heat as HE
import mcpacker.model.core.ecology.humidity as HU
import mcpacker.model.core.ecology.soil as SO
import mcpacker.model.core.ecology.water as WA
import mcpacker.model.core.fauna.active as AC
import mcpacker.model.core.fauna.group as GR
import mcpacker.model.core.fauna.location as LO
import mcpacker.model.core.scarcity as SC
import mcpacker.model.core.season as SE
import textwrap
# Fixtures #########################################################################################
@fixture(name="addMobs")
def defineAddMobs():
def addMobs(pack:ModPack):
pack.world.mobs.add(Mob("minecraft:chicken", AC.DIURNAL))
yield addMobs
@fixture(name="addMobSpawns")
def defineAddMobSpawnss():
def addMobSpawns(pack:ModPack):
mobs = pack.world.mobs
spawns = pack.world.mobSpawns
pack.world.mobSpawns.add(
MobSpawn(mobs["minecraft:chicken"],
Habitat(
altitude = AL.span(AL.LOWLANDS, AL.UPLANDS),
biomeFilter = BF([HE.TROPICAL, HU.WET, FL.within(FL.CANOPY, FL.CLEARING)]),
seasons = SE.SUMMER,
group = GR.TROUP,
scarcity = SC.COMMON,
).derive(
seasons = SE.exclude(SE.SUMMER),
scarcity = SC.UNCOMMON
)
)
)
yield addMobSpawns
@fixture(name="pack")
def createPack(addMobs, addMobSpawns):
pack = ModPack("testModPack")
pack.augment(addMobs)
pack.augment(addMobSpawns)
yield pack
@fixture(name="report")
def createReport(pack, tmp_path):
report = MobSpawnReport(pack, tmp_path)
report.write()
yield report
# Tests ############################################################################################
def test_write(report, tmp_path):
path = tmp_path / "testModPack" / "reports" / "mobspawns.md"
assert path.read_text() == textwrap.dedent("""
# Mob: minecraft:chicken
* habitat 1
* altitude: lowlands-uplands
* biomeFilter:
* Heat: tropical
* Humidity: wet
* Flora: any of: canopy, forest, clearing
* seasons: summer
* group: troup
* location: outside
* scarcity: common
* habitat 2
* altitude: lowlands-uplands
* biomeFilter:
* Heat: tropical
* Humidity: wet
* Flora: any of: canopy, forest, clearing
* seasons: spring, autumn, winter
* group: troup
* location: outside
* scarcity: uncommon
""").lstrip()
@@ -1,4 +1,4 @@
from mcpacker.emit.markdown.report import Report from mcpacker.write.markdown.report import Report
from pytest import fixture from pytest import fixture
@@ -1,4 +1,4 @@
from mcpacker.emit.markdown.report import Report from mcpacker.write.markdown.report import Report
import os import os
import shutil import shutil
@@ -1,4 +1,4 @@
import mcpacker.emit.markdown.reportwriter import mcpacker.write.markdown.reportwriter
# Tests ############################################################################################ # Tests ############################################################################################
@@ -1,4 +1,4 @@
import mcpacker.emit.resourcepack import mcpacker.write.resourcepack
# Tests ############################################################################################ # Tests ############################################################################################
+54
View File
@@ -0,0 +1,54 @@
from collections.abc import Iterable
from mcpacker.model.modpack import ModPack
from pathlib import Path
from typing import TypeVar
import os
import shutil
# Type Support #####################################################################################
WriterSubclass = TypeVar("WriterSubclass", bound="Writer")
# Class ############################################################################################
class Writer:
def __init__(self, pack:ModPack, outputDir:Path):
self.pack = pack
self.outputDir = outputDir
def resetOutputDir(self):
if self.outputDir.exists():
shutil.rmtree(self.outputDir)
self.outputDir.mkdir(parents=True)
def write(self):
raise NotImplementedError()
# Class ############################################################################################
class CompositeWriter(Writer):
def __init__(
self,
pack:ModPack,
outputDir:Path,
writers:Iterable[type[WriterSubclass]]|None=None
):
super().__init__(pack, outputDir)
self._writers:list[Writer] = []
for writerClass in (writers or []):
self.add(writerClass)
def add(self, writerClass:type[WriterSubclass]):
self._writers.append(writerClass(self.pack, self.outputDir))
def write(self):
for writer in self._writers:
writer.write()