use std::collections::BTreeMap;
use std::io::{self, BufRead, Write};
use std::process;
use clap::Parser;
use consortium::msg_tree::{MsgTree, MsgTreeMode};
use consortium::node_set::NodeSet;
use consortium_cli::display;
use consortium_cli::output::{CliOutput, OutputArgs};
#[derive(Parser)]
#[command(name = "molt", version, about)]
struct Args {
#[arg(short = 'b', long = "dshbak")]
dshbak: bool,
#[arg(short = 'L', long = "line")]
line: bool,
#[arg(short = 'S', long = "separator", default_value = ":")]
separator: String,
#[arg(short = 'T', long = "tree")]
tree: bool,
#[arg(short = 'G', long = "groupbase")]
groupbase: bool,
files: Vec<String>,
#[command(flatten)]
output: OutputArgs,
}
fn main() {
let args = Args::parse();
let _out = CliOutput::from_args(&args.output);
if let Err(e) = run(args) {
eprintln!("molt: {e}");
process::exit(1);
}
}
fn run(args: Args) -> anyhow::Result<()> {
let stdout = io::stdout();
let mut out = stdout.lock();
let lines = read_input(&args)?;
if args.dshbak {
run_dshbak(&lines, &args.separator, &mut out)?;
} else if args.line {
run_line_mode(&lines, &args.separator, &mut out)?;
} else {
run_passthrough(&lines, &mut out)?;
}
Ok(())
}
fn read_input(args: &Args) -> anyhow::Result<Vec<String>> {
let mut lines = Vec::new();
if args.files.is_empty() {
let stdin = io::stdin();
for line in stdin.lock().lines() {
lines.push(line?);
}
} else {
for path in &args.files {
let file = std::fs::File::open(path)?;
let reader = io::BufReader::new(file);
for line in reader.lines() {
lines.push(line?);
}
}
}
Ok(lines)
}
fn parse_line<'a>(line: &'a str, separator: &str) -> Option<(&'a str, &'a str)> {
let idx = line.find(separator)?;
let key = line[..idx].trim();
let msg = line[idx + separator.len()..].trim_start();
if key.is_empty() {
return None;
}
Some((key, msg))
}
fn run_dshbak(lines: &[String], separator: &str, out: &mut impl Write) -> anyhow::Result<()> {
let mut tree = MsgTree::new(MsgTreeMode::Shift);
for line in lines {
if let Some((key, msg)) = parse_line(line, separator) {
tree.add(key.to_string(), msg.as_bytes().to_vec());
}
}
let mut groups: BTreeMap<Vec<u8>, NodeSet> = BTreeMap::new();
for (message, keys) in tree.walk(None) {
let ns = groups.entry(message).or_insert_with(NodeSet::new);
for key in keys {
let _ = ns.update_str(&key);
}
}
for (message, ns) in &groups {
display::print_gathered_header(&ns.to_string(), out)?;
out.write_all(message)?;
if !message.ends_with(b"\n") {
writeln!(out)?;
}
}
Ok(())
}
fn run_line_mode(lines: &[String], separator: &str, out: &mut impl Write) -> anyhow::Result<()> {
let mut node_output: BTreeMap<String, Vec<String>> = BTreeMap::new();
for line in lines {
if let Some((key, msg)) = parse_line(line, separator) {
node_output
.entry(key.to_string())
.or_default()
.push(msg.to_string());
}
}
for (node, msgs) in &node_output {
for msg in msgs {
display::print_line_with_label(node, msg, out)?;
}
}
Ok(())
}
fn run_passthrough(lines: &[String], out: &mut impl Write) -> anyhow::Result<()> {
for line in lines {
writeln!(out, "{line}")?;
}
Ok(())
}