Add root doc comment, gemtext module

[?]
Jan 8, 2021, 8:21 AM
J4PKMKJXBUPG4QFHQVATUOXHWLEWMX3LTGWMNFKFAQUTJHU5GSAAC

Dependencies

  • [2] BOFUYB6I Add documentation and implement some feedback from Discord (https://discord.com/channels/273534239310479360/354038657075904544/796256815024701480)
  • [3] NKDEPTTN Fix typo
  • [4] JS6JZ7IA Add an initial planned feature list for `gemini`
  • [5] 5II6T7YE Add gemini library
  • [*] L6GZJGTU Add READMEs

Change contents

  • replacement in gemini/src/lib.rs at line 8
    [2.14214][2.14214:14223]()
    //! TODO
    [2.14214]
    [2.14223]
    //! A general purpose library of types for working with the Gemini protocol
    //!
    //! Represented are Gemini requests, responses, headers, and status codes.
    //! Requests are currently just a small layer of functionality over top of a
    //! `Url` from the aptly named `url` crate. All types are implemented with an
    //! eye towards implementing the Gemini specification faithfully and making
    //! illegal or invalid states unrepresentable.
  • edit in gemini/src/lib.rs at line 16
    [2.14224]
    [3.7933]
    pub mod gemtext;
  • edit in gemini/src/lib.rs at line 22
    [2.14226]
    [2.14226]
    pub use gemtext::{Builder, Doc, Level};
  • file addition: gemtext.rs (-xw-x--x--)
    [3.16]
    //! Gemtext documents
    //!
    //! The Gemini specification lays out a lightweight document format to
    //! facilitate highly readable and sufficiently interactive content.
    //!
    //! Presently, this module only provides a way to incrementally build up a
    //! gemtext document programmatically, and serialize as utf-8 text using any
    //! type that implements `std::fmt::Write`.
    //!
    //! ```
    //! use gemini::Builder;
    //!
    //! let doc = Builder::new().text("foo").line().quote("bar");
    //! assert_eq!(doc.build(), "foo\n\n> bar\n");
    //! ```
    use std::fmt::Display;
    /// Representation of lines in a gemtext document.
    #[derive(Debug, Clone, Eq, PartialEq)]
    pub enum Doc {
    /// Text line, corresponding to a paragraph of plain text.
    Text(String),
    /// Link line, with an optional title.
    Link {
    /// Link target.
    to: String,
    /// Optional name for the link.
    name: Option<String>,
    },
    /// A heading to add structure to a document.
    Heading(Level, String),
    /// An individual list item. Repeated items will form a single list.
    ListItem(String),
    /// Block quote.
    Quote(String),
    /// Preformatted text. This is the only document that should contain line breaks.
    Preformatted(String),
    /// Blank line. Technically equivalent to a Text line, but we use a sentinel to avoid allocation.
    Blank,
    }
    /// Heading level.
    #[derive(Debug, Copy, Clone, Eq, PartialEq)]
    pub enum Level {
    /// H1, top level headings.
    One,
    /// H2, section headings.
    Two,
    /// H3, subsection headings.
    Three,
    }
    impl Display for Level {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    match self {
    Level::One => write!(f, "#"),
    Level::Two => write!(f, "##"),
    Level::Three => write!(f, "###"),
    }
    }
    }
    /// A type for incrementally building up a gemtext document that can then be
    /// reified into text format.
    #[derive(Debug, Default, Clone, Eq, PartialEq)]
    pub struct Builder {
    docs: Vec<Doc>,
    }
    impl Builder {
    /// Create a new builder, representing the empty document.
    pub fn new() -> Self {
    Self::default()
    }
    fn push(mut self, doc: Doc) -> Self {
    self.docs.push(doc);
    self
    }
    fn extend(mut self, docs: impl Iterator<Item = Doc>) -> Self {
    self.docs.extend(docs);
    self
    }
    /// Add a text line to the document.
    pub fn text(self, text: impl Into<String>) -> Self {
    self.push(Doc::Text(text.into()))
    }
    /// Add a blank line to the document.
    pub fn line(self) -> Self {
    self.push(Doc::Blank)
    }
    /// Add a link line to the document.
    pub fn link(self, to: impl Into<String>, name: Option<impl Into<String>>) -> Self {
    let to = to.into();
    let name = name.map(Into::into);
    self.push(Doc::Link { to, name })
    }
    /// Add a heading of any level to the document.
    pub fn heading(self, level: Level, heading: impl Into<String>) -> Self {
    self.push(Doc::Heading(level, heading.into()))
    }
    /// Add a top level heading to the document.
    pub fn h1(self, heading: impl Into<String>) -> Self {
    self.heading(Level::One, heading)
    }
    /// Add a section heading to the document.
    pub fn h2(self, heading: impl Into<String>) -> Self {
    self.heading(Level::Two, heading)
    }
    /// Add a subsection heading to the document.
    pub fn h3(self, heading: impl Into<String>) -> Self {
    self.heading(Level::Three, heading)
    }
    /// Add an individual list item to the document.
    pub fn list_item(self, item: String) -> Self {
    self.push(Doc::ListItem(item))
    }
    /// Add a list to the document, from an iterator of strings.
    pub fn list<S: Into<String>>(self, items: impl IntoIterator<Item = S>) -> Self {
    let items = items.into_iter().map(|s| Doc::ListItem(s.into()));
    self.extend(items)
    }
    /// Add a block quote to the document.
    pub fn quote(self, quote: impl Into<String>) -> Self {
    self.push(Doc::Quote(quote.into()))
    }
    /// Add some preformatted text to the document.
    pub fn preformatted(self, preformatted: impl Into<String>) -> Self {
    self.push(Doc::Preformatted(preformatted.into()))
    }
    /// Generate a string representation of a document.
    pub fn build(self) -> String {
    format!("{}", self)
    }
    }
    impl Display for Builder {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
    self.docs.iter().try_for_each(|doc| match doc {
    Doc::Text(t) => {
    writeln!(f, "{}", t)
    }
    Doc::Link { to, name } => {
    write!(f, "=> {}", to)?;
    if let Some(name) = name {
    write!(f, " {}", name)?;
    }
    writeln!(f)
    }
    Doc::Heading(lvl, h) => {
    writeln!(f, "{} {}", lvl, h)
    }
    Doc::ListItem(i) => {
    writeln!(f, "* {}", i)
    }
    Doc::Quote(q) => {
    writeln!(f, "> {}", q)
    }
    Doc::Preformatted(p) => {
    writeln!(f, "```\n{}\n```", p)
    }
    Doc::Blank => writeln!(f),
    })
    }
    }
    #[cfg(test)]
    mod test {
    use super::*;
    #[test]
    pub fn test_builder() {
    let doc = Builder::new()
    .preformatted(" wooo\n/^^^^\\\n| |\n\\____/")
    .line()
    .h1("GAZE INTO THE SPHERE!")
    .line()
    .text("critics are raving")
    .quote("i love the sphere - bort")
    .quote("the sphere gives me purpose - frelvin")
    .line()
    .list(vec!["always", "trust", "the sphere"])
    .link("gemini://sphere.gaze", Some("gaze more here"));
    assert_eq!(
    doc.build(),
    r#"```
    wooo
    /^^^^\
    | |
    \____/
    ```
    # GAZE INTO THE SPHERE!
    critics are raving
    > i love the sphere - bort
    > the sphere gives me purpose - frelvin
    * always
    * trust
    * the sphere
    => gemini://sphere.gaze gaze more here
    "#
    )
    }
    }
  • replacement in gemini/README.md at line 8
    [3.99][2.16561:16691]()
    * a `gemtext` module for representing `text/gemini` content
    * parsers using `nom`
    * streaming response bodies
    * no_std support
    [3.99]
    [X] a `gemtext` module for representing `text/gemini` content
    [ ] parsers using `nom`
    [ ] streaming response bodies
    [ ] no_std support
    [ ] improve doc comments with links n such