chore(refactor): add pre-commit rules and refactor with clippy

This commit is contained in:
thek4n 2026-02-21 12:46:32 +03:00
parent b277c95ba8
commit fe9d32ec19
3 changed files with 60 additions and 38 deletions

View File

@ -27,14 +27,14 @@ repos:
- id: clippy - id: clippy
name: clippy name: clippy
language: system language: system
entry: cargo clippy entry: cargo clippy -- -W clippy::all -W clippy::pedantic
pass_filenames: false pass_filenames: false
always_run: true always_run: true
- id: cargo-fmt - id: cargo-fmt
name: cargo fmt name: cargo fmt
language: system language: system
entry: cargo fmt entry: cargo fmt --check
pass_filenames: false pass_filenames: false
always_run: true always_run: true

22
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,22 @@
# Contributing Guide
## <a name="commit-message-format"></a> Commit message format
We use [this](https://www.conventionalcommits.org/en/v1.0.0/) standard
```gitcommit
<type>(<scope>): <subject>
<BLANK LINE>
[optional body]
<BLANK LINE>
[optional footer]
```
## <a name="pre-commit"></a> Setting Up Pre-Commit Hooks
We use pre-commit to automatically enforce code quality checks before commits:
```sh
python3 -m venv venv
. ./venv/bin/activate
pip install pre-commit
pre-commit install
pre-commit install --hook-type commit-msg
```
Hooks will now run automatically on every commit.

View File

@ -9,6 +9,7 @@ use futures::StreamExt;
use notify::{Config, EventKind, RecommendedWatcher, RecursiveMode, Watcher}; use notify::{Config, EventKind, RecommendedWatcher, RecursiveMode, Watcher};
use pulldown_cmark::{CodeBlockKind, Event, Options, Tag, html}; use pulldown_cmark::{CodeBlockKind, Event, Options, Tag, html};
use std::convert::Infallible; use std::convert::Infallible;
use std::fmt::Write;
use std::net::{SocketAddr, ToSocketAddrs}; use std::net::{SocketAddr, ToSocketAddrs};
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
@ -97,14 +98,14 @@ async fn main() {
let addr = resolve_addr(&args.host, args.port).unwrap(); let addr = resolve_addr(&args.host, args.port).unwrap();
println!("Сервер запущен на http://{}", addr); println!("Сервер запущен на http://{addr}");
let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
axum::serve(listener, app).await.unwrap(); axum::serve(listener, app).await.unwrap();
} }
fn resolve_addr(host: &str, port: u16) -> io::Result<SocketAddr> { fn resolve_addr(host: &str, port: u16) -> io::Result<SocketAddr> {
let addr_str = format!("{}:{}", host, port); let addr_str = format!("{host}:{port}");
addr_str addr_str
.to_socket_addrs()? .to_socket_addrs()?
@ -178,25 +179,26 @@ async fn serve_file(
requested_path.push(&full_path); requested_path.push(&full_path);
// Безопасность путей // Безопасность путей
let safe_path = match fs::canonicalize(&requested_path).await { let Ok(safe_path) = fs::canonicalize(&requested_path).await else {
Ok(p) => p, return Err(StatusCode::NOT_FOUND);
Err(_) => return Err(StatusCode::NOT_FOUND),
}; };
let base_dir = match fs::canonicalize(&state.root).await { let Ok(base_dir) = fs::canonicalize(&state.root).await else {
Ok(p) => p, return Err(StatusCode::INTERNAL_SERVER_ERROR);
Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR),
}; };
if !safe_path.starts_with(&base_dir) { if !safe_path.starts_with(&base_dir) {
eprintln!("Попытка выхода за пределы директории: {:?}", safe_path); eprintln!(
"Попытка выхода за пределы директории: {}",
safe_path.display()
);
return Err(StatusCode::FORBIDDEN); return Err(StatusCode::FORBIDDEN);
} }
let metadata = match fs::metadata(&safe_path).await { let metadata = match fs::metadata(&safe_path).await {
Ok(m) => m, Ok(m) => m,
Err(e) => { Err(e) => {
eprintln!("Ошибка получения метаданных: {}", e); eprintln!("Ошибка получения метаданных: {e}");
return Err(StatusCode::INTERNAL_SERVER_ERROR); return Err(StatusCode::INTERNAL_SERVER_ERROR);
} }
}; };
@ -208,7 +210,7 @@ async fn serve_file(
let content = match fs::read_to_string(&safe_path).await { let content = match fs::read_to_string(&safe_path).await {
Ok(c) => c, Ok(c) => c,
Err(e) => { Err(e) => {
eprintln!("Ошибка чтения: {}", e); eprintln!("Ошибка чтения: {e}");
return Err(StatusCode::INTERNAL_SERVER_ERROR); return Err(StatusCode::INTERNAL_SERVER_ERROR);
} }
}; };
@ -219,7 +221,7 @@ async fn serve_file(
if parent.is_empty() { if parent.is_empty() {
"/".to_string() "/".to_string()
} else { } else {
format!("/{}", parent) format!("/{parent}")
} }
} else { } else {
"/".to_string() "/".to_string()
@ -227,12 +229,11 @@ async fn serve_file(
let back_button_html = format!( let back_button_html = format!(
r#"<div style="margin-bottom: 20px;"> r#"<div style="margin-bottom: 20px;">
<a href="{}" style="display: inline-flex; align-items: center; color: #90a4ae; text-decoration: none; font-size: 0.95em; transition: color 0.2s;" <a href="{back_link}" style="display: inline-flex; align-items: center; color: #90a4ae; text-decoration: none; font-size: 0.95em; transition: color 0.2s;"
onmouseover="this.style.color='#ffffff'" onmouseout="this.style.color='#90a4ae'"> onmouseover="this.style.color='#ffffff'" onmouseout="this.style.color='#90a4ae'">
<span style="margin-right: 8px; font-size: 1.2em;"></span> Назад <span style="margin-right: 8px; font-size: 1.2em;"></span> Назад
</a> </a>
</div>"#, </div>"#
back_link
); );
let html_content = markdown_to_html(&content, &state.syntax_set, &state.theme_set, &full_path); let html_content = markdown_to_html(&content, &state.syntax_set, &state.theme_set, &full_path);
@ -240,7 +241,7 @@ async fn serve_file(
// Заполнение шаблона // Заполнение шаблона
let final_html = TEMPLATE_FILE let final_html = TEMPLATE_FILE
.replace("{{CONTENT}}", &html_content) .replace("{{CONTENT}}", &html_content)
.replace("{{SSE_URL}}", &format!("/events/{}", full_path)) .replace("{{SSE_URL}}", &format!("/events/{full_path}"))
.replace("{{BACK_BUTTON}}", &back_button_html); .replace("{{BACK_BUTTON}}", &back_button_html);
Ok(Html(final_html)) Ok(Html(final_html))
@ -253,7 +254,7 @@ async fn render_directory_index(
let mut entries = match fs::read_dir(dir_path).await { let mut entries = match fs::read_dir(dir_path).await {
Ok(list) => list, Ok(list) => list,
Err(e) => { Err(e) => {
eprintln!("Ошибка чтения директории: {}", e); eprintln!("Ошибка чтения директории: {e}");
return Err(StatusCode::FORBIDDEN); return Err(StatusCode::FORBIDDEN);
} }
}; };
@ -291,24 +292,25 @@ async fn render_directory_index(
let mut list_html = String::from("<ul>"); let mut list_html = String::from("<ul>");
if !request_path.is_empty() && request_path != "files" { if !request_path.is_empty() && request_path != "files" {
let parent_path = request_path.rsplit_once('/').map(|(p, _)| p).unwrap_or(""); let parent_path = request_path.rsplit_once('/').map_or("", |(p, _)| p);
let parent_link = if parent_path.is_empty() { let parent_link = if parent_path.is_empty() {
"/".to_string() "/".to_string()
} else { } else {
format!("/{}", parent_path) format!("/{parent_path}")
}; };
list_html.push_str(&format!( let _ = write!(
list_html,
r#"<li> r#"<li>
<a href="{}" class="back-link">📁 ..</a> <a href="{parent_link}" class="back-link">📁 ..</a>
</li>"#, </li>"#
parent_link );
));
} }
for (name, link, is_dir) in files { for (name, link, is_dir) in files {
let icon = if is_dir { "📁" } else { "📄" }; let icon = if is_dir { "📁" } else { "📄" };
list_html.push_str(&format!( let _ = write!(
list_html,
r#"<li> r#"<li>
<a href="/{}" class="file-link"> <a href="/{}" class="file-link">
<span class="icon">{}</span> <span class="icon">{}</span>
@ -318,7 +320,7 @@ async fn render_directory_index(
link.trim_start_matches('/'), link.trim_start_matches('/'),
icon, icon,
name name
)); );
} }
list_html.push_str("</ul>"); list_html.push_str("</ul>");
@ -355,7 +357,7 @@ async fn run_file_watcher(state: AppState) {
let watch_path = PathBuf::from("./notes"); let watch_path = PathBuf::from("./notes");
if let Err(e) = watcher.watch(&watch_path, RecursiveMode::Recursive) { if let Err(e) = watcher.watch(&watch_path, RecursiveMode::Recursive) {
eprintln!("Ошибка настройки watcher: {}", e); eprintln!("Ошибка настройки watcher: {e}");
return; return;
} }
@ -406,9 +408,8 @@ fn markdown_to_html(markdown: &str, ss: &SyntaxSet, ts: &ThemeSet, _file_path: &
<span class="code-lang">Mermaid Diagram</span> <span class="code-lang">Mermaid Diagram</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button> <button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div> </div>
<div class="mermaid" style="background: transparent; padding: 20px; text-align: center;">{}</div> <div class="mermaid" style="background: transparent; padding: 20px; text-align: center;">{escaped_code}</div>
</div>"#, </div>"#
escaped_code
); );
processed_events.push(Event::Html(mermaid_html.into())); processed_events.push(Event::Html(mermaid_html.into()));
} else { } else {
@ -420,7 +421,7 @@ fn markdown_to_html(markdown: &str, ss: &SyntaxSet, ts: &ThemeSet, _file_path: &
let mut h = HighlightLines::new(syntax, theme); let mut h = HighlightLines::new(syntax, theme);
let mut result_html = String::new(); let mut result_html = String::new();
for line in current_code.lines() { for line in current_code.lines() {
let line_with_newline = format!("{}\n", line); let line_with_newline = format!("{line}\n");
match h.highlight_line(&line_with_newline, ss) { match h.highlight_line(&line_with_newline, ss) {
Ok(regions) => { Ok(regions) => {
let html_line = styled_line_to_highlighted_html( let html_line = styled_line_to_highlighted_html(
@ -431,7 +432,7 @@ fn markdown_to_html(markdown: &str, ss: &SyntaxSet, ts: &ThemeSet, _file_path: &
result_html.push_str(&html_line); result_html.push_str(&html_line);
} }
Err(_) => { Err(_) => {
result_html.push_str(&escape_html(&line_with_newline)) result_html.push_str(&escape_html(&line_with_newline));
} }
} }
} }
@ -446,12 +447,11 @@ fn markdown_to_html(markdown: &str, ss: &SyntaxSet, ts: &ThemeSet, _file_path: &
let code_container = format!( let code_container = format!(
r#"<div class="code-block-wrapper"> r#"<div class="code-block-wrapper">
<div class="code-header"> <div class="code-header">
<span class="code-lang">{}</span> <span class="code-lang">{lang_escaped}</span>
<button class="copy-btn" onclick="copyCode(this)">Copy</button> <button class="copy-btn" onclick="copyCode(this)">Copy</button>
</div> </div>
<pre style="margin: 0; border-radius: 0 0 6px 6px;"><code>{}</code></pre> <pre style="margin: 0; border-radius: 0 0 6px 6px;"><code>{highlighted_html}</code></pre>
</div>"#, </div>"#
lang_escaped, highlighted_html
); );
processed_events.push(Event::Html(code_container.into())); processed_events.push(Event::Html(code_container.into()));
} }