From 1cd7b99fea9aa30c4a5989622f01f842a9371b51 Mon Sep 17 00:00:00 2001 From: Christine Dodrill Date: Sat, 25 Jul 2020 12:39:10 -0400 Subject: [PATCH] add majc prototype --- Cargo.toml | 5 +++ majc/Cargo.toml | 13 ++++++ majc/src/help.gmi | 24 +++++++++++ majc/src/main.rs | 100 ++++++++++++++++++++++++++++++++++++++++++++++ shell.nix | 3 ++ src/client.rs | 20 +++++++++- 6 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 majc/Cargo.toml create mode 100644 majc/src/help.gmi create mode 100644 majc/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 37e5ac4..20ffb30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,3 +25,8 @@ default = ["client", "server"] client = ["rustls", "webpki", "webpki-roots"] server = ["rustls", "webpki", "webpki-roots"] + +[workspace] +members = [ + "./majc" +] diff --git a/majc/Cargo.toml b/majc/Cargo.toml new file mode 100644 index 0000000..8782781 --- /dev/null +++ b/majc/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "majc" +version = "0.1.0" +authors = ["Christine Dodrill "] +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 = ".." } diff --git a/majc/src/help.gmi b/majc/src/help.gmi new file mode 100644 index 0000000..8aa8f1c --- /dev/null +++ b/majc/src/help.gmi @@ -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 + +: opens the menubar +o: prompts to open a URL +q: quits majc +?: shows this screen +~: toggles the debug logging pane diff --git a/majc/src/main.rs b/majc/src/main.rs new file mode 100644 index 0000000..baa60fd --- /dev/null +++ b/majc/src/main.rs @@ -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))); + } + } +} diff --git a/shell.nix b/shell.nix index bcc085d..9714826 100644 --- a/shell.nix +++ b/shell.nix @@ -3,5 +3,8 @@ pkgs.mkShell { buildInputs = with pkgs; [ rustc cargo rls rustfmt cargo-watch + + pkg-config + ncurses ]; } diff --git a/src/client.rs b/src/client.rs index a9b05c7..0e173ae 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,4 +1,4 @@ -use crate::{Response}; +use crate::Response; use rustls::{ClientConfig, ClientSession, Stream, TLSError}; use std::{io::prelude::*, net::TcpStream, sync::Arc}; use url::Url; @@ -27,13 +27,29 @@ pub enum Error { #[error("Response parsing error: {0:?}")] ResponseParse(#[from] crate::ResponseError), + + #[error("Invalid URL scheme {0:?}")] + InvalidScheme(String), } -pub fn get(u: String) -> Result { +pub fn get(u: T) -> Result +where + T: Into, +{ + let u = u.into(); let mut ur = Url::parse(&u.clone())?; if ur.port().is_none() { 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 host = ur.host_str().unwrap(); let mut sock = TcpStream::connect(&format!("{}:{}", host, ur.port().unwrap()))?;