CLI Reference
Synopsis
dfmt [<path>…] sets the project up the first time; after that, a dry run
dfmt diff [<path>…] show what would change: a browser in a terminal, a unified diff otherwise
dfmt write [<path>…] format files in place (only those that change)
dfmt check [<path>…] exit 1 if any file needs formatting (for CI)
dfmt format <file> print one file formatted to stdout; writes nothing
dfmt setup set up or tune this project: its folders of SQL, dialects, formatting, variables
dfmt setup --dialect <d> write a one-dialect dlab.json without asking
dfmt setup <path>… explain how dfmt reads a file or folder, and why
dfmt detect-dialect [<path>…] which dialect and client the SQL under a path is written for
dfmt options this project's formatting options, and where each value came from
dfmt vars open setup on the project's variables
dfmt tree <file> print a file's syntax tree
dfmt lsp run the language server (started by editors, not by hand)
dfmt completion <bash|zsh|fish> print a shell completion script
dfmt help [<command>|options] help on a command, or on every formatting option
dfmt --version print the version
<path> is a file or a folder. With none, dfmt works on the project the current folder is in.
Everything about how files are read and laid out (the dialect, what runs the files, every
formatting option) is the project's, recorded in dlab.json by dfmt setup. No command takes it
as a flag: a setting worth having is worth having on every run, for everyone.
Formatting commands
diff, write, check and format share the flags below, and differ only in what they do with
the result.
| Command | What it does |
|---|---|
dfmt / dfmt <path>… | A dry run. Formats every matched file in memory, prints a summary, writes nothing. In a terminal with no project set up, it says so, opens dfmt setup, and carries on once the setup is saved. |
diff [<path>…] | Writes nothing. In a terminal it opens a browser of the changes, where a file with warnings is marked ▲ and shows them above its diff. Piped, or with --no-tui, it prints a unified diff. |
write [<path>…] | Writes the formatted output back, only to files that change. |
check [<path>…] | Writes nothing. Exits 1 if any file would change or could not be fully parsed (see Exit codes). |
format <file> | Prints the formatted file to stdout. Writes nothing. |
Several paths may be given (dfmt check a.sql b.sql models/), so a pre-commit hook or
git diff --name-only | xargs dfmt check can hand over its list. They must all belong to the same
project (the same dlab.json and the same dbt project); paths from two projects are refused with a
list of which path belongs where.
| Flag | Meaning |
|---|---|
--changed-since <ref> | Only files changed since a git ref (branch, tag or SHA), filtered through include and exclude. |
--model <name> | Format one dbt model by name (requires a dbt project). With format, prints it to stdout. |
-d, --directory <dir> | Run as if started in <dir>. |
-v, --verbose | List every file, not only the ones with something to say. |
--color <auto|always|never> | Colour in the output. auto (the default) colours a terminal and nothing else. |
--no-tui | With diff in a terminal, print the unified diff instead of opening the browser. |
--telemetry-preview | Print the anonymous usage event before sending it. See Telemetry. |
Project commands
| Command | What it does |
|---|---|
setup | In a terminal, opens the setup screen. It has two halves: what it reads, the folders of SQL set up in dlab.json, each with its dialect, what runs its files (psql, Flyway, dbt, none…) and the variables they read, plus the folders of SQL found that are not set up yet; and how it is formatted, the formatting options, which you can try live on your files. Changes wait until s saves them all to dlab.json; q with unsaved changes asks first. With no dlab.json, it reads your files first and proposes a setup. Outside a terminal, or with --print, it prints the setup instead. |
setup --dialect <d> | Writes a dlab.json with that one dialect and its file patterns, without asking: for a script, a CI job, or a project with one dialect. --all writes every formatting option with its current value too. |
setup <path>… | Says what dfmt decides for each path, and why: which dlab.json and which folder of it, which dialect and where that came from, what runs the files, and which project was found above. |
detect-dialect [<path>…] | Parses a sample of the files under each path under every SQL dialect, with and without each dialect's client, and says which dialect and client they are written for. --json prints the result for tools; -v shows the evidence. A tie is reported as a tie. |
options | Prints every formatting option with its value and, when it differs, its default. --set id=value (repeatable, dialect=<name> included) records a value in dlab.json; a value equal to the dialect's default is removed from the file rather than written. --file <path> reads the options as they apply to that file; --json prints the list for tools. |
vars | Opens dfmt setup on the project's variables. Outside a terminal it says where variables are set and exits 2. |
The project setup in dlab.json is what every command reads to know a file's dialect and what runs
it, the editor integration included.
Other commands
| Command | What it does |
|---|---|
tree <file> | Prints the file's syntax tree, named as the grammar names it. --json for tools, --prose to include the grammar's descriptions, --all-fields to show empty fields in the text tree, --sql '<text>' instead of a file, --dialect <d> and --runner <r> to read it other than as the project says. |
lsp | The language server, over stdin and stdout. Editors start it; see Editors. |
completion <shell> | Prints a completion script for bash, zsh or fish, completing commands, flags, dialect names and option values. |
help [<command>] | Help on one command. dfmt help options lists every formatting option with an example. |
--version | Prints the version. |
Resolution order
- Dialect: the folder of
dlab.jsonthe file is in, thendlab.json's own"dialect", then a project file above the file (a dbt project's adapter type fromprofiles.yml, a Flyway JDBC url,schemachange-config.yml). If none of these names one, dfmt stops. There is no default dialect. - Formatting options: the dialect's defaults, then
"dfmt"indlab.json, then the section under"dfmt"named after the dialect being formatted. - Files:
includeandexcludeindlab.json, narrowed by the paths you give and by--changed-since. In a project with nodlab.json, the dialect's own file suffixes (.sql, and for example.pgsqland.pgfor PostgreSQL) under the target.
Variables
A file whose template dfmt has to run (a dbt model's var() or env_var()) and that reads a
variable with no default, set nowhere, is not formatted. The run names the file and the variable,
formats every other file, and exits 2:
✗ transform/models/regional.sql reads var('region'), which nothing sets: set it in dfmt setup
(variables), or pin it under "variables" in dlab.json.
A read with a default (var('region', 'eu')) uses the default. A null under "variables" in
dlab.json names the variable without setting it. A substitution (Flyway's ${x}, psql's :x,
sqlcmd's $(x), SQL*Plus's &x) is laid out whether it is set or not; dfmt setup still lists
it, because the tool that runs the file needs it.
Exit codes
| Code | Meaning |
|---|---|
0 | Success, or check found nothing to reformat. |
1 | An error, a write failure, or check found files that need formatting or could not be fully parsed. |
2 | --changed-since could not resolve the ref, so nothing was checked. Distinct from 1 so CI does not read "nothing changed" as a pass. Also: a file was refused because a template it runs reads a variable nothing sets (see Variables); the other files were formatted. |
What a run prints
A run lists a file only when there is something to read about it (a region that did not parse, a
part handed back unformatted, a dbt model's warn), as soon as it is found, and counts every file
in one line at the end:
─ sql/broken.sql
warning: unexpected '='; …
--> sql/broken.sql:1:25
👀 352 files, nothing written: 12 would be reformatted, 337 already formatted, 2 partly formatted, 1 not formatted, 0 failed (1.2s)
`dfmt write` to apply, `dfmt diff` to see them first, `dfmt check` for an exit code, -v to list every file
A run with nothing to change ends on ✅ instead, and says no more.
write says 12 reformatted. A file dfmt could not read all of is counted apart: partly
formatted when it laid out the rest around what it could not read, not formatted when it laid
out none of it. -v lists every file. A blank file counts as already formatted.
The check summary
check prints one line and changes no files:
❌ 42 files checked: 3 would be reformatted, 36 already formatted, 2 partly formatted, 1 not formatted (1.2s)
Partly formatted and not formatted are the columns to watch. Where dfmt could not parse part of a file it returns that part exactly as written, which looks the same as "already formatted" and must not be counted as a pass. A partly formatted file was laid out around what dfmt could not read; a not formatted one was not laid out at all. Any non-zero would be reformatted, partly formatted, not formatted or failed count exits 1.
When files fail to parse, dfmt adds a hint naming the dialect it used. The usual cause of many parse failures is not broken SQL but the wrong dialect.