Skip to content
This repository was archived by the owner on May 11, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 16 additions & 16 deletions .pre-commit-hooks.yaml
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
- id: mdlinker
name: Markdown Linker
description: This hook checks that all markdown links are accounted for.
entry: mdlinker
language: rust
pass_filenames: false
- id: enforce-ascii
name: Enforce ASCII Compliance
description: Detects and replaces non-ASCII characters in specified files.
entry: ./bin/enforce-ascii
language: script
types: [text]
pass_filenames: true
always_run: false
args:
- id: mdlinker
name: Markdown Linker
description: This hook checks that all markdown links are accounted for.
entry: mdlinker
language: rust
pass_filenames: false
- id: enforce-ascii
name: Enforce ASCII Compliance
description: Detects and replaces non-ASCII characters in specified files.
entry: ./bin/enforce-ascii
language: script
types: [text]
pass_filenames: true
always_run: false
args:
- --fix
additional_dependencies: []
additional_dependencies: []
2 changes: 1 addition & 1 deletion rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[toolchain]
channel = "nightly"
channel = "1.91.0"
components = ["clippy", "rustfmt"]
40 changes: 17 additions & 23 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,16 @@ pub enum NewConfigError {
/// Used to reconcile the two
#[derive(Builder)]
pub struct Config {
#[allow(clippy::struct_field_names)]
file_config: file::Config,
#[allow(clippy::struct_field_names)]
cli_config: cli::Config,
/// See [`self::cli::Config::pages_directory`]
pub pages_directory: PathBuf,
/// See [`self::cli::Config::other_directories`]
/// See [`self::cli::Config::files`]
#[builder(default=vec![])]
pub other_directories: Vec<PathBuf>,
pub files: Vec<PathBuf>,
/// See [`self::cli::Config::root_directory`]
#[builder(default=PathBuf::from("."))]
pub new_files_directory: PathBuf,
/// See [`self::cli::Config::ngram_size`]
#[builder(default = 2)]
pub ngram_size: usize,
Expand Down Expand Up @@ -84,8 +87,8 @@ pub struct Config {
/// these can be unioned with one another
/// and then we can use that to create the final config
pub trait Partial {
fn pages_directory(&self) -> Option<PathBuf>;
fn other_directories(&self) -> Option<Vec<PathBuf>>;
fn files(&self) -> Option<Vec<PathBuf>>;
fn new_files_directory(&self) -> Option<PathBuf>;
fn ngram_size(&self) -> Option<usize>;
fn boundary_pattern(&self) -> Option<String>;
fn filename_spacing_pattern(&self) -> Option<String>;
Expand Down Expand Up @@ -162,18 +165,12 @@ fn combine_partials(
})
.maybe_fix(cli_config.fix().or(file_config.fix()))
.maybe_allow_dirty(cli_config.allow_dirty().or(file_config.allow_dirty()))
.pages_directory(
.files(
cli_config
.pages_directory()
.or(file_config.pages_directory())
.files()
.or(file_config.files())
.expect("A default is set"),
)
.maybe_other_directories(Some(
cli_config
.other_directories()
.or(file_config.other_directories())
.expect("A default is set"),
))
.maybe_ignore_word_pairs(
cli_config
.ignore_word_pairs()
Expand All @@ -192,9 +189,9 @@ impl Config {
///
/// # Errors
///
/// - [`Error::FileDoesNotExistError`] - Config file does not exist
/// - [`Error::FileDoesNotParseError`] - Config file does not parse from toml into the
/// expected format
/// - [`Error::FileDoesNotExistError`] - Config file does not exist
/// - [`Error::FileDoesNotParseError`] - Config file does not parse from toml into the
/// expected format
///
pub fn new() -> Result<Self, NewConfigError> {
let cli = cli::Config::parse();
Expand Down Expand Up @@ -227,11 +224,8 @@ impl Config {
/// Legacy directories function
/// Gets all the directories into one vec
#[must_use]
pub fn directories(&self) -> Vec<PathBuf> {
let mut out = Vec::new();
out.push(self.pages_directory.clone());
out.extend(self.other_directories.clone());
out
pub fn files(&self) -> &[PathBuf] {
&self.files
}

pub fn add_report_to_ignore(&mut self, report: &impl ReportTrait) {
Expand Down
27 changes: 13 additions & 14 deletions src/config/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ use super::Partial;
#[derive(Parser, Default, Clone)]
#[command(version, about, long_about = None)]
pub struct Config {
/// The pages directory is the directory where pages are named for their alias
/// and where new pages should be created when running --fix
#[clap(short = 'p', long = "pages")]
pub pages_directory: Option<PathBuf>,
/// Globs or paths to relevant files
#[clap()]
pub files: Vec<PathBuf>,

/// Other directories to search in
#[clap(short = 'd', long = "dir")]
pub other_directories: Vec<PathBuf>,
/// A location to store new files in created by --fix
/// for the [`super::rules::broken_wikilink::BrokenWikilink`] rule
#[clap(short = 'n', long = "newf")]
pub new_files_directory: Option<PathBuf>,

/// Path to a configuration file
#[clap(short = 'c', long = "config", default_value = "mdlinker.toml")]
Expand Down Expand Up @@ -67,17 +67,16 @@ pub struct Config {
}

impl Partial for Config {
fn pages_directory(&self) -> Option<PathBuf> {
self.pages_directory.clone()
}
fn other_directories(&self) -> Option<Vec<PathBuf>> {
let out = self.other_directories.clone();
if out.is_empty() {
fn files(&self) -> Option<Vec<PathBuf>> {
if self.files.is_empty() {
None
} else {
Some(out)
Some(self.files.clone())
}
}
fn new_files_directory(&self) -> Option<PathBuf> {
self.new_files_directory.clone()
}
fn ngram_size(&self) -> Option<usize> {
self.ngram_size
}
Expand Down
68 changes: 57 additions & 11 deletions src/config/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ use super::{Config as MasterConfig, NewConfigError, Partial};

#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct Config {
/// See [`super::cli::Config::pages_directory`]
pub pages_directory: PathBuf,
/// See [`super::cli::Config::files`]
#[serde(default)]
pub files: Option<Vec<String>>,

/// See [`super::cli::Config::other_directories`]
pub other_directories: Vec<PathBuf>,
/// See [`super::cli::Config::new_files_directory`]
#[serde(default)]
pub new_files_directory: Option<PathBuf>,

/// See [`super::cli::Config::ngram_size`]
#[serde(default)]
Expand Down Expand Up @@ -68,8 +70,14 @@ impl Config {
impl From<MasterConfig> for Config {
fn from(value: MasterConfig) -> Self {
Self {
pages_directory: value.pages_directory,
other_directories: value.other_directories,
files: Some(
value
.files
.into_iter()
.map(|file| file.to_string_lossy().to_string())
.collect(),
),
new_files_directory: Some(value.new_files_directory),
ngram_size: Some(value.ngram_size),
boundary_pattern: Some(value.boundary_pattern),
filename_spacing_pattern: Some(value.filename_spacing_pattern),
Expand All @@ -83,18 +91,56 @@ impl From<MasterConfig> for Config {
}

impl Partial for Config {
fn pages_directory(&self) -> Option<PathBuf> {
Some(self.pages_directory.clone())
}
fn other_directories(&self) -> Option<Vec<PathBuf>> {
let out = self.other_directories.clone();
fn files(&self) -> Option<Vec<PathBuf>> {
let mut out = Vec::new();
match &self.files {
None => return None,
Some(files) if files.is_empty() => return None,
Some(files) => {
for file in files {
if file.contains('*') {
let pattern = glob::Pattern::new(file);
match pattern {
Ok(_) => {
let globs = glob::glob(file);
match globs {
Ok(globs) => {
for glob in globs {
match glob {
Ok(path) => out.push(path),
Err(e) => {
eprintln!(
"Error processing glob '{file}': {e}",
);
}
}
}
}
Err(e) => {
eprintln!("Error parsing glob pattern '{file}': {e}");
}
}
}
Err(e) => {
eprintln!("Error parsing glob pattern '{file}': {e}");
return None;
}
}
}
}
}
}
if out.is_empty() {
None
} else {
Some(out)
}
}

fn new_files_directory(&self) -> Option<PathBuf> {
self.new_files_directory.clone()
}

fn ngram_size(&self) -> Option<usize> {
self.ngram_size
}
Expand Down
20 changes: 0 additions & 20 deletions src/file.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,4 @@
use std::path::PathBuf;

use walkdir::WalkDir;

use thiserror::Error;

use std;

pub mod content;
pub mod name;

/// Walk the directories and get just the files
pub fn get_files(dirs: &Vec<PathBuf>) -> Vec<PathBuf> {
let mut out = Vec::new();
for path in dirs {
let walk = WalkDir::new(path);
for entry in walk.into_iter().filter_map(Result::ok) {
if entry.file_type().is_file() {
out.push(entry.into_path());
}
}
}
out
}
11 changes: 7 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub mod sed;
pub mod visitor;

use console::{style, Emoji};
use file::{get_files, name::ngrams};
use file::name::ngrams;
use indicatif::ProgressBar;
use miette::{Diagnostic, Result};
use ngrams::CalculateError;
Expand Down Expand Up @@ -114,6 +114,7 @@ fn is_repo_dirty(repo: &Repository) -> Result<bool, Error> {
}

/// Runs [`check`] in a loop until no more fixes can be made
#[allow(clippy::result_large_err)]
fn fix(config: &config::Config) -> Result<OutputReport, OutputErrors> {
// Check if the git repo is dirty
match git2::Repository::open_from_env() {
Expand Down Expand Up @@ -145,7 +146,7 @@ fn fix(config: &config::Config) -> Result<OutputReport, OutputErrors> {
style("[1/3]").bold().dim(),
CHECK
);
};
}

let mut output_report = check(config)?;

Expand Down Expand Up @@ -191,7 +192,7 @@ fn fix(config: &config::Config) -> Result<OutputReport, OutputErrors> {
style("[3/3]").bold().dim(),
CHECK_AGAIN
);
};
}
output_report = check(config)?;
} else if env::var("RUNNING_TESTS").is_err() {
println!(
Expand All @@ -204,12 +205,13 @@ fn fix(config: &config::Config) -> Result<OutputReport, OutputErrors> {
Ok(output_report)
}

#[allow(clippy::result_large_err)]
fn check(config: &config::Config) -> Result<OutputReport, OutputErrors> {
// Compile our regex patterns
let boundary_regex = regex::Regex::new(&config.boundary_pattern)?;
let filename_spacing_regex = regex::Regex::new(&config.filename_spacing_pattern)?;

let all_files = get_files(&config.directories());
let all_files = config.files().to_vec();
let file_ngrams = ngrams(
&all_files,
config.ngram_size,
Expand Down Expand Up @@ -329,6 +331,7 @@ fn check(config: &config::Config) -> Result<OutputReport, OutputErrors> {
///
/// Basically if this library fails, this returns an Err
/// but if this library runs, even if it finds linting violations, this returns an Ok
#[allow(clippy::result_large_err)]
pub fn lib(config: &config::Config) -> Result<OutputReport, OutputErrors> {
if config.fix {
fix(config)
Expand Down
1 change: 0 additions & 1 deletion src/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,6 @@ fn dedupe_by_code<T: ReportTrait + PartialOrd>(mut this: Vec<T>) -> Vec<T> {

/// Used for filtering out items that start with the exclude code
impl<T: ReportTrait + PartialOrd> VecHasIdExtensions<T> for Vec<T> {
#[must_use]
fn finalize(self, excludes: &[ErrorCode]) -> Self {
dedupe_by_code(filter_by_excludes(self, excludes))
}
Expand Down
2 changes: 1 addition & 1 deletion src/rules/broken_wikilink.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ impl ReportTrait for BrokenWikilink {
self.src.name()
);
let filename = format!("{}.md", FilenameLowercase::from_alias(&self.alias, config));
let path = config.pages_directory.join(filename);
let path = config.new_files_directory.join(filename);
std::fs::write(path.clone(), "").map_err(|source| FixError::IOError {
source,
backtrace: Backtrace::force_capture(),
Expand Down
2 changes: 1 addition & 1 deletion src/rules/unlinked_text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ impl ReportTrait for UnlinkedText {
/// TODO: Be able to handle this in parallel with other reports
fn fix(&self, _config: &Config) -> Result<Option<()>, FixError> {
let file = self.src.name().to_owned();
trace!("Fixing unlinked text: {:?}", file);
trace!("Fixing unlinked text: {file:?}");
let mut source = std::fs::read_to_string(&file).map_err(|src| FixError::IOError {
source: src,
file: file.clone(),
Expand Down
4 changes: 2 additions & 2 deletions src/visitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ pub trait Visitor {
/// WARNING: Don't overwrite this, its already written for you.
/// Implement [`Self::_finalize_file`] instead
fn finalize_file(&mut self, source: &str, path: &Path) -> Result<(), FinalizeError> {
trace!("{:?} finalizing file {:?}", self.name(), path);
trace!("{:?} finalizing file {:?}", self.name(), path.display());
#[allow(clippy::used_underscore_items)]
self._finalize_file(source, path)
}
Expand Down Expand Up @@ -118,7 +118,7 @@ pub enum ParseError {
/// Parse the source code and visit all the nodes using tree-sitter
#[allow(clippy::result_large_err)]
pub fn parse(path: &PathBuf, visitors: Vec<Rc<RefCell<dyn Visitor>>>) -> Result<(), ParseError> {
debug!("Parsing file {:?}", path);
debug!("Parsing file {:?}", path.display());
let source = std::fs::read_to_string(path).map_err(|source| ParseError::IoError {
file: path.clone(),
source,
Expand Down
Loading
Loading