From be34fc77374315b230d46c47da42969f7ebcb66c Mon Sep 17 00:00:00 2001 From: Penelope Gwen Date: Mon, 23 Feb 2026 00:57:01 -0800 Subject: [PATCH] re-implement cli, other tuning --- Cargo.lock | 42 ++++++++++++- Cargo.toml | 2 + src/lib/curl.rs | 139 ++++++++++++++++++++++++++++++++++++++++++ src/lib/markdowner.rs | 4 +- src/lib/sidebar.rs | 61 +++++++++++++++++- src/main.rs | 90 ++++++--------------------- 6 files changed, 260 insertions(+), 78 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 779a17f..ecbc54c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -379,6 +379,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.0", +] + [[package]] name = "chrono" version = "0.4.43" @@ -516,6 +527,15 @@ dependencies = [ "libc", ] +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -979,6 +999,7 @@ dependencies = [ "cfg-if", "libc", "r-efi", + "rand_core 0.10.0", "wasip2", "wasip3", ] @@ -2124,6 +2145,17 @@ dependencies = [ "rand_core 0.9.5", ] +[[package]] +name = "rand" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc266eb313df6c5c09c1c7b1fbe2510961e5bcd3add930c1e31f7ed9da0feff8" +dependencies = [ + "chacha20", + "getrandom 0.4.1", + "rand_core 0.10.0", +] + [[package]] name = "rand_chacha" version = "0.9.0" @@ -2149,6 +2181,12 @@ dependencies = [ "getrandom 0.3.4", ] +[[package]] +name = "rand_core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" + [[package]] name = "rav1e" version = "0.8.1" @@ -2512,7 +2550,9 @@ dependencies = [ "mdriver", "mq-markdown", "mq-view", + "rand 0.10.0", "serde", + "strip-ansi-escapes", "termimad", "text-template", "tokio", @@ -2528,7 +2568,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", - "cpufeatures", + "cpufeatures 0.2.17", "digest", ] diff --git a/Cargo.toml b/Cargo.toml index 3fc413c..11fb823 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,3 +19,5 @@ http = "1.4.0" anyhow = "1.0.101" toml-frontmatter = "0.1.0" serde = "1.0.228" +rand = "0.10.0" +strip-ansi-escapes = "0.2.1" diff --git a/src/lib/curl.rs b/src/lib/curl.rs index e69de29..4662c89 100644 --- a/src/lib/curl.rs +++ b/src/lib/curl.rs @@ -0,0 +1,139 @@ +use crate::markdowner::MarkdownModule; +use lipgloss::{Border, utils::width}; +use mq_markdown::{ListStyle, RenderOptions, UrlSurroundStyle}; +use mq_view::{RenderConfig, render_markdown_with_config}; +use termimad::minimad::lines; +use warp::Reply; + +fn box_content(content: &String, width: i32) -> String { + let lg_border = Border::new("─", "─", "│", "│", "╭", "╮", "╰", "╯", "", "", "", "", ""); + let border_style = lipgloss::Style::new() + .border(lg_border) + .width(width) + .padding(0, 2, 0, 2) + .align_horizontal(lipgloss::position::LEFT); + + border_style.render(content.as_str()) +} + +fn markdown_to_cli(content: &String) -> String { + let mut mq: mq_markdown::Markdown = content.parse().expect("could not parse markdown content!"); + let render_opts = RenderOptions { + list_style: ListStyle::Dash, + link_url_style: UrlSurroundStyle::None, + link_title_style: mq_markdown::TitleSurroundStyle::Single, + }; + mq.set_options(render_opts); + let render_conf = RenderConfig { + header_full_width_highlight: false, + }; + let _styled_mq = mq_view::render_markdown_to_string(&mq).unwrap(); + + let mut writer: Vec = Vec::new(); + + render_markdown_with_config(&mq, &mut writer, &render_conf).unwrap(); + + String::from_utf8(writer).unwrap() +} + +fn render_content(content: &String, width: i32) -> String { + let md = markdown_to_cli(content); + + box_content(&md, width) +} + +fn get_cli_header(page_contents: &Vec, width: i32) -> String { + let header = page_contents + .iter() + .find(|m| m.path.file_name().unwrap_or_default().eq("header.md")); + match header { + Some(h) => render_content(&h.content, width), + None => "".to_string(), + } +} + +fn get_cli_content(page_contents: &Vec, width: i32) -> Vec { + let content = page_contents + .iter() + .filter(|m| m.path.file_name().unwrap_or_default().ne("header.md")); + content + .map(|m| render_content(&m.content, (width / 2) - 4)) + .collect() +} + +fn column_layout(mut left_column: String, mut right_column: String) -> String { + let left_pad = left_column + .lines() + .map(|l| { + String::from_utf8(strip_ansi_escapes::strip(&l)) + .expect("could not strip ansi from text") + .chars() + .count() + }) + .max() + .unwrap_or(0); + while right_column + .lines() + .count() + .lt(&left_column.lines().count()) + { + right_column = format!("{}\n", right_column); + } + while left_column + .lines() + .count() + .lt(&right_column.lines().count()) + { + left_column = format!("{}\n", left_column); + } + println!( + "page: {}\nsidebar: {}", + right_column.lines().count(), + left_column.lines().count() + ); + let cli_columns: Vec = left_column + .lines() + .zip(right_column.lines()) + .map(|(left, right)| format!("{:width$} {}", left, right, width = left_pad)) + .collect(); + cli_columns.join("\n") +} + +pub fn curl_response( + page_contents: Vec, + sidebar_contents: Vec, + width: Option, +) -> Box { + let w = width.unwrap_or(100); + let shell_header = if width.is_none() { + "curl -s beta.pogmom.me/?width=$(tput cols);exit 0\n".to_string() + } else { + "".to_string() + }; + let shell_footer = if width.is_none() { + box_content( + &"Did you know‽\nIf you (dangerously) pipe this output to your shell, it autosizes! more interactivity is planned in the future".to_string(), + w - 4) + } else { + "".to_string() + }; + let terminal_header = get_cli_header(&page_contents, w - 4); + let terminal_sidebar = get_cli_content(&sidebar_contents, w).join("\n"); + let terminal_page = get_cli_content(&page_contents, w).join("\n"); + + let terminal_body = if w.gt(&110) { + column_layout(terminal_sidebar, terminal_page) + } else { + format!("{}\n{}\n", terminal_sidebar, terminal_page) + }; + + let cli_contents = format!( + "{}{}\n{}\n{}\n", + shell_header, terminal_header, terminal_body, shell_footer + ); + + Box::new(warp::reply::with_status( + cli_contents, + warp::http::StatusCode::OK, + )) +} diff --git a/src/lib/markdowner.rs b/src/lib/markdowner.rs index c2d7862..7a028a4 100644 --- a/src/lib/markdowner.rs +++ b/src/lib/markdowner.rs @@ -1,13 +1,13 @@ use std::path::PathBuf; -#[derive(serde::Deserialize, Debug)] +#[derive(serde::Deserialize, Debug, Clone)] pub struct FrontMatter { pub title: String, pub date_created: Option, pub date_updated: Option, pub index: Option, } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct MarkdownModule { pub path: PathBuf, pub content: String, diff --git a/src/lib/sidebar.rs b/src/lib/sidebar.rs index e227459..fc44bdd 100644 --- a/src/lib/sidebar.rs +++ b/src/lib/sidebar.rs @@ -1,3 +1,60 @@ -pub fn sidebar_content() -> Vec { - vec!["test".to_string()] +use crate::{MarkdownModule, markdowner}; +use rand::seq::IteratorRandom; +use std::{fs::ReadDir, path::PathBuf}; + +fn random_image_module(module: &MarkdownModule, directory: &str) -> MarkdownModule { + let image = random_image(directory); + let template_content = module.content.clone(); + let template = text_template::Template::from(template_content.as_str()); + let mut values = std::collections::HashMap::new(); + + values.insert( + "file_path", + image + .strip_prefix("./serve/") + .unwrap() + .to_str() + .unwrap_or_default(), + ); + values.insert( + "file_name", + image + .file_stem() + .unwrap_or_default() + .to_str() + .unwrap_or_default(), + ); + let module = MarkdownModule { + path: module.path.clone(), + content: template.fill_in(&values).to_string(), + metadata: module.metadata.clone(), + }; + module.clone() +} + +pub fn sidebar_content(target_path: &PathBuf) -> Vec { + let mut sidebar_modules = markdowner::get_markdown_modules(target_path); + let sidebar_modules: Vec = sidebar_modules + .iter() + .map(|f| match f.metadata.title.as_str() { + "rats" => random_image_module(f, "rats"), + "buttons" => random_image_module(f, "buttons"), + _ => MarkdownModule { + path: f.path.clone(), + content: f.content.clone(), + metadata: f.metadata.clone(), + }, + }) + .collect(); + sidebar_modules +} + +fn random_image(directory: &str) -> PathBuf { + let rat_image = std::fs::read_dir(PathBuf::from("./serve/assets/img/random/").join(directory)) + .expect("where the fuck are your rat pictures?") + .map(|f| f.expect("umm what is this?").path()) + .choose(&mut rand::rng()) + .expect("where is my rat"); + println!("{:#?}", rat_image); + rat_image } diff --git a/src/main.rs b/src/main.rs index e57d029..0ba8862 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,12 @@ #![warn(unused_extern_crates)] #![allow(clippy::style)] -use std::{path::PathBuf, str::FromStr}; +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; -use http::uri::Authority; -use lipgloss::Border; -use mq_markdown::{ColorTheme, ListStyle, RenderOptions, UrlSurroundStyle}; -use mq_view::{RenderConfig, render_markdown_with_config}; use warp::{Filter, filters::path::FullPath}; +#[path = "lib/curl.rs"] +mod curl; #[path = "lib/html.rs"] mod html; #[path = "lib/markdowner.rs"] @@ -15,10 +14,13 @@ mod markdowner; #[path = "lib/sidebar.rs"] mod sidebar; use crate::{ - html::html_response, - markdowner::{FrontMatter, MarkdownModule}, + curl::curl_response, html::html_response, markdowner::MarkdownModule, sidebar::sidebar_content, }; -use sidebar::sidebar_content; + +#[derive(Serialize, Deserialize)] +struct WebQuery { + width: Option, +} fn router(request_path: PathBuf) -> PathBuf { std::env::current_dir() @@ -27,8 +29,7 @@ fn router(request_path: PathBuf) -> PathBuf { .join(request_path) } -fn renderer(path: FullPath, user_agent: String) -> Box { - //let time = chrono::Local::now().to_rfc2822(); +fn renderer(path: FullPath, user_agent: String, query: WebQuery) -> Box { println!("{:?} requested by {}", path, user_agent); let request_path: PathBuf = path.as_str().strip_prefix("/").unwrap_or_default().into(); let target_path = router(request_path); @@ -45,10 +46,11 @@ fn renderer(path: FullPath, user_agent: String) -> Box { let page_contents = markdowner::get_markdown_modules(&target_path); let sidebar_dir = PathBuf::from("assets/sidebar/"); - let sidebar_contents = markdowner::get_markdown_modules(&router(sidebar_dir)); + + let sidebar_contents = sidebar_content(&router(sidebar_dir)); let response = if user_agent.starts_with("curl/") { - todo!("reimplement curl return") + curl_response(page_contents, sidebar_contents, query.width) } else { html_response( page_contents, @@ -62,63 +64,6 @@ fn renderer(path: FullPath, user_agent: String) -> Box { ) }; response - - /* - if user_agent.starts_with("curl/") { - println!("displaying curl formatting"); - let page_markdowns: Vec = page_contents - .iter() - .map(|x| { - let source = std::fs::read_to_string(x).unwrap(); - let mut mq: mq_markdown::Markdown = source.parse().unwrap(); - let render_opts = RenderOptions { - list_style: ListStyle::Dash, - link_url_style: UrlSurroundStyle::None, - link_title_style: mq_markdown::TitleSurroundStyle::Single, - }; - mq.set_options(render_opts); - let theme = ColorTheme::parse_colors("heading=34:code=31"); - mq.to_colored_string_with_theme(&theme); - let render_conf = RenderConfig { - header_full_width_highlight: false, - }; - - let _styled_mq = mq_view::render_markdown_to_string(&mq).unwrap(); - - let mut writer: Vec = Vec::new(); - - render_markdown_with_config(&mq, &mut writer, &render_conf).unwrap(); - - let render_string = String::from_utf8(writer).unwrap(); - - println!("{}", render_string); - - let _md = mdriver::StreamingParser::new().feed( - termimad::text(std::fs::read_to_string(x).unwrap_or_default().as_str()) - .to_string() - .as_str(), - ); - let border_style = lipgloss::Style::new() - .border(Border::new( - "─", "─", "│", "│", "╭", "╮", "╰", "╯", "", "", "", "", "", - )) - .width(80) - .padding(0, 2, 0, 2) - .align_horizontal(lipgloss::position::LEFT); - border_style.render(render_string.as_str()) - }) - .collect(); - - for c in &page_markdowns { - println!("{}\n", c); - } - let page_md_collected = format!("{}\n", page_markdowns.join("\n")); - - Box::new(warp::reply::with_status( - page_md_collected, - warp::http::StatusCode::OK, - )) - }*/ } #[tokio::main] @@ -127,14 +72,13 @@ async fn main() { let assets = warp::path("assets").and(warp::fs::dir("./serve/assets/")); let favicon = warp::path("favicon.ico").and(warp::fs::file("./serve/favicon.ico")); - // let no_meta = warp:: - let markdowns = warp::any() //path::end() .and(warp::path::full()) .and(warp::header("user-agent")) - .map(|path: FullPath, agent: String| renderer(path, agent)); + .and(warp::query::()) + .map(|path: FullPath, agent: String, query: WebQuery| renderer(path, agent, query)); let routes = favicon.or(assets).or(markdowns); - warp::serve(routes).run(([127, 0, 0, 1], 3030)).await; + warp::serve(routes).run(([0, 0, 0, 0], 3030)).await; }