draft link parser

This commit is contained in:
yggverse 2024-09-27 00:07:48 +03:00
parent 454feaeba5
commit 860f76ce49
7 changed files with 147 additions and 13 deletions

View File

@ -4,6 +4,7 @@ mod text;
use text::Text; use text::Text;
use gtk::{ use gtk::{
glib::Uri,
prelude::{BoxExt, WidgetExt}, prelude::{BoxExt, WidgetExt},
Box, Orientation, Box, Orientation,
}; };
@ -29,7 +30,7 @@ impl Content {
} }
// Actions // Actions
pub fn reset(&self, mime: Mime, data: &str) { pub fn reset(&self, mime: Mime, base: &Uri, data: &str) {
// Cleanup // Cleanup
while let Some(child) = self.widget.last_child() { while let Some(child) = self.widget.last_child() {
self.widget.remove(&child) self.widget.remove(&child)
@ -38,7 +39,7 @@ impl Content {
// Compose // Compose
match mime { match mime {
Mime::TextGemini => { Mime::TextGemini => {
self.widget.append(Text::gemini(data).widget()); self.widget.append(Text::gemini(data, base).widget());
} }
Mime::TextPlain => { Mime::TextPlain => {
todo!() todo!()

View File

@ -2,17 +2,21 @@ mod reader;
use reader::Reader; use reader::Reader;
use gtk::Viewport; use gtk::{
glib::{GString, Uri},
Viewport,
};
pub struct Gemini { pub struct Gemini {
reader: Reader,
widget: Viewport, widget: Viewport,
} }
impl Gemini { impl Gemini {
// Construct // Construct
pub fn new(gemtext: &str) -> Self { pub fn new(gemtext: &str, base: &Uri) -> Self {
// Init components // Init components
let reader = Reader::new(gemtext); let reader = Reader::new(gemtext, base);
// Init widget // Init widget
let widget = Viewport::builder().scroll_to_focus(false).build(); let widget = Viewport::builder().scroll_to_focus(false).build();
@ -20,10 +24,14 @@ impl Gemini {
widget.set_child(Some(reader.widget())); widget.set_child(Some(reader.widget()));
// Result // Result
Self { widget } Self { reader, widget }
} }
// Getters // Getters
pub fn reader_title(&self) -> &Option<GString> {
&self.reader.title()
}
pub fn widget(&self) -> &Viewport { pub fn widget(&self) -> &Viewport {
&self.widget &self.widget
} }

View File

@ -1,10 +1,11 @@
mod parser; mod parser;
use parser::header::Header; use parser::header::Header;
use parser::link::Link;
use parser::plain::Plain; use parser::plain::Plain;
use gtk::{ use gtk::{
glib::GString, glib::{GString, Uri},
prelude::{StyleContextExt, WidgetExt}, prelude::{StyleContextExt, WidgetExt},
Align, CssProvider, Label, STYLE_PROVIDER_PRIORITY_APPLICATION, Align, CssProvider, Label, STYLE_PROVIDER_PRIORITY_APPLICATION,
}; };
@ -17,7 +18,7 @@ pub struct Reader {
impl Reader { impl Reader {
// Construct // Construct
pub fn new(gemtext: &str) -> Self { pub fn new(gemtext: &str, base: &Uri) -> Self {
// Init title // Init title
let mut title = None; let mut title = None;
@ -38,7 +39,13 @@ impl Reader {
continue; continue;
} }
// Is link @TODO // Is link
if let Some(link) = Link::from(line, base) {
// Format
markup.push_str(link.markup());
continue;
}
// Nothing match, escape string just // Nothing match, escape string just
markup.push_str(Plain::from(line).markup()) markup.push_str(Plain::from(line).markup())

View File

@ -0,0 +1,117 @@
use gtk::glib::{
markup_escape_text, GString, Regex, RegexCompileFlags, RegexMatchFlags, Uri, UriFlags,
};
pub struct Link {
alt: Option<GString>, // [optional] alternative text
date: Option<GString>, // [optional] date @TODO store in UnixTime?
external: bool, // external link indicator
link: GString, // original link, wanted for title tooltip
markup: GString, // pango markup with escaped special chars
uri: Uri, // parsed link object (currently not in use)
}
impl Link {
// Link structure parser
// line - gemtext subject to parse
// base - Uri object, required for:
// 1. relative to absolute address conversion
// 2. external links indication
// returns new Link struct or None
pub fn from(line: &str, base: &Uri) -> Option<Link> {
// Init struct members
let alt: Option<GString> = None;
let date: Option<GString> = None;
let external: bool;
let link: GString;
let markup: GString;
let uri: Uri;
// Parse line
let parsed = Regex::split_simple(
r"^=>\s*([^\s]+)(\s(\d{4}-\d{2}-\d{2}))?(\s(.+))?$",
line,
RegexCompileFlags::DEFAULT,
RegexMatchFlags::DEFAULT,
);
// Address
match parsed.get(1) {
Some(address) => {
// Define original link value (used in titles or when alt is empty)
link = GString::from(address.as_str());
// Links in document usually relative, make them absolute to base given
match Uri::resolve_relative(Some(&base.to_str()), address.as_str(), UriFlags::NONE)
{
Ok(resolved) => {
// Make URI parsed as always valid (no idea why does lib operate strings, not objects)
match Uri::parse(&resolved, UriFlags::NONE) {
Ok(object) => {
// Set external status
external = object.host() == base.host();
// Set struct URI
uri = object;
}
Err(_) => return None,
}
}
Err(_) => return None,
}
}
None => return None,
}
// Date
if let Some(date) = parsed.get(2) {
// date = date.as_str();
}
// Alt
if let Some(alt) = parsed.get(3) {
// alt = alt.as_str();
}
// Markup
markup = GString::from(format!(
"<a href=\"{}\" title=\"{}\"><span underline=\"none\">{}</span></a>\n",
markup_escape_text(&uri.to_str()), // use resolved address for href
markup_escape_text(&link), // show original address for title
markup_escape_text(&link), // @TODO
));
Some(Self {
alt,
date,
external,
link,
markup,
uri,
})
}
// Getters
pub fn alt(&self) -> &Option<GString> {
&self.alt
}
pub fn date(&self) -> &Option<GString> {
&self.date
}
pub fn external(&self) -> &bool {
&self.external
}
pub fn link(&self) -> &GString {
&self.link
}
pub fn markup(&self) -> &GString {
&self.markup
}
pub fn uri(&self) -> &Uri {
&self.uri
}
}

View File

@ -1,2 +1,3 @@
pub mod header; pub mod header;
pub mod link;
pub mod plain; pub mod plain;

View File

@ -2,7 +2,7 @@ mod gemini;
use gemini::Gemini; use gemini::Gemini;
use gtk::ScrolledWindow; use gtk::{glib::Uri, ScrolledWindow};
pub struct Text { pub struct Text {
widget: ScrolledWindow, widget: ScrolledWindow,
@ -10,9 +10,9 @@ pub struct Text {
impl Text { impl Text {
// Construct // Construct
pub fn gemini(gemtext: &str) -> Self { pub fn gemini(gemtext: &str, base: &Uri) -> Self {
// Init components // Init components
let gemini = Gemini::new(gemtext); let gemini = Gemini::new(gemtext, base);
// Init widget // Init widget
let widget = ScrolledWindow::builder().build(); let widget = ScrolledWindow::builder().build();

View File

@ -160,7 +160,7 @@ impl Page {
meta.borrow_mut().mime = Mime::TextGemini; meta.borrow_mut().mime = Mime::TextGemini;
// Select widget // Select widget
match parts.get(4) { match parts.get(4) {
Some(source) => content.reset(content::Mime::TextGemini, source), Some(source) => content.reset(content::Mime::TextGemini, &uri, &source),
None => todo!(), None => todo!(),
} }
}, },