extern crate argparse; extern crate html2text; use argparse::{ArgumentParser, Store, StoreOption, StoreTrue}; use html2text::config::{self, Config}; use html2text::render::{TextDecorator, TrivialDecorator}; use log::trace; use std::io; use std::io::Write; #[cfg(unix)] use html2text::render::RichAnnotation; #[cfg(unix)] fn default_colour_map( annotations: &[RichAnnotation], s: &str, use_css_colours: bool, no_default_colours: bool, ) -> String { use termion::color::*; use RichAnnotation::*; // Explicit CSS colours override any other colours let mut have_explicit_colour = no_default_colours; let mut start = Vec::new(); let mut finish = Vec::new(); trace!("default_colour_map: str={s}, annotations={annotations:?}"); for annotation in annotations.iter() { match annotation { Default => {} Link(_) => { start.push(format!("{}", termion::style::Underline)); finish.push(format!("{}", termion::style::Reset)); } Image(_) => { if !have_explicit_colour { start.push(format!("{}", Fg(Blue))); finish.push(format!("{}", Fg(Reset))); } } Emphasis => { start.push(format!("{}", termion::style::Bold)); finish.push(format!("{}", termion::style::Reset)); } Strong => { if !have_explicit_colour { start.push(format!("{}", Fg(LightYellow))); finish.push(format!("{}", Fg(Reset))); } } Strikeout => { if !have_explicit_colour { start.push(format!("{}", Fg(LightBlack))); finish.push(format!("{}", Fg(Reset))); } } Code => { if !have_explicit_colour { start.push(format!("{}", Fg(Blue))); finish.push(format!("{}", Fg(Reset))); } } Preformat(_) => { if !have_explicit_colour { start.push(format!("{}", Fg(Blue))); finish.push(format!("{}", Fg(Reset))); } } Colour(c) => { if use_css_colours { start.push(format!("{}", Fg(Rgb(c.r, c.g, c.b)))); finish.push(format!("{}", Fg(Reset))); have_explicit_colour = true; } } BgColour(c) => { if use_css_colours { start.push(format!("{}", Bg(Rgb(c.r, c.g, c.b)))); finish.push(format!("{}", Bg(Reset))); } } _ => {} } } // Reverse the finish sequences finish.reverse(); let mut result = start.join(""); result.push_str(s); for s in finish { result.push_str(&s); } trace!("default_colour_map: output={result}"); result } fn update_config(mut config: Config, flags: &Flags) -> Config { if let Some(wrap_width) = flags.wrap_width { config = config.max_wrap_width(wrap_width); } #[cfg(feature = "css")] if flags.use_css { config = config.use_doc_css(); } config } fn translate(input: R, flags: Flags, literal: bool) -> String where R: io::Read, { #[cfg(unix)] { if flags.use_colour { let conf = config::rich(); let conf = update_config(conf, &flags); #[cfg(feature = "css")] let use_css_colours = !flags.ignore_css_colours; #[cfg(not(feature = "css"))] let use_css_colours = false; #[cfg(feature = "css")] let use_only_css = flags.use_only_css; #[cfg(not(feature = "css"))] let use_only_css = false; return conf .coloured(input, flags.width, move |anns, s| { default_colour_map(anns, s, use_css_colours, use_only_css) }) .unwrap(); } } if flags.show_dom { let conf = config::plain(); let conf = update_config(conf, &flags); let dom = conf.parse_html(input).unwrap(); dom.as_dom_string() } else if flags.show_render { let conf = config::plain(); let conf = update_config(conf, &flags); let dom = conf.parse_html(input).unwrap(); let rendertree = conf.dom_to_render_tree(&dom).unwrap(); rendertree.to_string() } else if literal { let conf = config::with_decorator(TrivialDecorator::new()); let conf = update_config(conf, &flags); conf.string_from_read(input, flags.width).unwrap() } else { let conf = config::plain(); let conf = update_config(conf, &flags); conf.string_from_read(input, flags.width).unwrap() } } #[derive(Debug)] struct Flags { width: usize, wrap_width: Option, #[allow(unused)] use_colour: bool, #[cfg(feature = "css")] use_css: bool, #[cfg(feature = "css")] ignore_css_colours: bool, #[cfg(feature = "css")] use_only_css: bool, show_dom: bool, show_render: bool, } fn main() { #[cfg(feature = "html_trace")] env_logger::init(); let mut infile: Option = None; let mut outfile: Option = None; let mut flags = Flags { width: 80, wrap_width: None, use_colour: false, #[cfg(feature = "css")] use_css: false, #[cfg(feature = "css")] ignore_css_colours: false, #[cfg(feature = "css")] use_only_css: false, show_dom: false, show_render: false, }; let mut literal: bool = false; { let mut ap = ArgumentParser::new(); ap.refer(&mut infile).add_argument( "infile", StoreOption, "Input HTML file (default is standard input)", ); ap.refer(&mut flags.width).add_option( &["-w", "--width"], Store, "Column width to format to (default is 80)", ); ap.refer(&mut flags.wrap_width).add_option( &["-W", "--wrap-width"], StoreOption, "Maximum text wrap width (default same as width)", ); ap.refer(&mut outfile).add_option( &["-o", "--output"], StoreOption, "Output file (default is standard output)", ); ap.refer(&mut literal).add_option( &["-L", "--literal"], StoreTrue, "Output only literal text (no decorations)", ); #[cfg(unix)] ap.refer(&mut flags.use_colour).add_option( &["--colour"], StoreTrue, "Use ANSI terminal colours", ); #[cfg(feature = "css")] ap.refer(&mut flags.use_css) .add_option(&["--css"], StoreTrue, "Enable CSS"); #[cfg(feature = "css")] ap.refer(&mut flags.ignore_css_colours) .add_option(&["--ignore-css-colour"], StoreTrue, "With --css, ignore CSS colour information (still hides elements with e.g. display: none)"); #[cfg(feature = "css")] ap.refer(&mut flags.use_only_css).add_option( &["--only-css"], StoreTrue, "Don't use default non-CSS colours", ); ap.refer(&mut flags.show_dom).add_option( &["--show-dom"], StoreTrue, "Show the parsed HTML DOM instead of rendered output", ); ap.refer(&mut flags.show_render).add_option( &["--show-render"], StoreTrue, "Show the computed render tree instead of the rendered output", ); ap.parse_args_or_exit(); } let data = match infile { None => { let stdin = io::stdin(); translate(&mut stdin.lock(), flags, literal) } Some(name) => { let mut file = std::fs::File::open(name).expect("Tried to open file"); translate(&mut file, flags, literal) } }; match outfile { None => { print!("{}", data); } Some(name) => { let mut file = std::fs::File::create(name).expect("Tried to create file"); write!(file, "{}", data).unwrap(); } }; }