diff --git a/Cargo.lock b/Cargo.lock index cce6952..a13e75f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -122,6 +122,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "getopts" version = "0.2.23" @@ -149,6 +155,7 @@ version = "0.1.6" dependencies = [ "clap", "colored", + "dunce", "pathdiff", "pulldown-cmark", "regex", diff --git a/Cargo.toml b/Cargo.toml index c66bbf6..9f368cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ categories = ["command-line-utilities", "development-tools"] [dependencies] clap = { version = "4.5.47", features = ["derive"] } colored = "3.0.0" +dunce = "1.0.5" pathdiff = "0.2.3" pulldown-cmark = "0.13.0" regex = "1.11.2" diff --git a/README.md b/README.md index 05b9e68..d434f54 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,41 @@ # mdrefcheck +[![PyPI version](https://img.shields.io/pypi/v/mdrefcheck.svg?logo=pypi&logoColor=white)](https://pypi.org/project/mdrefcheck/) +[![crates.io version](https://img.shields.io/crates/v/mdrefcheck.svg?logo=rust&logoColor=white)](https://crates.io/crates/mdrefcheck) +[![Build Status](https://github.com/gospodima/mdrefcheck/actions/workflows/ci.yml/badge.svg)](https://github.com/gospodima/mdrefcheck/actions/workflows/ci.yml) +[![License](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) + A CLI tool to validate references and links in Markdown files (CommonMark spec). It helps to ensure that your documentation is free from broken section links, missing images or files. ## Features -- Validate local file paths in image and section references -- Check section links (`#heading-link`) match existing headings according to [GitHub Flavored Markdown (GFM)](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#section-links) rules -- Identify broken reference-style links -- Email validation +- Validate local file paths in image and file references +- Check section links against actual headings, following [GitHub Flavored Markdown (GFM)](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax#section-links) rules, including cross-file references (e.g. `./subfolder/another-file.md#heading-link`) +- Detect broken reference-style links +- Basic email validation ## Installation ### Cargo -`mdrefcheck` is also published on [crates.io](https://crates.io/) and can be installed -with cargo: - ```bash cargo install mdrefcheck ``` ### PyPI -`mdrefcheck` can be installed with - ```bash pip install mdrefcheck ``` -It also can be used as a tool in an isolated environment, e.g., with `uvx`: +or run it directly in an isolated environment, e.g., with `uvx`: ```bash uvx mdrefcheck . ``` -### Pre-commit integration - -You can use `mdrefcheck` as a pre-commit hook. +## Pre-commit integration Add this to your `.pre-commit-config.yaml`: diff --git a/src/checks/email.rs b/src/checks/email.rs index 86bbe0f..4a0195b 100644 --- a/src/checks/email.rs +++ b/src/checks/email.rs @@ -8,9 +8,8 @@ pub fn validate_email(email: &str) -> Result<(), String> { } } - /// Email validation according to https://spec.commonmark.org/0.31.2/#email-address fn is_valid_email(s: &str) -> bool { static EMAIL_RE: &str = r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"; Regex::new(EMAIL_RE).unwrap().is_match(s) -} \ No newline at end of file +} diff --git a/src/diagnostics.rs b/src/diagnostics.rs index e99a888..4efee57 100644 --- a/src/diagnostics.rs +++ b/src/diagnostics.rs @@ -1,6 +1,6 @@ use crate::utils::relative_path; -use std::path::Path; use colored::Colorize; +use std::path::Path; /// Represents a markdown validation issue (Ruff-compatible output) pub struct ValidationError { @@ -11,7 +11,12 @@ pub struct ValidationError { } impl ValidationError { - pub fn new(path: &Path, line: usize, col: usize, message: impl Into) -> Self { + pub fn new( + path: &Path, + line: usize, + col: usize, + message: impl Into, + ) -> Self { Self { path: relative_path(path), line, @@ -23,6 +28,13 @@ impl ValidationError { impl std::fmt::Display for ValidationError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}:{}:{}: {}", self.path.bold(), self.line, self.col, self.message) + write!( + f, + "{}:{}:{}: {}", + self.path.bold(), + self.line, + self.col, + self.message + ) } } diff --git a/src/utils.rs b/src/utils.rs index fdabd67..27b9c62 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -10,7 +10,7 @@ pub fn create_options() -> Options { Options::ENABLE_FOOTNOTES | Options::ENABLE_WIKILINKS } -/// Create HashSet of canonicalized paths from vector of paths +/// Create HashSet of canonicalized paths from vector of paths pub fn create_file_set(vec_files: &Vec) -> HashSet { vec_files .iter() @@ -18,11 +18,16 @@ pub fn create_file_set(vec_files: &Vec) -> HashSet { .collect() } -/// Return a path relative to current working directory +/// Return a path relative to the current working directory pub fn relative_path(target: &Path) -> String { let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from(".")); - pathdiff::diff_paths(target, cwd) - .unwrap_or_else(|| target.to_path_buf()) + + // Normalize target path first (fixes Windows \\?\ prefixes) + let normalized = + dunce::canonicalize(target).unwrap_or_else(|_| target.to_path_buf()); + + pathdiff::diff_paths(&normalized, cwd) + .unwrap_or(normalized) .display() .to_string() }