fun with generics

pull/6/head
dvkt 5 years ago
parent 2750aec73a
commit 8bc2c3d3f5

@ -0,0 +1,45 @@
#![allow(dead_code)]
use std::io;
use std::io::{Read, Write};
use std::net::TcpStream;
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum Type {
Text = '0' as isize, // 0
Menu, // 1
CSOEntity, // 2
Error, // 3
Binhex, // 4
DOSFile, // 5
UUEncoded, // 6
Search, // 7
Telnet, // 8
Binary, // 9
Mirror = '+' as isize, // +
GIF = 'g' as isize, // g
Telnet3270 = 'T' as isize, // T
HTML = 'h' as isize, // h
Info = 'i' as isize, // i
Sound = 's' as isize, // s
Document = 'd' as isize, // d
}
// Fetches a URL and returns a raw Gopher response.
pub fn fetch(host: &str, port: &str, selector: &str) -> io::Result<String> {
let mut body = String::new();
let stream = TcpStream::connect(format!("{}:{}", host, port))
.and_then(|mut stream| {
stream.write(format!("{}\r\n", selector).as_ref());
Ok(stream)
})
.and_then(|mut stream| {
stream.read_to_string(&mut body);
Ok(())
});
match stream {
Ok(_) => Ok(body),
Err(e) => Err(e),
}
}

@ -3,7 +3,8 @@
extern crate termion;
mod fetch;
mod page;
mod gopher;
mod menu;
mod types;
mod ui;
@ -24,7 +25,7 @@ fn main() {
}
let mut ui = ui::UI::new();
ui.load(host, port, selector);
ui.load(gopher::Type::Menu, host, port, selector);
ui.run();
}

