Added breadcrumbs
This commit is contained in:
128
src/main.rs
128
src/main.rs
@@ -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> > </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 {
|
||||
|
||||
Reference in New Issue
Block a user