Update `gemini`: Convenience methods, make streaming parsers public; update tests

[?]
Apr 16, 2021, 2:24 AM
3UXSFGJYCPS5IT5O5QDKLJOMDJKE7ZBTCFX3BYXTH2JFLP5GLDMAC

Dependencies

  • [2] M5HGUS2T Add parser tests and fix some bugs
  • [3] 2L47PJWN Fix Cargo repo again
  • [4] 2OIMF64I Update README
  • [5] J4PKMKJX Add root doc comment, gemtext module
  • [6] N3U3GWEL Fix README formatting
  • [7] LYRVL33U Bump version, add CHANGELOG
  • [8] ENARAOAB Tweak Cargo manifest for gemini
  • [9] LSYI2TXC Housekeeping, add RequestType
  • [10] NKDEPTTN Fix typo
  • [11] 55JQX7NF Add sputnik
  • [12] K37J3USB Add WIP gemtext parsers
  • [13] Y55SCAUN Finish gemtext parsing implementation
  • [14] RTDVYLFT Give gemtext parser a more uniform interface wrt other parsers
  • [15] BOFUYB6I Add documentation and implement some feedback from Discord (https://discord.com/channels/273534239310479360/354038657075904544/796256815024701480)
  • [16] KP6SZZ34 Fix building without parsers
  • [17] JS6JZ7IA Add an initial planned feature list for `gemini`
  • [18] JBZGFYVO Add nom parsers for main types
  • [19] 7DH43OFG Add gemtext helpers for headers and responses
  • [20] 362KVUHO Even more housekeeping
  • [21] YYPDM4ND More housekeeping
  • [22] XZ6Z2D6H DRY parser impls
  • [23] G6HLEG2X Update ignore
  • [24] XHDJKZOS Fix test
  • [25] AOSTKTLO Licensing
  • [26] WJROXZ3C Add note to README thanking mgattozzi
  • [27] 5II6T7YE Add gemini library
  • [28] L6GZJGTU Add READMEs

Change contents

  • file move: gemini (dxwrx-rx-r)gemini (d--r------)
    [1.0]
    [5.9]
  • file move: Cargo.toml (-xw-x--x--)Cargo.toml (----------)
    [5.9]
    [5.9738]
  • replacement in gemini/Cargo.toml at line 11
    [3.59][5.0:22](),[5.360][5.0:22]()
    version = "0.0.3"
    [3.59]
    [5.9852]
    version = "0.0.4"
  • replacement in gemini/Cargo.toml at line 20
    [5.9966][5.3583:3624]()
    thiserror = "1.0.23"
    url = "2.2.0"
    [5.9966]
    [5.4312]
    thiserror = "1.0.24"
    url = "2.2.1"
  • replacement in gemini/Cargo.toml at line 23
    [5.4313][5.4313:4407]()
    nom = { version = "6.0.1", optional = true }
    paste = { version = "1.0.4", optional = true }
    [5.4313]
    nom = { version = "6.1.2", optional = true }
    paste = { version = "1.0.5", optional = true }
  • file move: CHANGELOG.md (-xw-x--x--)CHANGELOG.md (----------)
    [5.9]
    [5.38]
  • replacement in gemini/CHANGELOG.md at line 9
    [5.308][5.308:332]()
    ## [0.0.3] - 2020-01-14
    [5.308]
    [5.332]
    ## [0.0.4] - 2021-04-15
    ### Added
    - Expose both complete and streaming parsers.
    ### Changed
    - Gemtext parsing doesn't preserve whitespace
    ## [0.0.3] - 2021-01-14
  • replacement in gemini/CHANGELOG.md at line 25
    [5.510][5.510:534]()
    ## [0.0.2] - 2020-01-14
    [5.510]
    [5.534]
    ## [0.0.2] - 2021-01-14
  • file move: README.md (-xw-x--x--)README.md (----------)
    [5.9]
    [5.12]
  • replacement in gemini/README.md at line 8
    [5.99][5.0:64](),[5.64][4.0:26](),[4.26][5.90:190](),[5.90][5.90:190]()
    - [x] a `gemtext` module for representing `text/gemini` content
    - [X] parsers using `nom`
    - [ ] streaming response bodies
    - [ ] `no_std` support
    - [ ] improve doc comments with links n such
    [5.99]
    [5.44]
    - [X] a `gemtext` module for representing `text/gemini` content
    - [X] parsers using `nom`
    - [ ] streaming response bodies
    - [ ] no_std support
    - [ ] improve doc comments with links n such
    - [ ] lossless `gemtext` representation
  • edit in gemini/README.md at line 18
    [5.46][4.118:119]()
  • file move: src (dxwrx-rx-r)src (d--r------)
    [5.9]
    [5.16]
  • file move: status.rs (-xw-x--x--)status.rs (----------)
    [5.16]
    [5.29]
  • edit in gemini/src/status.rs at line 83
    [5.2041][5.2041:2042]()
  • replacement in gemini/src/status.rs at line 239
    [5.56][5.56:140]()
    use nom::{bytes::complete::take, combinator::map_res, error::context, IResult};
    [5.56]
    [5.140]
    use nom::{bytes::streaming::take, combinator::map_res, error::context, IResult};
  • file move: response.rs (-xw-x--x--)response.rs (----------)
    [5.16]
    [5.5630]
  • file move: request.rs (-xw-x--x--)request.rs (----------)
    [5.16]
    [5.6278]
  • edit in gemini/src/request.rs at line 11
    [5.6320]
    [5.6320]
    pub use request_type::{Any, Gemini};
  • replacement in gemini/src/request.rs at line 115
    [5.7315][5.7315:7364]()
    self.url.scheme() == Self::GEMINI_SCHEME
    [5.7315]
    [5.7364]
    self.scheme() == Self::GEMINI_SCHEME
  • edit in gemini/src/request.rs at line 122
    [5.7586]
    [5.1920]
    /// Return the scheme for the underlying url.
    pub fn scheme(&self) -> &str {
    self.url.scheme()
    }
    /// Return a reference to the underlying url.
    pub fn url(&self) -> &Url {
    &self.url
    }
  • edit in gemini/src/request.rs at line 152
    [5.1402]
    [5.1402]
    impl GeminiRequest {
    /// Return the hostname associated with the Gemini request.
    pub fn host(&self) -> &str {
    self.url.host_str().unwrap()
    }
    /// Return the path portion of the Gemini request url.
    pub fn path(&self) -> &str {
    self.url.path()
    }
    /// Return the port associated with the Gemini request.
    pub fn port(&self) -> u16 {
    self.url.port().unwrap()
    }
    }
  • replacement in gemini/src/request.rs at line 175
    [5.1491][5.1491:1535]()
    bytes::complete::{tag, take_until},
    [5.1491]
    [5.1535]
    bytes::streaming::{tag, take_until},
  • file move: request (dxwrx-rx-r)request (d--r------)
    [5.16]
    [5.2255]
  • file move: request_type.rs (-xw-x--x--)request_type.rs (----------)
    [5.2255]
    [5.2274]
  • file move: lib.rs (-xw-x--x--)lib.rs (----------)
    [5.16]
    [5.7932]
  • replacement in gemini/src/lib.rs at line 24
    [5.14262][5.14262:14311]()
    pub use request::{InvalidRequest, Request, Url};
    [5.14262]
    [5.14311]
    pub use request::{AnyRequest, GeminiRequest, InvalidRequest, Request, Url};
  • edit in gemini/src/lib.rs at line 28
    [5.2125]
    [5.2125]
    /// Helper module with parsers for *COMPLETE* streams of bytes. If you want
    /// streaming/resumable parsers, use the parser functions in each submodule
    /// directly. Gemtext parsers only work with complete input.
  • replacement in gemini/src/lib.rs at line 32
    [5.2153][5.2153:2165]()
    mod parse {
    [5.2153]
    [5.2700]
    pub mod parse {
  • edit in gemini/src/lib.rs at line 36
    [5.2225]
    [5.0]
    pub use nom::Err;
  • replacement in gemini/src/lib.rs at line 65
    [5.3092][5.3092:3133]()
    /// Parse a `Header` from bytes.
    [5.3092]
    [5.3133]
    /// Parse a complete `Header` from bytes.
  • replacement in gemini/src/lib.rs at line 67
    [5.3157][5.3157:3199]()
    /// Parse a `Request` from bytes.
    [5.3157]
    [5.3299]
    /// Parse a complete `Request` from bytes.
  • replacement in gemini/src/lib.rs at line 69
    [5.3328][5.3225:3268](),[5.3225][5.3225:3268]()
    /// Parse a `Response` from bytes.
    [5.3328]
    [5.3268]
    /// Parse a complete `Response` from bytes.
  • replacement in gemini/src/lib.rs at line 71
    [5.3296][5.3296:3337]()
    /// Parse a `Status` from bytes.
    [5.3296]
    [5.3337]
    /// Parse a complete `Status` from bytes.
  • edit in gemini/src/lib.rs at line 87
    [5.3369][5.3369:3370](),[5.3370][5.0:28](),[5.28][5.613:704]()
    #[cfg(feature = "parsers")]
    pub use parse::{parse_gemtext, parse_header, parse_request, parse_response, parse_status};
  • file move: header.rs (-xw-x--x--)header.rs (----------)
    [5.16]
    [5.8013]
  • edit in gemini/src/header.rs at line 99
    [5.798]
    [5.798]
    /// Construct a new `Header` without checking the validity of the arguments.
    pub fn new_unchecked(status: Status, meta: String) -> Self {
    Header { status, meta }
    }
  • replacement in gemini/src/header.rs at line 105
    [5.869][2.2377:2497]()
    pub fn success(mime_type: impl Into<String>) -> Option<Self> {
    Self::new(Status::SUCCESS, mime_type.into())
    [5.869]
    [5.971]
    pub fn success(mime_type: String) -> Option<Self> {
    Self::new(Status::SUCCESS, mime_type)
  • replacement in gemini/src/header.rs at line 121
    [5.3518][5.3518:3562]()
    bytes::complete::{tag, take_until},
    [5.3518]
    [5.3562]
    bytes::streaming::{tag, take_until},
  • replacement in gemini/src/header.rs at line 162
    [2.2895][2.2895:2949]()
    Header::success("text/json").unwrap()
    [2.2895]
    [2.2949]
    Header::success("text/json".to_string()).unwrap()
  • file move: gemtext.rs (-xw-x--x--)gemtext.rs (----------)
    [5.16]
    [5.506]
  • edit in gemini/src/gemtext.rs at line 9
    [5.873]
    [5.873]
    //!
    //! NB: Parsing gemtext from strings won't necessarily preserve whitespace.
  • edit in gemini/src/gemtext.rs at line 21
    [5.1056]
    [5.1056]
    // TODO: track "trivia"? eg spacing between sigils and content, trailing whitespace
  • edit in gemini/src/gemtext.rs at line 201
    [5.6661]
    [5.133]
    #[cfg(test)]
    const DOCUMENT: &str = r#"
    ```logo
    wooo
    /^^^^\
    | |
    \____/
    ```
  • edit in gemini/src/gemtext.rs at line 211
    [5.134]
    [5.108]
    # 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/src/gemtext.rs at line 228
    [5.803][5.803:855](),[5.855][2.3250:3327]()
    bytes::complete::{is_not, tag, take_until},
    character::complete::{line_ending, not_line_ending, space0, space1},
    [5.803]
    [5.856]
    bytes::complete::{tag, take_until},
    character::complete::{line_ending, not_line_ending},
  • replacement in gemini/src/gemtext.rs at line 232
    [5.919][5.919:941](),[5.941][2.3328:3383]()
    multi::many1,
    sequence::{pair, preceded, terminated, tuple},
    [5.919]
    [5.399]
    multi::separated_list0,
    sequence::{delimited, pair, preceded, terminated},
  • edit in gemini/src/gemtext.rs at line 239
    [5.443][5.443:556]()
    fn line(input: &str) -> IResult<&str, &str> {
    terminated(not_line_ending, line_ending)(input)
    }
  • replacement in gemini/src/gemtext.rs at line 242
    [5.1041][5.1041:1075]()
    map(line, |s: &str| {
    [5.1041]
    [5.1075]
    map(not_line_ending, |s: &str| {
  • replacement in gemini/src/gemtext.rs at line 255
    [5.1362][5.1362:1379](),[5.1379][2.3384:3655](),[2.3655][5.1461:1653](),[5.1461][5.1461:1653]()
    map(
    preceded(
    pair(tag("=>"), space1),
    terminated(
    pair(is_not(" \t\r\n"), opt(preceded(space1, not_line_ending))),
    line_ending,
    ),
    ),
    |(to, name): (&str, Option<&str>)| Doc::Link {
    to: to.to_string(),
    name: name.map(|s| s.to_string()),
    },
    ),
    [5.1362]
    [5.1188]
    map(preceded(tag("=>"), not_line_ending), |s: &str| {
    let s = s.trim();
    let idx = s.chars().position(char::is_whitespace);
    let (to, name) = if let Some(idx) = idx {
    let (to, name) = s.split_at(idx);
    (to.to_string(), Some(name.trim().to_string()))
    } else {
    (s.to_string(), None)
    };
    Doc::Link { to, name }
    }),
  • edit in gemini/src/gemtext.rs at line 271
    [5.1668][5.1668:1751]()
    value(Level::One, tag("#")),
    value(Level::Two, tag("##")),
  • edit in gemini/src/gemtext.rs at line 272
    [5.1796]
    [5.1796]
    value(Level::Two, tag("##")),
    value(Level::One, tag("#")),
  • replacement in gemini/src/gemtext.rs at line 280
    [5.1920][5.1920:1937](),[5.1937][2.3656:3806](),[2.3806][5.2008:2084](),[5.2008][5.2008:2084]()
    map(
    terminated(
    pair(terminated(level, space0), not_line_ending),
    line_ending,
    ),
    |(lvl, s)| Doc::Heading(lvl, s.to_string()),
    ),
    [5.1920]
    [5.1751]
    map(pair(level, not_line_ending), |(lvl, s)| {
    Doc::Heading(lvl, s.trim().to_string())
    }),
  • replacement in gemini/src/gemtext.rs at line 289
    [5.2193][5.2193:2210](),[5.2210][2.3807:3886](),[2.3886][5.2288:2359](),[5.2288][5.2288:2359]()
    map(
    terminated(preceded(tag("* "), not_line_ending), line_ending),
    |s: &str| Doc::ListItem(s.to_string()),
    ),
    [5.2193]
    [5.2359]
    map(preceded(tag("* "), not_line_ending), |s: &str| {
    Doc::ListItem(s.trim().to_string())
    }),
  • replacement in gemini/src/gemtext.rs at line 298
    [5.2478][5.2478:2495](),[5.2495][2.3887:4044](),[2.4044][5.2573:2641](),[5.2573][5.2573:2641]()
    map(
    terminated(
    preceded(terminated(tag(">"), space0), not_line_ending),
    line_ending,
    ),
    |s: &str| Doc::Quote(s.to_string()),
    ),
    [5.2478]
    [5.2641]
    map(preceded(tag(">"), not_line_ending), |s: &str| {
    Doc::Quote(s.trim().to_string())
    }),
  • replacement in gemini/src/gemtext.rs at line 310
    [5.2846][5.2846:2939]()
    terminated(preceded(tag("```"), opt(not_line_ending)), line_ending),
    [5.2846]
    [5.2939]
    delimited(tag("```"), opt(not_line_ending), line_ending),
  • replacement in gemini/src/gemtext.rs at line 313
    [5.3007][2.4045:4129]()
    tuple((line_ending, tag("```"), not_line_ending, line_ending)),
    [5.3007]
    [5.3058]
    pair(line_ending, tag("```")),
  • replacement in gemini/src/gemtext.rs at line 316
    [5.3149][5.3149:3202]()
    alt: alt.map(|s| s.to_string()),
    [5.3149]
    [5.3202]
    alt: alt.and_then(|s| {
    let s = s.trim();
    if s.is_empty() {
    None
    } else {
    Some(s.to_string())
    }
    }),
  • replacement in gemini/src/gemtext.rs at line 330
    [5.2017][5.3298:3346]()
    /// Parse a utf-8 encoded gemtext document.
    [5.2017]
    [5.2017]
    /// Parse a *complete* gemtext document from utf-8 encoded text.
  • replacement in gemini/src/gemtext.rs at line 335
    [5.3413][2.4130:4213]()
    many1(alt((link, heading, list_item, quote, preformatted, text))),
    [5.3413]
    [5.3496]
    terminated(
    separated_list0(
    line_ending,
    alt((link, heading, list_item, quote, preformatted, text)),
    ),
    opt(line_ending),
    ),
  • edit in gemini/src/gemtext.rs at line 350
    [2.4275]
    [2.4275]
    trait Is {
    type Item;
    fn is(&self, other: Self::Item) -> bool;
    }
    impl<T: Eq, U, E> Is for Result<(U, T), E> {
    type Item = T;
    fn is(&self, other: Self::Item) -> bool {
    if let Ok((_, inner)) = self {
    inner == &other
    } else {
    false
    }
    }
    }
    macro_rules! assert_is {
    ($actual:expr, $expected:expr) => {
    assert!(
    $actual.is($expected),
    "\n\nexpected: {:#?}\n\nactual: {:#?}",
    $expected,
    $actual
    )
    };
    }
    #[test]
    fn test_text() {
    assert_is!(text("foo"), Doc::Text("foo".to_string()));
    assert_is!(text("\nfoo"), Doc::Blank);
    }
  • edit in gemini/src/gemtext.rs at line 386
    [2.4292]
    [2.4292]
    fn test_link() {
    assert_is!(
    link("=> gemini://foo"),
    Doc::Link {
    to: "gemini://foo".to_string(),
    name: None
    }
    );
    assert_is!(
    link("=> gemini://foo bar"),
    Doc::Link {
    to: "gemini://foo".to_string(),
    name: Some("bar".to_string()),
    }
    );
    assert_is!(
    link("=> gemini://foo bar baz bax"),
    Doc::Link {
    to: "gemini://foo".to_string(),
    name: Some("bar baz bax".to_string())
    }
    );
    assert_is!(
    link("=>gemini://foo"),
    Doc::Link {
    to: "gemini://foo".to_string(),
    name: None
    }
    )
    }
    #[test]
    fn test_heading() {
    assert_is!(
    heading("# foo"),
    Doc::Heading(Level::One, "foo".to_string())
    );
    assert_is!(
    heading("## bar"),
    Doc::Heading(Level::Two, "bar".to_string())
    );
    assert_is!(
    heading("###baz "),
    Doc::Heading(Level::Three, "baz".to_string())
    )
    }
    #[test]
    fn test_list_item() {
    assert_is!(list_item("* foo"), Doc::ListItem("foo".to_string()));
    assert_is!(list_item("* bar"), Doc::ListItem("bar".to_string()));
    assert!(list_item("*bad").is_err())
    }
    #[test]
    fn test_quote() {
    assert_is!(quote("> foo"), Doc::Quote("foo".to_string()));
    assert_is!(quote(">bar"), Doc::Quote("bar".to_string()));
    }
    #[test]
    fn test_preformatted() {
    assert_is!(
    preformatted("```\nfoo\n```"),
    Doc::Preformatted {
    alt: None,
    text: "foo".to_string()
    }
    );
    assert_is!(
    preformatted("```\n\nfoo\n> bar\n=> baz\n```\n"),
    Doc::Preformatted {
    alt: None,
    text: "\nfoo\n> bar\n=> baz".to_string()
    }
    );
    assert_is!(
    preformatted("```foo\nbar\n```"),
    Doc::Preformatted {
    alt: Some("foo".to_string()),
    text: "bar".to_string()
    }
    );
    assert_is!(
    preformatted("``` foo \nbar\n```"),
    Doc::Preformatted {
    alt: Some("foo".to_string()),
    text: "bar".to_string()
    }
    );
    assert!(preformatted("```\n").is_err());
    assert!(preformatted("```\nfoo```").is_err());
    }
    #[test]
  • replacement in gemini/src/gemtext.rs at line 482
    [2.4321][2.4321:4392]()
    assert_eq!(
    document(DOCUMENT).unwrap().1,
    [2.4321]
    [2.4392]
    assert_is!(
    document(DOCUMENT),
  • replacement in gemini/src/gemtext.rs at line 503
    [2.5395][2.5395:5417]()
    }
    [2.5395]
    [2.5417]
    },
    Doc::Blank
  • edit in gemini/src/gemtext.rs at line 531
    [2.5536][2.5536:5585](),[2.5585][5.112:120](),[5.112][5.112:120](),[5.120][5.766:979](),[5.766][5.766:979](),[5.979][2.5586:5590]()
    #[cfg(test)]
    const DOCUMENT: &'static str = r#"
    ```logo
    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
    "#;