-- Leo's gemini proxy

-- Connecting to typed-hole.org:1965...

-- Connected

-- Sending request

-- Meta line: 20 text/gemini

commit 5e2c8135d82dd23b09e7dfc581736ab3163e31bf

Author: Julien Blanchard <julien@sideburns.eu>

Date: Mon Dec 23 12:28:21 2019 +0100


Better parsing


diff --git a/src/gui.rs b/src/gui.rs

index bbda792..4685127 100644

--- a/src/gui.rs

+++ b/src/gui.rs

@@ -52,10 +52,6 @@ impl Gui {

// self.result.set_text(&format!("{}", state.value));

// }


- pub fn window(&self) -> &ApplicationWindow {

- &self.window

- }

-

pub fn url_bar(&self) -> &Entry {

&self.url_bar

}

diff --git a/src/link.rs b/src/link.rs

index fd1ac68..0b3ecd6 100644

--- a/src/link.rs

+++ b/src/link.rs

@@ -1,5 +1,4 @@

extern crate regex;

-use json::JsonValue;

use regex::Regex;

use std::str::FromStr;

use url::Url;

@@ -61,7 +60,3 @@ fn make_link(url: String, label: String) -> Option<Link> {

_ => None,

}

}

-

-pub fn is_gemini(line: &JsonValue) -> bool {

- line["type"] == "gemini"

-}

diff --git a/src/main.rs b/src/main.rs

index e7eb719..5cfaef3 100644

--- a/src/main.rs

+++ b/src/main.rs

@@ -1,19 +1,15 @@

-#![allow(unused_variables, unused_mut)]

-

extern crate gio;

extern crate glib;

extern crate gtk;

extern crate lazy_static;


+use std::str::FromStr;

use std::sync::Arc;


use glib::clone;

use gtk::prelude::*;

-use gtk::{TextBuffer, TextBufferExt};

-

-extern crate regex;

-use regex::Regex;

+use gtk::TextBuffer;


mod gui;

use gui::Gui;

@@ -21,14 +17,10 @@ mod absolute;

mod content;

mod history;

mod link;

+use link::Link;

+mod parser;

mod tags;

-

-const LINK_REGEX: &str = r"^=>\s*(\S*)\s*(.*)?$";

-const H1_REGEX: &str = r"^#\s+(.*)$";

-const H2_REGEX: &str = r"^##\s+(.*)$";

-const H3_REGEX: &str = r"^###\s+(.*)$";

-const UL_REGEX: &str = r"^\s*\*\s+(.*)$";

-

+use parser::{ParseError, TextElement, TextElement::*};


