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 args: Option<Vec<String>>,
23
24 #[arg(long)]
26 lua: Option<String>,
27
28 #[arg(long)]
30 test: bool,
31
32 #[arg(long)]
34 build: bool,
35
36 #[clap(default_value_t = false)]
40 #[arg(long)]
41 no_loader: bool,
42
43 #[clap(flatten)]
44 build_args: Build,
45
46 #[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 .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}