Skip to content

AMD fact-sheet reader

Coerce a friendly Label: value fenced block into a dict — a companion parser for AMD documents.

Overview

AMD documents are #/## headings with --- fences (the same shape quests, clans, and universes are authored in). document_get_amd_file already parses the headings and fences; this module is the companion data_parser that turns a single fence's Label: value lines into a dict, with light value coercion:

Helper Coerces Example
amd_num int → float → str 88
amd_pct percent or number 40%0.4
amd_list comma list a, b["a", "b"]
amd_weighted name N weights by-the-book 40{"by_the_book": 40}
amd_makeup N% name / list / scalar 60% X, 40% Y{"X": 60, "Y": 40}
amd_coords first N ints 6, 4[6, 4]
amd_norm canonicalize a token By-The-Bookby_the_book

A fence that uses YAML flow ({ or [) is parsed as YAML instead, so YAML fences keep working through the same reader.

The domain interpretation — what "Yields" or "Values" means — is the caller's job, supplied via a handler(data, label, value) callback; this module stays content-agnostic (just parsing + coercion). See the Open Universe's universe_amd.py for a worked example.

Quick example

from sbs_utils.procedural.amd import amd_parse_facts, amd_list, amd_num

def handler(data, label, value):
    if label == "yields":
        data["yields"] = amd_list(value)
        return True          # consumed
    return False             # fall through to the default coercion

# fence_text is one `---` block: "Yields: ore 8\nReserve: 4000"
facts = amd_parse_facts(fence_text, handler=handler, default=amd_num)
# -> {"yields": ["ore 8"], "reserve": 4000}

handler receives the mutable data dict, so it can setdefault / nest / append freely; return truthy to consume a label, falsy to let default(value) handle it under amd_norm(label).

API

Friendly AMD fact-sheet fence reader.

A companion data_parser for procedural.quest.document_get_amd_file (which already parses the #/## headings and --- fences): this turns a single Label: value fenced block into a dict, with light value coercion - comma lists, "name N" weights, "name N%" makeup, coord pairs, percentages. A block that uses YAML flow ({ or [) is parsed as YAML instead, so YAML fences keep working through the same reader.

Domain label->key interpretation (what "flies" or "yields" means) is the CALLER's job, supplied via a handler callback - see the Open Universe's universe_amd.py for a worked example. This module stays content-agnostic: just parsing + coercion.

Dependency-light (only load_yaml_string) so it imports cleanly and is unit-testable outside the engine.

amd_coords(s, n=2)

'6, 4' -> [6, 4] (the first n signed-integer tokens).

amd_fact_lines(text)

Yield (label, value) per Label: value line - label lowercased, both stripped. Skips blanks, // comments, and lines without a colon.

amd_is_yaml_flow(text)

True when the fence should be parsed as YAML (contains '{' or '[').

amd_list(s)

Comma-split, trimmed, empties dropped.

amd_makeup(s)

'60% X, 40% Y' -> {X:60, Y:40}; 'X, Y' -> list; 'X' -> str. (Three shapes; the percent form keeps the original display casing of the key.)

amd_norm(name)

Canonicalize a token: lowercase, hyphens/spaces -> underscores.

amd_num(s)

int -> float -> the trimmed string, whichever parses first.

amd_parse_facts(text, handler=None, default=amd_num)

Parse a friendly fact-sheet fence into a dict.

If amd_is_yaml_flow(text), delegate to load_yaml_string. Otherwise, for each (label, value): call handler(data, label, value) when given, and if it returns a truthy value the label is consumed; otherwise fall back to data[amd_norm(label)] = default(value). The handler receives the mutable data dict so it can setdefault / nest / append freely. Returns data.

amd_pct(s)

'40%' -> 0.4; '0.4' -> 0.4; a bare number -> float; else the string.

amd_weighted(s)

'by-the-book 40, fearsome 30' -> {by_the_book: 40, fearsome: 30} (trailing integer is the weight; a bare name gets weight 0).