87 lines
5.5 KiB
Python
87 lines
5.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Compile/check the reviewed Markdown taxonomy; no downloads or generated recipes."""
|
|
import argparse,json,re
|
|
from pathlib import Path
|
|
ROOT=Path(__file__).resolve().parents[1]
|
|
DATA=ROOT/'mods/sanctuary/src/main/resources'
|
|
TARGET=DATA/'data/sanctuary/sanctuary_recipe_collections'
|
|
LEGACY=json.loads((ROOT/'scripts/data/collections-legacy-beta034.json').read_text())
|
|
# World-only blocks with an item ID do not become inventory discovery seeds.
|
|
WORLD_ONLY={'bedrock','end_portal_frame','reinforced_deepslate','spawner','trial_spawner','vault','budding_amethyst','suspicious_sand','suspicious_gravel','petrified_oak_slab','farmland','dirt_path'}
|
|
|
|
def sections(text,pattern):
|
|
parts=re.split(pattern,text,flags=re.M)
|
|
return {code:body for code,body in zip(parts[1::2],parts[2::2])}
|
|
def ids(text):return re.findall(r'`(minecraft:[a-z0-9_/.]+)`',text)
|
|
def catalogue():
|
|
design=(ROOT/'docs/collections-minecraft.md').read_text().split('## Correspondance des advancements')[0]
|
|
entries=sections(design,r'^### C(\d{2})\s*$')
|
|
inventory=sections((ROOT/'docs/collections-minecraft-inventaire.md').read_text(),r'^## C(\d{2})\s*$')
|
|
assert len(entries)==len(inventory)==67
|
|
all_recipes=[];all_members=[];result={};labels={'fr_fr':{},'en_us':{}}
|
|
for code,body in entries.items():
|
|
name=re.search(r'\*\*(.*?) · (.*?)\*\*',body)
|
|
cid=re.search(r'`(sanctuary:[a-z0-9_]+)`',body).group(1)
|
|
recipes_text,members_text=inventory[code].split('### Recettes',1)[1].split('### Blocs et objets',1)
|
|
recipes=re.findall(r'^\| `(minecraft:[^`]+)` \|',recipes_text,re.M)
|
|
rows=re.findall(r'^\| `(minecraft:[^`]+)` \| [^|]+ \| ([^|]+) \|',members_text,re.M)
|
|
all_recipes+=recipes;all_members.extend(i for i,k in rows)
|
|
blocks=[i for i,k in rows if k.strip().startswith('Bloc')]
|
|
items=[i for i,k in rows if k.strip() in ('Bloc et objet','Objet')]
|
|
triggers=[];adv=[]
|
|
for line in body.splitlines():
|
|
if line.startswith('**Amorces de découverte :**'):triggers+=ids(line)
|
|
if line.startswith('**Advancements qui ouvrent cette collection :**'):adv+=ids(line)
|
|
triggers=sorted(set(triggers+items)-{'minecraft:'+i for i in WORLD_ONLY})
|
|
triggers=[i for i in triggers if not i.startswith('minecraft:infested_')]
|
|
entry={'source':f'collections-minecraft.md#c{code}', 'items':triggers if code!='65' else [],'advancements':adv,'recipes':recipes,'members':{'blocks':blocks,'items':items}}
|
|
if code=='65':entry['operator_only']=True
|
|
short=cid.split(':')[1]
|
|
if short in LEGACY:
|
|
prior=LEGACY[short]
|
|
for key in ('item_tags','recipe_types'):
|
|
if prior.get(key):entry[key]=prior[key]
|
|
entry['additional_recipes']=prior.get('recipes',[])
|
|
result[short]=entry
|
|
for lang,value in zip(('fr_fr','en_us'),name.groups()):labels[lang]['sanctuary.recipes.collection.'+cid.replace(':','.')]=value
|
|
assert len(all_recipes)==len(set(all_recipes))==2042
|
|
assert len(all_members)==len(set(all_members))==1815
|
|
assert sum(len(e['members']['items']) for e in result.values())==1658
|
|
assert sum(len(e['members']['blocks']) for e in result.values())==1286
|
|
# Only existing vanilla recipes remain in compatibility additions; never derive suffixes.
|
|
for entry in result.values():
|
|
if 'additional_recipes' in entry:entry['additional_recipes']=sorted(set(entry['additional_recipes'])&set(all_recipes)-set(entry['recipes']))
|
|
# Cross-check the second, advancement-centric table, so neither direction can drift.
|
|
table=(ROOT/'docs/collections-minecraft.md').read_text().split('## Correspondance des advancements')[1].split('## Variantes et cas particuliers')[0]
|
|
mapping={ids(line)[0]:set(re.findall(r'\[C(\d{2})\]',line)) for line in table.splitlines() if line.startswith('|') and ids(line)}
|
|
assert len(mapping)==126
|
|
for adv,codes in mapping.items():assert codes=={e['source'][-2:] for e in result.values() if adv in e['advancements']},adv
|
|
# Explicit Sanctuary extensions keep the reviewed vanilla census intact.
|
|
additions=json.loads((ROOT/'scripts/data/collections-sanctuary.json').read_text())
|
|
for name,extra in additions.items():
|
|
assert name in result,name
|
|
for recipe in extra.get('additional_recipes',[]):
|
|
assert re.fullmatch(r'sanctuary:[a-z0-9_/]+',recipe),recipe
|
|
assert (DATA/'data/sanctuary/recipe'/(recipe.split(':')[1]+'.json')).is_file(),recipe
|
|
result[name]['additional_recipes']=sorted(set(result[name].get('additional_recipes',[])+extra.get('additional_recipes',[])))
|
|
result[name]['items']=sorted(set(result[name]['items']+extra.get('additional_items',[])))
|
|
for kind,values in extra.get('members',{}).items():
|
|
assert kind in ('blocks','items'),kind
|
|
for value in values:assert re.fullmatch(r'sanctuary:[a-z0-9_/]+',value),value
|
|
result[name]['members'][kind]=sorted(set(result[name]['members'][kind]+values))
|
|
return result,labels
|
|
|
|
def main():
|
|
parser=argparse.ArgumentParser();parser.add_argument('--write',action='store_true');args=parser.parse_args()
|
|
entries,labels=catalogue();TARGET.mkdir(parents=True,exist_ok=True)
|
|
for name,entry in entries.items():
|
|
path=TARGET/(name+'.json');expected=json.dumps(entry,ensure_ascii=False,indent=2)+'\n'
|
|
if args.write:path.write_text(expected)
|
|
else:assert path.read_text()==expected,f'Outdated collection: {path}'
|
|
for lang,names in labels.items():
|
|
path=DATA/f'assets/sanctuary/lang/{lang}.json';contents=json.loads(path.read_text())
|
|
if args.write:contents.update(names);path.write_text(json.dumps(contents,ensure_ascii=False,indent=2)+'\n')
|
|
else:assert all(contents.get(k)==v for k,v in names.items()),lang
|
|
print('Collections035: 67 families, 126 advancements, 2042 unique recipes, 1815 block/item IDs; Markdown, runtime JSON and FR/EN agree.')
|
|
if __name__=='__main__':main()
|