lux_cli/
run_lua.rs

1use tokio::process::Command;
2
3use clap::Args;
4use eyre::{eyre, Result};
5use itertools::Itertools;
6use lux_lib::{
7    config::{Config, LuaVersion},
8    operations::{self, LuaBinary},
9    project::Project,
10    rockspec::LuaVersionCompatibility,
11};
12
13use crate::build::{self, Build};
14
15#[derive(Args, Default)]
16#[clap(disable_help_flag = true)]
17pub struct RunLua {
18    /// Arguments to pass to Lua. See `lua -h`.
19    args: Option<Vec<String>>,
20
21    /// Path to the Lua interpreter to use
22    #[arg(long)]
23    lua: Option<String>,
24
25    /// Print help
26    #[arg(long)]
27    help: bool,
28
29    #[clap(flatten)]
30    build: Build,
31}
32
33pub async fn run_lua(run_lua: RunLua, config: Config) -> Result<()> {
34    let lua_cmd = run_lua.lua.map(LuaBinary::Custom).unwrap_or(LuaBinary::Lua);
35
36    if run_lua.help {
37        return print_lua_help(&lua_cmd).await;
38    }
39
40    let project = Project::current()?;
41    let (lua_version, root, tree) = match &project {
42        Some(project) => (
43            project.toml().lua_version_matches(&config)?,
44            project.root().to_path_buf(),
45            project.tree(&config)?,
46        ),
47        None => {
48            let version = LuaVersion::from(&config)?.clone();
49            (
50                version.clone(),
51                std::env::current_dir()?,
52                config.user_tree(version)?,
53            )
54        }
55    };
56
57    if project.is_some() {
58        build::build(run_lua.build, config.clone()).await?;
59    }
60
61    operations::run_lua(
62        &root,
63        &tree,
64        lua_version,
65        lua_cmd,
66        &run_lua.args.unwrap_or_default(),
67    )
68    .await?;
69
70    Ok(())
71}
72
73async fn print_lua_help(lua_cmd: &LuaBinary) -> Result<()> {
74    let output = match Command::new(lua_cmd.to_string())
75        // HACK: This fails with exit 1, because lua doesn't actually have a help flag (╯°□°)╯︵ ┻━┻
76        .arg("-h")
77        .output()
78        .await
79    {
80        Ok(output) => Ok(output),
81        Err(err) => Err(eyre!("Failed to run {}: {}", lua_cmd, err)),
82    }?;
83    let lua_help = String::from_utf8_lossy(&output.stderr)
84        .lines()
85        .skip(2)
86        .map(|line| format!("  {}", line))
87        .collect_vec()
88        .join("\n");
89    print!(
90        "
91Usage: lx lua -- [LUA_OPTIONS] [SCRIPT [ARGS]]...
92
93Arguments:
94  [LUA_OPTIONS]...
95{}
96
97Options:
98  --lua       Path to the Lua interpreter to use
99  --no-lock   When building a project, ignore the project's lockfile and don't create one
100  -h, --help  Print help
101",
102        lua_help,
103    );
104    Ok(())
105}
106
107#[cfg(test)]
108mod test {
109    use std::path::PathBuf;
110
111    use lux_lib::config::ConfigBuilder;
112    use serial_test::serial;
113
114    use super::*;
115
116    #[serial]
117    #[tokio::test]
118    async fn test_run_lua() {
119        let args = RunLua {
120            args: Some(vec!["-v".into()]),
121            ..RunLua::default()
122        };
123        let temp: PathBuf = assert_fs::TempDir::new().unwrap().path().into();
124        let cwd = &std::env::current_dir().unwrap();
125        tokio::fs::create_dir_all(&temp).await.unwrap();
126        std::env::set_current_dir(&temp).unwrap();
127        let config = ConfigBuilder::new()
128            .unwrap()
129            .user_tree(Some(temp.clone()))
130            .build()
131            .unwrap();
132        run_lua(args, config).await.unwrap();
133        std::env::set_current_dir(cwd).unwrap();
134    }
135}