""" Feature markdown utilities for project status documentation. """ import os from typing import Dict, Any, List, Optional import re from datetime import datetime # Mapping of status to emojis STATUS_EMOJIS = { "completed": "โœ…", "in_progress": "๐Ÿ”„", "planned": "๐Ÿ“", "error": "โ˜ฃ๏ธ", "blocked": "โ›”", "failed": "โŒ", "warning": "โš ๏ธ", "unknown": "โ“", "done": "โœ…", "testing": "๐Ÿงช", "review": "๐Ÿ‘€", "design": "๐ŸŽจ", "research": "๐Ÿ”", "deprecated": "๐Ÿ—‘๏ธ", "postponed": "โณ", } def emoji_status(status: str) -> str: """Return emoji for a status.""" return STATUS_EMOJIS.get(status.lower(), STATUS_EMOJIS["unknown"]) def format_feature_md(feature_name: str, data: dict) -> str: """Format a single feature into Markdown.""" status = data.get("status", "unknown") status_emoji = emoji_status(status) lines = [f"## {feature_name}"] lines.append(f"- Status: {status_emoji} {status}") if "description" in data: lines.append(f"- Description: {data['description']}") if "date" in data: lines.append(f"- Last Update: {data['date']}") if "author" in data: lines.append(f"- Updated By: {data['author']}") if "details" in data: lines.append(f"- Details: {data['details']}") return "\n".join(lines) def build_feature_md(features: dict, title: str = "# Feature Tracking") -> str: """Build the full Markdown document from features dictionary.""" lines = [title, ""] for feature_name, data in features.items(): lines.append(format_feature_md(feature_name, data)) lines.append("") # Add an empty line between features return "\n".join(lines) def extract_features( features_file: str, verbose: bool = False ) -> Dict[str, Dict[str, Any]]: """Extract all valid features from the file, focusing on descriptions.""" features = {} seen_descriptions = set() if not os.path.exists(features_file): if verbose: print(f"File not found: {features_file}") return features with open(features_file, "r", encoding="utf-8") as f: content = f.read() # First, try to extract features from the specific format # This format has ### feature_name followed by status, description, date status_blocks = re.findall( r"### ([^\n]+)\s+\*\*Status:\*\* ([^\n]+)\s+\*\*Description:\*\* ([^\n]+)\s+\*\*Last Update:\*\* ([^\n]+)", content, ) for feature_key, status_line, desc, date in status_blocks: desc = desc.strip() date = date.strip() if desc and len(desc) > 2 and desc not in seen_descriptions: seen_descriptions.add(desc) # Extract status without emoji status_match = re.search( r"[^a-zA-Z]*([a-zA-Z_]+)", status_line ) status = ( status_match.group(1).lower() if status_match else "planned" ) # Clean up the feature key clean_key = re.sub( r"[^a-zA-Z0-9_]", "", feature_key.replace(" ", "_").lower() ) if not clean_key or len(clean_key) < 2: # Generate key from description words = desc.split()[:3] clean_key = "_".join(words).lower() clean_key = re.sub(r"[^a-zA-Z0-9_]", "", clean_key) features[clean_key] = { "status": status, "description": desc, "date": date, "author": "", "details": "", } # Also try the format: ### **Status:** status \n **Description:** desc \n **Last Update:** date alt_blocks = re.findall( r"### \*\*Status:\*\* ([^\n]+)\s+\*\*Description:\*\* ([^\n]+)\s+\*\*Last Update:\*\* ([^\n]+)", content, ) for status_line, desc, date in alt_blocks: desc = desc.strip() date = date.strip() if desc and len(desc) > 2 and desc not in seen_descriptions: seen_descriptions.add(desc) # Extract status without emoji status_match = re.search( r"[^a-zA-Z]*([a-zA-Z_]+)", status_line ) status = ( status_match.group(1).lower() if status_match else "planned" ) # Generate key from description words = desc.split()[:3] clean_key = "_".join(words).lower() clean_key = re.sub(r"[^a-zA-Z0-9_]", "", clean_key) features[clean_key] = { "status": status, "description": desc, "date": date, "author": "", "details": "", } return features def enrich_features( features: Dict[str, Dict[str, Any]] ) -> Dict[str, Dict[str, Any]]: """Add additional information to features for display purposes.""" enriched = {} for key, data in features.items(): enriched[key] = data.copy() status = data.get("status", "unknown") enriched[key]["status_emoji"] = emoji_status(status) # Add title-cased key for display enriched[key]["display_name"] = key.replace("_", " ").title() return enriched def generate_features_md( features: Dict[str, Dict[str, Any]], project_name: str ) -> str: """Generate a clean features markdown from the features dictionary.""" lines = [ "# Feature Tracking", "", "## Overview", "", f"This document tracks the implementation status of all features in the {project_name} project.", "", "## Feature Status Summary", "", "| Status | Description |", "|--------|-------------|", "| โœ… Completed | Features that are fully implemented and tested |", "| ๐Ÿ”„ In Progress | Features currently being implemented |", "| ๐Ÿ“ Planned | Features planned for future implementation |", "", "## Features", "", ] # Add each feature for feature_key, feature in sorted(features.items()): status = feature.get("status", "planned").lower() status_emoji = emoji_status(status) description = feature.get("description", "") date = feature.get("date", "") or datetime.now().strftime("%Y-%m-%d") author = feature.get("author", "") details = feature.get("details", "") lines.append(f"### {feature_key}") lines.append("") lines.append(f"**Status:** {status_emoji} {status}") lines.append(f"**Description:** {description}") lines.append(f"**Last Update:** {date}") if author: lines.append(f"**Owner:** {author}") if details: lines.append(f"**Details:** {details}") lines.append("") # Add categorized lists lines.append("## Feature Categories") lines.append("") # Completed features lines.append("### Completed Features") lines.append("") completed_count = 0 for feature_key, feature in sorted(features.items()): if feature.get("status", "").lower() in ["completed", "done"]: lines.append( f"- **{feature_key}**: {feature.get('description', '')}" ) completed_count += 1 if completed_count == 0: lines.append("*No completed features yet.*") lines.append("") # In progress features lines.append("### In Progress Features") lines.append("") in_progress_count = 0 for feature_key, feature in sorted(features.items()): if feature.get("status", "").lower() == "in_progress": lines.append( f"- **{feature_key}**: {feature.get('description', '')}" ) in_progress_count += 1 if in_progress_count == 0: lines.append("*No features currently in progress.*") lines.append("") # Planned features lines.append("### Planned Features") lines.append("") planned_count = 0 for feature_key, feature in sorted(features.items()): if feature.get("status", "").lower() == "planned": lines.append( f"- **{feature_key}**: {feature.get('description', '')}" ) planned_count += 1 if planned_count == 0: lines.append("*No planned features yet.*") lines.append("") # Add CSS styling lines.append( """""" ) return "\n".join(lines)