mirror of
https://github.com/Hopiu/lychee.git
synced 2026-03-31 11:50:23 +00:00
Refactor and add documentation
This commit is contained in:
parent
1474ae802b
commit
d0b7a64d0a
4 changed files with 115 additions and 99 deletions
100
src/checker.rs
100
src/checker.rs
|
|
@ -1,8 +1,7 @@
|
|||
use crate::{
|
||||
extract::{self, Uri},
|
||||
options::Config,
|
||||
types::{Excludes, RequestMethod, Status},
|
||||
};
|
||||
use anyhow::anyhow;
|
||||
use anyhow::{Context, Result};
|
||||
use check_if_email_exists::{check_email, CheckEmailInput};
|
||||
use headers::{HeaderMap, HeaderValue};
|
||||
|
|
@ -11,105 +10,10 @@ use indicatif::ProgressBar;
|
|||
use regex::{Regex, RegexSet};
|
||||
use reqwest::header;
|
||||
use std::net::IpAddr;
|
||||
use std::{collections::HashSet, convert::TryFrom, time::Duration};
|
||||
use std::{collections::HashSet, time::Duration};
|
||||
use tokio::time::delay_for;
|
||||
use url::Url;
|
||||
|
||||
pub(crate) enum RequestMethod {
|
||||
GET,
|
||||
HEAD,
|
||||
}
|
||||
|
||||
impl TryFrom<String> for RequestMethod {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
match value.to_lowercase().as_ref() {
|
||||
"get" => Ok(RequestMethod::GET),
|
||||
"head" => Ok(RequestMethod::HEAD),
|
||||
_ => Err(anyhow!("Only `get` and `head` allowed, got {}", value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Status {
|
||||
Ok(http::StatusCode),
|
||||
Failed(http::StatusCode),
|
||||
Timeout,
|
||||
Redirected,
|
||||
Excluded,
|
||||
Error(String),
|
||||
}
|
||||
|
||||
impl Status {
|
||||
pub fn new(statuscode: http::StatusCode, accepted: Option<HashSet<http::StatusCode>>) -> Self {
|
||||
if let Some(accepted) = accepted {
|
||||
if accepted.contains(&statuscode) {
|
||||
return Status::Ok(statuscode);
|
||||
}
|
||||
} else if statuscode.is_success() {
|
||||
return Status::Ok(statuscode);
|
||||
};
|
||||
if statuscode.is_redirection() {
|
||||
Status::Redirected
|
||||
} else {
|
||||
Status::Failed(statuscode)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_success(&self) -> bool {
|
||||
matches!(self, Status::Ok(_))
|
||||
}
|
||||
|
||||
pub fn is_excluded(&self) -> bool {
|
||||
matches!(self, Status::Excluded)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<reqwest::Error> for Status {
|
||||
fn from(e: reqwest::Error) -> Self {
|
||||
if e.is_timeout() {
|
||||
Status::Timeout
|
||||
} else {
|
||||
Status::Error(e.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Exclude configuration for the link checker.
|
||||
pub(crate) struct Excludes {
|
||||
regex: Option<RegexSet>,
|
||||
private_ips: bool,
|
||||
link_local_ips: bool,
|
||||
loopback_ips: bool,
|
||||
}
|
||||
|
||||
impl Excludes {
|
||||
pub fn from_options(config: &Config) -> Self {
|
||||
// exclude_all_private option turns on all "private" excludes,
|
||||
// including private IPs, link-local IPs and loopback IPs
|
||||
let enable_exclude = |opt| opt || config.exclude_all_private;
|
||||
|
||||
Self {
|
||||
regex: RegexSet::new(&config.exclude).ok(),
|
||||
private_ips: enable_exclude(config.exclude_private),
|
||||
link_local_ips: enable_exclude(config.exclude_link_local),
|
||||
loopback_ips: enable_exclude(config.exclude_loopback),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Excludes {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
regex: None,
|
||||
private_ips: false,
|
||||
link_local_ips: false,
|
||||
loopback_ips: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A link checker using an API token for Github links
|
||||
/// otherwise a normal HTTP client.
|
||||
pub(crate) struct Checker<'a> {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ use reqwest::Url;
|
|||
use std::path::Path;
|
||||
use std::{collections::HashSet, fs};
|
||||
|
||||
/// Detect if the given path points to a Markdown, HTML, or plaintext file.
|
||||
fn resolve_file_type_by_path<P: AsRef<Path>>(p: P) -> FileType {
|
||||
let path = p.as_ref();
|
||||
match path.extension() {
|
||||
|
|
@ -18,6 +19,8 @@ fn resolve_file_type_by_path<P: AsRef<Path>>(p: P) -> FileType {
|
|||
}
|
||||
}
|
||||
|
||||
/// Fetch all unique links from a vector of inputs
|
||||
/// All relative URLs get prefixed with `base_url` if given.
|
||||
pub(crate) async fn collect_links(
|
||||
inputs: Vec<String>,
|
||||
base_url: Option<String>,
|
||||
|
|
|
|||
|
|
@ -14,10 +14,12 @@ mod checker;
|
|||
mod collector;
|
||||
mod extract;
|
||||
mod options;
|
||||
mod types;
|
||||
|
||||
use checker::{Checker, Excludes, Status};
|
||||
use checker::Checker;
|
||||
use extract::Uri;
|
||||
use options::{Config, LycheeOptions};
|
||||
use types::{Excludes, Status};
|
||||
|
||||
/// A C-like enum that can be cast to `i32` and used as process exit code.
|
||||
enum ExitCode {
|
||||
|
|
|
|||
107
src/types.rs
Normal file
107
src/types.rs
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
use crate::options::Config;
|
||||
use anyhow::anyhow;
|
||||
use std::{collections::HashSet, convert::TryFrom};
|
||||
|
||||
use regex::RegexSet;
|
||||
|
||||
/// Specifies how requests to websites will be made
|
||||
pub(crate) enum RequestMethod {
|
||||
GET,
|
||||
HEAD,
|
||||
}
|
||||
|
||||
impl TryFrom<String> for RequestMethod {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||
match value.to_lowercase().as_ref() {
|
||||
"get" => Ok(RequestMethod::GET),
|
||||
"head" => Ok(RequestMethod::HEAD),
|
||||
_ => Err(anyhow!("Only `get` and `head` allowed, got {}", value)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Response status of the request
|
||||
#[derive(Debug)]
|
||||
pub enum Status {
|
||||
Ok(http::StatusCode),
|
||||
Failed(http::StatusCode),
|
||||
Timeout,
|
||||
Redirected,
|
||||
Excluded,
|
||||
Error(String),
|
||||
}
|
||||
|
||||
impl Status {
|
||||
pub fn new(statuscode: http::StatusCode, accepted: Option<HashSet<http::StatusCode>>) -> Self {
|
||||
if let Some(accepted) = accepted {
|
||||
if accepted.contains(&statuscode) {
|
||||
return Status::Ok(statuscode);
|
||||
}
|
||||
} else if statuscode.is_success() {
|
||||
return Status::Ok(statuscode);
|
||||
};
|
||||
if statuscode.is_redirection() {
|
||||
Status::Redirected
|
||||
} else {
|
||||
Status::Failed(statuscode)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_success(&self) -> bool {
|
||||
matches!(self, Status::Ok(_))
|
||||
}
|
||||
|
||||
pub fn is_excluded(&self) -> bool {
|
||||
matches!(self, Status::Excluded)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<reqwest::Error> for Status {
|
||||
fn from(e: reqwest::Error) -> Self {
|
||||
if e.is_timeout() {
|
||||
Status::Timeout
|
||||
} else {
|
||||
Status::Error(e.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Exclude configuration for the link checker.
|
||||
/// You can ignore links based on
|
||||
pub(crate) struct Excludes {
|
||||
pub regex: Option<RegexSet>,
|
||||
/// Example: 192.168.0.1
|
||||
pub private_ips: bool,
|
||||
/// Example: 169.254.0.0
|
||||
pub link_local_ips: bool,
|
||||
/// For IPv4: 127.0. 0.1/8
|
||||
/// For IPv6: ::1/128
|
||||
pub loopback_ips: bool,
|
||||
}
|
||||
|
||||
impl Excludes {
|
||||
pub fn from_options(config: &Config) -> Self {
|
||||
// exclude_all_private option turns on all "private" excludes,
|
||||
// including private IPs, link-local IPs and loopback IPs
|
||||
let enable_exclude = |opt| opt || config.exclude_all_private;
|
||||
|
||||
Self {
|
||||
regex: RegexSet::new(&config.exclude).ok(),
|
||||
private_ips: enable_exclude(config.exclude_private),
|
||||
link_local_ips: enable_exclude(config.exclude_link_local),
|
||||
loopback_ips: enable_exclude(config.exclude_loopback),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Excludes {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
regex: None,
|
||||
private_ips: false,
|
||||
link_local_ips: false,
|
||||
loopback_ips: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue