commit - bf6a94c965569ff8cd4e55c0aff269a484f946ed
commit + 7c20cf6f8ff15852c42ea0a124c7072173e41c60
blob - e268781dc44180e52ee6c9426f3c3740f1bdf74b
blob + 2d2064bd5a743e522fa883ea1ed71d33bb7ca18b
--- src/main.rs
+++ src/main.rs
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
-use std::{env, fs, path::{Path, PathBuf}, collections::HashMap, sync::OnceLock};
+use std::{
+ collections::HashMap,
+ env, fs,
+ net::{TcpListener, TcpStream},
+ path::{Path, PathBuf},
+ sync::OnceLock,
+};
fn main() {
LOG_ENABLED.set(env::var("KSSG_LOG").is_ok()).ok();
match args.get(1).map(|s| s.as_str()) {
Some("build") => build(),
- _ => eprintln!("usage: ksgg build"),
+ Some("serve") => serve(),
+ _ => eprintln!("usage: ksgg <build|serve>"),
}
}
//////////////////////////////////////////////////////////////////////////////
+// Log
+//////////////////////////////////////////////////////////////////////////////
+
+static LOG_ENABLED: OnceLock<bool> = OnceLock::new();
+
+macro_rules! log {
+ ($($arg:tt)*) => {
+ if *LOG_ENABLED.get().unwrap_or(&false) {
+ eprintln!("[{}] {}", now_utc(), format_args!($($arg)*));
+ }
+ };
+}
+
+//////////////////////////////////////////////////////////////////////////////
+// Server
+//////////////////////////////////////////////////////////////////////////////
+
+fn content_type(path: &Path) -> &'static str {
+ match path.extension().and_then(|e| e.to_str()) {
+ Some("html") => "text/html; charset=utf-8",
+ Some("css") => "text/css",
+ _ => "application/octet-stream",
+ }
+}
+
+fn serve() {
+ build();
+
+ let addr = "127.0.0.1:8080";
+ let listener = TcpListener::bind(addr).expect("bind");
+ println!("serving at http://{}", addr);
+
+ for stream in listener.incoming() {
+ let stream = match stream {
+ Ok(s) => s,
+ Err(_) => continue,
+ };
+ handle_request(stream);
+ }
+}
+
+fn handle_request(mut stream: TcpStream) {
+ use std::io::{BufRead, BufReader, Write};
+
+ let reader = BufReader::new(&stream);
+ let request_line = match reader.lines().next() {
+ Some(Ok(line)) => line,
+ _ => return,
+ };
+
+ log!("{}", request_line);
+
+ // "GET /path HTTP/1.1"
+ let path = request_line.split_whitespace().nth(1).unwrap_or("/");
+
+ let mut file_path = PathBuf::from("public");
+ file_path.push(&path[1..]);
+
+ if file_path.is_dir() {
+ file_path.push("index.html");
+ }
+ if !file_path.exists()
+ && !file_path.to_str().map(|s| s.contains('.')).unwrap_or(false)
+ {
+ file_path.set_extension("html");
+ }
+
+ let (status, body, ctype) = if file_path.exists() {
+ let body = fs::read(&file_path).expect("read file");
+ let ctype = content_type(&file_path);
+ ("200 OK", body, ctype)
+ } else {
+ let body = b"404".to_vec();
+ ("404 Not Found", body, "text/plain")
+ };
+
+ let response = format!(
+ "HTTP/1.1 {}\r\nContent-Type: {}\r\n\
+ Content-Length: {}\r\nConnection: close\r\n\r\n",
+ status,
+ ctype,
+ body.len()
+ );
+
+ let _ = stream.write_all(response.as_bytes());
+ let _ = stream.write_all(&body);
+}
+
+//////////////////////////////////////////////////////////////////////////////
// DateTime
//////////////////////////////////////////////////////////////////////////////
}
//////////////////////////////////////////////////////////////////////////////
-// Log
-//////////////////////////////////////////////////////////////////////////////
-
-static LOG_ENABLED: OnceLock<bool> = OnceLock::new();
-
-macro_rules! log {
- ($($arg:tt)*) => {
- if *LOG_ENABLED.get().unwrap_or(&false) {
- eprintln!("[{}] {}", now_utc(), format_args!($($arg)*));
- }
- };
-}
-
-//////////////////////////////////////////////////////////////////////////////
// Frontmatter
//////////////////////////////////////////////////////////////////////////////
}
fn parse_frontmatter(content: &str) -> Metadata {
- let tree = markdown::to_mdast(content, &parse_options())
- .expect("parse mdast");
+ let tree =
+ markdown::to_mdast(content, &parse_options()).expect("parse mdast");
let mut map = HashMap::new();
if let markdown::mdast::Node::Root(root) = tree {
}
}
-
//////////////////////////////////////////////////////////////////////////////
// Build
//////////////////////////////////////////////////////////////////////////////
continue;
}
- let md = fs::read_to_string(file)
- .expect("read content");
+ let md = fs::read_to_string(file).expect("read content");
let meta = parse_frontmatter(&md);
if meta.draft {
continue;
}
- let html_content = markdown::to_html_with_options(&md, &markdown::Options {
- parse: parse_options(),
- ..markdown::Options::default()
- }).expect("markdown to html");
+ let html_content = markdown::to_html_with_options(
+ &md,
+ &markdown::Options {
+ parse: parse_options(),
+ ..markdown::Options::default()
+ },
+ )
+ .expect("markdown to html");
let relative = file.strip_prefix(content_dir).expect("strip prefix");
- let out_path = Path::new("public").join(relative).with_extension("html");
+ let out_path =
+ Path::new("public").join(relative).with_extension("html");
- posts.push(Post { meta, html_content, out_path });
-
+ posts.push(Post {
+ meta,
+ html_content,
+ out_path,
+ });
}
let post_list = generate_post_list(&posts);
if !nav.is_empty() {
content.push_str(&nav);
}
- println!("{}", posts[i].out_path.display());
let list = if posts[i].out_path == Path::new("public/index.html") {
&post_list
if let Some(p) = prev {
let href = format!("/{}", p.out_path.display());
- nav.push_str(
- &format!("<a href=\"{}\">\u{2190} {}</a>", href, p.meta.title)
- );
+ nav.push_str(&format!(
+ "<a href=\"{}\">\u{2190} {}</a>",
+ href, p.meta.title
+ ));
}
if let Some(p) = next {
let href = format!("/{}", p.out_path.display());
- nav.push_str(
- &format!("<a href=\"{}\">{} \u{2192}</a>", href, p.meta.title)
- );
+ nav.push_str(&format!(
+ "<a href=\"{}\">{} \u{2192}</a>",
+ href, p.meta.title
+ ));
}
nav.push_str("</nav>");
if post.out_path == Path::new("public/index.html") {
continue;
}
- items.push((
- &post.meta.date,
- &post.meta.title,
- &post.out_path,
- ));
+ items.push((&post.meta.date, &post.meta.title, &post.out_path));
}
items.sort_by(|a, b| b.0.cmp(a.0));
let mut html = String::from("<ul class=\"post-list\">\n");
for (date, title, path) in &items {
- let href = format!("/{}", path.strip_prefix("public").unwrap_or(path).display());
- html.push_str(&format!("<li><time>{}</time> - <a href=\"{}\">{}</a></li>\n", date, href, title));
+ let href = format!(
+ "/{}",
+ path.strip_prefix("public").unwrap_or(path).display()
+ );
+ html.push_str(&format!(
+ "<li><time>{}</time> - <a href=\"{}\">{}</a></li>\n",
+ date, href, title
+ ));
}
html.push_str("</ul>");