#![feature(pattern, never_type, unwrap_infallible)]
use clap::Parser;
use media::Media;
use std::fmt::Display;
mod cli;
mod core;
mod danger;
mod media;
mod network;
mod response;
mod utils;
pub trait Ui {
fn show(&mut self, content: media::Media);
fn warn<D: Display>(&mut self, warning: D);
fn read(&mut self, prompt: impl Display) -> Option<String>;
fn read_secret(&mut self, prompt: impl Display) -> Option<String>;
}
pub struct NopUi;
impl Ui for NopUi {
fn show(&mut self, _content: media::Media) {}
fn warn<D: Display>(&mut self, _warning: D) {}
fn read(&mut self, _prompt: impl Display) -> Option<String> {
None
}
fn read_secret(&mut self, _prompt: impl Display) -> Option<String> {
None
}
}
fn main() {
let cli: cli::App = cli::App::parse();
match cli.run() {
Ok(()) => (),
Err(e) => match e {},
}
}
fn decode_media(mime: response::Mime, body: &str) -> Media<'_> {
let media = match (mime.type_(), mime.subtype().as_ref()) {
(mime::TEXT, "gemini") => Media::Gemini(body.into()),
_ => Media::Text(body),
};
media
}
#[cfg(test)]
mod tests {
use std::{
cell::RefCell,
fmt::Display,
io::{Read, Write},
rc::Rc,
};
use rstest::rstest;
use url::Url;
use crate::{media, network::Network, NopUi, Ui};
#[rstest]
#[case("gemini://gemini.circumlunar.space")]
#[case("gemini://gemini.circumlunar.space/docs/gemtext.gmi")]
fn test_connect(#[case] url: Url) {
let mut net = crate::network::TcpNetwork::new(crate::danger::Naive);
crate::core::run_url(&mut net, &mut NopUi, url);
}
#[test]
fn test_response_body() {
let url: Url = "gemini://gemini.circumlunar.space".parse().unwrap();
let response = "20 \r\nsome text\r\n";
let body = "some text\r\n";
let mut fui = FakeUi::new();
let mut net = FakeNet::new(FakeConn::new(response.into()));
crate::core::run_url(&mut net, &mut fui, url.clone());
let net_sink = Rc::try_unwrap(net.conn.0).unwrap().into_inner().sink;
let net_sink = String::from_utf8(net_sink).unwrap();
assert_eq!(net_sink, format!("{}\r\n", url));
let ui_sink = fui.sink;
assert_eq!(ui_sink.as_ref(), [FUIEvent::Show]);
}
#[derive(Default)]
struct FakeUi {
source: Vec<String>,
sink: Vec<FUIEvent>,
}
#[derive(Debug, PartialEq, PartialOrd, Ord, Eq)]
enum FUIEvent {
Show,
Warn(String),
Read(String),
ReadSecret(String),
}
impl FakeUi {
pub fn new() -> Self {
Default::default()
}
}
impl Ui for FakeUi {
fn show(&mut self, content: media::Media) {
self.sink.push(FUIEvent::Show)
}
fn warn<D: Display>(&mut self, warning: D) {
self.sink.push(FUIEvent::Warn(warning.to_string()))
}
fn read(&mut self, prompt: impl Display) -> Option<String> {
self.sink.push(FUIEvent::Read(prompt.to_string()));
self.source.pop()
}
fn read_secret(&mut self, prompt: impl Display) -> Option<String> {
self.sink.push(FUIEvent::ReadSecret(prompt.to_string()));
self.source.pop()
}
}
#[derive(Debug)]
struct FakeNet {
conn: SharedFakeConn,
}
impl FakeNet {
fn new(conn: FakeConn) -> Self {
Self {
conn: SharedFakeConn(Rc::new(RefCell::new(conn))),
}
}
}
#[derive(Clone, Debug)]
struct SharedFakeConn(Rc<RefCell<FakeConn>>);
#[derive(Debug)]
struct FakeConn {
sink: Vec<u8>,
source: Vec<u8>,
}
impl FakeConn {
fn new(source: String) -> Self {
Self {
sink: Vec::new(),
source: source.into_bytes(),
}
}
}
impl Read for SharedFakeConn {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
self.0.borrow_mut().read(buf)
}
}
impl Write for SharedFakeConn {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.0.borrow_mut().write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.0.borrow_mut().flush()
}
}
impl Read for FakeConn {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
(&*self.source).read(buf)
}
}
impl Write for FakeConn {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.sink.write(buf)
}
fn flush(&mut self) -> std::io::Result<()> {
self.sink.flush()
}
}
impl Network for FakeNet {
type Error = &'static str;
type Connection = SharedFakeConn;
fn connect(&mut self, _: &Url) -> Result<Self::Connection, Self::Error> {
Ok(self.conn.clone())
}
}
}