parse_url returns simple struct, not tuple

pull/14/head
chris west 4 years ago
parent 999171e0e5
commit d66a9c610c

@ -59,11 +59,23 @@ impl Write for Stream {
} }
} }
/// Gopher URL. Returned by `parse_url()`.
pub struct Url<'a> {
/// Gopher Type
pub typ: Type,
/// Hostname
pub host: &'a str,
/// Port. Defaults to 70
pub port: &'a str,
/// Selector
pub sel: &'a str,
}
/// Fetches a gopher URL and returns a tuple of: /// Fetches a gopher URL and returns a tuple of:
/// (did tls work?, raw Gopher response) /// (did tls work?, raw Gopher response)
pub fn fetch_url(url: &str, tls: bool, tor: bool) -> Result<(bool, String)> { pub fn fetch_url(url: &str, tls: bool, tor: bool) -> Result<(bool, String)> {
let (_, host, port, sel) = parse_url(url); let u = parse_url(url);
fetch(host, port, sel, tls, tor) fetch(u.host, u.port, u.sel, tls, tor)
} }
/// Fetches a gopher URL by its component parts and returns a tuple of: /// Fetches a gopher URL by its component parts and returns a tuple of:
@ -98,18 +110,19 @@ fn clean_response(res: &str) -> String {
/// Returns a tuple of: /// Returns a tuple of:
/// (path it was saved to, the size in bytes) /// (path it was saved to, the size in bytes)
pub fn download_url(url: &str, tls: bool, tor: bool) -> Result<(String, usize)> { pub fn download_url(url: &str, tls: bool, tor: bool) -> Result<(String, usize)> {
let (_, host, port, sel) = parse_url(url); let u = parse_url(url);
let filename = sel let filename = u
.sel
.split_terminator('/') .split_terminator('/')
.rev() .rev()
.nth(0) .nth(0)
.ok_or_else(|| error!("Bad download filename: {}", sel))?; .ok_or_else(|| error!("Bad download filename: {}", u.sel))?;
let mut path = std::path::PathBuf::from("."); let mut path = std::path::PathBuf::from(".");
path.push(filename); path.push(filename);
let stdin = termion::async_stdin(); let stdin = termion::async_stdin();
let mut keys = stdin.keys(); let mut keys = stdin.keys();
let mut stream = request(host, port, sel, tls, tor)?; let mut stream = request(u.host, u.port, u.sel, tls, tor)?;
let mut file = std::fs::OpenOptions::new() let mut file = std::fs::OpenOptions::new()
.write(true) .write(true)
.create(true) .create(true)
@ -192,9 +205,21 @@ pub fn request(host: &str, port: &str, selector: &str, tls: bool, tor: bool) ->
}) })
} }
impl<'a> Url<'a> {
/// Creates a new Gopher Url quickly from a tuple of Url fields.
pub fn new(typ: Type, host: &'a str, port: &'a str, sel: &'a str) -> Url<'a> {
Url {
typ,
host,
port,
sel,
}
}
}
/// Parses gopher URL into parts. /// Parses gopher URL into parts.
/// Returns (Type, host, port, sel) /// Returns (Type, host, port, sel)
pub fn parse_url(url: &str) -> (Type, &str, &str, &str) { pub fn parse_url<'a>(url: &'a str) -> Url<'a> {
let mut url = url.trim_start_matches("gopher://"); let mut url = url.trim_start_matches("gopher://");
let mut typ = Type::Menu; let mut typ = Type::Menu;
let mut host; let mut host;
@ -203,7 +228,7 @@ pub fn parse_url(url: &str) -> (Type, &str, &str, &str) {
// simple URLs, ex: "dog.com" // simple URLs, ex: "dog.com"
if !url.contains(':') && !url.contains('/') { if !url.contains(':') && !url.contains('/') {
return (Type::Menu, url, "70", ""); return Url::new(Type::Menu, url, "70", "");
} }
// telnet urls // telnet urls
@ -212,7 +237,7 @@ pub fn parse_url(url: &str) -> (Type, &str, &str, &str) {
url = url.trim_start_matches("telnet://"); url = url.trim_start_matches("telnet://");
} else if url.contains("://") { } else if url.contains("://") {
// non-gopher URLs, stick everything in selector // non-gopher URLs, stick everything in selector
return (Type::HTML, "", "", url); return Url::new(Type::HTML, "", "", url);
} }
// check selector first // check selector first
@ -233,7 +258,7 @@ pub fn parse_url(url: &str) -> (Type, &str, &str, &str) {
} }
} }
} else { } else {
return (Type::Error, "Unclosed ipv6 bracket", "", url); return Url::new(Type::Error, "Unclosed ipv6 bracket", "", url);
} }
} else if let Some(idx) = host.find(':') { } else if let Some(idx) = host.find(':') {
// two :'s == probably ipv6 // two :'s == probably ipv6
@ -255,7 +280,7 @@ pub fn parse_url(url: &str) -> (Type, &str, &str, &str) {
} }
} }
(typ, host, port, sel) Url::new(typ, host, port, sel)
} }
#[cfg(test)] #[cfg(test)]
@ -282,94 +307,94 @@ mod tests {
"telnet://bbs.impakt.net:6502/", "telnet://bbs.impakt.net:6502/",
]; ];
let (typ, host, port, sel) = parse_url(urls[0]); let url = parse_url(urls[0]);
assert_eq!(typ, Type::Menu); assert_eq!(url.typ, Type::Menu);
assert_eq!(host, "gopher.club"); assert_eq!(url.host, "gopher.club");
assert_eq!(port, "70"); assert_eq!(url.port, "70");
assert_eq!(sel, "/phlogs/"); assert_eq!(url.sel, "/phlogs/");
let (typ, host, port, sel) = parse_url(urls[1]); let url = parse_url(urls[1]);
assert_eq!(typ, Type::Menu); assert_eq!(url.typ, Type::Menu);
assert_eq!(host, "sdf.org"); assert_eq!(url.host, "sdf.org");
assert_eq!(port, "7777"); assert_eq!(url.port, "7777");
assert_eq!(sel, "/maps"); assert_eq!(url.sel, "/maps");
let (typ, host, port, sel) = parse_url(urls[2]); let url = parse_url(urls[2]);
assert_eq!(typ, Type::Menu); assert_eq!(url.typ, Type::Menu);
assert_eq!(host, "gopher.floodgap.org"); assert_eq!(url.host, "gopher.floodgap.org");
assert_eq!(port, "70"); assert_eq!(url.port, "70");
assert_eq!(sel, ""); assert_eq!(url.sel, "");
let (typ, host, port, sel) = parse_url(urls[3]); let url = parse_url(urls[3]);
assert_eq!(typ, Type::Text); assert_eq!(url.typ, Type::Text);
assert_eq!(host, "gopher.floodgap.com"); assert_eq!(url.host, "gopher.floodgap.com");
assert_eq!(port, "70"); assert_eq!(url.port, "70");
assert_eq!(sel, "/gopher/relevance.txt"); assert_eq!(url.sel, "/gopher/relevance.txt");
let (typ, host, port, sel) = parse_url(urls[4]); let url = parse_url(urls[4]);
assert_eq!(typ, Type::Search); assert_eq!(url.typ, Type::Search);
assert_eq!(host, "gopherpedia.com"); assert_eq!(url.host, "gopherpedia.com");
assert_eq!(port, "70"); assert_eq!(url.port, "70");
assert_eq!(sel, "/lookup?Gopher"); assert_eq!(url.sel, "/lookup?Gopher");
let (typ, host, port, sel) = parse_url(urls[5]); let url = parse_url(urls[5]);
assert_eq!(typ, Type::Menu); assert_eq!(url.typ, Type::Menu);
assert_eq!(host, "dead:beef:1234:5678:9012:3456:feed:deed"); assert_eq!(url.host, "dead:beef:1234:5678:9012:3456:feed:deed");
assert_eq!(port, "70"); assert_eq!(url.port, "70");
assert_eq!(sel, ""); assert_eq!(url.sel, "");
let (typ, host, port, sel) = parse_url(urls[6]); let url = parse_url(urls[6]);
assert_eq!(typ, Type::Menu); assert_eq!(url.typ, Type::Menu);
assert_eq!(host, "1234:2345:dead:4567:7890:1234:beef:1111"); assert_eq!(url.host, "1234:2345:dead:4567:7890:1234:beef:1111");
assert_eq!(port, "70"); assert_eq!(url.port, "70");
assert_eq!(sel, "/files"); assert_eq!(url.sel, "/files");
let (typ, host, port, sel) = parse_url(urls[7]); let url = parse_url(urls[7]);
assert_eq!(typ, Type::Menu); assert_eq!(url.typ, Type::Menu);
assert_eq!(host, "2001:cdba:0000:0000:0000:0000:3257:9121"); assert_eq!(url.host, "2001:cdba:0000:0000:0000:0000:3257:9121");
assert_eq!(port, "70"); assert_eq!(url.port, "70");
assert_eq!(sel, ""); assert_eq!(url.sel, "");
let (typ, host, port, sel) = parse_url(urls[8]); let url = parse_url(urls[8]);
assert_eq!(typ, Type::Menu); assert_eq!(url.typ, Type::Menu);
assert_eq!(host, "2001:cdba::3257:9652"); assert_eq!(url.host, "2001:cdba::3257:9652");
assert_eq!(port, "70"); assert_eq!(url.port, "70");
assert_eq!(sel, ""); assert_eq!(url.sel, "");
let (typ, host, port, sel) = parse_url(urls[9]); let url = parse_url(urls[9]);
assert_eq!(typ, Type::Menu); assert_eq!(url.typ, Type::Menu);
assert_eq!(host, "9999:aaaa::abab:baba:aaaa:9999"); assert_eq!(url.host, "9999:aaaa::abab:baba:aaaa:9999");
assert_eq!(port, "70"); assert_eq!(url.port, "70");
assert_eq!(sel, ""); assert_eq!(url.sel, "");
let (typ, host, port, sel) = parse_url(urls[10]); let url = parse_url(urls[10]);
assert_eq!(typ, Type::Error); assert_eq!(url.typ, Type::Error);
assert_eq!(host, "Unclosed ipv6 bracket"); assert_eq!(url.host, "Unclosed ipv6 bracket");
assert_eq!(port, ""); assert_eq!(url.port, "");
assert_eq!(sel, "[2001:2099:dead:beef:0000"); assert_eq!(url.sel, "[2001:2099:dead:beef:0000");
let (typ, host, port, sel) = parse_url(urls[11]); let url = parse_url(urls[11]);
assert_eq!(typ, Type::Menu); assert_eq!(url.typ, Type::Menu);
assert_eq!(host, "::1"); assert_eq!(url.host, "::1");
assert_eq!(port, "70"); assert_eq!(url.port, "70");
assert_eq!(sel, ""); assert_eq!(url.sel, "");
let (typ, host, port, sel) = parse_url(urls[12]); let url = parse_url(urls[12]);
assert_eq!(typ, Type::HTML); assert_eq!(url.typ, Type::HTML);
assert_eq!(host, ""); assert_eq!(url.host, "");
assert_eq!(port, ""); assert_eq!(url.port, "");
assert_eq!(sel, "ssh://kiosk@bitreich.org"); assert_eq!(url.sel, "ssh://kiosk@bitreich.org");
let (typ, host, port, sel) = parse_url(urls[13]); let url = parse_url(urls[13]);
assert_eq!(typ, Type::HTML); assert_eq!(url.typ, Type::HTML);
assert_eq!(host, ""); assert_eq!(url.host, "");
assert_eq!(port, ""); assert_eq!(url.port, "");
assert_eq!(sel, "https://github.com/xvxx/phetch"); assert_eq!(url.sel, "https://github.com/xvxx/phetch");
let (typ, host, port, sel) = parse_url(urls[14]); let url = parse_url(urls[14]);
assert_eq!(typ, Type::Telnet); assert_eq!(url.typ, Type::Telnet);
assert_eq!(host, "bbs.impakt.net"); assert_eq!(url.host, "bbs.impakt.net");
assert_eq!(port, "6502"); assert_eq!(url.port, "6502");
assert_eq!(sel, "/"); assert_eq!(url.sel, "/");
} }
} }

