majc: render gemini pages

This commit is contained in:
Cadey Ratio 2020-07-25 14:05:23 -04:00
parent 479fb697ee
commit d96a7a85bf
3 changed files with 90 additions and 25 deletions

View File

@ -1,22 +1,24 @@
__ # majc
```
__
_____ _____ |__| ____ _____ _____ |__| ____
/ \ \__ \ | |_/ ___\ / \ \__ \ | |_/ ___\
| Y Y \ / __ \_ | |\ \___ | Y Y \ / __ \_ | |\ \___
|__|_| /(____ //\__| | \___ > |__|_| /(____ //\__| | \___ >
\/ \/ \______| \/ \/ \/ \______| \/
```
A curses client for Gemini! A curses client for Gemini!
=> gemini://gemini.circumlunar.space/ Gemini homepage => gemini://gemini.circumlunar.space/ Gemini homepage
## Homepage ## Homepage
The main homepage for majc is on tulpa.dev: The main homepage for majc is on tulpa.dev:
=> https://tulpa.dev/cadey/maj => https://tulpa.dev/cadey/maj
## Important Keys ## Important Keys
<esc>: opens the menubar <esc>: opens the menubar
c: closes the active window c: closes the active window
o: prompts to open a URL o: prompts to open a URL

View File

@ -1,7 +1,9 @@
use cursive::{ use cursive::{
event::Key, event::Key,
menu::MenuTree, menu::MenuTree,
theme::{BaseColor, Color, Effect, Style},
traits::*, traits::*,
utils::markup::StyledString,
views::{Dialog, EditView, Panel, ResizedView, TextView}, views::{Dialog, EditView, Panel, ResizedView, TextView},
Cursive, Cursive,
}; };
@ -51,7 +53,7 @@ fn help(siv: &mut Cursive) {
let content = include_str!("./help.gmi"); let content = include_str!("./help.gmi");
siv.add_layer( siv.add_layer(
Dialog::around(Panel::new(TextView::new(content).scrollable())) Dialog::around(Panel::new(TextView::new(render_gemini(content)).scrollable()))
.title("Help") .title("Help")
.dismiss_button("Ok"), .dismiss_button("Ok"),
); );
@ -94,23 +96,69 @@ fn open(siv: &mut Cursive, url: &str) {
} }
fn show(siv: &mut Cursive, url: &str, resp: Response) { fn show(siv: &mut Cursive, url: &str, resp: Response) {
if resp.status != StatusCode::Success { use StatusCode::*;
siv.add_layer(Dialog::info(format!("{:?}: {}", resp.status, resp.meta)));
return;
}
match str::from_utf8(&resp.body) { match resp.status {
Ok(content) => { Success => {
siv.add_fullscreen_layer(ResizedView::with_full_screen( match str::from_utf8(&resp.body) {
Dialog::around(TextView::new(content).scrollable()) Ok(content) => {
.title(format!("{}: {}", url, resp.meta)), siv.add_fullscreen_layer(ResizedView::with_full_screen(
)); Dialog::around(TextView::new(render_gemini(content)).scrollable())
.title(format!("{}: {}", url, resp.meta)),
));
}
Err(why) => {
siv.add_layer(Dialog::info(format!(
"UTF/8 decoding error for {}: {:?}",
url, why
)));
}
}
} }
Err(why) => {
siv.add_layer(Dialog::info(format!( TemporaryRedirect => {
"UTF/8 decoding error for {}: {:?}", open(siv, resp.meta.as_str());
url, why }
)));
PermanentRedirect => {
open(siv, resp.meta.as_str());
}
_ => {
siv.add_layer(Dialog::info(format!("{:?}: {}", resp.status, resp.meta)));
return;
} }
} }
} }
fn render_gemini(body: &str) -> StyledString {
let doc = maj::gemini::parse(body);
let mut styled = StyledString::new();
use maj::gemini::Node::*;
for node in doc {
match node {
Text(line) => styled.append(StyledString::plain(line)),
Link { to, name } => match name {
None => styled.append(StyledString::styled(
to,
Style::from(Effect::Underline),
)),
Some(name) => styled.append(StyledString::styled(
format!("{}: {}", to, name),
Style::from(Effect::Underline),
)),
},
Preformatted(data) => styled.append(StyledString::plain(data)),
Heading { level: _, body } => {
styled.append(StyledString::styled(body, Style::from(Effect::Bold)))
}
ListItem(item) => styled.append(StyledString::plain(format!("* {}", item))),
Quote(quote) => styled.append(StyledString::plain(format!("> {}", quote))),
}
styled.append(StyledString::plain("\n"));
}
styled
}

View File

@ -93,7 +93,10 @@ pub fn parse(doc: &str) -> Vec<Node> {
collect_preformatted = !collect_preformatted; collect_preformatted = !collect_preformatted;
if !collect_preformatted { if !collect_preformatted {
result.push(Node::Preformatted( result.push(Node::Preformatted(
String::from_utf8(preformatted_buffer).unwrap(), String::from_utf8(preformatted_buffer)
.unwrap()
.trim_end()
.to_string(),
)); ));
preformatted_buffer = vec![]; preformatted_buffer = vec![];
} }
@ -145,8 +148,14 @@ pub fn parse(doc: &str) -> Vec<Node> {
let sp = line[2..].split_ascii_whitespace().collect::<Vec<&str>>(); let sp = line[2..].split_ascii_whitespace().collect::<Vec<&str>>();
match sp.len() { match sp.len() {
1 => result.push(Node::Link { to: sp[0].trim().to_string(), name: None }), 1 => result.push(Node::Link {
_ => result.push(Node::Link { to: sp[0].trim().to_string(), name: Some(sp[1..].join(" ").trim().to_string()) }), to: sp[0].trim().to_string(),
name: None,
}),
_ => result.push(Node::Link {
to: sp[0].trim().to_string(),
name: Some(sp[1..].join(" ").trim().to_string()),
}),
} }
continue; continue;
@ -194,7 +203,7 @@ mod tests {
\n\ \n\
Test\n"; Test\n";
let expected: Vec<Node> = vec![ let expected: Vec<Node> = vec![
Node::Preformatted("hi there\n".to_string()), Node::Preformatted("hi there".to_string()),
Node::Text(String::new()), Node::Text(String::new()),
Node::Text("Test".to_string()), Node::Text("Test".to_string()),
]; ];
@ -227,8 +236,14 @@ mod tests {
let _ = pretty_env_logger::try_init(); let _ = pretty_env_logger::try_init();
let msg = "=>/\n=> / Go home"; let msg = "=>/\n=> / Go home";
let expected: Vec<Node> = vec![ let expected: Vec<Node> = vec![
Node::Link{to: "/".to_string(), name: None}, Node::Link {
Node::Link{to: "/".to_string(), name: Some("Go home".to_string()) }, to: "/".to_string(),
name: None,
},
Node::Link {
to: "/".to_string(),
name: Some("Go home".to_string()),
},
]; ];
assert_eq!(expected, parse(msg)); assert_eq!(expected, parse(msg));
} }