empty sed for reimplementation

This commit is contained in:
Sylvestre Ledru
2025-02-01 18:07:27 +01:00
commit 495de53f0d
19 changed files with 5879 additions and 0 deletions
Generated
+1010
View File
File diff suppressed because it is too large Load Diff
+107
View File
@@ -0,0 +1,107 @@
# sed (uutils)
# * see the repository LICENSE, README, and CONTRIBUTING files for more information
# spell-checker:ignore (libs) bigdecimal datetime fundu gethostid kqueue libselinux mangen memmap sed uuhelp
[package]
name = "sed"
version = "0.0.1"
authors = ["uutils developers"]
license = "MIT"
description = "sed ~ implemented as universal (cross-platform) utils, written in Rust"
default-run = "sed"
homepage = "https://github.com/uutils/sed"
repository = "https://github.com/uutils/sed"
readme = "README.md"
keywords = ["sed", "uutils", "cross-platform", "cli", "utility"]
categories = ["command-line-utilities"]
rust-version = "1.70.0"
edition = "2021"
build = "build.rs"
[features]
default = ["feat_common_core"]
uudoc = []
feat_common_core = [
"sed",
]
[workspace.dependencies]
uucore = "0.0.25"
clap = { version = "4.4", features = ["wrap_help", "cargo"] }
clap_complete = "4.5"
clap_mangen = "0.2"
regex = "1.10.4"
sysinfo = "0.30"
libc = "0.2.153"
phf = "0.11.2"
phf_codegen = "0.11.2"
textwrap = { version = "0.16.1", features = ["terminal_size"] }
xattr = "1.3.1"
tempfile = "3.10.1"
rand = { version = "0.8", features = ["small_rng"] }
bytesize = "1.3.0"
chrono = { version = "0.4.37", default-features = false, features = [
"clock",
] }
[dependencies]
clap = { workspace = true }
clap_complete = { workspace = true }
clap_mangen = { workspace = true }
uucore = { workspace = true }
phf = { workspace = true }
textwrap = { workspace = true }
sysinfo = { workspace = true }
sed = { optional = true, version = "0.0.1", package = "uu_sed", path = "src/uu/sed" }
[dev-dependencies]
pretty_assertions = "1"
regex = { workspace = true }
tempfile = { workspace = true }
libc = { workspace = true }
rand = { workspace = true }
uucore = { workspace = true, features = ["entries", "process", "signals"] }
chrono = { workspace = true }
[target.'cfg(unix)'.dev-dependencies]
xattr = { workspace = true }
[target.'cfg(any(target_os = "linux", target_os = "android"))'.dev-dependencies]
rlimit = "0.10.1"
[build-dependencies]
phf_codegen = { workspace = true }
[[bin]]
name = "sed"
path = "src/bin/sed.rs"
[[bin]]
name = "uudoc"
path = "src/bin/uudoc.rs"
required-features = ["uudoc"]
# The default release profile. It contains all optimizations, without
# sacrificing debug info. With this profile (like in the standard
# release profile), the debug info and the stack traces will still be available.
[profile.release]
lto = true
# A release-like profile that is tuned to be fast, even when being fast
# compromises on binary size. This includes aborting on panic.
[profile.release-fast]
inherits = "release"
panic = "abort"
# A release-like profile that is as small as possible.
[profile.release-small]
inherits = "release"
opt-level = "z"
panic = "abort"
strip = true
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 uutils
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+27
View File
@@ -0,0 +1,27 @@
[![Crates.io](https://img.shields.io/crates/v/sed.svg)](https://crates.io/crates/sed)
[![Discord](https://img.shields.io/badge/discord-join-7289DA.svg?logo=discord&longCache=true&style=flat)](https://discord.gg/wQVJbvJ)
[![License](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/uutils/sed/blob/main/LICENSE)
[![dependency status](https://deps.rs/repo/github/uutils/sed/status.svg)](https://deps.rs/repo/github/uutils/sed)
[![CodeCov](https://codecov.io/gh/uutils/sed/branch/master/graph/badge.svg)](https://codecov.io/gh/uutils/sed)
# sed
Rust reimplementation of the sed project
## Installation
Ensure you have Rust installed on your system. You can install Rust through [rustup](https://rustup.rs/).
Clone the repository and build the project using Cargo:
```bash
git clone https://github.com/uutils/sed.git
cd sed
cargo build --release
cargo run --release
```
## License
sed is licensed under the MIT License - see the `LICENSE` file for details
+101
View File
@@ -0,0 +1,101 @@
// 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 (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();
}
+6
View File
@@ -0,0 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended"
]
}
+222
View File
@@ -0,0 +1,222 @@
// This file is part of the uutils procps 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 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<T: uucore::Args>(
args: impl Iterator<Item = OsString>,
util_map: &UtilityMap<T>,
) -> ! {
let all_utilities: Vec<_> = std::iter::once("procps")
.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 == "procps" {
gen_procps_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("procps")
.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 == "procps" {
gen_procps_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_procps_app<T: uucore::Args>(util_map: &UtilityMap<T>) -> Command {
let mut command = Command::new("procps");
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
}
+361
View File
@@ -0,0 +1,361 @@
// 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 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!("&lt;{}&gt;", 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!("&lt;{}&gt;", 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)
}
+24
View File
@@ -0,0 +1,24 @@
[package]
name = "uu_sed"
version = "0.0.1"
edition = "2021"
authors = ["uutils developers"]
license = "MIT"
description = "sed ~ (uutils) stream editor for filtering/transforming text"
homepage = "https://github.com/uutils/sed"
repository = "https://github.com/uutils/sed/tree/main/src/uu/sed"
keywords = ["acl", "uutils", "cross-platform", "cli", "utility"]
categories = ["command-line-utilities"]
[dependencies]
uucore = { workspace = true }
clap = { workspace = true }
[lib]
path = "src/sed.rs"
[[bin]]
name = "sed"
path = "src/main.rs"
+7
View File
@@ -0,0 +1,7 @@
# free
```
free [options]
```
Display amount of free and used memory in the system
+1
View File
@@ -0,0 +1 @@
uucore::bin!(uu_free);
+34
View File
@@ -0,0 +1,34 @@
// 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 clap::{arg, Command};
use std::{
fs,
io::{self, BufRead, Write},
process,
};
use uucore::{error::UResult, format_usage, help_about, help_usage};
const ABOUT: &str = "Stream editor for filtering and transforming text";
const USAGE: &str = "sed [-n] script [file...]";
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from(args)?;
// TODO
Ok(())
}
pub fn uu_app() -> Command {
Command::new(uucore::util_name())
.about(ABOUT)
.override_usage(format_usage(USAGE))
.infer_long_args(true)
.args([
arg!(<script> "script to execute"),
arg!([files] ... "input files"),
arg!(-n --quiet "suppress automatic printing of pattern space"),
])
}
+20
View File
@@ -0,0 +1,20 @@
// 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 (words) symdir somefakedir
use pretty_assertions::assert_eq;
use regex::Regex;
use crate::common::util::TestScenario;
#[test]
fn test_invalid_arg() {
new_ucmd!().arg("--definitely-invalid").fails().code_is(1);
}
#[test]
fn test_sed() {
let result = new_ucmd!().succeeds();
}
+93
View File
@@ -0,0 +1,93 @@
// This file is part of the uutils procps package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
/// Platform-independent helper for constructing a `PathBuf` from individual elements
#[macro_export]
macro_rules! path_concat {
($e:expr, ..$n:expr) => {{
use std::path::PathBuf;
let n = $n;
let mut pb = PathBuf::new();
for _ in 0..n {
pb.push($e);
}
pb.to_str().unwrap().to_owned()
}};
($($e:expr),*) => {{
use std::path::PathBuf;
let mut pb = PathBuf::new();
$(
pb.push($e);
)*
pb.to_str().unwrap().to_owned()
}};
}
/// Deduce the name of the test binary from the test filename.
///
/// e.g.: `tests/by-util/test_cat.rs` -> `cat`
#[macro_export]
macro_rules! util_name {
() => {
module_path!()
.split("_")
.nth(1)
.and_then(|s| s.split("::").next())
.expect("no test name")
};
}
/// Convenience macro for acquiring a [`UCommand`] builder.
///
/// Returns the following:
/// - a [`UCommand`] builder for invoking the binary to be tested
///
/// This macro is intended for quick, single-call tests. For more complex tests
/// that require multiple invocations of the tested binary, see [`TestScenario`]
///
/// [`UCommand`]: crate::tests::common::util::UCommand
/// [`TestScenario]: crate::tests::common::util::TestScenario
#[macro_export]
macro_rules! new_ucmd {
() => {
TestScenario::new(util_name!()).ucmd()
};
}
/// Convenience macro for acquiring a [`UCommand`] builder and a test path.
///
/// Returns a tuple containing the following:
/// - an [`AtPath`] that points to a unique temporary test directory
/// - a [`UCommand`] builder for invoking the binary to be tested
///
/// This macro is intended for quick, single-call tests. For more complex tests
/// that require multiple invocations of the tested binary, see [`TestScenario`]
///
/// [`UCommand`]: crate::tests::common::util::UCommand
/// [`AtPath`]: crate::tests::common::util::AtPath
/// [`TestScenario]: crate::tests::common::util::TestScenario
#[macro_export]
macro_rules! at_and_ucmd {
() => {{
let ts = TestScenario::new(util_name!());
(ts.fixtures.clone(), ts.ucmd())
}};
}
/// If `common::util::expected_result` returns an error, i.e. the `util` in `$PATH` doesn't
/// include a procps version string or the version is too low,
/// this macro can be used to automatically skip the test and print the reason.
#[macro_export]
macro_rules! unwrap_or_return {
( $e:expr ) => {
match $e {
Ok(x) => x,
Err(e) => {
println!("test skipped: {}", e);
return;
}
}
};
}
+8
View File
@@ -0,0 +1,8 @@
// This file is part of the uutils procps package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
#[macro_use]
pub mod macros;
pub mod random;
pub mod util;
+339
View File
@@ -0,0 +1,339 @@
// This file is part of the uutils procps package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
use rand::distributions::{Distribution, Uniform};
use rand::{thread_rng, Rng};
/// Samples alphanumeric characters `[A-Za-z0-9]` including newline `\n`
///
/// # Examples
///
/// ```rust,ignore
/// use rand::{Rng, thread_rng};
///
/// let vec = thread_rng()
/// .sample_iter(AlphanumericNewline)
/// .take(10)
/// .collect::<Vec<u8>>();
/// println!("Random chars: {}", String::from_utf8(vec).unwrap());
/// ```
#[derive(Clone, Copy, Debug)]
pub struct AlphanumericNewline;
impl AlphanumericNewline {
/// The charset to act upon
const CHARSET: &'static [u8] =
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\n";
/// Generate a random byte from [`Self::CHARSET`] and return it as `u8`.
///
/// # Arguments
///
/// * `rng`: A [`rand::Rng`]
///
/// returns: u8
fn random<R>(rng: &mut R) -> u8
where
R: Rng + ?Sized,
{
let idx = rng.gen_range(0..Self::CHARSET.len());
Self::CHARSET[idx]
}
}
impl Distribution<u8> for AlphanumericNewline {
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> u8 {
Self::random(rng)
}
}
/// Generate a random string from a [`Distribution`]
///
/// # Examples
///
/// ```rust,ignore
/// use crate::common::random::{AlphanumericNewline, RandomString};
/// use rand::distributions::Alphanumeric;
///
/// // generates a 100 byte string with characters from AlphanumericNewline
/// let random_string = RandomString::generate(AlphanumericNewline, 100);
/// assert_eq!(100, random_string.len());
///
/// // generates a 100 byte string with 10 newline characters not ending with a newline
/// let string = RandomString::generate_with_delimiter(Alphanumeric, b'\n', 10, false, 100);
/// assert_eq!(100, random_string.len());
/// ```
pub struct RandomString;
impl RandomString {
/// Generate a random string from the given [`Distribution`] with the given `length` in bytes.
///
/// # Arguments
///
/// * `dist`: A u8 [`Distribution`]
/// * `length`: the length of the resulting string in bytes
///
/// returns: String
pub fn generate<D>(dist: D, length: usize) -> String
where
D: Distribution<u8>,
{
thread_rng()
.sample_iter(dist)
.take(length)
.map(|b| b as char)
.collect()
}
/// Generate a random string from the [`Distribution`] with the given `length` in bytes. The
/// function takes a `delimiter`, which is randomly distributed in the string, such that exactly
/// `num_delimiter` amount of `delimiter`s occur. If `end_with_delimiter` is set, then the
/// string ends with the delimiter, else the string does not end with the delimiter.
///
/// # Arguments
///
/// * `dist`: A `u8` [`Distribution`]
/// * `delimiter`: A `u8` delimiter, which does not need to be included in the `Distribution`
/// * `num_delimiter`: The number of `delimiter`s contained in the resulting string
/// * `end_with_delimiter`: If the string shall end with the given delimiter
/// * `length`: the length of the resulting string in bytes
///
/// returns: String
///
/// # Examples
///
/// ```rust,ignore
/// use crate::common::random::{AlphanumericNewline, RandomString};
///
/// // generates a 100 byte string with 10 '\0' byte characters not ending with a '\0' byte
/// let string = RandomString::generate_with_delimiter(AlphanumericNewline, 0, 10, false, 100);
/// assert_eq!(100, random_string.len());
/// assert_eq!(
/// 10,
/// random_string.as_bytes().iter().filter(|p| **p == 0).count()
/// );
/// assert!(!random_string.as_bytes().ends_with(&[0]));
/// ```
pub fn generate_with_delimiter<D>(
dist: D,
delimiter: u8,
num_delimiter: usize,
end_with_delimiter: bool,
length: usize,
) -> String
where
D: Distribution<u8>,
{
if length == 0 {
return String::new();
} else if length == 1 {
return if num_delimiter > 0 {
String::from(delimiter as char)
} else {
String::from(thread_rng().sample(&dist) as char)
};
}
let samples = length - 1;
let mut result: Vec<u8> = thread_rng().sample_iter(&dist).take(samples).collect();
if num_delimiter == 0 {
result.push(thread_rng().sample(&dist));
return String::from_utf8(result).unwrap();
}
let num_delimiter = if end_with_delimiter {
num_delimiter - 1
} else {
num_delimiter
};
let between = Uniform::new(0, samples);
for _ in 0..num_delimiter {
let mut pos = between.sample(&mut thread_rng());
let turn = pos;
while result[pos] == delimiter {
pos += 1;
if pos >= samples {
pos = 0;
}
if pos == turn {
break;
}
}
result[pos] = delimiter;
}
if end_with_delimiter {
result.push(delimiter);
} else {
result.push(thread_rng().sample(&dist));
}
String::from_utf8(result).unwrap()
}
}
#[cfg(test)]
mod tests {
use super::*;
use rand::distributions::Alphanumeric;
#[test]
fn test_random_string_generate() {
let random_string = RandomString::generate(AlphanumericNewline, 0);
assert_eq!(0, random_string.len());
let random_string = RandomString::generate(AlphanumericNewline, 1);
assert_eq!(1, random_string.len());
let random_string = RandomString::generate(AlphanumericNewline, 100);
assert_eq!(100, random_string.len());
}
#[test]
fn test_random_string_generate_with_delimiter_when_length_is_zero() {
let random_string = RandomString::generate_with_delimiter(Alphanumeric, 0, 0, false, 0);
assert_eq!(0, random_string.len());
}
#[test]
fn test_random_string_generate_with_delimiter_when_num_delimiter_is_greater_than_length() {
let random_string = RandomString::generate_with_delimiter(Alphanumeric, 0, 2, false, 1);
assert_eq!(1, random_string.len());
assert!(random_string.as_bytes().contains(&0));
assert!(random_string.as_bytes().ends_with(&[0]));
}
#[test]
fn test_random_string_generate_with_delimiter_should_end_with_delimiter() {
let random_string = RandomString::generate_with_delimiter(Alphanumeric, 0, 1, true, 1);
assert_eq!(1, random_string.len());
assert_eq!(
1,
random_string.as_bytes().iter().filter(|p| **p == 0).count()
);
assert!(random_string.as_bytes().ends_with(&[0]));
let random_string = RandomString::generate_with_delimiter(Alphanumeric, 0, 1, false, 1);
assert_eq!(1, random_string.len());
assert_eq!(
1,
random_string.as_bytes().iter().filter(|p| **p == 0).count()
);
assert!(random_string.as_bytes().ends_with(&[0]));
let random_string = RandomString::generate_with_delimiter(Alphanumeric, 0, 1, true, 2);
assert_eq!(2, random_string.len());
assert_eq!(
1,
random_string.as_bytes().iter().filter(|p| **p == 0).count()
);
assert!(random_string.as_bytes().ends_with(&[0]));
let random_string = RandomString::generate_with_delimiter(Alphanumeric, 0, 2, true, 2);
assert_eq!(2, random_string.len());
assert_eq!(
2,
random_string.as_bytes().iter().filter(|p| **p == 0).count()
);
assert!(random_string.as_bytes().ends_with(&[0]));
let random_string = RandomString::generate_with_delimiter(Alphanumeric, 0, 1, true, 3);
assert_eq!(3, random_string.len());
assert_eq!(
1,
random_string.as_bytes().iter().filter(|p| **p == 0).count()
);
assert!(random_string.as_bytes().ends_with(&[0]));
}
#[test]
fn test_random_string_generate_with_delimiter_should_not_end_with_delimiter() {
let random_string = RandomString::generate_with_delimiter(Alphanumeric, 0, 0, false, 1);
assert_eq!(1, random_string.len());
assert_eq!(
0,
random_string.as_bytes().iter().filter(|p| **p == 0).count()
);
let random_string = RandomString::generate_with_delimiter(Alphanumeric, 0, 0, true, 1);
assert_eq!(1, random_string.len());
assert_eq!(
0,
random_string.as_bytes().iter().filter(|p| **p == 0).count()
);
let random_string = RandomString::generate_with_delimiter(Alphanumeric, 0, 1, false, 2);
assert_eq!(2, random_string.len());
assert_eq!(
1,
random_string.as_bytes().iter().filter(|p| **p == 0).count()
);
assert!(!random_string.as_bytes().ends_with(&[0]));
let random_string = RandomString::generate_with_delimiter(Alphanumeric, 0, 1, false, 3);
assert_eq!(3, random_string.len());
assert_eq!(
1,
random_string.as_bytes().iter().filter(|p| **p == 0).count()
);
assert!(!random_string.as_bytes().ends_with(&[0]));
let random_string = RandomString::generate_with_delimiter(Alphanumeric, 0, 2, false, 3);
assert_eq!(3, random_string.len());
assert_eq!(
2,
random_string.as_bytes().iter().filter(|p| **p == 0).count()
);
assert!(!random_string.as_bytes().ends_with(&[0]));
}
#[test]
fn test_generate_with_delimiter_with_greater_length() {
let random_string =
RandomString::generate_with_delimiter(Alphanumeric, 0, 100, false, 1000);
assert_eq!(1000, random_string.len());
assert_eq!(
100,
random_string.as_bytes().iter().filter(|p| **p == 0).count()
);
assert!(!random_string.as_bytes().ends_with(&[0]));
let random_string = RandomString::generate_with_delimiter(Alphanumeric, 0, 100, true, 1000);
assert_eq!(1000, random_string.len());
assert_eq!(
100,
random_string.as_bytes().iter().filter(|p| **p == 0).count()
);
assert!(random_string.as_bytes().ends_with(&[0]));
}
/// Originally used to exclude an error within the `random` module. The two
/// affected tests timed out on windows, but only in the ci. These tests are
/// also the source for the concrete numbers. The timed out tests are
/// `test_tail.rs::test_pipe_when_lines_option_given_input_size_has_multiple_size_of_buffer_size`
/// `test_tail.rs::test_pipe_when_bytes_option_given_input_size_has_multiple_size_of_buffer_size`.
#[test]
fn test_generate_random_strings_when_length_is_around_critical_buffer_sizes() {
let length = 8192 * 3;
let random_string = RandomString::generate(AlphanumericNewline, length);
assert_eq!(length, random_string.len());
let length = 8192 * 3 + 1;
let random_string =
RandomString::generate_with_delimiter(Alphanumeric, b'\n', 100, true, length);
assert_eq!(length, random_string.len());
assert_eq!(
100,
random_string
.as_bytes()
.iter()
.filter(|p| **p == b'\n')
.count()
);
assert!(!random_string.as_bytes().ends_with(&[0]));
}
}
+3416
View File
File diff suppressed because it is too large Load Diff
+10
View File
@@ -0,0 +1,10 @@
// 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.
#[macro_use]
mod common;
#[cfg(feature = "sed")]
#[path = "by-util/test_sed.rs"]
mod test_sed;
+72
View File
@@ -0,0 +1,72 @@
#!/bin/sh
# spell-checker:ignore uuhelp
ARG=""
if test "$1" != "--do-it"; then
ARG="--dry-run --allow-dirty"
fi
# Function to check if the crate is already published
is_already_published() {
local crate_name=$1
local crate_version=$2
# Use the crates.io API to get the latest version of the crate
local latest_published_version
latest_published_version=$(curl -s https://crates.io/api/v1/crates/$crate_name | jq -r '.crate.max_version')
if [ "$latest_published_version" = "$crate_version" ]; then
return 0
else
return 1
fi
}
# Figure out any dependencies between the util via Cargo.toml
# We store this as edges in a graph with each line:
# [dependent] [dependency]
# We use ROOT as a the node that should come before all other nodes.
PROGS=$(ls -1d src/uu/*/)
PARTIAL_ORDER=""
for p in $PROGS; do
DEPENDENCIES=$(grep -oE "^uu_[a-z0-9]+" ${p}Cargo.toml)
# Turn "src/uu/util/" into "util"
p=${p#src/uu/}
p=${p%/}
PARTIAL_ORDER+="$p ROOT\n"
while read d; do
if [ $d ]; then
# Remove "uu_" prefix
d=${d#uu_}
PARTIAL_ORDER+="$p $d\n"
fi
done <<<"$DEPENDENCIES"
done
# Apply tsort to get the order in which to publish the crates
TOTAL_ORDER=$(echo -e $PARTIAL_ORDER | tsort | tac)
# Remove the ROOT node from the start
TOTAL_ORDER=${TOTAL_ORDER#ROOT}
CRATE_VERSION=$(grep '^version' Cargo.toml | head -n1 | cut -d '"' -f2)
set -e
for p in $TOTAL_ORDER; do
(
cd "src/uu/$p"
CRATE_NAME=$(grep '^name =' "Cargo.toml" | head -n1 | cut -d '"' -f2)
#shellcheck disable=SC2086
if ! is_already_published "$CRATE_NAME" "$CRATE_VERSION"; then
cargo publish $ARG
else
echo "Skip: $CRATE_NAME $CRATE_VERSION already published"
fi
)
done
#shellcheck disable=SC2086
cargo publish $ARG