@venn-lang/fmt
v0.9.0
Published
The fmt namespace: a value in, formatted text out. JSON, tables, YAML, CSV and XML.
Readme
@venn-lang/fmt
The
fmtnamespace: a value in, formatted text out. JSON, tables, YAML, CSV and XML.
Formatting is kept separate from printing on purpose. Every verb here returns a string, which is a
value you can print, assert against, send in a request or write to a file, not something that only
ever reaches a terminal. The plugin is pure: it needs no host capability and touches nothing.
Install
@venn-lang/fmt is part of the stdlib the venn CLI and the language server load, so there is nothing
to install. A file that formats says so:
import { fmt } from "venn/fmt"Usage
# report.vn, run with `venn run report.vn`
import { fmt } from "venn/fmt"
const people = [
{ name: "Ada", age: 36 },
{ name: "Linus", age: 54 }
]
print fmt.table(people)
print fmt.csv(people)
print fmt.json({ count: people.len }, 0)name │ age
──────┼────
Ada │ 36
Linus │ 54
name,age
Ada,36
Linus,54
{"count":2}print is in the prelude, so that file imports nothing but fmt.
Verbs
| Verb | Signature | What it does |
| --- | --- | --- |
| fmt.json | (dynamic, number?) -> string | JSON text. Indents by 2 by default; fmt.json(x, 0) puts it on one line. A value that contains itself degrades instead of throwing. |
| fmt.table | (list<dynamic>) -> string | An aligned ASCII table of a list of records. |
| fmt.yaml | (dynamic) -> string | YAML for a map, a list or a scalar. |
| fmt.csv | (list<dynamic>, string?) -> string | CSV with a header row. fmt.csv(rows, ";") changes the separator. |
| fmt.xml | (dynamic, string?) -> string | XML text. fmt.xml(x, "user") names the root element, which is root otherwise. |
Every knob is positional, and every verb ends in a string. None of them reads an options map, so
none declares one: an options map in the editor's hover that the verb never looks at would quietly
strip the keys a caller wrote there.
What each renderer decides
tabletakes the columns from the union of every row's keys, in first-seen order, so a row missing a field still lines up. Each column is padded to its widest cell. An empty list renders as(no rows). A cell holding a map or a list is written the way the language itself writes it (the same renderer behindprintand${}), not as JSON: a table is written for a person to read, and{ homework: 95, final: 92 }is what that person would have typed.json,csv,xmlandyamlkeep their own writers, because they answer to formats outside this language.yamlputs a scalar on its key's line and opens an indented block for a map or a list. A string that would not read back as plain YAML is quoted, and so is the empty string. An empty list is[]and an empty map is{}.csvfollows RFC 4180: a field is quoted only when it holds a separator, a quote or a newline, and inner quotes are doubled. A list with no records renders as the empty string.xmlturns keys into elements and repeats a tag for each item of a list. Text is escaped (&,<,>,"), and a map with nothing in it becomes a self-closing element.
API
| Export | What it is |
| --- | --- |
| fmtPlugin (also the default export) | The PluginDefinition: namespace fmt, five actions, no required capability, no types of its own. |
| fmtActions | The five ActionDefinitions. |
| toJson(value, show, spaces?) | The renderer behind fmt.json. Defaults to 2 spaces. |
| toTable(rows, show) | The renderer behind fmt.table. |
| toYaml(value, show, indent?) | The renderer behind fmt.yaml. |
| toCsv(rows, show, separator?) | The renderer behind fmt.csv. Defaults to a comma. |
| toXml({ value, show, tag?, indent? }) | The renderer behind fmt.xml. Defaults to the tag root. |
| Show | The type of show: (value: unknown) => string. |
The renderers take no ports and reach for no host capability. Every one of them takes show, the
language's own writer, which each verb passes as ctx.show. That is not a convenience: a 250ms
reaches a plugin as { kind: "duration", ms: 250 }, and four of these five renderers used to write
that envelope out because each kept a private writer of its own. A format decides how a value is
delimited; the language decides what the value is.
import { toCsv, type Show, toYaml } from "@venn-lang/fmt";
// Inside an action this is `ctx.show`. Standalone, any writer will do.
const show: Show = (value) => (typeof value === "string" ? value : JSON.stringify(value));
toCsv([{ text: 'say "hi", now', plain: "ok" }], show);
// 'text,plain\n"say ""hi"", now",ok'
toYaml({ name: "Ada", tags: ["a", "b"], nested: { n: 1 } }, show);
// "name: Ada\ntags:\n - a\n - b\nnested:\n n: 1"See also
@venn-lang/iofor writing the resulting text to standard output or standard error.@venn-lang/assertfor asserting on it once it is a string.@venn-lang/sdkfordefineActionand the typed argument helpers used here.
