From 1b4b449d49b98a4e2c3d0da85592b39dc327d70c Mon Sep 17 00:00:00 2001 From: Sylvestre Ledru <sylvestre@debian.org> Date: Fri, 26 Jan 2024 19:09:54 +0100 Subject: [PATCH] match the coreutils style --- build.rs | 101 +++++++++ src/bin/util-linux.rs | 224 ++++++++++++++++++++ src/bin/uudoc.rs | 361 ++++++++++++++++++++++++++++++++ src/uu/lscpu/lscpu.md | 7 + src/uu/lscpu/src/main.rs | 1 + src/uu/mountpoint/mountpoint.md | 9 + src/uu/mountpoint/src/main.rs | 1 + src/uu/pwdx/pwdx.md | 7 + src/uu/pwdx/src/main.rs | 1 + src/uu/renice/renice.md | 7 + src/uu/renice/src/main.rs | 1 + 11 files changed, 720 insertions(+) create mode 100644 build.rs create mode 100644 src/bin/util-linux.rs create mode 100644 src/bin/uudoc.rs create mode 100644 src/uu/lscpu/lscpu.md create mode 100644 src/uu/lscpu/src/main.rs create mode 100644 src/uu/mountpoint/mountpoint.md create mode 100644 src/uu/mountpoint/src/main.rs create mode 100644 src/uu/pwdx/pwdx.md create mode 100644 src/uu/pwdx/src/main.rs create mode 100644 src/uu/renice/renice.md create mode 100644 src/uu/renice/src/main.rs diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..bb4e2b5 --- /dev/null +++ b/build.rs @@ -0,0 +1,101 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. + +// spell-checker:ignore (vars) krate + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::Path; + +pub fn main() { + if let Ok(profile) = env::var("PROFILE") { + println!("cargo:rustc-cfg=build={profile:?}"); + } + + const ENV_FEATURE_PREFIX: &str = "CARGO_FEATURE_"; + const FEATURE_PREFIX: &str = "feat_"; + const OVERRIDE_PREFIX: &str = "uu_"; + + let out_dir = env::var("OUT_DIR").unwrap(); + + let mut crates = Vec::new(); + for (key, val) in env::vars() { + if val == "1" && key.starts_with(ENV_FEATURE_PREFIX) { + let krate = key[ENV_FEATURE_PREFIX.len()..].to_lowercase(); + // Allow this as we have a bunch of info in the comments + #[allow(clippy::match_same_arms)] + match krate.as_ref() { + "default" | "macos" | "unix" | "windows" | "selinux" | "zip" => continue, // common/standard feature names + "nightly" | "test_unimplemented" => continue, // crate-local custom features + "uudoc" => continue, // is not a utility + "test" => continue, // over-ridden with 'uu_test' to avoid collision with rust core crate 'test' + s if s.starts_with(FEATURE_PREFIX) => continue, // crate feature sets + _ => {} // util feature name + } + crates.push(krate); + } + } + crates.sort(); + + let mut mf = File::create(Path::new(&out_dir).join("uutils_map.rs")).unwrap(); + + mf.write_all( + "type UtilityMap<T> = phf::OrderedMap<&'static str, (fn(T) -> i32, fn() -> Command)>;\n\ + \n\ + #[allow(clippy::too_many_lines)] + fn util_map<T: uucore::Args>() -> UtilityMap<T> {\n" + .as_bytes(), + ) + .unwrap(); + + let mut phf_map = phf_codegen::OrderedMap::<&str>::new(); + for krate in &crates { + let map_value = format!("({krate}::uumain, {krate}::uu_app)"); + match krate.as_ref() { + // 'test' is named uu_test to avoid collision with rust core crate 'test'. + // It can also be invoked by name '[' for the '[ expr ] syntax'. + "uu_test" => { + phf_map.entry("test", &map_value); + phf_map.entry("[", &map_value); + } + k if k.starts_with(OVERRIDE_PREFIX) => { + phf_map.entry(&k[OVERRIDE_PREFIX.len()..], &map_value); + } + "false" | "true" => { + phf_map.entry(krate, &format!("(r#{krate}::uumain, r#{krate}::uu_app)")); + } + "hashsum" => { + phf_map.entry(krate, &format!("({krate}::uumain, {krate}::uu_app_custom)")); + + let map_value = format!("({krate}::uumain, {krate}::uu_app_common)"); + let map_value_bits = format!("({krate}::uumain, {krate}::uu_app_bits)"); + let map_value_b3sum = format!("({krate}::uumain, {krate}::uu_app_b3sum)"); + phf_map.entry("md5sum", &map_value); + phf_map.entry("sha1sum", &map_value); + phf_map.entry("sha224sum", &map_value); + phf_map.entry("sha256sum", &map_value); + phf_map.entry("sha384sum", &map_value); + phf_map.entry("sha512sum", &map_value); + phf_map.entry("sha3sum", &map_value_bits); + phf_map.entry("sha3-224sum", &map_value); + phf_map.entry("sha3-256sum", &map_value); + phf_map.entry("sha3-384sum", &map_value); + phf_map.entry("sha3-512sum", &map_value); + phf_map.entry("shake128sum", &map_value_bits); + phf_map.entry("shake256sum", &map_value_bits); + phf_map.entry("b2sum", &map_value); + phf_map.entry("b3sum", &map_value_b3sum); + } + _ => { + phf_map.entry(krate, &map_value); + } + } + } + write!(mf, "{}", phf_map.build()).unwrap(); + mf.write_all(b"\n}\n").unwrap(); + + mf.flush().unwrap(); +} diff --git a/src/bin/util-linux.rs b/src/bin/util-linux.rs new file mode 100644 index 0000000..fc2cd16 --- /dev/null +++ b/src/bin/util-linux.rs @@ -0,0 +1,224 @@ +// This file is part of the uutils coreutils 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<T>(utils: &UtilityMap<T>, 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<Item = OsString>) -> 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, "<unknown binary name>"); + 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 util = match util_os.to_str() { + Some(util) => util, + None => 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 util = match util_os.to_str() { + Some(util) => util, + None => 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<T: uucore::Args>( + args: impl Iterator<Item = OsString>, + util_map: &UtilityMap<T>, +) -> ! { + let all_utilities: Vec<_> = std::iter::once("coreutils") + .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::<Shell>::new()) + .required(true), + ) + .get_matches_from(std::iter::once(OsString::from("completion")).chain(args)); + + let utility = matches.get_one::<String>("utility").unwrap(); + let shell = *matches.get_one::<Shell>("shell").unwrap(); + + let mut command = if utility == "coreutils" { + gen_coreutils_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<T: uucore::Args>( + args: impl Iterator<Item = OsString>, + util_map: &UtilityMap<T>, +) -> ! { + let all_utilities: Vec<_> = std::iter::once("coreutils") + .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::<String>("utility").unwrap(); + + let command = if utility == "coreutils" { + gen_coreutils_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_coreutils_app<T: uucore::Args>(util_map: &UtilityMap<T>) -> Command { + let mut command = Command::new("coreutils"); + 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/bin/uudoc.rs b/src/bin/uudoc.rs new file mode 100644 index 0000000..77c7a2f --- /dev/null +++ b/src/bin/uudoc.rs @@ -0,0 +1,361 @@ +// This file is part of the uutils coreutils package. +// +// For the full copyright and license information, please view the LICENSE +// file that was distributed with this source code. +// spell-checker:ignore tldr uuhelp + +use clap::Command; +use std::collections::HashMap; +use std::ffi::OsString; +use std::fs::File; +use std::io::{self, Read, Seek, Write}; +use zip::ZipArchive; + +include!(concat!(env!("OUT_DIR"), "/uutils_map.rs")); + +fn main() -> io::Result<()> { + let mut tldr_zip = File::open("docs/tldr.zip") + .ok() + .and_then(|f| ZipArchive::new(f).ok()); + + if tldr_zip.is_none() { + println!("Warning: No tldr archive found, so the documentation will not include examples."); + println!("To include examples in the documentation, download the tldr archive and put it in the docs/ folder."); + println!(); + println!(" curl https://tldr.sh/assets/tldr.zip -o docs/tldr.zip"); + println!(); + } + + let utils = util_map::<Box<dyn Iterator<Item = OsString>>>(); + match std::fs::create_dir("docs/src/utils/") { + Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => Ok(()), + x => x, + }?; + + println!("Writing initial info to SUMMARY.md"); + let mut summary = File::create("docs/src/SUMMARY.md")?; + + let _ = write!( + summary, + "# Summary\n\ + \n\ + [Introduction](index.md)\n\ + * [Installation](installation.md)\n\ + * [Build from source](build.md)\n\ + * [Platform support](platforms.md)\n\ + * [Contributing](contributing.md)\n\ + * [GNU test coverage](test_coverage.md)\n\ + * [Extensions](extensions.md)\n\ + \n\ + # Reference\n\ + * [Multi-call binary](multicall.md)\n", + ); + + println!("Gathering utils per platform"); + let utils_per_platform = { + let mut map = HashMap::new(); + for platform in ["unix", "macos", "windows", "unix_android"] { + let platform_utils: Vec<String> = String::from_utf8( + std::process::Command::new("./util/show-utils.sh") + .arg(format!("--features=feat_os_{}", platform)) + .output()? + .stdout, + ) + .unwrap() + .trim() + .split(' ') + .map(ToString::to_string) + .collect(); + map.insert(platform, platform_utils); + } + + // Linux is a special case because it can support selinux + let platform_utils: Vec<String> = String::from_utf8( + std::process::Command::new("./util/show-utils.sh") + .arg("--features=feat_os_unix feat_selinux") + .output()? + .stdout, + ) + .unwrap() + .trim() + .split(' ') + .map(ToString::to_string) + .collect(); + map.insert("linux", platform_utils); + + map + }; + + let mut utils = utils.entries().collect::<Vec<_>>(); + utils.sort(); + + println!("Writing util per platform table"); + { + let mut platform_table_file = File::create("docs/src/platform_table.md").unwrap(); + + // sum, cksum, b2sum, etc. are all available on all platforms, but not in the data structure + // otherwise, we check the map for the util name. + let check_supported = |name: &str, platform: &str| { + if name.ends_with("sum") || utils_per_platform[platform].iter().any(|u| u == name) { + "✓" + } else { + " " + } + }; + writeln!( + platform_table_file, + "| util | Linux | macOS | Windows | FreeBSD | Android |\n\ + | ---------------- | ----- | ----- | ------- | ------- | ------- |" + )?; + for (&name, _) in &utils { + if name == "[" { + continue; + } + // The alignment is not necessary, but makes the output a bit more + // pretty when viewed as plain markdown. + writeln!( + platform_table_file, + "| {:<16} | {:<5} | {:<5} | {:<7} | {:<7} | {:<7} |", + format!("**{name}**"), + check_supported(name, "linux"), + check_supported(name, "macos"), + check_supported(name, "windows"), + check_supported(name, "unix"), + check_supported(name, "unix_android"), + )?; + } + } + + println!("Writing to utils"); + for (&name, (_, command)) in utils { + if name == "[" { + continue; + } + let p = format!("docs/src/utils/{}.md", name); + + let markdown = File::open(format!("src/uu/{name}/{name}.md")) + .and_then(|mut f: File| { + let mut s = String::new(); + f.read_to_string(&mut s)?; + Ok(s) + }) + .ok(); + + if let Ok(f) = File::create(&p) { + MDWriter { + w: Box::new(f), + command: command(), + name, + tldr_zip: &mut tldr_zip, + utils_per_platform: &utils_per_platform, + markdown, + } + .markdown()?; + println!("Wrote to '{}'", p); + } else { + println!("Error writing to {}", p); + } + writeln!(summary, "* [{0}](utils/{0}.md)", name)?; + } + Ok(()) +} + +struct MDWriter<'a, 'b> { + w: Box<dyn Write>, + command: Command, + name: &'a str, + tldr_zip: &'b mut Option<ZipArchive<File>>, + utils_per_platform: &'b HashMap<&'b str, Vec<String>>, + markdown: Option<String>, +} + +impl<'a, 'b> MDWriter<'a, 'b> { + fn markdown(&mut self) -> io::Result<()> { + write!(self.w, "# {}\n\n", self.name)?; + self.additional()?; + self.usage()?; + self.about()?; + self.options()?; + self.after_help()?; + self.examples() + } + + fn additional(&mut self) -> io::Result<()> { + writeln!(self.w, "<div class=\"additional\">")?; + self.platforms()?; + self.version()?; + writeln!(self.w, "</div>") + } + + fn platforms(&mut self) -> io::Result<()> { + writeln!(self.w, "<div class=\"platforms\">")?; + for (feature, icon) in [ + ("linux", "linux"), + // freebsd is disabled for now because mdbook does not use font-awesome 5 yet. + // ("unix", "freebsd"), + ("macos", "apple"), + ("windows", "windows"), + ] { + if self.name.contains("sum") + || self.utils_per_platform[feature] + .iter() + .any(|u| u == self.name) + { + writeln!(self.w, "<i class=\"fa fa-brands fa-{}\"></i>", icon)?; + } + } + writeln!(self.w, "</div>")?; + + Ok(()) + } + + fn version(&mut self) -> io::Result<()> { + writeln!( + self.w, + "<div class=\"version\">v{}</div>", + self.command.render_version().split_once(' ').unwrap().1 + ) + } + + fn usage(&mut self) -> io::Result<()> { + if let Some(markdown) = &self.markdown { + let usage = uuhelp_parser::parse_usage(markdown); + let usage = usage.replace("{}", self.name); + + writeln!(self.w, "\n```")?; + writeln!(self.w, "{}", usage)?; + writeln!(self.w, "```") + } else { + Ok(()) + } + } + + fn about(&mut self) -> io::Result<()> { + if let Some(markdown) = &self.markdown { + writeln!(self.w, "{}", uuhelp_parser::parse_about(markdown)) + } else { + Ok(()) + } + } + + fn after_help(&mut self) -> io::Result<()> { + if let Some(markdown) = &self.markdown { + if let Some(after_help) = uuhelp_parser::parse_section("after help", markdown) { + return writeln!(self.w, "\n\n{after_help}"); + } + } + + Ok(()) + } + + fn examples(&mut self) -> io::Result<()> { + if let Some(zip) = self.tldr_zip { + let content = if let Some(f) = + get_zip_content(zip, &format!("pages/common/{}.md", self.name)) + { + f + } else if let Some(f) = get_zip_content(zip, &format!("pages/linux/{}.md", self.name)) { + f + } else { + println!( + "Warning: Could not find tldr examples for page '{}'", + self.name + ); + return Ok(()); + }; + + writeln!(self.w, "## Examples")?; + writeln!(self.w)?; + for line in content.lines().skip_while(|l| !l.starts_with('-')) { + if let Some(l) = line.strip_prefix("- ") { + writeln!(self.w, "{}", l)?; + } else if line.starts_with('`') { + writeln!(self.w, "```shell\n{}\n```", line.trim_matches('`'))?; + } else if line.is_empty() { + writeln!(self.w)?; + } else { + println!("Not sure what to do with this line:"); + println!("{}", line); + } + } + writeln!(self.w)?; + writeln!( + self.w, + "> The examples are provided by the [tldr-pages project](https://tldr.sh) under the [CC BY 4.0 License](https://github.com/tldr-pages/tldr/blob/main/LICENSE.md)." + )?; + writeln!(self.w, ">")?; + writeln!( + self.w, + "> Please note that, as uutils is a work in progress, some examples might fail." + )?; + } + Ok(()) + } + + fn options(&mut self) -> io::Result<()> { + writeln!(self.w, "<h2>Options</h2>")?; + write!(self.w, "<dl>")?; + for arg in self.command.get_arguments() { + write!(self.w, "<dt>")?; + let mut first = true; + for l in arg.get_long_and_visible_aliases().unwrap_or_default() { + if first { + first = false; + } else { + write!(self.w, ", ")?; + } + write!(self.w, "<code>")?; + write!(self.w, "--{}", l)?; + if let Some(names) = arg.get_value_names() { + write!( + self.w, + "={}", + names + .iter() + .map(|x| format!("<{}>", x)) + .collect::<Vec<_>>() + .join(" ") + )?; + } + write!(self.w, "</code>")?; + } + for s in arg.get_short_and_visible_aliases().unwrap_or_default() { + if first { + first = false; + } else { + write!(self.w, ", ")?; + } + write!(self.w, "<code>")?; + write!(self.w, "-{}", s)?; + if let Some(names) = arg.get_value_names() { + write!( + self.w, + " {}", + names + .iter() + .map(|x| format!("<{}>", x)) + .collect::<Vec<_>>() + .join(" ") + )?; + } + write!(self.w, "</code>")?; + } + writeln!(self.w, "</dt>")?; + writeln!( + self.w, + "<dd>\n\n{}\n\n</dd>", + arg.get_help() + .unwrap_or_default() + .to_string() + .replace('\n', "<br />") + )?; + } + writeln!(self.w, "</dl>\n") + } +} + +fn get_zip_content(archive: &mut ZipArchive<impl Read + Seek>, name: &str) -> Option<String> { + let mut s = String::new(); + archive.by_name(name).ok()?.read_to_string(&mut s).unwrap(); + Some(s) +} diff --git a/src/uu/lscpu/lscpu.md b/src/uu/lscpu/lscpu.md new file mode 100644 index 0000000..ada8e8d --- /dev/null +++ b/src/uu/lscpu/lscpu.md @@ -0,0 +1,7 @@ +# lscpu + +``` +lscpu [OPTION]... +``` + +display information about the CPU architecture \ No newline at end of file diff --git a/src/uu/lscpu/src/main.rs b/src/uu/lscpu/src/main.rs new file mode 100644 index 0000000..d0e1be6 --- /dev/null +++ b/src/uu/lscpu/src/main.rs @@ -0,0 +1 @@ +uucore::bin!(uu_lscpu); diff --git a/src/uu/mountpoint/mountpoint.md b/src/uu/mountpoint/mountpoint.md new file mode 100644 index 0000000..fc84920 --- /dev/null +++ b/src/uu/mountpoint/mountpoint.md @@ -0,0 +1,9 @@ +# mountpoint + +``` +mountpoint [-d|-q] directory|file + +mountpoint -x device +``` + +See if a directory or file is a mountpoint \ No newline at end of file diff --git a/src/uu/mountpoint/src/main.rs b/src/uu/mountpoint/src/main.rs new file mode 100644 index 0000000..8661979 --- /dev/null +++ b/src/uu/mountpoint/src/main.rs @@ -0,0 +1 @@ +uucore::bin!(uu_mountpoint); diff --git a/src/uu/pwdx/pwdx.md b/src/uu/pwdx/pwdx.md new file mode 100644 index 0000000..0efb6d6 --- /dev/null +++ b/src/uu/pwdx/pwdx.md @@ -0,0 +1,7 @@ +# pwdx + +``` +pwdx [options] pid [...] +``` + +Report current working directory of a process \ No newline at end of file diff --git a/src/uu/pwdx/src/main.rs b/src/uu/pwdx/src/main.rs new file mode 100644 index 0000000..ee1a336 --- /dev/null +++ b/src/uu/pwdx/src/main.rs @@ -0,0 +1 @@ +uucore::bin!(uu_pwdx); diff --git a/src/uu/renice/renice.md b/src/uu/renice/renice.md new file mode 100644 index 0000000..43fd7e5 --- /dev/null +++ b/src/uu/renice/renice.md @@ -0,0 +1,7 @@ +# renice + +``` +renice [--priority|--relative] priority [-g|-p|-u] identifier... +``` + +Alter priority of running processes \ No newline at end of file diff --git a/src/uu/renice/src/main.rs b/src/uu/renice/src/main.rs new file mode 100644 index 0000000..899bccb --- /dev/null +++ b/src/uu/renice/src/main.rs @@ -0,0 +1 @@ +uucore::bin!(uu_renice);