use std::fs;
use tempfile::TempDir;
use sphinx_ultra::config::BuildConfig;
use sphinx_ultra::matching::{get_matching_files, pattern_match, translate_pattern};
use sphinx_ultra::python_config::PythonConfigParser;
#[test]
fn test_sphinx_compatibility_default_patterns() {
let config = BuildConfig::default();
assert_eq!(config.include_patterns, vec!["**"]);
assert_eq!(config.exclude_patterns, Vec::<String>::new());
}
#[test]
fn test_pattern_translation_compatibility() {
assert_eq!(translate_pattern("*.rst"), "^[^/]*\\.rst$");
assert_eq!(translate_pattern("**"), "^.*$");
assert_eq!(
translate_pattern("**/index.rst"),
"^(?:[^/]+/)*index\\.rst$"
);
assert_eq!(
translate_pattern("docs/**/*.rst"),
"^docs/(?:[^/]+/)*[^/]*\\.rst$"
);
assert_eq!(translate_pattern("[abc].rst"), "^[abc]\\.rst$");
assert_eq!(translate_pattern("[!_]*.rst"), "^[^_][^/]*\\.rst$");
}
#[test]
fn test_pattern_matching_sphinx_examples() {
assert!(pattern_match("index.rst", "*.rst").unwrap());
assert!(pattern_match("chapter1.rst", "chapter?.rst").unwrap());
assert!(!pattern_match("chapter10.rst", "chapter?.rst").unwrap());
assert!(pattern_match("docs/api/module.rst", "**/api/*.rst").unwrap());
assert!(pattern_match("api/module.rst", "**/api/*.rst").unwrap());
assert!(pattern_match("deep/nested/api/module.rst", "**/api/*.rst").unwrap());
assert!(pattern_match("_build/index.html", "_build/**").unwrap());
assert!(pattern_match("_build/html/index.html", "_build/**").unwrap());
assert!(pattern_match("Thumbs.db", "Thumbs.db").unwrap());
assert!(pattern_match("docs/index.rst", "docs/**").unwrap());
assert!(!pattern_match("src/code.py", "docs/**").unwrap());
}
#[test]
fn test_file_discovery_with_patterns() {
let temp_dir = TempDir::new().unwrap();
let base_path = temp_dir.path();
fs::create_dir_all(base_path.join("docs/api")).unwrap();
fs::create_dir_all(base_path.join("_build/html")).unwrap();
fs::create_dir_all(base_path.join("src")).unwrap();
fs::write(base_path.join("index.rst"), "Main index").unwrap();
fs::write(base_path.join("docs/guide.rst"), "User guide").unwrap();
fs::write(base_path.join("docs/api/reference.rst"), "API reference").unwrap();
fs::write(base_path.join("_build/html/index.html"), "Built HTML").unwrap();
fs::write(base_path.join("src/code.py"), "Python code").unwrap();
fs::write(base_path.join("README.md"), "Readme").unwrap();
fs::write(base_path.join("Thumbs.db"), "Windows thumbnail").unwrap();
let files = get_matching_files(base_path, &["**/*.rst".to_string()], &[]).unwrap();
assert_eq!(files.len(), 3);
let file_names: Vec<_> = files
.iter()
.map(|p| p.file_name().unwrap().to_string_lossy())
.collect();
assert!(file_names.contains(&"index.rst".into()));
assert!(file_names.contains(&"guide.rst".into()));
assert!(file_names.contains(&"reference.rst".into()));
let files =
get_matching_files(base_path, &["**".to_string()], &["_build/**".to_string()]).unwrap();
assert!(!files.iter().any(|p| p.to_string_lossy().contains("_build")));
let files = get_matching_files(
base_path,
&["**".to_string()],
&[
"_build/**".to_string(),
"**/*.py".to_string(),
"Thumbs.db".to_string(),
],
)
.unwrap();
assert!(!files
.iter()
.any(|p| p.extension().is_some_and(|ext| ext == "py")));
assert!(!files.iter().any(|p| p.file_name().unwrap() == "Thumbs.db"));
assert!(!files.iter().any(|p| p.to_string_lossy().contains("_build")));
let files = get_matching_files(base_path, &["docs/**".to_string()], &[]).unwrap();
assert_eq!(files.len(), 2);
assert!(files.iter().all(|p| p.to_string_lossy().contains("docs")));
}
#[test]
fn test_sphinx_built_in_excludes() {
let temp_dir = TempDir::new().unwrap();
let base_path = temp_dir.path();
fs::create_dir_all(base_path.join(".git")).unwrap();
fs::create_dir_all(base_path.join("__pycache__")).unwrap();
fs::write(base_path.join(".git/config"), "git config").unwrap();
fs::write(base_path.join("__pycache__/module.pyc"), "compiled python").unwrap();
fs::write(base_path.join(".DS_Store"), "macOS metadata").unwrap();
fs::write(base_path.join("index.rst"), "documentation").unwrap();
let default_excludes = vec![
"_build/**".to_string(),
"__pycache__/**".to_string(),
".git/**".to_string(),
".*/**".to_string(),
"Thumbs.db".to_string(),
".DS_Store".to_string(),
];
let files = get_matching_files(base_path, &["**".to_string()], &default_excludes).unwrap();
assert_eq!(files.len(), 1);
assert_eq!(files[0].file_name().unwrap(), "index.rst");
}
#[test]
fn test_conf_py_pattern_parsing() {
let temp_dir = TempDir::new().unwrap();
let conf_path = temp_dir.path().join("conf.py");
let conf_content = r#"
# Sphinx configuration
project = 'Test Project'
version = '1.0'
# File patterns
include_patterns = ['docs/**', '*.rst']
exclude_patterns = ['_build/**', '*.tmp', 'drafts/**']
# Extensions
extensions = ['sphinx.ext.autodoc']
html_theme = 'sphinx_rtd_theme'
"#;
fs::write(&conf_path, conf_content).unwrap();
let mut parser = PythonConfigParser::new().unwrap();
let conf_config = parser.parse_conf_py(&conf_path).unwrap();
let build_config = conf_config.to_build_config();
assert_eq!(build_config.include_patterns, vec!["docs/**", "*.rst"]);
assert_eq!(
build_config.exclude_patterns,
vec!["_build/**", "*.tmp", "drafts/**"]
);
}
#[test]
fn test_conf_py_default_patterns() {
let temp_dir = TempDir::new().unwrap();
let conf_path = temp_dir.path().join("conf.py");
let conf_content = r#"
project = 'Test Project'
version = '1.0'
html_theme = 'alabaster'
"#;
fs::write(&conf_path, conf_content).unwrap();
let mut parser = PythonConfigParser::new().unwrap();
let conf_config = parser.parse_conf_py(&conf_path).unwrap();
let build_config = conf_config.to_build_config();
assert_eq!(build_config.include_patterns, vec!["**"]);
assert_eq!(build_config.exclude_patterns, Vec::<String>::new());
}
#[test]
fn test_pattern_priority_exclude_over_include() {
let temp_dir = TempDir::new().unwrap();
let base_path = temp_dir.path();
fs::create_dir_all(base_path.join("drafts")).unwrap();
fs::write(base_path.join("index.rst"), "Main file").unwrap();
fs::write(base_path.join("drafts/unfinished.rst"), "Draft file").unwrap();
let files = get_matching_files(
base_path,
&["**/*.rst".to_string()], &["drafts/**".to_string()], )
.unwrap();
assert_eq!(files.len(), 1);
assert_eq!(files[0].file_name().unwrap(), "index.rst");
}
#[test]
fn test_character_class_patterns() {
assert!(pattern_match("a.rst", "[abc].rst").unwrap());
assert!(pattern_match("b.rst", "[abc].rst").unwrap());
assert!(pattern_match("c.rst", "[abc].rst").unwrap());
assert!(!pattern_match("d.rst", "[abc].rst").unwrap());
assert!(!pattern_match("_hidden.rst", "[!_]*.rst").unwrap());
assert!(pattern_match("visible.rst", "[!_]*.rst").unwrap());
}
#[test]
fn test_cross_platform_path_handling() {
let normalized_path = "docs/api/module.rst";
assert!(pattern_match(normalized_path, "**/api/*.rst").unwrap());
assert!(pattern_match(normalized_path, "docs/**/*.rst").unwrap());
use sphinx_ultra::matching::normalize_path;
use std::path::Path;
let windows_path = Path::new("docs\\api\\module.rst");
let normalized = normalize_path(windows_path);
assert_eq!(normalized, "docs/api/module.rst");
assert!(pattern_match(&normalized, "**/api/*.rst").unwrap());
}