diff --git a/src/deps.typ b/src/deps.typ new file mode 100644 index 0000000..3cac77c --- /dev/null +++ b/src/deps.typ @@ -0,0 +1 @@ +#import "@preview/valkyrie:0.2.1" as z \ No newline at end of file diff --git a/src/impl.typ b/src/impl.typ index 5d12146..2db4df6 100644 --- a/src/impl.typ +++ b/src/impl.typ @@ -1,5 +1,7 @@ #import "styles.typ": * #import "note.typ" +#import "deps.typ": * +#import "validation.typ" // Query is an array of key, value, and inherit #let recurse-columns(columns, queries) = { @@ -140,6 +142,9 @@ ..args ) = { + // Recurse once to handle input validate + columns = z.parse(columns, validation.columns-schema) + let (columns, max-depth, length) = sanitize-input(columns, depth: 0) let query-result = recurse-columns(columns, ( fill: (inherit: true, default: none), @@ -160,8 +165,12 @@ stroke: none, fill: query-result.map(it=>it.fill.value), align: query-result.map(it=>it.align.value), - column-gutter: query-result.map(it=>it.gutter.value), - columns: query-result.map(it=>it.width.value), + column-gutter: query-result.map( + it=>{if it.gutter.value != none {it.gutter.value} else {auto}} + ), + columns: query-result.map( + it=>{if it.width.value != none {it.width.value} else {auto}} + ), table.header( table.hline(stroke: toprule), ..build-header(columns, max-depth: max-depth), diff --git a/src/validation.typ b/src/validation.typ new file mode 100644 index 0000000..3e6da23 --- /dev/null +++ b/src/validation.typ @@ -0,0 +1,64 @@ +#import "deps.typ": * + +#let columns-schema = z.array( + z.dictionary((: + key: z.any(), + header: z.content(optional: true), + display: z.function(optional: true), + fill: z.color(optional: true), + align: z.alignment(default: start), + gutter: z.length(optional: true), + width: z.any(optional: true), + )) + (: + handle-descendents: (self, it, ctx: z.z-ctx(), scope: ()) => { + + if (it.len() == 0 and self.optional) { + return none + } + + // Check `it` if strict + if (ctx.strict == true) { + for (key, value) in it { + if (key not in self.dictionary-schema) { + return (self.fail-validation)( + self, + it, + ctx: ctx, + scope: scope, + message: "Unknown key `" + key + "` in dictionary", + ) + } + } + } + + if "children" in it { + it.children = z.parse( + it.children, + z.array( + self, + z.coerce.array + ), + ctx: ctx + ) + } + + for (key, schema) in self.dictionary-schema { + + let entry = ( + schema.validate + )( + schema, + it.at(key, default: none), // implicitly handles missing entries + ctx: ctx, + scope: (..scope, str(key)) + ) + + if (entry != none or ctx.remove-optional-none == false) { + it.insert(key, entry) + } + + } + return it + }, + ) +) diff --git a/tests/test1/ref/1.png b/tests/test1/ref/1.png deleted file mode 100644 index e726341..0000000 Binary files a/tests/test1/ref/1.png and /dev/null differ diff --git a/tests/test1/test.typ b/tests/test1/test.typ deleted file mode 100644 index 557db03..0000000 --- a/tests/test1/test.typ +++ /dev/null @@ -1 +0,0 @@ -Hello World diff --git a/tests/topdown/ledger/ref/1.png b/tests/topdown/ledger/ref/1.png new file mode 100644 index 0000000..d683282 Binary files /dev/null and b/tests/topdown/ledger/ref/1.png differ diff --git a/tests/topdown/ledger/test.typ b/tests/topdown/ledger/test.typ new file mode 100644 index 0000000..312fc5d --- /dev/null +++ b/tests/topdown/ledger/test.typ @@ -0,0 +1,87 @@ +#import "/src/lib.typ" as dining-table + +#let data = ( + ( + date: datetime.today(), + particulars: lorem(05), + ledger: [JRS123] + dining-table.note.make[Hello World], + amount: (unit: $100$, decimal: $00$), + total: (unit: $99$, decimal: $00$), + ), +)*7 + +#import "@preview/typpuccino:0.1.0" +#let bg-fill-1 = typpuccino.latte.base +#let bg-fill-2 = typpuccino.latte.mantle + +#let example = ( + ( + key: "date", + header: align(left)[Date], + display: (it)=>it.display(auto), + fill: bg-fill-1, + align: start, + gutter: 0.5em, + ), + ( + key: "particulars", + header: text(tracking: 5pt)[Particulars], + width: 1fr, + gutter: 0.5em, + ), + ( + key: "ledger", + header: [Ledger], + fill: bg-fill-2, + width: 2cm, + gutter: 0.5em, + ), + ( + header: align(center)[Amount], + fill: bg-fill-1, + gutter: 0.5em, + hline: arguments(stroke: dining-table.lightrule), + children: ( + ( + key: "amount.unit", + header: align(left)[£], + width: 5em, + align: right, + vline: arguments(stroke: dining-table.lightrule), + gutter: 0em, + ), + ( + key: "amount.decimal", + header: align(right, text(number-type: "old-style")[.00]), + align: left + ), + ) + ), + ( + header: align(center)[Total], + gutter: 0.5em, + hline: arguments(stroke: dining-table.lightrule), + children: ( + ( + key: "total.unit", + header: align(left)[£], + width: 5em, + align: right, + vline: arguments(stroke: dining-table.lightrule), + gutter: 0em, + ), + ( + key: "total.decimal", + header: align(right, text(number-type: "old-style")[.00]), + align: left + ), + ) + ), +) + +#set text(size: 11pt) +#set page(height: auto, margin: 1em) +#dining-table.make(columns: example, + data: data, + notes: dining-table.note.display-list +) \ No newline at end of file diff --git a/tests/validation/test-1/ref/1.png b/tests/validation/test-1/ref/1.png new file mode 100644 index 0000000..9f8d53a Binary files /dev/null and b/tests/validation/test-1/ref/1.png differ diff --git a/tests/validation/test-1/test.typ b/tests/validation/test-1/test.typ new file mode 100644 index 0000000..c809d83 --- /dev/null +++ b/tests/validation/test-1/test.typ @@ -0,0 +1,78 @@ +#import "/src/deps.typ": * +#import "/src/validation.typ" +#import "/src/lib.typ" as dining-table + +#import "@preview/typpuccino:0.1.0" +#let bg-fill-1 = typpuccino.latte.base +#let bg-fill-2 = typpuccino.latte.mantle + +#let example = ( + ( + key: "date", + header: align(left)[Date], + display: (it)=>it.display(auto), + fill: bg-fill-1, + align: start, + gutter: 0.5em, + ), + ( + key: "particulars", + header: text(tracking: 5pt)[Particulars], + width: 1fr, + gutter: 0.5em, + ), + ( + key: "ledger", + header: [Ledger], + fill: bg-fill-2, + width: 2cm, + gutter: 0.5em, + ), + ( + header: align(center)[Amount], + fill: bg-fill-1, + gutter: 0.5em, + hline: arguments(stroke: dining-table.lightrule), + children: ( + ( + key: "amount.unit", + header: align(left)[£], + width: 5em, + align: right, + vline: arguments(stroke: dining-table.lightrule), + gutter: 0em, + ), + ( + key: "amount.decimal", + header: align(right, text(number-type: "old-style")[.00]), + align: left + ), + ) + ), + ( + header: align(center)[Total], + gutter: 0.5em, + hline: arguments(stroke: dining-table.lightrule), + children: ( + ( + key: "total.unit", + header: align(left)[£], + width: 5em, + align: right, + vline: arguments(stroke: dining-table.lightrule), + gutter: 0em, + ), + ( + key: "total.decimal", + header: align(right, text(number-type: "old-style")[.00]), + align: left + ), + ) + ), +) + +#set text(size: 11pt) +#set page(height: auto, margin: 1em) + + +#z.parse(example, validation.columns-schema, ctx: z.z-ctx(remove-optional-none: true)) \ No newline at end of file