@ -123,7 +123,7 @@ fn print_raw(url: &str, tls: bool, tor: bool) -> i32 {
/// (like a pipe). /// (like a pipe).
fn print_plain(url: &str, tls: bool, tor: bool) -> i32 { fn print_plain(url: &str, tls: bool, tor: bool) -> i32 {
let mut out = String::new(); let mut out = String::new();
let (typ, _, _, _) = gopher::parse_url(url); let gopher::Url { typ, .. } = gopher::parse_url(url);
match gopher::fetch_url(url, tls, tor) { match gopher::fetch_url(url, tls, tor) {
Ok((_, response)) => match typ { Ok((_, response)) => match typ {
gopher::Type::Menu => { gopher::Type::Menu => {

@ -632,7 +632,7 @@ impl Menu {
if let Some(line) = self.link(self.link) { if let Some(line) = self.link(self.link) {
let url = line.url.to_string(); let url = line.url.to_string();
let (typ, _, _, _) = gopher::parse_url(&url); let gopher::Url { typ, .. } = gopher::parse_url(&url);
match typ { match typ {
Type::Search => { Type::Search => {
let prompt = format!("{}> ", line.text); let prompt = format!("{}> ", line.text);

@ -42,15 +42,15 @@ pub fn append(filename: &str, label: &str, url: &str) -> Result<()> {
path().and_then(|dotdir| { path().and_then(|dotdir| {
let path = dotdir.join(filename); let path = dotdir.join(filename);
if let Ok(mut file) = OpenOptions::new().append(true).create(true).open(path) { if let Ok(mut file) = OpenOptions::new().append(true).create(true).open(path) {
let (t, host, port, sel) = gopher::parse_url(&url); let u = gopher::parse_url(&url);
file.write_all( file.write_all(
format!( format!(
"{}{}\t{}\t{}\t{}\r\n", "{}{}\t{}\t{}\t{}\r\n",
t.to_char().unwrap_or('i'), u.typ.to_char().unwrap_or('i'),
label, label,
sel, u.sel,
host, u.host,
port u.port
) )
.as_ref(), .as_ref(),
); );
@ -71,18 +71,18 @@ pub fn prepend(filename: &str, label: &str, url: &str) -> Result<()> {
.create(true) .create(true)
.open(path) .open(path)
{ {
let (t, host, port, sel) = gopher::parse_url(&url); let url = gopher::parse_url(&url);
let mut buf = vec![]; let mut buf = vec![];
file.read_to_end(&mut buf); file.read_to_end(&mut buf);
file.seek(std::io::SeekFrom::Start(0)); file.seek(std::io::SeekFrom::Start(0));
file.write_all( file.write_all(
format!( format!(
"{}{}\t{}\t{}\t{}\r\n", "{}{}\t{}\t{}\t{}\r\n",
t.to_char().unwrap_or('i'), url.typ.to_char().unwrap_or('i'),
label, label,
sel, url.sel,
host, url.host,
port url.port
) )
.as_ref(), .as_ref(),
); );

@ -188,7 +188,7 @@ impl UI {
} }
// binary downloads // binary downloads
let (typ, _, _, _) = gopher::parse_url(url); let gopher::Url { typ, .. } = gopher::parse_url(url);
if typ.is_download() { if typ.is_download() {
self.dirty = true; self.dirty = true;
return if self.confirm(&format!("Download {}?", url)) { return if self.confirm(&format!("Download {}?", url)) {
@ -244,7 +244,7 @@ impl UI {
} else { } else {
self.spinner("", move || gopher::fetch_url(&thread_url, tls, tor))?? self.spinner("", move || gopher::fetch_url(&thread_url, tls, tor))??
}; };
let (typ, _, _, _) = gopher::parse_url(&url); let gopher::Url { typ, .. } = gopher::parse_url(&url);
match typ { match typ {
Type::Menu | Type::Search => Ok(Box::new(Menu::from(url, res, tls, tor))), Type::Menu | Type::Search => Ok(Box::new(Menu::from(url, res, tls, tor))),
Type::Text | Type::HTML => Ok(Box::new(Text::from(url, res, tls, tor))), Type::Text | Type::HTML => Ok(Box::new(Text::from(url, res, tls, tor))),
@ -489,7 +489,7 @@ impl UI {
/// Opens an interactive telnet session. /// Opens an interactive telnet session.
fn telnet(&mut self, url: &str) -> Result<()> { fn telnet(&mut self, url: &str) -> Result<()> {
let (_, host, port, _) = gopher::parse_url(url); let gopher::Url { host, port, .. } = gopher::parse_url(url);
let out = self.out.borrow_mut(); let out = self.out.borrow_mut();
out.suspend_raw_mode(); out.suspend_raw_mode();
let mut cmd = process::Command::new("telnet") let mut cmd = process::Command::new("telnet")

Loading…
Cancel
Save