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")]
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}