diff --git a/Cargo.toml b/Cargo.toml index d8cf84c1..62eb7b41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ version = "0.0.1" authors = ["uutils developers"] license = "MIT" description = "sed ~ implemented as universal (cross-platform) utils, written in Rust" -default-run = "sedapp" +default-run = "sed" homepage = "https://github.com/uutils/sed" repository = "https://github.com/uutils/sed" @@ -96,8 +96,8 @@ phf_codegen = { workspace = true } path = "src/lib.rs" [[bin]] -name = "sedapp" -path = "src/bin/sedapp.rs" +name = "sed" +path = "src/bin/sed.rs" [[bin]] name = "uudoc" diff --git a/README.md b/README.md index eca652d7..806525e9 100644 --- a/README.md +++ b/README.md @@ -39,11 +39,7 @@ cargo build --release cargo run --release ``` -To call the binary `sed`, create a symlink like: - -```bash -ln -s target/release/sedapp sed -``` +The binary is named `sed` in `target/release/sed`. ## Extensions and incompatibilities ### Supported GNU extensions diff --git a/src/bin/sed.rs b/src/bin/sed.rs new file mode 100644 index 00000000..03dfafdd --- /dev/null +++ b/src/bin/sed.rs @@ -0,0 +1,39 @@ +// This file is part of the uutils sed package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +use std::ffi::OsString; +use std::process; + +fn main() { + // Detect multicall vs single-call BEFORE any uucore calls + // This must happen first to set the utility name detection mode correctly + let raw_args: Vec = std::env::args_os().collect(); + if raw_args.len() > 1 && raw_args[1] == "sed" { + // Multicall binary mode: tell uucore to use args[1] for util_name() + uucore::set_utility_is_second_arg(); + } + + uucore::panic::mute_sigpipe_panic(); + + let mut args = raw_args; + + // Strip .exe extension from binary name on Windows for consistent error messages + #[cfg(windows)] + if let Some(binary_name) = args.get_mut(0) { + let binary_str = binary_name.to_string_lossy(); + if let Some(stripped) = binary_str.strip_suffix(".exe") { + *binary_name = OsString::from(stripped); + } + } + + // Handle both single-call and multi-call binary compatibility + // If first argument after binary name is "sed", skip it (multi-call compatibility) + if args.len() > 1 && args[1] == "sed" { + args.remove(1); + } + + let code = sed::sed::uumain(args.into_iter()); + process::exit(code); +} diff --git a/src/bin/sedapp.rs b/src/bin/sedapp.rs deleted file mode 100644 index 6fbd6925..00000000 --- a/src/bin/sedapp.rs +++ /dev/null @@ -1,222 +0,0 @@ -// This file is part of the uutils sed package. -// -// For the full copyright and license information, please view the LICENSE -// file that was distributed with this source code. - -// spell-checker:ignore manpages mangen - -use clap::{Arg, Command}; -use clap_complete::Shell; -use std::cmp; -use std::ffi::OsStr; -use std::ffi::OsString; -use std::io::{self, Write}; -use std::path::{Path, PathBuf}; -use std::process; -use uucore::display::Quotable; - -const VERSION: &str = env!("CARGO_PKG_VERSION"); - -include!(concat!(env!("OUT_DIR"), "/uutils_map.rs")); - -fn usage(utils: &UtilityMap, name: &str) { - println!("{name} {VERSION} (multi-call binary)\n"); - println!("Usage: {name} [function [arguments...]]\n"); - println!("Currently defined functions:\n"); - #[allow(clippy::map_clone)] - let mut utils: Vec<&str> = utils.keys().map(|&s| s).collect(); - utils.sort_unstable(); - let display_list = utils.join(", "); - let width = cmp::min(textwrap::termwidth(), 100) - 4 * 2; // (opinion/heuristic) max 100 chars wide with 4 character side indentions - println!( - "{}", - textwrap::indent(&textwrap::fill(&display_list, width), " ") - ); -} - -fn binary_path(args: &mut impl Iterator) -> PathBuf { - match args.next() { - Some(ref s) if !s.is_empty() => PathBuf::from(s), - _ => std::env::current_exe().unwrap(), - } -} - -fn name(binary_path: &Path) -> Option<&str> { - binary_path.file_stem()?.to_str() -} - -#[allow(clippy::cognitive_complexity)] -fn main() { - uucore::panic::mute_sigpipe_panic(); - - let utils = util_map(); - let mut args = uucore::args_os(); - - let binary = binary_path(&mut args); - let binary_as_util = name(&binary).unwrap_or_else(|| { - usage(&utils, ""); - process::exit(0); - }); - - // binary name equals util name? - if let Some(&(uumain, _)) = utils.get(binary_as_util) { - process::exit(uumain((vec![binary.into()].into_iter()).chain(args))); - } - - // binary name equals prefixed util name? - // * prefix/stem may be any string ending in a non-alphanumeric character - let util_name = if let Some(util) = utils.keys().find(|util| { - binary_as_util.ends_with(*util) - && !binary_as_util[..binary_as_util.len() - (*util).len()] - .ends_with(char::is_alphanumeric) - }) { - // prefixed util => replace 0th (aka, executable name) argument - Some(OsString::from(*util)) - } else { - // unmatched binary name => regard as multi-binary container and advance argument list - uucore::set_utility_is_second_arg(); - args.next() - }; - - // 0th argument equals util name? - if let Some(util_os) = util_name { - fn not_found(util: &OsStr) -> ! { - println!("{}: function/utility not found", util.maybe_quote()); - process::exit(1); - } - - let Some(util) = util_os.to_str() else { - not_found(&util_os) - }; - - if util == "completion" { - gen_completions(args, &utils); - } - - if util == "manpage" { - gen_manpage(args, &utils); - } - - match utils.get(util) { - Some(&(uumain, _)) => { - process::exit(uumain((vec![util_os].into_iter()).chain(args))); - } - None => { - if util == "--help" || util == "-h" { - // see if they want help on a specific util - if let Some(util_os) = args.next() { - let Some(util) = util_os.to_str() else { - not_found(&util_os) - }; - - match utils.get(util) { - Some(&(uumain, _)) => { - let code = uumain( - (vec![util_os, OsString::from("--help")].into_iter()) - .chain(args), - ); - io::stdout().flush().expect("could not flush stdout"); - process::exit(code); - } - None => not_found(&util_os), - } - } - usage(&utils, binary_as_util); - process::exit(0); - } else { - not_found(&util_os); - } - } - } - } else { - // no arguments provided - usage(&utils, binary_as_util); - process::exit(0); - } -} - -/// Prints completions for the utility in the first parameter for the shell in the second parameter to stdout -fn gen_completions( - args: impl Iterator, - util_map: &UtilityMap, -) -> ! { - let all_utilities: Vec<_> = std::iter::once("sedapp") - .chain(util_map.keys().copied()) - .collect(); - - let matches = Command::new("completion") - .about("Prints completions to stdout") - .arg( - Arg::new("utility") - .value_parser(clap::builder::PossibleValuesParser::new(all_utilities)) - .required(true), - ) - .arg( - Arg::new("shell") - .value_parser(clap::builder::EnumValueParser::::new()) - .required(true), - ) - .get_matches_from(std::iter::once(OsString::from("completion")).chain(args)); - - let utility = matches.get_one::("utility").unwrap(); - let shell = *matches.get_one::("shell").unwrap(); - - let mut command = if utility == "sedapp" { - gen_sed_app(util_map) - } else { - util_map.get(utility).unwrap().1() - }; - let bin_name = std::env::var("PROG_PREFIX").unwrap_or_default() + utility; - - clap_complete::generate(shell, &mut command, bin_name, &mut io::stdout()); - io::stdout().flush().unwrap(); - process::exit(0); -} - -/// Generate the manpage for the utility in the first parameter -fn gen_manpage( - args: impl Iterator, - util_map: &UtilityMap, -) -> ! { - let all_utilities: Vec<_> = std::iter::once("sedapp") - .chain(util_map.keys().copied()) - .collect(); - - let matches = Command::new("manpage") - .about("Prints manpage to stdout") - .arg( - Arg::new("utility") - .value_parser(clap::builder::PossibleValuesParser::new(all_utilities)) - .required(true), - ) - .get_matches_from(std::iter::once(OsString::from("manpage")).chain(args)); - - let utility = matches.get_one::("utility").unwrap(); - - let command = if utility == "sedapp" { - gen_sed_app(util_map) - } else { - util_map.get(utility).unwrap().1() - }; - - let man = clap_mangen::Man::new(command); - man.render(&mut io::stdout()) - .expect("Man page generation failed"); - io::stdout().flush().unwrap(); - process::exit(0); -} - -fn gen_sed_app(util_map: &UtilityMap) -> Command { - let mut command = Command::new("sedapp"); - for (name, (_, sub_app)) in util_map { - // Recreate a small subcommand with only the relevant info - // (name & short description) - let about = sub_app() - .get_about() - .expect("Could not get the 'about'") - .to_string(); - let sub_app = Command::new(name).about(about); - command = command.subcommand(sub_app); - } - command -} diff --git a/src/sed/mod.rs b/src/sed/mod.rs index 9ec10fa1..88356e8a 100644 --- a/src/sed/mod.rs +++ b/src/sed/mod.rs @@ -46,7 +46,12 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> { #[allow(clippy::cognitive_complexity)] pub fn uu_app() -> Command { - Command::new(uucore::util_name()) + #[cfg(windows)] + let util_name = "sed"; + #[cfg(not(windows))] + let util_name = uucore::util_name(); + + Command::new(util_name) .version(crate_version!()) .about(ABOUT) .override_usage(format_usage(USAGE)) diff --git a/tests/tests.rs b/tests/tests.rs index 0302e208..06fd1462 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -4,7 +4,7 @@ // file that was distributed with this source code. use std::env; -pub const TESTS_BINARY: &str = env!("CARGO_BIN_EXE_sedapp"); +pub const TESTS_BINARY: &str = env!("CARGO_BIN_EXE_sed"); // Use the ctor attribute to run this function before any tests #[ctor::ctor] @@ -12,6 +12,10 @@ fn init() { unsafe { // Necessary for uutests to be able to find the binary std::env::set_var("UUTESTS_BINARY_PATH", TESTS_BINARY); + // For single-call binaries, tell uutests not to auto-add the utility name + std::env::remove_var("UUTESTS_UTIL_NAME"); + std::env::set_var("UUTESTS_UTIL_NAME", ""); + std::env::set_var("UUTILS_MULTICALL", "0"); } }