3UXSFGJYCPS5IT5O5QDKLJOMDJKE7ZBTCFX3BYXTH2JFLP5GLDMAC
M5HGUS2TL72TRSUS7NN7ZQND4CDPVBERJQNUQJ4XTZ6GRUEPUIEAC
2L47PJWNRMFJQTSQR2R2AY67QW7CHQ5NK2SHJY6AUHBYDIVXDISQC
2OIMF64IICYQ3TZW3Z7YMYWQSA5WT2XNWSMMOYRKYTO4RUSIN4FQC
5II6T7YETYWUIKUMNJIYFUOQHCA26N2YVJDSHLMNO2GR62MP2ORQC
LYRVL33UETNJV4SJ2YC4LBLKMMYPVCHDAIQMWBY7NLWCHL4Q2V2QC
ENARAOABLYA4PF2FRU6FFO75KVXQYUB7IVDXL6FSPZOLJ3NYP3ZAC
LSYI2TXC2N33NQF4MG2RNBETSPSO7BRTJVUVVO7P3XN4SRJDEWLAC
JBZGFYVOKR3X2GH25SOOW3X7RKY6ZACESYFF5NO26PJFUP3XGXUAC
L6GZJGTUSHJVC3RGMEIRWEEDZN2LNQCRA2Q4KFTATA42WBP6IBIAC
JS6JZ7IAZ6EUSPFURFLZ7AQKFSDGSHV3E7VKEV6VJHYJZMTITHIQC
N3U3GWELZPVC2MXIYR4QMBL6VVBKEBOCKKGYWSD3X5AGPF23I2CQC
WJROXZ3CGQOYXG5BTGFKJ7UWE26WJC7DRZ4FOZDECGRPBYCGF2TQC
BOFUYB6IISDQYT3G5MKVDNWB2WWGHMNYDKTITJBVS5RED6XJLB4QC
RTDVYLFT3PRNC4F4TWU47YFMLQPXR65CAAB6IVS7FIY2EB7HJGJQC
KP6SZZ346AU4MCK2EZK3WHGGGNDMUGX7AG4CBYKVO4HZMUYRBU6AC
7DH43OFGO4RVLW3LL23FZDMXRHQLAEMK7VD62T7FZBRHZOF3RRDAC
J4PKMKJXBUPG4QFHQVATUOXHWLEWMX3LTGWMNFKFAQUTJHU5GSAAC
K37J3USB4DC54L5CFARGQOZYF76I4USNJHYEXCTTRF5D5CTJCN3QC
Y55SCAUN3MQCMRGJQBBMICEO6ARTGICWGGZYA7VB6ZRPK6US4B5AC
XHDJKZOSBFCZWKPTBFTSLDRDGTUBOJLL6XVKVCGMBJXDOELSGLYQC
- [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
- [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
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()
}
}
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()),
},
),
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 }
}),
map(
terminated(
pair(terminated(level, space0), not_line_ending),
line_ending,
),
|(lvl, s)| Doc::Heading(lvl, s.to_string()),
),
map(pair(level, not_line_ending), |(lvl, s)| {
Doc::Heading(lvl, s.trim().to_string())
}),
map(
terminated(preceded(tag("* "), not_line_ending), line_ending),
|s: &str| Doc::ListItem(s.to_string()),
),
map(preceded(tag("* "), not_line_ending), |s: &str| {
Doc::ListItem(s.trim().to_string())
}),
map(
terminated(
preceded(terminated(tag(">"), space0), not_line_ending),
line_ending,
),
|s: &str| Doc::Quote(s.to_string()),
),
map(preceded(tag(">"), not_line_ending), |s: &str| {
Doc::Quote(s.trim().to_string())
}),
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);
}
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]
#[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
"#;