Add xargs

This commit is contained in:
Austen Adler 2022-05-08 13:54:29 -04:00
parent 6ead19042d
commit 8f3e1f5dc1
2 changed files with 26 additions and 85 deletions

View File

@ -16,6 +16,7 @@ mod shuf;
mod sort; mod sort;
mod trim; mod trim;
mod uniq; mod uniq;
mod xargs;
use clap::{Parser, Subcommand}; use clap::{Parser, Subcommand};
use errors::KakMessage; use errors::KakMessage;
pub use kak::*; pub use kak::*;
@ -41,6 +42,7 @@ enum Commands {
#[clap(visible_aliases = &["bc", "eval"])] #[clap(visible_aliases = &["bc", "eval"])]
MathEval(math_eval::Options), MathEval(math_eval::Options),
Trim(trim::Options), Trim(trim::Options),
Xargs(xargs::Options),
} }
fn main() { fn main() {
@ -76,6 +78,7 @@ fn run() -> Result<KakMessage, KakMessage> {
Commands::Uniq(o) => uniq::uniq(o), Commands::Uniq(o) => uniq::uniq(o),
Commands::MathEval(o) => math_eval::math_eval(o), Commands::MathEval(o) => math_eval::math_eval(o),
Commands::Trim(o) => trim::trim(o), Commands::Trim(o) => trim::trim(o),
Commands::Xargs(o) => xargs::xargs(o),
} }
} }

View File

@ -1,102 +1,40 @@
use crate::{ use crate::{get_selections_with_desc, set_selections, KakMessage};
get_selections_desc, set_selections, set_selections_desc, KakMessage, SelectionWithDesc,
};
use regex::Regex;
use std::{ use std::{
collections::{hash_map::DefaultHasher, BTreeSet}, io::{BufRead, BufReader, Write},
hash::{Hash, Hasher}, process::{Command, Stdio},
}; };
#[derive(clap::StructOpt, Debug)] #[derive(clap::StructOpt, Debug)]
pub struct Options { pub struct Options {
args: Vec<String>,
} }
pub fn xargs(options: &Options) -> Result<KakMessage, KakMessage> { pub fn xargs(options: &Options) -> Result<KakMessage, KakMessage> {
// let mut selections = get_selections()?;
let mut child = Command::new("xargs") let mut child = Command::new("xargs")
.args(["-0"]) .arg("-0")
.args(&options.args)
.stdin(Stdio::piped()) .stdin(Stdio::piped())
.stdout(Stdio::piped()) .stdout(Stdio::piped())
.spawn().expect("Failed to spawn child process"); .spawn()
.expect("Failed to spawn child process");
let mut stdin = child.stdin.take().expect("Failed to open stdin"); let mut stdin = child.stdin.take().expect("Failed to open stdin");
std::thread::spawn(move || { let handle = std::thread::spawn(move || -> Result<(), KakMessage> {
for s in crate::get_selections_with_desc()? { for s in get_selections_with_desc()? {
stdin.write_all(s.selection).expect("Failed to write to stdin"); write!(stdin, "{}\0", s.content)?;
stdin.write_all('\0').expect("Failed to write to stdin"); // stdin
// .write_all(&.as_bytes())
// .expect("Failed to write to stdin");
// stdin.write_all(&[b'\0']).expect("Failed to write to stdin");
} }
Ok(())
}) });
// Sort selections so the first element is the xargsue one, not an arbitrary one based on primary selection
selections.sort_by_key(|s| s.desc.sort());
// Set the new selection types set_selections(BufReader::new(child.stdout.take().expect("Failed to get stdout")).split(b'\0'));
let new_selections: Vec<Option<SelectionWithDesc>> = selections
.into_iter()
// Create a BTreeSet of hashes of selections. This way, string content is not stored, but xargsueness can be determined
.scan(BTreeSet::new(), |state, s| {
// Strip whitespace if requested
let mut key = if options.no_skip_whitespace {
s.content.as_str()
} else {
s.content.trim()
};
if let Some(regex_match) = (|| { // stdout.
let captures = options.regex.as_ref()?.captures(key)?;
captures
.get(1)
.or_else(|| captures.get(0))
.map(|m| m.as_str())
})() {
key = regex_match;
}
// Ignore case if requested // set_selections(selections.iter())?;
let key = if options.ignore_case {
key.to_lowercase()
} else {
// TODO: Do I really need to clone this?
key.to_string()
};
let mut hasher = DefaultHasher::new(); Ok(KakMessage(format!("Shuf selections",), None))
key.hash(&mut hasher);
// Try inserting to the hash
if state.insert(hasher.finish()) {
// True if this is a string we haven't seen before
Some(Some(s))
} else {
// Nothing was inserted because we already saw this string
// Return Some(None) so the iterator can continue
Some(None)
}
})
.collect();
// Preallocate so the content and string have the same type, but allocation is not repeated
// TODO: Do we really have to do this?
let empty_string = String::default();
set_selections(new_selections.iter().map(|i| match i {
Some(s) => &s.content,
None => &empty_string,
}))?;
// Deselect all `None` strings (all rows that have been seen before)
let mut new_selections_desc = get_selections_desc()?;
new_selections_desc.sort();
set_selections_desc(
// Refresh seelections_desc because positions have changed
new_selections_desc
.iter()
.zip(new_selections.iter())
// If the string was emptied (None), then do not set `sd`
.filter_map(|(sd, s)| if s.is_some() { Some(sd) } else { None }),
)?;
let old_count = new_selections.len();
let new_count = new_selections.iter().flatten().count();
Ok(KakMessage(
format!("{} xargs selections out of {}", new_count, old_count),
None,
))
} }