I used Thaura to write this. It traverses directories and lists files that don’t match a simple naming convention. The naming conventions are applied based on config files in each directory.
#!/usr/bin/env python3
"""
tree_validator.py
A tool to validate directory naming conventions based on _naming.ini configurations.
This tool descends directory trees, reads _naming.ini files, and applies
the correct validation rules for each directory.
"""
import os
import re
import sys
from pathlib import Path
from typing import List, Pattern
# Pre-compiled regex patterns for better performance
DATE_REGEX: Pattern = re.compile(r'^\d{4}-\d{2}(-\d{2})? ')
YEAR_REGEX: Pattern = re.compile(r'^\d{4} ')
VERSION_REGEX: Pattern = re.compile(r'.* v\d+(-\d+)*(-\d+)*.*$')
PREFIX_REGEX: Pattern = re.compile(r'^\d+ |[A-Z].* ')
def get_files_in_directory(dir_path: Path) -> List[Path]:
"""Get all files and directories in a directory, excluding hidden files and _naming.ini."""
files = []
try:
for item in dir_path.iterdir():
if item.name == '_naming.ini' or item.name.startswith('.'):
continue
files.append(item)
except PermissionError:
pass
return files
def validate_directory(dir_path: Path) -> None:
"""Validate a directory based on its _naming.ini configuration."""
naming_file = dir_path / '_naming.ini'
if not naming_file.exists():
return
try:
config_lines = naming_file.read_text().strip().split('\n')
except (IOError, UnicodeDecodeError):
return
for config_line in config_lines:
config_line = config_line.strip().upper()
if not config_line:
continue
files = get_files_in_directory(dir_path)
if config_line == "DATE_PREFIXED":
invalid_files = [f for f in files if not DATE_REGEX.match(f.name)]
elif config_line == "YEAR_PREFIXED":
invalid_files = [f for f in files if not YEAR_REGEX.match(f.name)]
elif config_line == "VERSION_SUFFIXED":
invalid_files = [f for f in files if not VERSION_REGEX.match(f.name)]
elif config_line == "PREFIXED":
invalid_files = [f for f in files if not PREFIX_REGEX.match(f.name)]
else:
continue
for invalid_file in invalid_files:
print(str(invalid_file))
def traverse_directory(root_path: Path) -> None:
"""Recursively traverse directory tree and validate each directory."""
for dir_path in root_path.rglob('*'):
if dir_path.is_dir():
validate_directory(dir_path)
def main() -> None:
"""Main entry point."""
if len(sys.argv) != 2:
print("Usage: tree_validator <directory>")
print("Validates directory tree based on _naming.ini configurations")
print("\nTo have a directory's contents checked make a file named")
print("_naming.ini in the directory, and set the contents to one of")
print("the following strings: PREFIXED, DATE_PREFIXED, YEAR_PREFIXED,")
print("VERSION_SUFFIXED.")
print()
print("Version numbers are in the format v10-1-2, v10-1, or v6.")
print("It's similar to semver, but with dashes, not dots."
sys.exit(1)
target_dir = Path(sys.argv[1])
if not target_dir.is_dir():
print(f"Directory not found: {target_dir}")
sys.exit(1)
traverse_directory(target_dir)
if __name__ == "__main__":
main()
Code language: Python (python)
This is a script to generate test data:
#!/bin/bash
# create_test_files.sh
# Creates test files for validation testing
create_test_dir() {
local dir="$1"
local naming_type="$2"
mkdir -p "$dir"
# Create _naming.ini
echo "$naming_type" > "$dir/_naming.ini"
# Create test files based on naming convention
case "$naming_type" in
"DATE_PREFIXED")
touch "$dir/2023-01-15 Valid File.txt"
touch "$dir/2023-02 Another Valid.jpg"
touch "$dir/2023-03-01 Third Valid.pdf"
touch "$dir/invalid-name.txt"
touch "$dir/2023-13 Invalid Month.txt"
touch "$dir/2023-00 Invalid Month.txt"
touch "$dir/2023-05-32 Invalid Day.txt"
;;
"YEAR_PREFIXED")
touch "$dir/2023 Valid File.txt"
touch "$dir/2024 Another Valid.jpg"
touch "$dir/1999 Old File.pdf"
touch "$dir/invalid-name.txt"
touch "$dir/abc123.txt"
touch "$dir/23-short.txt"
;;
"VERSION_SUFFIXED")
touch "$dir/Project v1-2-3.txt"
touch "$dir/Document v10-5-1.jpg"
touch "$dir/Config v2-0-0.pdf"
touch "$dir/invalid-name.txt"
touch "$dir/Project v1-2.txt"
touch "$dir/Project v1-2-3-4.txt"
;;
"PREFIXED")
touch "$dir/123 Valid File.txt"
touch "$dir/ABC Another Valid.jpg"
touch "$dir/456 Third Valid.pdf"
touch "$dir/invalid-name.txt"
touch "$dir/file1.txt"
touch "$dir/123file.txt"
;;
esac
}
create_test_tree() {
local root="$1"
# Create directories with different naming conventions
create_test_dir "$root/project1" "DATE_PREFIXED"
create_test_dir "$root/project2" "YEAR_PREFIXED"
create_test_dir "$root/project3" "VERSION_SUFFIXED"
create_test_dir "$root/project4" "PREFIXED"
# Create nested directory with same convention
create_test_dir "$root/project1/subdir" "DATE_PREFIXED"
create_test_dir "$root/project2/subdir" "YEAR_PREFIXED"
# Create some files in root without naming convention
touch "$root/standalone.txt"
touch "$root/another_file.jpg"
}
if [[ $# -eq 0 ]]; then
echo "Usage: $0 <directory>"
echo "Creates test files for validation testing"
exit 1
fi
target_dir="$1"
if [[ -d "$target_dir" ]]; then
echo "Directory already exists: $target_dir"
exit 1
fi
create_test_tree "$target_dir"
echo "Test files created in: $target_dir"
Code language: PHP (php)