updates
This commit is contained in:
73 files changed
+3290
-4522
No files matched your search
+198
@@ -0,0 +1,198 @@
|
||||
import logging
|
||||
import re
|
||||
import subprocess
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
import yaml
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
|
||||
def ensure_log_directory() -> Path:
|
||||
log_dir = Path.home() / ".cache" / "devgen"
|
||||
log_dir.mkdir(parents=True, exist_ok=True)
|
||||
return log_dir
|
||||
|
||||
|
||||
def get_main_log_path() -> Path:
|
||||
return ensure_log_directory() / "devgen.log"
|
||||
|
||||
|
||||
def get_commit_dry_run_path() -> Path:
|
||||
return ensure_log_directory() / "commit_dry_run.md"
|
||||
|
||||
|
||||
def is_file_recent(file_path: Path | str, max_age_minutes: int = 120) -> bool:
|
||||
path = Path(file_path)
|
||||
if not path.exists():
|
||||
return False
|
||||
return (time.time() - path.stat().st_mtime) <= max_age_minutes * 60
|
||||
|
||||
|
||||
def sanitize_ai_commit_message(raw_text: str) -> str:
|
||||
lines = raw_text.strip().split("\n")
|
||||
cleaned_lines = []
|
||||
in_block = False
|
||||
# Regex for conventional commit header
|
||||
header_pattern = re.compile(
|
||||
r"^(feat|fix|chore|refactor|docs|style|test|build|ci)(\(.*\))?!?: .*"
|
||||
)
|
||||
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
if in_block:
|
||||
if header_pattern.match(stripped) or "**Sponsor**" in line:
|
||||
break
|
||||
cleaned_lines.append(line)
|
||||
elif header_pattern.match(stripped):
|
||||
in_block = True
|
||||
cleaned_lines.append(line)
|
||||
|
||||
return "\n".join(cleaned_lines).strip() if cleaned_lines else ""
|
||||
|
||||
|
||||
def parse_markdown_sections(
|
||||
filepath: Path | str, marker_pattern: str
|
||||
) -> dict[str, str]:
|
||||
path = Path(filepath)
|
||||
if not path.exists():
|
||||
return {}
|
||||
|
||||
with path.open(encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
results = {}
|
||||
matches = re.findall(marker_pattern, content, re.DOTALL)
|
||||
for key, value in matches:
|
||||
results[key] = value.strip()
|
||||
return results
|
||||
|
||||
|
||||
def extract_commit_messages(filepath: Path | str) -> dict[str, str]:
|
||||
pattern = r"## Group: `(.*?)`\s*```md\n(.*?)\n```"
|
||||
return parse_markdown_sections(filepath, pattern)
|
||||
|
||||
|
||||
def configure_logger(
|
||||
name: str = "devgen", log_file: Optional[Path | str] = None
|
||||
) -> logging.Logger:
|
||||
logger = logging.getLogger(name)
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
if logger.hasHandlers():
|
||||
logger.handlers.clear()
|
||||
|
||||
formatter = logging.Formatter(
|
||||
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
)
|
||||
|
||||
# Console handler
|
||||
ch = logging.StreamHandler()
|
||||
ch.setFormatter(formatter)
|
||||
logger.addHandler(ch)
|
||||
|
||||
# File handler
|
||||
if log_file:
|
||||
path = Path(log_file)
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
fh = logging.FileHandler(path, mode="w", encoding="utf-8")
|
||||
fh.setFormatter(formatter)
|
||||
logger.addHandler(fh)
|
||||
|
||||
return logger
|
||||
|
||||
|
||||
def get_git_staged_files() -> list[str]:
|
||||
try:
|
||||
res = subprocess.run(
|
||||
["git", "diff", "--name-only", "--cached"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True,
|
||||
)
|
||||
return [f for f in res.stdout.splitlines() if f.strip()]
|
||||
except subprocess.CalledProcessError:
|
||||
return []
|
||||
|
||||
|
||||
def read_file_content(filepath: Path | str) -> Optional[str]:
|
||||
path = Path(filepath)
|
||||
if path.exists():
|
||||
return path.read_text(encoding="utf-8")
|
||||
return None
|
||||
|
||||
|
||||
def delete_file(filepath: Path | str) -> bool:
|
||||
path = Path(filepath)
|
||||
if path.exists():
|
||||
path.unlink()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def load_template_env(sub_dir: str) -> Environment:
|
||||
template_dir = Path(__file__).parent / "prompt_templates" / sub_dir
|
||||
return Environment(loader=FileSystemLoader(template_dir))
|
||||
|
||||
|
||||
def load_config() -> Dict[str, Any]:
|
||||
config_path = Path.home() / ".devgen.yaml"
|
||||
|
||||
if not config_path.exists():
|
||||
default_config = {
|
||||
"provider": "gemini",
|
||||
"model": "gemini-2.5-flash",
|
||||
"api_key": "",
|
||||
"emoji": True,
|
||||
}
|
||||
try:
|
||||
with config_path.open("w", encoding="utf-8") as f:
|
||||
yaml.dump(default_config, f, default_flow_style=False)
|
||||
# We don't print here to avoid noise during normal execution
|
||||
except Exception as e:
|
||||
print(f"Warning: Failed to create default config at {config_path}: {e}")
|
||||
return {}
|
||||
|
||||
try:
|
||||
with config_path.open("r", encoding="utf-8") as f:
|
||||
return yaml.safe_load(f) or {}
|
||||
except Exception as e:
|
||||
print(f"Warning: Failed to load config from {config_path}: {e}")
|
||||
return {}
|
||||
|
||||
|
||||
__all__ = [
|
||||
"ensure_log_directory",
|
||||
"get_main_log_path",
|
||||
"get_commit_dry_run_path",
|
||||
"is_file_recent",
|
||||
"sanitize_ai_commit_message",
|
||||
"extract_commit_messages",
|
||||
"configure_logger",
|
||||
"get_git_staged_files",
|
||||
"read_file_content",
|
||||
"delete_file",
|
||||
"load_template_env",
|
||||
"load_config",
|
||||
"get_questionary_style",
|
||||
]
|
||||
|
||||
|
||||
def get_questionary_style():
|
||||
from questionary import Style
|
||||
|
||||
return Style(
|
||||
[
|
||||
("qmark", "fg:#673ab7 bold"), # Token.QuestionMark
|
||||
("question", "bold"), # Token.Question
|
||||
("answer", "fg:#f44336 bold"), # Token.Answer
|
||||
("pointer", "fg:#673ab7 bold"), # Token.Pointer
|
||||
("highlighted", "fg:#673ab7 bold"), # Token.Selected
|
||||
("selected", "fg:#cc5454"), # Token.SelectedItem
|
||||
("separator", "fg:#cc5454"), # Token.Separator
|
||||
("instruction", ""), # Token.Instruction
|
||||
("text", ""), # Token.Text
|
||||
("disabled", "fg:#858585 italic"), # Token.Disabled
|
||||
]
|
||||
)
|
||||
Reference in new issue
Block a user