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