lux_cli/
path.rs

1use clap::Subcommand;
2use eyre::Result;
3use lux_lib::{
4    config::Config,
5    path::{BinPath, PackagePath, Paths},
6};
7use strum_macros::{Display, EnumString, VariantNames};
8
9use clap::{Args, ValueEnum};
10
11use crate::utils::project::current_project_or_user_tree;
12
13#[derive(Args)]
14pub struct Path {
15    #[command(subcommand)]
16    cmd: Option<PathCmd>,
17
18    /// Prepend the rocks tree paths to the system paths.
19    #[clap(default_value_t = false)]
20    #[arg(long)]
21    prepend: bool,
22}
23
24#[derive(Subcommand, PartialEq, Eq, Debug, Clone)]
25#[clap(rename_all = "kebab_case")]
26enum PathCmd {
27    /// Generate an export statement for all paths
28    /// (formatted as a shell command). [Default]
29    Full(FullArgs),
30    /// Generate a `LUA_PATH` expression for `lua` libraries in the lux tree.
31    /// (not formatted as a shell command)
32    Lua,
33    /// Generate a `LUA_CPATH` expression for native `lib` libraries in the lux tree.
34    /// (not formatted as a shell command)
35    C,
36    /// Generate a `PATH` expression for `bin` executables in the lux tree.
37    /// (not formatted as a shell command)
38    Bin,
39    /// Generate a `LUA_INIT` expression for the lux loader.
40    /// (not formatted as a shell command)
41    Init,
42}
43
44impl Default for PathCmd {
45    fn default() -> Self {
46        Self::Full(FullArgs::default())
47    }
48}
49
50#[derive(Args, PartialEq, Eq, Debug, Clone, Default)]
51struct FullArgs {
52    /// Do not export `PATH` (`bin` paths).
53    #[clap(default_value_t = false)]
54    #[arg(long)]
55    no_bin: bool,
56
57    /// Do not add `require('lux').loader()` to `LUA_INIT`.
58    /// If a rock has conflicting transitive dependencies,
59    /// disabling the Lux loader may result in the wrong modules being loaded.
60    #[clap(default_value_t = false)]
61    #[arg(long)]
62    no_loader: bool,
63
64    /// The shell to format for.
65    #[clap(default_value_t = Shell::default())]
66    #[arg(long)]
67    shell: Shell,
68}
69
70#[derive(EnumString, VariantNames, Display, ValueEnum, PartialEq, Eq, Debug, Clone)]
71#[strum(serialize_all = "lowercase")]
72enum Shell {
73    Posix,
74    Fish,
75    Nu,
76}
77
78impl Default for Shell {
79    fn default() -> Self {
80        Self::Posix
81    }
82}
83
84pub async fn path(path_data: Path, config: Config) -> Result<()> {
85    let tree = current_project_or_user_tree(&config)?;
86    let paths = Paths::new(&tree)?;
87    let cmd = path_data.cmd.unwrap_or_default();
88    let prepend = path_data.prepend;
89    match cmd {
90        PathCmd::Full(args) => {
91            let mut result = String::new();
92            let no_loader = args.no_loader || {
93                if tree.version().lux_lib_dir().is_none() {
94                    eprintln!(
95                        "⚠️ WARNING: lux-lua library not found.
96Cannot use the `lux.loader`.
97To suppress this warning, set the `--no-loader` option.
98                "
99                    );
100                    true
101                } else {
102                    false
103                }
104            };
105            let shell = args.shell;
106            let package_path = mk_package_path(&paths, prepend);
107            if !package_path.is_empty() {
108                result.push_str(format_export(&shell, "LUA_PATH", &package_path).as_str());
109                result.push('\n')
110            }
111            let package_cpath = mk_package_cpath(&paths, prepend);
112            if !package_cpath.is_empty() {
113                result.push_str(format_export(&shell, "LUA_CPATH", &package_cpath).as_str());
114                result.push('\n')
115            }
116            if !args.no_bin {
117                let path = mk_bin_path(&paths, prepend)?;
118                if !path.is_empty() {
119                    result.push_str(format_export(&shell, "PATH", &path).as_str());
120                    result.push('\n')
121                }
122            }
123            if !no_loader {
124                result.push_str(format_export(&shell, "LUA_INIT", &paths.init()).as_str());
125                result.push('\n')
126            }
127            println!("{}", &result);
128        }
129        PathCmd::Lua => println!("{}", &mk_package_path(&paths, prepend)),
130        PathCmd::C => println!("{}", &mk_package_cpath(&paths, prepend)),
131        PathCmd::Bin => println!("{}", &mk_bin_path(&paths, prepend)?),
132        PathCmd::Init => println!("{}", paths.init()),
133    }
134    Ok(())
135}
136
137fn mk_package_path(paths: &Paths, prepend: bool) -> PackagePath {
138    if prepend {
139        paths.package_path_prepended()
140    } else {
141        paths.package_path().clone()
142    }
143}
144
145fn mk_package_cpath(paths: &Paths, prepend: bool) -> PackagePath {
146    if prepend {
147        paths.package_cpath_prepended()
148    } else {
149        paths.package_cpath().clone()
150    }
151}
152
153fn mk_bin_path(paths: &Paths, prepend: bool) -> Result<BinPath> {
154    let mut result = if prepend {
155        BinPath::from_env()
156    } else {
157        BinPath::default()
158    };
159    result.prepend(paths.path());
160    Ok(result)
161}
162
163fn format_export<D>(shell: &Shell, var_name: &str, var: &D) -> String
164where
165    D: std::fmt::Display,
166{
167    match shell {
168        Shell::Posix => format!("export {var_name}='{var}';"),
169        Shell::Fish => format!("set -x {var_name} \"{var}\";"),
170        Shell::Nu => format!("$env.{var_name} = \"{var}\";"),
171    }
172}