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