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