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