Skip to main content

lux_cli/
path.rs

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