lux_cli/lib.rs
1use crate::{completion::Completion, format::Fmt, project::NewProject};
2use std::error::Error;
3use std::path::PathBuf;
4
5use add::Add;
6use build::Build;
7use check::Check;
8use clap::{Parser, Subcommand};
9use config::ConfigCmd;
10use debug::Debug;
11use doc::Doc;
12use download::Download;
13use exec::Exec;
14use generate_rockspec::GenerateRockspec;
15use info::Info;
16use install::Install;
17use install_rockspec::InstallRockspec;
18use lint::Lint;
19use list::ListCmd;
20use lux_lib::config::LuaVersion;
21use outdated::Outdated;
22use pack::Pack;
23use path::Path;
24use pin::ChangePin;
25use remove::Remove;
26use run::Run;
27use run_lua::RunLua;
28use search::Search;
29use shell::Shell;
30use test::Test;
31use uninstall::Uninstall;
32use update::Update;
33use upload::Upload;
34use url::Url;
35use which::Which;
36
37pub mod add;
38pub mod build;
39pub mod check;
40pub mod completion;
41pub mod config;
42pub mod debug;
43pub mod doc;
44pub mod download;
45pub mod exec;
46pub mod fetch;
47pub mod format;
48pub mod generate_rockspec;
49pub mod info;
50pub mod install;
51pub mod install_lua;
52pub mod install_rockspec;
53pub mod lint;
54pub mod list;
55pub mod outdated;
56pub mod pack;
57pub mod path;
58pub mod pin;
59pub mod project;
60pub mod purge;
61pub mod remove;
62pub mod run;
63pub mod run_lua;
64pub mod search;
65pub mod shell;
66pub mod test;
67pub mod uninstall;
68pub mod unpack;
69pub mod update;
70pub mod upload;
71pub mod utils;
72pub mod which;
73
74/// A luxurious package manager for Lua.
75#[derive(Parser)]
76#[command(author, version, about, long_about = None, arg_required_else_help = true)]
77pub struct Cli {
78 /// Enable the sub-repositories in luarocks servers for rockspecs of in-development versions.
79 #[arg(long)]
80 pub dev: bool,
81
82 /// Fetch rocks/rockspecs from this server (takes priority over config file).
83 #[arg(long, value_name = "server")]
84 pub server: Option<Url>,
85
86 /// Fetch rocks/rockspecs from this server in addition to the main server{n}
87 /// (overrides any entries in the config file).
88 #[arg(long, value_name = "extra-server")]
89 pub extra_servers: Option<Vec<Url>>,
90
91 /// Restrict downloads to paths matching the given URL.
92 #[arg(long, value_name = "url")]
93 pub only_sources: Option<String>,
94
95 /// Specify the luarocks server namespace to use.
96 #[arg(long, value_name = "namespace")]
97 pub namespace: Option<String>,
98
99 /// Specify the directory in which to install Lua{n}
100 /// if not found.
101 #[arg(long, value_name = "prefix")]
102 pub lua_dir: Option<PathBuf>,
103
104 /// Which Lua installation to use.{n}
105 /// Valid versions are: '5.1', '5.2', '5.3', '5.4', 'jit' and 'jit52'.
106 #[arg(long, value_name = "ver")]
107 pub lua_version: Option<LuaVersion>,
108
109 /// Which tree to operate on.
110 #[arg(long, value_name = "tree")]
111 pub tree: Option<PathBuf>,
112
113 /// Specifies the cache directory for e.g. luarocks manifests.
114 #[arg(long, value_name = "path")]
115 pub cache_path: Option<PathBuf>,
116
117 /// Do not use project tree even if running from a project folder.
118 #[arg(long)]
119 pub no_project: bool,
120
121 /// Override config variables.{n}
122 /// Example: `lx -v "LUA=/path/to/lua" ...`
123 #[arg(long, value_name = "variable", visible_short_aliases = ['v'], value_parser = parse_key_val::<String, String>)]
124 pub variables: Option<Vec<(String, String)>>,
125
126 /// Display verbose output of commands executed.
127 #[arg(long)]
128 pub verbose: bool,
129
130 /// Configure lux for installing Neovim packages.
131 #[arg(long)]
132 pub nvim: bool,
133
134 /// Timeout on network operations, in seconds.{n}
135 /// 0 means no timeout (wait forever). Default is 30.
136 #[arg(long, value_name = "seconds")]
137 pub timeout: Option<usize>,
138
139 /// Do not generate or update a `.luarc.json` file when building{n}
140 /// a project.
141 #[arg(long)]
142 pub no_luarc: bool,
143
144 #[command(subcommand)]
145 pub command: Commands,
146}
147
148#[derive(Subcommand)]
149pub enum Commands {
150 /// Add a dependency to the current project.
151 Add(Add),
152 /// Build/compile a project.
153 Build(Build),
154 /// [EXPERIMENTAL]{n}
155 /// Type check the current project based on EmmyLua/LuaCATS annotations.{n}
156 /// Respects `.emmyrc.json` and `.luarc.json` files in the project directory.
157 Check(Check),
158 /// Interact with the lux configuration.
159 #[command(subcommand, arg_required_else_help = true)]
160 Config(ConfigCmd),
161 /// Generate autocompletion scripts for the shell.{n}
162 /// Example: `lx completion zsh > ~/.zsh/completions/_lx`
163 Completion(Completion),
164 /// Internal commands for debugging Lux itself.
165 #[command(subcommand, arg_required_else_help = true)]
166 Debug(Debug),
167 /// Show documentation for an installed rock.
168 Doc(Doc),
169 /// Download a specific rock file from a luarocks server.
170 #[command(arg_required_else_help = true)]
171 Download(Download),
172 /// Formats the codebase with stylua.
173 Fmt(Fmt),
174 /// Generate a rockspec file from a project.
175 GenerateRockspec(GenerateRockspec),
176 /// Show metadata for any rock.
177 Info(Info),
178 /// Install a rock for use on the system.
179 #[command(arg_required_else_help = true)]
180 Install(Install),
181 /// Install a local rockspec for use on the system.
182 #[command(arg_required_else_help = true)]
183 InstallRockspec(InstallRockspec),
184 /// Manually install and manage Lua headers for various Lua versions.
185 InstallLua,
186 /// Lint the current project using `luacheck`.
187 Lint(Lint),
188 /// List currently installed rocks.
189 List(ListCmd),
190 /// Run lua, with the `LUA_PATH` and `LUA_CPATH` set to the specified lux tree.
191 Lua(RunLua),
192 /// Create a new Lua project.
193 New(NewProject),
194 /// List outdated rocks.
195 Outdated(Outdated),
196 /// Create a packed rock for distribution, packing sources or binaries.
197 Pack(Pack),
198 /// Return the currently configured package path.
199 Path(Path),
200 /// Pin an existing rock, preventing any updates to the package.
201 Pin(ChangePin),
202 /// Remove all installed rocks from a tree.
203 Purge,
204 /// Remove a rock from the current project's lux.toml dependencies.
205 Remove(Remove),
206 /// Run the current project with the provided arguments.
207 Run(Run),
208 /// Execute a command that has been installed with lux.
209 /// If the command is not found, a package named after the command
210 /// will be installed.
211 Exec(Exec),
212 /// Query the luarocks servers.
213 #[command(arg_required_else_help = true)]
214 Search(Search),
215 /// Run the test suite in the current project directory.{n}
216 /// Lux supports the following test backends, specified by the `[test]` table in the lux.toml:{n}
217 /// {n}
218 /// - busted:{n}
219 /// {n}
220 /// https://lunarmodules.github.io/busted/{n}
221 /// {n}
222 /// Example:{n}
223 /// {n}
224 /// ```toml{n}
225 /// [test]{n}
226 /// type = "busted"{n}
227 /// flags = [ ] # Optional CLI flags to pass to busted{n}
228 /// ```{n}
229 /// {n}
230 /// `lx test` will default to using `busted` if no test backend is specified and:{n}
231 /// * there is a `.busted` file in the project root{n}
232 /// * or `busted` is one of the `test_dependencies`).{n}
233 /// {n}
234 /// - busted-nlua:{n}:
235 /// {n}
236 /// [currently broken on macOS and Windows]
237 /// A build backend for running busted tests with Neovim as the Lua interpreter.
238 /// Used for testing Neovim plugins.
239 /// {n}
240 /// Example:{n}
241 /// {n}
242 /// ```toml{n}
243 /// [test]{n}
244 /// type = "busted-nlua"{n}
245 /// flags = [ ] # Optional CLI flags to pass to busted{n}
246 /// ```{n}
247 /// {n}
248 /// `lx test` will default to using `busted-nlua` if no test backend is specified and:{n}
249 /// * there is a `.busted` file in the project root{n}
250 /// * or `busted` and `nlua` are `test_dependencies`.{n}
251 /// {n}
252 /// - command:{n}
253 /// {n}
254 /// Name/file name of a shell command that will run the test suite.{n}
255 /// Example:{n}
256 /// {n}
257 /// ```toml{n}
258 /// [test]{n}
259 /// type = "command"{n}
260 /// command = "make"{n}
261 /// flags = [ "test" ]{n}
262 /// ```{n}
263 /// {n}
264 /// - script:{n}
265 /// {n}
266 /// Relative path to a Lua script that will run the test suite.{n}
267 /// Example:{n}
268 /// {n}
269 /// ```toml{n}
270 /// [test]{n}
271 /// type = "script"{n}
272 /// script = "tests.lua" # Expects a tests.lua file in the project root{n}
273 /// flags = [ ] # Optional arguments passed to the test script{n}
274 /// ```{n}
275 Test(Test),
276 /// Uninstall a rock from the system.
277 Uninstall(Uninstall),
278 /// Unpins an existing rock, allowing updates to alter the package.
279 Unpin(ChangePin),
280 /// Updates all rocks in a project.
281 Update(Update),
282 /// Generate a Lua rockspec for a Lux project and upload it to the public luarocks repository.{n}
283 /// You can specify a source template for release and dev packages in the lux.toml.{n}
284 /// {n}
285 /// Example:{n}
286 /// {n}
287 /// ```toml{n}
288 /// [source]{n}
289 /// url = "https://host.com/owner/$(PACKAGE)/refs/tags/$(REF).zip"{n}
290 /// dev = "git+https://host.com/owner/$(PACKAGE).git"{n}
291 /// ```{n}
292 /// {n}
293 /// You can use the following variables in the source template:{n}
294 /// {n}
295 /// - $(PACKAGE): The package name.{n}
296 /// - $(VERSION): The package version.{n}
297 /// - $(REF): The git tag or revision (if in a git repository).{n}
298 /// - You may also specify environment variables with `$(<VAR_NAME>)`.{n}
299 /// {n}
300 /// If the `version` is not set in the lux.toml, lux will search the current
301 /// commit for SemVer tags and if found, will use it to generate the package version.
302 Upload(Upload),
303 /// Tell which file corresponds to a given module name.
304 Which(Which),
305 /// Spawns an interactive shell with PATH, LUA_PATH, LUA_CPATH and LUA_INIT set.
306 Shell(Shell),
307}
308
309/// Parse a key=value pair.
310fn parse_key_val<T, U>(s: &str) -> Result<(T, U), Box<dyn Error + Send + Sync + 'static>>
311where
312 T: std::str::FromStr,
313 T::Err: Error + Send + Sync + 'static,
314 U: std::str::FromStr,
315 U::Err: Error + Send + Sync + 'static,
316{
317 let pos = s
318 .find('=')
319 .ok_or_else(|| format!("invalid KEY=value: no `=` found in `{s}`"))?;
320 Ok((s[..pos].parse()?, s[pos + 1..].parse()?))
321}