116 lines
4.7 KiB
Python
116 lines
4.7 KiB
Python
import os
|
|
import sys
|
|
import yaml
|
|
import importlib
|
|
import fnmatch
|
|
|
|
class Project:
|
|
@staticmethod
|
|
def _load_pbignore(root_path):
|
|
"""Load .pbignore file and return list of ignore patterns"""
|
|
pbignore_path = os.path.join(root_path, '.pbignore')
|
|
ignore_patterns = []
|
|
|
|
if os.path.exists(pbignore_path):
|
|
try:
|
|
with open(pbignore_path, 'r', encoding='utf-8') as f:
|
|
for line in f:
|
|
line = line.strip()
|
|
# Skip empty lines and comments
|
|
if line and not line.startswith('#'):
|
|
ignore_patterns.append(line)
|
|
except Exception as e:
|
|
print(f"Warning: Could not read .pbignore file: {e}")
|
|
|
|
return ignore_patterns
|
|
|
|
@staticmethod
|
|
def _should_ignore(path, ignore_patterns, root_path):
|
|
"""Check if a path should be ignored based on .pbignore patterns"""
|
|
if not ignore_patterns:
|
|
return False
|
|
|
|
# Get relative path from root
|
|
try:
|
|
rel_path = os.path.relpath(path, root_path)
|
|
except ValueError:
|
|
# If path is not relative to root, don't ignore
|
|
return False
|
|
|
|
# Normalize path separators
|
|
rel_path = rel_path.replace(os.sep, '/')
|
|
|
|
for pattern in ignore_patterns:
|
|
# Handle directory patterns (ending with /)
|
|
if pattern.endswith('/'):
|
|
pattern = pattern[:-1]
|
|
if fnmatch.fnmatch(rel_path, pattern) or fnmatch.fnmatch(rel_path, pattern + '/*'):
|
|
return True
|
|
else:
|
|
# Handle file/directory patterns
|
|
if fnmatch.fnmatch(rel_path, pattern) or fnmatch.fnmatch(os.path.basename(rel_path), pattern):
|
|
return True
|
|
|
|
return False
|
|
|
|
@staticmethod
|
|
def scan_project(root_path):
|
|
sys.path.append(root_path)
|
|
if not os.path.exists(root_path) or not os.path.isdir(root_path):
|
|
raise ValueError(f"The provided root_path '{root_path}' is not a valid directory.")
|
|
|
|
parent_dir = os.path.dirname(root_path)
|
|
sys.path.insert(0, parent_dir)
|
|
|
|
# Load .pbignore patterns
|
|
ignore_patterns = Project._load_pbignore(root_path)
|
|
|
|
def import_all_modules(path, package_name):
|
|
for root, dirs, files in os.walk(path):
|
|
# Check if current directory should be ignored
|
|
if Project._should_ignore(root, ignore_patterns, root_path):
|
|
dirs[:] = [] # Don't traverse into ignored directories
|
|
continue
|
|
|
|
relative_path = os.path.relpath(root, root_path)
|
|
if relative_path == '.':
|
|
module_package = package_name
|
|
else:
|
|
module_package = f"{package_name}.{relative_path.replace(os.sep, '.')}"
|
|
|
|
for file in files:
|
|
if file.endswith(".py") and file != "__init__.py":
|
|
file_path = os.path.join(root, file)
|
|
# Check if file should be ignored
|
|
if Project._should_ignore(file_path, ignore_patterns, root_path):
|
|
continue
|
|
|
|
module_name = file[:-3]
|
|
full_module_name = f"{module_package}.{module_name}"
|
|
if full_module_name not in sys.modules:
|
|
importlib.import_module(full_module_name)
|
|
|
|
# Filter out ignored directories from further traversal
|
|
dirs[:] = [d for d in dirs if not d.startswith('.') and
|
|
not Project._should_ignore(os.path.join(root, d), ignore_patterns, root_path)]
|
|
|
|
package_name = os.path.basename(root_path)
|
|
import_all_modules(root_path, package_name)
|
|
|
|
@staticmethod
|
|
def scan_configs(root_path):
|
|
configs = {}
|
|
for root, dirs, files in os.walk(root_path):
|
|
for file in files:
|
|
if file.endswith(('.yaml', '.yml')):
|
|
if file.startswith('__'):
|
|
continue
|
|
file_path = os.path.join(root, file)
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
try:
|
|
content = yaml.safe_load(f)
|
|
configs[os.path.splitext(file)[0]] = content
|
|
except yaml.YAMLError as e:
|
|
print(f"Error reading {file_path}: {e}")
|
|
|
|
return configs |