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