import type { FormatCodec } from "../format-codec.js"; /** * A segment in a compiled prose template. * Either a literal text segment or a field placeholder. */ export type ProseSegment = { readonly type: "literal"; readonly text: string; } | { readonly type: "field"; readonly name: string; }; /** * A compiled template ready for encoding/decoding. * Contains the ordered list of segments and extracted field names. */ export interface CompiledTemplate { readonly segments: ReadonlyArray; readonly fields: ReadonlyArray; } /** * Options for creating a prose codec. */ export interface ProseCodecOptions { /** The headline template with {fieldName} placeholders. Optional — if omitted, the template is learned from the first decoded .prose file's @prose directive. */ readonly template?: string; /** Optional overflow templates for additional fields on indented lines */ readonly overflow?: ReadonlyArray; } /** * Compiles a template string into an ordered list of segments. * Parses `{fieldName}` placeholders and literal text into segments. * * @param template - The template string with {fieldName} placeholders * @returns A CompiledTemplate with segments and field names * * @example * ```typescript * const compiled = compileTemplate('#{id} "{title}" by {author}') * // compiled.segments = [ * // { type: "literal", text: "#" }, * // { type: "field", name: "id" }, * // { type: "literal", text: ' "' }, * // { type: "field", name: "title" }, * // { type: "literal", text: '" by ' }, * // { type: "field", name: "author" }, * // ] * // compiled.fields = ["id", "title", "author"] * ``` */ export declare const compileTemplate: (template: string) => CompiledTemplate; /** * Compiles an array of overflow template strings into CompiledTemplates. * Each overflow template follows the same {fieldName} placeholder syntax as the headline template. * * @param overflow - Optional array of overflow template strings * @returns An array of CompiledTemplate objects, or empty array if no overflow templates * * @example * ```typescript * const compiled = compileOverflowTemplates(['tagged {tags}', '~ {description}']) * // compiled[0].segments = [ * // { type: "literal", text: "tagged " }, * // { type: "field", name: "tags" }, * // ] * // compiled[0].fields = ["tags"] * // compiled[1].segments = [ * // { type: "literal", text: "~ " }, * // { type: "field", name: "description" }, * // ] * // compiled[1].fields = ["description"] * ``` */ export declare const compileOverflowTemplates: (overflow: ReadonlyArray | undefined) => ReadonlyArray; /** * Serializes a value to its prose format string representation. * * Type mapping: * - null/undefined → `~` * - boolean → `true` / `false` * - number → digit characters (e.g., `42`, `-3.14`) * - array → `[a, b, c]` with element quoting for `,` and `]` * - string → bare text (quoting for delimiters handled by encodeHeadline) * * @param value - The value to serialize * @returns The serialized string representation * * @example * ```typescript * serializeValue(42) // "42" * serializeValue(true) // "true" * serializeValue(null) // "~" * serializeValue("hello") // "hello" * serializeValue(["a", "b"]) // "[a, b]" * ``` */ export declare const serializeValue: (value: unknown) => string; /** * Deserializes a prose format string back to its typed value. * Uses heuristic type detection: * - Numbers: matches `/^-?\d+(\.\d+)?$/` * - Booleans: exact match `true` or `false` * - Null: exact match `~` * - Arrays: starts with `[`, ends with `]` * - Strings: default (anything not matching above) * * @param text - The serialized string to deserialize * @returns The deserialized value with its inferred type * * @example * ```typescript * deserializeValue("42") // 42 (number) * deserializeValue("-3.14") // -3.14 (number) * deserializeValue("true") // true (boolean) * deserializeValue("false") // false (boolean) * deserializeValue("~") // null * deserializeValue("[a, b, c]") // ["a", "b", "c"] (array) * deserializeValue("hello") // "hello" (string) * ``` */ export declare const deserializeValue: (text: string) => unknown; /** * Encodes a record into a headline string using a compiled template. * Substitutes field values into the template, emitting literals verbatim. * For non-last fields, if the serialized value contains the next literal * delimiter, the value is quoted to prevent parsing ambiguity. * * @param record - The record object with field values * @param template - The compiled template with segments and fields * @returns The encoded headline string * * @example * ```typescript * const template = compileTemplate('#{id} "{title}" by {author}') * const record = { id: "1", title: "Dune", author: "Frank Herbert" } * encodeHeadline(record, template) * // → '#1 "Dune" by Frank Herbert' * * // When value contains the next delimiter: * const record2 = { id: "1", title: 'Say "hello"', author: "Test" } * encodeHeadline(record2, template) * // → '#1 "Say \"hello\"" by Test' * ``` */ export declare const encodeHeadline: (record: Record, template: CompiledTemplate) => string; /** * Decodes a headline string back to a record using a compiled template. * Performs a left-to-right scan matching literals and capturing field text between them. * Returns null if the line doesn't match the template structure. * * @param line - The headline string to decode * @param template - The compiled template with segments and fields * @returns The decoded record object, or null if the line doesn't match * * @example * ```typescript * const template = compileTemplate('#{id} "{title}" by {author}') * decodeHeadline('#1 "Dune" by Frank Herbert', template) * // → { id: "1", title: "Dune", author: "Frank Herbert" } * * decodeHeadline('This does not match', template) * // → null * ``` */ export declare const decodeHeadline: (line: string, template: CompiledTemplate) => Record | null; /** * Encodes overflow fields for a record as indented lines. * For each overflow template, if the record has a non-null/non-undefined value * for the field in that template, emits an indented line using the template. * Overflow fields with null or undefined values are omitted. * * For multi-line string values (containing newlines), the first line is encoded * on the template line, and subsequent lines are emitted as continuation lines * with deeper indentation. * * @param record - The record object with field values * @param overflowTemplates - Array of compiled overflow templates * @returns Array of indented overflow line strings * * @example * ```typescript * const templates = compileOverflowTemplates(['tagged {tags}', '~ {description}']) * const record = { id: "1", title: "Dune", tags: ["classic"], description: null } * encodeOverflowLines(record, templates) * // → [' tagged [classic]'] * // Note: description is null, so its overflow line is omitted * * // Multi-line value: * const record2 = { id: "1", description: "Line one\nLine two" } * encodeOverflowLines(record2, compileOverflowTemplates(['~ {description}'])) * // → [' ~ Line one', ' Line two'] * ``` */ export declare const encodeOverflowLines: (record: Record, overflowTemplates: ReadonlyArray) => ReadonlyArray; /** * Result of decoding overflow lines for a record. */ export interface DecodeOverflowResult { /** The decoded field values from overflow lines */ readonly fields: Record; /** Number of lines consumed (including continuation lines) */ readonly linesConsumed: number; } /** * Decodes overflow lines for a record using the configured overflow templates. * Collects indented lines belonging to the record, tries each overflow template * in order, skips on non-match, and captures field values on match. * * For each indented line: * 1. Try matching against each overflow template (in order) * 2. If a template matches, capture the field values and move to next line * 3. If no template matches, check if it's a continuation line (deeper indentation) * 4. Continuation lines are appended to the previous field's value with newline * * @param lines - Array of indented lines (already collected for this record) * @param overflowTemplates - Array of compiled overflow templates * @param baseIndent - The expected indentation level for overflow lines (default: 2) * @returns The decoded field values and number of lines consumed * * @example * ```typescript * const templates = compileOverflowTemplates(['tagged {tags}', '~ {description}']) * const lines = [' tagged [sci-fi]', ' ~ A classic novel'] * const result = decodeOverflowLines(lines, templates) * // → { fields: { tags: ['sci-fi'], description: 'A classic novel' }, linesConsumed: 2 } * ``` */ export declare const decodeOverflowLines: (lines: ReadonlyArray, overflowTemplates: ReadonlyArray, baseIndent?: number) => DecodeOverflowResult; /** * Result of scanning for the @prose directive in a document. */ export interface ScanDirectiveResult { /** Index of the last line before the directive (or -1 if no preamble) */ readonly preambleEnd: number; /** Index of the line containing the @prose directive */ readonly directiveStart: number; } /** * Scans a document for the @prose directive. * The directive is a line starting with `@prose ` (note the trailing space). * * Rules: * - Exactly one @prose directive must exist in the file * - If no directive is found, throws an error * - If multiple directives are found, throws an error * - All lines before the directive are preamble * * @param lines - Array of lines from the document * @returns The position information for preamble and directive * @throws Error if no directive found or multiple directives found * * @example * ```typescript * const lines = ['# My Books', '', '@prose #{id} {title}', '#1 Dune'] * const result = scanDirective(lines) * // → { preambleEnd: 1, directiveStart: 2 } * * const linesNoPreable = ['@prose #{id} {title}', '#1 Dune'] * const result2 = scanDirective(linesNoPreable) * // → { preambleEnd: -1, directiveStart: 0 } * ``` */ export declare const scanDirective: (lines: ReadonlyArray) => ScanDirectiveResult; /** * Result of parsing a directive block. */ export interface DirectiveBlock { /** The headline template (content after @prose) */ readonly headlineTemplate: string; /** Overflow templates (indented lines immediately after @prose) */ readonly overflowTemplates: ReadonlyArray; /** Index of the first line after the directive block (body start) */ readonly bodyStart: number; } /** * Parses a directive block from the document. * Extracts the headline template from the @prose line and collects * any indented overflow templates that immediately follow. * * The directive block structure: * ``` * @prose #{id} "{title}" by {author} ← headline template * tagged {tags} ← overflow template 1 * ~ {description} ← overflow template 2 * ← blank line or non-indented = end of block * ``` * * Overflow templates are lines that: * - Immediately follow the @prose line (no blank lines between) * - Are indented (start with whitespace) * * @param lines - Array of lines from the document * @param directiveStart - Index of the @prose directive line * @returns The parsed directive block with template strings and body start index * * @example * ```typescript * const lines = [ * '@prose #{id} "{title}"', * ' tagged {tags}', * ' ~ {description}', * '', * '#1 "Dune"', * ] * const result = parseDirectiveBlock(lines, 0) * // → { * // headlineTemplate: '#{id} "{title}"', * // overflowTemplates: ['tagged {tags}', '~ {description}'], * // bodyStart: 3 * // } * ``` */ export declare const parseDirectiveBlock: (lines: ReadonlyArray, directiveStart: number) => DirectiveBlock; /** * Represents a parsed entry from the body section of a prose document. * Can be either a record (matched the headline template) or pass-through text. */ export type ProseEntry = { readonly type: "record"; /** The decoded headline fields */ readonly fields: Record; /** The raw headline line */ readonly headline: string; /** Indented overflow lines belonging to this record */ readonly overflowLines: ReadonlyArray; } | { readonly type: "passthrough"; /** Raw text lines that didn't match the template */ readonly lines: ReadonlyArray; }; /** * Result of parsing the body section of a prose document. */ export interface ParseBodyResult { /** The parsed entries (interleaved records and pass-through text) */ readonly entries: ReadonlyArray; } /** * Parses the body section of a prose document. * Iterates lines after the directive block and classifies each as: * - Record headline (matches the compiled template) * - Indented overflow/continuation (part of the current record) * - Pass-through text (doesn't match, preserved verbatim) * * @param lines - Array of lines from the document * @param bodyStart - Index of the first line of the body (after directive block) * @param headlineTemplate - The compiled headline template * @returns The parsed body with interleaved records and pass-through text * * @example * ```typescript * const lines = [ * '@prose #{id} "{title}"', * '', * '## Science Fiction', * '#1 "Dune"', * ' tagged [classic]', * '#2 "Neuromancer"', * '', * '## Fantasy', * '#3 "The Hobbit"', * ] * const template = compileTemplate('#{id} "{title}"') * const result = parseBody(lines, 1, template) * // → { * // entries: [ * // { type: "passthrough", lines: ["", "## Science Fiction"] }, * // { type: "record", fields: { id: "1", title: "Dune" }, headline: '#1 "Dune"', overflowLines: [" tagged [classic]"] }, * // { type: "record", fields: { id: "2", title: "Neuromancer" }, headline: '#2 "Neuromancer"', overflowLines: [] }, * // { type: "passthrough", lines: ["", "## Fantasy"] }, * // { type: "record", fields: { id: "3", title: "The Hobbit" }, headline: '#3 "The Hobbit"', overflowLines: [] }, * // ] * // } * ``` */ export declare const parseBody: (lines: ReadonlyArray, bodyStart: number, headlineTemplate: CompiledTemplate) => ParseBodyResult; /** * Creates a prose format codec for human-readable, template-driven serialization. * * The prose format uses a `@prose` directive to define a sentence-like pattern * mapping field names to positions within literal delimiter text. Records follow * this pattern, producing human-readable lines. * * Templates use `{fieldName}` placeholders mixed with literal text: * ``` * @prose #{id} "{title}" by {authorId} ({year}) — {genre} * tagged {tags} * ~ {description} * ``` * * The codec compiles templates at construction time and returns a standard * FormatCodec with encode/decode functions. * * @param options - Codec configuration with headline and overflow templates * @param options.template - The headline template with {fieldName} placeholders * @param options.overflow - Optional array of overflow templates for additional fields * @returns A FormatCodec for prose serialization * * @example * ```typescript * const codec = proseCodec({ * template: '#{id} "{title}" by {author}', * overflow: ['tagged {tags}', '~ {description}'], * }) * * const layer = makeSerializerLayer([codec]) * * // Encoded output: * // @prose #{id} "{title}" by {author} * // tagged {tags} * // ~ {description} * // * // #1 "Dune" by Frank Herbert * // tagged [sci-fi, classic] * // ~ A masterpiece of science fiction * ``` */ export declare const proseCodec: (options?: ProseCodecOptions) => FormatCodec; //# sourceMappingURL=prose.d.ts.map