add majc prototype

This commit is contained in:
Cadey Ratio 2020-07-25 12:39:10 -04:00
parent 49190b32ff
commit 1cd7b99fea
6 changed files with 163 additions and 2 deletions

View File

@ -25,3 +25,8 @@ default = ["client", "server"]
client = ["rustls", "webpki", "webpki-roots"] client = ["rustls", "webpki", "webpki-roots"]
server = ["rustls", "webpki", "webpki-roots"] server = ["rustls", "webpki", "webpki-roots"]
[workspace]
members = [
"./majc"
]

13
majc/Cargo.toml Normal file
View File

@ -0,0 +1,13 @@
[package]
name = "majc"
version = "0.1.0"
authors = ["Christine Dodrill <me@christine.website>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
cursive = "0.15"
log = "0.4"
maj = { path = ".." }

24
majc/src/help.gmi Normal file
View File

@ -0,0 +1,24 @@
__
_____ _____ |__| ____
/ \ \__ \ | |_/ ___\
| Y Y \ / __ \_ | |\ \___
|__|_| /(____ //\__| | \___ >
\/ \/ \______| \/
A curses client for Gemini!
=> gemini://gemini.circumlunar.space/ Gemini homepage
## Homepage
The main homepage for majc is on tulpa.dev:
=> https://tulpa.dev/cadey/maj
## Important Keys
<esc>: opens the menubar
o: prompts to open a URL
q: quits majc
?: shows this screen
~: toggles the debug logging pane

100
majc/src/main.rs Normal file
View File

@ -0,0 +1,100 @@
use cursive::{
event::Key,
menu::MenuTree,
traits::*,
views::{Dialog, EditView, Panel, ResizedView, TextView},
Cursive,
};
fn main() {
cursive::logger::init();
let mut siv = cursive::default();
siv.add_global_callback('q', cursive::Cursive::quit);
siv.add_global_callback('~', cursive::Cursive::toggle_debug_console);
siv.add_global_callback('o', open_prompt);
siv.add_global_callback('?', help);
siv.menubar()
.add_subtree(
"majc",
MenuTree::new()
.leaf("About", move |s| {
s.add_layer(Dialog::info(format!(
"{} {}",
env!("CARGO_PKG_NAME"),
env!("CARGO_PKG_VERSION")
)));
})
.leaf("Help", move |s| {
help(s);
}),
)
.add_leaf("Open", |s| open_prompt(s));
siv.add_global_callback(Key::Esc, |s| s.select_menubar());
help(&mut siv);
siv.run();
}
fn help(siv: &mut Cursive) {
let content = include_str!("./help.gmi");
siv.add_layer(
Dialog::around(Panel::new(TextView::new(content).scrollable()))
.title("Help")
.dismiss_button("Ok"),
);
}
fn open_prompt(siv: &mut Cursive) {
siv.add_layer(
Dialog::around(
EditView::new()
.on_submit(open)
.with_name("url")
.fixed_width(50),
)
.title("Enter a Gemini URL")
.button("Ok", |s| {
let url = s
.call_on_name("url", |view: &mut EditView| view.get_content())
.unwrap();
open(s, &url);
})
.button("Cancel", |s| {
s.pop_layer();
}),
);
}
fn open(siv: &mut Cursive, url: &str) {
use maj::{get, StatusCode};
use std::str;
siv.pop_layer();
log::debug!("got URL: {}", url);
match get(url.to_string()) {
Ok(resp) => {
if resp.status != StatusCode::Success {
siv.add_layer(Dialog::info(format!("{:?}: {}", resp.status, resp.meta)));
return;
}
siv.add_fullscreen_layer(ResizedView::with_full_screen(
Dialog::around(Panel::new(
TextView::new(str::from_utf8(&resp.body).unwrap()).scrollable(),
))
.title(format!("{}: {}", url, resp.meta)),
));
}
Err(why) => {
log::error!("got response error: {:?}", why);
siv.add_layer(Dialog::info(format!("Error fetching response: {:?}", why)));
}
}
}

View File

@ -3,5 +3,8 @@
pkgs.mkShell { pkgs.mkShell {
buildInputs = with pkgs; [ buildInputs = with pkgs; [
rustc cargo rls rustfmt cargo-watch rustc cargo rls rustfmt cargo-watch
pkg-config
ncurses
]; ];
} }

View File

@ -1,4 +1,4 @@
use crate::{Response}; use crate::Response;
use rustls::{ClientConfig, ClientSession, Stream, TLSError}; use rustls::{ClientConfig, ClientSession, Stream, TLSError};
use std::{io::prelude::*, net::TcpStream, sync::Arc}; use std::{io::prelude::*, net::TcpStream, sync::Arc};
use url::Url; use url::Url;
@ -27,13 +27,29 @@ pub enum Error {
#[error("Response parsing error: {0:?}")] #[error("Response parsing error: {0:?}")]
ResponseParse(#[from] crate::ResponseError), ResponseParse(#[from] crate::ResponseError),
#[error("Invalid URL scheme {0:?}")]
InvalidScheme(String),
} }
pub fn get(u: String) -> Result<crate::Response, Error> { pub fn get<T>(u: T) -> Result<crate::Response, Error>
where
T: Into<String>,
{
let u = u.into();
let mut ur = Url::parse(&u.clone())?; let mut ur = Url::parse(&u.clone())?;
if ur.port().is_none() { if ur.port().is_none() {
ur.set_port(Some(1965)).unwrap(); ur.set_port(Some(1965)).unwrap();
} }
if ur.scheme() == "" {
let _ = ur.set_scheme("gemini");
}
if ur.scheme() != "gemini" {
return Err(Error::InvalidScheme(ur.scheme().to_string()));
}
let cfg = Arc::new(config()); let cfg = Arc::new(config());
let host = ur.host_str().unwrap(); let host = ur.host_str().unwrap();
let mut sock = TcpStream::connect(&format!("{}:{}", host, ur.port().unwrap()))?; let mut sock = TcpStream::connect(&format!("{}:{}", host, ur.port().unwrap()))?;