// Copyright 2018-2019 Sebastian Wiesner // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // http://www.apache.org/licenses/LICENSE-2.0 // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #![deny(warnings, missing_docs, clippy::all)] //! Write markdown to TTYs. #[cfg(feature = "resources")] use url; use ansi_term::{Colour, Style}; use failure::Error; use pulldown_cmark::Event::*; use pulldown_cmark::Tag::*; use pulldown_cmark::{CowStr, Event, LinkType, Tag}; use std::collections::VecDeque; use std::io; use std::io::Write; use std::path::Path; use syntect::easy::HighlightLines; use syntect::highlighting::{Theme, ThemeSet}; use syntect::parsing::SyntaxSet; mod resources; mod terminal; // Expose some select things for use in main pub use crate::resources::ResourceAccess; pub use crate::terminal::*; /// Dump markdown events to a writer. pub fn dump_events<'a, W, I>(writer: &mut W, events: I) -> Result<(), Error> where I: Iterator>, W: Write, { for event in events { writeln!(writer, "{:?}", event)?; } Ok(()) } /// Write markdown to a TTY. /// /// Iterate over Markdown AST `events`, format each event for TTY output and /// write the result to a `writer`. /// /// `push_tty` tries to limit output to the given number of TTY `columns` but /// does not guarantee that output stays within the column limit. pub fn push_tty<'a, 'e, W, I>( writer: &'a mut W, capabilities: TerminalCapabilities, size: TerminalSize, mut events: I, base_dir: &'a Path, resource_access: ResourceAccess, syntax_set: SyntaxSet, ) -> Result<(), Error> where I: Iterator>, W: Write, { let theme = &ThemeSet::load_defaults().themes["Solarized (dark)"]; events .try_fold( Context::new( writer, capabilities, size, base_dir, resource_access, syntax_set, theme, ), write_event, )? .write_pending_links()?; Ok(()) } /// The "level" the current event occurs at. #[derive(Debug, PartialEq)] enum BlockLevel { /// The event occurs at block-level. Block, /// The event occurs in inline text. Inline, } /// The kind of the current list item #[derive(Debug)] enum ListItemKind { /// An unordered list item Unordered, /// An ordered list item with its current number Ordered(usize), } /// A link. #[derive(Debug)] struct Link<'a> { /// The index of the link. index: usize, /// The link destination. destination: CowStr<'a>, /// The link title. title: CowStr<'a>, } /// Input context. #[cfg(feature = "resources")] struct ResourceContext<'a> { /// The base directory, to resolve relative paths. base_dir: &'a Path, /// What resources we may access when processing markdown. resource_access: ResourceAccess, } #[cfg(feature = "resources")] impl ResourceContext<'_> { /// Resolve a reference in the input. /// /// If `reference` parses as URL return the parsed URL. Otherwise assume /// `reference` is a file path, resolve it against `base_dir` and turn it /// into a file:// URL. If this also fails return `None`. fn resolve_reference(&self, reference: &str) -> Option { use url::Url; Url::parse(reference) .or_else(|_| Url::from_file_path(self.base_dir.join(reference))) .ok() } } /// Context for TTY output. struct OutputContext<'a, W: Write> { /// The terminal dimensions to limit output to. size: TerminalSize, /// A writer to the terminal. writer: &'a mut W, /// The capabilities of the terminal. capabilities: TerminalCapabilities, } #[derive(Debug)] struct StyleContext { /// The current style current: Style, /// Previous styles. /// /// Holds previous styles; whenever we disable the current style we restore /// the last one from this list. previous: Vec