-- Leo's gemini proxy
-- Connecting to typed-hole.org:1965...
-- Connected
-- Sending request
-- Meta line: 20 text/gemini
commit 058b69e7383cffafcecf0121046c4d83114cf2cb
Author: Julien Blanchard <julien@sideburns.eu>
Date: Tue Dec 31 18:41:24 2019 +0100
Color support
diff --git a/Cargo.lock b/Cargo.lock
index 7a98f60..302fa4a 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -9,6 +9,14 @@ dependencies = [
]
[[package]]
+name = "ansi-parser"
+version = "0.6.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "arrayref"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -130,6 +138,7 @@ dependencies = [
name = "castor"
version = "0.1.0"
dependencies = [
+ "ansi-parser 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
"dirs 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
"gdk 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
"gdk-pixbuf 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -141,6 +150,7 @@ dependencies = [
"native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"open 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"pango 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -542,6 +552,15 @@ dependencies = [
]
[[package]]
+name = "nom"
+version = "4.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
name = "open"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -898,6 +917,11 @@ version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
+name = "version_check"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
name = "wasi"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -923,6 +947,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
[metadata]
"checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d"
+"checksum ansi-parser 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "761ac675f1638a6a49e26f6ac3a4067ca3fefa8029816ae4ef8d3fa3d06a5194"
"checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee"
"checksum arrayvec 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
"checksum atk 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "444daefa55f229af145ea58d77efd23725024ee1f6f3102743709aa6b18c663e"
@@ -977,6 +1002,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
"checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e"
+"checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
"checksum open 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "94b424e1086328b0df10235c6ff47be63708071881bead9e76997d9291c0134b"
"checksum openssl 0.10.26 (registry+https://github.com/rust-lang/crates.io-index)" = "3a3cc5799d98e1088141b8e01ff760112bbd9f19d850c124500566ca6901a585"
"checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
@@ -1020,6 +1046,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
"checksum url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "75b414f6c464c879d7f9babf951f23bc3743fb7313c081b2e6ca719067ea9d61"
"checksum vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168"
+"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
"checksum wasi 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b89c3ce4ce14bdc6fb6beaf9ec7928ca331de5df7e5ea278375642a2f478570d"
"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
diff --git a/Cargo.toml b/Cargo.toml
index 0ba960a..7f4c5eb 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -20,6 +20,8 @@ url = "*"
tempfile = "*"
dirs = "*"
lazy_static = "*"
+ansi-parser = "0.6.5"
+percent-encoding="*"
[dependencies.gtk]
version = "0.8.0"
diff --git a/src/client.rs b/src/client.rs
index 8d5ed0d..7ab2537 100644
--- a/src/client.rs
+++ b/src/client.rs
@@ -1,90 +1,11 @@
-use std::io::{Read, Write};
+use std::io::{Write};
use tempfile::NamedTempFile;
-use native_tls::TlsConnector;
-use std::net::{TcpStream, ToSocketAddrs};
-use std::time::Duration;
-
-use crate::Gemini;
-use crate::Gopher;
-use crate::Protocol;
-
pub trait Client {
fn get_data(&self) -> Result<(Option<Vec<u8>>, Vec<u8>), String>;
}
-impl Client for Gemini {
- fn get_data(&self) -> Result<(Option<Vec<u8>>, Vec<u8>), String> {
- let url = self.get_source_url();
- let host = url.host_str().unwrap();
- let urlf = format!("{}:1965", host);
-
- let mut builder = TlsConnector::builder();
- builder.danger_accept_invalid_hostnames(true);
- builder.danger_accept_invalid_certs(true);
- let connector = builder.build().unwrap();
-
- match urlf.to_socket_addrs() {
- Ok(mut addrs_iter) => match addrs_iter.next() {
- Some(socket_addr) => {
- let stream = TcpStream::connect_timeout(&socket_addr, Duration::new(5, 0));
-
- match stream {
- Ok(stream) => {
- let mstream = connector.connect(&host, stream);
-
- match mstream {
- Ok(mut stream) => {
- let url = format!("{}\r\n", url);
- stream.write_all(url.as_bytes()).unwrap();
- let mut res = vec![];
- stream.read_to_end(&mut res).unwrap();
-
- let clrf_idx = find_clrf(&res);
- let content = res.split_off(clrf_idx.unwrap() + 2);
-
- Ok((Some(res), content))
- }
- Err(e) => Err(format!("Could not connect to {}\n{}", urlf, e)),
- }
- }
- Err(e) => Err(format!("Could not connect to {}\n{}", urlf, e)),
- }
- }
- None => Err(format!("Could not connect to {}", urlf)),
- },
- Err(e) => Err(format!("Could not connect to {}\n{}", urlf, e)),
- }
- }
-}
-
-impl Client for Gopher {
- fn get_data(&self) -> Result<(Option<Vec<u8>>, Vec<u8>), String> {
- let url = self.get_source_url();
- let host = url.host_str().unwrap();
- let urlf = format!("{}:70", host);
- println!("{:?}", url.path());
-
- match TcpStream::connect(&urlf) {
- Ok(mut stream) => {
- let mut url = format!("{}\r\n", url.path());
- let url = if url.starts_with("/0/") || url.starts_with("/1/") {
- url.split_off(2)
- } else {
- url
- };
- stream.write_all(url.as_bytes()).unwrap();
- let mut res = vec![];
- stream.read_to_end(&mut res).unwrap();
-
- Ok((None, res))
- }
- Err(e) => Err(format!("Could not connect to {}\n{}", urlf, e)),
- }
- }
-}
-
pub fn download(content: Vec<u8>) {
let path = write_tmp_file(content);
open::that(path).unwrap();
@@ -96,8 +17,3 @@ fn write_tmp_file(content: Vec<u8>) -> std::path::PathBuf {
let (_file, path) = tmp_file.keep().unwrap();
path
}
-
-fn find_clrf(data: &[u8]) -> Option<usize> {
- let clrf = b"\r\n";
- data.windows(clrf.len()).position(|window| window == clrf)
-}
diff --git a/src/colors.rs b/src/colors.rs
new file mode 100644
index 0000000..c4cc60e
--- /dev/null
+++ b/src/colors.rs
@@ -0,0 +1,294 @@
+pub mod colors {
+ use ansi_parser::AnsiSequence;
+ use ansi_parser::{AnsiParser, Output};
+
+ extern crate regex;
+ use regex::Regex;
+
+ const COLOR_CODE_REGEX: &str = r"(?-u)\u{1b}\[[\d|;]+m";
+
+ pub fn cleanup(line: &str) -> String {
+ let color_code_regexp = Regex::new(COLOR_CODE_REGEX).unwrap();
+ let line = color_code_regexp.replace_all(line, "");
+ String::from(line)
+ }
+
+ pub fn colorize(line: &str) -> String {
+ let parsed: Vec<Output> = line.ansi_parse().collect();
+
+ let mut s = String::new();
+ for p in parsed {
+ match p {
+ Output::Escape(AnsiSequence::SetGraphicsMode(colors)) => {
+ if colors.len() == 1 {
+ s.push_str("");
+ } else {
+ let color = colors.last().unwrap();
+ s.push_str(&format!("<span foreground={:?}>", ansi_color_to_hex(*color)))
+ }
+ }
+ Output::TextBlock(text) => {
+ if s.ends_with('>') {
+ s.push_str(&format!("{}</span>", text))
+ } else {
+ s.push_str(text)
+ }
+ }
+ _ => s.push_str("something else"),
+ }
+ }
+ s
+ }
+
+ fn ansi_color_to_hex(color: u32) -> &'static str {
+ match color {
+ 10 => "#00ff00",
+ 11 => "#ffff00",
+ 12 => "#0000ff",
+ 13 => "#ff00ff",
+ 14 => "#00ffff",
+ 15 => "#ffffff",
+ 16 => "#000000",
+ 17 => "#00005f",
+ 18 => "#000087",
+ 19 => "#0000af",
+ 20 => "#0000d7",
+ 21 => "#0000ff",
+ 22 => "#005f00",
+ 23 => "#005f5f",
+ 24 => "#005f87",
+ 25 => "#005faf",
+ 26 => "#005fd7",
+ 27 => "#005fff",
+ 28 => "#008700",
+ 29 => "#00875f",
+ 30 => "#008787",
+ 31 => "#0087af",
+ 32 => "#0087d7",
+ 33 => "#0087ff",
+ 34 => "#00af00",
+ 35 => "#00af5f",
+ 36 => "#00af87",
+ 37 => "#00afaf",
+ 38 => "#00afd7",
+ 39 => "#00afff",
+ 40 => "#00d700",
+ 41 => "#00d75f",
+ 42 => "#00d787",
+ 43 => "#00d7af",
+ 44 => "#00d7d7",
+ 45 => "#00d7ff",
+ 46 => "#00ff00",
+ 47 => "#00ff5f",
+ 48 => "#00ff87",
+ 49 => "#00ffaf",
+ 50 => "#00ffd7",
+ 51 => "#00ffff",
+ 52 => "#5f0000",
+ 53 => "#5f005f",
+ 54 => "#5f0087",
+ 55 => "#5f00af",
+ 56 => "#5f00d7",
+ 57 => "#5f00ff",
+ 58 => "#5f5f00",
+ 59 => "#5f5f5f",
+ 60 => "#5f5f87",
+ 61 => "#5f5faf",
+ 62 => "#5f5fd7",
+ 63 => "#5f5fff",
+ 64 => "#5f8700",
+ 65 => "#5f875f",
+ 66 => "#5f8787",
+ 67 => "#5f87af",
+ 68 => "#5f87d7",
+ 69 => "#5f87ff",
+ 70 => "#5faf00",
+ 71 => "#5faf5f",
+ 72 => "#5faf87",
+ 73 => "#5fafaf",
+ 74 => "#5fafd7",
+ 75 => "#5fafff",
+ 76 => "#5fd700",
+ 77 => "#5fd75f",
+ 78 => "#5fd787",
+ 79 => "#5fd7af",
+ 80 => "#5fd7d7",
+ 81 => "#5fd7ff",
+ 82 => "#5fff00",
+ 83 => "#5fff5f",
+ 84 => "#5fff87",
+ 85 => "#5fffaf",
+ 86 => "#5fffd7",
+ 87 => "#5fffff",
+ 88 => "#870000",
+ 89 => "#87005f",
+ 90 => "#870087",
+ 91 => "#8700af",
+ 92 => "#8700d7",
+ 93 => "#8700ff",
+ 94 => "#875f00",
+ 95 => "#875f5f",
+ 96 => "#875f87",
+ 97 => "#875faf",
+ 98 => "#875fd7",
+ 99 => "#875fff",
+ 100 => "#878700",
+ 101 => "#87875f",
+ 102 => "#878787",
+ 103 => "#8787af",
+ 104 => "#8787d7",
+ 105 => "#8787ff",
+ 106 => "#87af00",
+ 107 => "#87af5f",
+ 108 => "#87af87",
+ 109 => "#87afaf",
+ 110 => "#87afd7",
+ 111 => "#87afff",
+ 112 => "#87d700",
+ 113 => "#87d75f",
+ 114 => "#87d787",
+ 115 => "#87d7af",
+ 116 => "#87d7d7",
+ 117 => "#87d7ff",
+ 118 => "#87ff00",
+ 119 => "#87ff5f",
+ 120 => "#87ff87",
+ 121 => "#87ffaf",
+ 122 => "#87ffd7",
+ 123 => "#87ffff",
+ 124 => "#af0000",
+ 125 => "#af005f",
+ 126 => "#af0087",
+ 127 => "#af00af",
+ 128 => "#af00d7",
+ 129 => "#af00ff",
+ 130 => "#af5f00",
+ 131 => "#af5f5f",
+ 132 => "#af5f87",
+ 133 => "#af5faf",
+ 134 => "#af5fd7",
+ 135 => "#af5fff",
+ 136 => "#af8700",
+ 137 => "#af875f",
+ 138 => "#af8787",
+ 139 => "#af87af",
+ 140 => "#af87d7",
+ 141 => "#af87ff",
+ 142 => "#afaf00",
+ 143 => "#afaf5f",
+ 144 => "#afaf87",
+ 145 => "#afafaf",
+ 146 => "#afafd7",
+ 147 => "#afafff",
+ 148 => "#afd700",
+ 149 => "#afd75f",
+ 150 => "#afd787",
+ 151 => "#afd7af",
+ 152 => "#afd7d7",
+ 153 => "#afd7ff",
+ 154 => "#afff00",
+ 155 => "#afff5f",
+ 156 => "#afff87",
+ 157 => "#afffaf",
+ 158 => "#afffd7",
+ 159 => "#afffff",
+ 160 => "#d70000",
+ 161 => "#d7005f",
+ 162 => "#d70087",
+ 163 => "#d700af",
+ 164 => "#d700d7",
+ 165 => "#d700ff",
+ 166 => "#d75f00",
+ 167 => "#d75f5f",
+ 168 => "#d75f87",
+ 169 => "#d75faf",
+ 170 => "#d75fd7",
+ 171 => "#d75fff",
+ 172 => "#d78700",
+ 173 => "#d7875f",
+ 174 => "#d78787",
+ 175 => "#d787af",
+ 176 => "#d787d7",
+ 177 => "#d787ff",
+ 178 => "#d7af00",
+ 179 => "#d7af5f",
+ 180 => "#d7af87",
+ 181 => "#d7afaf",
+ 182 => "#d7afd7",
+ 183 => "#d7afff",
+ 184 => "#d7d700",
+ 185 => "#d7d75f",
+ 186 => "#d7d787",
+ 187 => "#d7d7af",
+ 188 => "#d7d7d7",
+ 189 => "#d7d7ff",
+ 190 => "#d7ff00",
+ 191 => "#d7ff5f",
+ 192 => "#d7ff87",
+ 193 => "#d7ffaf",
+ 194 => "#d7ffd7",
+ 195 => "#d7ffff",
+ 196 => "#ff0000",
+ 197 => "#ff005f",
+ 198 => "#ff0087",
+ 199 => "#ff00af",
+ 200 => "#ff00d7",
+ 201 => "#ff00ff",
+ 202 => "#ff5f00",
+ 203 => "#ff5f5f",
+ 204 => "#ff5f87",
+ 205 => "#ff5faf",
+ 206 => "#ff5fd7",
+ 207 => "#ff5fff",
+ 208 => "#ff8700",
+ 209 => "#ff875f",
+ 210 => "#ff8787",
+ 211 => "#ff87af",
+ 212 => "#ff87d7",
+ 213 => "#ff87ff",
+ 214 => "#ffaf00",
+ 215 => "#ffaf5f",
+ 216 => "#ffaf87",
+ 217 => "#ffafaf",
+ 218 => "#ffafd7",
+ 219 => "#ffafff",
+ 220 => "#ffd700",
+ 221 => "#ffd75f",
+ 222 => "#ffd787",
+ 223 => "#ffd7af",
+ 224 => "#ffd7d7",
+ 225 => "#ffd7ff",
+ 226 => "#ffff00",
+ 227 => "#ffff5f",
+ 228 => "#ffff87",
+ 229 => "#ffffaf",
+ 230 => "#ffffd7",
+ 231 => "#ffffff",
+ 232 => "#080808",
+ 233 => "#121212",
+ 234 => "#1c1c1c",
+ 235 => "#262626",
+ 236 => "#303030",
+ 237 => "#3a3a3a",
+ 238 => "#444444",
+ 239 => "#4e4e4e",
+ 240 => "#585858",
+ 241 => "#626262",
+ 242 => "#6c6c6c",
+ 243 => "#767676",
+ 244 => "#808080",
+ 245 => "#8a8a8a",
+ 246 => "#949494",
+ 247 => "#9e9e9e",
+ 248 => "#a8a8a8",
+ 249 => "#b2b2b2",
+ 250 => "#bcbcbc",
+ 251 => "#c6c6c6",
+ 252 => "#d0d0d0",
+ 253 => "#dadada",
+ 254 => "#e4e4e4",
+ 255 => "#eeeeee",
+ _ => "#00ff00",
+ }
+ }
+}
diff --git a/src/gemini/client.rs b/src/gemini/client.rs
index 5b44690..0658ff0 100644
--- a/src/gemini/client.rs
+++ b/src/gemini/client.rs
@@ -1,12 +1,12 @@
use std::io::{Read, Write};
-use tempfile::NamedTempFile;
-
use native_tls::TlsConnector;
use std::net::{TcpStream, ToSocketAddrs};
use std::time::Duration;
+use crate::protocols::*;
-pub fn get(url: &url::Url) -> Result<(Option<Vec<u8>>, Vec<u8>), String> {
+pub fn get_data<T: Protocol>(url: T) -> Result<(Option<Vec<u8>>, Vec<u8>), String> {
+ let url = url.get_source_url();
let host = url.host_str().unwrap();
let urlf = format!("{}:1965", host);
@@ -48,18 +48,6 @@ pub fn get(url: &url::Url) -> Result<(Option<Vec<u8>>, Vec<u8>), String> {
}
}
-pub fn download(content: Vec<u8>) {
- let path = write_tmp_file(content);
- open::that(path).unwrap();
-}
-
-fn write_tmp_file(content: Vec<u8>) -> std::path::PathBuf {
- let mut tmp_file = NamedTempFile::new().unwrap();
- tmp_file.write_all(&content).unwrap();
- let (_file, path) = tmp_file.keep().unwrap();
- path
-}
-
fn find_clrf(data: &[u8]) -> Option<usize> {
let clrf = b"\r\n";
data.windows(clrf.len()).position(|window| window == clrf)
diff --git a/src/gemini/mod.rs b/src/gemini/mod.rs
index 9d35acf..b6c0233 100644
--- a/src/gemini/mod.rs
+++ b/src/gemini/mod.rs
@@ -1,5 +1,3 @@
pub mod client;
pub mod link;
pub mod parser;
-
-struct GeminiUrl(&'static str);
diff --git a/src/gemini/parser.rs b/src/gemini/parser.rs
index 87f8c8f..af20d20 100644
--- a/src/gemini/parser.rs
+++ b/src/gemini/parser.rs
@@ -1,6 +1,8 @@
extern crate regex;
use regex::Regex;
+use crate::colors::*;
+
use std::str::FromStr;
@@ -19,7 +21,7 @@ pub struct ParseError;
const H1_REGEX: &str = r"^#\s+(.*)$";
const H2_REGEX: &str = r"^##\s+(.*)$";
const H3_REGEX: &str = r"^###\s+(.*)$";
-const LIST_ITEM_REGEX: &str = r"^\s*\*\s+(.*)$";
+const LIST_ITEM_REGEX: &str = r"^\s*\*\s+([^*]*)$";
const LINK_ITEM_REGEX: &str = r"^=>\s*(\S*)\s*(.*)?$";
impl FromStr for TextElement {
@@ -52,8 +54,7 @@ impl FromStr for TextElement {
} else if link_item_regexp.is_match(&line) {
Ok(TextElement::LinkItem(String::from(line)))
} else {
- let text_line = String::from(line);
- Ok(TextElement::Text(text_line))
+ Ok(TextElement::Text(colors::colorize(line)))
}
}
}
diff --git a/src/gopher/client.rs b/src/gopher/client.rs
index 724a892..5d2c12c 100644
--- a/src/gopher/client.rs
+++ b/src/gopher/client.rs
@@ -1,12 +1,13 @@
use std::io::{Read, Write};
-use tempfile::NamedTempFile;
-use std::net::{TcpStream};
+use std::net::TcpStream;
+use percent_encoding::percent_decode;
+use crate::Protocol;
-pub fn get(url: &url::Url) -> Result<(Option<Vec<u8>>, Vec<u8>), String> {
- let host = url.host_str().unwrap();
+pub fn get_data<T: Protocol>(url: T) -> Result<(Option<Vec<u8>>, Vec<u8>), String> {
+ let url = url.get_source_url();
+ let host = url.host_str().unwrap().to_string();
let urlf = format!("{}:70", host);
- println!("{:?}", url.path());
match TcpStream::connect(&urlf) {
Ok(mut stream) => {
@@ -16,6 +17,7 @@ pub fn get(url: &url::Url) -> Result<(Option<Vec<u8>>, Vec<u8>), String> {
} else {
url
};
+ let url = percent_decode(url.as_bytes()).decode_utf8().unwrap();
stream.write_all(url.as_bytes()).unwrap();
let mut res = vec![];
stream.read_to_end(&mut res).unwrap();
@@ -25,20 +27,3 @@ pub fn get(url: &url::Url) -> Result<(Option<Vec<u8>>, Vec<u8>), String> {
Err(e) => Err(format!("Could not connect to {}\n{}", urlf, e)),
}
}
-
-pub fn download(content: Vec<u8>) {
- let path = write_tmp_file(content);
- open::that(path).unwrap();
-}
-
-fn write_tmp_file(content: Vec<u8>) -> std::path::PathBuf {
- let mut tmp_file = NamedTempFile::new().unwrap();
- tmp_file.write_all(&content).unwrap();
- let (_file, path) = tmp_file.keep().unwrap();
- path
-}
-
-fn find_clrf(data: &[u8]) -> Option<usize> {
- let clrf = b"\r\n";
- data.windows(clrf.len()).position(|window| window == clrf)
-}
diff --git a/src/gopher/link.rs b/src/gopher/link.rs
index f9fa012..7785575 100644
--- a/src/gopher/link.rs
+++ b/src/gopher/link.rs
@@ -1,5 +1,3 @@
-extern crate regex;
-use regex::Regex;
use std::str::FromStr;
use url::Url;
@@ -20,23 +18,19 @@ impl FromStr for Link {
// Parses a &str into an instance of 'Link'
fn from_str(line: &str) -> Result<Self, Self::Err> {
- let mut els = line.split("\t");
+ let mut els = line.split('\t');
if line.starts_with('0') || line.starts_with('1') {
let label = els.next().expect("no label");
- println!("{:?}", label);
let path = els.next();
- println!("{:?}", path);
let host = els.next();
- println!("{:?}", host);
- let port = els.next();
- println!("{:?}", port);
if let Some(host) = host {
if let Some(path) = path {
let mut text = String::from(label);
text.remove(0);
- Ok(Link::Gopher(Url::parse(&format!("gopher://{}{}", host, path)).unwrap(), String::from(text)))
+ let url = Url::parse(&format!("gopher://{}{}", host, path)).unwrap();
+ Ok(Link::Gopher(url, text))
} else {
Err(ParseError)
}
@@ -45,22 +39,16 @@ impl FromStr for Link {
}
} else if line.starts_with('h') {
let label = els.next();
- println!("{:?}", label);
let url = els.next();
- println!("{:?}", url);
if let Some(label) = label {
if let Some(url) = url {
- let mut text = String::from(label);
- text.remove(0);
- let mut url = String::from(url);
- let url = url.split_off(4);
- let url = Url::parse(&url).unwrap();
- match url.scheme() {
- "gemini" => Ok(Link::Gemini(url, String::from(text))),
- "http" => Ok(Link::Http(url, String::from(text))),
- "https" => Ok(Link::Http(url, String::from(text))),
- _ => Ok(Link::Unknown(url, String::from(text))),
+ let mut label = String::from(label);
+ label.remove(0);
+ let url = String::from(url);
+ match make_link(url, label) {
+ Some(link) => Ok(link),
+ None => Err(ParseError)
}
} else {
Err(ParseError)
@@ -69,54 +57,22 @@ impl FromStr for Link {
Err(ParseError)
}
} else if line.starts_with('[') {
- let label = line;
- println!("{:?}", label);
let mut url = String::from(line);
let url = url.split_off(4);
- println!("{:?}", url);
- let url = Url::parse(&url);
+ let label = String::from(line);
- if let Ok(url) = url {
- println!("SCHEME {}", url.scheme());
- match url.scheme() {
- "gemini" => Ok(Link::Gemini(url, String::from(line))),
- "gopher" => Ok(Link::Gopher(url, String::from(line))),
- "http" => Ok(Link::Http(url, String::from(line))),
- "https" => Ok(Link::Http(url, String::from(line))),
- _ => Ok(Link::Unknown(url, String::from(line))),
- }
- } else {
- Err(ParseError)
+ match make_link(url, label) {
+ Some(link) => Ok(link),
+ None => Err(ParseError)
}
} else {
Err(ParseError)
}
-
- // match link_regexp.captures(&line) {
- // Some(caps) => {
- // let url_str = caps.get(1).map_or("", |m| m.as_str());
- // let label_str = caps.get(2).map_or("", |m| m.as_str());
-
- // let url = url_str.to_string();
- // let label = if label_str.is_empty() {
- // url_str.to_string()
- // } else {
- // label_str.to_string()
- // };
-
- // match make_link(url, label) {
- // Some(link) => Ok(link),
- // None => Err(ParseError),
- // }
- // }
- // None => Err(ParseError),
- // }
}
}
fn make_link(url: String, label: String) -> Option<Link> {
- let urlp = Url::parse(&url);
- match urlp {
+ match Url::parse(&url) {
Ok(url) => match url.scheme() {
"gemini" => Some(Link::Gemini(url, label)),
"gopher" => Some(Link::Gopher(url, label)),
diff --git a/src/gopher/parser.rs b/src/gopher/parser.rs
index 99e8d98..107deba 100644
--- a/src/gopher/parser.rs
+++ b/src/gopher/parser.rs
@@ -1,5 +1,5 @@
- use std::str::FromStr;
-
+use std::str::FromStr;
+use crate::colors::*;
pub enum TextElement {
@@ -17,26 +17,32 @@ impl FromStr for TextElement {
// Parses a &str into an instance of 'TextElement'
fn from_str(line: &str) -> Result<TextElement, ParseError> {
- if line.starts_with('0') {
- Ok(TextElement::LinkItem(String::from(line)))
- } else if line.starts_with('1') {
- Ok(TextElement::LinkItem(String::from(line)))
+ if line.starts_with('0') || line.starts_with('1') {
+ Ok(TextElement::LinkItem(colors::cleanup(line)))
} else if line.starts_with('i') {
- let mut els = line.split("\t");
+ let mut els = line.split('\t');
let text = els.next().expect("");
let mut text = String::from(text);
text.remove(0);
- Ok(TextElement::Text(String::from(text)))
+ Ok(TextElement::Text(colors::colorize(&text)))
} else if line.starts_with('h') {
- Ok(TextElement::ExternalLinkItem(String::from(line)))
+ Ok(TextElement::ExternalLinkItem(colors::cleanup(line)))
} else if line.starts_with('I') {
- let mut els = line.split("\t");
+ let mut els = line.split('\t');
let text = els.next().expect("");
let mut text = String::from(text);
text.remove(0);
- Ok(TextElement::Image(String::from(text)))
+ Ok(TextElement::Image(colors::cleanup(&text)))
+ } else if line.contains("://") {
+ if line.contains("gopher://") {
+ Ok(TextElement::LinkItem(String::from(line)))
+ } else if line.contains("http://") || line.contains("https://") {
+ Ok(TextElement::ExternalLinkItem(String::from(line)))
+ } else {
+ Ok(TextElement::Text(colors::colorize(line)))
+ }
} else {
- Ok(TextElement::Text(String::from(line)))
+ Ok(TextElement::Text(colors::colorize(line)))
}
}
}
diff --git a/src/main.rs b/src/main.rs
index fb217d1..ab56d35 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -19,7 +19,7 @@ mod absolute_url;
use absolute_url::AbsoluteUrl;
mod bookmarks;
mod client;
-use client::Client;
+mod colors;
mod gemini;
mod gopher;
mod history;
@@ -129,7 +129,7 @@ fn show_bookmarks(gui: &Arc<Gui>) {
content_view.show_all();
}
-fn visit_url<T: AbsoluteUrl + Client + Protocol>(gui: &Arc<Gui>, url: T) {
+fn visit_url<T: AbsoluteUrl + Protocol>(gui: &Arc<Gui>, url: T) {
{
if url.get_source_str() == "gemini://::bookmarks" {
show_bookmarks(&gui);
@@ -142,7 +142,7 @@ fn visit_url<T: AbsoluteUrl + Client + Protocol>(gui: &Arc<Gui>, url: T) {
let absolute_url = url.to_absolute_url();
match absolute_url {
- Ok(url2) => match url.get_data() {
+ Ok(absolute_url) => match gemini::client::get_data(url) {
Ok((meta, new_content)) => {
let meta_str = String::from_utf8_lossy(&meta.unwrap()).to_string();
if let Ok(status) = Status::from_str(&meta_str) {
@@ -150,8 +150,8 @@ fn visit_url<T: AbsoluteUrl + Client + Protocol>(gui: &Arc<Gui>, url: T) {
Status::Success(meta) => {
if meta.starts_with("text/") {
// display text files.
- history::append(url2.as_str());
- update_url_field(&gui, url2.as_str());
+ history::append(absolute_url.as_str());
+ update_url_field(&gui, absolute_url.as_str());
let content_str =
String::from_utf8_lossy(&new_content).to_string();
@@ -162,7 +162,7 @@ fn visit_url<T: AbsoluteUrl + Client + Protocol>(gui: &Arc<Gui>, url: T) {
content_view.show_all();
} else {
// download and try to open the rest.
- gemini::client::download(new_content);
+ client::download(new_content);
}
}
Status::Gone(_meta) => {
@@ -180,7 +180,7 @@ fn visit_url<T: AbsoluteUrl + Client + Protocol>(gui: &Arc<Gui>, url: T) {
);
}
Status::Input(message) => {
- input_dialog(&gui, url2, &message);
+ input_dialog(&gui, absolute_url, &message);
}
_ => (),
}
@@ -197,10 +197,10 @@ fn visit_url<T: AbsoluteUrl + Client + Protocol>(gui: &Arc<Gui>, url: T) {
} else {
let absolute_url = url.to_absolute_url();
match absolute_url {
- Ok(url) => match gopher::client::get(&url) {
+ Ok(abs_url) => match gopher::client::get_data(url) {
Ok((_meta, new_content)) => {
- history::append(url.as_str());
- update_url_field(&gui, url.as_str());
+ history::append(abs_url.as_str());
+ update_url_field(&gui, abs_url.as_str());
let content_str = String::from_utf8_lossy(&new_content).to_string();
let parsed_content = gopher::parser::parse(content_str);
@@ -269,7 +269,11 @@ fn draw_gemini_content(
}
Ok(gemini::parser::TextElement::Text(text)) => {
let mut end_iter = buffer.get_end_iter();
- buffer.insert(&mut end_iter, &format!("{}\n", text));
+ buffer.insert_markup(
+ &mut end_iter,
+ &format!("{}\n", text),
+ );
+
}
Ok(gemini::parser::TextElement::LinkItem(link_item)) => {
draw_gemini_link(&gui, link_item);
@@ -290,12 +294,11 @@ fn draw_gopher_content(
for el in content {
match el {
Ok(gopher::parser::TextElement::Text(text)) => {
- if text.contains("://") {
- draw_gopher_link(&gui, text);
- } else {
- let mut end_iter = buffer.get_end_iter();
- buffer.insert(&mut end_iter, &format!("{}\n", text));
- }
+ let mut end_iter = buffer.get_end_iter();
+ buffer.insert_markup(
+ &mut end_iter,
+ &format!("{}\n", text),
+ );
}
Ok(gopher::parser::TextElement::LinkItem(link_item)) => {
draw_gopher_link(&gui, link_item);
---
Served by Pollux Gemini Server.
-- Response ended
-- Page fetched on Sun May 19 07:23:17 2024