1use clap::Subcommand;
2use lux_lib::{
3 config::Config,
4 path::{BinPath, PackagePath, Paths},
5 tree::InstallTree,
6};
7use miette::Result;
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 tracing::warn!(
92 r#"lux-lua library not found.
93Cannot use the `lux.loader`.
94To suppress this warning, run `lx path full --no-loader`."#
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}