From e4e3ee20159d71336c2bd776033ee33f1d6acfe1 Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Mon, 2 Feb 2026 15:53:17 +0000 Subject: [PATCH] Initial upload: core files and configuration --- gitignore_generator/validator.py | 149 +++++++++++++++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 gitignore_generator/validator.py diff --git a/gitignore_generator/validator.py b/gitignore_generator/validator.py new file mode 100644 index 0000000..ade907f --- /dev/null +++ b/gitignore_generator/validator.py @@ -0,0 +1,149 @@ +"""Syntax validator for gitignore patterns.""" + +import re +from dataclasses import dataclass +from typing import List, Optional + + +@dataclass +class ValidationIssue: + """Represents a validation issue.""" + line_number: int + line: str + issue_type: str + message: str + severity: str + + +class Validator: + """Validate gitignore syntax and patterns.""" + + TRAILING_SLASH_PATTERN = re.compile(r".*[^/]sky/$") + NEGATION_PATTERN = re.compile(r"^\s*!") + BACKSLASH_PATTERN = re.compile(r"\\[nrt]") + DOUBLE_STAR_PATTERN = re.compile(r"\*\*") + UNESCAPED_EXCLAMATION = re.compile(r"[^\\]!") + + def __init__(self): + """Initialize validator.""" + self.issues: List[ValidationIssue] = [] + self.warnings: List[ValidationIssue] = [] + + def validate_content(self, content: str) -> List[ValidationIssue]: + """Validate gitignore content.""" + self.issues = [] + self.warnings = [] + + lines = content.splitlines() + for line_num, line in enumerate(lines, start=1): + self._validate_line(line, line_num, lines) + + return self.issues + self.warnings + + def _validate_line(self, line: str, line_num: int, all_lines: List[str]) -> None: + """Validate a single line.""" + stripped = line.strip() + + if not stripped or stripped.startswith("#"): + return + + self._check_trailing_slash(stripped, line_num) + self._check_double_negation(stripped, line_num, all_lines) + self._check_backslash_escapes(stripped, line_num) + self._check_invalid_wildcards(stripped, line_num) + self._check_duplicate_pattern(stripped, line_num, all_lines) + + def _check_trailing_slash(self, line: str, line_num: int) -> None: + """Check for trailing slashes (not allowed for directories).""" + if self.TRAILING_SLASH_PATTERN.match(line): + self.warnings.append(ValidationIssue( + line_number=line_num, + line=line, + issue_type="trailing_slash", + message="Trailing slash on directory pattern - consider removing it", + severity="warning" + )) + + def _check_double_negation(self, line: str, line_num: int, all_lines: List[str]) -> None: + """Check for double negation patterns.""" + if line.startswith("!"): + prev_lines = [l.strip() for l in all_lines[:line_num - 1] if l.strip() and not l.strip().startswith("#")] + if prev_lines and prev_lines[-1].startswith("!"): + self.warnings.append(ValidationIssue( + line_number=line_num, + line=line, + issue_type="double_negation", + message="Consecutive negation patterns - verify this is intentional", + severity="warning" + )) + + def _check_backslash_escapes(self, line: str, line_num: int) -> None: + """Check for invalid backslash escapes.""" + if self.BACKSLASH_PATTERN.search(line): + self.issues.append(ValidationIssue( + line_number=line_num, + line=line, + issue_type="invalid_escape", + message="Backslash escape not recognized in .gitignore", + severity="error" + )) + + def _check_invalid_wildcards(self, line: str, line_num: int) -> None: + """Check for potentially invalid wildcard usage.""" + if "**" in line and not (line.startswith("**") or line.endswith("**")): + self.warnings.append(ValidationIssue( + line_number=line_num, + line=line, + issue_type="double_star", + message="Double wildcard '**' should only be used at start or end", + severity="warning" + )) + + def _check_duplicate_pattern(self, line: str, line_num: int, all_lines: List[str]) -> None: + """Check for duplicate patterns.""" + stripped = line.strip() + for prev_line_num, prev_line in enumerate(all_lines[:line_num - 1], start=1): + prev_stripped = prev_line.strip() + if prev_stripped == stripped: + self.warnings.append(ValidationIssue( + line_number=line_num, + line=line, + issue_type="duplicate", + message=f"Duplicate of pattern on line {prev_line_num}", + severity="warning" + )) + break + + def validate_pattern(self, pattern: str) -> Optional[ValidationIssue]: + """Validate a single pattern.""" + if pattern.startswith("#") or not pattern.strip(): + return None + + if self.BACKSLASH_PATTERN.search(pattern): + return ValidationIssue( + line_number=0, + line=pattern, + issue_type="invalid_escape", + message="Backslash escape not recognized in .gitignore", + severity="error" + ) + + return None + + def is_valid(self, content: str) -> bool: + """Check if content is valid.""" + self.validate_content(content) + return not any(issue.severity == "error" for issue in self.issues + self.warnings) + + def get_summary(self, content: str) -> dict: + """Get validation summary.""" + self.validate_content(content) + return { + "valid": not any(issue.severity == "error" for issue in self.issues + self.warnings), + "errors": len([i for i in self.issues if i.severity == "error"]), + "warnings": len([i for i in self.warnings if i.severity == "warning"]), + "issues": self.issues + self.warnings + } + + +validator = Validator()