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