1use std::{
9 ffi::{OsStr, OsString},
10 io::{self, Write},
11 path::{Path, PathBuf},
12 process,
13};
14
15use clap::{Arg, Command};
16use clap_complete::Shell;
17use uucore::display::Quotable;
18
19const VERSION: &str = env!("CARGO_PKG_VERSION");
20
21include!(concat!(env!("OUT_DIR"), "/uutils_map.rs"));
22
23fn usage<T>(utils: &UtilityMap<T>, name: &str) {
24 println!("{name} {VERSION} (multi-call binary)\n");
25 println!("Usage: {name} [function [arguments...]]");
26 println!(" {name} --list\n");
27 println!("Options:");
28 println!(" --list lists all defined functions, one per row\n");
29 println!("Currently defined functions:\n");
30 #[allow(clippy::map_clone)]
31 let mut utils: Vec<&str> = utils.keys().map(|&s| s).collect();
32 utils.sort_unstable();
33 let display_list = utils.join(", ");
34 let width = 100 - 4 * 2; println!(
36 "{}",
37 textwrap::indent(&textwrap::fill(&display_list, width), " ")
38 );
39}
40
41fn binary_path(args: &mut impl Iterator<Item = OsString>) -> PathBuf {
44 match args.next() {
45 Some(ref s) if !s.is_empty() => PathBuf::from(s),
46 _ => std::env::current_exe().unwrap(),
47 }
48}
49
50fn name(binary_path: &Path) -> Option<&str> {
51 binary_path.file_stem()?.to_str()
52}
53
54#[allow(clippy::cognitive_complexity)]
55fn main() {
56 uucore::panic::mute_sigpipe_panic();
57
58 let utils = util_map();
59 let mut args = uucore::args_os();
60
61 let binary = binary_path(&mut args);
62 let binary_as_util = name(&binary).unwrap_or_else(|| {
63 usage(&utils, "<unknown binary name>");
64 process::exit(0);
65 });
66
67 if let Some(&(uumain, _)) = utils.get(binary_as_util) {
69 process::exit(uumain((vec![binary.into()].into_iter()).chain(args)));
70 }
71
72 let util_name = if let Some(util) = utils.keys().find(|util| {
75 binary_as_util.ends_with(*util)
76 && !binary_as_util[..binary_as_util.len() - (*util).len()]
77 .ends_with(char::is_alphanumeric)
78 }) {
79 Some(OsString::from(*util))
81 } else {
82 uucore::set_utility_is_second_arg();
84 args.next()
85 };
86
87 if let Some(util_os) = util_name {
89 fn not_found(util: &OsStr) -> ! {
90 println!("{}: function/utility not found", util.maybe_quote());
91 process::exit(1);
92 }
93
94 let Some(util) = util_os.to_str() else {
95 not_found(&util_os)
96 };
97
98 match util {
99 "completion" => gen_completions(args, &utils),
100 "manpage" => gen_manpage(args, &utils),
101 "--list" => {
102 let mut utils: Vec<_> = utils.keys().collect();
103 utils.sort();
104 for util in utils {
105 println!("{util}");
106 }
107 process::exit(0);
108 }
109 _ => {}
111 };
112
113 match utils.get(util) {
114 Some(&(uumain, _)) => {
115 process::exit(uumain((vec![util_os].into_iter()).chain(args)));
116 }
117 None => {
118 if util == "--help" || util == "-h" {
119 if let Some(util_os) = args.next() {
121 let Some(util) = util_os.to_str() else {
122 not_found(&util_os)
123 };
124
125 match utils.get(util) {
126 Some(&(uumain, _)) => {
127 let code = uumain(
128 (vec![util_os, OsString::from("--help")].into_iter())
129 .chain(args),
130 );
131 io::stdout().flush().expect("could not flush stdout");
132 process::exit(code);
133 }
134 None => not_found(&util_os),
135 }
136 }
137 usage(&utils, binary_as_util);
138 process::exit(0);
139 } else {
140 not_found(&util_os);
141 }
142 }
143 }
144 } else {
145 usage(&utils, binary_as_util);
147 process::exit(0);
148 }
149}
150
151fn gen_completions<T: uucore::Args>(
155 args: impl Iterator<Item = OsString>,
156 util_map: &UtilityMap<T>,
157) -> ! {
158 let all_utilities: Vec<_> = std::iter::once("coreutils")
159 .chain(util_map.keys().copied())
160 .collect();
161
162 let matches = Command::new("completion")
163 .about("Prints completions to stdout")
164 .arg(
165 Arg::new("utility")
166 .value_parser(clap::builder::PossibleValuesParser::new(all_utilities))
167 .required(true),
168 )
169 .arg(
170 Arg::new("shell")
171 .value_parser(clap::builder::EnumValueParser::<Shell>::new())
172 .required(true),
173 )
174 .get_matches_from(std::iter::once(OsString::from("completion")).chain(args));
175
176 let utility = matches.get_one::<String>("utility").unwrap();
177 let shell = *matches.get_one::<Shell>("shell").unwrap();
178
179 let mut command = if utility == "coreutils" {
180 gen_coreutils_app(util_map)
181 } else {
182 util_map.get(utility).unwrap().1()
183 };
184 let bin_name = std::env::var("PROG_PREFIX").unwrap_or_default() + utility;
185
186 clap_complete::generate(shell, &mut command, bin_name, &mut io::stdout());
187 io::stdout().flush().unwrap();
188 process::exit(0);
189}
190
191fn gen_manpage<T: uucore::Args>(
195 args: impl Iterator<Item = OsString>,
196 util_map: &UtilityMap<T>,
197) -> ! {
198 let all_utilities: Vec<_> = std::iter::once("coreutils")
199 .chain(util_map.keys().copied())
200 .collect();
201
202 let matches = Command::new("manpage")
203 .about("Prints manpage to stdout")
204 .arg(
205 Arg::new("utility")
206 .value_parser(clap::builder::PossibleValuesParser::new(all_utilities))
207 .required(true),
208 )
209 .get_matches_from(std::iter::once(OsString::from("manpage")).chain(args));
210
211 let utility = matches.get_one::<String>("utility").unwrap();
212
213 let command = if utility == "coreutils" {
214 gen_coreutils_app(util_map)
215 } else {
216 util_map.get(utility).unwrap().1()
217 };
218
219 let man = clap_mangen::Man::new(command);
220 man.render(&mut io::stdout())
221 .expect("Man page generation failed");
222 io::stdout().flush().unwrap();
223 process::exit(0);
224}
225
226fn gen_coreutils_app<T: uucore::Args>(util_map: &UtilityMap<T>) -> Command {
229 let mut command = Command::new("coreutils");
230 for (name, (_, sub_app)) in util_map {
231 let about = sub_app()
234 .get_about()
235 .expect("Could not get the 'about'")
236 .to_string();
237 let sub_app = Command::new(name).about(about);
238 command = command.subcommand(sub_app);
239 }
240 command
241}
242