@ -1,21 +1,19 @@
use gopher;
use gopher::Type;
use std::io;
use std::io::{Read, Write};
use std::net::TcpStream;
use types::Type;
use ui::{Action, Key, View};
#[derive(Debug)]
pub struct PageView {
pub struct MenuView {
pub input: String, // user's inputted value
pub page: Page, // data
pub menu: Menu, // data
pub line: usize, // selected line
pub scroll: usize, // scrolling offset
}
#[derive(Debug)]
pub struct Page {
pub struct Menu {
lines: Vec<Line>, // lines
typ: Type, // entry type
raw: String, // raw gopher response
url: String, // gopher url
}
@ -30,7 +28,7 @@ pub struct Line {
typ: Type,
}
impl View for PageView {
impl View for MenuView {
fn process_input(&mut self, key: Key) -> Action {
match key {
Key::Char('\n') => return Action::Open,
@ -72,9 +70,9 @@ impl View for PageView {
}
Key::Char(c) => {
self.input.push(c);
for (i, link) in self.page.lines.iter().enumerate() {
for (i, link) in self.menu.lines.iter().enumerate() {
// jump to number
let count = self.page.lines.len();
let count = self.menu.lines.len();
if count < 10 && c == '1' && i == 0 {
return Action::FollowLink(i);
} else if count < 20 && c == '2' && i == 1 {
@ -114,10 +112,10 @@ impl View for PageView {
}
}
impl PageView {
pub fn from(page: Page) -> PageView {
PageView {
page,
impl MenuView {
pub fn from(url: String, response: String) -> MenuView {
MenuView {
menu: Menu::from(url, response),
input: String::new(),
line: 0,
scroll: 0,
@ -125,41 +123,22 @@ impl PageView {
}
}
impl Page {
pub fn from(url: String, gopher_response: String) -> Page {
Self::parse_menu(url, gopher_response)
impl Menu {
pub fn from(url: String, gopher_response: String) -> Menu {
Self::parse(url, gopher_response)
}
// Loads a Page given a URL.
pub fn load(host: &str, port: &str, selector: &str) -> io::Result<Page> {
// Loads a Menu given a URL.
pub fn load(host: &str, port: &str, selector: &str) -> io::Result<Menu> {
let url = format!("{}:{}{}", host, port, selector);
match Self::fetch(host, port, selector) {
Ok(res) => Ok(Page::from(url, res)),
Err(e) => Err(e),
}
}
// Fetches a URL and returns a raw Gopher response.
fn fetch(host: &str, port: &str, selector: &str) -> io::Result<String> {
let mut body = String::new();
let stream = TcpStream::connect(format!("{}:{}", host, port))
.and_then(|mut stream| {
stream.write(format!("{}\r\n", selector).as_ref());
Ok(stream)
})
.and_then(|mut stream| {
stream.read_to_string(&mut body);
Ok(())
});
match stream {
Ok(_) => Ok(body),
match gopher::fetch(host, port, selector) {
Ok(res) => Ok(Menu::from(url, res)),
Err(e) => Err(e),
}
}
// Parses the lines in a raw Gopher menu response.
fn parse_menu(url: String, raw: String) -> Page {
fn parse(url: String, raw: String) -> Menu {
let mut lines = vec![];
let mut line = (0, 0, Type::Menu); // (name start pos, name end, type)
let mut start = true; // are we at beginning of a line?
@ -216,11 +195,6 @@ impl Page {
}
}
Page {
raw,
url,
lines,
typ: Type::Menu,
}
Menu { raw, url, lines }
}
}

@ -1,22 +0,0 @@
#![allow(dead_code)]
#[derive(Copy, Clone, PartialEq, Debug)]
pub enum Type {
Text = '0' as isize, // 0
Menu, // 1
CSOEntity, // 2
Error, // 3
Binhex, // 4
DOSFile, // 5
UUEncoded, // 6
Search, // 7
Telnet, // 8
Binary, // 9
Mirror = '+' as isize, // +
GIF = 'g' as isize, // g
Telnet3270 = 'T' as isize, // T
HTML = 'h' as isize, // h
Info = 'i' as isize, // i
Sound = 's' as isize, // s
Document = 'd' as isize, // d
}

@ -1,16 +1,17 @@
use page::{Page, PageView};
use std::io;
use std::io::{stdin, stdout, Write};
use termion::input::TermRead;
use termion::raw::IntoRawMode;
use gopher;
use gopher::Type;
use menu::{Menu, MenuView};
pub type Key = termion::event::Key;
pub type Error = io::Error;
#[derive(Debug)]
pub struct UI {
pages: Vec<PageView>,
pages: Vec<Box<dyn View>>,
page: usize,
}
@ -24,9 +25,9 @@ pub enum Action {
Back,
Forward,
Open,
FollowLink(usize),
Quit,
Unknown,
FollowLink(usize),
}
pub trait View {
@ -49,8 +50,8 @@ impl UI {
}
pub fn print(&self) {
// print!("{}", self.render());
print!("{:#?}", self);
print!("{}", self.render());
// print!("{:#?}", self);
}
pub fn render(&self) -> String {
@ -58,35 +59,32 @@ impl UI {
String::new()
}
pub fn load(&mut self, host: &str, port: &str, selector: &str) {
match Page::load(host, port, selector) {
Ok(page) => self.add_page(PageView::from(page)),
Err(e) => {
pub fn load(&mut self, typ: Type, host: &str, port: &str, selector: &str) {
let response = gopher::fetch(host, port, selector)
.map_err(|e| {
eprintln!(
"\x1B[91merror loading \x1b[93m{}:{}{}: \x1B[0m{}",
host, port, selector, e
);
std::process::exit(1);
}
})
.unwrap();
let url = format!("{}:{}{}", host, port, selector); // TODO
match typ {
Type::Menu => self.add_view(MenuView::from(url, response)),
_ => panic!("unknown type"),
}
}
fn add_page(&mut self, page: PageView) {
self.pages.push(page);
fn add_view<T: View + 'static>(&mut self, view: T) {
self.pages.push(Box::from(view));
if self.pages.len() > 1 {
self.page += 1;
}
}
// Get a mutable reference to the currently loaded view.
fn mut_view(&mut self) -> Option<&mut PageView> {
if self.pages.len() > 0 && self.page < self.pages.len() {
Some(self.pages.get_mut(self.page).unwrap())
} else {
None
}
}
fn respond_to_user(&mut self) {
match self.process_input() {
Action::Quit => std::process::exit(1),
@ -98,7 +96,7 @@ impl UI {
let stdin = stdin();
let mut stdout = stdout().into_raw_mode().unwrap();
stdout.flush().unwrap();
let page = self.mut_view().expect("expected page to be loaded");
let page = self.pages.get_mut(self.page).expect("expected Page");
for c in stdin.keys() {
let key = c.expect("UI error on stdin.keys");

Loading…
Cancel
Save