pub struct Document { /* private fields */ }Expand description
A fully-parsed Quillmark document. Serde routes through StoredDocument;
for the plate wire shape see Document::to_plate_json.
Parse-time warnings are not document state — they ride out-of-band on
Parsed from Document::parse, the single owner. Equality and the
storage DTO therefore cover only structural content (main and cards).
Implementations§
Source§impl Document
impl Document
pub fn set_quill_ref(&mut self, reference: QuillReference)
pub fn card_mut(&mut self, index: usize) -> Option<&mut Card>
Sourcepub fn push_card(&mut self, card: Card) -> Result<(), EditError>
pub fn push_card(&mut self, card: Card) -> Result<(), EditError>
Append a composable card. Its $kind must be a valid, non-reserved
composable kind (EditError::InvalidKindName /
EditError::ReservedKind otherwise) — the invariant for any card in
the cards list, enforced here so every entry path shares it.
Sourcepub fn insert_card(&mut self, index: usize, card: Card) -> Result<(), EditError>
pub fn insert_card(&mut self, index: usize, card: Card) -> Result<(), EditError>
Insert a composable card at index (index > len →
EditError::IndexOutOfRange; invalid $kind →
EditError::InvalidKindName / EditError::ReservedKind).
pub fn remove_card(&mut self, index: usize) -> Option<Card>
Sourcepub fn set_card_kind(
&mut self,
index: usize,
new_kind: impl Into<String>,
) -> Result<(), EditError>
pub fn set_card_kind( &mut self, index: usize, new_kind: impl Into<String>, ) -> Result<(), EditError>
Replace the $kind of the composable card at index.
Only the $kind metadata changes; the payload and body are untouched
(field-bag semantics). Old-schema fields linger in the bag; new-schema
fields are absent until set explicitly. Schema migration is the caller’s
responsibility — this is a structural primitive.
Returns EditError::IndexOutOfRange, EditError::InvalidKindName,
or EditError::ReservedKind on constraint violations.
Source§impl Document
impl Document
Sourcepub fn to_markdown(&self) -> String
pub fn to_markdown(&self) -> String
Emit canonical Quillmark Markdown from this document.
§Contract
-
Type-fidelity round-trip.
Document::parse(&doc.to_markdown())returns aDocumentequal todocby value and by type variant.QuillValue::String("on")round-trips as a string, never as a bool.QuillValue::String("01234")round-trips as a string, never as an integer. This guarantee is the whole point of owning emission.Content-field carve-out. A richtext field committed as a canonical content object (and the card
$body) is intentionally markdown-lossy on markdown emit: it projects to its markdown form (project_content_field), so identity marks (anchors, island ids) and content-only marks (underline) do not survive ato_markdown→from_markdownround-trip. On-disk identity is markdown-lossy by design; the storage DTO is the lossless carrier. The value-equality guarantee above holds for every field the writer did not commit as canonical content. -
Emit-idempotent.
to_markdownis a pure function ofdoc; two calls on the samedocreturn byte-equal strings.
Byte-equality with the original source is not guaranteed.
§Emission rules (§9)
- Line endings:
\nonly. CRLF normalization happens on import. - Every block is emitted as a
~~~card-yaml fence: a bare~~~opener, the$-prefixed system-metadata lines ($quill: <ref>for the root block,$kind: <kind>for composable cards) leading the YAML payload, the user-defined data fields, then a closing~~~. - Cards: one blank line before each, then the block, then the card body.
- Body: emitted verbatim after the root block (and after each card).
- Mappings and sequences: block style at every nesting level.
- Scalars (booleans, null, numbers, strings): delegated to
serde-saphyr, which emits the type-canonical form (true/false,null, bare numeric literal) and quotes strings only when the unquoted form would be misread (on/yes/off,null/~, numeric-looking strings, leading flow indicators,:runs, …). Quoting form is not stable — what matters is that the emitted scalar round-trips to the sameQuillValuevariant. This is the type-fidelity guarantee. - Multi-line strings: emitted as inline double-quoted scalars with
\nescapes; no|/>block forms.
§Design notes
-
Nested-map order.
QuillValueis backed byserde_json::Valuewhose object type (serde_json::Map) preserves insertion order when theserde_json/preserve_orderfeature is enabled (it is in this workspace). Insertion order is therefore preserved for nested maps at emit time. -
Empty containers.
- Empty object (
{}) → the key is omitted from emit entirely. - Empty array (
[]) → emitted askey: []\n.
- Empty object (
§What is preserved
- YAML comments: own-line and inline trailing comments round-trip at their source position. Comments whose host disappears at emit time (empty-mapping omission, programmatic field removal) degrade to own-line comments at the same indent so the comment text is preserved even when its position shifts.
!must_filltags: round-trip via thefillflag onPayloadItem::Field.
§What is lost
- Other custom tags (
!include,!env, …): the tag is dropped; the scalar value is preserved. - Original quoting style: strings are re-emitted in saphyr’s canonical form (plain when safe, quoted when ambiguous). The form chosen for emit may not match the form in the source.
Source§impl Document
impl Document
Sourcepub fn new(quill: QuillReference) -> Self
pub fn new(quill: QuillReference) -> Self
Create a blank document: a main card carrying only $quill, an empty
body, and no composable cards. The programmatic blank canvas — every
schema field is absent and resolves at render time (default, else
type-empty zero), so nothing the caller did not set reaches the
output. For an example-filled starter shaped like the blueprint, use
Quill::seed_document.
Sourcepub fn from_main_and_cards(main: Card, cards: Vec<Card>) -> Self
pub fn from_main_and_cards(main: Card, cards: Vec<Card>) -> Self
Create a Document from a pre-built main card and composable cards.
main must carry $quill; composable cards must not.
Sourcepub fn parse(markdown: &str) -> Result<Parsed, ParseError>
pub fn parse(markdown: &str) -> Result<Parsed, ParseError>
Parse card-yaml Markdown into a Parsed — the Document plus any
non-fatal warnings. The single parse entry; a caller that wants only the
document writes Document::parse(md)?.document. Errors on malformed
YAML, a missing root $quill, an over-size input, and the other
ParseError variants.
pub fn main(&self) -> &Card
pub fn main_mut(&mut self) -> &mut Card
Sourcepub fn quill_reference(&self) -> QuillReference
pub fn quill_reference(&self) -> QuillReference
The $quill reference from the root block. Always present on parsed documents.
pub fn cards(&self) -> &[Card]
pub fn cards_mut(&mut self) -> &mut [Card]
Sourcepub fn find_card(&self, id: &str) -> Option<(usize, &Card)>
pub fn find_card(&self, id: &str) -> Option<(usize, &Card)>
The first composable card whose $id equals id, with its index —
resolving the canonical durable address (PROGRAMMATIC.md) without a
hand-rolled scan over cards. $id is non-unique by
design, so this returns the first match; None when no card carries it.
Sourcepub fn to_plate_json(&self) -> Value
pub fn to_plate_json(&self) -> Value
Serialize to the JSON wire shape consumed by backend plates. This is
the only place in quillmark-core that produces this shape:
{
"$quill": "<ref>",
"$body": { "text": "…", "lines": [...], "marks": [...], "islands": [...] },
"$cards": [{ "$kind": "<tag>", "$body": <content>, "<field>": <value>, ... }],
"<field>": <value>, ...
}$body (global and per-card) is canonical Content-JSON — the content as
a nested object, not a markdown string. Richtext payload fields likewise
cross as content objects (committed at coercion time).
$-prefixed keys carry document-level metadata (quill ref, body
text, card list, card kind). User payload fields stay flat at the
root — they cannot collide with $ keys because user field names are
never $-prefixed (they match [A-Za-z_][A-Za-z0-9_]*).