//! # Select HTML
//!
//! > **Extract HTML using CSS selectors in the command-line.**
use std::{
fs::File,
io::{stdin, Read},
path::PathBuf,
};
use {
clap::Parser,
color_eyre::{eyre::eyre, install, Result},
scraper::{Html, Selector},
};
/// CLI arguments struct using [`clap`]'s Derive API.
#[derive(Debug, Parser)]
#[clap(about, author, version)]
pub struct Args {
/// Output the attribute's value from the selected element, can be used
/// multiple times.
#[clap(short, long, group = "output")]
pub attribute: Vec,
/// A HTML file to read, if not specified stdin will be used instead.
#[clap(long, parse(from_os_str))]
pub file: Option,
/// The CSS selector to use.
pub selector: String,
/// Output inner text of the selected elements.
#[clap(short, long, group = "output")]
pub text: bool,
/// Trim whitespace from selected items.
#[clap(long)]
pub trim: bool,
}
/// The main CLI function.
fn main() -> Result<()> {
install()?;
let args = Args::parse();
let selector = Selector::parse(&args.selector)
.map_err(|_| eyre!("Failed to parse selector"))?;
let document = {
let mut html = String::new();
if let Some(path) = args.file {
File::open(path)?.read_to_string(&mut html)?;
} else {
stdin().read_to_string(&mut html)?;
};
Html::parse_document(&html)
};
let mut to_print = vec![];
for element in document.select(&selector) {
if args.text {
to_print.push(element.text().collect::());
} else if !args.attribute.is_empty() {
let element = element.value();
for attribute in &args.attribute {
if let Some(value) = element.attr(attribute) {
to_print.push(value.to_string());
}
}
} else {
to_print.push(element.html());
}
}
for value in to_print {
if args.trim {
println!("{}", value.trim());
} else {
println!("{}", value);
}
}
Ok(())
}