diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 299e8cb..7b0ad5e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -27,7 +27,7 @@ repos: - id: clippy name: clippy language: system - entry: cargo clippy -- -W clippy::all -W clippy::pedantic + entry: cargo clippy -- -D clippy::all -D clippy::pedantic pass_filenames: false always_run: true diff --git a/TODO.md b/TODO.md index fa56e1b..bf05227 100644 --- a/TODO.md +++ b/TODO.md @@ -2,3 +2,5 @@ * [X] ~~Сделать кнопку скопировать путь~~ * [X] ~~Сделать кнопку скопировать путь вместе с командой (`note edit ...`)~~ +* [X] ~~Сделать отображение типа кода и кнопку скопировать код~~ +* [ ] Починить заголовки таблицы diff --git a/src/main.rs b/src/main.rs index a411824..f768b46 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,7 +28,6 @@ use syntect::parsing::SyntaxSet; use tower_http::trace::TraceLayer; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; -use webbrowser; use clap::Parser; use std::io; @@ -152,10 +151,8 @@ async fn main() { if args.browser { let mut url = format!("http://{actual_addr}"); - if args.random { - if !state.is_root_file { - url = format!("{url}/random") - } + if args.random && !state.is_root_file { + url = format!("{url}/random"); } let _ = webbrowser::open(url.as_str()); } @@ -185,12 +182,9 @@ async fn root_handler(State(state): State) -> impl IntoResponse { } } -async fn render_single_file( - file_path: &StdPath, -) -> Result { - let metadata = match fs::metadata(file_path).await { - Ok(m) => m, - Err(_) => return Err(StatusCode::NOT_FOUND), +async fn render_single_file(file_path: &StdPath) -> Result { + let Ok(metadata) = fs::metadata(file_path).await else { + return Err(StatusCode::NOT_FOUND); }; if metadata.is_dir() { @@ -203,9 +197,8 @@ async fn render_single_file( .unwrap_or("") .to_lowercase(); - let content = match fs::read_to_string(file_path).await { - Ok(c) => c, - Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR), + let Ok(content) = fs::read_to_string(file_path).await else { + return Err(StatusCode::INTERNAL_SERVER_ERROR); }; let display_path = ""; @@ -216,12 +209,7 @@ async fn render_single_file( let html_content = if extension == "md" { markdown_to_html(&content, display_path) } else { - code_to_html( - &content, - extension.as_str(), - &ss, - &ts, - ) + code_to_html(&content, extension.as_str(), &ss, &ts) }; let filename = file_path @@ -275,9 +263,8 @@ async fn serve_file( return Err(StatusCode::FORBIDDEN); } - let metadata = match fs::metadata(&safe_path).await { - Ok(m) => m, - Err(_) => return Err(StatusCode::NOT_FOUND), + let Ok(metadata) = fs::metadata(&safe_path).await else { + return Err(StatusCode::NOT_FOUND); }; if metadata.is_dir() { @@ -299,17 +286,15 @@ async fn serve_file( ); if is_image { - let file_content = match fs::read(&safe_path).await { - Ok(content) => content, - Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR), + let Ok(file_content) = fs::read(&safe_path).await else { + return Err(StatusCode::INTERNAL_SERVER_ERROR); }; let mime_type = from_path(&safe_path).first_or_octet_stream(); return Ok(([(header::CONTENT_TYPE, mime_type.as_ref())], file_content).into_response()); } - let content = match fs::read_to_string(&safe_path).await { - Ok(c) => c, - Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR), + let Ok(content) = fs::read_to_string(&safe_path).await else { + return Err(StatusCode::INTERNAL_SERVER_ERROR); }; let ss = SyntaxSet::load_defaults_newlines(); @@ -318,12 +303,7 @@ async fn serve_file( let html_content = if extension == "md" { markdown_to_html(&content, &full_path) } else { - code_to_html( - &content, - extension.as_str(), - &ss, - &ts, - ) + code_to_html(&content, extension.as_str(), &ss, &ts) }; let filename = safe_path @@ -365,9 +345,8 @@ async fn render_directory_index( dir_path: &StdPath, request_path: &str, ) -> Result { - let mut entries = match fs::read_dir(dir_path).await { - Ok(list) => list, - Err(_) => return Err(StatusCode::FORBIDDEN), + let Ok(mut entries) = fs::read_dir(dir_path).await else { + return Err(StatusCode::FORBIDDEN); }; let mut files: Vec = Vec::new(); @@ -388,7 +367,7 @@ async fn render_directory_index( let link = if request_path.is_empty() { file_name.clone() } else { - format!("{}/{}", request_path, file_name) + format!("{request_path}/{file_name}") }; files.push(FileEntry { @@ -513,9 +492,8 @@ async fn random_file(State(state): State) -> impl IntoResponse { let mut rng = rand::thread_rng(); let random_path = files.choose(&mut rng).unwrap(); - let relative_path = match random_path.strip_prefix(&state.root) { - Ok(p) => p, - Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR), + let Ok(relative_path) = random_path.strip_prefix(&state.root) else { + return Err(StatusCode::INTERNAL_SERVER_ERROR); }; let url_path = relative_path.to_string_lossy().replace('\\', "/"); diff --git a/src/markdown.rs b/src/markdown.rs index d5a2f31..1f07f5b 100644 --- a/src/markdown.rs +++ b/src/markdown.rs @@ -3,6 +3,8 @@ use pulldown_cmark::{ TextMergeStream, }; +use std::fmt::Write; + pub fn markdown_to_html(markdown: &str, _file_path: &str) -> String { let mut options = Options::empty(); options.insert(Options::ENABLE_TABLES); @@ -51,21 +53,21 @@ pub fn markdown_to_html(markdown: &str, _file_path: &str) -> String { Event::Code(code) => { html_output.push_str(format!("{}", escape_html(&code)).as_str()); } - Event::SoftBreak => html_output.push_str(" "), + Event::SoftBreak => html_output.push(' '), Event::HardBreak => html_output.push_str("
"), Event::Html(html) => html_output.push_str(&html), Event::Rule => html_output.push_str("
"), - Event::FootnoteReference(fref) => html_output.push_str(&format!( - "", - fref, fref - )), + Event::FootnoteReference(fref) => { + let _ = write!( + html_output, + "" + ); + } - Event::InlineHtml(_) => (), - Event::InlineMath(_) => (), - Event::DisplayMath(_) => (), + Event::InlineHtml(_) | Event::InlineMath(_) | Event::DisplayMath(_) => (), } } @@ -94,31 +96,30 @@ fn render_start_tag(tag: Tag<'_>) -> String { dest_url, title, .. - } => output.push_str(render_link(link_type, dest_url, title).as_str()), + } => output.push_str(render_link(link_type, &dest_url, &title).as_str()), Tag::Image { link_type, dest_url, title, .. - } => output.push_str(render_image(link_type, dest_url, title).as_str()), + } => output.push_str(render_image(link_type, &dest_url, &title).as_str()), - Tag::Table(_) => output.push_str(""), // TODO: fix table + Tag::Table(_) => output.push_str("
"), Tag::TableHead => output.push_str(""), Tag::TableCell => output.push_str(""), - Tag::HtmlBlock => (), // TODO: implement - Tag::FootnoteDefinition(fref) => { - output.push_str(&format!("", fref, fref)) + let _ = write!(output, ""); } - Tag::DefinitionList => (), - Tag::DefinitionListTitle => (), - Tag::DefinitionListDefinition => (), - Tag::Superscript => (), - Tag::Subscript => (), - Tag::MetadataBlock(_) => (), + Tag::HtmlBlock + | Tag::DefinitionList + | Tag::DefinitionListTitle + | Tag::DefinitionListDefinition + | Tag::Superscript + | Tag::Subscript + | Tag::MetadataBlock(_) => (), } output @@ -137,7 +138,7 @@ fn render_end_tag(tag: TagEnd) -> String { TagEnd::List(false) => output.push_str(""), TagEnd::Item => output.push_str(""), TagEnd::Paragraph => output.push_str("

"), - TagEnd::CodeBlock => output.push_str(""), + TagEnd::CodeBlock => output.push_str(""), TagEnd::Emphasis => output.push_str(""), TagEnd::Strong => output.push_str(""), TagEnd::BlockQuote(_) => output.push_str(""), @@ -150,78 +151,81 @@ fn render_end_tag(tag: TagEnd) -> String { TagEnd::TableRow => output.push_str("
"), TagEnd::FootnoteDefinition => output.push_str(""), - TagEnd::HtmlBlock => (), // TODO: implement - TagEnd::DefinitionList => (), - TagEnd::DefinitionListTitle => (), - TagEnd::DefinitionListDefinition => (), - TagEnd::Superscript => (), - TagEnd::Subscript => (), - TagEnd::MetadataBlock(_) => (), + TagEnd::HtmlBlock + | TagEnd::DefinitionList + | TagEnd::DefinitionListTitle + | TagEnd::DefinitionListDefinition + | TagEnd::Superscript + | TagEnd::Subscript + | TagEnd::MetadataBlock(_) => (), } output } fn render_codeblock(block_kind: CodeBlockKind<'_>) -> String { + let header = String::from( + r#" +
+
+ %CODE% +
+
"#,
+    );
+
+    let mut output = String::new();
+
     match block_kind {
-        CodeBlockKind::Indented => "
".to_string(),
+        CodeBlockKind::Indented => {
+            output.push_str(&header.replace("%CODE%", "code"));
+            output.push_str("");
+        }
+
         CodeBlockKind::Fenced(CowStr::Borrowed("mermaid")) => {
-            "
".to_string()
+            output.push_str(&header.replace("%CODE%", "mermaid"));
+            output.push_str("");
         }
+
         CodeBlockKind::Fenced(CowStr::Borrowed("rawmermaid")) => {
-            "
".to_string()
+            output.push_str(&header.replace("%CODE%", "mermaid"));
+            output.push_str("");
+        }
+
+        CodeBlockKind::Fenced(code_type) => {
+            output.push_str(&header.replace("%CODE%", &code_type));
+            output.push_str(format!("").as_str());
         }
-        CodeBlockKind::Fenced(code_type) => format!("
", code_type),
     }
+
+    output
 }
 
 fn render_blockquote(quote_kind: Option) -> String {
-    match quote_kind {
-        None => {
-            "
".to_string() - }, - Some(BlockQuoteKind::Note) => "

Note

".to_string(), - Some(BlockQuoteKind::Tip) => "

Tip

".to_string(), - Some(BlockQuoteKind::Important) =>"

Important

".to_string(), - Some(BlockQuoteKind::Warning) => "

Warning

".to_string(), - Some(BlockQuoteKind::Caution) => "

Caution

".to_string(), - } + let alert_class_and_title = match quote_kind { + None => return "
".to_string(), + Some(BlockQuoteKind::Note) => ("note", "Note"), + Some(BlockQuoteKind::Tip) => ("tip", "Tip"), + Some(BlockQuoteKind::Important) => ("important", "Important"), + Some(BlockQuoteKind::Warning) => ("warning", "Warning"), + Some(BlockQuoteKind::Caution) => ("caution", "Caution"), + }; + + format!( + "

{}

", + alert_class_and_title.0, alert_class_and_title.1 + ) } -fn render_link(link_type: LinkType, dest_url: CowStr<'_>, title: CowStr<'_>) -> String { + +fn render_link(link_type: LinkType, dest_url: &CowStr<'_>, title: &CowStr<'_>) -> String { match link_type { - LinkType::Inline => format!("", dest_url, title), - LinkType::Reference => format!("", dest_url, title), - LinkType::ReferenceUnknown => format!("", dest_url, title), - LinkType::Collapsed => format!("", dest_url, title), - LinkType::CollapsedUnknown => format!("", dest_url, title), - LinkType::Shortcut => format!("", dest_url, title), - LinkType::ShortcutUnknown => format!("", dest_url, title), - LinkType::Autolink => format!("", dest_url, title), - LinkType::Email => format!("", dest_url, title), - LinkType::WikiLink { has_pothole: _ } => { - // TODO: refactor - format!("", dest_url, title) - } + LinkType::Email => format!(""), + _ => format!(""), } } -fn render_image(link_type: LinkType, dest_url: CowStr<'_>, title: CowStr<'_>) -> String { - match link_type { - LinkType::Inline => format!("\"{}\"", dest_url, title), - LinkType::Reference => format!("", dest_url, title), - LinkType::ReferenceUnknown => format!("\"{}\"", dest_url, title), - LinkType::Collapsed => format!("\"{}\"", dest_url, title), - LinkType::CollapsedUnknown => format!("\"{}\"", dest_url, title), - LinkType::Shortcut => format!("\"{}\"", dest_url, title), - LinkType::ShortcutUnknown => format!("\"{}\"", dest_url, title), - LinkType::Autolink => format!("\"{}\"", dest_url, title), - LinkType::Email => format!("\"{}\"", dest_url, title), - LinkType::WikiLink { has_pothole: _ } => { - // TODO: refactor - format!("\"{}\"", dest_url, title) - } - } +fn render_image(_link_type: LinkType, dest_url: &CowStr<'_>, title: &CowStr<'_>) -> String { + format!("\"{title}\"") } fn escape_html(text: &str) -> String { diff --git a/src/other.rs b/src/other.rs index 0e4a66e..f5e6e3b 100644 --- a/src/other.rs +++ b/src/other.rs @@ -8,8 +8,8 @@ use syntect::parsing::SyntaxSet; /// # Аргументы /// * `code` - Исходный код как строка. /// * `lang` - Идентификатор языка (например, "rust", "python", "mermaid"). -/// * `ss` - Набор синтаксисов (SyntaxSet). -/// * `ts` - Набор тем (ThemeSet). +/// * `ss` - Набор синтаксисов (`SyntaxSet`). +/// * `ts` - Набор тем (`ThemeSet`). /// /// # Возвращает /// Строку HTML, содержащую обертку блока кода с заголовком и кнопкой копирования. @@ -32,30 +32,28 @@ pub fn code_to_html(code: &str, lang: &str, ss: &SyntaxSet, ts: &ThemeSet) -> St let lang_display = if lang.is_empty() { "text" } else { lang }; let lang_escaped = escape_html(lang_display); - let highlighted_html = if !lang.is_empty() { - if let Some(syntax) = ss.find_syntax_by_token(lang) { - let mut h = HighlightLines::new(syntax, theme); - let mut result_html = String::new(); + let highlighted_html = if lang.is_empty() { + escape_html(code) + } else if let Some(syntax) = ss.find_syntax_by_token(lang) { + let mut h = HighlightLines::new(syntax, theme); + let mut result_html = String::new(); - for line in code.lines() { - let line_with_newline = format!("{line}\n"); + for line in code.lines() { + let line_with_newline = format!("{line}\n"); - match h.highlight_line(&line_with_newline, ss) { - Ok(regions) => { - match styled_line_to_highlighted_html(®ions[..], IncludeBackground::No) { - Ok(html_line) => result_html.push_str(&html_line), - Err(_) => result_html.push_str(&escape_html(&line_with_newline)), - } - } - Err(_) => { - result_html.push_str(&escape_html(&line_with_newline)); + match h.highlight_line(&line_with_newline, ss) { + Ok(regions) => { + match styled_line_to_highlighted_html(®ions[..], IncludeBackground::No) { + Ok(html_line) => result_html.push_str(&html_line), + Err(_) => result_html.push_str(&escape_html(&line_with_newline)), } } + Err(_) => { + result_html.push_str(&escape_html(&line_with_newline)); + } } - result_html - } else { - escape_html(code) } + result_html } else { escape_html(code) }; diff --git a/templates/base.html b/templates/base.html index 159ad38..cead784 100644 --- a/templates/base.html +++ b/templates/base.html @@ -55,6 +55,7 @@ overflow: hidden; background-color: #2b303b; } + .code-header { display: flex; justify-content: space-between;
"), Tag::TableRow => output.push_str("