//! # 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::>(); if args.len() < 2 { eprintln!("Usage: .mharkup [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 = "\n"; true } else if req.contains(" /1") { state.head_to_body = "\n"; false } else if req.contains(" /s") { state.head_to_body = "\n\n"; true } else { state.head_to_body = r#" "#; 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"), } }