oxabl.toml holds a project’s settings. Put it at the root of the project. Every command looks for it in the directory it works on and in each directory above it, and uses the first one it finds. Paths in the file are relative to the file.
Every table is optional except [workspace], which needs a name. A key you leave out takes its default.
[workspace]
name = "acme"
[workspace.sources]
include_paths = ["src"]
encoding = "windows-1252"
[workspace.schema]
files = ["schema/sports.df"]
[workspace.preprocessor]
proversion = "12.8"
databases = ["sports"]
[workspace.lint]
enable = ["strict.locks"]
unused-variable = "info"
[[workspace.lint.overrides]]
files = ["src/legacy/**"]
disable = ["strict"]
[workspace.style]
indent_size = 2
keyword_case = "Uppercase"When the file has a mistake
Parsing is strict: a key oxabl doesn’t know, or a value of the wrong type, is an error.
oxabl checkrefuses to run and exits2, so a mistake can’t turn into a pass.oxabl formatprints the same message as awarning:, then formats with the default style.
error: failed to parse oxabl.toml: TOML parse error at line 1, column 1
|
1 | [workspace]
| ^^^^^^^^^^^
missing field `name`The exceptions are rule and group names in [workspace.lint]: an unknown one is a warning, that entry is ignored, and the rest of the table applies.
For completion and validation while you edit the file, oxabl schema prints its JSON Schema.
[workspace]
| Key | Type | Default | What it does |
|---|---|---|---|
name |
string | required | The project’s name. |
[workspace.sources]
Where oxabl finds your code, and how it reads it.
| Key | Type | Default | What it does |
|---|---|---|---|
include_paths |
list of paths | [] |
Where to look for include files, classes and RUN targets, in order. The first match wins, like a PROPATH. -I on the command line adds to it. |
root_relative_include_paths |
list of paths | [] |
Paths searched first, relative to the directory of each program. [".", "rf"] makes a program in src/app search src/app and src/app/rf before include_paths. |
root_extensions |
list of strings | ["p", "w", "cls", "v"] |
The extensions a directory walk checks, without the dot, in any case. The list replaces the default, so keep the ones you still need. i is refused with a warning, because an include file is never checked on its own. |
run_extensions |
list of strings | ["p", "w"] |
The extensions tried, in order, for a RUN target written without one (RUN post-order). |
case_insensitive_lookup |
boolean | false |
Also finds a file whose name differs in case from the one in the source, such as RUN lib/shiporder.p for lib/ShipOrder.p. For a tree written on Windows and checked on Linux. It costs more on every miss. |
encoding |
"utf-8", "windows-1252" or "iso-8859-1" |
"utf-8" |
How every file is decoded: programs, includes, classes and .df files. oxabl format writes a file back in the same encoding. A file with a UTF-8 byte order mark is always read as UTF-8. |
fallback_encoding |
same values, or unset | unset | With encoding = "utf-8", the encoding for a file that isn’t valid UTF-8. For a tree that mixes the two. |
directories |
list of paths | [] |
Accepted, but no command reads it today. oxabl check checks the path you give it. |
encoding also accepts utf8, cp1252 and latin1. Without an encoding that fits, a single-byte file can’t be read:
error: cannot read src/app/latin.p: not valid UTF-8 (invalid byte at offset 55); set `[workspace.sources] encoding` for a single-byte sourceoxabl only looks where it’s told. With no include_paths, it doesn’t report names from other files as undefined, because it hasn’t looked. With paths set, a RUN target, USING import or NEW that none of them supplies is LINT0001:
src/app/caller.p:2:5: error[LINT0001]: undefined symbol `lib/missing.p`: no file on the configured search paths declares it
2 | run lib/missing.p.
help: check `[workspace.sources].include_paths` in `oxabl.toml`, or pass the directory with `-I`; oxabl only searches the paths it is given[workspace.schema]
| Key | Type | Default | What it does |
|---|---|---|---|
files |
list of paths | [] |
.df schema dump files, or directories of them. With a schema, oxabl checks table and field names (LINT0003) and runs the strict rules that need to know which tables are in the database. --schema replaces this list for one run. |
A file that can’t be read is a warning, and the run goes on without it:
warning: schema: [SCHEMA0001] cannot read schema file `nosuch.df`: No such file or directory (os error 2)[workspace.preprocessor]
The AVM answers some preprocessor names from the machine that compiles the code. oxabl has no such machine, so you describe it here, and every &IF that reads one of these names is decided against it. The keys are written with hyphens.
| Key | Type | Default | What it does |
|---|---|---|---|
opsys |
string | "UNIX" |
{&OPSYS} and the OPSYS function: "UNIX" or "WIN32". |
window-system |
string | "TTY" |
{&WINDOW-SYSTEM}: "TTY" or "MS-WINDOWS". |
batch-mode |
boolean | true |
{&BATCH-MODE}: yes or no. |
process-architecture |
integer | 64 |
{&PROCESS-ARCHITECTURE}: 32 or 64. |
proversion |
string, or unset | unset | {&PROVERSION} and the PROVERSION function, such as "12.8". |
databases |
list of strings, or unset | unset | The logical names of the databases connected at compile time, in connection order. CONNECTED, DBTYPE and LDBNAME answer from it. [] means none. |
database-types |
table | {} |
DBTYPE by logical name, such as { stock = "ORACLE" }. A listed database not named here is PROGRESS. |
proversion has no default because a tool can’t guess it. Until you set it, an &IF that reads it is decided as if it were empty, and oxabl says so:
src/lib/env.p:1:1: warning[PREPROC009]: predefined name '{&PROVERSION}' has no value — the condition is decided as if it were empty
1 | &IF PROVERSION >= "12" &THEN
help: set `proversion` under [workspace.preprocessor] in oxabl.tomlWithout databases, the file names in [workspace.schema] stand in for the logical names (schema/ledger.df connects ledger), as a partial list. With neither, a condition that asks about a database is a guess, and oxabl reports PREPROC010. Neither warning changes the exit code of check.
[workspace.lint]
Which rules run, and at what severity.
| Key | Type | Default | What it does |
|---|---|---|---|
enable |
list of group names | [] |
Turns groups on. "strict" turns on every strict.* group. |
disable |
list of group names | [] |
Turns groups off. Applied after enable. |
<rule> = "<severity>" |
severity | the rule’s default | Sets one rule’s severity, by name (unused-variable) or code (LINT0002). A severity turns the rule on, even when its group is off. |
overrides |
list of tables | [] |
Settings for some files only. See below. |
A severity is "off", "hint", "info", "warn" (or "warning") or "error". Every severity except off fails oxabl check.
The default group is always on and can’t be turned off as a group; set its rules to off one by one. The strict.* groups are off until you enable them, and their rules are in preview.
Overrides
[[workspace.lint.overrides]]
files = ["src/legacy/**", "src/old/report.p"]
disable = ["strict"]
undefined-symbol = "warn"| Key | Type | What it does |
|---|---|---|
files |
list of globs | The files it applies to, relative to oxabl.toml, written with /, matched without regard to case. * stays in one directory, ** crosses directories, and a directory matches every file under it. An override without files applies to nothing and is reported. |
enable, disable, <rule> |
as above | Applied to those files. |
Settings apply lowest first: the rule’s default, then [workspace.lint], then each override whose files match, in the order they’re written. Put broad patterns before narrow ones. Within one table, a rule’s own setting beats its group’s, and disable beats enable.
Rules
| Code | Name | Group | Default | Reports |
|---|---|---|---|---|
LINT0001 |
undefined-symbol |
default |
error | A name that resolves to nothing in scope. |
LINT0002 |
unused-variable |
default |
warning | A variable or parameter that nothing references. |
LINT0003 |
unknown-table-or-field |
default |
warning | A table, field or sequence the loaded schema doesn’t declare. |
LINT0004 |
type-mismatch-assignment |
default |
warning | An assignment whose value type doesn’t fit the target. |
LINT0005 |
block-var-used-outside |
default |
info | A block-defined variable read outside its block before it’s set. |
LINT0006 |
assigned-but-never-read |
default |
warning | A variable that’s written but never read. |
CL001 |
run-value |
strict.calls |
warning | RUN VALUE(...), which calls a procedure chosen at run time. |
CL002 |
dynamic-invoke |
strict.calls |
warning | DYNAMIC-FUNCTION, DYNAMIC-INVOKE, DYNAMIC-NEW, DYNAMIC-PROPERTY and CALL objects. |
CL003 |
signature-mismatch |
strict.calls |
warning | A RUN whose arguments differ from the callee’s parameters in count, kind, mode or type. |
CL004 |
unresolved-call |
strict.calls |
warning | A RUN of a name that’s neither a procedure of the file nor a program on the search paths. |
CL005 |
shared-state |
strict.calls |
warning | NEW SHARED and SHARED definitions. |
TX001 |
implicit-transaction |
strict.transactions |
warning | A block that’s a transaction only because it updates the database. |
TX002 |
procedure-wide-transaction |
strict.transactions |
warning | A main block that’s a transaction block, so the transaction spans the procedure. |
TX003 |
transaction-spans-ui |
strict.transactions |
warning | A statement that waits for the user inside a transaction. |
TX004 |
transaction-spans-call |
strict.transactions |
warning | A RUN inside a transaction whose callee isn’t in the file. |
TX005 |
scope-widened |
strict.transactions |
warning | A reference outside a FOR block that raises the block’s buffer scope to the procedure. |
TX006 |
missing-no-undo |
strict.transactions |
warning | A variable, parameter, temp-table or property defined without NO-UNDO. |
LK001 |
default-lock |
strict.locks |
warning | A database read with no lock phrase, which takes SHARE-LOCK. |
LK002 |
lock-outlives-transaction |
strict.locks |
warning | A record still locked after its transaction, because the buffer’s scope is wider. |
LK003 |
lock-upgrade-in-loop |
strict.locks |
warning | A NO-LOCK read upgraded to EXCLUSIVE-LOCK on every iteration of a loop. |
DB001 |
find-in-loop |
strict.access |
warning | A FIND or CAN-FIND that runs once per row of a FOR EACH or query loop. |
HD001 |
handle-not-deleted |
strict.handles |
warning | A dynamic object that some path loses without deleting it. |
HD002 |
delete-not-in-finally |
strict.handles |
warning | A delete that an error between the create and the delete skips. |
HD003 |
persistent-procedure-leak |
strict.handles |
warning | A persistent procedure that nothing deletes. |
HD004 |
memptr-not-freed |
strict.handles |
warning | A MEMPTR given a size and never set back to 0. |
HD005 |
missing-widget-pool |
strict.handles |
warning | Dynamic objects created with no widget pool scoped to the procedure. |
NL001 |
unknown-in-comparison |
strict.nulls |
warning | A possibly unknown variable in an ordering comparison or condition. |
NL002 |
unknown-propagation |
strict.nulls |
warning | A possibly unknown variable in arithmetic that’s stored or displayed. |
NL003 |
field-after-failed-find |
strict.nulls |
warning | A buffer field used after a FIND ... NO-ERROR that may have failed, with no AVAILABLE test. |
NL004 |
nullable-db-field |
strict.nulls |
warning | A database field that starts at ? and isn’t mandatory, used without a test for ?. |
The strict.locks, strict.transactions (except TX003, TX004 and TX006) and strict.access rules report only on tables in the loaded schema, so they’re quiet without one.
[workspace.style]
How oxabl format lays out code. --style on the command line replaces this table for one run, with a preset (oestandards or consultingwerk) or a style file holding these keys at the top level.
Six keys change what oxabl format writes today:
| Key | Values | Default | What it does |
|---|---|---|---|
indent_size |
integer | 4 |
Spaces per indent level. |
indent_style |
"Spaces", "Tabs" |
"Spaces" |
Indent with spaces or with tabs. |
max_consecutive_blank_lines |
integer | 1 |
Longer runs of blank lines are cut to this many. |
keyword_case |
"Preserve", "Uppercase", "Lowercase" |
"Preserve" |
The case of keywords. |
keyword_abbreviation |
"KeepAbbreviations", "AbbreviateNothing", "AbbreviateEverything" |
"KeepAbbreviations" |
"AbbreviateNothing" writes keywords in full (def var becomes DEFINE VARIABLE). "AbbreviateEverything" has no effect yet. |
end_with_type |
boolean | false |
Adds the block type to END, such as END PROCEDURE. |
With keyword_case = "Preserve", "AbbreviateNothing" and end_with_type write the words they add in uppercase:
DEFINE VARIABLE i as INTEGER no-undo.The other keys are accepted and validated, so a style file written for them works, but neither oxabl format nor oxabl check acts on them yet. max_line_length and wrap_long_lines are among them: a long line stays as long as it was.
| Key | Values | Default |
|---|---|---|
and_or_placement |
"EndOfLine", "BeginningOfLine" |
"EndOfLine" |
blank_lines_between_sections |
boolean | false |
buffer_naming |
"Unspecified", "BPrefix", "RoleTable" |
"Unspecified" |
class_naming |
"Unspecified", "SuffixBaseClass" |
"Unspecified" |
class_structure_order |
list of strings | [] |
comment_style |
"Either", "BlockComment", "SlashSlash" |
"Either" |
disallow_commented_code |
boolean | false |
disallow_unnecessary_blocks |
boolean | false |
do_placement |
"SameLine", "NewLine" |
"SameLine" |
dot_colon_same_line |
boolean | false |
file_name_casing |
"Unspecified", "LowerCamelCase" |
"Unspecified" |
first_param_same_line |
boolean | true |
global_prefix |
one character, or unset | unset |
interface_prefix |
one character, or unset | unset |
max_line_length |
integer | 120 |
method_case |
"Unspecified", "PascalCase", "CamelCase" |
"Unspecified" |
multi_param_threshold |
integer | 3 |
named_events_on_prefix |
boolean | false |
parameter_prefix |
"None", "IpOpIop", "PLowercase" |
"None" |
period_placement |
"SameLine", "NewLine" |
"SameLine" |
prefer_substitute |
"Never", "ThreeOrMore", "Always" |
"ThreeOrMore" |
procedure_structure_order |
list of strings | [] |
require_assign_statement |
boolean | false |
require_block_labels |
boolean | false |
require_file_headers |
boolean | false |
require_if_parentheses |
boolean | false |
require_no_undo |
boolean | false |
require_symbolic_operators |
boolean | false |
require_this_object |
boolean | false |
require_widget_pool |
boolean | false |
run_in_this_procedure |
boolean | false |
static_member_ref |
"Unspecified", "ShortName", "ClassName" |
"Unspecified" |
temp_table_in_include |
boolean | false |
temp_table_prefix |
"Unspecified", "TtBiPdsDs", "TtE" |
"Unspecified" |
using_sort |
boolean | false |
variable_case |
"Unspecified", "CamelCase", "LowerCase", "SnakeCase" |
"Unspecified" |
variable_decl_alignment |
"None", "Tabular" |
"None" |
variable_type_prefix |
boolean | false |
where_placement |
"NewLine", "SameLine" |
"NewLine" |
wrap_long_lines |
boolean | true |
To enforce NO-UNDO today, use the lint rule TX006 (missing-no-undo) instead of require_no_undo.