diff --git a/examples/html2term.rs b/examples/html2term.rs index a1c2937..141479f 100644 --- a/examples/html2term.rs +++ b/examples/html2term.rs @@ -269,21 +269,17 @@ mod top { Key::Char('k') | Key::Up => { if inspect_path.is_empty() { doc_y = doc_y.saturating_sub(1); - } else { - if *inspect_path.last().unwrap() > 1 { - *inspect_path.last_mut().unwrap() -= 1; - annotated = rerender(&dom, &inspect_path, width, &options); - } + } else if *inspect_path.last().unwrap() > 1 { + *inspect_path.last_mut().unwrap() -= 1; + annotated = rerender(&dom, &inspect_path, width, &options); } } Key::Char('h') | Key::Left => { if inspect_path.is_empty() { doc_x = doc_x.saturating_sub(1); - } else { - if inspect_path.len() > 1 { - inspect_path.pop(); - annotated = rerender(&dom, &inspect_path, width, &options); - } + } else if inspect_path.len() > 1 { + inspect_path.pop(); + annotated = rerender(&dom, &inspect_path, width, &options); } } Key::Char('l') | Key::Right => { @@ -378,7 +374,7 @@ mod top { }; if inspect_path.is_empty() { let render_tree = config - .dom_to_render_tree(&dom) + .dom_to_render_tree(dom) .expect("Failed to build render tree"); config .render_to_lines(render_tree, width) @@ -405,7 +401,7 @@ mod top { ) .expect("Invalid CSS"); let render_tree = config - .dom_to_render_tree(&dom) + .dom_to_render_tree(dom) .expect("Failed to build render tree"); config .render_to_lines(render_tree, width) diff --git a/src/ansi_colours.rs b/src/ansi_colours.rs index 9dd54a1..5c9a19e 100644 --- a/src/ansi_colours.rs +++ b/src/ansi_colours.rs @@ -4,10 +4,11 @@ //! can be achieved using inline characters sent to the terminal such as //! underlining in some terminals). -use crate::{parse, RichAnnotation, RichDecorator}; +use crate::RichAnnotation; use std::io; /// Reads HTML from `input`, and returns text wrapped to `width` columns. +/// /// The text is returned as a `Vec>`; the annotations are vectors /// of `RichAnnotation`. The "outer" annotation comes first in the `Vec`. /// @@ -24,16 +25,5 @@ where R: io::Read, FMap: Fn(&[RichAnnotation], &str) -> String, { - let lines = parse(input)? - .render(width, RichDecorator::new())? - .into_lines()?; - - let mut result = String::new(); - for line in lines { - for ts in line.tagged_strings() { - result.push_str(&colour_map(&ts.tag, &ts.s)); - } - result.push('\n'); - } - Ok(result) + super::config::rich().coloured(input, width, colour_map) } diff --git a/src/css.rs b/src/css.rs index f3207e9..68256e9 100644 --- a/src/css.rs +++ b/src/css.rs @@ -16,7 +16,7 @@ use crate::{ use self::parser::Importance; -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub(crate) enum SelectorComponent { Class(String), Element(String), @@ -46,7 +46,7 @@ impl std::fmt::Display for SelectorComponent { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct Selector { // List of components, right first so we match from the leaf. components: Vec, @@ -132,10 +132,8 @@ impl Selector { if Rc::ptr_eq(child, node) { break; } - } else { - if Rc::ptr_eq(child, node) { - return false; - } + } else if Rc::ptr_eq(child, node) { + return false; } } } @@ -148,14 +146,14 @@ impl Selector { */ let idx_offset = idx - b; if *a == 0 { - return idx_offset == 0 && Self::do_matches(&comps[1..], &node); + return idx_offset == 0 && Self::do_matches(&comps[1..], node); } if (idx_offset % a) != 0 { // Not a multiple return false; } let n = idx_offset / a; - n >= 0 && Self::do_matches(&comps[1..], &node) + n >= 0 && Self::do_matches(&comps[1..], node) } }, } @@ -191,7 +189,7 @@ impl Selector { } } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(crate) enum Display { /// display: none None, @@ -200,7 +198,7 @@ pub(crate) enum Display { ExtRawDom, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub(crate) enum Style { Colour(Colour), BgColour(Colour), @@ -208,7 +206,7 @@ pub(crate) enum Style { WhiteSpace(WhiteSpace), } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct StyleDecl { style: Style, importance: Importance, @@ -232,7 +230,7 @@ impl std::fmt::Display for StyleDecl { } } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] struct Ruleset { selector: Selector, styles: Vec, @@ -250,7 +248,7 @@ impl std::fmt::Display for Ruleset { } /// Stylesheet data which can be used while building the render tree. -#[derive(Clone, Default, Debug)] +#[derive(Clone, Default, Debug, PartialEq, Eq)] pub(crate) struct StyleData { agent_rules: Vec, user_rules: Vec, diff --git a/src/css/parser.rs b/src/css/parser.rs index 0d64f0c..5e33bfa 100644 --- a/src/css/parser.rs +++ b/src/css/parser.rs @@ -390,6 +390,10 @@ pub(crate) fn parse_color_attribute( parse_color(&value.tokens).or_else(|e| parse_faulty_color(e, text)) } +fn parse_color_part(text: &str, index: std::ops::Range) -> Option { + u8::from_str_radix(text.get(index)?, 16).ok() +} + // Both Firefox and Chromium accept "00aabb" as a bgcolor - I'm not sure this has ever been legal, // but regrettably I've had e-mails which were unreadable without doing this. fn parse_faulty_color( @@ -397,13 +401,11 @@ fn parse_faulty_color( text: &str, ) -> Result>> { let text = text.trim(); - if text.chars().all(|c| c.is_hex_digit()) { - if text.len() == 6 { - let r = u8::from_str_radix(&text[0..2], 16).unwrap(); - let g = u8::from_str_radix(&text[2..4], 16).unwrap(); - let b = u8::from_str_radix(&text[4..6], 16).unwrap(); - return Ok(Colour::Rgb(r, g, b)); - } + let r = parse_color_part(text, 0..2); + let g = parse_color_part(text, 2..4); + let b = parse_color_part(text, 4..6); + if let (Some(r), Some(g), Some(b)) = (r, g, b) { + return Ok(Colour::Rgb(r, g, b)); } Err(e) } @@ -620,7 +622,7 @@ fn parse_string_token(text: &str) -> IResult<&str, Token> { loop { match chars.next() { - None => return Ok((&text[text.len()..], Token::String(s.into()))), + None => return Ok(("", Token::String(s.into()))), Some((i, c)) if c == end_char => { return Ok((&text[i + 1..], Token::String(s.into()))); } diff --git a/src/lib.rs b/src/lib.rs index 41bea8a..9861f07 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,11 +67,10 @@ pub mod render; use render::text_renderer::{ RenderLine, RenderOptions, RichAnnotation, SubRenderer, TaggedLine, TextRenderer, }; -use render::{Renderer, RichDecorator, TextDecorator}; +use render::{Renderer, TextDecorator}; use html5ever::driver::ParseOpts; use html5ever::parse_document; -use html5ever::tendril::TendrilSink; use html5ever::tree_builder::TreeBuilderOpts; mod markup5ever_rcdom; pub use markup5ever_rcdom::RcDom; @@ -89,7 +88,7 @@ use std::io; use std::io::Write; use std::iter::{once, repeat}; -#[derive(Debug, Copy, Clone, Default, PartialEq)] +#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)] pub(crate) enum WhiteSpace { #[default] Normal, @@ -846,7 +845,7 @@ impl RenderNode { if style.internal_pre { write!(f, " internal_pre")?; } - writeln!(f, "") + writeln!(f) } fn write_self( &self, @@ -858,52 +857,52 @@ impl RenderNode { match &self.info { RenderNodeInfo::Text(s) => writeln!(f, "{:indent$}{s:?}", "")?, RenderNodeInfo::Container(v) => { - self.write_container("Container", &v, f, indent)?; + self.write_container("Container", v, f, indent)?; } RenderNodeInfo::Link(targ, v) => { - self.write_container(&format!("Link({})", targ), &v, f, indent)?; + self.write_container(&format!("Link({})", targ), v, f, indent)?; } RenderNodeInfo::Em(v) => { - self.write_container("Em", &v, f, indent)?; + self.write_container("Em", v, f, indent)?; } RenderNodeInfo::Strong(v) => { - self.write_container("Strong", &v, f, indent)?; + self.write_container("Strong", v, f, indent)?; } RenderNodeInfo::Strikeout(v) => { - self.write_container("Strikeout", &v, f, indent)?; + self.write_container("Strikeout", v, f, indent)?; } RenderNodeInfo::Code(v) => { - self.write_container("Code", &v, f, indent)?; + self.write_container("Code", v, f, indent)?; } RenderNodeInfo::Img(src, title) => { writeln!(f, "{:indent$}Img src={:?} title={:?}:", "", src, title)?; } RenderNodeInfo::Block(v) => { - self.write_container("Block", &v, f, indent)?; + self.write_container("Block", v, f, indent)?; } RenderNodeInfo::Header(depth, v) => { - self.write_container(&format!("Header({})", depth), &v, f, indent)?; + self.write_container(&format!("Header({})", depth), v, f, indent)?; } RenderNodeInfo::Div(v) => { - self.write_container("Div", &v, f, indent)?; + self.write_container("Div", v, f, indent)?; } RenderNodeInfo::BlockQuote(v) => { - self.write_container("BlockQuote", &v, f, indent)?; + self.write_container("BlockQuote", v, f, indent)?; } RenderNodeInfo::Ul(v) => { - self.write_container("Ul", &v, f, indent)?; + self.write_container("Ul", v, f, indent)?; } RenderNodeInfo::Ol(start, v) => { - self.write_container(&format!("Ol({})", start), &v, f, indent)?; + self.write_container(&format!("Ol({})", start), v, f, indent)?; } RenderNodeInfo::Dl(v) => { - self.write_container("Dl", &v, f, indent)?; + self.write_container("Dl", v, f, indent)?; } RenderNodeInfo::Dt(v) => { - self.write_container("Dt", &v, f, indent)?; + self.write_container("Dt", v, f, indent)?; } RenderNodeInfo::Dd(v) => { - self.write_container("Dd", &v, f, indent)?; + self.write_container("Dd", v, f, indent)?; } RenderNodeInfo::Break => { writeln!(f, "{:indent$}Break", "", indent = indent)?; @@ -942,10 +941,10 @@ impl RenderNode { writeln!(f, "{:indent$}FragStart({}):", "", frag)?; } RenderNodeInfo::ListItem(v) => { - self.write_container("ListItem", &v, f, indent)?; + self.write_container("ListItem", v, f, indent)?; } RenderNodeInfo::Sup(v) => { - self.write_container("Sup", &v, f, indent)?; + self.write_container("Sup", v, f, indent)?; } } Ok(()) @@ -1307,7 +1306,7 @@ where } } -#[derive(Default, Debug)] +#[derive(Debug, PartialEq, Eq)] struct HtmlContext { #[cfg(feature = "css")] style_data: css::StyleData, @@ -2355,7 +2354,7 @@ pub mod config { impl Config { /// Make the HtmlContext from self. - fn make_context(&self) -> HtmlContext { + pub(crate) fn make_context(&self) -> HtmlContext { HtmlContext { #[cfg(feature = "css")] style_data: self.style.clone(), @@ -2372,12 +2371,18 @@ pub mod config { } } /// Parse with context. - fn do_parse( - &mut self, - context: &mut HtmlContext, - input: R, - ) -> Result { - super::parse_with_context(input, context) + pub(crate) fn do_parse(&self, context: &mut HtmlContext, input: R) -> Result + where + R: io::Read, + { + let dom = self.parse_html(input)?; + let render_tree = super::dom_to_render_tree_with_context( + dom.document.clone(), + &mut io::sink(), + context, + )? + .ok_or(Error::Fail)?; + Ok(RenderTree(render_tree)) } /// Parse the HTML into a DOM structure. @@ -2439,11 +2444,7 @@ pub mod config { /// Reads HTML from `input`, and returns a `String` with text wrapped to /// `width` columns. - pub fn string_from_read( - mut self, - input: R, - width: usize, - ) -> Result { + pub fn string_from_read(self, input: R, width: usize) -> Result { let mut context = self.make_context(); let s = self .do_parse(&mut context, input)? @@ -2457,7 +2458,7 @@ pub mod config { /// of the provided text decorator's `Annotation`. The "outer" annotation comes first in /// the `Vec`. pub fn lines_from_read( - mut self, + self, input: R, width: usize, ) -> Result>>> { @@ -2549,30 +2550,14 @@ pub mod config { /// a list of `RichAnnotation` and some text, and returns the text /// with any terminal escapes desired to indicate those annotations /// (such as colour). - pub fn coloured( - mut self, - input: R, - width: usize, - colour_map: FMap, - ) -> Result + pub fn coloured(self, input: R, width: usize, colour_map: FMap) -> Result where R: std::io::Read, FMap: Fn(&[RichAnnotation], &str) -> String, { let mut context = self.make_context(); - let lines = self - .do_parse(&mut context, input)? - .render_with_context(&mut context, width, self.decorator)? - .into_lines()?; - - let mut result = String::new(); - for line in lines { - for ts in line.tagged_strings() { - result.push_str(&colour_map(&ts.tag, &ts.s)); - } - result.push('\n'); - } - Ok(result) + let render_tree = self.do_parse(&mut context, input)?; + self.render_coloured(render_tree, width, colour_map) } /// Return coloured text from a RenderTree. `colour_map` is a function which takes a list @@ -2602,38 +2587,12 @@ pub mod config { /// Return a Config initialized with a `RichDecorator`. pub fn rich() -> Config { - Config { - decorator: RichDecorator::new(), - #[cfg(feature = "css")] - style: Default::default(), - #[cfg(feature = "css")] - use_doc_css: false, - max_wrap_width: None, - pad_block_width: false, - allow_width_overflow: false, - min_wrap_width: MIN_WIDTH, - raw: false, - draw_borders: true, - wrap_links: true, - } + with_decorator(RichDecorator::new()) } /// Return a Config initialized with a `PlainDecorator`. pub fn plain() -> Config { - Config { - decorator: PlainDecorator::new(), - #[cfg(feature = "css")] - style: Default::default(), - #[cfg(feature = "css")] - use_doc_css: false, - max_wrap_width: None, - pad_block_width: false, - allow_width_overflow: false, - min_wrap_width: MIN_WIDTH, - raw: false, - draw_borders: true, - wrap_links: true, - } + with_decorator(PlainDecorator::new()) } /// Return a Config initialized with a custom decorator. @@ -2694,11 +2653,6 @@ impl RenderTree { render_tree_to_string(context, builder, &test_decorator, self.0, &mut io::sink())?; Ok(RenderedText(builder)) } - - /// Render this document using the given `decorator` and wrap it to `width` columns. - fn render(self, width: usize, decorator: D) -> Result> { - self.render_with_context(&mut Default::default(), width, decorator) - } } /// A rendered HTML document. @@ -2722,26 +2676,10 @@ impl RenderedText { } } -fn parse_with_context(mut input: impl io::Read, context: &mut HtmlContext) -> Result { - let opts = ParseOpts { - tree_builder: TreeBuilderOpts { - drop_doctype: true, - ..Default::default() - }, - ..Default::default() - }; - let dom = parse_document(RcDom::default(), opts) - .from_utf8() - .read_from(&mut input)?; - let render_tree = - dom_to_render_tree_with_context(dom.document.clone(), &mut io::sink(), context)? - .ok_or(Error::Fail)?; - Ok(RenderTree(render_tree)) -} - /// Reads and parses HTML from `input` and prepares a render tree. pub fn parse(input: impl io::Read) -> Result { - parse_with_context(input, &mut Default::default()) + let cfg = config::plain(); + cfg.do_parse(&mut cfg.make_context(), input) } /// Reads HTML from `input`, decorates it using `decorator`, and @@ -2764,6 +2702,7 @@ where } /// Reads HTML from `input`, and returns text wrapped to `width` columns. +/// /// The text is returned as a `Vec>`; the annotations are vectors /// of `RichAnnotation`. The "outer" annotation comes first in the `Vec`. pub fn from_read_rich(input: R, width: usize) -> Result>>> diff --git a/src/markup5ever_rcdom.rs b/src/markup5ever_rcdom.rs index dfdccd9..ef3ea24 100644 --- a/src/markup5ever_rcdom.rs +++ b/src/markup5ever_rcdom.rs @@ -151,7 +151,7 @@ impl Node { /// Return the element type (if an element) pub fn element_name(&self) -> Option { if let NodeData::Element { ref name, .. } = self.data { - Some(format!("{}", &*name.local_name())) + Some(format!("{}", name.local_name())) } else { None } diff --git a/src/tests.rs b/src/tests.rs index c06c12c..3787b49 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1529,13 +1529,9 @@ fn test_finalise() { TestDecorator } } - assert_eq!( - crate::parse("test".as_bytes()) - .unwrap() - .render(80, TestDecorator) - .unwrap() - .into_lines() + config::with_decorator(TestDecorator) + .lines_from_read("test".as_bytes(), 80) .unwrap(), vec![ TaggedLine::from_string("test".to_owned(), &Vec::new()), @@ -1934,9 +1930,9 @@ fn test_issue_93_x() { 114, 104, 60, 47, 101, 109, 62, 60, 99, 99, 172, 97, 97, 58, 60, 119, 99, 64, 126, 118, 104, 100, 100, 107, 105, 60, 120, 98, 255, 255, 255, 0, 60, 255, 127, 46, 60, 113, 127, ]; - let _local0 = crate::parse(&data[..]).unwrap(); - let d1 = TrivialDecorator::new(); - let _local1 = crate::RenderTree::render(_local0, 1, d1); + config::with_decorator(TrivialDecorator::new()) + .string_from_read(&data[..], 1) + .unwrap_err(); } #[test]