add majc prototype
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
parent
49190b32ff
commit
1cd7b99fea
|
@ -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"
|
||||||
|
]
|
||||||
|
|
|
@ -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 = ".." }
|
|
@ -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
|
|
@ -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)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()))?;
|
||||||
|
|
Loading…
Reference in New Issue