diff --git a/CHANGELOG.md b/CHANGELOG.md index 0045818..ee98eb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning]. ### Added - New --link-only flag ([#30], courtesy of [@egrieco]). - Support links to markdown headings with inline code (backticks). +- New tag CASE_FRAG for fragments that match only case-insensitively. ### Other - Introduce GitHub Actions for push and pull\_request ([#27]), courtesy of [@sanxiyn]. diff --git a/README.md b/README.md index e3e138c..fd76a36 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ example_site/path/to/example.md:8: other.md#non-existing example_site/path/to/example.md:9: #heading example_site/path/to/example.md:10: #non-existing example_site/path/to/example.md:11: #heading-with-backticks +example_site/path/to/example.md:12: #HEADING ``` The output lists all the extracted links along with their respective @@ -66,6 +67,7 @@ example_site/path/to/example.md:8: NO_FRAG other.md#non-existing example_site/path/to/example.md:9: OK #heading example_site/path/to/example.md:10: NO_FRAG #non-existing example_site/path/to/example.md:11: OK #heading-with-backticks +example_site/path/to/example.md:12: CASE_FRAG #HEADING ``` A status token is now added to each line indicating the outcome of @@ -94,6 +96,7 @@ example_site/path/to/example.md:8: other.md#non-existing example_site/path/to/example.md:9: #heading example_site/path/to/example.md:10: #non-existing example_site/path/to/example.md:11: #heading-with-backticks +example_site/path/to/example.md:12: #HEADING example_site/path/to/follow.md:2: http://github.com/mattias-p/linky/blob/master/example_site/path/to/other.md example_site/path/to/follow.md:3: http://github.com/mattias-p/linky/blob/master/example_site/path/to/other.md#non-existing example_site/path/to/fragment.md:2: https://github.com/mattias-p/linky/blob/master/example_site/path/to/other.md#existing @@ -253,7 +256,12 @@ example_site/path/to/example.md:9: OK #heading WARN linky > context: link = /tmp/linky/example_site/path/to/example.md WARN linky > context: fragment = #non-existing example_site/path/to/example.md:10: NO_FRAG #non-existing -example_site/path/to/example.md:11: OK #heading-with-backticks +example_site/path/to/example.md:11: OK #heading-with-code + WARN linky > Fragment not found case-sensitively + WARN linky > context: link = /tmp/linky/example_site/path/to/example.md + WARN linky > context: fragment = #HEADING + WARN linky > context: anchor = #heading +example_site/path/to/example.md:12: CASE_FRAG #HEADING ``` diff --git a/example_site/path/to/example.md b/example_site/path/to/example.md index d72c2c3..25d587c 100644 --- a/example_site/path/to/example.md +++ b/example_site/path/to/example.md @@ -9,3 +9,4 @@ * [in-document link with fragment, ok](#heading) * [in-document link with fragment, broken](#non-existing) * [in-document link to heading with code, ok](#heading-with-code) +* [in-document link with case-insensitive fragment, broken](#HEADING) diff --git a/src/error.rs b/src/error.rs index ad05ef3..12047e1 100644 --- a/src/error.rs +++ b/src/error.rs @@ -28,6 +28,7 @@ pub enum Tag { UnrecognizedMime, DecodingError, Prefixed, + CaseInsensitiveFragment, } impl fmt::Display for Tag { @@ -48,6 +49,7 @@ impl fmt::Display for Tag { Tag::UnrecognizedMime => write!(f, "MIME"), Tag::DecodingError => write!(f, "DEC_ERR"), Tag::Prefixed => write!(f, "PREFIXED"), + Tag::CaseInsensitiveFragment => write!(f, "CASE_FRAG"), } } } @@ -69,6 +71,7 @@ impl FromStr for Tag { "NO_MIME" => Ok(Tag::NoMime), "MIME" => Ok(Tag::UnrecognizedMime), "PREFIXED" => Ok(Tag::Prefixed), + "CASE_FRAG" => Ok(Tag::CaseInsensitiveFragment), s if s.starts_with("HTTP_") => u16::from_str(&s[5..]) .ok() .and_then(|s| StatusCode::from_u16(s).ok()) @@ -177,6 +180,7 @@ impl fmt::Display for Error { Tag::UnrecognizedMime => write!(f, "Unrecognized mime type"), Tag::DecodingError => write!(f, "Decoding error"), Tag::Prefixed => write!(f, "Fragment not found without prefix"), + Tag::CaseInsensitiveFragment => write!(f, "Fragment not found case-sensitively"), } } } @@ -199,6 +203,7 @@ impl error::Error for Error { Tag::UnrecognizedMime => "unrecognized mime type", Tag::DecodingError => "decoding error", Tag::Prefixed => "prefixed fragmendt", + Tag::CaseInsensitiveFragment => "case-insensitive fragmendt", } } diff --git a/src/linky.rs b/src/linky.rs index 6bbd8ec..daa7e78 100644 --- a/src/linky.rs +++ b/src/linky.rs @@ -137,22 +137,38 @@ impl<'a> FragResolver<'a> { } pub fn fragment(&self, document: &Document, fragment: &str) -> Result<()> { - self.find_prefix(fragment, document) - .ok_or_else(|| { - Tag::NoFragment - .as_error() - .context(Cow::from(format!("fragment = #{fragment}"))) - }) - .and_then(|prefix| { - if prefix.is_empty() { - Ok(()) + match self.find_prefix(fragment, document) { + Some(prefix) => Ok(prefix), + None => { + let fragment_lc = fragment.to_lowercase(); + let temp = if fragment_lc != fragment { + self.find_prefix(&fragment_lc, document).map(|_| { + Err(Tag::CaseInsensitiveFragment + .as_error() + .context(Cow::from(format!("anchor = #{fragment_lc}"))) + .context(Cow::from(format!("fragment = #{fragment}")))) + }) } else { - Err(Tag::Prefixed + None + }; + match temp { + Some(res) => res, + None => Err(Tag::NoFragment .as_error() - .context(Cow::from(format!("prefix = {prefix}"))) - .context(Cow::from(format!("fragment = #{fragment}")))) + .context(Cow::from(format!("fragment = #{fragment}")))), } - }) + } + } + .and_then(|prefix| { + if prefix.is_empty() { + Ok(()) + } else { + Err(Tag::Prefixed + .as_error() + .context(Cow::from(format!("prefix = {prefix}"))) + .context(Cow::from(format!("fragment = #{fragment}")))) + } + }) } pub fn link( @@ -200,9 +216,7 @@ impl Client { pub fn new_follow() -> Self { let redirects = sync::Arc::new(sync::Mutex::new(vec![])); - let inner = reqwest::blocking::Client::builder() - .build() - .unwrap(); + let inner = reqwest::blocking::Client::builder().build().unwrap(); Client { inner, redirects } } @@ -578,6 +592,7 @@ mod tests { assert_eq!(parser.next(), Some((9, "#heading".into()))); assert_eq!(parser.next(), Some((10, "#non-existing".into()))); assert_eq!(parser.next(), Some((11, "#heading-with-code".into()))); + assert_eq!(parser.next(), Some((12, "#HEADING".into()))); assert_eq!(parser.next(), None); } diff --git a/src/main.rs b/src/main.rs index b8b9a57..a7680a4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -161,17 +161,17 @@ fn print_result( } } if link_only { - println!("{}", record.link); + println!("{}", record.link); } else { - println!( - "{}:{}: {} {}", - record.doc_path.to_string_lossy(), - record.doc_line, - tag.as_ref() - .map(|tag| tag as &dyn fmt::Display) - .unwrap_or(&"" as &dyn fmt::Display), - record.link - ); + println!( + "{}:{}: {} {}", + record.doc_path.to_string_lossy(), + record.doc_line, + tag.as_ref() + .map(|tag| tag as &dyn fmt::Display) + .unwrap_or(&"" as &dyn fmt::Display), + record.link + ); } } }