lux_cli/
path.rs

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