fn main() {

// Start up the GTK3 subsystem.

@@ -42,16 +34,16 @@ fn main() {

{

let button = gui.back_button();

let gui = gui.clone();

- button.connect_clicked(clone!(@weak content_view => move |_| {

+ button.connect_clicked(move |_| {

go_back(&gui);

- }));

+ });

}


// Bind URL bar

{

let gui2 = gui.clone();

let url_bar = gui.url_bar();

- url_bar.connect_activate(clone!(@weak content_view => move |bar| {

+ url_bar.connect_activate(move |bar| {

let url = bar.get_text().expect("get_text failed").to_string();

let full_url = if url.starts_with("gemini://") {

url

@@ -59,8 +51,8 @@ fn main() {

format!("gemini://{}", url)

};


- let new_content = visit_url(&gui2, full_url);

- }));

+ visit_url(&gui2, full_url);

+ });

}


// Create Pango tags

@@ -93,9 +85,10 @@ fn visit_url(gui: &Arc<Gui>, url: String) {

update_url_field(&gui, url.as_str());

let content_str = String::from_utf8_lossy(&new_content).to_string();


+ let parsed_content = parser::parse(content_str);

clear_buffer(&content_view);

+ draw_content(&gui, parsed_content);


- parse_gemini(&gui, content_str);

content_view.show_all();

}

Err(_) => {

@@ -117,92 +110,156 @@ fn visit_url(gui: &Arc<Gui>, url: String) {

}

}


-fn parse_gemini(gui: &Arc<Gui>, content: String) -> TextBuffer {

- let link_regexp = Regex::new(LINK_REGEX).unwrap();

- let h1_regexp = Regex::new(H1_REGEX).unwrap();

- let h2_regexp = Regex::new(H2_REGEX).unwrap();

- let h3_regexp = Regex::new(H3_REGEX).unwrap();

- let ul_regexp = Regex::new(UL_REGEX).unwrap();

+fn draw_content(gui: &Arc<Gui>, content: Vec<Result<TextElement, ParseError>>) -> TextBuffer {

let content_view = gui.content_view();

let buffer = content_view.get_buffer().unwrap();

- let mut i = 0;


- for line in content.lines() {

- if link_regexp.is_match(line) {

- let caps = link_regexp.captures(&line).unwrap();

- let dest = String::from(caps.get(1).map_or("", |m| m.as_str()));

- let label = String::from(caps.get(2).map_or("", |m| m.as_str()));

+ for el in content {

+ match el {

+ Ok(H1(header)) => {

+ let mut end_iter = buffer.get_end_iter();

+ buffer.insert_markup(

+ &mut end_iter,

+ &format!(

+ "<span foreground=\"#9932CC\" size=\"x-large\">{}</span>\n",

+ header

+ ),

+ );

+ }

+ Ok(H2(header)) => {

+ let mut end_iter = buffer.get_end_iter();

+ buffer.insert_markup(

+ &mut end_iter,

+ &format!(

+ "<span foreground=\"#FF1493\" size=\"large\">{}</span>\n",

+ header

+ ),

+ );

+ }

+ Ok(H3(header)) => {

+ let mut end_iter = buffer.get_end_iter();

+ buffer.insert_markup(

+ &mut end_iter,

+ &format!(

+ "<span foreground=\"#87CEFA\" size=\"medium\">{}</span>\n",

+ header

+ ),

+ );

+ }

+ Ok(ListItem(item)) => {

+ let mut end_iter = buffer.get_end_iter();

+ buffer.insert_markup(

+ &mut end_iter,

+ &format!("<span foreground=\"green\">■ {}</span>\n", item),

+ );

+ }

+ Ok(Text(text)) => {

+ let mut end_iter = buffer.get_end_iter();

+ buffer.insert(&mut end_iter, &format!("{}\n", text));

+ }

+ Ok(LinkItem(link_item)) => {

+ draw_link(&gui, link_item);

+ }

+ Err(_) => println!("Something failed."),

+ }

+ }

+ buffer

+}


+fn draw_link(gui: &Arc<Gui>, link_item: String) {

+ let content_view = gui.content_view();

+ let buffer = content_view.get_buffer().unwrap();

+

+ match Link::from_str(&link_item) {

+ Ok(Link::Http(url, label)) => {

let button_label = if label.is_empty() {

- dest.clone()

+ url.clone().to_string()

} else {

label

};

+ let www_label = format!("{} [WWW]", button_label);


- let button = gtk::Button::new_with_label(&button_label);

- button.set_tooltip_text(Some(&dest));

+ let button = gtk::Button::new_with_label(&www_label);

+ button.set_tooltip_text(Some(&url.to_string()));


- button.connect_clicked(clone!(@weak gui => move |button| {

- let new_url = absolute::make(&dest.clone()).unwrap().to_string();

+ button.connect_clicked(clone!(@weak gui => move |_| {

+ let new_url = absolute::make(&url.clone().to_string()).unwrap().to_string();

visit_url(&gui, new_url);

}));


- let mut start_iter = buffer.get_iter_at_line(i);

+ let mut start_iter = buffer.get_end_iter();

let anchor = buffer.create_child_anchor(&mut start_iter).unwrap();

content_view.add_child_at_anchor(&button, &anchor);

let mut end_iter = buffer.get_end_iter();

buffer.insert(&mut end_iter, "\n");

- } else if h1_regexp.is_match(line) {

- let caps = h1_regexp.captures(&line).unwrap();

- let header = caps.get(1).map_or("", |m| m.as_str());

- let mut end_iter = buffer.get_end_iter();

- buffer.insert_markup(

- &mut end_iter,

- &format!(

- "<span foreground=\"#9932CC\" size=\"x-large\">{}</span>\n",

- header

- ),

- );

- } else if h2_regexp.is_match(line) {

- let caps = h2_regexp.captures(&line).unwrap();

- let header = caps.get(1).map_or("", |m| m.as_str());

- let mut end_iter = buffer.get_end_iter();

- buffer.insert_markup(

- &mut end_iter,

- &format!(

- "<span foreground=\"#FF1493\" size=\"large\">{}</span>\n",

- header

- ),

- );

- } else if h3_regexp.is_match(line) {

- let caps = h3_regexp.captures(&line).unwrap();

- let header = caps.get(1).map_or("", |m| m.as_str());

- let mut end_iter = buffer.get_end_iter();

- buffer.insert_markup(

- &mut end_iter,

- &format!(

- "<span foreground=\"#87CEFA\" size=\"medium\">{}</span>\n",

- header

- ),

- );

- } else if ul_regexp.is_match(line) {

- let caps = ul_regexp.captures(&line).unwrap();

- let header = caps.get(1).map_or("", |m| m.as_str());

+ }

+ Ok(Link::Gopher(url, label)) => {

+ let button_label = if label.is_empty() {

+ url.clone().to_string()

+ } else {

+ label

+ };

+ let gopher_label = format!("{} [Gopher]", button_label);

+

+ let button = gtk::Button::new_with_label(&gopher_label);

+ button.set_tooltip_text(Some(&url.to_string()));

+

+ button.connect_clicked(clone!(@weak gui => move |_| {

+ let new_url = absolute::make(&url.clone().to_string()).unwrap().to_string();

+ visit_url(&gui, new_url);

+ }));

+

+ let mut start_iter = buffer.get_end_iter();

+ let anchor = buffer.create_child_anchor(&mut start_iter).unwrap();

+ content_view.add_child_at_anchor(&button, &anchor);

let mut end_iter = buffer.get_end_iter();

- buffer.insert_markup(

- &mut end_iter,

- &format!("<span foreground=\"green\">■ {}</span>\n", header),

- );

- } else if line.is_empty() {

+ buffer.insert(&mut end_iter, "\n");

+ }

+ Ok(Link::Gemini(url, label)) => {

+ let button_label = if label.is_empty() {

+ url.clone().to_string()

+ } else {

+ label

+ };

+

+ let button = gtk::Button::new_with_label(&button_label);

+ button.set_tooltip_text(Some(&url.to_string()));

+

+ button.connect_clicked(clone!(@weak gui => move |_| {

+ let new_url = absolute::make(&url.clone().to_string()).unwrap().to_string();

+ visit_url(&gui, new_url);

+ }));

+

+ let mut start_iter = buffer.get_end_iter();

+ let anchor = buffer.create_child_anchor(&mut start_iter).unwrap();

+ content_view.add_child_at_anchor(&button, &anchor);

let mut end_iter = buffer.get_end_iter();

buffer.insert(&mut end_iter, "\n");

- } else {

+ }

+ Ok(Link::Relative(url, label)) => {

+ let button_label = if label.is_empty() {

+ url.clone().to_string()

+ } else {

+ label

+ };

+

+ let button = gtk::Button::new_with_label(&button_label);

+ button.set_tooltip_text(Some(&url.to_string()));

+

+ button.connect_clicked(clone!(@weak gui => move |_| {

+ let new_url = absolute::make(&url.clone().to_string()).unwrap().to_string();

+ visit_url(&gui, new_url);

+ }));

+

+ let mut start_iter = buffer.get_end_iter();

+ let anchor = buffer.create_child_anchor(&mut start_iter).unwrap();

+ content_view.add_child_at_anchor(&button, &anchor);

let mut end_iter = buffer.get_end_iter();

- buffer.insert(&mut end_iter, &format!("{}\n", line));

+ buffer.insert(&mut end_iter, "\n");

}

- i += 1;

+ Ok(Link::Unknown(_, _)) => (),

+ Err(_) => (),

}

- buffer

}


fn clear_buffer(view: &gtk::TextView) {

diff --git a/src/parser.rs b/src/parser.rs

new file mode 100644

index 0000000..87f8c8f

--- /dev/null

+++ b/src/parser.rs

@@ -0,0 +1,68 @@

+extern crate regex;

+use regex::Regex;

+

+use std::str::FromStr;

+

+#[derive(Debug)]

+pub enum TextElement {

+ H1(String),

+ H2(String),

+ H3(String),

+ ListItem(String),

+ LinkItem(String),

+ Text(String),

+}

+

+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]

+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 LINK_ITEM_REGEX: &str = r"^=>\s*(\S*)\s*(.*)?$";

+

+impl FromStr for TextElement {

+ type Err = ParseError;

+

+ // Parses a &str into an instance of 'TextElement'

+ fn from_str(line: &str) -> Result<TextElement, ParseError> {

+ let h1_regexp = Regex::new(H1_REGEX).unwrap();

+ let h2_regexp = Regex::new(H2_REGEX).unwrap();

+ let h3_regexp = Regex::new(H3_REGEX).unwrap();

+ let list_item_regexp = Regex::new(LIST_ITEM_REGEX).unwrap();

+ let link_item_regexp = Regex::new(LINK_ITEM_REGEX).unwrap();

+

+ if h1_regexp.is_match(&line) {

+ let caps = h1_regexp.captures(&line).unwrap();

+ let header = caps.get(1).map_or("", |m| m.as_str());

+ Ok(TextElement::H1(String::from(header)))

+ } else if h2_regexp.is_match(&line) {

+ let caps = h2_regexp.captures(&line).unwrap();

+ let header = caps.get(1).map_or("", |m| m.as_str());

+ Ok(TextElement::H2(String::from(header)))

+ } else if h3_regexp.is_match(&line) {

+ let caps = h3_regexp.captures(&line).unwrap();

+ let header = caps.get(1).map_or("", |m| m.as_str());

+ Ok(TextElement::H3(String::from(header)))

+ } else if list_item_regexp.is_match(&line) {

+ let caps = list_item_regexp.captures(&line).unwrap();

+ let header = caps.get(1).map_or("", |m| m.as_str());

+ Ok(TextElement::ListItem(String::from(header)))

+ } 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))

+ }

+ }

+}

+

+pub fn parse(content: String) -> Vec<Result<TextElement, ParseError>> {

+ let mut parsed = Vec::new();

+

+ for line in content.lines() {

+ parsed.push(TextElement::from_str(line));

+ }

+ parsed

+}



---

Served by Pollux Gemini Server.

-- Response ended

-- Page fetched on Sun May 19 05:03:24 2024