Added breadcrumbs

This commit is contained in:
2025-12-13 01:29:22 +05:00
parent 4f0c1e0f9c
commit 633b0af62e

View File

@@ -4,6 +4,18 @@ use std::net::{TcpListener, TcpStream};
use std::path::Path;
use regex::Regex;
#[derive(PartialEq, Clone, Copy)]
enum ListType {
Unordered,
Ordered,
}
struct TocItem {
level: usize,
text: String,
anchor: String,
}
fn main() {
if !Path::new("public").exists() {
fs::create_dir("public").expect("Не удалось создать папку public");
@@ -34,11 +46,11 @@ fn handle_connection(mut stream: TcpStream) {
let buf_reader = BufReader::new(&stream);
let re = Regex::new(r"^GET\s+(\S+)\s+HTTP/\d\.\d$").unwrap();
let request_line = buf_reader.lines().next().unwrap().unwrap();
let binding = re.captures(&request_line).and_then(|caps| caps.get(1)).map(|m| m.as_str().to_string()).unwrap_or(String::new());
let binding = re.captures(&request_line).and_then(|caps| caps.get(1)).map(|m| m.as_str().to_owned()).unwrap_or(String::new());
let request_line = binding.trim();
let request_line = if request_line == "/" { "index" } else { &request_line[1..] };
let (status_line, html_content) = match read_markdown(&format!("public/{}.md", request_line)) {
let (status_line, html_content) = match read_markdown(request_line) {
Ok(html) => ("HTTP/1.1 200 OK", html),
Err(_) => ("HTTP/1.1 404 NOT FOUND", String::from("<h1>404 - Страница не найдена</h1>")),
};
@@ -54,16 +66,17 @@ fn handle_connection(mut stream: TcpStream) {
stream.flush().unwrap();
}
fn read_markdown(filename: &str) -> Result<String, std::io::Error> {
let markdown_content = fs::read_to_string(filename)?;
fn read_markdown(request_line: &str) -> Result<String, std::io::Error> {
let filename = format!("public/{}.md", request_line);
let markdown_content = fs::read_to_string(&filename)?;
let (html, toc) = markdown_to_html(&markdown_content);
let css = fs::read_to_string("public/style.css")?;
let toc_html = if !toc.is_empty() {
let mut toc_list = String::from("<nav class=\"toc\"><h2>Содержание</h2><ul>");
let mut toc_list = String::from(r#"<nav class="toc"><h2>Содержание</h2><ul>"#);
for item in toc {
toc_list.push_str(&format!(
"<li class=\"toc-h{}\"><a href=\"#{}\">{}</a></li>",
r##"<li class="toc-h{}"><a href="#{}">{}</a></li>"##,
item.level, item.anchor, item.text
));
}
@@ -73,6 +86,8 @@ fn read_markdown(filename: &str) -> Result<String, std::io::Error> {
String::new()
};
let breadcrumbs = breadcrumbs(request_line);
Ok(format!(
r#"<!DOCTYPE html>
<html>
@@ -84,16 +99,29 @@ fn read_markdown(filename: &str) -> Result<String, std::io::Error> {
<body>
{}
{}
{}
</body>
</html>"#,
filename, css, toc_html, html
filename, css, breadcrumbs, toc_html, html
))
}
struct TocItem {
level: usize,
text: String,
anchor: String,
fn breadcrumbs(request_line: &str) -> String {
if request_line == "index" {
return String::new()
}
let mut crumbs = vec![String::from(r#"<a href="/" class="crumb">/</a>"#)];
let mut url = String::new();
for part in request_line.split("/") {
url = format!("{}/{}", url, part);
let crumb = String::from(format!(r#"<a href="{}" class="crumb">{}</a>"#, url, part));
crumbs.push(crumb);
}
let crumbs = crumbs.join("<span>&nbsp;&gt;&nbsp;</span>");
format!(r#"<div class="breadcrumbs">{}</div>"#, crumbs)
}
fn markdown_to_html(markdown: &str) -> (String, Vec<TocItem>) {
@@ -109,13 +137,13 @@ fn markdown_to_html(markdown: &str) -> (String, Vec<TocItem>) {
if in_code_block {
html.push_str("<pre><code>");
} else {
html.push_str("</code></pre>\n");
html.push_str("</code></pre>");
}
continue;
}
if in_code_block {
html.push_str(&escape_html(line));
html.push('\n');
html.push_str("\n");
continue;
}
@@ -124,8 +152,8 @@ fn markdown_to_html(markdown: &str) -> (String, Vec<TocItem>) {
if in_list.is_some() && !is_list_item && !line.is_empty() {
match in_list {
Some(ListType::Unordered) => html.push_str("</ul>\n"),
Some(ListType::Ordered) => html.push_str("</ol>\n"),
Some(ListType::Unordered) => html.push_str("</ul>"),
Some(ListType::Ordered) => html.push_str("</ol>"),
None => {}
}
in_list = None;
@@ -139,105 +167,99 @@ fn markdown_to_html(markdown: &str) -> (String, Vec<TocItem>) {
if line.starts_with("###### ") {
let text = &line[7..];
let anchor = create_anchor(text);
toc.push(TocItem { level: 6, text: text.to_string(), anchor: anchor.clone() });
html.push_str(&format!("<h6 id=\"{}\">{}</h6>\n", anchor, process_inline(text)));
toc.push(TocItem { level: 6, text: text.to_owned(), anchor: anchor.clone() });
html.push_str(&format!(r#"<h6 id="{}">{}</h6>"#, anchor, process_inline(text)));
} else if line.starts_with("##### ") {
let text = &line[6..];
let anchor = create_anchor(text);
toc.push(TocItem { level: 5, text: text.to_string(), anchor: anchor.clone() });
html.push_str(&format!("<h5 id=\"{}\">{}</h5>\n", anchor, process_inline(text)));
toc.push(TocItem { level: 5, text: text.to_owned(), anchor: anchor.clone() });
html.push_str(&format!(r#"<h5 id="{}">{}</h5>"#, anchor, process_inline(text)));
} else if line.starts_with("#### ") {
let text = &line[5..];
let anchor = create_anchor(text);
toc.push(TocItem { level: 4, text: text.to_string(), anchor: anchor.clone() });
html.push_str(&format!("<h4 id=\"{}\">{}</h4>\n", anchor, process_inline(text)));
toc.push(TocItem { level: 4, text: text.to_owned(), anchor: anchor.clone() });
html.push_str(&format!(r#"<h4 id="{}">{}</h4>"#, anchor, process_inline(text)));
} else if line.starts_with("### ") {
let text = &line[4..];
let anchor = create_anchor(text);
toc.push(TocItem { level: 3, text: text.to_string(), anchor: anchor.clone() });
html.push_str(&format!("<h3 id=\"{}\">{}</h3>\n", anchor, process_inline(text)));
toc.push(TocItem { level: 3, text: text.to_owned(), anchor: anchor.clone() });
html.push_str(&format!(r#"<h3 id="{}">{}</h3>"#, anchor, process_inline(text)));
} else if line.starts_with("## ") {
let text = &line[3..];
let anchor = create_anchor(text);
toc.push(TocItem { level: 2, text: text.to_string(), anchor: anchor.clone() });
html.push_str(&format!("<h2 id=\"{}\">{}</h2>\n", anchor, process_inline(text)));
toc.push(TocItem { level: 2, text: text.to_owned(), anchor: anchor.clone() });
html.push_str(&format!(r#"<h2 id="{}">{}</h2>"#, anchor, process_inline(text)));
} else if line.starts_with("# ") {
let text = &line[2..];
let anchor = create_anchor(text);
toc.push(TocItem { level: 1, text: text.to_string(), anchor: anchor.clone() });
html.push_str(&format!("<h1 id=\"{}\">{}</h1>\n", anchor, process_inline(text)));
toc.push(TocItem { level: 1, text: text.to_owned(), anchor: anchor.clone() });
html.push_str(&format!(r#"<h1 id="{}">{}</h1>"#, anchor, process_inline(text)));
} else if line == "---" || line == "***" || line == "___" {
html.push_str("<hr>\n");
html.push_str("<hr>");
} else if line.starts_with("> ") {
if !in_blockquote {
html.push_str("<blockquote>\n");
html.push_str("<blockquote>");
in_blockquote = true;
}
html.push_str(&format!("<p>{}</p>\n", process_inline(&line[2..])));
html.push_str(&format!("<p>{}</p>", process_inline(&line[2..])));
} else if line.starts_with("- ") || line.starts_with("* ") {
if in_list != Some(ListType::Unordered) {
if in_list.is_some() {
match in_list {
Some(ListType::Ordered) => html.push_str("</ol>\n"),
Some(ListType::Ordered) => html.push_str("</ol>"),
_ => {}
}
}
html.push_str("<ul>\n");
html.push_str("<ul>");
in_list = Some(ListType::Unordered);
}
html.push_str(&format!("<li>{}</li>\n", process_inline(&line[2..])));
html.push_str(&format!("<li>{}</li>", process_inline(&line[2..])));
} else if line.len() > 2 && line.chars().next().unwrap().is_numeric() && line.chars().nth(1) == Some('.') {
if in_list != Some(ListType::Ordered) {
if in_list.is_some() {
match in_list {
Some(ListType::Unordered) => html.push_str("</ul>\n"),
Some(ListType::Unordered) => html.push_str("</ul>"),
_ => {}
}
}
html.push_str("<ol>\n");
html.push_str("<ol>");
in_list = Some(ListType::Ordered);
}
html.push_str(&format!("<li>{}</li>\n", process_inline(&line[3..])));
html.push_str(&format!("<li>{}</li>", process_inline(&line[3..])));
} else if line.is_empty() {
if in_list.is_some() {
match in_list {
Some(ListType::Unordered) => html.push_str("</ul>\n"),
Some(ListType::Ordered) => html.push_str("</ol>\n"),
Some(ListType::Unordered) => html.push_str("</ul>"),
Some(ListType::Ordered) => html.push_str("</ol>"),
None => {}
}
in_list = None;
}
if in_blockquote {
html.push_str("</blockquote>\n");
html.push_str("</blockquote>");
in_blockquote = false;
}
} else {
html.push_str(&format!("<p>{}</p>\n", process_inline(line)));
html.push_str(&format!("<p>{}</p>", process_inline(line)));
}
}
if in_list.is_some() {
match in_list {
Some(ListType::Unordered) => html.push_str("</ul>\n"),
Some(ListType::Ordered) => html.push_str("</ol>\n"),
Some(ListType::Unordered) => html.push_str("</ul>"),
Some(ListType::Ordered) => html.push_str("</ol>"),
None => {}
}
}
if in_blockquote {
html.push_str("</blockquote>\n");
html.push_str("</blockquote>");
}
(html, toc)
}
#[derive(PartialEq, Clone, Copy)]
enum ListType {
Unordered,
Ordered,
}
fn process_inline(text: &str) -> String {
let mut result = text.to_string();
let mut result = text.to_owned();
while let Some(start) = result.find('`') {
if let Some(end) = result[start + 1..].find('`') {
@@ -328,7 +350,7 @@ fn process_inline(text: &str) -> String {
if let Some(end) = result[start + mid..].find(')') {
let alt = &result[start + 2..start + mid];
let url = &result[start + mid + 2..start + mid + end];
let replaced = format!("<img src=\"{}\" alt=\"{}\">", url, alt);
let replaced = format!(r#"<img src="{}" alt="{}">"#, url, alt);
result = format!("{}{}{}", &result[..start], replaced, &result[start + mid + end + 1..]);
} else {
break;
@@ -343,7 +365,7 @@ fn process_inline(text: &str) -> String {
if let Some(end) = result[start + mid..].find(')') {
let text = &result[start + 1..start + mid];
let url = &result[start + mid + 2..start + mid + end];
let replaced = format!("<a href=\"{}\">{}</a>", url, text);
let replaced = format!(r#"<a href="{}">{}</a>"#, url, text);
result = format!("{}{}{}", &result[..start], replaced, &result[start + mid + end + 1..]);
} else {
break;
@@ -376,7 +398,7 @@ fn create_anchor(text: &str) -> String {
})
.collect::<String>()
.trim_matches('-')
.to_string()
.to_owned()
}
fn escape_html(s: &str) -> String {