Skip to content

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

tova
fs.exists(path) -> Bool

Returns true if the file or directory exists.

tova
if fs.exists("config.json") {
  config = read("config.json")
}

fs.is_file

tova
fs.isFile(path) -> Bool

Returns true if the path is a regular file.

fs.is_dir

tova
fs.isDir(path) -> Bool

Returns true if the path is a directory.

fs.ls

tova
fs.ls(dir?, opts?) -> [String]

Lists entries in a directory. Defaults to the current directory. Pass opts.full to get full paths.

tova
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

tova
fs.mkdir(path) -> Result<String>

Creates a directory (and parent directories if needed). Returns Ok(path) on success, Err(message) on failure.

tova
fs.mkdir("output/reports")

fs.rm

tova
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

tova
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.

tova
fs.cp("template.tova", "new-project/main.tova")

fs.mv

tova
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

tova
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.

tova
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

tova
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.

tova
fs.writeText("output.txt", "Hello, World!")

// Append to a file
fs.writeText("log.txt", "New entry\n", append: true)

fs.read_bytes

tova
fs.readBytes(path) -> Result<Buffer>

Reads the entire contents of a file as binary data. Returns Ok(buffer) on success, Err(message) on failure.

tova
data = fs.readBytes("image.png").unwrap()
print("Read {len(data)} bytes")

fs.glob_files

tova
fs.globFiles(pattern) -> [String]

Returns file paths matching a glob pattern.

tova
tova_files = fs.globFiles("src/**/*.tova")
test_files = fs.globFiles("tests/*.tova")

fs.file_stat

tova
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

tova
fs.fileSize(path) -> Result<Int>

Returns the file size in bytes. Returns Ok(size) on success, Err(message) on failure.

tova
size = fs.fileSize("data.csv").unwrap()
print("File is {size} bytes")

Path Utilities

path_join

tova
pathJoin(...parts) -> String

Joins path segments with the platform separator.

tova
pathJoin("src", "utils", "helpers.tova")
// "src/utils/helpers.tova"

path_dirname

tova
pathDirname(path) -> String

Returns the directory portion of a path.

tova
pathDirname("/home/user/file.tova")    // "/home/user"

path_basename

tova
pathBasename(path) -> String

Returns the file name portion of a path.

tova
pathBasename("/home/user/file.tova")   // "file.tova"

path_resolve

tova
pathResolve(path) -> String

Resolves a path to an absolute path.

path_ext

tova
pathExt(path) -> String

Returns the file extension.

tova
pathExt("data.csv")        // ".csv"
pathExt("archive.tar.gz")  // ".gz"

path_relative

tova
pathRelative(from, to) -> String

Returns the relative path from from to to.


tova
symlink(target, link_path) -> Result<Nil>

Creates a symbolic link. Returns Ok(null) on success, Err(message) on failure.

tova
readlink(path) -> Result<String>

Returns the target of a symbolic link. Returns Ok(target) on success, Err(message) on failure.

tova
isSymlink(path) -> Bool

Returns true if the path is a symbolic link.


Shell Commands

sh

tova
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.

tova
match sh("ls -la") {
  Ok(result) => print(result.stdout)
  Err(msg) => print("Command failed: {msg}")
}

version = sh("git --version").unwrap().stdout

WARNING

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

tova
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.

tova
match exec("git", ["log", "--oneline", "-5"]) {
  Ok(r) => print(r.stdout)
  Err(msg) => print("Command failed: {msg}")
}

version = exec("node", ["--version"]).unwrap().stdout

spawn

tova
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.

tova
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

tova
env(key?, fallback?) -> String | Object | Nil

Returns 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.

tova
home = env("HOME")
all_vars = env()

// With fallback value
port = env("PORT", "3000")

set_env

tova
setEnv(key, value) -> Nil

Sets an environment variable for the current process.

tova
setEnv("NODE_ENV", "production")

args

tova
args() -> [String]

Returns command-line arguments passed to the script.

tova
arguments = args()
if len(arguments) < 2 {
  print("Usage: tova run script.tova <input>")
  exit(1)
}

parse_args

tova
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).

tova
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

tova
exit(code?) -> Never

Exits the process with an optional exit code (default: 0).

tova
if error_occurred {
  print("Fatal error")
  exit(1)
}

cwd

tova
cwd() -> String

Returns the current working directory.

tova
print("Working in: {cwd()}")

chdir

tova
chdir(dir) -> Result<String>

Changes the current working directory. Returns Ok(dir) on success, Err(message) on failure.

tova
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

tova
scriptPath() -> String | Nil

Returns the absolute path of the currently running script, or null if not available.

script_dir

tova
scriptDir() -> String | Nil

Returns the directory containing the currently running script, or null if not available.

on_signal

tova
onSignal(signal, handler) -> Nil

Registers a handler function for a process signal (e.g., "SIGINT", "SIGTERM").

tova
onSignal("SIGINT", fn() {
  print("Caught interrupt, cleaning up...")
  cleanup()
  exit(0)
})

Standard Input

read_stdin

tova
readStdin() -> String

Reads all input from stdin.

tova
// Pipe data: echo "hello" | tova run script.tova
input = readStdin()
print("Got: {input}")

read_lines

tova
readLines() -> [String]

Reads stdin and splits into lines.

tova
for line in readLines() {
  process(line)
}

Data Formats

The read() and write() functions support these formats via file extension:

ExtensionFormatReadWriteNotes
.csvCSVYesYesAuto-detects delimiter
.tsvTSVYesYesTab-delimited
.jsonJSONYesYesArray of objects
.jsonlJSON LinesYesYesOne object per line
.parquetApache ParquetYesYesVia parquet-wasm (lazy-loaded)
.xlsxExcelYesYesVia exceljs (lazy-loaded)

Parquet

tova
data = read("warehouse.parquet")
write(table, "output.parquet")
write(table, "output.parquet", compression: "gzip")   // default: snappy

Compression options: "snappy" (default), "gzip", "none".

Excel

tova
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

tova
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

tova
#!/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

tova
port = env("PORT", "3000") |> toInt()
debug = env("DEBUG") == "true"
db_url = env("DATABASE_URL", "sqlite:./dev.db")

Released under the MIT License.