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