Files
mharkup/src/main.rs
2026-03-24 23:19:02 +01:00

148 lines
5.9 KiB
Rust

//! # mharkup
//!
//! another markup language
//!
//! ## sections
//!
//! Sections are delimited by double newlines (\n\n).
//! A double newline can be included in any section by surrounding
//! the section with at least one set of square brackets.
//!
//! ## input
//!
//! Must be utf-8 and must end with a linebreak.
//! Linebreaks are \n (no \r).
use std::{
io::{Read, Write},
net::Shutdown,
time::{Duration, SystemTime},
};
use crate::{doc::Document, parse::parse};
mod doc;
mod parse;
mod to;
fn main() {
let args = std::env::args().skip(1).collect::<Vec<_>>();
if args.len() < 2 {
eprintln!("Usage: <filename>.mharkup <format> [specs...]");
eprintln!("output formats and (available specs)");
eprintln!("` html: a static html+css document (html, texm)");
eprintln!("` html-http: html with auto-refresh");
return;
}
let specs = &args[2..];
let src = || std::fs::read_to_string(&args[0]).unwrap();
match args[1].to_lowercase().trim() {
"html" => println!(
"{}",
to::html::page(&parse(&src()).unwrap(), specs, &mut to::html::State::new())
),
"html-http" => {
let addr = std::env::var("ADDR");
let addr = addr.as_ref().map_or("localhost:8000", |v| v.as_str());
let wait = std::env::var("WAIT")
.ok()
.and_then(|v| v.trim().parse().ok())
.unwrap_or(100);
eprintln!();
eprintln!("Binding to {addr} (ADDR env var)");
eprintln!("Checking file mtime every {wait}ms while handling a request (WAIT env var)");
let listener = std::net::TcpListener::bind(addr).unwrap();
eprintln!();
eprintln!("With your browser, open http://{addr}/");
eprintln!("Without javascript, use http://{addr}/s");
eprintln!("For a static page, open http://{addr}/1");
let mut ptime = SystemTime::UNIX_EPOCH + Duration::from_mins(9);
loop {
let mut state = to::html::State::new();
let mut buf1 = vec![String::new(); 1024];
let mut buf2 = vec![None; 1024];
let mut i = 0;
// connection might time out after 30 seconds,
// so send a 304 before that can happen.
let maxchecks = 25 * 1000 / wait;
'accept_connection: while i < 1024 {
let (mut connection, _) = listener.accept().unwrap();
let mut buf = [0u8; 8];
if connection.read_exact(&mut buf).is_err() {
continue;
}
let allow_delay = if let Ok(req) = str::from_utf8(&buf) {
if req.contains(" /u") {
state.head_to_body = "</head>\n<body>";
true
} else if req.contains(" /1") {
state.head_to_body = "</head>\n<body>";
false
} else if req.contains(" /s") {
state.head_to_body =
"<meta http-equiv=\"refresh\" content=\"1\">\n</head>\n<body>";
true
} else {
state.head_to_body = r#"<script>
const BODY_REGEX = new RegExp("<bo"+"dy>.*<\\/bo"+"dy>", "misv");
(async () => { while (true) {
let res = await fetch("/u");
if (!res.ok) continue;
let str = (await res.text()).match(BODY_REGEX)[0];
document.body.outerHTML = str;
} })();
</script>
</head>
<body>"#;
false
}
} else {
continue;
};
if allow_delay {
'delay_connection: {
for _ in 0..maxchecks {
let mtime =
std::fs::metadata(&args[0]).unwrap().modified().unwrap();
if mtime != ptime {
ptime = mtime;
break 'delay_connection;
} else {
std::thread::sleep(std::time::Duration::from_millis(wait));
}
}
connection
.write_all(
b"HTTP/1.1 304 Not Modified\r\nContent-Length: 0\r\n\r\n",
)
.ok();
connection.shutdown(Shutdown::Both).ok();
continue 'accept_connection;
}
}
buf1[i] = src();
let src = unsafe { &*((&buf1[i]) as *const String) };
buf2[i] = Some(match parse(src) {
Ok(v) => v,
Err(e) => {
eprintln!("{}", e.display(src));
continue;
}
});
let doc = unsafe { &*(buf2[i].as_ref().unwrap() as *const Document<'_>) };
i += 1;
let mut http =
"HTTP/1.1 200 Ok\r\nContent-Length: 0000000000\r\n\r\n".to_owned();
let len = http.len();
to::html::page_to(doc, specs, &mut state, &mut http);
let content_length = format!("{}", http.len() - len);
http.replace_range(len - 4 - content_length.len()..len - 4, &content_length);
connection.write_all(http.as_bytes()).ok();
connection.shutdown(Shutdown::Both).ok();
}
}
}
_ => eprintln!("Unknown format, run without arguments for help"),
}
}