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