initial commit

This commit is contained in:
Hampus Kraft
2026-01-01 20:42:59 +00:00
commit 2f557eda8c
9029 changed files with 1490197 additions and 0 deletions

381
scripts/license-enforcer/Cargo.lock generated Normal file
View File

@@ -0,0 +1,381 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "autocfg"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
[[package]]
name = "bumpalo"
version = "3.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43"
[[package]]
name = "cc"
version = "1.2.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac9fe6cdbb24b6ade63616c0a0688e45bb56732262c158df3c0c4bea4ca47cb7"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "chrono"
version = "0.4.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
dependencies = [
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-link",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "find-msvc-tools"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52051878f80a721bb68ebfbc930e07b65ba72f2da88968ea5c06fd6ca3d3a127"
[[package]]
name = "iana-time-zone"
version = "0.1.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"log",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "js-sys"
version = "0.3.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec48937a97411dcb524a265206ccd4c90bb711fca92b2792c407f268825b9305"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.177"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
[[package]]
name = "license-enforcer"
version = "0.1.0"
dependencies = [
"chrono",
"regex",
"walkdir",
]
[[package]]
name = "log"
version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]]
name = "memchr"
version = "2.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "once_cell"
version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "proc-macro2"
version = "1.0.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89ae43fd86e4158d6db51ad8e2b80f313af9cc74f5c0e03ccb87de09998732de"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.41"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce25767e7b499d1b604768e7cde645d14cc8584231ea6b295e9c9eb22c02e1d1"
dependencies = [
"proc-macro2",
]
[[package]]
name = "regex"
version = "1.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "syn"
version = "2.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a26dbd934e5451d21ef060c018dae56fc073894c5a7896f882928a76e6d081b"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f63a545481291138910575129486daeaf8ac54aee4387fe7906919f7830c7d9d"
[[package]]
name = "walkdir"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasm-bindgen"
version = "0.2.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1da10c01ae9f1ae40cbfac0bac3b1e724b320abfcf52229f80b547c0d250e2d"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "671c9a5a66f49d8a47345ab942e2cb93c7d1d0339065d4f8139c486121b43b19"
dependencies = [
"bumpalo",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ca60477e4c59f5f2986c50191cd972e3a50d8a95603bc9434501cf156a9a119"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f07d2f20d4da7b26400c9f4a0511e6e0345b040694e8a75bd41d578fa4421d7"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bad67dc8b2a1a6e5448428adec4c3e84c43e561d8c9ee8a9e5aabeb193ec41d1"
dependencies = [
"unicode-ident",
]
[[package]]
name = "winapi-util"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
"windows-sys",
]
[[package]]
name = "windows-core"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link",
"windows-result",
"windows-strings",
]
[[package]]
name = "windows-implement"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-interface"
version = "0.59.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-result"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-strings"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
dependencies = [
"windows-link",
]
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]

View File

@@ -0,0 +1,15 @@
[package]
name = "license-enforcer"
version = "0.1.0"
edition = "2024"
[dependencies]
walkdir = "2.4"
regex = "1.10"
chrono = "0.4"
[profile.release]
lto = true
panic = "abort"
codegen-units = 1
opt-level = "z"

View File

