use std::io::{self, BufRead, Write};
use std::process;
use clap::Parser;
use consortium::node_set::NodeSet;
use consortium::range_set::RangeSet;
#[derive(Parser)]
#[command(name = "pinch", version, about)]
struct Args {
#[arg(short = 'c', long = "count", group = "operation")]
count: bool,
#[arg(short = 'e', long = "expand", group = "operation")]
expand: bool,
#[arg(short = 'f', long = "fold", group = "operation")]
fold: bool,
#[arg(short = 'l', long = "list", action = clap::ArgAction::Count, group = "operation")]
list: u8,
#[arg(short = 'r', long = "regroup", group = "operation")]
regroup: bool,
#[arg(long = "groupsources", group = "operation")]
groupsources: bool,
#[arg(short = 'x', long = "exclude")]
exclude: Vec<String>,
#[arg(short = 'i', long = "intersection")]
intersection: Vec<String>,
#[arg(short = 'X', long = "xor")]
xor: Vec<String>,
#[arg(short = 'a', long = "all")]
all: bool,
#[arg(short = 'g', long = "group")]
group: Vec<String>,
#[arg(short = 'S', long = "separator")]
separator: Option<String>,
#[arg(short = 'R', long = "rangeset")]
rangeset: bool,
#[arg(long = "autostep")]
autostep: Option<String>,
#[arg(long = "split")]
split: Option<usize>,
#[arg(long = "contiguous")]
contiguous: bool,
#[arg(long = "pick")]
pick: Option<usize>,
nodesets: Vec<String>,
}
fn main() {
let args = Args::parse();
if let Err(e) = run(args) {
eprintln!("pinch: {e}");
process::exit(1);
}
}
fn run(args: Args) -> anyhow::Result<()> {
let stdout = io::stdout();
let mut out = stdout.lock();
if args.rangeset {
return run_rangeset(&args, &mut out);
}
let mut ns = gather_input(&args)?;
for pat in &args.exclude {
let other = NodeSet::parse(pat)?;
ns.difference_update(&other);
}
for pat in &args.intersection {
let other = NodeSet::parse(pat)?;
ns.intersection_update(&other);
}
for pat in &args.xor {
let other = NodeSet::parse(pat)?;
ns.symmetric_difference_update(&other);
}
if let Some(n) = args.pick {
ns = pick_random(&ns, n);
}
let sep = args.separator.as_deref().unwrap_or(" ");
if args.count {
writeln!(out, "{}", ns.len())?;
} else if args.expand {
let nodes: Vec<String> = ns.iter().collect();
writeln!(out, "{}", nodes.join(sep))?;
} else if let Some(n) = args.split {
let subsets = ns.split(n);
for subset in subsets {
writeln!(out, "{subset}")?;
}
} else if args.contiguous {
let subsets = ns.split(ns.len().max(1));
for subset in subsets {
if !subset.is_empty() {
writeln!(out, "{subset}")?;
}
}
} else {
writeln!(out, "{ns}")?;
}
Ok(())
}
fn gather_input(args: &Args) -> anyhow::Result<NodeSet> {
let mut ns = NodeSet::new();
for pat in &args.nodesets {
let parsed = NodeSet::parse(pat)?;
ns.update(&parsed);
}
if args.nodesets.is_empty() && !is_terminal::is_terminal(io::stdin()) {
let stdin = io::stdin();
for line in stdin.lock().lines() {
let line = line?;
let trimmed = line.trim();
if !trimmed.is_empty() {
let parsed = NodeSet::parse(trimmed)?;
ns.update(&parsed);
}
}
}
Ok(ns)
}
fn run_rangeset(args: &Args, out: &mut impl Write) -> anyhow::Result<()> {
let autostep = parse_autostep(&args.autostep);
let mut rs = RangeSet::new();
for pat in &args.nodesets {
let parsed = RangeSet::parse(pat, autostep)?;
rs.update(&parsed);
}
if args.nodesets.is_empty() && !is_terminal::is_terminal(io::stdin()) {
let stdin = io::stdin();
for line in stdin.lock().lines() {
let line = line?;
let trimmed = line.trim();
if !trimmed.is_empty() {
let parsed = RangeSet::parse(trimmed, autostep)?;
rs.update(&parsed);
}
}
}
for pat in &args.exclude {
let other = RangeSet::parse(pat, autostep)?;
rs.difference_update(&other);
}
for pat in &args.intersection {
let other = RangeSet::parse(pat, autostep)?;
rs.intersection_update(&other);
}
for pat in &args.xor {
let other = RangeSet::parse(pat, autostep)?;
rs.symmetric_difference_update(&other);
}
let sep = args.separator.as_deref().unwrap_or(" ");
if args.count {
writeln!(out, "{}", rs.len())?;
} else if args.expand {
let items: Vec<String> = rs.striter().collect();
writeln!(out, "{}", items.join(sep))?;
} else {
writeln!(out, "{rs}")?;
}
Ok(())
}
fn parse_autostep(s: &Option<String>) -> Option<u32> {
let s = s.as_deref()?;
match s {
"auto" => Some(0), _ => s.parse::<u32>().ok(),
}
}
fn pick_random(ns: &NodeSet, n: usize) -> NodeSet {
use std::collections::HashSet;
let all: Vec<String> = ns.iter().collect();
if n >= all.len() {
return ns.clone();
}
let mut indices: Vec<usize> = (0..all.len()).collect();
let mut seed: u64 = std::process::id() as u64
^ std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos() as u64;
for i in (1..indices.len()).rev() {
seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1);
let j = (seed >> 33) as usize % (i + 1);
indices.swap(i, j);
}
let picked: HashSet<&str> = indices[..n].iter().map(|&i| all[i].as_str()).collect();
let mut result = NodeSet::new();
for node in picked {
let _ = result.update_str(node);
}
result
}