-
Notifications
You must be signed in to change notification settings - Fork 0
Guide File System
ARO provides built-in file system operations for reading, writing, and watching files. This chapter covers file I/O and directory monitoring.
<Read> the <content> from the <file: "./data.txt">.
<Read> the <readme> from the <file: "./README.md">.
<Read> the <config: JSON> from the <file: "./config.json">.
<Read> the <data: JSON> from the <file: "./data.json">.
<Read> the <image: bytes> from the <file: "./logo.png">.
<Read> the <document: bytes> from the <file: "./report.pdf">.
(GET /files/{name}: File API) {
<Extract> the <filename> from the <request: parameters>.
<Read> the <content> from the <file: "./uploads/${filename}">.
<Return> an <OK: status> with <content>.
}
(Load Config: Configuration) {
<Read> the <config: JSON> from the <file: "./config.json">.
<Log> "Config not found, using defaults" to the <console> when <config> is empty.
<Create> the <config> with {
port: 8080,
debug: false
} when <config> is empty.
<Publish> as <app-config> <config>.
<Return> an <OK: status> for the <loading>.
}
<Write> the <content> to the <file: "./output.txt">.
<Write> the <report> to the <file: "./reports/daily.txt">.
<Write> the <config: JSON> to the <file: "./config.json">.
<Write> the <data: JSON> to the <file: "./export.json">.
<Store> the <log-entry> into the <file: "./logs/app.log">.
<Store> the <record> into the <file: "./data/records.csv">.
Directories are created automatically when writing:
(* ./reports/2024/01/ is created if it doesn't exist *)
<Write> the <report> to the <file: "./reports/2024/01/daily.txt">.
ARO automatically detects the file format based on the extension and serializes/deserializes data accordingly. Write to .json and get JSON. Write to .csv and get CSV. No manual serialization needed.
| Extension | Format | Description |
|---|---|---|
.json |
JSON | Pretty-printed JSON |
.jsonl, .ndjson
|
JSON Lines | One JSON object per line |
.yaml, .yml
|
YAML | Human-readable YAML |
.toml |
TOML | Configuration format |
.xml |
XML | Variable name as root element |
.csv |
CSV | Comma-separated with headers |
.tsv |
TSV | Tab-separated with headers |
.md |
Markdown | Markdown table |
.html |
HTML | HTML table |
.txt |
Plain Text | key=value pairs |
.sql |
SQL | INSERT statements |
.log |
Log | Date-prefixed entries |
.env |
Environment | KEY=VALUE format |
.obj |
Binary | Raw binary data |
Same data, different formats - just change the extension:
<Create> the <users> with [
{ "id": 1, "name": "Alice", "email": "alice@example.com" },
{ "id": 2, "name": "Bob", "email": "bob@example.com" }
].
(* JSON - pretty printed *)
<Write> the <users> to "./output/users.json".
(* JSON Lines - one object per line, compact *)
<Write> the <users> to "./output/users.jsonl".
(* YAML - human readable *)
<Write> the <users> to "./output/users.yaml".
(* CSV - spreadsheet friendly *)
<Write> the <users> to "./output/users.csv".
(* XML - variable name becomes root element *)
<Write> the <users> to "./output/users.xml".
(* Markdown table *)
<Write> the <users> to "./docs/users.md".
(* SQL INSERT statements *)
<Write> the <users> to "./backup/users.sql".
(* Log file - date-prefixed entries *)
<Write> the <message> to "./app.log" with "Server started".
<Append> the <entry> to "./app.log" with "User logged in".
(* Environment file - uppercase keys with underscore nesting *)
<Write> the <config> to "./.env".
When reading, the extension determines how content is parsed:
(* JSON - parsed to object/array *)
<Read> the <config> from "./settings.json".
(* JSON Lines - parsed to array of objects *)
<Read> the <events> from "./logs/events.jsonl".
(* CSV - parsed to array with headers as keys *)
<Read> the <records> from "./data.csv".
(* YAML - parsed to object/array *)
<Read> the <settings> from "./config.yaml".
Use the String qualifier to read raw content without parsing:
(* Read as raw string, no JSON parsing *)
<Read> the <raw-json: String> from "./data.json".
CSV and TSV support custom options:
(* Custom delimiter *)
<Write> the <data> to "./export.csv" with { delimiter: ";" }.
(* Without header row *)
<Write> the <data> to "./export.csv" with { header: false }.
(* Read with custom options *)
<Read> the <data> from "./import.csv" with { delimiter: ";", header: false }.
| Option | Type | Default | Description |
|---|---|---|---|
delimiter |
String |
, / \t
|
Field separator |
header |
Boolean | true |
Include/expect header row |
quote |
String | " |
Quote character |
A common pattern is exporting data to multiple formats:
(exportData: Export API) {
<Retrieve> the <users> from the <user-repository>.
(* Export to multiple formats *)
<Write> the <users> to "./export/users.json".
<Write> the <users> to "./export/users.csv".
<Write> the <users> to "./export/users.yaml".
<Write> the <users> to "./docs/users.md".
<Return> an <OK: status> with "Exported to 4 formats".
}
Read from one format, write to another:
(convertData: Data Conversion) {
(* Read CSV input *)
<Read> the <records> from "./input/data.csv".
(* Write to JSON and YAML *)
<Write> the <records> to "./output/data.json".
<Write> the <records> to "./output/data.yaml".
<Return> an <OK: status> for the <conversion>.
}
<Delete> the <file: "./temp/cache.json">.
<Delete> the <file: path>.
List all files in a directory:
<Create> the <uploads-path> with "./uploads".
<List> the <entries> from the <directory: uploads-path>.
Filter with glob patterns:
<Create> the <src-path> with "./src".
<List> the <aro-files> from the <directory: src-path> matching "*.aro".
List recursively:
<Create> the <project-path> with "./project".
<List> the <all-files> from the <directory: project-path> recursively.
Each entry contains:
-
name- file or directory name -
path- full path -
size- file size in bytes -
isFile- true if file -
isDirectory- true if directory -
modified- last modification date
Check if a file exists:
<Exists> the <found> for the <file: "./config.json">.
when <found> is false {
<Log> "Config not found!" to the <console>.
}
Check if a directory exists:
<Exists> the <dir-exists> for the <directory: "./output">.
Create a directory (including parent directories):
(* Natural syntax with "make" verb *)
<Make> the <directory> at the <path: "./output/reports/2024">.
(* Alternative syntax *)
<CreateDirectory> the <output-dir> to the <path: "./output/reports/2024">.
Get detailed metadata for a file:
<Stat> the <info> for the <file: "./document.pdf">.
<Log> <info: size> to the <console>.
<Log> <info: modified> to the <console>.
Get directory metadata:
<Stat> the <dir-info> for the <directory: "./src">.
Copy a file:
<Copy> the <file: "./template.txt"> to the <destination: "./copy.txt">.
Copy a directory (recursive by default):
<Copy> the <directory: "./src"> to the <destination: "./backup/src">.
Rename a file:
<Move> the <file: "./draft.txt"> to the <destination: "./final.txt">.
Move to a different directory:
<Move> the <file: "./inbox/report.pdf"> to the <destination: "./archive/report.pdf">.
Move a directory:
<Move> the <directory: "./temp"> to the <destination: "./processed">.
Append data to a file:
<Append> the <log-line> to the <file: "./logs/app.log">.
Creates the file if it doesn't exist.
Watch directories for changes using the <Watch> action:
(Application-Start: File Processor) {
<Log> "Starting file processor" to the <console>.
(* Watch the inbox directory for new files *)
<Watch> the <file-monitor> for the <directory> with "./inbox".
(* Keep running until shutdown *)
<Keepalive> the <application> for the <events>.
<Return> an <OK: status> for the <startup>.
}
Watch Syntax:
<Watch> the <file-monitor> for the <directory> with "path".
The <Watch> action:
- Monitors the specified directory recursively
- Emits events when files are created, modified, or deleted
- Runs asynchronously (does not block execution)
- Continues until application shutdown
Watchers emit events when files change:
| Event | When Triggered | Data |
|---|---|---|
FileCreatedEvent |
New file created |
path - file path |
FileModifiedEvent |
Existing file modified |
path - file path |
FileDeletedEvent |
File deleted |
path - file path |
Feature sets with business activity File Event Handler receive file events. The feature set name determines which event type it handles:
| Feature Set Name | Handles Event |
|---|---|
Handle File Created |
FileCreatedEvent |
Handle File Modified |
FileModifiedEvent |
Handle File Deleted |
FileDeletedEvent |
(* Handle new files *)
(Handle File Created: File Event Handler) {
<Extract> the <path> from the <event: path>.
<Log> "file created" to the <console>.
<Return> an <OK: status> for the <event>.
}
(* Handle modified files *)
(Handle File Modified: File Event Handler) {
<Extract> the <path> from the <event: path>.
<Log> "file modified" to the <console>.
<Return> an <OK: status> for the <event>.
}
(* Handle deleted files *)
(Handle File Deleted: File Event Handler) {
<Extract> the <path> from the <event: path>.
<Log> "file deleted" to the <console>.
<Return> an <OK: status> for the <event>.
}
(Application-Start: Hot Reload App) {
(* Load initial config *)
<Read> the <config: JSON> from the <file: "./config.json">.
<Publish> as <app-config> <config>.
(* Watch for config changes *)
<Watch> the <file-monitor> for the <directory> with ".".
<Start> the <http-server> on port <config: port>.
<Wait> for <shutdown-signal>.
<Return> an <OK: status> for the <startup>.
}
(Handle File Modified: File Event Handler) {
<Extract> the <path> from the <event: path>.
<Log> "Reloading configuration..." to the <console> when <path> is "./config.json".
<Read> the <new-config: JSON> from the <file: "./config.json"> when <path> is "./config.json".
<Publish> as <app-config> <new-config> when <path> is "./config.json".
<Log> "Configuration reloaded" to the <console> when <path> is "./config.json".
<Return> an <OK: status> for the <reload>.
}
(Application-Start: Upload Processor) {
<Watch> the <file-monitor> for the <directory> with "./uploads".
<Start> the <http-server> on port 8080.
<Wait> for <shutdown-signal>.
<Return> an <OK: status> for the <startup>.
}
(POST /upload: Upload API) {
<Extract> the <file-data> from the <request: body>.
<Extract> the <filename> from the <request: headers.filename>.
<Write> the <file-data> to the <file: "./uploads/${filename}">.
<Return> a <Created: status> with { filename: <filename> }.
}
(Handle File Created: File Event Handler) {
<Extract> the <path> from the <event: path>.
(* Only process files in uploads directory *)
<Read> the <content> from the <file: path> when <path> starts with "./uploads/".
<Transform> the <processed> from the <content> when <path> starts with "./uploads/".
<Write> the <processed> to the <file: "./processed/${filename}"> when <path> starts with "./uploads/".
<Delete> the <file: path> when <path> starts with "./uploads/".
<Log> "Processed: ${path}" to the <console> when <path> starts with "./uploads/".
<Return> an <OK: status> for the <processing>.
}
(Application-Start: Logging App) {
(* Create log directory if needed *)
<Write> the <header> to the <file: "./logs/app.log">.
<Return> an <OK: status> for the <startup>.
}
(Log Event: ApplicationEvent Handler) {
<Extract> the <event-type> from the <event: type>.
<Extract> the <event-data> from the <event: data>.
<Create> the <log-entry> with {
timestamp: <current-time>,
type: <event-type>,
data: <event-data>
}.
<Store> the <log-entry: JSON> into the <file: "./logs/app.log">.
<Return> an <OK: status> for the <logging>.
}
(Process Batch: Scheduled Task) {
<List> the <files> from the <directory: "./inbox">.
for each <file> in <files> {
<Read> the <content> from the <file: file>.
<Process> the <result> from the <content>.
<Write> the <result> to the <file: "./outbox/${file.name}">.
<Delete> the <file: file>.
}
<Return> an <OK: status> for the <batch>.
}
<Read> the <content> from the <file: "./config.json">. (* Relative to app *)
<Read> the <content> from the <file: "../shared/data.json">. (* Parent directory *)
<Read> the <content> from the <file: "/etc/myapp/config.json">.
<Create> the <path> with "./uploads/${user-id}/${filename}".
<Write> the <data> to the <file: path>.
(Load Data: Initialization) {
<Read> the <data: JSON> from the <file: "./data.json">.
(* Handle missing file *)
<Create> the <data> with { items: [] } when <data> is empty.
<Write> the <data: JSON> to the <file: "./data.json"> when <data> is empty.
<Publish> as <app-data> <data>.
<Return> an <OK: status> for the <loading>.
}
(POST /upload: Upload API) {
<Extract> the <filename> from the <request: headers.filename>.
<Extract> the <content-type> from the <request: headers.Content-Type>.
(* Validate file type *)
when <content-type> is not "image/png" and <content-type> is not "image/jpeg" {
<Return> a <BadRequest: status> for the <invalid: file-type>.
}
<Write> the <data> to the <file: "./uploads/${filename}">.
<Return> a <Created: status> with { filename: <filename> }.
}
(* Text files *)
<Read> the <text> from the <file: "./data.txt">.
(* Binary files *)
<Read> the <binary: bytes> from the <file: "./image.png">.
(* JSON files *)
<Read> the <json: JSON> from the <file: "./config.json">.
(Process File: Temporary Processing) {
<Read> the <input> from the <file: "./input.txt">.
<Write> the <temp> to the <file: "./temp/processing.tmp">.
<Process> the <result> from the <temp>.
<Write> the <result> to the <file: "./output.txt">.
<Delete> the <file: "./temp/processing.tmp">.
<Return> an <OK: status> for the <processing>.
}
- Sockets - TCP communication
- Events - Event-driven architecture
- Application Lifecycle - Startup and shutdown
Fundamentals
- The Basics
- Feature Sets
- Actions
- Variables
- Type System
- Control Flow
- Error Handling
- Computations
- Dates
- Concurrency
Runtime & Events
I/O & Communication
Advanced