From 90513d4007a5d37599d8f98ce142bf04eeb73379 Mon Sep 17 00:00:00 2001 From: kirbylife Date: Tue, 25 Jun 2024 20:48:36 -0600 Subject: [PATCH] Initial support to Gemini protocol --- Cargo.toml | 4 ++ cert.pem | 31 +++++++++++ key.pem | 54 +++++++++++++++++++ src/bin/gemini.rs | 86 ++++++++++++++++++++++++++++++ src/lib.rs | 12 +++++ src/main.rs | 11 ++-- src/misc.rs | 92 +++++++++++++++++++++++++++++++++ templates/gemini/index.gmi.tera | 24 +++++++++ templates/gemini/post.gmi.tera | 7 +++ 9 files changed, 314 insertions(+), 7 deletions(-) create mode 100644 cert.pem create mode 100644 key.pem create mode 100644 src/bin/gemini.rs create mode 100644 src/lib.rs create mode 100644 templates/gemini/index.gmi.tera create mode 100644 templates/gemini/post.gmi.tera diff --git a/Cargo.toml b/Cargo.toml index 7f22b92..c7fac3c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,7 @@ comrak = "0.24.1" tera = "1.19.1" isbot = "0.1.3" once_cell = "1.19.0" +windmark = { version = "0.3.10", features = ["response-macros", "logger"] } +tokio = { version = "1.26.0", features = ["full"] } +md2gemtext = "0.1.0" +glob = "0.3.1" diff --git a/cert.pem b/cert.pem new file mode 100644 index 0000000..4364f46 --- /dev/null +++ b/cert.pem @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFazCCA1OgAwIBAgIUfrrLPPEvujX8T/aKVSwJKr1E00gwDQYJKoZIhvcNAQEL +BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDA2MjUwMTQyNThaFw0yNDA3 +MjUwMTQyNThaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw +HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIiMA0GCSqGSIb3DQEB +AQUAA4ICDwAwggIKAoICAQDYa/XTMXG6dG+LBlQMvc9qk6YG0DfwW//na1DYdS5e +XA2jjhPNJ/Fzro51D8zctUuN6RoiZS1w53REBs+3pSdX0Orfu3grdwSXbGz+pkJ1 +pqaaseSKH7xp0bYFefWG8mhwdOxSA5h/O2Y62YJ1/2UGNmM6zyJo99Be05HnAc7c +RK6LcS1DtzsNQ/DDNmvQvHEx88AuF5F99JqzYoioaavDzzzIcRvXl8gvykPw74+i +T4jZ/hKS53y2v17ArQRz/eQO5EiDk2b8fj6BUuTUkjvOyHY31FjVlicMkOM0l2tE +cE/rj1HIONkzjBwOlVBobNovyVZTTXHa+NN83QbEy9rbo8na3IonQiUARzTabfR8 +ZuHOLcgnH4JZI20IeLIsvcHBH762UXbFBS9vHVvY49MnclPudNQV4PhAqBGkgzos +4jLec7jxqfNYQ6IHZDKnJcFPGMgQwxXpuk3x6jGnsDbb39E+Dqfw+645GpsSxBiz +zRS3A+q8I3jZaIiUbJC5YY0cTMUUV16QjfvUnln/JWZAc9IwDkr/AA6oeVZztD5w +PQ6RSVLPKhYCFpHTtYY2dyuGZvLqmfjuTWl1DLI8Aehmr603r3Hvmry00UeDHmDB +AzLtFX9UoTpfRwQFSZUfZHD7NrHOg4C4PGYyYy5YOImi0LTfyv5gREu98Oj/FxoG +6QIDAQABo1MwUTAdBgNVHQ4EFgQUcFc2bBA7ElhkULl2k/1P7EUas6owHwYDVR0j +BBgwFoAUcFc2bBA7ElhkULl2k/1P7EUas6owDwYDVR0TAQH/BAUwAwEB/zANBgkq +hkiG9w0BAQsFAAOCAgEAbaaC55Ru2kbMmzCNvbxU2VndbOUsr+mrEnIFPWnvWQ2q +1h7k5jAKEziTl+UvnBMkHYa9b8idhneevxp53jH8rfmxBmWSO4cIP424xAE1ThhR +S72sXELRwLUuplWfwdYlyPzt6QDv/GH5pA8qbrzFBmmE7p2ECj3xa5M+yQI6pSl7 +KtUY/0nB4C69xIEJadukwSQFZUxARlhT7bXUDJAZnfhzmWZBij/iPtNQeAjy1sDm +rVzAQGCUAltIDa1CMhOyrmpFMbSNKsbX7tiWHhEKoDe71QmxE1ccuappDhWUusp7 +mISFunE7H258lrXl6VHvkBVwvUlppWOEtP+TxoxZuxpZplV0HQUArEAAtC0+JCkG +up0nrNElgu+7qgX3Qm5ufU/p1lqEozPt3mvUV5CsZqKm2CjmtzrFyxEHC/UpnEhX +dzxOTZ6ZBjoBwwBsG2PvAO+wu438EeBcfwzKlP20cX1s2Bq/CjzZNlxAeK8iCvf2 +TbfuyojwIcU7ceLIXxZjBt5UmaR2w5nYCRxKWpNHxGoPGZFfSET2XbvZy4WET6bv +a7WNXgSiE866qMrw0u57tiI1Zmp16sV5oovdSdpZjjzHNO7EtmryVANIkmNvQgLl +WyvFTgr5Q/GISBS4cayyiIwMn+oMfKaSW/kVspT8+QQi+crfr/Qv7s7If0yTKuI= +-----END CERTIFICATE----- diff --git a/key.pem b/key.pem new file mode 100644 index 0000000..6a028ac --- /dev/null +++ b/key.pem @@ -0,0 +1,54 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +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 +mwFIH5TpX3CFJXdlZybNcA== +-----END ENCRYPTED PRIVATE KEY----- diff --git a/src/bin/gemini.rs b/src/bin/gemini.rs new file mode 100644 index 0000000..b0d3f23 --- /dev/null +++ b/src/bin/gemini.rs @@ -0,0 +1,86 @@ +use codigocomentado::*; + +use controllers::posts; +use dotenv::dotenv; +use misc::{gen_title, replace_tables}; +use tera::Tera; +use tokio::sync::OnceCell; + +static TERA: OnceCell = OnceCell::const_new(); + +async fn render>(name: S, context: &tera::Context) -> Result { + let tera = TERA + .get_or_init(|| async { + let mut tera = Tera::default(); + for path in glob::glob("templates/gemini/*").unwrap().flatten() { + let raw_path = path.clone(); + let filename = raw_path + .file_name() + .unwrap() // This should be safe (? + .to_str() + .unwrap() // ._.? + .split('.') + .next(); + tera.add_template_file(path, filename).unwrap() + } + tera + }) + .await; + tera.render(name.as_ref(), context).map_err(|_| ()) +} + +#[windmark::main] +async fn main() -> Result<(), Box> { + dotenv().ok(); + + windmark::router::Router::new() + .set_private_key_file("key.pem") + .set_certificate_file("cert.pem") + .set_languages(["es"]) + .enable_default_logger(true) + .set_fix_path(true) + .mount("/", move |_| { + let (posts, _) = posts::get_posts(None); + let mut context = tera::Context::new(); + + context.insert("title", &gen_title()); + context.insert("posts", &posts); + + async move { + windmark::response::Response::success(render("index", &context).await.unwrap()) + } + }) + .mount("/post/:title", |request| { + let title_splited: Vec<&str> = request + .parameters + .get("title") + // This should be safe because its requires the parameter to enter to the route + .unwrap() + .split('-') + .collect(); + let id = title_splited.last().unwrap().parse().unwrap_or(-1); + async move { + let result = match posts::get_post(id) { + Ok(post) => { + let mut context = tera::Context::new(); + let content = replace_tables(post.content) + .replace("
", "```")
+                            .replace("
", "```"); + + context.insert("header", &gen_title()); + context.insert("title", &post.title); + context.insert("created_at", &post.created_at); + context.insert("content", &content); + render("post", &context).await.unwrap() + } + Err(_) => "No se encontró ningún Post :c".into(), + }; + windmark::response::Response::success(result) + } + }) + .set_error_handler(|_| { + windmark::response::Response::permanent_failure("This route does not exist!") + }) + .run() + .await +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..fbd3a67 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,12 @@ +pub mod admin; +pub mod contexts; +pub mod controllers; +pub mod misc; +pub mod models; +mod schema; + +#[macro_use] +extern crate diesel; + +#[macro_use] +extern crate rocket; diff --git a/src/main.rs b/src/main.rs index c177675..3a973e2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,12 @@ -pub mod admin; -pub mod contexts; -pub mod controllers; -pub mod misc; -pub mod models; mod schema; -#[macro_use] -extern crate rocket; #[macro_use] extern crate diesel; +#[macro_use] +extern crate rocket; + +use codigocomentado::*; use comrak::{markdown_to_html, Options}; use controllers::posts; use dotenv::dotenv; diff --git a/src/misc.rs b/src/misc.rs index 82db982..914294a 100644 --- a/src/misc.rs +++ b/src/misc.rs @@ -20,3 +20,95 @@ pub fn gen_title() -> String { let title = str::replace(title_fmt, "{}", "CódigoComentado"); title.to_string() } + +pub fn ascii_table(raw_table: String) -> String { + let raw_table = raw_table.trim(); + let mut new_table: Vec>> = Vec::new(); + let mut max_width: Option> = None; + let mut result = String::new(); + + for line in raw_table.lines() { + let items: Vec = line.split('|').map(|s| s.to_string()).collect(); + let mut new_items: Vec> = vec![Vec::new(); items.len()]; + + if max_width.is_none() { + max_width = Some(vec![0; items.len()]); + } + + for (n, item) in items.iter().enumerate() { + let item = item + .replace("", "") + .replace("", "") + .replace("
", "")
+                .replace("
", "") + .replace("\\", ""); + let split_items: Vec = item.split("
").map(|s| s.to_string()).collect(); + new_items[n].clone_from(&split_items); + let max_local = split_items.iter().map(|s| s.len()).max().unwrap_or(0); + if let Some(ref mut mw) = max_width { + mw[n] = std::cmp::max(mw[n], max_local); + } + } + + new_table.push(new_items); + } + + for row in new_table { + let max_height = row.iter().map(|col| col.len()).max().unwrap_or(0); + for index in 0..(row.len() * max_height) { + let index_subrow = index / row.len(); + let index_col = index % row.len(); + let text = if let Some(col) = row.get(index_col) { + if let Some(&ref text) = col.get(index_subrow) { + text + } else { + &String::from("") + } + } else { + &String::from("") + }; + let text = text.trim_end(); + if let Some(ref mw) = max_width { + result.push_str(&format!( + "{:width$}{}", + text, + if index_col < row.len() - 1 { "|" } else { "\n" }, + width = mw[index_col] + )); + } + } + } + + format!("```\n{}\n```\n", result) +} + +pub fn replace_tables(text: String) -> String { + let mut result = String::new(); + let mut table: Option = None; + let mut is_code = false; + + for line in text.lines() { + if line.starts_with('|') & !is_code { + if table.is_none() { + table = Some(String::new()); + } + if let Some(ref mut tab) = table { + tab.push_str(&format!("{}\n", line)); + } + } else { + let temp = match table { + Some(ref tab) => { + println!("raw:\n{}", tab); + let new_table = ascii_table(tab.to_string()); + println!("formated:\n{}", new_table); + table = None; + new_table + } + None => line.to_string(), + }; + result.push_str(&temp); + result.push('\n'); + } + } + result +} diff --git a/templates/gemini/index.gmi.tera b/templates/gemini/index.gmi.tera new file mode 100644 index 0000000..1dde6b8 --- /dev/null +++ b/templates/gemini/index.gmi.tera @@ -0,0 +1,24 @@ +# {{ title }} + +¡Hola!, acabas de aterrizar en mi cápsula, puedes tomar asiento donde gustes. +Me presento, en el mundo de los internets decidí que mi apodo sería "kirbylife" y de vez en cuando me gusta escribir sobre cosas de programación y todo lo relacionado con las computadoras en general, y para hacerlo me creé un blog en la web de los hipertextos y por fin decidí a traerla a este otro espacio de internet, espero les guste lo que leen y, con un poco de suerte, le encuentren utilidad. + +## Artículos +{% for post in posts %} +=> /post/{{ post.title | slugify }}-{{ post.id }} {{ post.title }} +{{ post.content | split(pat=" ") | slice(end=30) | join(sep=" ") | split(pat="#") | slice(end=1) | join(sep="") | replace(from="\n", to="") | trim }}... +{% endfor %} + +## Info +Este blog está escrito en el lenguaje de programación Rust, utilizando el framework Windmark, con el editor Emacs y cuenta con feeds RSS/Atom. +=> https://blog.kirbylife.dev/feed.xml RSS +=> https://blog.kirbylife.dev/atom.xml Atom + +## Contacto +En caso de que quieran contactarme por la razón que sea, pueden hacerlo por cualquiera de estos medios: +Correo electrónico +* hola(at)kirbylife.dev +Mastodon +* @kirbylife@mstdn.mx +Telegram +* @kirbylife \ No newline at end of file diff --git a/templates/gemini/post.gmi.tera b/templates/gemini/post.gmi.tera new file mode 100644 index 0000000..b6829bd --- /dev/null +++ b/templates/gemini/post.gmi.tera @@ -0,0 +1,7 @@ +# {{ header }} +=> / Volver al inicio + +# {{ title }} +Publicado el: {{ created_at | date(format="%Y-%m-%d") }} + +{{ content | striptags }}