diff --git a/src/gopher.rs b/src/gopher.rs index dd4ca49..5932911 100644 --- a/src/gopher.rs +++ b/src/gopher.rs @@ -50,28 +50,53 @@ impl Type { } pub fn type_for_char(c: char) -> Option { - match c { - '0' => Some(Type::Text), - '1' => Some(Type::Menu), - '2' => Some(Type::CSOEntity), - '3' => Some(Type::Error), - '4' => Some(Type::Binhex), - '5' => Some(Type::DOSFile), - '6' => Some(Type::UUEncoded), - '7' => Some(Type::Search), - '8' => Some(Type::Telnet), - '9' => Some(Type::Binary), - '+' => Some(Type::Mirror), - 'g' => Some(Type::GIF), - 'T' => Some(Type::Telnet3270), - 'h' => Some(Type::HTML), - 'I' => Some(Type::Image), - 'p' => Some(Type::PNG), - 'i' => Some(Type::Info), - 's' => Some(Type::Sound), - 'd' => Some(Type::Document), - _ => None, - } + Some(match c { + '0' => Type::Text, + '1' => Type::Menu, + '2' => Type::CSOEntity, + '3' => Type::Error, + '4' => Type::Binhex, + '5' => Type::DOSFile, + '6' => Type::UUEncoded, + '7' => Type::Search, + '8' => Type::Telnet, + '9' => Type::Binary, + '+' => Type::Mirror, + 'g' => Type::GIF, + 'T' => Type::Telnet3270, + 'h' => Type::HTML, + 'I' => Type::Image, + 'p' => Type::PNG, + 'i' => Type::Info, + 's' => Type::Sound, + 'd' => Type::Document, + _ => return None, + }) +} + +pub fn char_for_type(t: Type) -> Option { + Some(match t { + Type::Text => '0', + Type::Menu => '1', + Type::CSOEntity => '2', + Type::Error => '3', + Type::Binhex => '4', + Type::DOSFile => '5', + Type::UUEncoded => '6', + Type::Search => '7', + Type::Telnet => '8', + Type::Binary => '9', + Type::Mirror => '+', + Type::GIF => 'g', + Type::Telnet3270 => 'T', + Type::HTML => 'h', + Type::Image => 'I', + Type::PNG => 'p', + Type::Info => 'i', + Type::Sound => 's', + Type::Document => 'd', + _ => return None, + }) } macro_rules! error { diff --git a/src/help.rs b/src/help.rs index 21a39a3..a6ff8d1 100644 --- a/src/help.rs +++ b/src/help.rs @@ -1,11 +1,14 @@ -pub fn lookup(name: &str) -> Option<&str> { - match name { - "" | "/" | "help" => Some(HELP), - "types" => Some(TYPES), - "nav" => Some(NAV), - "home" => Some(HOME), - _ => None, - } +use history; + +pub fn lookup(name: &str) -> Option { + Some(match name { + "" | "/" | "help" => HELP.into(), + "types" => TYPES.into(), + "nav" => NAV.into(), + "home" => HOME.into(), + "history" => history::load_as_raw_menu().unwrap_or_else(|| String::new()), + _ => return None, + }) } pub const HOME: &str = " diff --git a/src/history.rs b/src/history.rs new file mode 100644 index 0000000..80d9eef --- /dev/null +++ b/src/history.rs @@ -0,0 +1,76 @@ +use gopher; +use std::fs::File; +use std::io::{BufRead, BufReader, Write}; + +pub fn load_as_raw_menu() -> Option { + let mut out = vec![]; + + if let Some(reader) = load() { + let mut lines = reader.lines(); + while let Some(Ok(url)) = lines.next() { + let (t, host, port, sel) = gopher::parse_url(&url); + out.insert( + 0, + format!( + "{}{}\t{}\t{}\t{}", + gopher::char_for_type(t).unwrap_or('i'), + url, + sel, + host, + port + ), + ); + } + } + + out.insert(0, "i~/.config/phetch/history:\r\ni".into()); + Some(out.join("\r\n")) +} + +pub fn load() -> Option> { + let dotdir = config_dir_path(); + if dotdir.is_none() { + return None; + } + let history = dotdir.unwrap().join("history"); + if let Ok(file) = std::fs::OpenOptions::new().read(true).open(history) { + return Some(BufReader::new(file)); + } + None +} + +pub fn save(urls: &[impl std::fmt::Display]) { + let dotdir = config_dir_path(); + if dotdir.is_none() { + return; + } + let dotdir = dotdir.unwrap(); + let mut out = String::new(); + for url in urls { + out.push_str(url.to_string().as_ref()); + out.push('\n'); + } + let history = dotdir.join("history"); + if let Ok(mut file) = std::fs::OpenOptions::new() + .append(true) + .create(true) + .open(history) + { + file.write_all(out.as_ref()); + } +} + +pub fn config_dir_path() -> Option { + let homevar = std::env::var("HOME"); + if homevar.is_err() { + return None; + } + + let dotdir = "~/.config/phetch".replace('~', &homevar.unwrap()); + let dotdir = std::path::Path::new(&dotdir); + if dotdir.exists() { + Some(std::path::PathBuf::from(dotdir)) + } else { + None + } +} diff --git a/src/main.rs b/src/main.rs index f16a7ab..320b050 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,7 @@ extern crate termion; #[macro_use] mod gopher; mod help; +mod history; mod menu; mod text; mod ui; diff --git a/src/menu.rs b/src/menu.rs index 02a2777..8ca487d 100644 --- a/src/menu.rs +++ b/src/menu.rs @@ -1,5 +1,6 @@ use gopher; use gopher::Type; +use std::fmt; use std::io::stdout; use std::io::Write; use ui; @@ -33,6 +34,12 @@ enum LinkDir { Visible, } +impl fmt::Display for Menu { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.url()) + } +} + impl View for Menu { fn raw(&self) -> String { self.raw.to_string() diff --git a/src/text.rs b/src/text.rs index 501b052..3057aac 100644 --- a/src/text.rs +++ b/src/text.rs @@ -1,3 +1,4 @@ +use std::fmt; use ui::{Action, Key, View, MAX_COLS, SCROLL_LINES}; pub struct Text { @@ -10,6 +11,12 @@ pub struct Text { pub wide: bool, // in wide mode? turns off margins } +impl fmt::Display for Text { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.url()) + } +} + impl View for Text { fn url(&self) -> String { self.url.to_string() diff --git a/src/ui.rs b/src/ui.rs index 3bf5500..de6c389 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -17,6 +17,7 @@ use termion::terminal_size; use gopher; use gopher::Type; use help; +use history; use menu::Menu; use text::Text; @@ -172,7 +173,7 @@ impl UI { &url.trim_start_matches("gopher://help/") .trim_start_matches("1/"), ) { - Ok(Box::new(Menu::from(url.to_string(), source.to_string()))) + Ok(Box::new(Menu::from(url.to_string(), source))) } else { Err(error!("Help file not found: {}", url)) } @@ -236,65 +237,10 @@ impl UI { self.size.1 as u16 } - fn startup(&mut self) { - self.load_history(); - } + fn startup(&mut self) {} fn shutdown(&self) { - self.save_history(); - } - - fn config_dir_path(&self) -> Option { - let homevar = std::env::var("HOME"); - if homevar.is_err() { - return None; - } - - let dotdir = "~/.config/phetch".replace('~', &homevar.unwrap()); - let dotdir = std::path::Path::new(&dotdir); - if dotdir.exists() { - Some(std::path::PathBuf::from(dotdir)) - } else { - None - } - } - - fn load_history(&mut self) { - // let dotdir = self.config_dir_path(); - // if dotdir.is_none() { - // return; - // } - // let history = dotdir.unwrap().join("history"); - // if let Ok(file) = std::fs::OpenOptions::new().read(true).open(history) { - // let buffered = BufReader::new(file); - // let mut lines = buffered.lines(); - // while let Some(Ok(url)) = lines.next() {} - // } - } - - fn save_history(&self) { - let dotdir = self.config_dir_path(); - if dotdir.is_none() { - return; - } - let dotdir = dotdir.unwrap(); - let mut out = String::new(); - for page in &self.views { - let url = page.url(); - if url.starts_with("gopher://help/") { - continue; - } - out.push_str(&page.url()); - out.push('\n'); - } - let history = dotdir.join("history"); - if let Ok(mut file) = std::fs::OpenOptions::new() - .append(true) - .create(true) - .open(history) - { - file.write_all(out.as_ref()); - } + history::save(&self.views); } fn term_size(&mut self, cols: usize, rows: usize) { @@ -378,6 +324,7 @@ impl UI { } } Action::Keypress(Key::Ctrl('h')) => self.open("gopher://help/")?, + Action::Keypress(Key::Ctrl('e')) => self.open("gopher://help/1/history")?, Action::Keypress(Key::Ctrl('u')) => { if let Some(page) = self.views.get(self.focused) { let url = page.url(); diff --git a/src/ui/view.rs b/src/ui/view.rs index eba43fa..04078d8 100644 --- a/src/ui/view.rs +++ b/src/ui/view.rs @@ -1,6 +1,7 @@ +use std::fmt; use ui; -pub trait View { +pub trait View: fmt::Display { fn respond(&mut self, key: ui::Key) -> ui::Action; fn render(&self) -> String; fn url(&self) -> String;