Scripting I/O
Tova provides comprehensive I/O functions for scripting, file system operations, shell commands, and environment access. These functions are available in tova run scripts and server blocks.
For reading and writing data files (CSV, JSON, etc.), see the I/O guide.
Filesystem
The fs namespace provides file and directory operations.
fs.exists
fs.exists(path) -> BoolReturns true if the file or directory exists.
if fs.exists("config.json") {
config = read("config.json")
}fs.is_file
fs.isFile(path) -> BoolReturns true if the path is a regular file.
fs.is_dir
fs.isDir(path) -> BoolReturns true if the path is a directory.
fs.ls
fs.ls(dir?, opts?) -> [String]Lists entries in a directory. Defaults to the current directory. Pass opts.full to get full paths.
files = fs.ls("src/")
// ["main.tova", "utils.tova", "lib/"]
// Full paths
full = fs.ls("src/", full: true)
// ["src/main.tova", "src/utils.tova", "src/lib/"]fs.mkdir
fs.mkdir(path) -> Result<String>Creates a directory (and parent directories if needed). Returns Ok(path) on success, Err(message) on failure.
fs.mkdir("output/reports")fs.rm
fs.rm(path, opts?) -> Result<String>Removes a file or directory. Returns Ok(path) on success, Err(message) on failure. Supports opts.recursive and opts.force.
fs.cp
fs.cp(src, dest, opts?) -> Result<String>Copies a file or directory. Returns Ok(dest) on success, Err(message) on failure. Supports opts.recursive for directory copies.
fs.cp("template.tova", "new-project/main.tova")fs.mv
fs.mv(src, dest) -> Result<String>Moves or renames a file or directory. Returns Ok(dest) on success, Err(message) on failure.
fs.read_text
fs.readText(path, encoding?) -> Result<String>Reads the entire contents of a file as a string. Returns Ok(content) on success, Err(message) on failure.
content = fs.readText("README.md").unwrap()
print(len(content))
// With error handling
match fs.readText("README.md") {
Ok(text) => print(len(text))
Err(msg) => print("Could not read file: {msg}")
}fs.write_text
fs.writeText(path, content, opts?) -> Result<String>Writes a string to a file, creating it if it does not exist. Returns Ok(path) on success, Err(message) on failure. Supports opts.append for append mode.
fs.writeText("output.txt", "Hello, World!")
// Append to a file
fs.writeText("log.txt", "New entry\n", append: true)fs.read_bytes
fs.readBytes(path) -> Result<Buffer>Reads the entire contents of a file as binary data. Returns Ok(buffer) on success, Err(message) on failure.
data = fs.readBytes("image.png").unwrap()
print("Read {len(data)} bytes")fs.glob_files
fs.globFiles(pattern) -> [String]Returns file paths matching a glob pattern.
tova_files = fs.globFiles("src/**/*.tova")
test_files = fs.globFiles("tests/*.tova")fs.file_stat
fs.fileStat(path) -> Result<Object>Returns file metadata. Returns Ok({size, mode, mtime, atime, isDir, isFile, isSymlink}) on success, Err(message) on failure.
fs.file_size
fs.fileSize(path) -> Result<Int>Returns the file size in bytes. Returns Ok(size) on success, Err(message) on failure.
size = fs.fileSize("data.csv").unwrap()
print("File is {size} bytes")Path Utilities
path_join
pathJoin(...parts) -> StringJoins path segments with the platform separator.
pathJoin("src", "utils", "helpers.tova")
// "src/utils/helpers.tova"path_dirname
pathDirname(path) -> StringReturns the directory portion of a path.
pathDirname("/home/user/file.tova") // "/home/user"path_basename
pathBasename(path) -> StringReturns the file name portion of a path.
pathBasename("/home/user/file.tova") // "file.tova"path_resolve
pathResolve(path) -> StringResolves a path to an absolute path.
path_ext
pathExt(path) -> StringReturns the file extension.
pathExt("data.csv") // ".csv"
pathExt("archive.tar.gz") // ".gz"path_relative
pathRelative(from, to) -> StringReturns the relative path from from to to.
Symlinks
symlink
symlink(target, link_path) -> Result<Nil>Creates a symbolic link. Returns Ok(null) on success, Err(message) on failure.
readlink
readlink(path) -> Result<String>Returns the target of a symbolic link. Returns Ok(target) on success, Err(message) on failure.
is_symlink
isSymlink(path) -> BoolReturns true if the path is a symbolic link.
Shell Commands
sh
sh(cmd, opts?) -> Result<{stdout: String, stderr: String, exitCode: Int}>Runs a command through the system shell. Returns Ok({stdout, stderr, exitCode}) on success, Err(message) on failure. Supports opts.cwd, opts.env, and opts.timeout.
match sh("ls -la") {
Ok(result) => print(result.stdout)
Err(msg) => print("Command failed: {msg}")
}
version = sh("git --version").unwrap().stdoutWARNING
sh passes the command through a shell. Do not include untrusted user input in the command string. Use exec instead for safe command execution with separate arguments.
exec
exec(cmd, args?, opts?) -> Result<{stdout: String, stderr: String, exitCode: Int}>Runs a command with an explicit argument list. Arguments are passed directly to the process without shell interpretation, preventing injection vulnerabilities. Returns Ok({stdout, stderr, exitCode}) on success, Err(message) on failure. Supports opts.cwd, opts.env, and opts.timeout.
match exec("git", ["log", "--oneline", "-5"]) {
Ok(r) => print(r.stdout)
Err(msg) => print("Command failed: {msg}")
}
version = exec("node", ["--version"]).unwrap().stdoutspawn
spawn(cmd, args?, opts?) -> Promise<Result<{stdout: String, stderr: String, exitCode: Int}>>Spawns an async child process. Returns a Promise that resolves to Ok({stdout, stderr, exitCode}) on success, Err(message) on failure. Supports opts.cwd, opts.env, and opts.shell.
result = await spawn("python3", ["server.py"])
match result {
Ok(r) => print("Exited with code {r.exitCode}")
Err(msg) => print("Failed to spawn: {msg}")
}Environment and CLI
Building a CLI tool?
For structured CLI tools with subcommands, typed arguments, and auto-generated help, use the cli {} block instead of manual argument parsing. For terminal colors, tables, progress bars, and interactive prompts, see Terminal & CLI.
env
env(key?, fallback?) -> String | Object | NilReturns an environment variable value, or all environment variables if no key is given. Returns null if the key is not set and no fallback is provided.
home = env("HOME")
all_vars = env()
// With fallback value
port = env("PORT", "3000")set_env
setEnv(key, value) -> NilSets an environment variable for the current process.
setEnv("NODE_ENV", "production")args
args() -> [String]Returns command-line arguments passed to the script.
arguments = args()
if len(arguments) < 2 {
print("Usage: tova run script.tova <input>")
exit(1)
}parse_args
parseArgs(argv) -> {flags: Object, positional: [String]}Parses command-line arguments into a structured object with flags (named options) and positional (positional arguments). Handles --key value, --key=value, --flag (boolean), and -abc (short flags).
opts = parseArgs(args())
// tova run build.tova --output dist --verbose
// { flags: { output: "dist", verbose: true }, positional: ["build.tova"] }
output_dir = opts.flags.output ?? "build"exit
exit(code?) -> NeverExits the process with an optional exit code (default: 0).
if error_occurred {
print("Fatal error")
exit(1)
}cwd
cwd() -> StringReturns the current working directory.
print("Working in: {cwd()}")chdir
chdir(dir) -> Result<String>Changes the current working directory. Returns Ok(dir) on success, Err(message) on failure.
chdir("/tmp")
print(cwd()) // "/tmp"
// With error handling
match chdir("/nonexistent") {
Ok(_) => print("Changed directory")
Err(msg) => print("Could not change directory: {msg}")
}script_path
scriptPath() -> String | NilReturns the absolute path of the currently running script, or null if not available.
script_dir
scriptDir() -> String | NilReturns the directory containing the currently running script, or null if not available.
on_signal
onSignal(signal, handler) -> NilRegisters a handler function for a process signal (e.g., "SIGINT", "SIGTERM").
onSignal("SIGINT", fn() {
print("Caught interrupt, cleaning up...")
cleanup()
exit(0)
})Standard Input
read_stdin
readStdin() -> StringReads all input from stdin.
// Pipe data: echo "hello" | tova run script.tova
input = readStdin()
print("Got: {input}")read_lines
readLines() -> [String]Reads stdin and splits into lines.
for line in readLines() {
process(line)
}Data Formats
The read() and write() functions support these formats via file extension:
| Extension | Format | Read | Write | Notes |
|---|---|---|---|---|
.csv | CSV | Yes | Yes | Auto-detects delimiter |
.tsv | TSV | Yes | Yes | Tab-delimited |
.json | JSON | Yes | Yes | Array of objects |
.jsonl | JSON Lines | Yes | Yes | One object per line |
.parquet | Apache Parquet | Yes | Yes | Via parquet-wasm (lazy-loaded) |
.xlsx | Excel | Yes | Yes | Via exceljs (lazy-loaded) |
Parquet
data = read("warehouse.parquet")
write(table, "output.parquet")
write(table, "output.parquet", compression: "gzip") // default: snappyCompression options: "snappy" (default), "gzip", "none".
Excel
data = read("report.xlsx")
data = read("report.xlsx", sheet: "Q4 Sales") // by name
data = read("report.xlsx", sheet: 1) // by index (1-based)
write(table, "output.xlsx")
write(table, "output.xlsx", sheet: "Summary")SQLite
db = sqlite("app.db")
db = sqlite(":memory:")
// Query returns a Table
users = db.query("SELECT * FROM users WHERE active = 1")
user = db.query("SELECT * FROM users WHERE id = ?", [42])
// Run statements
db.exec("CREATE TABLE logs (id INTEGER PRIMARY KEY, msg TEXT)")
db.exec("INSERT INTO logs (msg) VALUES (?)", ["hello"])
// Write a Table to a database table
write(sales, db, "sales")
write(sales, db, "sales", append: true)
db.close()Examples
File Processing Script
#!/usr/bin/env tova
arguments = args()
guard len(arguments) >= 1 else {
print("Usage: process.tova <input-dir>")
exit(1)
}
input_dir = arguments[0]
files = fs.globFiles(pathJoin(input_dir, "*.csv"))
for file in files {
data = read(file)
result = data |> where(.valid) |> sorted(fn(r) r.date)
output = replace(file, ".csv", "_clean.csv")
result |> write(output)
print("Processed {file} -> {output}")
}Environment Configuration
port = env("PORT", "3000") |> toInt()
debug = env("DEBUG") == "true"
db_url = env("DATABASE_URL", "sqlite:./dev.db")