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 #[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 Full(FullArgs),
31 Lua,
34 C,
37 Bin,
40 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 #[clap(default_value_t = false)]
55 #[arg(long)]
56 no_bin: bool,
57
58 #[clap(default_value_t = false)]
62 #[arg(long)]
63 no_loader: bool,
64
65 #[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}