#![doc = include_str!("../README.md")]
use std::{
fs::{create_dir, File},
io::{Read, Write},
path::{Path, PathBuf},
process::Command as SysCommand,
};
use anyhow::{bail, Context};
use clap::{Args, Parser, Subcommand};
use tempfile::tempdir;
pub const DEFAULT_LUA_INIT: &str = include_str!("default_init.lua");
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
#[command(propagate_version = true)]
pub struct Cli {
#[command(subcommand)]
command: Command,
}
impl Cli {
pub fn process_command(self) -> anyhow::Result<()> {
match self.command {
Command::Run(args) => process_run_command(args),
}
}
}
#[derive(Subcommand, Clone)]
enum Command {
Run(RunArgs),
}
#[derive(Args, Clone)]
struct RunArgs {
#[arg(
short,
long,
help = r#"A path to the package you want to run. Package must export specified entrypoint. Example: "/path/to/package.so""#,
value_name = "FILE"
)]
path: PathBuf,
#[arg(
short,
long,
help = r#"An entrypoint you want to execute. Package must export function with the same name you specify here. Example: "some_entrypoint""#
)]
entrypoint: String,
#[arg(
short,
long,
help = r#"An initializing script that tarantool-runner would use instead of built-in script. Use it whenever you need to override specific options, like use vinyl instead of memtx."#
)]
init_script: Option<PathBuf>,
#[arg(
last = true,
help = r#"An OPTIONAL input which is propagated to the executed entrypoint. It is given to your entrypoint in string format."#
)]
input: Option<String>,
}
impl RunArgs {
fn init_script_content(&self) -> anyhow::Result<Option<String>> {
self.init_script
.as_ref()
.map(|file| {
let mut file = File::open(file)?;
let mut result = String::new();
file.read_to_string(&mut result)?;
Ok(result)
})
.transpose()
}
}
fn process_run_command(args: RunArgs) -> anyhow::Result<()> {
let base_dir =
tempdir().with_context(|| "failed to create base dir for storing runtime data")?;
let current_dir = Path::new(".");
let package_location = args.path.parent().unwrap_or(current_dir);
let package_name = args
.path
.file_stem()
.with_context(|| "path is malformed - it doesn't points to a file")?;
let tnt_init_file = base_dir.path().join("init.lua");
let tnt_tmpdir = base_dir.path().join("tmp");
let maybe_init_script = args.init_script_content()?;
let init_content = maybe_init_script.as_deref().unwrap_or(DEFAULT_LUA_INIT);
let mut file = File::create(&tnt_init_file)?;
file.write_all(init_content.as_bytes())?;
create_dir(tnt_tmpdir.as_path())
.with_context(|| "failed to create tmpdir for tarantool runtime")?;
let status = SysCommand::new("tarantool")
.arg(tnt_init_file)
.env("TARANTOOL_RUNNER_PACKAGE_FULLPATH", &args.path)
.env("TARANTOOL_RUNNER_PACKAGE_NAME", package_name)
.env("TARANTOOL_RUNNER_PACKAGE_LOCATION", package_location)
.env("TARANTOOL_RUNNER_PACKAGE_ENTRYPOINT", &args.entrypoint)
.env("TARANTOOL_RUNNER_BASEDIR", base_dir.path())
.env("TARANTOOL_RUNNER_TMPDIR", tnt_tmpdir)
.env(
"TARANTOOL_RUNNER_INPUT",
args.input.as_deref().unwrap_or(""),
)
.status()
.with_context(|| "failed to get status code from tarantool process")?;
if !status.success() {
bail!("exit code of tarantool process is not success: {status:?}")
}
Ok(())
}