Initial support to Gemini protocol
parent
42b4285ae9
commit
90513d4007
|
@ -16,3 +16,7 @@ comrak = "0.24.1"
|
||||||
tera = "1.19.1"
|
tera = "1.19.1"
|
||||||
isbot = "0.1.3"
|
isbot = "0.1.3"
|
||||||
once_cell = "1.19.0"
|
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"
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
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
|
||||||
|
-----END CERTIFICATE-----
|
|
@ -0,0 +1,54 @@
|
||||||
|
-----BEGIN ENCRYPTED PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
mwFIH5TpX3CFJXdlZybNcA==
|
||||||
|
-----END ENCRYPTED PRIVATE KEY-----
|
|
@ -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<Tera> = OnceCell::const_new();
|
||||||
|
|
||||||
|
async fn render<S: AsRef<str>>(name: S, context: &tera::Context) -> Result<String, ()> {
|
||||||
|
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<dyn std::error::Error>> {
|
||||||
|
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("<pre>", "```")
|
||||||
|
.replace("</pre>", "```");
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
|
@ -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;
|
11
src/main.rs
11
src/main.rs
|
@ -1,15 +1,12 @@
|
||||||
pub mod admin;
|
|
||||||
pub mod contexts;
|
|
||||||
pub mod controllers;
|
|
||||||
pub mod misc;
|
|
||||||
pub mod models;
|
|
||||||
mod schema;
|
mod schema;
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate rocket;
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate diesel;
|
extern crate diesel;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate rocket;
|
||||||
|
|
||||||
|
use codigocomentado::*;
|
||||||
use comrak::{markdown_to_html, Options};
|
use comrak::{markdown_to_html, Options};
|
||||||
use controllers::posts;
|
use controllers::posts;
|
||||||
use dotenv::dotenv;
|
use dotenv::dotenv;
|
||||||
|
|
92
src/misc.rs
92
src/misc.rs
|
@ -20,3 +20,95 @@ pub fn gen_title() -> String {
|
||||||
let title = str::replace(title_fmt, "{}", "CódigoComentado");
|
let title = str::replace(title_fmt, "{}", "CódigoComentado");
|
||||||
title.to_string()
|
title.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn ascii_table(raw_table: String) -> String {
|
||||||
|
let raw_table = raw_table.trim();
|
||||||
|
let mut new_table: Vec<Vec<Vec<String>>> = Vec::new();
|
||||||
|
let mut max_width: Option<Vec<usize>> = None;
|
||||||
|
let mut result = String::new();
|
||||||
|
|
||||||
|
for line in raw_table.lines() {
|
||||||
|
let items: Vec<String> = line.split('|').map(|s| s.to_string()).collect();
|
||||||
|
let mut new_items: Vec<Vec<String>> = 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("<b>", "")
|
||||||
|
.replace("</b>", "")
|
||||||
|
.replace("<pre>", "")
|
||||||
|
.replace("</pre>", "")
|
||||||
|
.replace("\\", "");
|
||||||
|
let split_items: Vec<String> = item.split("<br>").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<String> = 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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
|
@ -0,0 +1,7 @@
|
||||||
|
# {{ header }}
|
||||||
|
=> / Volver al inicio
|
||||||
|
|
||||||
|
# {{ title }}
|
||||||
|
Publicado el: {{ created_at | date(format="%Y-%m-%d") }}
|
||||||
|
|
||||||
|
{{ content | striptags }}
|
Loading…
Reference in New Issue