lux_cli/
run_lua.rs

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