1use tokio::process::Command;
2
3use clap::Args;
4use eyre::{eyre, Result};
5use itertools::Itertools;
6use lux_lib::{
7 config::{Config, LuaVersion},
8 lua_installation::LuaBinary,
9 operations,
10 project::Project,
11 rockspec::LuaVersionCompatibility,
12};
13
14use crate::build::{self, Build};
15
16#[derive(Args, Default)]
17#[clap(disable_help_flag = true)]
18pub struct RunLua {
19 #[arg(long)]
20 test: bool,
21
22 #[arg(long)]
23 build: bool,
24
25 args: Option<Vec<String>>,
27
28 #[arg(long)]
30 lua: Option<String>,
31
32 #[arg(long)]
34 help: bool,
35
36 #[clap(flatten)]
37 build_args: Build,
38}
39
40pub async fn run_lua(run_lua: RunLua, config: Config) -> Result<()> {
41 let project = Project::current()?;
42 let (lua_version, root, tree) = match &project {
43 Some(project) => (
44 project.toml().lua_version_matches(&config)?,
45 project.root().to_path_buf(),
46 project.tree(&config)?,
47 ),
48 None => {
49 let version = LuaVersion::from(&config)?.clone();
50 (
51 version.clone(),
52 std::env::current_dir()?,
53 config.user_tree(version)?,
54 )
55 }
56 };
57
58 let lua_cmd = run_lua
59 .lua
60 .map(LuaBinary::Custom)
61 .unwrap_or(LuaBinary::new(lua_version, &config));
62
63 if run_lua.help {
64 return print_lua_help(&lua_cmd).await;
65 }
66
67 if project.is_some() {
68 build::build(run_lua.build_args, config.clone()).await?;
69 }
70
71 let args = &run_lua.args.unwrap_or_default();
72
73 operations::RunLua::new()
74 .root(&root)
75 .tree(&tree)
76 .config(&config)
77 .lua_cmd(lua_cmd)
78 .args(args)
79 .prepend_test_paths(run_lua.test)
80 .prepend_build_paths(run_lua.build)
81 .run_lua()
82 .await?;
83
84 Ok(())
85}
86
87async fn print_lua_help(lua_cmd: &LuaBinary) -> Result<()> {
88 let output = match Command::new(lua_cmd.to_string())
89 .arg("-h")
91 .output()
92 .await
93 {
94 Ok(output) => Ok(output),
95 Err(err) => Err(eyre!("Failed to run {}: {}", lua_cmd, err)),
96 }?;
97 let lua_help = String::from_utf8_lossy(&output.stderr)
98 .lines()
99 .skip(2)
100 .map(|line| format!(" {}", line))
101 .collect_vec()
102 .join("\n");
103 print!(
104 "
105Usage: lx lua -- [LUA_OPTIONS] [SCRIPT [ARGS]]...
106
107Arguments:
108 [LUA_OPTIONS]...
109{}
110
111Options:
112 --lua Path to the Lua interpreter to use
113 --no-lock When building a project, ignore the project's lockfile and don't create one
114 --test Prepend test dependencies to the LUA_PATH and LUA_CPATH
115 --build Prepend build dependencies to the LUA_PATH and LUA_CPATH
116 -h, --help Print help
117",
118 lua_help,
119 );
120 Ok(())
121}
122
123#[cfg(test)]
124mod test {
125 use std::path::PathBuf;
126
127 use lux_lib::config::ConfigBuilder;
128 use serial_test::serial;
129
130 use super::*;
131
132 #[serial]
133 #[tokio::test]
134 async fn test_run_lua() {
135 let args = RunLua {
136 args: Some(vec!["-v".into()]),
137 ..RunLua::default()
138 };
139 let temp: PathBuf = assert_fs::TempDir::new().unwrap().path().into();
140 let cwd = &std::env::current_dir().unwrap();
141 tokio::fs::create_dir_all(&temp).await.unwrap();
142 std::env::set_current_dir(&temp).unwrap();
143 let config = ConfigBuilder::new()
144 .unwrap()
145 .user_tree(Some(temp.clone()))
146 .build()
147 .unwrap();
148 run_lua(args, config).await.unwrap();
149 std::env::set_current_dir(cwd).unwrap();
150 }
151}