@@ -0,0 +1,426 @@
#![allow(clippy::four_forward_slashes)]
/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
use regex::Regex;
use std::fs;
use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf};
use walkdir::WalkDir;
const TS_LICENSE_HEADER: &str = r"/*
* Copyright (C) {year} Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/";
const ERLANG_LICENSE_HEADER: &str = r"%% Copyright (C) {year} Fluxer Contributors
%%
%% This file is part of Fluxer.
%%
%% Fluxer is free software: you can redistribute it and/or modify
%% it under the terms of the GNU Affero General Public License as published by
%% the Free Software Foundation, either version 3 of the License, or
%% (at your option) any later version.
%%
%% Fluxer is distributed in the hope that it will be useful,
%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
%% GNU Affero General Public License for more details.
%%
%% You should have received a copy of the GNU Affero General Public License
%% along with Fluxer. If not, see <https://www.gnu.org/licenses/>.";
const GLEAM_LICENSE_HEADER: &str = r"//// Copyright (C) {year} Fluxer Contributors
////
//// This file is part of Fluxer.
////
//// Fluxer is free software: you can redistribute it and/or modify
//// it under the terms of the GNU Affero General Public License as published by
//// the Free Software Foundation, either version 3 of the License, or
//// (at your option) any later version.
////
//// Fluxer is distributed in the hope that it will be useful,
//// but WITHOUT ANY WARRANTY; without even the implied warranty of
//// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//// GNU Affero General Public License for more details.
////
//// You should have received a copy of the GNU Affero General Public License
//// along with Fluxer. If not, see <https://www.gnu.org/licenses/>.";
const SHELL_LICENSE_HEADER: &str = r"# Copyright (C) {year} Fluxer Contributors
#
# This file is part of Fluxer.
#
# Fluxer is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Fluxer is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with Fluxer. If not, see <https://www.gnu.org/licenses/>.";
const BLOCK_COMMENT_EXTS: &[&str] = &[
"ts", "tsx", "js", "jsx", "mjs", "cjs", "css", "go", "rs", "c", "cc", "cpp", "cxx", "h", "hh",
"hpp", "hxx", "mm", "m", "java", "kt", "kts", "swift", "scala", "dart", "cs", "fs",
];
const HASH_LINE_EXTS: &[&str] = &[
"sh", "bash", "zsh", "py", "rb", "ps1", "psm1", "psd1", "ksh", "fish",
];
#[derive(Clone, Copy)]
enum HeaderStyle {
Block,
Line(&'static str),
}
#[derive(Clone, Copy)]
struct FileTemplate {
header: &'static str,
style: HeaderStyle,
}
impl FileTemplate {
const fn new(header: &'static str, style: HeaderStyle) -> Self {
Self { header, style }
}
}
struct Processor {
current_year: i32,
updated: usize,
ignore_patterns: Vec<String>,
}
impl Processor {
fn new() -> Self {
let current_year = chrono::Datelike::year(&chrono::Utc::now());
let mut processor = Processor {
current_year,
updated: 0,
ignore_patterns: Vec::new(),
};
processor.load_gitignore();
processor
}
fn load_gitignore(&mut self) {
if let Ok(file) = fs::File::open(".gitignore") {
let reader = BufReader::new(file);
for line in reader.lines().map_while(Result::ok) {
let trimmed = line.trim();
if !trimmed.is_empty() && !trimmed.starts_with('#') {
self.ignore_patterns.push(trimmed.to_string());
}
}
}
}
fn should_ignore(&self, path: &str) -> bool {
if path.contains("fluxer_static") {
return true;
}
for pattern in &self.ignore_patterns {
if self.match_pattern(pattern, path) {
return true;
}
}
false
}
fn match_pattern(&self, pattern: &str, path: &str) -> bool {
if let Some(sub_pattern) = pattern.strip_prefix("**/") {
if sub_pattern.ends_with('/') {
let dir_name = sub_pattern.trim_end_matches('/');
return path
.split(std::path::MAIN_SEPARATOR)
.any(|part| part == dir_name);
}
return path
.split(std::path::MAIN_SEPARATOR)
.any(|part| part == sub_pattern);
}
if pattern.ends_with('/') {
let dir_pattern = pattern.trim_end_matches('/');
return path
.split(std::path::MAIN_SEPARATOR)
.any(|part| part == dir_pattern)
|| path.starts_with(&format!("{dir_pattern}/"));
}
if let Some(p) = pattern.strip_prefix('/') {
return path == p;
}
path.split(std::path::MAIN_SEPARATOR)
.any(|part| part == pattern)
|| Path::new(path).file_name().and_then(|f| f.to_str()) == Some(pattern)
}
fn is_target_file(&self, path: &Path) -> bool {
self.get_template(path).is_some()
}
fn get_template(&self, path: &Path) -> Option<FileTemplate> {
path.extension()
.and_then(|ext| ext.to_str())
.and_then(Self::template_for_extension)
}
fn template_for_extension(ext: &str) -> Option<FileTemplate> {
let normalized = ext.to_ascii_lowercase();
if BLOCK_COMMENT_EXTS.contains(&normalized.as_str()) {
Some(FileTemplate::new(TS_LICENSE_HEADER, HeaderStyle::Block))
} else if HASH_LINE_EXTS.contains(&normalized.as_str()) {
Some(FileTemplate::new(
SHELL_LICENSE_HEADER,
HeaderStyle::Line("#"),
))
} else {
match normalized.as_str() {
"gleam" => Some(FileTemplate::new(
GLEAM_LICENSE_HEADER,
HeaderStyle::Line("////"),
)),
"erl" | "hrl" => Some(FileTemplate::new(
ERLANG_LICENSE_HEADER,
HeaderStyle::Line("%%"),
)),
_ => None,
}
}
}
fn detect_license(&self, content: &str) -> (bool, Option<i32>) {
let lines: Vec<&str> = content.lines().take(25).collect();
let mut has_agpl = false;
let mut has_fluxer = false;
let mut detected_year = None;
let year_regex = Regex::new(r"\b(20\d{2})\b").unwrap();
for line in lines {
let lower = line.to_lowercase();
if lower.contains("gnu affero general public license") || lower.contains("agpl") {
has_agpl = true;
}
if lower.contains("fluxer") {
has_fluxer = true;
}
if lower.contains("copyright")
&& lower.contains("fluxer")
&& detected_year.is_none()
&& let Some(cap) = year_regex.captures(line)
&& let Ok(year) = cap[1].parse::<i32>()
&& (1900..3000).contains(&year)
{
detected_year = Some(year);
}
}
(has_agpl && has_fluxer, detected_year)
}
fn update_year(&self, content: &str, old_year: i32) -> String {
content.replacen(&old_year.to_string(), &self.current_year.to_string(), 1)
}
fn strip_license_header(&self, content: &str, style: HeaderStyle) -> (String, bool) {
let lines: Vec<&str> = content.split('\n').collect();
if lines.is_empty() {
return (content.to_string(), false);
}
let mut prefix_end = 0;
if let Some(first) = lines.get(0) {
if first.starts_with("#!") {
prefix_end = 1;
}
}
let mut header_start = prefix_end;
while header_start < lines.len() && lines[header_start].trim().is_empty() {
header_start += 1;
}
if header_start >= lines.len() {
return (content.to_string(), false);
}
let original_ending = content.ends_with('\n');
let after_idx = match style {
HeaderStyle::Block => {
let first = lines[header_start].trim_start();
if !first.starts_with("/*") {
return (content.to_string(), false);
}
let mut header_end = header_start;
let mut found_end = false;
for i in header_start..lines.len() {
if lines[i].contains("*/") {
header_end = i;
found_end = true;
break;
}
}
if !found_end {
return (content.to_string(), false);
}
let mut after = header_end + 1;
while after < lines.len() && lines[after].trim().is_empty() {
after += 1;
}
after
}
HeaderStyle::Line(prefix) => {
let first = lines[header_start].trim_start();
if !first.starts_with(prefix) {
return (content.to_string(), false);
}
let mut header_end = header_start;
while header_end < lines.len() {
let trimmed = lines[header_end].trim_start();
if trimmed.is_empty() {
break;
}
if trimmed.starts_with(prefix) {
header_end += 1;
continue;
}
break;
}
let mut after = header_end;
while after < lines.len() && lines[after].trim().is_empty() {
after += 1;
}
after
}
};
let mut new_lines = Vec::new();
new_lines.extend_from_slice(&lines[..prefix_end]);
new_lines.extend_from_slice(&lines[after_idx..]);
let mut result = new_lines.join("\n");
if original_ending && !result.ends_with('\n') {
result.push('\n');
}
(result, true)
}
fn add_header(&self, content: &str, template: FileTemplate) -> String {
let header = template
.header
.replace("{year}", &self.current_year.to_string());
if let Some(first_line) = content.lines().next()
&& first_line.starts_with("#!")
{
let rest = content.lines().skip(1).collect::<Vec<_>>().join("\n");
return format!("{first_line}\n\n{header}\n\n{rest}");
}
format!("{header}\n\n{content}")
}
fn process_file(&mut self, path: &Path) -> Result<(), Box<dyn std::error::Error>> {
let content = fs::read_to_string(path)?;
let template = self.get_template(path).ok_or("Unknown file type")?;
let (has_header, detected_year) = self.detect_license(&content);
let (new_content, action) = if !has_header {
(self.add_header(&content, template), "Added header")
} else {
let (stripped, stripped_ok) = self.strip_license_header(&content, template.style);
if stripped_ok {
(self.add_header(&stripped, template), "Normalized header")
} else if let Some(old_year) = detected_year {
if old_year == self.current_year {
return Ok(());
}
(
self.update_year(&content, old_year),
&format!("Updated year {}{}", old_year, self.current_year) as &str,
)
} else {
return Ok(());
}
};
fs::write(path, new_content)?;
self.updated += 1;
println!("{}: {}", action, path.display());
Ok(())
}
fn walk(&mut self) -> Result<(), Box<dyn std::error::Error>> {
let paths: Vec<PathBuf> = WalkDir::new(".")
.into_iter()
.filter_map(std::result::Result::ok)
.filter(|e| {
let path = e.path();
let path_str = path.to_string_lossy();
!self.should_ignore(&path_str)
&& e.file_type().is_file()
&& self.is_target_file(path)
})
.map(|e| e.path().to_path_buf())
.collect();
for path in paths {
if let Err(e) = self.process_file(&path) {
eprintln!("Error processing {path:?}: {e}");
}
}
Ok(())
}
}
fn main() {
let mut processor = Processor::new();
if let Err(e) = processor.walk() {
eprintln!("Error: {e}");
std::process::exit(1);
}
println!("Updated {} files", processor.updated);
}