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 #[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 Full(FullArgs),
30 Lua,
33 C,
36 Bin,
39 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 #[clap(default_value_t = false)]
54 #[arg(long)]
55 no_bin: bool,
56
57 #[clap(default_value_t = false)]
61 #[arg(long)]
62 no_loader: bool,
63
64 #[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")]
72#[derive(Default)]
73enum Shell {
74 #[default]
75 Posix,
76 Fish,
77 Nu,
78}
79
80pub async fn path(path_data: Path, config: Config) -> Result<()> {
81 let tree = current_project_or_user_tree(&config)?;
82 let paths = Paths::new(&tree)?;
83 let cmd = path_data.cmd.unwrap_or_default();
84 let prepend = path_data.prepend;
85 match cmd {
86 PathCmd::Full(args) => {
87 let mut result = String::new();
88 let no_loader = args.no_loader || {
89 if tree.version().lux_lib_dir().is_none() {
90 eprintln!(
91 "⚠️ WARNING: lux-lua library not found.
92Cannot use the `lux.loader`.
93To suppress this warning, run `lx path full --no-loader`.
94 "
95 );
96 true
97 } else {
98 false
99 }
100 };
101 let shell = args.shell;
102 let package_path = mk_package_path(&paths, prepend);
103 if !package_path.is_empty() {
104 result.push_str(format_export(&shell, "LUA_PATH", &package_path).as_str());
105 result.push('\n')
106 }
107 let package_cpath = mk_package_cpath(&paths, prepend);
108 if !package_cpath.is_empty() {
109 result.push_str(format_export(&shell, "LUA_CPATH", &package_cpath).as_str());
110 result.push('\n')
111 }
112 if !args.no_bin {
113 let path = mk_bin_path(&paths, prepend)?;
114 if !path.is_empty() {
115 result.push_str(format_export(&shell, "PATH", &path).as_str());
116 result.push('\n')
117 }
118 }
119 if !no_loader {
120 result.push_str(format_export(&shell, "LUA_INIT", &paths.init()).as_str());
121 result.push('\n')
122 }
123 println!("{}", &result);
124 }
125 PathCmd::Lua => println!("{}", &mk_package_path(&paths, prepend)),
126 PathCmd::C => println!("{}", &mk_package_cpath(&paths, prepend)),
127 PathCmd::Bin => println!("{}", &mk_bin_path(&paths, prepend)?),
128 PathCmd::Init => println!("{}", paths.init()),
129 }
130 Ok(())
131}
132
133fn mk_package_path(paths: &Paths, prepend: bool) -> PackagePath {
134 if prepend {
135 paths.package_path_prepended()
136 } else {
137 paths.package_path().clone()
138 }
139}
140
141fn mk_package_cpath(paths: &Paths, prepend: bool) -> PackagePath {
142 if prepend {
143 paths.package_cpath_prepended()
144 } else {
145 paths.package_cpath().clone()
146 }
147}
148
149fn mk_bin_path(paths: &Paths, prepend: bool) -> Result<BinPath> {
150 let mut result = if prepend {
151 BinPath::from_env()
152 } else {
153 BinPath::default()
154 };
155 result.prepend(paths.path());
156 Ok(result)
157}
158
159fn format_export<D>(shell: &Shell, var_name: &str, var: &D) -> String
160where
161 D: std::fmt::Display,
162{
163 match shell {
164 Shell::Posix => format!("export {var_name}='{var}';"),
165 Shell::Fish => format!("set -x {var_name} \"{var}\";"),
166 Shell::Nu => format!("$env.{var_name} = \"{var}\";"),
167 }
168}