2020-11-15 20:40:16 +00:00
|
|
|
//! Provides types for creating Gemini Documents.
|
|
|
|
//!
|
|
|
|
//! The module is centered around the `Document` type,
|
|
|
|
//! which provides all the necessary methods for programatically
|
|
|
|
//! creation of Gemini documents.
|
|
|
|
//!
|
|
|
|
//! # Examples
|
|
|
|
//!
|
|
|
|
//! ```
|
2020-11-25 05:42:09 +00:00
|
|
|
//! use kochab::document::HeadingLevel::*;
|
2020-11-15 20:40:16 +00:00
|
|
|
//!
|
2020-11-25 05:42:09 +00:00
|
|
|
//! let mut document = kochab::Document::new();
|
2020-11-15 20:40:16 +00:00
|
|
|
//!
|
|
|
|
//! document.add_heading(H1, "Heading 1");
|
|
|
|
//! document.add_heading(H2, "Heading 2");
|
|
|
|
//! document.add_heading(H3, "Heading 3");
|
|
|
|
//! document.add_blank_line();
|
|
|
|
//! document.add_text("text");
|
|
|
|
//! document.add_link("gemini://gemini.circumlunar.space", "Project Gemini");
|
|
|
|
//! document.add_unordered_list_item("list item");
|
|
|
|
//! document.add_quote("quote");
|
|
|
|
//! document.add_preformatted("preformatted");
|
|
|
|
//!
|
|
|
|
//! assert_eq!(document.to_string(), "\
|
|
|
|
//! ## Heading 1\n\
|
|
|
|
//! ### Heading 2\n\
|
|
|
|
//! #### Heading 3\n\
|
|
|
|
//! \n\
|
|
|
|
//! text\n\
|
|
|
|
//! => gemini://gemini.circumlunar.space/ Project Gemini\n\
|
|
|
|
//! * list item\n\
|
|
|
|
//! > quote\n\
|
|
|
|
//! ```\n\
|
|
|
|
//! preformatted\n\
|
|
|
|
//! ```\n\
|
|
|
|
//! ");
|
|
|
|
//! ```
|
|
|
|
#![warn(missing_docs)]
|
2020-11-14 08:55:21 +00:00
|
|
|
use std::convert::TryInto;
|
|
|
|
use std::fmt;
|
|
|
|
|
|
|
|
use crate::types::URIReference;
|
2020-11-14 21:46:29 +00:00
|
|
|
use crate::util::Cowy;
|
2020-11-14 08:55:21 +00:00
|
|
|
|
|
|
|
#[derive(Default)]
|
2020-11-15 20:40:16 +00:00
|
|
|
/// Represents a Gemini document.
|
|
|
|
///
|
|
|
|
/// Provides convenient methods for programatically
|
|
|
|
/// creation of Gemini documents.
|
2020-11-14 08:55:21 +00:00
|
|
|
pub struct Document {
|
|
|
|
items: Vec<Item>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Document {
|
2020-11-15 06:01:38 +00:00
|
|
|
/// Creates an empty Gemini `Document`.
|
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// ```
|
2020-11-25 05:42:09 +00:00
|
|
|
/// let document = kochab::Document::new();
|
2020-11-15 06:01:38 +00:00
|
|
|
///
|
|
|
|
/// assert_eq!(document.to_string(), "");
|
|
|
|
/// ```
|
2020-11-14 08:55:21 +00:00
|
|
|
pub fn new() -> Self {
|
|
|
|
Self::default()
|
|
|
|
}
|
|
|
|
|
2020-11-15 06:01:38 +00:00
|
|
|
/// Adds an `item` to the document.
|
|
|
|
///
|
|
|
|
/// An `item` usually corresponds to a single line,
|
|
|
|
/// except in the case of preformatted text.
|
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
2020-11-15 20:40:16 +00:00
|
|
|
/// ```compile_fail
|
2020-11-25 05:42:09 +00:00
|
|
|
/// use kochab::document::{Document, Item, Text};
|
2020-11-15 06:01:38 +00:00
|
|
|
///
|
|
|
|
/// let mut document = Document::new();
|
|
|
|
/// let text = Text::new_lossy("foo");
|
|
|
|
/// let item = Item::Text(text);
|
|
|
|
///
|
|
|
|
/// document.add_item(item);
|
|
|
|
///
|
|
|
|
/// assert_eq!(document.to_string(), "foo\n");
|
|
|
|
/// ```
|
2020-11-15 20:17:22 +00:00
|
|
|
fn add_item(&mut self, item: Item) -> &mut Self {
|
2020-11-14 08:55:21 +00:00
|
|
|
self.items.push(item);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-11-15 06:01:38 +00:00
|
|
|
/// Adds multiple `items` to the document.
|
|
|
|
///
|
|
|
|
/// This is a convenience wrapper around `add_item`.
|
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
2020-11-15 20:40:16 +00:00
|
|
|
/// ```compile_fail
|
2020-11-25 05:42:09 +00:00
|
|
|
/// use kochab::document::{Document, Item, Text};
|
2020-11-15 06:01:38 +00:00
|
|
|
///
|
|
|
|
/// let mut document = Document::new();
|
|
|
|
/// let items = vec!["foo", "bar", "baz"]
|
|
|
|
/// .into_iter()
|
|
|
|
/// .map(Text::new_lossy)
|
|
|
|
/// .map(Item::Text);
|
|
|
|
///
|
|
|
|
/// document.add_items(items);
|
|
|
|
///
|
|
|
|
/// assert_eq!(document.to_string(), "foo\nbar\nbaz\n");
|
|
|
|
/// ```
|
2020-11-15 20:17:22 +00:00
|
|
|
fn add_items<I>(&mut self, items: I) -> &mut Self
|
2020-11-14 08:55:21 +00:00
|
|
|
where
|
|
|
|
I: IntoIterator<Item = Item>,
|
|
|
|
{
|
|
|
|
self.items.extend(items);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-11-15 06:01:38 +00:00
|
|
|
/// Adds a blank line to the document.
|
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// ```
|
2020-11-25 05:42:09 +00:00
|
|
|
/// let mut document = kochab::Document::new();
|
2020-11-15 06:01:38 +00:00
|
|
|
///
|
|
|
|
/// document.add_blank_line();
|
|
|
|
///
|
|
|
|
/// assert_eq!(document.to_string(), "\n");
|
|
|
|
/// ```
|
2020-11-14 08:55:21 +00:00
|
|
|
pub fn add_blank_line(&mut self) -> &mut Self {
|
|
|
|
self.add_item(Item::Text(Text::blank()))
|
|
|
|
}
|
|
|
|
|
2020-11-15 06:01:38 +00:00
|
|
|
/// Adds plain text to the document.
|
|
|
|
///
|
|
|
|
/// This function allows adding multiple lines at once.
|
|
|
|
///
|
|
|
|
/// It inserts a whitespace at the beginning of a line
|
|
|
|
/// if it starts with a character sequence that
|
|
|
|
/// would make it a non-plain text line (e.g. link, heading etc).
|
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// ```
|
2020-11-25 05:42:09 +00:00
|
|
|
/// let mut document = kochab::Document::new();
|
2020-11-15 06:01:38 +00:00
|
|
|
///
|
|
|
|
/// document.add_text("hello\n* world!");
|
|
|
|
///
|
|
|
|
/// assert_eq!(document.to_string(), "hello\n * world!\n");
|
|
|
|
/// ```
|
2020-11-28 19:47:39 +00:00
|
|
|
pub fn add_text(&mut self, text: impl AsRef<str>) -> &mut Self {
|
2020-11-14 08:55:21 +00:00
|
|
|
let text = text
|
2020-11-28 19:47:39 +00:00
|
|
|
.as_ref()
|
2020-11-14 08:55:21 +00:00
|
|
|
.lines()
|
|
|
|
.map(Text::new_lossy)
|
|
|
|
.map(Item::Text);
|
|
|
|
|
|
|
|
self.add_items(text);
|
|
|
|
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-11-15 06:01:38 +00:00
|
|
|
/// Adds a link to the document.
|
|
|
|
///
|
|
|
|
/// `uri`s that fail to parse are substituted with `.`.
|
|
|
|
///
|
|
|
|
/// Consecutive newlines in `label` will be replaced
|
|
|
|
/// with a single whitespace.
|
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// ```
|
2020-11-25 05:42:09 +00:00
|
|
|
/// let mut document = kochab::Document::new();
|
2020-11-15 06:01:38 +00:00
|
|
|
///
|
|
|
|
/// document.add_link("https://wikipedia.org", "Wiki\n\nWiki");
|
|
|
|
///
|
|
|
|
/// assert_eq!(document.to_string(), "=> https://wikipedia.org/ Wiki Wiki\n");
|
|
|
|
/// ```
|
2020-11-14 21:46:29 +00:00
|
|
|
pub fn add_link<'a, U>(&mut self, uri: U, label: impl Cowy<str>) -> &mut Self
|
2020-11-14 08:55:21 +00:00
|
|
|
where
|
|
|
|
U: TryInto<URIReference<'a>>,
|
|
|
|
{
|
|
|
|
let uri = uri
|
|
|
|
.try_into()
|
|
|
|
.map(URIReference::into_owned)
|
|
|
|
.or_else(|_| ".".try_into()).expect("Northstar BUG");
|
|
|
|
let label = LinkLabel::from_lossy(label);
|
2020-11-19 22:58:33 +00:00
|
|
|
let link = Link { uri: Box::new(uri), label: Some(label) };
|
2020-11-14 08:55:21 +00:00
|
|
|
let link = Item::Link(link);
|
|
|
|
|
|
|
|
self.add_item(link);
|
|
|
|
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-11-15 20:14:41 +00:00
|
|
|
/// Adds a link to the document, but without a label.
|
|
|
|
///
|
|
|
|
/// See `add_link` for details.
|
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// ```
|
2020-11-25 05:42:09 +00:00
|
|
|
/// let mut document = kochab::Document::new();
|
2020-11-15 20:14:41 +00:00
|
|
|
///
|
|
|
|
/// document.add_link_without_label("https://wikipedia.org");
|
|
|
|
///
|
|
|
|
/// assert_eq!(document.to_string(), "=> https://wikipedia.org/\n");
|
|
|
|
/// ```
|
|
|
|
pub fn add_link_without_label<'a, U>(&mut self, uri: U) -> &mut Self
|
|
|
|
where
|
|
|
|
U: TryInto<URIReference<'a>>,
|
|
|
|
{
|
|
|
|
let uri = uri
|
|
|
|
.try_into()
|
|
|
|
.map(URIReference::into_owned)
|
|
|
|
.or_else(|_| ".".try_into()).expect("Northstar BUG");
|
2020-11-14 08:55:21 +00:00
|
|
|
let link = Link {
|
2020-11-19 22:58:33 +00:00
|
|
|
uri: Box::new(uri),
|
2020-11-14 08:55:21 +00:00
|
|
|
label: None,
|
|
|
|
};
|
|
|
|
let link = Item::Link(link);
|
|
|
|
|
|
|
|
self.add_item(link);
|
2020-11-15 06:01:38 +00:00
|
|
|
|
2020-11-14 08:55:21 +00:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-11-15 20:14:41 +00:00
|
|
|
/// Adds a block of preformatted text.
|
|
|
|
///
|
|
|
|
/// Lines that start with ` ``` ` will be prependend with a whitespace.
|
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// ```
|
2020-11-25 05:42:09 +00:00
|
|
|
/// let mut document = kochab::Document::new();
|
2020-11-15 20:14:41 +00:00
|
|
|
///
|
|
|
|
/// document.add_preformatted("a\n b\n c");
|
|
|
|
///
|
|
|
|
/// assert_eq!(document.to_string(), "```\na\n b\n c\n```\n");
|
|
|
|
/// ```
|
2020-11-28 19:47:39 +00:00
|
|
|
pub fn add_preformatted(&mut self, preformatted_text: impl AsRef<str>) -> &mut Self {
|
|
|
|
self.add_preformatted_with_alt("", preformatted_text.as_ref())
|
2020-11-14 08:55:21 +00:00
|
|
|
}
|
|
|
|
|
2020-11-15 20:14:41 +00:00
|
|
|
/// Adds a block of preformatted text with an alt text.
|
|
|
|
///
|
|
|
|
/// Consecutive newlines in `alt` will be replaced
|
|
|
|
/// with a single whitespace.
|
|
|
|
///
|
|
|
|
/// `preformatted_text` lines that start with ` ``` `
|
|
|
|
/// will be prependend with a whitespace.
|
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// ```
|
2020-11-25 05:42:09 +00:00
|
|
|
/// let mut document = kochab::Document::new();
|
2020-11-15 20:14:41 +00:00
|
|
|
///
|
|
|
|
/// document.add_preformatted_with_alt("rust", "fn main() {\n}\n");
|
|
|
|
///
|
|
|
|
/// assert_eq!(document.to_string(), "```rust\nfn main() {\n}\n```\n");
|
|
|
|
/// ```
|
2020-11-28 19:47:39 +00:00
|
|
|
pub fn add_preformatted_with_alt(&mut self, alt: impl AsRef<str>, preformatted_text: impl AsRef<str>) -> &mut Self {
|
|
|
|
let alt = AltText::new_lossy(alt.as_ref());
|
2020-11-14 08:55:21 +00:00
|
|
|
let lines = preformatted_text
|
2020-11-28 19:47:39 +00:00
|
|
|
.as_ref()
|
2020-11-14 08:55:21 +00:00
|
|
|
.lines()
|
|
|
|
.map(PreformattedText::new_lossy)
|
|
|
|
.collect();
|
|
|
|
let preformatted = Preformatted {
|
|
|
|
alt,
|
|
|
|
lines,
|
|
|
|
};
|
|
|
|
let preformatted = Item::Preformatted(preformatted);
|
|
|
|
|
|
|
|
self.add_item(preformatted);
|
|
|
|
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-11-15 20:14:41 +00:00
|
|
|
/// Adds a heading.
|
|
|
|
///
|
|
|
|
/// Consecutive newlines in `text` will be replaced
|
|
|
|
/// with a single whitespace.
|
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// ```
|
2020-11-25 05:42:09 +00:00
|
|
|
/// use kochab::document::HeadingLevel::H1;
|
2020-11-15 20:14:41 +00:00
|
|
|
///
|
2020-11-25 05:42:09 +00:00
|
|
|
/// let mut document = kochab::Document::new();
|
2020-11-15 20:14:41 +00:00
|
|
|
///
|
|
|
|
/// document.add_heading(H1, "Welcome!");
|
|
|
|
///
|
|
|
|
/// assert_eq!(document.to_string(), "# Welcome!\n");
|
|
|
|
/// ```
|
2020-11-14 21:46:29 +00:00
|
|
|
pub fn add_heading(&mut self, level: HeadingLevel, text: impl Cowy<str>) -> &mut Self {
|
2020-11-14 08:55:21 +00:00
|
|
|
let text = HeadingText::new_lossy(text);
|
|
|
|
let heading = Heading {
|
|
|
|
level,
|
|
|
|
text,
|
|
|
|
};
|
|
|
|
let heading = Item::Heading(heading);
|
|
|
|
|
|
|
|
self.add_item(heading);
|
|
|
|
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-11-15 20:14:41 +00:00
|
|
|
/// Adds an unordered list item.
|
|
|
|
///
|
|
|
|
/// Consecutive newlines in `text` will be replaced
|
|
|
|
/// with a single whitespace.
|
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// ```
|
2020-11-25 05:42:09 +00:00
|
|
|
/// let mut document = kochab::Document::new();
|
2020-11-15 20:14:41 +00:00
|
|
|
///
|
|
|
|
/// document.add_unordered_list_item("milk");
|
|
|
|
/// document.add_unordered_list_item("eggs");
|
|
|
|
///
|
|
|
|
/// assert_eq!(document.to_string(), "* milk\n* eggs\n");
|
|
|
|
/// ```
|
2020-11-28 19:47:39 +00:00
|
|
|
pub fn add_unordered_list_item(&mut self, text: impl AsRef<str>) -> &mut Self {
|
|
|
|
let item = UnorderedListItem::new_lossy(text.as_ref());
|
2020-11-14 08:55:21 +00:00
|
|
|
let item = Item::UnorderedListItem(item);
|
|
|
|
|
|
|
|
self.add_item(item);
|
|
|
|
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
2020-11-15 20:14:41 +00:00
|
|
|
/// Adds a quote.
|
|
|
|
///
|
|
|
|
/// This function allows adding multiple quote lines at once.
|
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// ```
|
2020-11-25 05:42:09 +00:00
|
|
|
/// let mut document = kochab::Document::new();
|
2020-11-15 20:14:41 +00:00
|
|
|
///
|
|
|
|
/// document.add_quote("I think,\ntherefore I am");
|
|
|
|
///
|
|
|
|
/// assert_eq!(document.to_string(), "> I think,\n> therefore I am\n");
|
|
|
|
/// ```
|
2020-11-28 19:47:39 +00:00
|
|
|
pub fn add_quote(&mut self, text: impl AsRef<str>) -> &mut Self {
|
2020-11-14 08:55:21 +00:00
|
|
|
let quote = text
|
2020-11-28 19:47:39 +00:00
|
|
|
.as_ref()
|
2020-11-14 08:55:21 +00:00
|
|
|
.lines()
|
|
|
|
.map(Quote::new_lossy)
|
|
|
|
.map(Item::Quote);
|
2020-11-15 06:01:38 +00:00
|
|
|
|
2020-11-14 08:55:21 +00:00
|
|
|
self.add_items(quote);
|
|
|
|
|
|
|
|
self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Display for Document {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
|
|
for item in &self.items {
|
|
|
|
match item {
|
|
|
|
Item::Text(text) => writeln!(f, "{}", text.0)?,
|
|
|
|
Item::Link(link) => {
|
|
|
|
let separator = if link.label.is_some() {" "} else {""};
|
|
|
|
let label = link.label.as_ref().map(|label| label.0.as_str())
|
|
|
|
.unwrap_or("");
|
|
|
|
|
2020-11-15 06:01:38 +00:00
|
|
|
writeln!(f, "=> {}{}{}", link.uri, separator, label)?;
|
2020-11-14 08:55:21 +00:00
|
|
|
}
|
|
|
|
Item::Preformatted(preformatted) => {
|
|
|
|
writeln!(f, "```{}", preformatted.alt.0)?;
|
|
|
|
|
|
|
|
for line in &preformatted.lines {
|
|
|
|
writeln!(f, "{}", line.0)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
writeln!(f, "```")?
|
|
|
|
}
|
|
|
|
Item::Heading(heading) => {
|
|
|
|
let level = match heading.level {
|
|
|
|
HeadingLevel::H1 => "#",
|
|
|
|
HeadingLevel::H2 => "##",
|
|
|
|
HeadingLevel::H3 => "###",
|
|
|
|
};
|
|
|
|
|
|
|
|
writeln!(f, "{} {}", level, heading.text.0)?;
|
|
|
|
}
|
|
|
|
Item::UnorderedListItem(item) => writeln!(f, "* {}", item.0)?,
|
|
|
|
Item::Quote(quote) => writeln!(f, "> {}", quote.0)?,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-19 22:58:33 +00:00
|
|
|
#[allow(clippy::enum_variant_names)]
|
2020-11-15 20:17:22 +00:00
|
|
|
enum Item {
|
2020-11-14 08:55:21 +00:00
|
|
|
Text(Text),
|
|
|
|
Link(Link),
|
|
|
|
Preformatted(Preformatted),
|
|
|
|
Heading(Heading),
|
|
|
|
UnorderedListItem(UnorderedListItem),
|
|
|
|
Quote(Quote),
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Default)]
|
2020-11-15 20:17:22 +00:00
|
|
|
struct Text(String);
|
2020-11-14 08:55:21 +00:00
|
|
|
|
|
|
|
impl Text {
|
2020-11-15 20:17:22 +00:00
|
|
|
fn blank() -> Self {
|
2020-11-14 08:55:21 +00:00
|
|
|
Self::default()
|
|
|
|
}
|
|
|
|
|
2020-11-15 20:17:22 +00:00
|
|
|
fn new_lossy(line: impl Cowy<str>) -> Self {
|
2020-11-14 08:55:21 +00:00
|
|
|
Self(lossy_escaped_line(line, SPECIAL_STARTS))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-15 20:17:22 +00:00
|
|
|
struct Link {
|
2020-11-19 22:58:33 +00:00
|
|
|
uri: Box<URIReference<'static>>,
|
2020-11-15 20:17:22 +00:00
|
|
|
label: Option<LinkLabel>,
|
2020-11-14 08:55:21 +00:00
|
|
|
}
|
|
|
|
|
2020-11-15 20:17:22 +00:00
|
|
|
struct LinkLabel(String);
|
2020-11-14 08:55:21 +00:00
|
|
|
|
|
|
|
impl LinkLabel {
|
2020-11-15 20:17:22 +00:00
|
|
|
fn from_lossy(line: impl Cowy<str>) -> Self {
|
2020-11-14 08:55:21 +00:00
|
|
|
let line = strip_newlines(line);
|
2020-11-15 06:01:38 +00:00
|
|
|
|
2020-11-19 22:58:33 +00:00
|
|
|
Self(line)
|
2020-11-14 08:55:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-15 20:17:22 +00:00
|
|
|
struct Preformatted {
|
|
|
|
alt: AltText,
|
|
|
|
lines: Vec<PreformattedText>,
|
2020-11-14 08:55:21 +00:00
|
|
|
}
|
|
|
|
|
2020-11-15 20:17:22 +00:00
|
|
|
struct PreformattedText(String);
|
2020-11-14 08:55:21 +00:00
|
|
|
|
|
|
|
impl PreformattedText {
|
2020-11-15 20:17:22 +00:00
|
|
|
fn new_lossy(line: impl Cowy<str>) -> Self {
|
2020-11-14 08:55:21 +00:00
|
|
|
Self(lossy_escaped_line(line, &[PREFORMATTED_TOGGLE_START]))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-15 20:17:22 +00:00
|
|
|
struct AltText(String);
|
2020-11-14 08:55:21 +00:00
|
|
|
|
|
|
|
impl AltText {
|
2020-11-15 20:17:22 +00:00
|
|
|
fn new_lossy(alt: &str) -> Self {
|
2020-11-14 08:55:21 +00:00
|
|
|
let alt = strip_newlines(alt);
|
2020-11-15 06:01:38 +00:00
|
|
|
|
2020-11-14 08:55:21 +00:00
|
|
|
Self(alt)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-15 20:17:22 +00:00
|
|
|
struct Heading {
|
|
|
|
level: HeadingLevel,
|
|
|
|
text: HeadingText,
|
2020-11-14 08:55:21 +00:00
|
|
|
}
|
|
|
|
|
2020-11-15 20:40:16 +00:00
|
|
|
/// The level of a heading.
|
2020-11-14 08:55:21 +00:00
|
|
|
pub enum HeadingLevel {
|
2020-11-15 20:40:16 +00:00
|
|
|
/// Heading level 1 (`#`)
|
2020-11-14 08:55:21 +00:00
|
|
|
H1,
|
2020-11-15 20:40:16 +00:00
|
|
|
/// Heading level 2 (`##`)
|
2020-11-14 08:55:21 +00:00
|
|
|
H2,
|
2020-11-15 20:40:16 +00:00
|
|
|
/// Heading level 3 (`###`)
|
2020-11-14 08:55:21 +00:00
|
|
|
H3,
|
|
|
|
}
|
|
|
|
|
2020-11-15 20:17:22 +00:00
|
|
|
struct HeadingText(String);
|
2020-11-14 08:55:21 +00:00
|
|
|
|
|
|
|
impl HeadingText {
|
2020-11-15 20:17:22 +00:00
|
|
|
fn new_lossy(line: impl Cowy<str>) -> Self {
|
2020-11-14 08:55:21 +00:00
|
|
|
let line = strip_newlines(line);
|
|
|
|
|
2020-11-15 20:14:53 +00:00
|
|
|
Self(line)
|
2020-11-14 08:55:21 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-15 20:17:22 +00:00
|
|
|
struct UnorderedListItem(String);
|
2020-11-14 08:55:21 +00:00
|
|
|
|
|
|
|
impl UnorderedListItem {
|
2020-11-15 20:17:22 +00:00
|
|
|
fn new_lossy(text: &str) -> Self {
|
2020-11-14 08:55:21 +00:00
|
|
|
let text = strip_newlines(text);
|
2020-11-15 06:01:38 +00:00
|
|
|
|
2020-11-14 08:55:21 +00:00
|
|
|
Self(text)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-15 20:17:22 +00:00
|
|
|
struct Quote(String);
|
2020-11-14 08:55:21 +00:00
|
|
|
|
|
|
|
impl Quote {
|
2020-11-15 20:17:22 +00:00
|
|
|
fn new_lossy(text: &str) -> Self {
|
2020-11-14 08:55:21 +00:00
|
|
|
Self(lossy_escaped_line(text, &[QUOTE_START]))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const LINK_START: &str = "=>";
|
|
|
|
const PREFORMATTED_TOGGLE_START: &str = "```";
|
|
|
|
const HEADING_START: &str = "#";
|
|
|
|
const UNORDERED_LIST_ITEM_START: &str = "*";
|
|
|
|
const QUOTE_START: &str = ">";
|
|
|
|
|
|
|
|
const SPECIAL_STARTS: &[&str] = &[
|
|
|
|
LINK_START,
|
|
|
|
PREFORMATTED_TOGGLE_START,
|
|
|
|
HEADING_START,
|
|
|
|
UNORDERED_LIST_ITEM_START,
|
|
|
|
QUOTE_START,
|
|
|
|
];
|
|
|
|
|
|
|
|
fn starts_with_any(s: &str, starts: &[&str]) -> bool {
|
|
|
|
for start in starts {
|
|
|
|
if s.starts_with(start) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
false
|
|
|
|
}
|
|
|
|
|
2020-11-14 21:46:29 +00:00
|
|
|
fn lossy_escaped_line(line: impl Cowy<str>, escape_starts: &[&str]) -> String {
|
2020-11-14 08:55:21 +00:00
|
|
|
let line_ref = line.as_ref();
|
|
|
|
let contains_newline = line_ref.contains('\n');
|
|
|
|
let has_special_start = starts_with_any(line_ref, escape_starts);
|
|
|
|
|
|
|
|
if !contains_newline && !has_special_start {
|
|
|
|
return line.into();
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut line = String::new();
|
|
|
|
|
|
|
|
if has_special_start {
|
|
|
|
line.push(' ');
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(line_ref) = line_ref.split('\n').next() {
|
|
|
|
line.push_str(line_ref);
|
|
|
|
}
|
|
|
|
|
|
|
|
line
|
|
|
|
}
|
|
|
|
|
2020-11-14 21:46:29 +00:00
|
|
|
fn strip_newlines(text: impl Cowy<str>) -> String {
|
2020-11-14 08:55:21 +00:00
|
|
|
if !text.as_ref().contains(&['\r', '\n'][..]) {
|
|
|
|
return text.into();
|
|
|
|
}
|
|
|
|
|
|
|
|
text.as_ref()
|
|
|
|
.lines()
|
|
|
|
.filter(|part| !part.is_empty())
|
2020-11-19 07:02:11 +00:00
|
|
|
.collect::<Vec<_>>()
|
2020-11-14 08:55:21 +00:00
|
|
|
.join(" ")
|
|
|
|
}
|