diff --git a/.gitignore b/.gitignore index e36513b..0b8cdbf 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,10 @@ src/bindings/python/__pycache__/ /src/AdvChkSys.Benchmarks/bin /src/AdvChkSys.Benchmarks/obj build/ +docs/features.md.* +scripts/__pycache__/ +snyk.exe +scripts/track_progress/__pycache__/ +scripts/track_progress/dist/ +scripts/track_progress/scripts/__pycache__/ +scripts/track_progress/docs/features.md.bak.* diff --git a/CHANGELOG.md b/CHANGELOG.md index 3333a98..a7b10c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,3 @@ # Changelog -- 2025-05-11: removed unnecessary ignores in gitignore (79d6716) -- 2025-05-11: XML comments added Builds cleanly (0ae815c) -- 2025-05-11: updated track_progress restored accidentally deleted source file (8a6f914) -- 2025-05-10: fixed a bug of not building (09ee0d8) -- 2025-05-10: added a build (e59a909) -- 2025-05-10: updated ChunkMark (f35c612) -- 2025-05-10: updated memory memory handling to be more safe (becedcc) -- 2025-05-09: first commit (f1b3868) - -- 2025-05-10: added a build (e59a909) -- 2025-05-10: updated memory memory handling to be more safe (becedcc) \ No newline at end of file diff --git a/docs/features.md b/docs/features.md deleted file mode 100644 index 32b4c86..0000000 --- a/docs/features.md +++ /dev/null @@ -1,118 +0,0 @@ -# Feature Tracking - -## chunk_management -- Status: completed -- Description: 2D/3D chunk management with memory efficiency -- Last Update: 2025-05-10 - -## memory_efficiency -- Status: completed -- Description: LRU caching, array pooling, and air-singleton patterns -- Last Update: 2025-05-10 - -## resource_tracking -- Status: completed -- Description: Track and manage chunk resources -- Last Update: 2025-05-10 - -## async_tasks -- Status: completed -- Description: Asynchronous chunk loading and processing -- Last Update: 2025-05-10 - -## serialization -- Status: completed -- Description: Chunk serialization and deserialization -- Last Update: 2025-05-10 - -## event_system -- Status: completed -- Description: Events for load/unload/save operations -- Last Update: 2025-05-10 - -## interop -- Status: completed -- Description: Python and .NET interoperability -- Last Update: 2025-05-10 - -## spatial_queries -- Status: completed -- Description: Spatial indexing and region queries with 2D/3D support and quadtree optimization -- Last Update: 2025-05-11 - -## thread_sync -- Status: completed -- Description: Explicit synchronization and concurrent collections -- Last Update: 2025-05-11 -- Updated By: Stan44 - -## spatial_interfaces -- Status: completed -- Description: Interface-level spatial query methods with support for custom filters -- Last Update: 2025-05-11 - -## parallel_processing -- Status: completed -- Description: Parallel processing of chunks based on spatial queries and regions -- Last Update: 2025-05-11 - -## feature_tracking -- Status: completed -- Description: Track feature statuses -- Last Update: 2025-05-11 - -## priority_loading -- Status: in_progress -- Description: Prioritized chunk loading system (partially implemented) -- Last Update: 2025-05-10 - -## dependency_tracking -- Status: in_progress -- Description: Dependency-aware disposal logic (Partially Implemented) -- Last Update: 2025-05-10 - -## dispose_pattern -- Status: in_progress -- Description: Full dispose pattern with finalizers for unmanaged resources (Partially Implemented) -- Last Update: 2025-05-10 - -## exception_handling -- Status: in_progress -- Description: Better async exception handling with specific types and logging -- Last Update: 2025-05-10 - -## performance_metrics -- Status: in_progress -- Description: Track load times, cache hit rates, and memory usage (partially implemented) -- Last Update: 2025-05-10 - -## progress_tracking -- Status: in_progress -- Description: Track progress and auto-update status documents (mostly implemented kinda still manual...) -- Last Update: 2025-05-11 - -## git_integration -- Status: in_progress -- Description: Parse Git logs for status updates (partially implemented testing with this push.) -- Last Update: 2025-05-11 - -## doc_generation -- Status: in_progress -- Description: Auto-generate status docs and changelog (partially implemented) -- Last Update: 2025-05-10 - -## dependency_interfaces -- Status: planned -- Description: Interface-level dependency methods -- Last Update: 2025-05-10 - -## runtime_config -- Status: planned -- Description: Runtime-adjustable configuration options -- Last Update: 2025-05-10 - -## known_issues -- Status: in_progress -- Description: Edge chunk unload delay under high concurrency -- Last Update: 2025-05-10 - diff --git a/docs/status/ChunkManager-Status.md b/docs/status/ChunkManager-Status.md deleted file mode 100644 index 185400d..0000000 --- a/docs/status/ChunkManager-Status.md +++ /dev/null @@ -1,87 +0,0 @@ -# AdvChkSys Development Status - -Last updated: 2025-05-11 - -## Code Statistics - -Total lines of code: **7414** - -Number of source files: **34** - -### All Files by Line Count - -| File | Lines | Path | -|------|------:|------| -| SpatialChunkIndex.cs | 1228 | src/AdvChkSys\Spatial\SpatialChunkIndex.cs | -| ChunkLoadingPriority.cs | 696 | src/AdvChkSys\Loading\ChunkLoadingPriority.cs | -| ChunkThreadingExtensions.cs | 380 | src/AdvChkSys\Threading\ChunkThreadingExtensions.cs | -| ChunkDependencyTracker.cs | 325 | src/AdvChkSys\Dependencies\ChunkDependencyTracker.cs | -| ChunkManager2D.cs | 313 | src/AdvChkSys\Manager\ChunkManager2D.cs | -| ChunkTaskScheduler.cs | 310 | src/AdvChkSys\Threading\ChunkTaskScheduler.cs | -| ChunkThreadingManager.cs | 280 | src/AdvChkSys\Threading\ChunkThreadingManager.cs | -| ChunkOperationQueue.cs | 274 | src/AdvChkSys\Threading\ChunkOperationQueue.cs | -| ChunkThreadingDiagnostics.cs | 273 | src/AdvChkSys\Threading\ChunkThreadingDiagnostics.cs | -| ChunkThreadingPerformanceMonitor.cs | 265 | src/AdvChkSys\Threading\ChunkThreadingPerformanceMonitor.cs | -| ChunkParallelProcessor.cs | 252 | src/AdvChkSys\Threading\ChunkParallelProcessor.cs | -| ChunkManager3D.cs | 244 | src/AdvChkSys\Manager\ChunkManager3D.cs | -| ChunkThreadSafetyManager.cs | 237 | src/AdvChkSys\Threading\ChunkThreadSafetyManager.cs | -| ChunkThreadingExtensions2.cs | 223 | src/AdvChkSys\Threading\ChunkThreadingExtensions2.cs | -| Chunk3D.cs | 215 | src/AdvChkSys\Chunk\Chunk3D.cs | -| ChunkSerializer.cs | 194 | src/AdvChkSys\Serialization\ChunkSerializer.cs | -| Chunk2D.cs | 174 | src/AdvChkSys\Chunk\Chunk2D.cs | -| LimitedConcurrencyTaskScheduler.cs | 170 | src/AdvChkSys\Threading\LimitedConcurrencyTaskScheduler.cs | -| ChunkAsyncLock.cs | 167 | src/AdvChkSys\Threading\ChunkAsyncLock.cs | -| AdvChkSys.cs | 162 | src/AdvChkSys\AdvChkSys.cs | -| MemoryHelper.cs | 143 | src/AdvChkSys\Util\MemoryHelper.cs | -| MemoryUsageReporter.cs | 140 | src/AdvChkSys\Diagnostics\MemoryUsageReporter.cs | -| LRUCache.cs | 135 | src/AdvChkSys\Util\LRUCache.cs | -| ChunkEvents.cs | 126 | src/AdvChkSys\Events\ChunkEvents.cs | -| ChunkThreadingConfiguration.cs | 98 | src/AdvChkSys\Threading\ChunkThreadingConfiguration.cs | -| ChunkResourceManager.cs | 72 | src/AdvChkSys\Resources\ChunkResourceManager.cs | -| WorldConstraints.cs | 67 | src/AdvChkSys\Constraints\WorldConstraints.cs | -| ChunkTaskSchedulerExtensions.cs | 63 | src/AdvChkSys\Threading\ChunkTaskSchedulerExtensions.cs | -| ChunkExtensions.cs | 40 | src/AdvChkSys\Spatial\ChunkExtensions.cs | -| IChunkManager.cs | 38 | src/AdvChkSys\Interfaces\IChunkManager.cs | -| IChunk.cs | 36 | src/AdvChkSys\Interfaces\IChunk.cs | -| CacheCapacityHelper.cs | 30 | src/AdvChkSys\Util\CacheCapacityHelper.cs | -| AdvChkSys.AssemblyInfo.cs | 22 | src/AdvChkSys\obj\Debug\netstandard2.1\AdvChkSys.AssemblyInfo.cs | -| AdvChkSys.AssemblyInfo.cs | 22 | src/AdvChkSys\obj\Release\netstandard2.1\AdvChkSys.AssemblyInfo.cs | - -## Feature Status - -| Feature | Status | Description | Last Update | -|---------|--------|-------------|-------------| -| Chunk Management | [COMPLETED] | 2D/3D chunk management with memory efficiency | 2025-05-10 | -| Memory Efficiency | [COMPLETED] | LRU caching, array pooling, and air-singleton patterns | 2025-05-10 | -| Resource Tracking | [COMPLETED] | Track and manage chunk resources | 2025-05-10 | -| Async Tasks | [COMPLETED] | Asynchronous chunk loading and processing | 2025-05-10 | -| Serialization | [COMPLETED] | Chunk serialization and deserialization | 2025-05-10 | -| Event System | [COMPLETED] | Events for load/unload/save operations | 2025-05-10 | -| Interop | [COMPLETED] | Python and .NET interoperability | 2025-05-10 | -| Spatial Queries | [COMPLETED] | Spatial indexing and region queries with 2D/3D support and quadtree optimization | 2025-05-11 | -| Thread Sync | [COMPLETED] | Explicit synchronization and concurrent collections | 2025-05-11 | -| Spatial Interfaces | [COMPLETED] | Interface-level spatial query methods with support for custom filters | 2025-05-11 | -| Parallel Processing | [COMPLETED] | Parallel processing of chunks based on spatial queries and regions | 2025-05-11 | -| Feature Tracking | [COMPLETED] | Track feature statuses | 2025-05-11 | -| Priority Loading | [IN PROGRESS] | Prioritized chunk loading system (partially implemented) | 2025-05-10 | -| Dependency Tracking | [IN PROGRESS] | Dependency-aware disposal logic (Partially Implemented) | 2025-05-10 | -| Dispose Pattern | [IN PROGRESS] | Full dispose pattern with finalizers for unmanaged resources (Partially Implemented) | 2025-05-10 | -| Exception Handling | [IN PROGRESS] | Better async exception handling with specific types and logging | 2025-05-10 | -| Performance Metrics | [IN PROGRESS] | Track load times, cache hit rates, and memory usage (partially implemented) | 2025-05-10 | -| Progress Tracking | [IN PROGRESS] | Track progress and auto-update status documents (mostly implemented kinda still manual...) | 2025-05-11 | -| Git Integration | [IN PROGRESS] | Parse Git logs for status updates (partially implemented testing with this push.) | 2025-05-11 | -| Doc Generation | [IN PROGRESS] | Auto-generate status docs and changelog (partially implemented) | 2025-05-10 | -| Dependency Interfaces | [PLANNED] | Interface-level dependency methods | 2025-05-10 | -| Runtime Config | [PLANNED] | Runtime-adjustable configuration options | 2025-05-10 | -| Known Issues | [IN PROGRESS] | Edge chunk unload delay under high concurrency | 2025-05-10 | - -## Recent Updates - -- 2025-05-11: removed unnecessary ignores in gitignore (79d6716) -- 2025-05-11: XML comments added Builds cleanly (0ae815c) -- 2025-05-11: updated track_progress restored accidentally deleted source file (8a6f914) -- 2025-05-10: fixed a bug of not building (09ee0d8) -- 2025-05-10: added a build (e59a909) -- 2025-05-10: updated ChunkMark (f35c612) -- 2025-05-10: updated memory memory handling to be more safe (becedcc) -- 2025-05-09: first commit (f1b3868) diff --git a/scripts/README.md b/scripts/README.md deleted file mode 100644 index a25c9b5..0000000 --- a/scripts/README.md +++ /dev/null @@ -1,81 +0,0 @@ -# AdvChkSys Development Scripts - -## Track Progress Script - -The `track_progress.py` script automates feature tracking, status updates, and changelog management for the AdvChkSys project. - -### Overview - -This script: -- Tracks features and their status in a human-readable Markdown file -- Generates a status document with current development progress -- Updates the changelog with tagged entries from commit messages -- Automatically detects new features from specially formatted commit messages - -### Usage - -Run the script periodically to update documentation: - -```bash -python scripts/track_progress.py -``` - -### Commit Message Tags - -The script recognizes special tags in commit messages: - -#### 1. Feature Status Updates - -``` -git commit -m "Implemented spatial queries [feature:spatial_queries]" -``` - -This marks the `spatial_queries` feature as completed and records the date and author. - -#### 2. New Features - -``` -git commit -m "Initial setup [new-feature:custom_serialization:Support for custom serialization formats]" -``` - -This adds a new feature called `custom_serialization` with the provided description and sets its status to "planned". - -#### 3. Changelog Entries - -``` -git commit -m "Fixed bug in chunk loading [changelog:Fixed race condition in async chunk loading]" -``` - -This adds an entry to the changelog with the date, message, and commit hash. - -#### 4. Status Updates - -``` -git commit -m "Completed milestone 1 [status:milestone1]" -``` - -This records a general status update. - -### Output Files - -The script generates and updates the following files: - -- `docs/features.md`: Human-readable feature tracking in Markdown format -- `docs/status/ChunkManager-Status.md`: Status document with feature progress and recent updates -- `CHANGELOG.md`: Project changelog with tagged entries from commits - -### Feature Statuses - -Features can have the following statuses: -- `planned`: Feature is planned but not yet started -- `in_progress`: Feature is currently being implemented -- `completed`: Feature has been implemented - -### Manual Editing - -You can manually edit the `docs/features.md` file to update feature descriptions, statuses, or add new features. The script will preserve your changes when it runs. - -### Requirements - -- Python 3.6+ -- Git repository \ No newline at end of file diff --git a/scripts/track_progress.py b/scripts/track_progress.py deleted file mode 100644 index 50174c6..0000000 --- a/scripts/track_progress.py +++ /dev/null @@ -1,291 +0,0 @@ -import os -import re -import sys -from datetime import datetime -from subprocess import check_output - -# Try to set UTF-8 encoding for better emoji support -sys.stderr = open( - sys.stderr.fileno(), mode="w", encoding="utf-8", errors="replace" -) # Configuration -PROGRESS_FILE = "docs/progress.json" -STATUS_DOC = "docs/status/ChunkManager-Status.md" -CHANGELOG_FILE = "CHANGELOG.md" -FEATURES_FILE = "docs/features.md" -GIT_LOG_LIMIT = 100 # number of commits to parse -UNTAGGED_COMMIT_LIMIT = 100 # number of untagged commits to include - -# Regular expression to match tags in commit messages -STATUS_TAG_RE = re.compile(r"\[status:(\w+)\]") -CHANGELOG_TAG_RE = re.compile(r"\[changelog:(.+?)\]") -FEATURE_TAG_RE = re.compile(r"\[feature:(\w+)\]") -NEW_FEATURE_RE = re.compile(r"\[new-feature:(\w+):(.+?)\]") - - -# Ensure directories exist -os.makedirs(os.path.dirname(STATUS_DOC), exist_ok=True) -os.makedirs(os.path.dirname(FEATURES_FILE), exist_ok=True) - - -def count_lines_of_code(): - """Count lines of code in the project and return statistics.""" - import os - import glob - - stats = {} - total_lines = 0 - file_stats = [] - - # Find all .cs files in the src/AdvChkSys directory and subdirectories - cs_files = glob.glob("src/AdvChkSys/**/*.cs", recursive=True) - - for file_path in cs_files: - with open(file_path, "r", encoding="utf-8", errors="ignore") as f: - lines = len(f.readlines()) - total_lines += lines - file_name = os.path.basename(file_path) - file_stats.append((file_name, lines, file_path)) - - # Sort by line count in descending order - file_stats.sort(key=lambda x: x[1], reverse=True) - - stats["total_lines"] = total_lines - stats["file_count"] = len(cs_files) - stats["files"] = file_stats - - return stats - - -# Load features from Markdown file if it exists -features = {} -if os.path.exists(FEATURES_FILE): - with open(FEATURES_FILE, "r", encoding="utf-8") as f: - content = f.read() - # Parse the markdown file - sections = re.split(r"## (\w+)", content)[1:] # Skip the header - for i in range(0, len(sections), 2): - if i + 1 < len(sections): - feature_key = sections[i] - feature_content = sections[i + 1] - - # Extract status, description, and date - status_match = re.search(r"- Status: (\w+)", feature_content) - desc_match = re.search( - r"- Description: (.+?)$", feature_content, re.MULTILINE - ) - date_match = re.search( - r"- Last Update: (.+?)$", feature_content, re.MULTILINE - ) - - status = status_match.group(1) if status_match else "planned" - description = desc_match.group(1) if desc_match else "" - date = date_match.group(1) if date_match else "" - - features[feature_key] = { - "status": status, - "description": description, - "date": date, - } -else: - # Default features - features = { - "spatial_queries": { - "status": "planned", - "description": "Methods to efficiently find chunks within regions or distances", # noqa: E501 - }, - "priority_loading": { - "status": "planned", - "description": "API to specify which chunks should be loaded first", # noqa: E501 - }, - "serialization_optimization": { - "status": "in_progress", - "description": "Further improvements to chunk saving/loading", - }, - "chunk_dependency": { - "status": "planned", - "description": "For cases where chunks need to reference neighbors", # noqa: E501 - }, - } - -# Get recent git log entries -git_log = check_output( - [ - "git", - "log", - f"-n{GIT_LOG_LIMIT}", - "--pretty=format:%h|%s|%ad|%an", - "--date=short", - ] -).decode() - -# Parse and collect updates -status_updates = {} -changelog_entries = [] -feature_updates = {} -new_features = {} -untagged_count = 0 # Initialize the counter for untagged commits - -for line in git_log.splitlines(): - parts = line.split("|") - if len(parts) < 4: - continue - - commit_hash, subject, date, author = parts - - status_match = STATUS_TAG_RE.search(subject) - changelog_match = CHANGELOG_TAG_RE.search(subject) - feature_match = FEATURE_TAG_RE.search(subject) - new_feature_match = NEW_FEATURE_RE.search(subject) - - if status_match: - status_key = status_match.group(1) - status_updates[status_key] = { - "status": "done", - "commit": commit_hash, - "date": date, - "message": subject, - } - - if changelog_match: - changelog_entries.append( - f"- {date}: {changelog_match.group(1)} ({commit_hash})" - ) - - if feature_match: - feature_key = feature_match.group(1) - if feature_key in features: - feature_updates[feature_key] = { - "status": "completed", - "date": date, - "author": author, - } - - if new_feature_match: - feature_key = new_feature_match.group(1) - feature_desc = new_feature_match.group(2) - if feature_key not in features: - new_features[feature_key] = { - "status": "planned", - "description": feature_desc, - "date": date, - "author": author, - } - - # Also add regular commits without tags to changelog entries - if not (status_match or changelog_match or feature_match or new_feature_match): - # Skip if we've reached the limit for untagged commits - if untagged_count >= UNTAGGED_COMMIT_LIMIT: - continue - - # Skip merge commits and very short messages - if not subject.startswith('Merge ') and len(subject) > 5: - # Extract the first sentence or up to 100 chars - commit_desc = subject.split('.')[0] - if len(commit_desc) > 100: - commit_desc = commit_desc[:97] + "..." - - # Add to changelog entries with a different format to distinguish from tagged entries - changelog_entries.append( - f"- {date}: {commit_desc} ({commit_hash})" - ) - untagged_count += 1 - -# Update features with new information -for feature_key, update in feature_updates.items(): - features[feature_key].update(update) - -# Add new features -for feature_key, feature_data in new_features.items(): - features[feature_key] = feature_data - -# Save features to Markdown file -with open(FEATURES_FILE, "w", encoding="utf-8") as f: - f.write("# Feature Tracking\n\n") - - for feature_key, feature_data in features.items(): - status = feature_data.get("status", "planned") - description = feature_data.get("description", "") - date = feature_data.get("date", "") - author = feature_data.get("author", "") - - f.write(f"## {feature_key}\n") - f.write(f"- Status: {status}\n") - f.write(f"- Description: {description}\n") - f.write(f"- Last Update: {date}\n") - if author: - f.write(f"- Updated By: {author}\n") - f.write("\n") - -# Generate status document -with open(STATUS_DOC, "w", encoding="utf-8") as f: - f.write("# AdvChkSys Development Status\n\n") - f.write(f"Last updated: {datetime.now().strftime('%Y-%m-%d')}\n\n") - - # Add code statistics section - f.write("## Code Statistics\n\n") - code_stats = count_lines_of_code() - f.write(f"Total lines of code: **{code_stats['total_lines']}**\n\n") - f.write(f"Number of source files: **{code_stats['file_count']}**\n\n") - - f.write("### All Files by Line Count\n\n") - f.write("| File | Lines | Path |\n") - f.write("|------|------:|------|\n") - - for file_name, lines, file_path in code_stats["files"]: - f.write(f"| {file_name} | {lines} | {file_path} |\n") - - f.write("\n") - - f.write("## Feature Status\n\n") - f.write("| Feature | Status | Description | Last Update |\n") - f.write("|---------|--------|-------------|-------------|\n") - - for feature_key, feature_data in features.items(): - status = feature_data.get("status", "unknown") - description = feature_data.get("description", "") - date = feature_data.get("date", "") - - # Use text indicators instead of emojis - status_indicator = { - "planned": "[PLANNED]", - "in_progress": "[IN PROGRESS]", - "completed": "[COMPLETED]", - "done": "[DONE]", - }.get(status, "[UNKNOWN]") - - f.write( - f"| {feature_key.replace('_', ' ').title()} | {status_indicator} | {description} | {date} |\n" # noqa: E501 - ) - - f.write("\n## Recent Updates\n\n") - for entry in changelog_entries[ - :100 - ]: - f.write(f"{entry}\n") - -# Update changelog if there are new entries -if changelog_entries and os.path.exists(CHANGELOG_FILE): - with open(CHANGELOG_FILE, "r", encoding="utf-8") as f: - existing_changelog = f.read() - - with open(CHANGELOG_FILE, "w", encoding="utf-8") as f: - # Add new entries at the top, under the first heading - lines = existing_changelog.splitlines() - insertion_point = next( - (i for i, line in enumerate(lines) if line.startswith("##")), 2 - ) - - updated_changelog = "\n".join(lines[:insertion_point]) + "\n\n" - updated_changelog += "\n".join(changelog_entries) + "\n\n" - updated_changelog += "\n".join(lines[insertion_point:]) - - f.write(updated_changelog) -elif changelog_entries: - # Create new changelog file - with open(CHANGELOG_FILE, "w", encoding="utf-8") as f: - f.write("# Changelog\n\n") - f.write("\n".join(changelog_entries)) - f.write("\n") - -print(f"Progress tracking updated. Status document generated at {STATUS_DOC}") -print(f"Features updated at {FEATURES_FILE}") diff --git a/scripts/track_progress/CHANGELOG.md b/scripts/track_progress/CHANGELOG.md new file mode 100644 index 0000000..4dc68c6 --- /dev/null +++ b/scripts/track_progress/CHANGELOG.md @@ -0,0 +1,2 @@ +# Changelog + diff --git a/scripts/track_progress/README.md b/scripts/track_progress/README.md new file mode 100644 index 0000000..2733efc --- /dev/null +++ b/scripts/track_progress/README.md @@ -0,0 +1,207 @@ +# Progress Tracker + +A comprehensive tool for tracking project progress, feature status, and generating documentation from Git history. + +## Overview + +The Progress Tracker analyzes your Git repository and codebase to automatically generate: + +- Feature tracking documentation +- Project status reports +- Changelog based on commit messages +- Code statistics and metrics + +## Installation + +### Prerequisites + +- Python 3.8 or higher +- Git + +### Dependencies + +Install the required dependencies: + +```bash +pip install -r scripts/track_progress/requirements.txt +``` + +For development or building the executable, install all dependencies: + +```bash +pip install -r scripts/track_progress/requirements.txt[all] +``` + +### Quick Install + +Run the setup script to install the tracker to your project: + +```bash +python scripts/track_progress/scripts/setup_install.py --project "Your Project Name" --dest path/to/destination +``` + +Options: +- `--project name`: Set your project name +- `--dest folder`: Set destination directory (default: "scripts") +- `--verbose` or `-v`: Enable verbose output +- `--force` or `-f`: Force overwrite existing files + +### Manual Installation + +1. Copy the `track_progress` directory to your project +2. Create a `docs/status` directory in your project root +3. Create a `docs/features.md` file if it doesn't exist +4. Create a `CHANGELOG.md` file in your project root + +## Usage + +### Basic Usage + +Run the tracker with default settings: + +```bash +# Using the Python script +python scripts/track_progress/track_progress.py + +# Using the executable (if built) +scripts/track_progress/track_progress +``` + +### Command-Line Options + +The tracker supports the following command-line options: + +``` +--config PATH Path to configuration file (default: scripts/track_progress/progress_config.json) +--project NAME Project name (overrides config) +--output-dir DIR Output directory for status document (overrides config) +--repo PATH Repository path to analyze (default: current directory) +--verbose, -v Enable verbose output +``` + +Examples: + +```bash +# Specify a custom configuration file +python scripts/track_progress/track_progress.py --config path/to/config.json + +# Override project name +python scripts/track_progress/track_progress.py --project "My Project" + +# Specify output directory +python scripts/track_progress/track_progress.py --output-dir docs/my-status + +# Analyze a different repository +python scripts/track_progress/track_progress.py --repo path/to/repo + +# Enable verbose output +python scripts/track_progress/track_progress.py --verbose +``` + +## Commit Message Tags + +The tracker recognizes special tags in commit messages to update feature status and documentation: + +| Tag | Description | Example | +|-----|-------------|---------| +| `[status:key]` | Update status of a component | `[status:database]` | +| `[feature:key]` | Mark a feature as completed | `[feature:login]` | +| `[new-feature:key:description]` | Add a new feature | `[new-feature:search:Implement search functionality]` | +| `[changelog:message]` | Add an entry to the changelog | `[changelog:Added dark mode]` | +| `[fix:description]` | Document a bug fix | `[fix:Fixed login issue]` | +| `[issue:id]` | Reference an issue | `[issue:42]` | +| `[breaking]` | Mark a breaking change | `[breaking]` | +| `[roadmap:milestone:item]` | Add an item to the roadmap | `[roadmap:v2:Add OAuth support]` | +| `[milestone:key]` | Reference a milestone | `[milestone:v1.0]` | +| `[priority:level]` | Set priority level | `[priority:high]` | +| `[owner:name]` | Assign an owner | `[owner:John]` | +| `[tag:name]` | Add a custom tag | `[tag:security]` | + +Example commit message: +``` +Implement search functionality [feature:search] [changelog:Added search with filtering] +``` + +## Configuration + +The tracker uses a JSON configuration file (`progress_config.json`) with the following settings: + +```json +{ + "project_name": "Your Project", + "output_dir": "docs/status", + "status_doc": "status.md", + "features_file": "docs/features.md", + "changelog_file": "CHANGELOG.md", + "git_log_limit": 100, + "untagged_commit_limit": 50, + "top_files_limit": 20, + "exclude_dirs": ["node_modules", "venv", ".git"], + "source_extensions": [".py", ".cs", ".js"], + "templates": { + "status": "status_template.md", + "features": "feature_template.md", + "changelog": "changelog_template.md" + } +} +``` + +## Output Files + +The tracker generates the following files: + +- `docs/features.md`: Feature tracking document +- `docs/status/status.md`: Project status document (or path specified in config) +- `CHANGELOG.md`: Project changelog + +## Components + +The tracker consists of the following components: + +- `track_progress.py`: Main script that orchestrates the tracking process +- `config.py`: Configuration management +- `git_analyzer.py`: Git history analysis +- `code_stats.py`: Code statistics and metrics +- `feature_markdown.py`: Feature tracking utilities +- `template_engine.py`: Template rendering engine +- `changelog_generator.py`: Changelog generation +- `roadmap_generator.py`: Roadmap generation + +## Building an Executable + +You can build a standalone executable using Nuitka: + +```bash +python scripts/track_progress/scripts/build_with_nuitka.py +``` + +This creates an executable in the `scripts/track_progress/dist` directory. + +## Customizing Templates + +The tracker uses Markdown templates to generate documentation. You can customize these templates in the `scripts/track_progress/templates` directory: + +- `status_template.md`: Template for the status document +- `feature_template.md`: Template for the feature tracking document +- `changelog_template.md`: Template for the changelog + +The templates use a simple syntax: +- `{{ variable }}`: Insert a variable +- `{% if condition %}...{% endif %}`: Conditional block +- `{% for item in items %}...{% endfor %}`: Loop over items + +## Troubleshooting + +### Common Issues + +1. **Missing Git Repository**: Ensure you're running the tracker in a Git repository. +2. **No Features Found**: Create a basic `docs/features.md` file with at least one feature. +3. **Template Not Found**: Check that templates exist in the templates directory. + +### Debug Mode + +Run with `--verbose` to see detailed output: + +```bash +python scripts/track_progress/track_progress.py --verbose +``` diff --git a/scripts/track_progress/__init__.py b/scripts/track_progress/__init__.py new file mode 100644 index 0000000..c2d4cd1 --- /dev/null +++ b/scripts/track_progress/__init__.py @@ -0,0 +1 @@ +# This file makes the directory a Python package \ No newline at end of file diff --git a/scripts/track_progress/changelog_generator.py b/scripts/track_progress/changelog_generator.py new file mode 100644 index 0000000..7c012b3 --- /dev/null +++ b/scripts/track_progress/changelog_generator.py @@ -0,0 +1,103 @@ +""" +Changelog generator for project documentation. +""" + +import os +from datetime import datetime +from typing import List, Dict, Any + + +def update_changelog_file( + changelog_entries: List[Dict[str, Any]], + changelog_file: str, + max_entries: int = 100, + verbose: bool = False, +) -> bool: + """Update the changelog file with new entries.""" + if not changelog_file: + if verbose: + print("No changelog file specified, skipping update") + return False + + if verbose: + print(f"Updating changelog file: {changelog_file}") + print(f"Found {len(changelog_entries)} entries to add") + + # Ensure the directory exists if the file is not in the current directory + changelog_dir = os.path.dirname(changelog_file) + if ( + changelog_dir + ): # Only create directory if there is one (not empty string) + os.makedirs(changelog_dir, exist_ok=True) + + # Create the file if it doesn't exist + if not os.path.exists(changelog_file): + with open(changelog_file, "w", encoding="utf-8") as f: + f.write("# Changelog\n\n") + if verbose: + print(f"Created new changelog file: {changelog_file}") + + # Read existing content + with open(changelog_file, "r", encoding="utf-8") as f: + content = f.read() + + # Extract header (everything before the first entry) + header_end = content.find("## ") + if header_end == -1: + header = "# Changelog\n\n" + else: + header = content[:header_end] + + # Format new entries + new_entries = [] + + # Group entries by date + entries_by_date = {} + for entry in changelog_entries: + date = entry.get("date", "") + if not date: + continue + + if date not in entries_by_date: + entries_by_date[date] = [] + + entries_by_date[date].append(entry) + + # Sort dates in reverse chronological order + for date in sorted(entries_by_date.keys(), reverse=True): + entries = entries_by_date[date] + + # Format date as a section header + new_entries.append(f"## {date}\n") + + # Add each entry + for entry in entries: + message = entry.get("message", "").strip() + commit = entry.get("commit", "") + + if message: + # Clean up the message + if message.startswith("- "): + message = message[2:] + + new_entries.append(f"- {message} ({commit})") + + new_entries.append("") # Add an empty line after each date section + + # If no new entries, just return + if not new_entries: + if verbose: + print("No new entries to add to changelog") + return False + + # Combine header and new entries + new_content = header + "\n".join(new_entries) + + # Write the updated content + with open(changelog_file, "w", encoding="utf-8") as f: + f.write(new_content) + + if verbose: + print(f"Updated changelog with {len(changelog_entries)} new entries") + + return True diff --git a/scripts/track_progress/code_stats.py b/scripts/track_progress/code_stats.py new file mode 100644 index 0000000..1fc03da --- /dev/null +++ b/scripts/track_progress/code_stats.py @@ -0,0 +1,121 @@ +import os +import glob +from typing import Dict, Any, List, Optional + + +def count_lines(file_path: str) -> int: + """Count lines in a file.""" + try: + with open(file_path, "r", encoding="utf-8", errors="ignore") as f: + return len(f.readlines()) + except Exception: + return 0 + + +def get_language(file_path: str) -> str: + """Determine language from file extension.""" + ext = os.path.splitext(file_path)[1].lower() + language_map = { + ".py": "Python", + ".cs": "C#", + ".js": "JavaScript", + ".ts": "TypeScript", + ".html": "HTML", + ".css": "CSS", + ".md": "Markdown", + ".json": "JSON", + ".xml": "XML", + ".java": "Java", + ".cpp": "C++", + ".c": "C", + ".h": "C/C++ Header", + ".go": "Go", + ".rs": "Rust", + ".php": "PHP", + ".rb": "Ruby", + ".sh": "Shell", + ".bat": "Batch", + ".ps1": "PowerShell", + ".sql": "SQL", + ".yaml": "YAML", + ".yml": "YAML", + } + return language_map.get(ext, "Other") + + +def analyze_code_stats( + root_dir: str = ".", + exclude_dirs: List[str] = None, + source_extensions: List[str] = None, + top_files_limit: int = 20, +) -> Dict[str, Any]: + """Analyze code statistics for the project.""" + if exclude_dirs is None: + exclude_dirs = ["node_modules", "venv", ".git", ".vs", "bin", "obj"] + + if source_extensions is None: + source_extensions = [ + ".py", + ".cs", + ".js", + ".ts", + ".html", + ".css", + ".md", + ] + + stats = { + "total_lines": 0, + "file_count": 0, + "languages": {}, + "top_files": [], + } + + file_stats = [] + + # Convert exclude_dirs to absolute paths + exclude_paths = [os.path.join(root_dir, d) for d in exclude_dirs] + + # Find all source files + for ext in source_extensions: + pattern = os.path.join(root_dir, "**", f"*{ext}") + for file_path in glob.glob(pattern, recursive=True): + # Skip excluded directories + if any( + file_path.startswith(exclude_path) + for exclude_path in exclude_paths + ): + continue + + # Count lines + lines = count_lines(file_path) + + # Get language + language = get_language(file_path) + + # Update statistics + stats["total_lines"] += lines + stats["file_count"] += 1 + + # Update language count + if language not in stats["languages"]: + stats["languages"][language] = 0 + stats["languages"][language] += 1 + + # Add to file stats + file_stats.append( + { + "path": file_path, + "name": os.path.basename(file_path), + "lines": lines, + "language": language, + } + ) + + # Sort file stats by line count + file_stats.sort(key=lambda x: x["lines"], reverse=True) + + # Get top files + stats["top_files"] = file_stats[:top_files_limit] + + return stats diff --git a/scripts/track_progress/config.py b/scripts/track_progress/config.py new file mode 100644 index 0000000..bec408d --- /dev/null +++ b/scripts/track_progress/config.py @@ -0,0 +1,87 @@ +""" +Configuration management for project status documentation. +""" + +import os +import json +from typing import Dict, Any, List, Optional + + +class Config: + """Configuration manager for project status documentation.""" + + def __init__(self, config_file: str = "scripts/progress_config.json"): + """Initialize with configuration file.""" + self.config_file = config_file + self.config = self._load_config() + + def _load_config(self) -> Dict[str, Any]: + """Load configuration from file.""" + default_config = { + "project_name": "Project", + "output_dir": "docs/status", + "status_doc": "status.md", + "features_file": "docs/features.md", + "changelog_file": "CHANGELOG.md", + "git_log_limit": 100, + "untagged_commit_limit": 50, + "top_files_limit": 20, + "exclude_dirs": [ + "node_modules", + "venv", + ".git", + ".vs", + "bin", + "obj", + ], + "source_extensions": [".py", ".cs", ".js", ".ts", ".html", ".css"], + "templates": { + "status": "status_template.md", + "features": "feature_template.md", + "changelog": "changelog_template.md", + }, + } + + if not os.path.exists(self.config_file): + return default_config + + try: + with open(self.config_file, "r", encoding="utf-8") as f: + config = json.load(f) + + # Merge with default config to ensure all keys exist + merged_config = default_config.copy() + merged_config.update(config) + + return merged_config + except Exception: + return default_config + + def get(self, key: str, default: Any = None) -> Any: + """Get a configuration value.""" + if "." in key: + # Handle nested keys like "templates.status" + parts = key.split(".") + value = self.config + for part in parts: + if isinstance(value, dict) and part in value: + value = value[part] + else: + return default + return value + + return self.config.get(key, default) + + def set(self, key: str, value: Any) -> None: + """Set a configuration value.""" + if "." in key: + # Handle nested keys + parts = key.split(".") + config = self.config + for part in parts[:-1]: + if part not in config: + config[part] = {} + config = config[part] + config[parts[-1]] = value + else: + self.config[key] = value diff --git a/scripts/track_progress/docs/features.md b/scripts/track_progress/docs/features.md new file mode 100644 index 0000000..8ef5244 --- /dev/null +++ b/scripts/track_progress/docs/features.md @@ -0,0 +1,218 @@ +# Feature Tracking + +## Overview + +This document tracks the implementation status of all features in the Project 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 + +### 2d3d_chunk_management + +**Status:** โœ… completed +**Description:** 2D/3D chunk management with memory efficiency +**Last Update:** 2025-05-10 + +### asynchronous_chunk_loading + +**Status:** โœ… completed +**Description:** Asynchronous chunk loading and processing +**Last Update:** 2025-05-10 + +### autogenerate_status_docs + +**Status:** ๐Ÿ”„ in_progress +**Description:** Auto-generate status docs and changelog (partially implemented) +**Last Update:** 2025-05-10 + +### better_async_exception + +**Status:** ๐Ÿ”„ in_progress +**Description:** Better async exception handling with specific types and logging +**Last Update:** 2025-05-10 + +### chunk_serialization_and + +**Status:** โœ… completed +**Description:** Chunk serialization and deserialization +**Last Update:** 2025-05-10 + +### dependencyaware_disposal_logic + +**Status:** ๐Ÿ”„ in_progress +**Description:** Dependency-aware disposal logic (Partially Implemented) +**Last Update:** 2025-05-10 + +### edge_chunk_unload + +**Status:** ๐Ÿ”„ in_progress +**Description:** Edge chunk unload delay under high concurrency +**Last Update:** 2025-05-10 + +### events_for_loadunloadsave + +**Status:** โœ… completed +**Description:** Events for load/unload/save operations +**Last Update:** 2025-05-10 + +### explicit_synchronization_and + +**Status:** โœ… completed +**Description:** Explicit synchronization and concurrent collections +**Last Update:** 2025-05-11 + +### full_dispose_pattern + +**Status:** ๐Ÿ”„ in_progress +**Description:** Full dispose pattern with finalizers for unmanaged resources (Partially Implemented) +**Last Update:** 2025-05-10 + +### interfacelevel_dependency_methods + +**Status:** ๐Ÿ“ planned +**Description:** Interface-level dependency methods +**Last Update:** 2025-05-10 + +### interfacelevel_spatial_query + +**Status:** โœ… completed +**Description:** Interface-level spatial query methods with support for custom filters +**Last Update:** 2025-05-11 + +### lru_caching_array + +**Status:** โœ… completed +**Description:** LRU caching, array pooling, and air-singleton patterns +**Last Update:** 2025-05-10 + +### parallel_processing_of + +**Status:** โœ… completed +**Description:** Parallel processing of chunks based on spatial queries and regions +**Last Update:** 2025-05-11 + +### parse_git_logs + +**Status:** โœ… completed +**Description:** Parse Git logs for status updates (partially implemented testing with this push.) +**Last Update:** 2025-05-12 + +### prioritized_chunk_loading + +**Status:** ๐Ÿ”„ in_progress +**Description:** Prioritized chunk loading system (partially implemented) +**Last Update:** 2025-05-10 + +### python_and_net + +**Status:** โœ… completed +**Description:** Python and .NET interoperability +**Last Update:** 2025-05-10 + +### runtimeadjustable_configuration_options + +**Status:** ๐Ÿ“ planned +**Description:** Runtime-adjustable configuration options +**Last Update:** 2025-05-10 + +### spatial_indexing_and + +**Status:** โœ… completed +**Description:** Spatial indexing and region queries with 2D/3D support and quadtree optimization +**Last Update:** 2025-05-11 + +### track_and_manage + +**Status:** โœ… completed +**Description:** Track and manage chunk resources +**Last Update:** 2025-05-10 + +### track_load_times + +**Status:** ๐Ÿ”„ in_progress +**Description:** Track load times, cache hit rates, and memory usage (partially implemented) +**Last Update:** 2025-05-10 + +### track_progress_and feature_statuses + +**Status:** โœ… completed +**Description:** Track progress and auto-update status documents (you can use cron or something or a vs commit script that requires running this after committing) +**Last Update:** 2025-05-12 + +## Feature Categories + +### Completed Features + +- **2d3d_chunk_management**: 2D/3D chunk management with memory efficiency +- **asynchronous_chunk_loading**: Asynchronous chunk loading and processing +- **chunk_serialization_and**: Chunk serialization and deserialization +- **events_for_loadunloadsave**: Events for load/unload/save operations +- **explicit_synchronization_and**: Explicit synchronization and concurrent collections +- **interfacelevel_spatial_query**: Interface-level spatial query methods with support for custom filters +- **lru_caching_array**: LRU caching, array pooling, and air-singleton patterns +- **parallel_processing_of**: Parallel processing of chunks based on spatial queries and regions +- **python_and_net**: Python and .NET interoperability +- **spatial_indexing_and**: Spatial indexing and region queries with 2D/3D support and quadtree optimization + +### In Progress Features + +- **autogenerate_status_docs**: Auto-generate status docs and changelog (partially implemented) +- **better_async_exception**: Better async exception handling with specific types and logging +- **dependencyaware_disposal_logic**: Dependency-aware disposal logic (Partially Implemented) +- **edge_chunk_unload**: Edge chunk unload delay under high concurrency +- **full_dispose_pattern**: Full dispose pattern with finalizers for unmanaged resources (Partially Implemented) +- **parse_git_logs**: Parse Git logs for status updates (partially implemented testing with this push.) +- **prioritized_chunk_loading**: Prioritized chunk loading system (partially implemented) +- **track_load_times**: Track load times, cache hit rates, and memory usage (partially implemented) + +### Planned Features + +- **interfacelevel_dependency_methods**: Interface-level dependency methods +- **runtimeadjustable_configuration_options**: Runtime-adjustable configuration options + + \ No newline at end of file diff --git a/scripts/track_progress/docs/status/status.md b/scripts/track_progress/docs/status/status.md new file mode 100644 index 0000000..102ce57 --- /dev/null +++ b/scripts/track_progress/docs/status/status.md @@ -0,0 +1,100 @@ +# Project Development Status + +Last updated: 2025-05-12 + +## Code Statistics + + +Total lines of code: **2151** + +Number of source files: **15** + +### Files by Language + +- **Python**: 15 files + + +### Top Files by Line Count + +| File | Lines | Language | Path | +|------|------:|----------|------| + +| 2D3D Chunk Management | โœ… completed | 2D/3D chunk management with memory efficiency | 2025-05-10 | | + +| Asynchronous Chunk Loading | โœ… completed | Asynchronous chunk loading and processing | 2025-05-10 | | + +| Autogenerate Status Docs | ๐Ÿ”„ in_progress | Auto-generate status docs and changelog (partially implemented) | 2025-05-10 | | + +| Better Async Exception | ๐Ÿ”„ in_progress | Better async exception handling with specific types and logging | 2025-05-10 | | + +| Chunk Serialization And | โœ… completed | Chunk serialization and deserialization | 2025-05-10 | | + +| Dependencyaware Disposal Logic | ๐Ÿ”„ in_progress | Dependency-aware disposal logic (Partially Implemented) | 2025-05-10 | | + +| Edge Chunk Unload | ๐Ÿ”„ in_progress | Edge chunk unload delay under high concurrency | 2025-05-10 | | + +| Events For Loadunloadsave | โœ… completed | Events for load/unload/save operations | 2025-05-10 | | + +| Explicit Synchronization And | โœ… completed | Explicit synchronization and concurrent collections | 2025-05-11 | | + +| Full Dispose Pattern | ๐Ÿ”„ in_progress | Full dispose pattern with finalizers for unmanaged resources (Partially Implemented) | 2025-05-10 | | + +| Interfacelevel Dependency Methods | ๐Ÿ“ planned | Interface-level dependency methods | 2025-05-10 | | + +| Interfacelevel Spatial Query | โœ… completed | Interface-level spatial query methods with support for custom filters | 2025-05-11 | | + +| Lru Caching Array | โœ… completed | LRU caching, array pooling, and air-singleton patterns | 2025-05-10 | | + +| Parallel Processing Of | โœ… completed | Parallel processing of chunks based on spatial queries and regions | 2025-05-11 | | + +| Parse Git Logs | ๐Ÿ”„ in_progress | Parse Git logs for status updates (partially implemented testing with this push.) | 2025-05-11 | | + +| Prioritized Chunk Loading | ๐Ÿ”„ in_progress | Prioritized chunk loading system (partially implemented) | 2025-05-10 | | + +| Python And Net | โœ… completed | Python and .NET interoperability | 2025-05-10 | | + +| Runtimeadjustable Configuration Options | ๐Ÿ“ planned | Runtime-adjustable configuration options | 2025-05-10 | | + +| Spatial Indexing And | โœ… completed | Spatial indexing and region queries with 2D/3D support and quadtree optimization | 2025-05-11 | | + +| Track And Manage | โœ… completed | Track and manage chunk resources | 2025-05-10 | | + +| Track Feature Statuses | โœ… completed | Track feature statuses | 2025-05-11 | | + +| Track Load Times | ๐Ÿ”„ in_progress | Track load times, cache hit rates, and memory usage (partially implemented) | 2025-05-10 | | + +| Track Progress And | ๐Ÿ”„ in_progress | Track progress and auto-update status documents (mostly implemented kinda still manual...) | 2025-05-11 | | + + +## Recent Updates + + +- 2025-05-11: part of the code formatter fix and minor bug fixes (5352e90) + +- 2025-05-11: fixed code formatter (01d2cbf) + +- 2025-05-11: fixed track_progress not getting git history it was not getting the data correctly (657e19f) + +- 2025-05-11: removed unnecessary ignores in gitignore (79d6716) + +- 2025-05-11: XML comments added Builds cleanly (0ae815c) + +- 2025-05-11: updated track_progress restored accidentally deleted source file (8a6f914) + +- 2025-05-10: fixed a bug of not building (09ee0d8) + +- 2025-05-10: added a build (e59a909) + +- 2025-05-10: updated ChunkMark (f35c612) + +- 2025-05-10: updated memory memory handling to be more safe (becedcc) + +- 2025-05-09: first commit (f1b3868) + + +{% if known_issues %} +## Known Issues + + + + diff --git a/scripts/track_progress/feature_markdown.py b/scripts/track_progress/feature_markdown.py new file mode 100644 index 0000000..4b74fda --- /dev/null +++ b/scripts/track_progress/feature_markdown.py @@ -0,0 +1,317 @@ +""" +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) diff --git a/scripts/track_progress/git_analyzer.py b/scripts/track_progress/git_analyzer.py new file mode 100644 index 0000000..04b2283 --- /dev/null +++ b/scripts/track_progress/git_analyzer.py @@ -0,0 +1,238 @@ +import os +import re +import subprocess +from datetime import datetime +from typing import Dict, Any, List, Optional, Tuple + + +class GitAnalyzer: + """Analyze Git repository for project status information.""" + + def __init__(self, repo_path: str = "."): + """Initialize with repository path.""" + self.repo_path = repo_path + + def _run_git_command(self, command: List[str]) -> str: + """Run a Git command and return the output.""" + try: + result = subprocess.run( + ["git"] + command, + cwd=self.repo_path, + capture_output=True, + text=True, + check=True, + ) + return result.stdout.strip() + except subprocess.CalledProcessError: + return "" + + def get_repo_info(self) -> Dict[str, Any]: + """Get basic repository information.""" + info = {} + + # Get remote URL + remote_url = self._run_git_command(["remote", "get-url", "origin"]) + info["remote_url"] = remote_url + + # Get current branch + branch = self._run_git_command(["branch", "--show-current"]) + info["branch"] = branch + + # Get last commit + last_commit = self._run_git_command( + ["log", "-1", "--pretty=format:%h|%s|%ad|%an", "--date=short"] + ) + if last_commit: + parts = last_commit.split("|") + if len(parts) >= 4: + info["last_commit"] = { + "hash": parts[0], + "message": parts[1], + "date": parts[2], + "author": parts[3], + } + + return info + + def analyze_commits( + self, limit: int = 100, untagged_limit: int = 50 + ) -> Dict[str, Any]: + """Analyze Git commits for feature updates, changelog entries, etc.""" + result = { + "feature_updates": {}, + "new_features": {}, + "changelog_entries": [], + "fixes": [], + "issues": [], + "untagged_commits": [], + "milestones": {}, + "roadmap_items": {}, + } + + # Regular expressions for parsing commit messages + status_re = re.compile(r"\[status:(\w+)\]") + feature_re = re.compile(r"\[feature:(\w+)\]") + new_feature_re = re.compile(r"\[new-feature:(\w+):(.+?)\]") + changelog_re = re.compile(r"\[changelog:(.+?)\]") + fix_re = re.compile(r"\[fix:(.+?)\]") + issue_re = re.compile(r"\[issue:(.+?)\]") + milestone_re = re.compile(r"\[milestone:(\w+)\]") + roadmap_re = re.compile(r"\[roadmap:(\w+):(.+?)\]") + + # Get Git log + git_log = self._run_git_command( + [ + "log", + f"-n{limit}", + "--pretty=format:%h|%s|%ad|%an", + "--date=short", + ] + ) + + untagged_count = 0 + + # Parse commits + for line in git_log.splitlines(): + parts = line.split("|") + if len(parts) < 4: + continue + + commit_hash, subject, date, author = parts + + # Check for tags + status_match = status_re.search(subject) + feature_match = feature_re.search(subject) + new_feature_match = new_feature_re.search(subject) + changelog_match = changelog_re.search(subject) + fix_match = fix_re.search(subject) + issue_match = issue_re.search(subject) + milestone_match = milestone_re.search(subject) + roadmap_match = roadmap_re.search(subject) + + # Process status updates + if status_match: + status_key = status_match.group(1) + result["feature_updates"][status_key] = { + "status": "completed", + "date": date, + "author": author, + } + + # Process feature updates + if feature_match: + feature_key = feature_match.group(1) + result["feature_updates"][feature_key] = { + "status": "completed", + "date": date, + "author": author, + } + + # Process new features + if new_feature_match: + feature_key = new_feature_match.group(1) + feature_desc = new_feature_match.group(2) + result["new_features"][feature_key] = { + "status": "planned", + "description": feature_desc, + "date": date, + "author": author, + } + + # Process changelog entries + if changelog_match: + result["changelog_entries"].append( + { + "message": changelog_match.group(1), + "date": date, + "commit": commit_hash, + "author": author, + } + ) + + # Process fixes + if fix_match: + result["fixes"].append( + { + "message": fix_match.group(1), + "date": date, + "commit": commit_hash, + "author": author, + } + ) + + # Process issues + if issue_match: + result["issues"].append( + { + "message": issue_match.group(1), + "date": date, + "commit": commit_hash, + "author": author, + } + ) + + # Process milestones + if milestone_match: + milestone_key = milestone_match.group(1) + if milestone_key not in result["milestones"]: + result["milestones"][milestone_key] = { + "first_date": date, + "last_date": date, + "commits": [], + } + else: + result["milestones"][milestone_key]["last_date"] = date + + result["milestones"][milestone_key]["commits"].append( + commit_hash + ) + + # Process roadmap items + if roadmap_match: + milestone_key = roadmap_match.group(1) + item_desc = roadmap_match.group(2) + + if milestone_key not in result["roadmap_items"]: + result["roadmap_items"][milestone_key] = [] + + result["roadmap_items"][milestone_key].append( + { + "description": item_desc, + "date": date, + "commit": commit_hash, + "author": author, + } + ) + + # Process untagged commits + if not any( + [ + status_match, + feature_match, + new_feature_match, + changelog_match, + fix_match, + issue_match, + milestone_match, + roadmap_match, + ] + ): + if untagged_count < untagged_limit: + # Skip merge commits and very short messages + if not subject.startswith("Merge ") and len(subject) > 5: + # Extract the first sentence or up to 100 chars + commit_desc = subject.split(".")[0] + if len(commit_desc) > 100: + commit_desc = commit_desc[:97] + "..." + + result["untagged_commits"].append( + { + "message": commit_desc, + "date": date, + "commit": commit_hash, + "author": author, + } + ) + untagged_count += 1 + + return result diff --git a/scripts/track_progress/progress_config.json b/scripts/track_progress/progress_config.json new file mode 100644 index 0000000..dc927a9 --- /dev/null +++ b/scripts/track_progress/progress_config.json @@ -0,0 +1,37 @@ +{ + "project_name": "AdvChkSys", + "output_dir": "docs/status", + "status_doc": "ChunkManager-Status.md", + "features_file": "docs/features.md", + "changelog_file": "CHANGELOG.md", + "git_log_limit": 100, + "untagged_commit_limit": 50, + "top_files_limit": 20, + "exclude_dirs": [ + "node_modules", + "venv", + ".git", + ".vs", + "bin", + "obj", + "dist", + "build" + ], + "source_extensions": [ + ".py", + ".cs", + ".js", + ".ts", + ".html", + ".css", + ".rs", + ".go", + ".java", + ".md" + ], + "templates": { + "status": "status_template.md", + "features": "feature_template.md", + "changelog": "changelog_template.md" + } +} \ No newline at end of file diff --git a/scripts/track_progress/requirements.txt b/scripts/track_progress/requirements.txt new file mode 100644 index 0000000..6540481 --- /dev/null +++ b/scripts/track_progress/requirements.txt @@ -0,0 +1,16 @@ +# Core dependencies +argparse # For parsing command-line arguments +shutil # For file and directory operations +GitPython # For Git repository analysis +Jinja2 # Alternative template engine (optional) +Markdown # For Markdown processing (optional) + +# Build dependencies (only needed for building the executable) +nuitka # For compiling Python to executable +ordered-set # Required by Nuitka +zstandard # For better compression in Nuitka builds + +# Development dependencies (optional) +black # Code formatting +pylint # Code linting +pytest # Testing \ No newline at end of file diff --git a/scripts/track_progress/roadmap_generator.py b/scripts/track_progress/roadmap_generator.py new file mode 100644 index 0000000..b56d766 --- /dev/null +++ b/scripts/track_progress/roadmap_generator.py @@ -0,0 +1,33 @@ +""" +Roadmap generator for project status documentation. +""" + +from typing import Dict, Any, List, Optional + + +def generate_roadmap( + milestones: Dict[str, Dict[str, Any]], + roadmap_items: Dict[str, List[Dict[str, Any]]], +) -> List[Dict[str, Any]]: + """Generate roadmap from milestones and roadmap items.""" + roadmap = [] + + # Process each milestone + for milestone_key, milestone_data in milestones.items(): + # Create milestone entry + milestone = { + "name": milestone_key.replace("_", " ").title(), + "target_date": milestone_data.get("last_date", "TBD"), + "items": [], + } + + # Add roadmap items for this milestone + if milestone_key in roadmap_items: + for item in roadmap_items[milestone_key]: + milestone["items"].append( + {"description": item["description"], "status": "planned"} + ) + + roadmap.append(milestone) + + return roadmap diff --git a/scripts/track_progress/scripts/SCRIPTS.md b/scripts/track_progress/scripts/SCRIPTS.md new file mode 100644 index 0000000..cf6bbee --- /dev/null +++ b/scripts/track_progress/scripts/SCRIPTS.md @@ -0,0 +1,55 @@ +# Progress Tracker Scripts + +This directory contains utility scripts for building, installing, and running the progress tracker. + +## Available Scripts + +### setup_install.py + +Installs the progress tracker to your project. + +```bash +python setup_install.py --dest scripts --project "Your Project Name" +``` + +Options: +- `--dest folder`: Set destination directory (default: "scripts") +- `--project name`: Set project name (default: "Project") +- `--verbose` or `-v`: Enable verbose output +- `--force` or `-f`: Force overwrite existing files + +### build_with_nuitka.py + +Builds the progress tracker into a standalone executable using Nuitka. + +```bash +python build_with_nuitka.py +``` + +Options: +- `--output-dir`: Output directory for the build (default: "dist") +- `--verbose` or `-v`: Enable verbose output + +### create_launcher_scripts.py + +Creates launcher scripts for running the progress tracker. This is usually called by setup_install.py. + +## Launcher Scripts + +The following launcher scripts are created during installation: + +- `run_progress_tracker.py`: Python launcher script +- `run_progress_tracker.sh`: Shell launcher script (Linux/macOS) +- `run_progress_tracker.bat`: Batch launcher script (Windows) + +## Usage Example + +1. Install the tracker: + ```bash + python setup_install.py --project "My Project" --verbose + ``` + +2. Run the tracker using one of the launcher scripts: + - Windows: `run_progress_tracker.bat` + - Linux/macOS: `./run_progress_tracker.sh` + - Any platform: `python run_progress_tracker.py` \ No newline at end of file diff --git a/scripts/track_progress/scripts/build_with_nuitka.py b/scripts/track_progress/scripts/build_with_nuitka.py new file mode 100644 index 0000000..f920509 --- /dev/null +++ b/scripts/track_progress/scripts/build_with_nuitka.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +""" +Build the progress tracker with Nuitka. +""" +import os +import sys +import shutil +import subprocess +import argparse + + +def main(): + """Build the progress tracker with Nuitka.""" + parser = argparse.ArgumentParser( + description="Build the progress tracker with Nuitka" + ) + parser.add_argument( + "--output-dir", default="dist", help="Output directory for the build" + ) + parser.add_argument( + "--verbose", "-v", action="store_true", help="Enable verbose output" + ) + parser.add_argument( + "--move-exe", + action="store_true", + help="Move executable to parent directory after build", + ) + args = parser.parse_args() + + verbose = args.verbose + output_dir = args.output_dir + move_exe = args.move_exe + + # Get the directory of this script + script_dir = os.path.dirname(os.path.abspath(__file__)) + parent_dir = os.path.dirname(script_dir) # scripts/track_progress + + # Check if Nuitka is installed + try: + subprocess.check_call( + [sys.executable, "-m", "pip", "show", "nuitka"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + except subprocess.CalledProcessError: + print("Nuitka is not installed. Installing...") + try: + subprocess.check_call( + [sys.executable, "-m", "pip", "install", "nuitka"] + ) + except subprocess.CalledProcessError: + print( + "Failed to install Nuitka. Please install it manually with 'pip install nuitka'." + ) + return 1 + + # Create output directory + dist_dir = os.path.join(parent_dir, output_dir) + os.makedirs(dist_dir, exist_ok=True) + + # Path to the main script + main_script = os.path.join(parent_dir, "track_progress.py") + + if not os.path.exists(main_script): + print(f"Error: Main script not found at {main_script}") + return 1 + + # Build command + cmd = [ + sys.executable, + "-m", + "nuitka", + "--standalone", + "--onefile", + "--lto=auto", + "--jobs=4", + "--include-package=track_progress", + "--include-data-dir=" + + os.path.join(parent_dir, "templates") + + "=templates", + "--output-dir=" + dist_dir, + main_script, + ] + + # Try to enable UPX if available + try: + subprocess.check_call( + ["upx", "--version"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + cmd.append("--enable-plugin=upx") + except (subprocess.CalledProcessError, FileNotFoundError): + if verbose: + print("UPX not found, continuing without compression") + + if verbose: + print(f"Building with command: {' '.join(cmd)}") + else: + cmd.append("--quiet") + + # Run Nuitka + try: + subprocess.check_call(cmd) + print(f"Build successful! Executable is in {dist_dir}") + + # Move the executable to the parent directory if requested + if move_exe: + exe_ext = ".exe" if sys.platform == "win32" else "" + exe_name = f"track_progress{exe_ext}" + src_exe = os.path.join(dist_dir, exe_name) + dst_exe = os.path.join(parent_dir, exe_name) + + if os.path.exists(src_exe): + # Remove existing executable if it exists + if os.path.exists(dst_exe): + os.remove(dst_exe) + + # Move the executable + shutil.move(src_exe, dst_exe) + if verbose: + print(f"Moved executable from {src_exe} to {dst_exe}") + else: + print(f"Warning: Built executable not found at {src_exe}") + + return 0 + except subprocess.CalledProcessError as e: + print(f"Build failed: {e}") + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/track_progress/scripts/create_launcher_scripts.py b/scripts/track_progress/scripts/create_launcher_scripts.py new file mode 100644 index 0000000..89c1e47 --- /dev/null +++ b/scripts/track_progress/scripts/create_launcher_scripts.py @@ -0,0 +1,217 @@ +""" +Create launcher scripts for the progress tracker. +""" + +import os +import sys +from typing import List, Tuple + + +def create_python_launcher( + output_path: str, dest_dir: str, project_name: str +) -> bool: + """Create a Python launcher script.""" + content = f"""#!/usr/bin/env python3 +\"\"\" +Launcher for the progress tracker. +Tries to use the executable version first, then falls back to the Python version. +\"\"\" +import os +import sys +import subprocess + +def main(): + \"\"\"Main function to run the tracker.\"\"\" + # Get the directory of this script + script_dir = os.path.dirname(os.path.abspath(__file__)) + tracker_dir = os.path.dirname(script_dir) + + # Paths to executable and Python script + exe_ext = ".exe" if sys.platform == "win32" else "" + exe_path = os.path.join(tracker_dir, f"track_progress{{exe_ext}}") + py_path = os.path.join(tracker_dir, "track_progress.py") + + # Command-line arguments to pass through + args = sys.argv[1:] + + # Try executable first + if os.path.exists(exe_path) and (sys.platform == "win32" or os.access(exe_path, os.X_OK)): + print("Running Progress Tracker (executable version)...") + try: + return subprocess.call([exe_path] + args) + except Exception as e: + print(f"Error running executable: {{e}}") + print("Falling back to Python version...") + else: + print("Executable not found, trying Python version...") + + # Fall back to Python script + if os.path.exists(py_path): + print("Running Progress Tracker (Python version)...") + try: + # Add the tracker directory to the Python path + sys.path.insert(0, tracker_dir) + + # Change to the tracker directory + os.chdir(tracker_dir) + + # Run the Python script + return subprocess.call([sys.executable, py_path] + args) + except Exception as e: + print(f"Error running Python script: {{e}}") + return 1 + else: + print("Error: Neither executable nor Python script found at:") + print(f"- {{exe_path}}") + print(f"- {{py_path}}") + return 1 + +if __name__ == "__main__": + exit_code = main() + if exit_code == 0: + print("Progress tracking completed successfully!") + else: + print(f"Error running progress tracker! (Exit code: {{exit_code}})") + + # On Windows, pause to see the output + if sys.platform == "win32": + input("Press Enter to continue...") + + sys.exit(exit_code) +""" + + try: + with open(output_path, "w", encoding="utf-8", newline="\n") as f: + f.write(content) + return True + except Exception as e: + print(f"Error creating Python launcher: {e}") + return False + + +def create_shell_launcher(output_path: str) -> bool: + """Create a shell launcher script.""" + content = """#!/bin/bash + +# Get the directory of this script +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TRACKER_DIR="$(dirname "$SCRIPT_DIR")" +EXE_PATH="$TRACKER_DIR/track_progress" +PY_PATH="$TRACKER_DIR/track_progress.py" + +# Check if executable exists and is executable +if [ -x "$EXE_PATH" ]; then + echo "Running Progress Tracker (executable version)..." + "$EXE_PATH" "$@" +elif [ -f "$PY_PATH" ]; then + echo "Executable not found, trying Python version..." + echo "Running Progress Tracker (Python version)..." + python "$PY_PATH" "$@" +else + echo "Error: Neither executable nor Python script found at:" + echo "- $EXE_PATH" + echo "- $PY_PATH" + exit 1 +fi + +EXIT_CODE=$? +if [ $EXIT_CODE -eq 0 ]; then + echo "Progress tracking completed successfully!" +else + echo "Error running progress tracker! (Exit code: $EXIT_CODE)" +fi +""" + + try: + with open(output_path, "w", encoding="utf-8", newline="\n") as f: + f.write(content) + + # Make the script executable on Unix-like systems + if sys.platform != "win32": + os.chmod(output_path, 0o755) + + return True + except Exception as e: + print(f"Error creating shell launcher: {e}") + return False + + +def create_batch_launcher(output_path: str) -> bool: + """Create a batch launcher script.""" + content = """@echo off +setlocal enabledelayedexpansion + +REM Get the directory of this script +set "SCRIPT_DIR=%~dp0" +set "TRACKER_DIR=%SCRIPT_DIR%.." +set "EXE_PATH=%TRACKER_DIR%\\track_progress.exe" +set "PY_PATH=%TRACKER_DIR%\\track_progress.py" + +REM Check if executable exists +if exist "%EXE_PATH%" ( + echo Running Progress Tracker (executable version)... + "%EXE_PATH%" %* +) else ( + echo Executable not found, trying Python version... + if exist "%PY_PATH%" ( + echo Running Progress Tracker (Python version)... + python "%PY_PATH%" %* + ) else ( + echo Error: Neither executable nor Python script found at: + echo - %EXE_PATH% + echo - %PY_PATH% + exit /b 1 + ) +) + +if %ERRORLEVEL% EQU 0 ( + echo Progress tracking completed successfully! +) else ( + echo Error running progress tracker! (Exit code: %ERRORLEVEL%) +) +pause +""" + + try: + with open(output_path, "w", encoding="utf-8", newline="\n") as f: + f.write(content) + return True + except Exception as e: + print(f"Error creating batch launcher: {e}") + return False + + +def ensure_launcher_scripts( + script_dir: str, + dest_dir: str, + project_name: str, + verbose: bool = False, + force: bool = False, +) -> List[str]: + """Ensure all launcher scripts exist in the script directory.""" + created_scripts = [] + + # Define the scripts to check/create + scripts_to_ensure = [ + ("run_progress_tracker.py", create_python_launcher), + ("run_progress_tracker.sh", create_shell_launcher), + ("run_progress_tracker.bat", create_batch_launcher), + ] + + for script_name, create_func in scripts_to_ensure: + script_path = os.path.join(script_dir, script_name) + + if not os.path.exists(script_path) or force: + if verbose: + print(f"Creating launcher script: {script_path}") + + if create_func(script_path, dest_dir, project_name): + created_scripts.append(script_path) + if verbose: + print(f"Created {script_path}") + else: + print(f"Failed to create {script_path}") + elif verbose: + print(f"Launcher script already exists: {script_path}") + + return created_scripts diff --git a/scripts/track_progress/scripts/run_progress_tracker.bat b/scripts/track_progress/scripts/run_progress_tracker.bat new file mode 100644 index 0000000..6bfcef8 --- /dev/null +++ b/scripts/track_progress/scripts/run_progress_tracker.bat @@ -0,0 +1,32 @@ +@echo off +setlocal enabledelayedexpansion + +REM Get the directory of this script +set "SCRIPT_DIR=%~dp0" +set "TRACKER_DIR=%SCRIPT_DIR%.." +set "EXE_PATH=%TRACKER_DIR%\track_progress.exe" +set "PY_PATH=%TRACKER_DIR%\track_progress.py" + +REM Check if executable exists +if exist "%EXE_PATH%" ( + echo Running Progress Tracker (executable version)... + "%EXE_PATH%" %* +) else ( + echo Executable not found, trying Python version... + if exist "%PY_PATH%" ( + echo Running Progress Tracker (Python version)... + python "%PY_PATH%" %* + ) else ( + echo Error: Neither executable nor Python script found at: + echo - %EXE_PATH% + echo - %PY_PATH% + exit /b 1 + ) +) + +if %ERRORLEVEL% EQU 0 ( + echo Progress tracking completed successfully! +) else ( + echo Error running progress tracker! (Exit code: %ERRORLEVEL%) +) +pause \ No newline at end of file diff --git a/scripts/track_progress/scripts/run_progress_tracker.py b/scripts/track_progress/scripts/run_progress_tracker.py new file mode 100644 index 0000000..b96f27e --- /dev/null +++ b/scripts/track_progress/scripts/run_progress_tracker.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +""" +Launcher for the progress tracker. +Tries to use the executable version first, then falls back to the Python version. +""" +import os +import sys +import subprocess + + +def main(): + """Main function to run the tracker.""" + # Get the directory of this script + script_dir = os.path.dirname(os.path.abspath(__file__)) + tracker_dir = os.path.dirname(script_dir) + + # Paths to executable and Python script + exe_ext = ".exe" if sys.platform == "win32" else "" + exe_path = os.path.join(tracker_dir, f"track_progress{exe_ext}") + py_path = os.path.join(tracker_dir, "track_progress.py") + + # Command-line arguments to pass through + args = sys.argv[1:] + + # Try executable first + if os.path.exists(exe_path) and ( + sys.platform == "win32" or os.access(exe_path, os.X_OK) + ): + print("Running Progress Tracker (executable version)...") + try: + return subprocess.call([exe_path] + args) + except Exception as e: + print(f"Error running executable: {e}") + print("Falling back to Python version...") + else: + print("Executable not found, trying Python version...") + + # Fall back to Python script + if os.path.exists(py_path): + print("Running Progress Tracker (Python version)...") + try: + # Add the tracker directory to the Python path + sys.path.insert(0, tracker_dir) + + # Change to the tracker directory + os.chdir(tracker_dir) + + # Run the Python script + return subprocess.call([sys.executable, py_path] + args) + except Exception as e: + print(f"Error running Python script: {e}") + return 1 + else: + print("Error: Neither executable nor Python script found at:") + print(f"- {exe_path}") + print(f"- {py_path}") + return 1 + + +if __name__ == "__main__": + exit_code = main() + if exit_code == 0: + print("Progress tracking completed successfully!") + else: + print(f"Error running progress tracker! (Exit code: {exit_code})") + + # On Windows, pause to see the output + if sys.platform == "win32": + input("Press Enter to continue...") + + sys.exit(exit_code) diff --git a/scripts/track_progress/scripts/run_progress_tracker.sh b/scripts/track_progress/scripts/run_progress_tracker.sh new file mode 100644 index 0000000..5dc513b --- /dev/null +++ b/scripts/track_progress/scripts/run_progress_tracker.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +# Get the directory of this script +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +TRACKER_DIR="$(dirname "$SCRIPT_DIR")" +EXE_PATH="$TRACKER_DIR/track_progress" +PY_PATH="$TRACKER_DIR/track_progress.py" + +# Check if executable exists and is executable +if [ -x "$EXE_PATH" ]; then + echo "Running Progress Tracker (executable version)..." + "$EXE_PATH" "$@" +elif [ -f "$PY_PATH" ]; then + echo "Executable not found, trying Python version..." + echo "Running Progress Tracker (Python version)..." + python "$PY_PATH" "$@" +else + echo "Error: Neither executable nor Python script found at:" + echo "- $EXE_PATH" + echo "- $PY_PATH" + exit 1 +fi + +EXIT_CODE=$? +if [ $EXIT_CODE -eq 0 ]; then + echo "Progress tracking completed successfully!" +else + echo "Error running progress tracker! (Exit code: $EXIT_CODE)" +fi diff --git a/scripts/track_progress/scripts/setup_install.py b/scripts/track_progress/scripts/setup_install.py new file mode 100644 index 0000000..edbd619 --- /dev/null +++ b/scripts/track_progress/scripts/setup_install.py @@ -0,0 +1,285 @@ +""" Run this to install to your projects + | "--dest folder" |to set destination dir + | "--project name" |to set project name + | "--verbose" or "-v"|to enable verbose output + | "--force" or "-f"|to force overwrite existing files +""" + +import os +import sys +import shutil +import argparse +import subprocess +from pathlib import Path + +# Try to import our script creation module +try: + from create_launcher_scripts import ensure_launcher_scripts +except ImportError: + # If we can't import it, we'll define a simple version here + def ensure_launcher_scripts( + script_dir, dest_dir, project_name, verbose=False, force=False + ): + """Fallback function if the module can't be imported.""" + print( + "Warning: create_launcher_scripts module not found. Using fallback." + ) + return [] + + +def main(): + """Main installation function.""" + parser = argparse.ArgumentParser( + description="Install the progress tracking system." + ) + parser.add_argument( + "--dest", default="scripts", help="Destination directory" + ) + parser.add_argument("--project", default="Project", help="Project name") + parser.add_argument( + "--verbose", "-v", action="store_true", help="Enable verbose output" + ) + parser.add_argument( + "--force", + "-f", + action="store_true", + help="Force overwrite existing files", + ) + args = parser.parse_args() + + verbose = args.verbose + force = args.force + + if verbose: + print( + f"Installing progress tracking system to {args.dest} for project {args.project}" + ) + + # Create destination directories + os.makedirs( + os.path.join(args.dest, "track_progress", "templates"), exist_ok=True + ) + os.makedirs("docs/status", exist_ok=True) + + # Get the directory of this script + script_dir = os.path.dirname(os.path.abspath(__file__)) + parent_dir = os.path.dirname(script_dir) # scripts/track_progress + + # Look for compiled executable + exe_ext = ".exe" if sys.platform == "win32" else "" + compiled_exe = os.path.join(parent_dir, f"track_progress{exe_ext}") + dist_exe = os.path.join(parent_dir, "dist", f"track_progress{exe_ext}") + + # Check if we have the executable in either location + if not os.path.exists(compiled_exe) and not os.path.exists(dist_exe): + # Try to build it + if verbose: + print(f"Compiled executable not found, trying to build it...") + + build_script = os.path.join(script_dir, "build_with_nuitka.py") + if os.path.exists(build_script): + build_cmd = [sys.executable, build_script] + if verbose: + build_cmd.append("--verbose") + + try: + subprocess.check_call(build_cmd) + # The executable should now be in the dist directory + + # Move the executable from dist to parent directory + if os.path.exists(dist_exe): + if os.path.exists(compiled_exe): + os.remove(compiled_exe) + shutil.move(dist_exe, compiled_exe) + if verbose: + print( + f"Moved executable from {dist_exe} to {compiled_exe}" + ) + except subprocess.CalledProcessError: + print( + "Failed to build the executable. Falling back to Python files." + ) + else: + print("Build script not found. Falling back to Python files.") + + # Check if we have the executable now + have_executable = os.path.exists(compiled_exe) + + if have_executable: + # Copy the compiled executable + dst_exe = os.path.join( + args.dest, "track_progress", f"track_progress{exe_ext}" + ) + if not os.path.exists(dst_exe) or force: + shutil.copy2(compiled_exe, dst_exe) + if verbose: + print( + f"Copied compiled executable from {compiled_exe} to {dst_exe}" + ) + elif verbose: + print(f"Skipping existing executable: {dst_exe}") + else: + # Copy individual Python files + python_files = [ + "track_progress.py", + "config.py", + "git_analyzer.py", + "code_stats.py", + "feature_markdown.py", + "template_engine.py", + "changelog_generator.py", + "roadmap_generator.py", + "__init__.py", # Add this to make it a proper package + "README.md", + ] + + for file in python_files: + src = os.path.join(parent_dir, file) + dst = os.path.join(args.dest, "track_progress", file) + + # Skip existing files unless force is specified + if os.path.exists(dst) and not force: + if verbose: + print(f"Skipping existing file: {dst}") + continue + + if os.path.exists(src): + shutil.copy2(src, dst) + if verbose: + print(f"Copied {file} to {dst}") + elif file == "__init__.py": + # Create empty __init__.py if it doesn't exist + with open(dst, "w", encoding="utf-8") as f: + f.write( + "# This file makes the directory a Python package\n" + ) + if verbose: + print(f"Created {dst}") + + # Copy templates (always copy templates, they should remain exposed) + template_files = [ + "status_template.md", + "feature_template.md", + "changelog_template.md", + ] + + for file in template_files: + src = os.path.join(parent_dir, "templates", file) + dst = os.path.join(args.dest, "track_progress", "templates", file) + + # Skip existing files unless force is specified + if os.path.exists(dst) and not force: + if verbose: + print(f"Skipping existing template: {dst}") + continue + + if os.path.exists(src): + shutil.copy2(src, dst) + if verbose: + print(f"Copied template {file} to {dst}") + + # Copy and customize configuration + config_src = os.path.join(parent_dir, "progress_config.json") + config_dst = os.path.join( + args.dest, "track_progress", "progress_config.json" + ) + + # Skip existing config unless force is specified + if not os.path.exists(config_dst) or force: + if os.path.exists(config_src): + # Read the config file + with open(config_src, "r", encoding="utf-8") as f: + config_content = f.read() + + # Replace project name + config_content = config_content.replace( + '"project_name": "AdvChkSys"', + f'"project_name": "{args.project}"', + ) + + # Ensure paths are relative to the project root + config_content = config_content.replace( + '"features_file": "docs/features.md"', + '"features_file": "docs/features.md"', + ) + config_content = config_content.replace( + '"output_dir": "docs/status"', + '"output_dir": "docs/status"', + ) + config_content = config_content.replace( + '"changelog_file": "CHANGELOG.md"', + '"changelog_file": "CHANGELOG.md"', + ) + + # Write the customized config + with open(config_dst, "w", encoding="utf-8") as f: + f.write(config_content) + + if verbose: + print(f"Created customized configuration at {config_dst}") + elif verbose: + print(f"Skipping existing configuration: {config_dst}") + + # Ensure launcher scripts exist in the scripts directory + created_scripts = ensure_launcher_scripts( + script_dir, args.dest, args.project, verbose, force + ) + if created_scripts and verbose: + print( + f"Created {len(created_scripts)} launcher scripts in {script_dir}" + ) + + # Create an empty CHANGELOG.md if it doesn't exist + if not os.path.exists("CHANGELOG.md"): + with open("CHANGELOG.md", "w", encoding="utf-8") as f: + f.write("# Changelog\n\n") + if verbose: + print("Created empty CHANGELOG.md") + + # Copy the scripts directory to the destination if it doesn't exist + scripts_dst = os.path.join(args.dest, "track_progress", "scripts") + if not os.path.exists(scripts_dst) or force: + os.makedirs(scripts_dst, exist_ok=True) + + # Copy script files + script_files = [ + "build_with_nuitka.py", + "create_launcher_scripts.py", + "setup_install.py", + ] + + for file in script_files: + src = os.path.join(script_dir, file) + dst = os.path.join(scripts_dst, file) + + if os.path.exists(src) and (not os.path.exists(dst) or force): + shutil.copy2(src, dst) + if verbose: + print(f"Copied script {file} to {dst}") + + print("\nInstallation complete!") + if have_executable: + print( + f"Compiled tracker installed to: {os.path.join(args.dest, 'track_progress')}" + ) + else: + print( + f"Python tracker installed to: {os.path.join(args.dest, 'track_progress')}" + ) + + # Check which launcher scripts exist + py_launcher = os.path.join(script_dir, "run_progress_tracker.py") + sh_launcher = os.path.join(script_dir, "run_progress_tracker.sh") + bat_launcher = os.path.join(script_dir, "run_progress_tracker.bat") + + print("\nTo run the tracker, use one of these commands:") + if os.path.exists(py_launcher): + print(f"- Python: python {py_launcher}") + if os.path.exists(sh_launcher) and sys.platform != "win32": + print(f"- Shell: {sh_launcher}") + if os.path.exists(bat_launcher) and sys.platform == "win32": + print(f"- Batch: {bat_launcher}") + + +if __name__ == "__main__": + main() diff --git a/scripts/track_progress/template_engine.py b/scripts/track_progress/template_engine.py new file mode 100644 index 0000000..b83849a --- /dev/null +++ b/scripts/track_progress/template_engine.py @@ -0,0 +1,239 @@ +""" +Template engine for generating Markdown documentation from templates. +""" + +import os +import re +import sys +from typing import Dict, Any, List, Optional + + +class TemplateEngine: + """Simple template engine for generating Markdown files.""" + + def __init__( + self, templates_dir: str = "scripts/track_progress/templates" + ): + """Initialize the template engine with the templates directory.""" + self.templates_dir = templates_dir + + def _load_template(self, template_name: str) -> str: + """Load a template file.""" + # Try multiple possible template locations + possible_paths = [ + # Original path + os.path.join(self.templates_dir, template_name), + # Path relative to current working directory + os.path.join("templates", template_name), + # Path relative to executable directory + os.path.join( + os.path.dirname(sys.executable), "templates", template_name + ), + # Path relative to script directory + os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "templates", + template_name, + ), + # Path with 'scripts/track_progress' removed (for compiled version) + template_name.replace("scripts/track_progress/", ""), + # Just the filename + template_name, + ] + + # Try each path + for template_path in possible_paths: + if os.path.exists(template_path): + with open(template_path, "r", encoding="utf-8") as f: + return f.read() + + # If we get here, none of the paths worked + error_message = f"Template not found: {template_name}\nTried paths: {possible_paths}" + raise FileNotFoundError(error_message) + + def _replace_variables( + self, template: str, context: Dict[str, Any] + ) -> str: + """Replace {{ variable }} with values from context.""" + + def replace_var(match): + var_name = match.group(1).strip() + + # Handle nested variables with dot notation + if "." in var_name: + parts = var_name.split(".") + value = context + for part in parts: + if isinstance(value, dict) and part in value: + value = value[part] + else: + return match.group(0) # Return original if not found + return str(value) if value is not None else "" + + # Handle simple variables + if var_name in context: + return ( + str(context[var_name]) + if context[var_name] is not None + else "" + ) + + return match.group(0) # Return original if not found + + # Replace {{ variable }} + pattern = r"{{(.*?)}}" + return re.sub(pattern, replace_var, template) + + def _process_conditionals( + self, template: str, context: Dict[str, Any] + ) -> str: + """Process {% if condition %} ... {% endif %} blocks.""" + + def replace_conditional(match): + condition_var = match.group(1).strip() + content = match.group(2) + + # Handle nested conditions with dot notation + if "." in condition_var: + parts = condition_var.split(".") + value = context + for part in parts: + if isinstance(value, dict) and part in value: + value = value[part] + else: + return "" # Condition not met + + if value: + return content + return "" + + # Handle simple conditions + if condition_var in context and context[condition_var]: + return content + return "" + + # Process {% if condition %} ... {% endif %} + pattern = r"{%\s*if\s+(.*?)\s*%}(.*?){%\s*endif\s*%}" + return re.sub(pattern, replace_conditional, template, flags=re.DOTALL) + + def _process_loops(self, template: str, context: Dict[str, Any]) -> str: + """Process {% for item_var, item in items.items() %} ... {% endfor %} blocks.""" + + # First, process dictionary iteration with items() + def replace_dict_loop(match): + item_key_var = match.group(1).strip() + item_val_var = match.group(2).strip() + collection_var = match.group(3).strip() + content_template = match.group(4) + + # Handle nested collections with dot notation + if "." in collection_var: + parts = collection_var.split(".") + collection = context + for part in parts: + if isinstance(collection, dict) and part in collection: + collection = collection[part] + else: + return "" # Collection not found + else: + collection = context.get(collection_var, {}) + + if not isinstance(collection, dict): + return "" # Not a dictionary + + result = [] + for key, value in collection.items(): + # Create a new context for each iteration + loop_context = context.copy() + loop_context[item_key_var] = key + loop_context[item_val_var] = value + + # Process the content template with the loop context + item_content = self._replace_variables( + content_template, loop_context + ) + item_content = self._process_conditionals( + item_content, loop_context + ) + result.append(item_content) + + return "".join(result) + + # Process {% for key, value in dict.items() %} ... {% endfor %} + dict_pattern = r"{%\s*for\s+(.*?),\s*(.*?)\s+in\s+(.*?)\.items\(\)\s*%}(.*?){%\s*endfor\s*%}" + template = re.sub( + dict_pattern, replace_dict_loop, template, flags=re.DOTALL + ) + + # Then, process regular list iteration + def replace_list_loop(match): + item_var = match.group(1).strip() + collection_var = match.group(2).strip() + content_template = match.group(3) + + # Handle nested collections with dot notation + if "." in collection_var: + parts = collection_var.split(".") + collection = context + for part in parts: + if isinstance(collection, dict) and part in collection: + collection = collection[part] + else: + return "" # Collection not found + else: + collection = context.get(collection_var, []) + + if not isinstance(collection, (list, dict)): + return "" # Not a collection + + result = [] + if isinstance(collection, dict): + # If it's a dict, iterate over keys + for key in collection: + # Create a new context for each iteration + loop_context = context.copy() + loop_context[item_var] = key + + # Process the content template with the loop context + item_content = self._replace_variables( + content_template, loop_context + ) + item_content = self._process_conditionals( + item_content, loop_context + ) + result.append(item_content) + else: # list + for item in collection: + # Create a new context for each iteration + loop_context = context.copy() + loop_context[item_var] = item + + # Process the content template with the loop context + item_content = self._replace_variables( + content_template, loop_context + ) + item_content = self._process_conditionals( + item_content, loop_context + ) + result.append(item_content) + + return "".join(result) + + # Process {% for item in items %} ... {% endfor %} + list_pattern = ( + r"{%\s*for\s+(.*?)\s+in\s+(.*?)\s*%}(.*?){%\s*endfor\s*%}" + ) + return re.sub( + list_pattern, replace_list_loop, template, flags=re.DOTALL + ) + + def render(self, template_name: str, context: Dict[str, Any]) -> str: + """Render a template with the given context.""" + template = self._load_template(template_name) + + # Process template directives in order + template = self._process_loops(template, context) + template = self._process_conditionals(template, context) + template = self._replace_variables(template, context) + + return template diff --git a/scripts/track_progress/templates/changelog_template.md b/scripts/track_progress/templates/changelog_template.md new file mode 100644 index 0000000..ce46bc3 --- /dev/null +++ b/scripts/track_progress/templates/changelog_template.md @@ -0,0 +1,7 @@ +# {{ project_name }} Changelog + +Last updated: {{ current_date }} + +{% for entry in changelog_entries %} +{{ entry }} +{% endfor %} \ No newline at end of file diff --git a/scripts/track_progress/templates/feature_template.md b/scripts/track_progress/templates/feature_template.md new file mode 100644 index 0000000..31ada23 --- /dev/null +++ b/scripts/track_progress/templates/feature_template.md @@ -0,0 +1,92 @@ +# {{ title }} + +## Overview + +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 + +{% for feature_key, feature in features.items() %} +### {{ feature_key }} + +**Status:** {{ feature.status_emoji }} {{ feature.status }} +**Description:** {{ feature.description }} +**Last Update:** {{ feature.date }} +{% if feature.author %}**Owner:** {{ feature.author }}{% endif %} +{% if feature.details %}**Details:** {{ feature.details }}{% endif %} + +{% endfor %} + +## Feature Categories + +### Completed Features + +{% for feature_key, feature in features.items() %} +{% if feature.status == 'completed' %} +- **{{ feature_key }}**: {{ feature.description }} +{% endif %} +{% endfor %} + +### In Progress Features + +{% for feature_key, feature in features.items() %} +{% if feature.status == 'in_progress' %} +- **{{ feature_key }}**: {{ feature.description }} +{% endif %} +{% endfor %} + +### Planned Features + +{% for feature_key, feature in features.items() %} +{% if feature.status == 'planned' %} +- **{{ feature_key }}**: {{ feature.description }} +{% endif %} +{% endfor %} + + \ No newline at end of file diff --git a/scripts/track_progress/templates/status_template.md b/scripts/track_progress/templates/status_template.md new file mode 100644 index 0000000..5d8f1d3 --- /dev/null +++ b/scripts/track_progress/templates/status_template.md @@ -0,0 +1,57 @@ +# {{ project_name }} Development Status + +Last updated: {{ current_date }} + +## Code Statistics + +{% if stats %} +Total lines of code: **{{ stats.total_lines }}** + +Number of source files: **{{ stats.file_count }}** + +### Files by Language +{% for lang, count in stats.languages.items() %} +- **{{ lang }}**: {{ count }} files +{% endfor %} + +### Top Files by Line Count + +| File | Lines | Language | Path | +|------|------:|----------|------| +{% for file in stats.top_files %} +| {{ file.name }} | {{ file.lines }} | {{ file.language }} | {{ file.path }} | +{% endfor %} +{% endif %} + +## Feature Status + +| Feature | Status | Description | Last Update | Owner | +|---------|--------|-------------|-------------|-------| +{% for feature_key, feature in features.items() %} +| {{ feature.display_name }} | {{ feature.status_emoji }} {{ feature.status }} | {{ feature.description }} | {{ feature.date }} | {{ feature.author }} | +{% endfor %} + +## Recent Updates + +{% for entry in changelog_entries %} +{{ entry }} +{% endfor %} + +{% if known_issues %} +## Known Issues + +{% for issue in known_issues %} +- {{ issue.description }} {% if issue.status %}({{ issue.status }}){% endif %} +{% endfor %} +{% endif %} + +{% if roadmap %} +## Roadmap + +{% for milestone in roadmap %} +### {{ milestone.name }} ({{ milestone.target_date }}) +{% for item in milestone.items %} +- {{ item.description }} {% if item.status %}[{{ item.status }}]{% endif %} +{% endfor %} +{% endfor %} +{% endif %} \ No newline at end of file diff --git a/scripts/track_progress/track_progress.exe b/scripts/track_progress/track_progress.exe new file mode 100644 index 0000000..0d8ed96 Binary files /dev/null and b/scripts/track_progress/track_progress.exe differ diff --git a/scripts/track_progress/track_progress.py b/scripts/track_progress/track_progress.py new file mode 100644 index 0000000..55deeea --- /dev/null +++ b/scripts/track_progress/track_progress.py @@ -0,0 +1,284 @@ +#!/usr/bin/env python3 +""" +Project progress tracker that generates status documentation from Git history. +""" +import os +import sys +import argparse +from datetime import datetime +from typing import Dict, Any, List, Optional + +# Add the current directory to the Python path +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +# Import our modules +from config import Config +from code_stats import analyze_code_stats +from git_analyzer import GitAnalyzer +from feature_markdown import ( + enrich_features, + emoji_status, + extract_features, + generate_features_md, +) +from template_engine import TemplateEngine +from changelog_generator import update_changelog_file +from roadmap_generator import generate_roadmap + + +def load_features(features_file: str) -> Dict[str, Dict[str, Any]]: + """Load features from a Markdown file.""" + # Use the improved extraction function + return extract_features(features_file) + + +def update_features( + features: Dict[str, Dict[str, Any]], + feature_updates: Dict[str, Dict[str, Any]], + new_features: Dict[str, Dict[str, Any]], +) -> Dict[str, Dict[str, Any]]: + """Update features with new information from Git history.""" + updated_features = features.copy() + + # Update existing features + for feature_key, update in feature_updates.items(): + if feature_key in updated_features: + updated_features[feature_key].update(update) + + # Add new features + for feature_key, feature_data in new_features.items(): + if feature_key not in updated_features: + updated_features[feature_key] = feature_data + + return updated_features + + +def format_changelog_entries(git_data: Dict[str, Any]) -> List[str]: + """Format changelog entries from Git data.""" + entries = [] + + # Add changelog entries + for entry in git_data.get("changelog_entries", []): + entries.append( + f"- {entry['date']}: {entry['message']} ({entry['commit']})" + ) + + # Add fix entries + for fix in git_data.get("fixes", []): + entries.append( + f"- {fix['date']}: Fixed: {fix['message']} ({fix['commit']})" + ) + + # Add untagged commits + for commit in git_data.get("untagged_commits", []): + entries.append( + f"- {commit['date']}: {commit['message']} ({commit['commit']})" + ) + + return entries + + +def extract_known_issues(git_data: Dict[str, Any]) -> List[Dict[str, Any]]: + """Extract known issues from Git data.""" + issues = [] + + # Look for issues in the Git data + for issue in git_data.get("issues", []): + issues.append( + {"description": issue.get("message", ""), "status": "open"} + ) + + return issues + + +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 main(): + """Main function to run the progress tracker.""" + parser = argparse.ArgumentParser( + description="Track project progress and generate status documentation." + ) + parser.add_argument( + "--config", + default="scripts/track_progress/progress_config.json", + help="Path to configuration file", + ) + parser.add_argument("--project", help="Project name (overrides config)") + parser.add_argument( + "--output-dir", help="Output directory (overrides config)" + ) + parser.add_argument("--repo", default=".", help="Repository path") + parser.add_argument( + "--verbose", "-v", action="store_true", help="Enable verbose output" + ) + args = parser.parse_args() + + verbose = args.verbose + + # Load configuration + config = Config(args.config) + + # Override config with command-line arguments + if args.project: + config.set("project_name", args.project) + if args.output_dir: + config.set("output_dir", args.output_dir) + + # Ensure output directory exists + output_dir = config.get("output_dir") + os.makedirs(output_dir, exist_ok=True) + + # Initialize components + git_analyzer = GitAnalyzer(args.repo) + template_engine = TemplateEngine() + + # Get repository information + repo_info = git_analyzer.get_repo_info() + + # Analyze Git history + git_data = git_analyzer.analyze_commits( + limit=config.get("git_log_limit"), + untagged_limit=config.get("untagged_commit_limit"), + ) + + # Analyze code statistics + code_stats = analyze_code_stats( + root_dir=args.repo, + exclude_dirs=config.get("exclude_dirs"), + source_extensions=config.get("source_extensions"), + top_files_limit=config.get("top_files_limit"), + ) + + # Load existing features + features_file = config.get("features_file") + if not os.path.isabs(features_file): + # If it's not an absolute path, make it relative to the repo root, not the output dir + features_file = os.path.join(args.repo, features_file) + + if verbose: + print(f"Loading features from: {features_file}") + + features = load_features(features_file) + + if verbose: + print(f"Loaded {len(features)} features") + + # Update features with Git data + features = update_features( + features, + git_data.get("feature_updates", {}), + git_data.get("new_features", {}), + ) + + if verbose: + print(f"Updated features count: {len(features)}") + + # Enrich features with emojis and other display information + enriched_features = enrich_features(features) + + # Format changelog entries + changelog_entries = format_changelog_entries(git_data) + + # Extract known issues + known_issues = extract_known_issues(git_data) + + # Generate roadmap + roadmap = generate_roadmap( + git_data.get("milestones", {}), git_data.get("roadmap_items", {}) + ) + + # Prepare template context + context = { + "project_name": config.get("project_name"), + "current_date": datetime.now().strftime("%Y-%m-%d"), + "repo_info": repo_info, + "stats": code_stats, + "features": enriched_features, + "changelog_entries": changelog_entries, + "known_issues": known_issues, + "roadmap": roadmap, + } + + # Generate status document + status_template = config.get("templates", {}).get( + "status", "status_template.md" + ) + status_content = template_engine.render(status_template, context) + + status_file = os.path.join(args.repo, output_dir, config.get("status_doc")) + os.makedirs(os.path.dirname(status_file), exist_ok=True) + with open(status_file, "w", encoding="utf-8") as f: + f.write(status_content) + + # Generate features document using the improved function + features_content = generate_features_md( + features, config.get("project_name") + ) + + # Ensure directory exists for features file + os.makedirs(os.path.dirname(features_file), exist_ok=True) + + # Create backup of original features file + if os.path.exists(features_file): + backup_file = ( + f"{features_file}.bak.{datetime.now().strftime('%Y%m%d%H%M%S')}" + ) + with open(features_file, "r", encoding="utf-8") as src: + with open(backup_file, "w", encoding="utf-8") as dst: + dst.write(src.read()) + if verbose: + print(f"Created backup: {backup_file}") + + # Write the new features file + with open(features_file, "w", encoding="utf-8") as f: + f.write(features_content) + + # Update changelog + changelog_file = config.get( + "changelog_file", "CHANGELOG.md" + ) # Default to CHANGELOG.md + + if not os.path.isabs(changelog_file): + # If it's a relative path, make it relative to the repo root, not the output dir + changelog_file = os.path.join(args.repo, changelog_file) + + if verbose: + print(f"Updating changelog file: {changelog_file}") + + # Ensure the directory exists + changelog_dir = os.path.dirname(changelog_file) + if changelog_dir: # Only create directory if there is one + os.makedirs(changelog_dir, exist_ok=True) + + update_changelog_file( + git_data.get("changelog_entries", []), + changelog_file, + max_entries=config.get("git_log_limit"), + verbose=verbose, + ) + + print(f"Progress tracking updated:") + print(f"- Status document: {status_file}") + print(f"- Features document: {features_file}") + print(f"- Changelog: {changelog_file}") + + return 0 + + +if __name__ == "__main__": + sys.exit(main())