First commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/target
|
||||||
54
Cargo.lock
generated
Normal file
54
Cargo.lock
generated
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.1.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "md_server_rs"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.7.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.12.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.4.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.8.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
||||||
7
Cargo.toml
Normal file
7
Cargo.toml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
[package]
|
||||||
|
name = "md_server_rs"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
regex = "1.12.2"
|
||||||
85
public/index.md
Normal file
85
public/index.md
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
# Демонстрация возможностей Markdown
|
||||||
|
|
||||||
|
Это полный пример всех основных возможностей Markdown.
|
||||||
|
|
||||||
|
## Заголовки
|
||||||
|
|
||||||
|
### Третий уровень
|
||||||
|
#### Четвертый уровень
|
||||||
|
##### Пятый уровень
|
||||||
|
###### Шестой уровень
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Форматирование текста
|
||||||
|
|
||||||
|
Это **жирный текст** или __альтернативный жирный__.
|
||||||
|
|
||||||
|
Это *курсивный текст* или _альтернативный курсив_.
|
||||||
|
|
||||||
|
Это ~~зачеркнутый текст~~.
|
||||||
|
|
||||||
|
Можно комбинировать: **жирный и *курсив* вместе**.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Списки
|
||||||
|
|
||||||
|
### Маркированный список
|
||||||
|
|
||||||
|
- Первый пункт
|
||||||
|
- Второй пункт
|
||||||
|
- Третий пункт
|
||||||
|
- Вложенность пока не поддерживается
|
||||||
|
|
||||||
|
### Нумерованный список
|
||||||
|
|
||||||
|
1. Первый пункт
|
||||||
|
2. Второй пункт
|
||||||
|
3. Третий пункт
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Код
|
||||||
|
|
||||||
|
Инлайн код: `const x = 42;`
|
||||||
|
|
||||||
|
Блок кода:
|
||||||
|
```
|
||||||
|
fn main() {
|
||||||
|
println!("Hello from Rust!");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Цитаты
|
||||||
|
|
||||||
|
> Это цитата.
|
||||||
|
> Она может занимать несколько строк.
|
||||||
|
|
||||||
|
> Другая цитата.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ссылки и изображения
|
||||||
|
|
||||||
|
Это [ссылка на хуй](https://natribu.org).
|
||||||
|
|
||||||
|
Изображение: 
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Горизонтальные линии
|
||||||
|
|
||||||
|
Линия выше создана с помощью `---`
|
||||||
|
|
||||||
|
Также можно использовать `***` или `___`
|
||||||
|
|
||||||
|
___
|
||||||
|
|
||||||
|
## Комбинированный пример
|
||||||
|
|
||||||
|
> *"Да как нахуй удалить этот Амиго"* (c) Касперский
|
||||||
|
|
||||||
|
~~Старая версия~~ → **Новая версия**
|
||||||
111
public/style.css
Normal file
111
public/style.css
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.toc {
|
||||||
|
background: #f5f5f5;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 15px 20px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
.toc h2 {
|
||||||
|
margin-top: 0;
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
.toc ul {
|
||||||
|
list-style: none;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
.toc li {
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
.toc a {
|
||||||
|
color: #0066cc;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.toc a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.toc-h1 { padding-left: 0; font-weight: bold; }
|
||||||
|
.toc-h2 { padding-left: 20px; }
|
||||||
|
.toc-h3 { padding-left: 40px; }
|
||||||
|
.toc-h4 { padding-left: 60px; }
|
||||||
|
.toc-h5 { padding-left: 80px; }
|
||||||
|
.toc-h6 { padding-left: 100px; }
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
margin-top: 1.5em;
|
||||||
|
margin-bottom: 0.5em;
|
||||||
|
}
|
||||||
|
h1 { border-bottom: 2px solid #ddd; padding-bottom: 0.3em; font-size: 2em; }
|
||||||
|
h2 { border-bottom: 1px solid #eee; padding-bottom: 0.3em; font-size: 1.5em; }
|
||||||
|
h3 { font-size: 1.25em; }
|
||||||
|
h4 { font-size: 1.1em; }
|
||||||
|
h5 { font-size: 1em; }
|
||||||
|
h6 { font-size: 0.9em; color: #666; }
|
||||||
|
pre {
|
||||||
|
background: #f6f8fa;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 5px;
|
||||||
|
overflow-x: auto;
|
||||||
|
border: 1px solid #e1e4e8;
|
||||||
|
}
|
||||||
|
code {
|
||||||
|
font-family: 'Courier New', Consolas, monospace;
|
||||||
|
background: #f6f8fa;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
pre code {
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
blockquote {
|
||||||
|
border-left: 4px solid #ddd;
|
||||||
|
padding-left: 20px;
|
||||||
|
margin: 20px 0;
|
||||||
|
color: #666;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
hr {
|
||||||
|
border: none;
|
||||||
|
border-top: 2px solid #eee;
|
||||||
|
margin: 30px 0;
|
||||||
|
}
|
||||||
|
ul, ol {
|
||||||
|
padding-left: 30px;
|
||||||
|
margin: 15px 0;
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: #0066cc;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
del {
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
strong {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
em {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin: 15px 0;
|
||||||
|
}
|
||||||
3
public/test.md
Normal file
3
public/test.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# TEST
|
||||||
|
|
||||||
|
test file
|
||||||
390
src/main.rs
Normal file
390
src/main.rs
Normal file
@@ -0,0 +1,390 @@
|
|||||||
|
use std::fs;
|
||||||
|
use std::io::{BufRead, BufReader, Write};
|
||||||
|
use std::net::{TcpListener, TcpStream};
|
||||||
|
use std::path::Path;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
if !Path::new("public").exists() {
|
||||||
|
fs::create_dir("public").expect("Не удалось создать папку public");
|
||||||
|
println!("Создана папка public/");
|
||||||
|
}
|
||||||
|
|
||||||
|
if !Path::new("public/style.css").exists() {
|
||||||
|
fs::write("public/style.css", "").expect("Не удалось создать style.css");
|
||||||
|
println!("Создан файл public/style.css");
|
||||||
|
}
|
||||||
|
|
||||||
|
if !Path::new("public/index.md").exists() {
|
||||||
|
fs::write("public/index.md", "").expect("Не удалось создать index.md");
|
||||||
|
println!("Создан файл public/index.md");
|
||||||
|
}
|
||||||
|
|
||||||
|
let listener = TcpListener::bind("127.0.0.1:8080").unwrap();
|
||||||
|
println!("\nСервер запущен на http://127.0.0.1:8080");
|
||||||
|
println!("Markdown файлы читаются из папки public/\n");
|
||||||
|
|
||||||
|
for stream in listener.incoming() {
|
||||||
|
let stream = stream.unwrap();
|
||||||
|
handle_connection(stream);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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)) {
|
||||||
|
Ok(html) => ("HTTP/1.1 200 OK", html),
|
||||||
|
Err(_) => ("HTTP/1.1 404 NOT FOUND", String::from("<h1>404 - Страница не найдена</h1>")),
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = format!(
|
||||||
|
"{}\r\nContent-Type: text/html; charset=utf-8\r\nContent-Length: {}\r\n\r\n{}",
|
||||||
|
status_line,
|
||||||
|
html_content.len(),
|
||||||
|
html_content
|
||||||
|
);
|
||||||
|
|
||||||
|
stream.write_all(response.as_bytes()).unwrap();
|
||||||
|
stream.flush().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_markdown(filename: &str) -> Result<String, std::io::Error> {
|
||||||
|
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>");
|
||||||
|
for item in toc {
|
||||||
|
toc_list.push_str(&format!(
|
||||||
|
"<li class=\"toc-h{}\"><a href=\"#{}\">{}</a></li>",
|
||||||
|
item.level, item.anchor, item.text
|
||||||
|
));
|
||||||
|
}
|
||||||
|
toc_list.push_str("</ul></nav>");
|
||||||
|
toc_list
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(format!(
|
||||||
|
r#"<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>{}</title>
|
||||||
|
<style>{}</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
{}
|
||||||
|
{}
|
||||||
|
</body>
|
||||||
|
</html>"#,
|
||||||
|
filename, css, toc_html, html
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TocItem {
|
||||||
|
level: usize,
|
||||||
|
text: String,
|
||||||
|
anchor: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn markdown_to_html(markdown: &str) -> (String, Vec<TocItem>) {
|
||||||
|
let mut html = String::new();
|
||||||
|
let mut toc = Vec::new();
|
||||||
|
let mut in_code_block = false;
|
||||||
|
let mut in_list: Option<ListType> = None;
|
||||||
|
let mut in_blockquote = false;
|
||||||
|
|
||||||
|
for line in markdown.lines() {
|
||||||
|
if line.starts_with("```") {
|
||||||
|
in_code_block = !in_code_block;
|
||||||
|
if in_code_block {
|
||||||
|
html.push_str("<pre><code>");
|
||||||
|
} else {
|
||||||
|
html.push_str("</code></pre>\n");
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if in_code_block {
|
||||||
|
html.push_str(&escape_html(line));
|
||||||
|
html.push('\n');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_list_item = line.starts_with("- ") || line.starts_with("* ") ||
|
||||||
|
(line.len() > 2 && line.chars().next().unwrap().is_numeric() && line.chars().nth(1) == Some('.'));
|
||||||
|
|
||||||
|
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"),
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
in_list = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if in_blockquote && !line.starts_with("> ") && !line.is_empty() {
|
||||||
|
html.push_str("</blockquote>\n");
|
||||||
|
in_blockquote = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)));
|
||||||
|
} 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)));
|
||||||
|
} 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)));
|
||||||
|
} 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)));
|
||||||
|
} 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)));
|
||||||
|
} 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)));
|
||||||
|
} else if line == "---" || line == "***" || line == "___" {
|
||||||
|
html.push_str("<hr>\n");
|
||||||
|
} else if line.starts_with("> ") {
|
||||||
|
if !in_blockquote {
|
||||||
|
html.push_str("<blockquote>\n");
|
||||||
|
in_blockquote = true;
|
||||||
|
}
|
||||||
|
html.push_str(&format!("<p>{}</p>\n", 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"),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
html.push_str("<ul>\n");
|
||||||
|
in_list = Some(ListType::Unordered);
|
||||||
|
}
|
||||||
|
html.push_str(&format!("<li>{}</li>\n", 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"),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
html.push_str("<ol>\n");
|
||||||
|
in_list = Some(ListType::Ordered);
|
||||||
|
}
|
||||||
|
html.push_str(&format!("<li>{}</li>\n", 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"),
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
in_list = None;
|
||||||
|
}
|
||||||
|
if in_blockquote {
|
||||||
|
html.push_str("</blockquote>\n");
|
||||||
|
in_blockquote = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
html.push_str(&format!("<p>{}</p>\n", 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"),
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if in_blockquote {
|
||||||
|
html.push_str("</blockquote>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
(html, toc)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Clone, Copy)]
|
||||||
|
enum ListType {
|
||||||
|
Unordered,
|
||||||
|
Ordered,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_inline(text: &str) -> String {
|
||||||
|
let mut result = text.to_string();
|
||||||
|
|
||||||
|
while let Some(start) = result.find('`') {
|
||||||
|
if let Some(end) = result[start + 1..].find('`') {
|
||||||
|
let inner = &result[start + 1..start + 1 + end];
|
||||||
|
let replaced = format!("<code>{}</code>", escape_html(inner));
|
||||||
|
result = format!("{}{}{}", &result[..start], replaced, &result[start + 2 + end..]);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(start) = result.find("**") {
|
||||||
|
if let Some(end) = result[start + 2..].find("**") {
|
||||||
|
let inner = &result[start + 2..start + 2 + end];
|
||||||
|
let replaced = format!("<strong>{}</strong>", inner);
|
||||||
|
result = format!("{}{}{}", &result[..start], replaced, &result[start + 4 + end..]);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(start) = result.find("__") {
|
||||||
|
if let Some(end) = result[start + 2..].find("__") {
|
||||||
|
let inner = &result[start + 2..start + 2 + end];
|
||||||
|
let replaced = format!("<strong>{}</strong>", inner);
|
||||||
|
result = format!("{}{}{}", &result[..start], replaced, &result[start + 4 + end..]);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut pos = 0;
|
||||||
|
while let Some(start) = result[pos..].find('*') {
|
||||||
|
let abs_start = pos + start;
|
||||||
|
if abs_start > 0 && result.chars().nth(abs_start - 1) == Some('*') {
|
||||||
|
pos = abs_start + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if abs_start + 1 < result.len() && result.chars().nth(abs_start + 1) == Some('*') {
|
||||||
|
pos = abs_start + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(end) = result[abs_start + 1..].find('*') {
|
||||||
|
let abs_end = abs_start + 1 + end;
|
||||||
|
if abs_end + 1 < result.len() && result.chars().nth(abs_end + 1) == Some('*') {
|
||||||
|
pos = abs_start + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let inner = &result[abs_start + 1..abs_end];
|
||||||
|
let replaced = format!("<em>{}</em>", inner);
|
||||||
|
result = format!("{}{}{}", &result[..abs_start], replaced, &result[abs_end + 1..]);
|
||||||
|
pos = abs_start + replaced.len();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut pos = 0;
|
||||||
|
while let Some(start) = result[pos..].find('_') {
|
||||||
|
let abs_start = pos + start;
|
||||||
|
if abs_start > 0 && result.chars().nth(abs_start - 1) == Some('_') {
|
||||||
|
pos = abs_start + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if abs_start + 1 < result.len() && result.chars().nth(abs_start + 1) == Some('_') {
|
||||||
|
pos = abs_start + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(end) = result[abs_start + 1..].find('_') {
|
||||||
|
let abs_end = abs_start + 1 + end;
|
||||||
|
if abs_end + 1 < result.len() && result.chars().nth(abs_end + 1) == Some('_') {
|
||||||
|
pos = abs_start + 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let inner = &result[abs_start + 1..abs_end];
|
||||||
|
let replaced = format!("<em>{}</em>", inner);
|
||||||
|
result = format!("{}{}{}", &result[..abs_start], replaced, &result[abs_end + 1..]);
|
||||||
|
pos = abs_start + replaced.len();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(start) = result.find("![") {
|
||||||
|
if let Some(mid) = result[start..].find("](") {
|
||||||
|
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);
|
||||||
|
result = format!("{}{}{}", &result[..start], replaced, &result[start + mid + end + 1..]);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(start) = result.find('[') {
|
||||||
|
if let Some(mid) = result[start..].find("](") {
|
||||||
|
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);
|
||||||
|
result = format!("{}{}{}", &result[..start], replaced, &result[start + mid + end + 1..]);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(start) = result.find("~~") {
|
||||||
|
if let Some(end) = result[start + 2..].find("~~") {
|
||||||
|
let inner = &result[start + 2..start + 2 + end];
|
||||||
|
let replaced = format!("<del>{}</del>", inner);
|
||||||
|
result = format!("{}{}{}", &result[..start], replaced, &result[start + 4 + end..]);
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_anchor(text: &str) -> String {
|
||||||
|
text.to_lowercase()
|
||||||
|
.chars()
|
||||||
|
.map(|c| match c {
|
||||||
|
'а'..='я' | 'a'..='z' | '0'..='9' => c,
|
||||||
|
' ' | '-' | '_' => '-',
|
||||||
|
_ => '_',
|
||||||
|
})
|
||||||
|
.collect::<String>()
|
||||||
|
.trim_matches('-')
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn escape_html(s: &str) -> String {
|
||||||
|
s.replace('&', "&")
|
||||||
|
.replace('*', "*")
|
||||||
|
.replace('_', "_")
|
||||||
|
.replace('<', "<")
|
||||||
|
.replace('>', ">")
|
||||||
|
.replace('"', """)
|
||||||
|
.replace('\'', "'")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user