mirror of
https://github.com/tcsenpai/overseer.git
synced 2025-06-02 17:30:06 +00:00
first commit
This commit is contained in:
commit
49d337b04a
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
@ -0,0 +1,17 @@
|
||||
# Python-generated files
|
||||
__pycache__/
|
||||
*.py[oc]
|
||||
build/
|
||||
dist/
|
||||
wheels/
|
||||
*.egg-info
|
||||
|
||||
# Virtual environments
|
||||
.venv
|
||||
|
||||
# Node
|
||||
node_modules/
|
||||
|
||||
*.pdf
|
||||
*.xlsx
|
||||
*.xls
|
1
.python-version
Normal file
1
.python-version
Normal file
@ -0,0 +1 @@
|
||||
3.11
|
138
README.md
Normal file
138
README.md
Normal file
@ -0,0 +1,138 @@
|
||||
# Overseer
|
||||
|
||||
A command-line tool for scanning and managing code comments in your projects. It helps you track TODOs, FIXMEs, and other comment markers across your codebase.
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
- 🔍 Scan for comment markers (TODO, FIXME, etc.) in your codebase
|
||||
- 📁 Filter files by name (partial or exact match)
|
||||
- 🎯 Customizable comment markers to include/exclude
|
||||
- 📝 Show code context around comments
|
||||
- 📤 Export results to different formats (text, JSON, PDF)
|
||||
- 🚫 Respects .gitignore patterns
|
||||
- 🔒 Skip hidden files and directories
|
||||
|
||||
## Configuration and compatibility
|
||||
|
||||
### Supported languages
|
||||
|
||||
- TypeScript
|
||||
- JavaScript
|
||||
- Python
|
||||
- C/C++
|
||||
- Java
|
||||
- Ruby
|
||||
- PHP
|
||||
|
||||
To add support for a new language, you would simply add its patterns to the `COMMENT_PATTERNS` dictionary in `config.py`.
|
||||
|
||||
### Default markers
|
||||
|
||||
- TODO
|
||||
- FIXME
|
||||
- REVIEW
|
||||
- NOTE
|
||||
- !
|
||||
- ?
|
||||
|
||||
You can add your own markers by editing the `config.py` file.
|
||||
|
||||
## Installation
|
||||
|
||||
### Clone the repository
|
||||
|
||||
```bash
|
||||
git clone https://github.com/tcsenpai/overseer.git
|
||||
cd overseer
|
||||
```
|
||||
|
||||
### Install dependencies
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
or, using uv:
|
||||
|
||||
NOTE: Using uv, you can run the script directly without installing dependencies as `uv run python main.py` (see below for usage), as the `pyproject.toml` file is present. You can still install dependencies using the command below if you prefer.
|
||||
|
||||
```bash
|
||||
uv pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
python main.py --help
|
||||
```
|
||||
|
||||
or, using uv:
|
||||
|
||||
```bash
|
||||
uv run python main.py --help
|
||||
```
|
||||
|
||||
```bash
|
||||
|
||||
usage: main.py [-h] [--workspace WORKSPACE] [--skip SKIP [SKIP ...]] [--include-all] [--no-context] [--export {pdf,xlsx}] [--output OUTPUT]
|
||||
[--filename FILENAME] [--complete-match] [--case-sensitive]
|
||||
|
||||
Scan TypeScript project comments
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
--workspace WORKSPACE, -w WORKSPACE
|
||||
Path to the workspace directory
|
||||
--skip SKIP [SKIP ...], -s SKIP [SKIP ...]
|
||||
Markers to skip (e.g., --skip NOTE TODO)
|
||||
--include-all, -a Include all markers (override default skip)
|
||||
--no-context, -nc Don't show context lines around comments
|
||||
--export {pdf,xlsx}, -e {pdf,xlsx}
|
||||
Export format (pdf or xlsx)
|
||||
--output OUTPUT, -o OUTPUT
|
||||
Output file path for export
|
||||
|
||||
filename filtering:
|
||||
--filename FILENAME, -f FILENAME
|
||||
Filter files by filename (case insensitive by default)
|
||||
--complete-match, -c Match complete filename instead of partial (only with -f)
|
||||
--case-sensitive, -C Make filename filter case sensitive (only with -f)
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
```bash
|
||||
main.py # Scan all files with default settings
|
||||
main.py -w /path/to/project # Scan a specific workspace
|
||||
main.py -f test.py # Find comments in files containing 'test.py' (case insensitive)
|
||||
main.py -f test.py -c # Find comments in files named exactly 'test.py'
|
||||
main.py -f Test.py -C # Find comments with case-sensitive filename match
|
||||
main.py -f test.py -c -C # Find comments in files named exactly 'test.py' (case sensitive)
|
||||
main.py --skip TODO FIXME # Skip TODO and FIXME comments
|
||||
main.py -a # Include all comment types
|
||||
main.py -e pdf -o comments.pdf # Export comments to PDF
|
||||
```
|
||||
|
||||
## Command Line Options
|
||||
|
||||
| Option | Short | Description |
|
||||
| ------------------ | ----- | -------------------------------------------------- |
|
||||
| `--workspace` | `-w` | Specify workspace directory to scan |
|
||||
| `--filename` | `-f` | Filter files by name (case insensitive by default) |
|
||||
| `--complete-match` | `-c` | Match complete filename instead of partial |
|
||||
| `--case-sensitive` | `-C` | Make filename filter case sensitive |
|
||||
| `--skip` | | Skip specified comment markers |
|
||||
| `--include-all` | `-a` | Include all comment types |
|
||||
| `--no-context` | | Hide code context around comments |
|
||||
| `--export` | `-e` | Export format (text, json, pdf) |
|
||||
| `--output` | `-o` | Output file path |
|
||||
|
||||
## Configuration
|
||||
|
||||
The tool respects:
|
||||
|
||||
- `.gitignore` patterns in your project
|
||||
- Hidden files and directories (starting with .)
|
||||
- Project-specific file patterns (configured in `config.py`)
|
97
config.py
Normal file
97
config.py
Normal file
@ -0,0 +1,97 @@
|
||||
from typing import Dict
|
||||
from pathlib import Path
|
||||
|
||||
# Default workspace path (can be overridden by CLI argument)
|
||||
DEFAULT_WORKSPACE = Path.cwd() # Current working directory by default
|
||||
|
||||
# Supported file patterns
|
||||
FILE_PATTERNS = [
|
||||
"*.ts",
|
||||
"*.tsx",
|
||||
"*.js",
|
||||
"*.jsx",
|
||||
"*.py",
|
||||
"*.rs",
|
||||
"*.go",
|
||||
"*.java",
|
||||
"*.c",
|
||||
"*.h",
|
||||
"*.cpp",
|
||||
"*.hpp",
|
||||
"*.cs",
|
||||
"*.vb",
|
||||
"*.sql",
|
||||
"*.md",
|
||||
"*.txt",
|
||||
"*.yaml",
|
||||
"*.yml",
|
||||
#"*.json",
|
||||
"*.xml",
|
||||
"*.html",
|
||||
"*.css",
|
||||
"*.scss",
|
||||
"*.sass",
|
||||
"*.less",
|
||||
"*.styl",
|
||||
"*.stylus",
|
||||
"*.scss",
|
||||
"*.sass",
|
||||
"*.less",
|
||||
"*.styl",
|
||||
"*.stylus",
|
||||
"*.scss",
|
||||
"*.sass",
|
||||
"*.less",
|
||||
"*.styl",
|
||||
"*.stylus",
|
||||
]
|
||||
|
||||
# Comment markers and their descriptions
|
||||
COMMENT_MARKERS: Dict[str, str] = {
|
||||
"!": "Important",
|
||||
"TODO": "To Do",
|
||||
"?": "Question",
|
||||
"REVIEW": "Needs Review",
|
||||
"FIXME": "Fix Required",
|
||||
"NOTE": "Note",
|
||||
}
|
||||
|
||||
# Color scheme for different comment types
|
||||
COMMENT_COLORS: Dict[str, str] = {
|
||||
"!": "red",
|
||||
"TODO": "yellow",
|
||||
"?": "blue",
|
||||
"REVIEW": "magenta",
|
||||
"FIXME": "red",
|
||||
"NOTE": "green",
|
||||
}
|
||||
|
||||
# Additional exclusions
|
||||
DEFAULT_EXCLUDES = {"node_modules", "__pycache__", "build", "dist"}
|
||||
|
||||
# Default settings
|
||||
DEFAULT_SKIP_MARKERS = {"NOTE"} # Skip NOTE comments by default
|
||||
CONTEXT_LINES = 2 # Number of lines before and after to show
|
||||
|
||||
# Output formats
|
||||
EXPORT_FORMATS = ["pdf", "xlsx"] # Supported export formats
|
||||
|
||||
COMMENT_PATTERNS = {
|
||||
# Default pattern (for unknown extensions)
|
||||
"default": {"single": ["//"], "multiline": None},
|
||||
# Python
|
||||
"py": {"single": ["#"], "multiline": ['"""', '"""']}, # Also handles '''
|
||||
# JavaScript/TypeScript
|
||||
"js": {"single": ["//"], "multiline": ["/*", "*/"]},
|
||||
"ts": {"single": ["//"], "multiline": ["/*", "*/"]},
|
||||
# C/C++
|
||||
"c": {"single": ["//"], "multiline": ["/*", "*/"]},
|
||||
"cpp": {"single": ["//"], "multiline": ["/*", "*/"]},
|
||||
"h": {"single": ["//"], "multiline": ["/*", "*/"]},
|
||||
# Java
|
||||
"java": {"single": ["//"], "multiline": ["/*", "*/"]},
|
||||
# Ruby
|
||||
"rb": {"single": ["#"], "multiline": ["=begin", "=end"]},
|
||||
# PHP
|
||||
"php": {"single": ["//"], "multiline": ["/*", "*/"]},
|
||||
}
|
536
main.py
Normal file
536
main.py
Normal file
@ -0,0 +1,536 @@
|
||||
from pathlib import Path
|
||||
from typing import List, Dict, Set
|
||||
from rich.console import Console
|
||||
from rich.table import Table
|
||||
import os
|
||||
import config
|
||||
import argparse
|
||||
from pathspec import PathSpec
|
||||
from pathspec.patterns import GitWildMatchPattern
|
||||
import pandas as pd
|
||||
from fpdf import FPDF
|
||||
from rich.progress import (
|
||||
Progress,
|
||||
SpinnerColumn,
|
||||
TextColumn,
|
||||
TimeElapsedColumn,
|
||||
BarColumn,
|
||||
TaskProgressColumn,
|
||||
)
|
||||
|
||||
|
||||
class CommentScanner:
|
||||
def __init__(
|
||||
self,
|
||||
workspace_path: str = None,
|
||||
skip_markers: Set[str] = None,
|
||||
show_context: bool = True,
|
||||
):
|
||||
# Resolve relative paths
|
||||
workspace_path = workspace_path or config.DEFAULT_WORKSPACE
|
||||
self.workspace_path = Path(workspace_path).resolve()
|
||||
self.console = Console()
|
||||
self.exclude_patterns = self._load_gitignore()
|
||||
self.skip_markers = skip_markers or config.DEFAULT_SKIP_MARKERS
|
||||
self.show_context = show_context
|
||||
|
||||
def _load_gitignore(self) -> PathSpec:
|
||||
gitignore_patterns = []
|
||||
gitignore_path = self.workspace_path / ".gitignore"
|
||||
|
||||
# Add default exclusions
|
||||
for exclude in config.DEFAULT_EXCLUDES:
|
||||
gitignore_patterns.append(exclude)
|
||||
|
||||
# Read .gitignore if it exists
|
||||
if gitignore_path.exists():
|
||||
with open(gitignore_path, "r", encoding="utf-8") as f:
|
||||
gitignore_patterns.extend(
|
||||
line.strip()
|
||||
for line in f
|
||||
if line.strip() and not line.startswith("#")
|
||||
)
|
||||
|
||||
return PathSpec.from_lines(GitWildMatchPattern, gitignore_patterns)
|
||||
|
||||
def should_skip_path(
|
||||
self,
|
||||
path: Path,
|
||||
filename_filter: str = None,
|
||||
case_sensitive: bool = False,
|
||||
complete_match: bool = False,
|
||||
) -> bool:
|
||||
"""Check if a path should be skipped based on exclusion rules and filename filter."""
|
||||
try:
|
||||
# Convert path to relative path from workspace root
|
||||
rel_path = path.relative_to(self.workspace_path)
|
||||
|
||||
# Apply filename filter if provided
|
||||
if filename_filter:
|
||||
filename = path.name
|
||||
if complete_match:
|
||||
if case_sensitive:
|
||||
if filename != filename_filter:
|
||||
return True
|
||||
else:
|
||||
if filename.lower() != filename_filter.lower():
|
||||
return True
|
||||
else:
|
||||
if case_sensitive:
|
||||
if filename_filter not in filename:
|
||||
return True
|
||||
else:
|
||||
if filename_filter.lower() not in filename.lower():
|
||||
return True
|
||||
|
||||
# Check if path matches gitignore patterns
|
||||
if self.exclude_patterns.match_file(str(rel_path)):
|
||||
return True
|
||||
|
||||
# Skip hidden files and directories
|
||||
if any(part.startswith(".") for part in path.parts):
|
||||
return True
|
||||
|
||||
return False
|
||||
except ValueError: # For paths outside workspace
|
||||
return True
|
||||
|
||||
def get_context_lines(self, all_lines: List[str], comment_line_idx: int) -> str:
|
||||
context = []
|
||||
start_idx = max(0, comment_line_idx - config.CONTEXT_LINES)
|
||||
end_idx = min(len(all_lines), comment_line_idx + config.CONTEXT_LINES + 1)
|
||||
|
||||
# Get lines before
|
||||
for i in range(start_idx, comment_line_idx):
|
||||
line = all_lines[i].strip()
|
||||
if line: # Skip empty lines
|
||||
context.append(f" {line}")
|
||||
|
||||
# Add the comment line itself
|
||||
context.append(f"→ {all_lines[comment_line_idx].strip()}")
|
||||
|
||||
# Get lines after
|
||||
for i in range(comment_line_idx + 1, end_idx):
|
||||
line = all_lines[i].strip()
|
||||
if line: # Skip empty lines
|
||||
context.append(f" {line}")
|
||||
|
||||
return "\n".join(context)
|
||||
|
||||
def scan_file(self, file_path: Path) -> List[Dict]:
|
||||
comments = []
|
||||
file_extension = file_path.suffix.lower()[1:]
|
||||
|
||||
# Skip files we don't support
|
||||
if file_extension not in config.COMMENT_PATTERNS:
|
||||
return comments
|
||||
|
||||
comment_patterns = config.COMMENT_PATTERNS[file_extension]
|
||||
|
||||
# Pre-compile patterns for faster matching
|
||||
single_patterns = comment_patterns.get("single", [])
|
||||
multiline_pattern = comment_patterns.get("multiline")
|
||||
|
||||
# Quick check if file might contain any markers
|
||||
try:
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
if not any(marker in content for marker in config.COMMENT_MARKERS):
|
||||
return comments
|
||||
|
||||
# Reset file pointer and continue with line-by-line processing
|
||||
f.seek(0)
|
||||
lines = f.readlines()
|
||||
except UnicodeDecodeError:
|
||||
return comments # Skip binary files
|
||||
|
||||
in_multiline_comment = False
|
||||
multiline_content = []
|
||||
|
||||
for line_num, line in enumerate(lines):
|
||||
stripped_line = line.strip()
|
||||
if not stripped_line: # Skip empty lines early
|
||||
continue
|
||||
|
||||
# Fast path: check if line might contain any comment
|
||||
if not any(
|
||||
pattern in stripped_line for pattern in single_patterns
|
||||
) and not (
|
||||
multiline_pattern
|
||||
and (
|
||||
multiline_pattern[0] in stripped_line
|
||||
or multiline_pattern[1] in stripped_line
|
||||
)
|
||||
):
|
||||
continue
|
||||
|
||||
# Handle multiline comments
|
||||
if multiline_pattern:
|
||||
start_pattern, end_pattern = multiline_pattern
|
||||
|
||||
if (
|
||||
start_pattern in stripped_line
|
||||
and end_pattern
|
||||
in stripped_line[
|
||||
stripped_line.find(start_pattern) + len(start_pattern) :
|
||||
]
|
||||
):
|
||||
comment_text = stripped_line[
|
||||
stripped_line.find(start_pattern)
|
||||
+ len(start_pattern) : stripped_line.rfind(end_pattern)
|
||||
].strip()
|
||||
self._process_comment(
|
||||
comment_text, comments, file_path, line_num, lines
|
||||
)
|
||||
continue
|
||||
|
||||
if start_pattern in stripped_line and not in_multiline_comment:
|
||||
in_multiline_comment = True
|
||||
multiline_content = [
|
||||
stripped_line[
|
||||
stripped_line.find(start_pattern) + len(start_pattern) :
|
||||
].strip()
|
||||
]
|
||||
continue
|
||||
|
||||
if in_multiline_comment:
|
||||
if end_pattern in stripped_line:
|
||||
in_multiline_comment = False
|
||||
multiline_content.append(
|
||||
stripped_line[: stripped_line.find(end_pattern)].strip()
|
||||
)
|
||||
comment_text = " ".join(multiline_content)
|
||||
self._process_comment(
|
||||
comment_text, comments, file_path, line_num, lines
|
||||
)
|
||||
multiline_content = []
|
||||
else:
|
||||
multiline_content.append(stripped_line)
|
||||
continue
|
||||
|
||||
# Handle single-line comments
|
||||
for pattern in single_patterns:
|
||||
if pattern in stripped_line:
|
||||
comment_text = stripped_line[
|
||||
stripped_line.find(pattern) + len(pattern) :
|
||||
].strip()
|
||||
self._process_comment(
|
||||
comment_text, comments, file_path, line_num, lines
|
||||
)
|
||||
break
|
||||
|
||||
return comments
|
||||
|
||||
def _process_comment(
|
||||
self,
|
||||
comment_text: str,
|
||||
comments: List[Dict],
|
||||
file_path: Path,
|
||||
line_num: int,
|
||||
lines: List[str],
|
||||
) -> None:
|
||||
"""Helper method to process and add valid comments to the comments list."""
|
||||
for marker in config.COMMENT_MARKERS:
|
||||
if comment_text.startswith(marker) and marker not in self.skip_markers:
|
||||
comments.append(
|
||||
{
|
||||
"type": marker,
|
||||
"text": comment_text[len(marker) :].strip(),
|
||||
"file": str(file_path.relative_to(self.workspace_path)),
|
||||
"line": line_num + 1,
|
||||
"context": self.get_context_lines(lines, line_num),
|
||||
}
|
||||
)
|
||||
break
|
||||
|
||||
def scan_workspace(
|
||||
self,
|
||||
filename_filter: str = None,
|
||||
case_sensitive: bool = True,
|
||||
complete_match: bool = False,
|
||||
) -> List[Dict]:
|
||||
all_comments = []
|
||||
|
||||
with Progress(
|
||||
SpinnerColumn(),
|
||||
TextColumn("[progress.description]{task.description}"),
|
||||
BarColumn(),
|
||||
TaskProgressColumn(),
|
||||
TimeElapsedColumn(),
|
||||
console=self.console,
|
||||
) as progress:
|
||||
# Start with an indeterminate progress bar
|
||||
scan_task = progress.add_task("[cyan]Scanning files...", total=None)
|
||||
files_processed = 0
|
||||
|
||||
for pattern in config.FILE_PATTERNS:
|
||||
try:
|
||||
for file_path in self.workspace_path.rglob(pattern):
|
||||
if file_path.is_file() and not self.should_skip_path(
|
||||
file_path, filename_filter, case_sensitive, complete_match
|
||||
):
|
||||
try:
|
||||
files_processed += 1
|
||||
progress.update(
|
||||
scan_task,
|
||||
completed=files_processed,
|
||||
description=f"[cyan]Scanning: {file_path.name}",
|
||||
)
|
||||
file_comments = self.scan_file(file_path)
|
||||
if file_comments: # Only extend if we found comments
|
||||
all_comments.extend(file_comments)
|
||||
except Exception as e:
|
||||
self.console.print(
|
||||
f"Error scanning {file_path}: {e}", style="red"
|
||||
)
|
||||
except Exception as e:
|
||||
self.console.print(f"Error during workspace scan: {e}", style="red")
|
||||
|
||||
return all_comments
|
||||
|
||||
def display_comments(self, comments: List[Dict]):
|
||||
table = Table(title="Project Comments Overview", show_lines=True)
|
||||
|
||||
table.add_column("Type", style="bold")
|
||||
table.add_column("Comment")
|
||||
if self.show_context:
|
||||
table.add_column("Context", style="dim")
|
||||
table.add_column("File", style="dim")
|
||||
table.add_column("Line", style="dim")
|
||||
|
||||
for comment in sorted(comments, key=lambda x: x["type"]):
|
||||
row = [
|
||||
config.COMMENT_MARKERS[comment["type"]],
|
||||
comment["text"],
|
||||
comment["file"],
|
||||
str(comment["line"]),
|
||||
]
|
||||
if self.show_context:
|
||||
row.insert(2, comment["context"])
|
||||
|
||||
table.add_row(
|
||||
*row, style=config.COMMENT_COLORS.get(comment["type"], "white")
|
||||
)
|
||||
|
||||
self.console.print(table)
|
||||
|
||||
def export_to_pdf(self, comments: List[Dict], output_path: str):
|
||||
class PDF(FPDF):
|
||||
def multi_cell_row(self, heights, cols, border=1):
|
||||
# Calculate max number of lines for all columns
|
||||
max_lines = 0
|
||||
lines = []
|
||||
# Adjust widths based on whether context is shown
|
||||
if self.show_context:
|
||||
widths = [20, 60, 60, 60, 20] # Type, Comment, Context, File, Line
|
||||
else:
|
||||
widths = [20, 60, 60, 20] # Type, Comment, File, Line
|
||||
|
||||
x_start = self.get_x()
|
||||
for i, col in enumerate(cols):
|
||||
self.set_x(x_start)
|
||||
lines.append(
|
||||
self.multi_cell(
|
||||
widths[i], heights, col, border=border, split_only=True
|
||||
)
|
||||
)
|
||||
max_lines = max(max_lines, len(lines[-1]))
|
||||
|
||||
# Draw multi-cells with same height
|
||||
height_of_line = heights
|
||||
x_start = self.get_x()
|
||||
for i in range(max_lines):
|
||||
self.set_x(x_start)
|
||||
for j, width in enumerate(widths):
|
||||
content = lines[j][i] if i < len(lines[j]) else ""
|
||||
self.multi_cell(width, height_of_line, content, border=border)
|
||||
self.set_xy(self.get_x() + width, self.get_y() - height_of_line)
|
||||
self.ln(height_of_line)
|
||||
|
||||
return max_lines * height_of_line
|
||||
|
||||
pdf = PDF()
|
||||
pdf.set_auto_page_break(auto=True, margin=15)
|
||||
pdf.add_page()
|
||||
pdf.set_font("Arial", size=10)
|
||||
pdf.show_context = self.show_context
|
||||
|
||||
# Add title
|
||||
pdf.set_font("Arial", "B", 14)
|
||||
pdf.cell(0, 10, "Project Comments Overview", ln=True, align="C")
|
||||
pdf.ln(5)
|
||||
pdf.set_font("Arial", size=10)
|
||||
|
||||
# Headers
|
||||
headers = ["Type", "Comment", "File", "Line"]
|
||||
if self.show_context:
|
||||
headers.insert(2, "Context")
|
||||
|
||||
pdf.set_fill_color(240, 240, 240)
|
||||
pdf.multi_cell_row(8, headers)
|
||||
|
||||
# Content
|
||||
for comment in sorted(comments, key=lambda x: x["type"]):
|
||||
try:
|
||||
row = [
|
||||
config.COMMENT_MARKERS[comment["type"]],
|
||||
comment["text"],
|
||||
comment["file"],
|
||||
str(comment["line"]),
|
||||
]
|
||||
|
||||
if self.show_context:
|
||||
# Clean up context for PDF compatibility
|
||||
context = comment["context"]
|
||||
context = context.replace("→", ">")
|
||||
context = context.encode("ascii", "replace").decode("ascii")
|
||||
context = context.replace(
|
||||
"\n", " | "
|
||||
) # Replace line breaks with separator
|
||||
row.insert(2, context)
|
||||
|
||||
# Clean up all cells for PDF compatibility
|
||||
row = [
|
||||
str(cell).encode("ascii", "replace").decode("ascii") for cell in row
|
||||
]
|
||||
|
||||
pdf.multi_cell_row(8, row)
|
||||
|
||||
except Exception as e:
|
||||
self.console.print(
|
||||
f"Warning: Skipped row due to encoding issue: {e}", style="yellow"
|
||||
)
|
||||
|
||||
pdf.output(output_path)
|
||||
|
||||
def export_to_excel(self, comments: List[Dict], output_path: str):
|
||||
df_data = []
|
||||
for comment in comments:
|
||||
row = {
|
||||
"Type": config.COMMENT_MARKERS[comment["type"]],
|
||||
"Comment": comment["text"],
|
||||
"File": comment["file"],
|
||||
"Line": comment["line"],
|
||||
}
|
||||
if self.show_context:
|
||||
row["Context"] = comment["context"]
|
||||
df_data.append(row)
|
||||
|
||||
df = pd.DataFrame(df_data)
|
||||
df.to_excel(output_path, index=False, engine="openpyxl")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Scan TypeScript project comments",
|
||||
epilog="""
|
||||
Examples:
|
||||
%(prog)s # Scan all files with default settings
|
||||
%(prog)s -w /path/to/project # Scan a specific workspace
|
||||
%(prog)s -f test.py # Find comments in files containing 'test.py' (case insensitive)
|
||||
%(prog)s -f test.py -c # Find comments in files named exactly 'test.py'
|
||||
%(prog)s -f Test.py -C # Find comments with case-sensitive filename match
|
||||
%(prog)s -f test.py -c -C # Find comments in files named exactly 'test.py' (case sensitive)
|
||||
%(prog)s --skip TODO FIXME # Skip TODO and FIXME comments
|
||||
%(prog)s -a # Include all comment types
|
||||
%(prog)s -e pdf -o comments.pdf # Export comments to PDF
|
||||
""",
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--workspace", "-w", type=str, help="Path to the workspace directory"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--skip",
|
||||
"-s",
|
||||
type=str,
|
||||
nargs="+",
|
||||
help="Markers to skip (e.g., --skip NOTE TODO)",
|
||||
default=list(config.DEFAULT_SKIP_MARKERS),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--include-all",
|
||||
"-a",
|
||||
action="store_true",
|
||||
help="Include all markers (override default skip)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-context",
|
||||
"-nc",
|
||||
action="store_true",
|
||||
help="Don't show context lines around comments",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--export",
|
||||
"-e",
|
||||
type=str,
|
||||
choices=config.EXPORT_FORMATS,
|
||||
help="Export format (pdf or xlsx)",
|
||||
)
|
||||
parser.add_argument("--output", "-o", type=str, help="Output file path for export")
|
||||
|
||||
# Create a filename filter group
|
||||
filename_group = parser.add_argument_group("filename filtering")
|
||||
filename_group.add_argument(
|
||||
"--filename",
|
||||
"-f",
|
||||
type=str,
|
||||
help="Filter files by filename (case insensitive by default)",
|
||||
)
|
||||
filename_group.add_argument(
|
||||
"--complete-match",
|
||||
"-c",
|
||||
action="store_true",
|
||||
help="Match complete filename instead of partial (only with -f)",
|
||||
)
|
||||
filename_group.add_argument(
|
||||
"--case-sensitive",
|
||||
"-C",
|
||||
action="store_true",
|
||||
help="Make filename filter case sensitive (only with -f)",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Update validation
|
||||
if args.case_sensitive and not args.filename:
|
||||
parser.error("--case-sensitive can only be used with --filename")
|
||||
if args.complete_match and not args.filename:
|
||||
parser.error("--complete-match can only be used with --filename")
|
||||
|
||||
try:
|
||||
skip_markers = set() if args.include_all else set(args.skip)
|
||||
scanner = CommentScanner(
|
||||
args.workspace, skip_markers, show_context=not args.no_context
|
||||
)
|
||||
comments = scanner.scan_workspace(
|
||||
filename_filter=args.filename,
|
||||
case_sensitive=args.case_sensitive,
|
||||
complete_match=args.complete_match,
|
||||
)
|
||||
|
||||
if not comments:
|
||||
scanner.console.print("No comments found!", style="yellow")
|
||||
return
|
||||
|
||||
# Display in console
|
||||
scanner.display_comments(comments)
|
||||
|
||||
# Export if requested
|
||||
if args.export:
|
||||
if not args.output:
|
||||
raise ValueError("Output path (-o) is required when exporting")
|
||||
|
||||
if args.export == "pdf":
|
||||
scanner.export_to_pdf(comments, args.output)
|
||||
elif args.export == "xlsx":
|
||||
scanner.export_to_excel(comments, args.output)
|
||||
|
||||
scanner.console.print(f"\nExported to {args.output}", style="green")
|
||||
|
||||
except Exception as e:
|
||||
Console().print(f"Error: {str(e)}", style="red")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
13
pyproject.toml
Normal file
13
pyproject.toml
Normal file
@ -0,0 +1,13 @@
|
||||
[project]
|
||||
name = "work-manager"
|
||||
version = "0.1.0"
|
||||
description = "Add your description here"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
"fpdf>=1.7.2",
|
||||
"openpyxl>=3.1.5",
|
||||
"pandas>=2.2.3",
|
||||
"pathspec>=0.12.1",
|
||||
"rich>=13.9.4",
|
||||
]
|
BIN
screenshot.png
Normal file
BIN
screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 MiB |
234
uv.lock
generated
Normal file
234
uv.lock
generated
Normal file
@ -0,0 +1,234 @@
|
||||
version = 1
|
||||
requires-python = ">=3.11"
|
||||
resolution-markers = [
|
||||
"python_full_version < '3.12'",
|
||||
"python_full_version >= '3.12'",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "et-xmlfile"
|
||||
version = "2.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d3/38/af70d7ab1ae9d4da450eeec1fa3918940a5fafb9055e934af8d6eb0c2313/et_xmlfile-2.0.0.tar.gz", hash = "sha256:dab3f4764309081ce75662649be815c4c9081e88f0837825f90fd28317d4da54", size = 17234 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c1/8b/5fe2cc11fee489817272089c4203e679c63b570a5aaeb18d852ae3cbba6a/et_xmlfile-2.0.0-py3-none-any.whl", hash = "sha256:7a91720bc756843502c3b7504c77b8fe44217c85c537d85037f0f536151b2caa", size = 18059 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fpdf"
|
||||
version = "1.7.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/37/c6/608a9e6c172bf9124aa687ec8b9f0e8e5d697d59a5f4fad0e2d5ec2a7556/fpdf-1.7.2.tar.gz", hash = "sha256:125840783289e7d12552b1e86ab692c37322e7a65b96a99e0ea86cca041b6779", size = 39504 }
|
||||
|
||||
[[package]]
|
||||
name = "markdown-it-py"
|
||||
version = "3.0.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "mdurl" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/38/71/3b932df36c1a044d397a1f92d1cf91ee0a503d91e470cbd670aa66b07ed0/markdown-it-py-3.0.0.tar.gz", hash = "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb", size = 74596 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/42/d7/1ec15b46af6af88f19b8e5ffea08fa375d433c998b8a7639e76935c14f1f/markdown_it_py-3.0.0-py3-none-any.whl", hash = "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", size = 87528 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mdurl"
|
||||
version = "0.1.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "numpy"
|
||||
version = "2.2.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f2/a5/fdbf6a7871703df6160b5cf3dd774074b086d278172285c52c2758b76305/numpy-2.2.1.tar.gz", hash = "sha256:45681fd7128c8ad1c379f0ca0776a8b0c6583d2f69889ddac01559dfe4390918", size = 20227662 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/59/14/645887347124e101d983e1daf95b48dc3e136bf8525cb4257bf9eab1b768/numpy-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:40f9e544c1c56ba8f1cf7686a8c9b5bb249e665d40d626a23899ba6d5d9e1484", size = 21217379 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9f/fd/2279000cf29f58ccfd3778cbf4670dfe3f7ce772df5e198c5abe9e88b7d7/numpy-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f9b57eaa3b0cd8db52049ed0330747b0364e899e8a606a624813452b8203d5f7", size = 14388520 },
|
||||
{ url = "https://files.pythonhosted.org/packages/58/b0/034eb5d5ba12d66ab658ff3455a31f20add0b78df8203c6a7451bd1bee21/numpy-2.2.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:bc8a37ad5b22c08e2dbd27df2b3ef7e5c0864235805b1e718a235bcb200cf1cb", size = 5389286 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5d/69/6f3cccde92e82e7835fdb475c2bf439761cbf8a1daa7c07338e1e132dfec/numpy-2.2.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:9036d6365d13b6cbe8f27a0eaf73ddcc070cae584e5ff94bb45e3e9d729feab5", size = 6930345 },
|
||||
{ url = "https://files.pythonhosted.org/packages/d1/72/1cd38e91ab563e67f584293fcc6aca855c9ae46dba42e6b5ff4600022899/numpy-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:51faf345324db860b515d3f364eaa93d0e0551a88d6218a7d61286554d190d73", size = 14335748 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f2/d4/f999444e86986f3533e7151c272bd8186c55dda554284def18557e013a2a/numpy-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38efc1e56b73cc9b182fe55e56e63b044dd26a72128fd2fbd502f75555d92591", size = 16391057 },
|
||||
{ url = "https://files.pythonhosted.org/packages/99/7b/85cef6a3ae1b19542b7afd97d0b296526b6ef9e3c43ea0c4d9c4404fb2d0/numpy-2.2.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:31b89fa67a8042e96715c68e071a1200c4e172f93b0fbe01a14c0ff3ff820fc8", size = 15556943 },
|
||||
{ url = "https://files.pythonhosted.org/packages/69/7e/b83cc884c3508e91af78760f6b17ab46ad649831b1fa35acb3eb26d9e6d2/numpy-2.2.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4c86e2a209199ead7ee0af65e1d9992d1dce7e1f63c4b9a616500f93820658d0", size = 18180785 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/9f/eb4a9a38867de059dcd4b6e18d47c3867fbd3795d4c9557bb49278f94087/numpy-2.2.1-cp311-cp311-win32.whl", hash = "sha256:b34d87e8a3090ea626003f87f9392b3929a7bbf4104a05b6667348b6bd4bf1cd", size = 6568983 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6d/1e/be3b9f3073da2f8c7fa361fcdc231b548266b0781029fdbaf75eeab997fd/numpy-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:360137f8fb1b753c5cde3ac388597ad680eccbbbb3865ab65efea062c4a1fd16", size = 12917260 },
|
||||
{ url = "https://files.pythonhosted.org/packages/62/12/b928871c570d4a87ab13d2cc19f8817f17e340d5481621930e76b80ffb7d/numpy-2.2.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:694f9e921a0c8f252980e85bce61ebbd07ed2b7d4fa72d0e4246f2f8aa6642ab", size = 20909861 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/c3/59df91ae1d8ad7c5e03efd63fd785dec62d96b0fe56d1f9ab600b55009af/numpy-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3683a8d166f2692664262fd4900f207791d005fb088d7fdb973cc8d663626faa", size = 14095776 },
|
||||
{ url = "https://files.pythonhosted.org/packages/af/4e/8ed5868efc8e601fb69419644a280e9c482b75691466b73bfaab7d86922c/numpy-2.2.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:780077d95eafc2ccc3ced969db22377b3864e5b9a0ea5eb347cc93b3ea900315", size = 5126239 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1a/74/dd0bbe650d7bc0014b051f092f2de65e34a8155aabb1287698919d124d7f/numpy-2.2.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:55ba24ebe208344aa7a00e4482f65742969a039c2acfcb910bc6fcd776eb4355", size = 6659296 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/11/4ebd7a3f4a655764dc98481f97bd0a662fb340d1001be6050606be13e162/numpy-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b1d07b53b78bf84a96898c1bc139ad7f10fda7423f5fd158fd0f47ec5e01ac7", size = 14047121 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7f/a7/c1f1d978166eb6b98ad009503e4d93a8c1962d0eb14a885c352ee0276a54/numpy-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5062dc1a4e32a10dc2b8b13cedd58988261416e811c1dc4dbdea4f57eea61b0d", size = 16096599 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/6d/0e22afd5fcbb4d8d0091f3f46bf4e8906399c458d4293da23292c0ba5022/numpy-2.2.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:fce4f615f8ca31b2e61aa0eb5865a21e14f5629515c9151850aa936c02a1ee51", size = 15243932 },
|
||||
{ url = "https://files.pythonhosted.org/packages/03/39/e4e5832820131ba424092b9610d996b37e5557180f8e2d6aebb05c31ae54/numpy-2.2.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:67d4cda6fa6ffa073b08c8372aa5fa767ceb10c9a0587c707505a6d426f4e046", size = 17861032 },
|
||||
{ url = "https://files.pythonhosted.org/packages/5f/8a/3794313acbf5e70df2d5c7d2aba8718676f8d054a05abe59e48417fb2981/numpy-2.2.1-cp312-cp312-win32.whl", hash = "sha256:32cb94448be47c500d2c7a95f93e2f21a01f1fd05dd2beea1ccd049bb6001cd2", size = 6274018 },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/c1/c31d3637f2641e25c7a19adf2ae822fdaf4ddd198b05d79a92a9ce7cb63e/numpy-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:ba5511d8f31c033a5fcbda22dd5c813630af98c70b2661f2d2c654ae3cdfcfc8", size = 12613843 },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/d6/91a26e671c396e0c10e327b763485ee295f5a5a7a48c553f18417e5a0ed5/numpy-2.2.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f1d09e520217618e76396377c81fba6f290d5f926f50c35f3a5f72b01a0da780", size = 20896464 },
|
||||
{ url = "https://files.pythonhosted.org/packages/8c/40/5792ccccd91d45e87d9e00033abc4f6ca8a828467b193f711139ff1f1cd9/numpy-2.2.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3ecc47cd7f6ea0336042be87d9e7da378e5c7e9b3c8ad0f7c966f714fc10d821", size = 14111350 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/2a/fb0a27f846cb857cef0c4c92bef89f133a3a1abb4e16bba1c4dace2e9b49/numpy-2.2.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:f419290bc8968a46c4933158c91a0012b7a99bb2e465d5ef5293879742f8797e", size = 5111629 },
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/e5/8e81bb9d84db88b047baf4e8b681a3e48d6390bc4d4e4453eca428ecbb49/numpy-2.2.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5b6c390bfaef8c45a260554888966618328d30e72173697e5cabe6b285fb2348", size = 6645865 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/1a/a90ceb191dd2f9e2897c69dde93ccc2d57dd21ce2acbd7b0333e8eea4e8d/numpy-2.2.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:526fc406ab991a340744aad7e25251dd47a6720a685fa3331e5c59fef5282a59", size = 14043508 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/5a/e572284c86a59dec0871a49cd4e5351e20b9c751399d5f1d79628c0542cb/numpy-2.2.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f74e6fdeb9a265624ec3a3918430205dff1df7e95a230779746a6af78bc615af", size = 16094100 },
|
||||
{ url = "https://files.pythonhosted.org/packages/0c/2c/a79d24f364788386d85899dd280a94f30b0950be4b4a545f4fa4ed1d4ca7/numpy-2.2.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:53c09385ff0b72ba79d8715683c1168c12e0b6e84fb0372e97553d1ea91efe51", size = 15239691 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cf/79/1e20fd1c9ce5a932111f964b544facc5bb9bde7865f5b42f00b4a6a9192b/numpy-2.2.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:f3eac17d9ec51be534685ba877b6ab5edc3ab7ec95c8f163e5d7b39859524716", size = 17856571 },
|
||||
{ url = "https://files.pythonhosted.org/packages/be/5b/cc155e107f75d694f562bdc84a26cc930569f3dfdfbccb3420b626065777/numpy-2.2.1-cp313-cp313-win32.whl", hash = "sha256:9ad014faa93dbb52c80d8f4d3dcf855865c876c9660cb9bd7553843dd03a4b1e", size = 6270841 },
|
||||
{ url = "https://files.pythonhosted.org/packages/44/be/0e5cd009d2162e4138d79a5afb3b5d2341f0fe4777ab6e675aa3d4a42e21/numpy-2.2.1-cp313-cp313-win_amd64.whl", hash = "sha256:164a829b6aacf79ca47ba4814b130c4020b202522a93d7bff2202bfb33b61c60", size = 12606618 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/87/04ddf02dd86fb17c7485a5f87b605c4437966d53de1e3745d450343a6f56/numpy-2.2.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4dfda918a13cc4f81e9118dea249e192ab167a0bb1966272d5503e39234d694e", size = 20921004 },
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/3e/d0e9e32ab14005425d180ef950badf31b862f3839c5b927796648b11f88a/numpy-2.2.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:733585f9f4b62e9b3528dd1070ec4f52b8acf64215b60a845fa13ebd73cd0712", size = 14119910 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b5/5b/aa2d1905b04a8fb681e08742bb79a7bddfc160c7ce8e1ff6d5c821be0236/numpy-2.2.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:89b16a18e7bba224ce5114db863e7029803c179979e1af6ad6a6b11f70545008", size = 5153612 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ce/35/6831808028df0648d9b43c5df7e1051129aa0d562525bacb70019c5f5030/numpy-2.2.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:676f4eebf6b2d430300f1f4f4c2461685f8269f94c89698d832cdf9277f30b84", size = 6668401 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b1/38/10ef509ad63a5946cc042f98d838daebfe7eaf45b9daaf13df2086b15ff9/numpy-2.2.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:27f5cdf9f493b35f7e41e8368e7d7b4bbafaf9660cba53fb21d2cd174ec09631", size = 14014198 },
|
||||
{ url = "https://files.pythonhosted.org/packages/df/f8/c80968ae01df23e249ee0a4487fae55a4c0fe2f838dfe9cc907aa8aea0fa/numpy-2.2.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1ad395cf254c4fbb5b2132fee391f361a6e8c1adbd28f2cd8e79308a615fe9d", size = 16076211 },
|
||||
{ url = "https://files.pythonhosted.org/packages/09/69/05c169376016a0b614b432967ac46ff14269eaffab80040ec03ae1ae8e2c/numpy-2.2.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:08ef779aed40dbc52729d6ffe7dd51df85796a702afbf68a4f4e41fafdc8bda5", size = 15220266 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f1/ff/94a4ce67ea909f41cf7ea712aebbe832dc67decad22944a1020bb398a5ee/numpy-2.2.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:26c9c4382b19fcfbbed3238a14abf7ff223890ea1936b8890f058e7ba35e8d71", size = 17852844 },
|
||||
{ url = "https://files.pythonhosted.org/packages/46/72/8a5dbce4020dfc595592333ef2fbb0a187d084ca243b67766d29d03e0096/numpy-2.2.1-cp313-cp313t-win32.whl", hash = "sha256:93cf4e045bae74c90ca833cba583c14b62cb4ba2cba0abd2b141ab52548247e2", size = 6326007 },
|
||||
{ url = "https://files.pythonhosted.org/packages/7b/9c/4fce9cf39dde2562584e4cfd351a0140240f82c0e3569ce25a250f47037d/numpy-2.2.1-cp313-cp313t-win_amd64.whl", hash = "sha256:bff7d8ec20f5f42607599f9994770fa65d76edca264a87b5e4ea5629bce12268", size = 12693107 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openpyxl"
|
||||
version = "3.1.5"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "et-xmlfile" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3d/f9/88d94a75de065ea32619465d2f77b29a0469500e99012523b91cc4141cd1/openpyxl-3.1.5.tar.gz", hash = "sha256:cf0e3cf56142039133628b5acffe8ef0c12bc902d2aadd3e0fe5878dc08d1050", size = 186464 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c0/da/977ded879c29cbd04de313843e76868e6e13408a94ed6b987245dc7c8506/openpyxl-3.1.5-py2.py3-none-any.whl", hash = "sha256:5282c12b107bffeef825f4617dc029afaf41d0ea60823bbb665ef3079dc79de2", size = 250910 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pandas"
|
||||
version = "2.2.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "numpy" },
|
||||
{ name = "python-dateutil" },
|
||||
{ name = "pytz" },
|
||||
{ name = "tzdata" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9c/d6/9f8431bacc2e19dca897724cd097b1bb224a6ad5433784a44b587c7c13af/pandas-2.2.3.tar.gz", hash = "sha256:4f18ba62b61d7e192368b84517265a99b4d7ee8912f8708660fb4a366cc82667", size = 4399213 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a8/44/d9502bf0ed197ba9bf1103c9867d5904ddcaf869e52329787fc54ed70cc8/pandas-2.2.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:66108071e1b935240e74525006034333f98bcdb87ea116de573a6a0dccb6c039", size = 12602222 },
|
||||
{ url = "https://files.pythonhosted.org/packages/52/11/9eac327a38834f162b8250aab32a6781339c69afe7574368fffe46387edf/pandas-2.2.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7c2875855b0ff77b2a64a0365e24455d9990730d6431b9e0ee18ad8acee13dbd", size = 11321274 },
|
||||
{ url = "https://files.pythonhosted.org/packages/45/fb/c4beeb084718598ba19aa9f5abbc8aed8b42f90930da861fcb1acdb54c3a/pandas-2.2.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cd8d0c3be0515c12fed0bdbae072551c8b54b7192c7b1fda0ba56059a0179698", size = 15579836 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cd/5f/4dba1d39bb9c38d574a9a22548c540177f78ea47b32f99c0ff2ec499fac5/pandas-2.2.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c124333816c3a9b03fbeef3a9f230ba9a737e9e5bb4060aa2107a86cc0a497fc", size = 13058505 },
|
||||
{ url = "https://files.pythonhosted.org/packages/b9/57/708135b90391995361636634df1f1130d03ba456e95bcf576fada459115a/pandas-2.2.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:63cc132e40a2e084cf01adf0775b15ac515ba905d7dcca47e9a251819c575ef3", size = 16744420 },
|
||||
{ url = "https://files.pythonhosted.org/packages/86/4a/03ed6b7ee323cf30404265c284cee9c65c56a212e0a08d9ee06984ba2240/pandas-2.2.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:29401dbfa9ad77319367d36940cd8a0b3a11aba16063e39632d98b0e931ddf32", size = 14440457 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/8c/87ddf1fcb55d11f9f847e3c69bb1c6f8e46e2f40ab1a2d2abadb2401b007/pandas-2.2.3-cp311-cp311-win_amd64.whl", hash = "sha256:3fc6873a41186404dad67245896a6e440baacc92f5b716ccd1bc9ed2995ab2c5", size = 11617166 },
|
||||
{ url = "https://files.pythonhosted.org/packages/17/a3/fb2734118db0af37ea7433f57f722c0a56687e14b14690edff0cdb4b7e58/pandas-2.2.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b1d432e8d08679a40e2a6d8b2f9770a5c21793a6f9f47fdd52c5ce1948a5a8a9", size = 12529893 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e1/0c/ad295fd74bfac85358fd579e271cded3ac969de81f62dd0142c426b9da91/pandas-2.2.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a5a1595fe639f5988ba6a8e5bc9649af3baf26df3998a0abe56c02609392e0a4", size = 11363475 },
|
||||
{ url = "https://files.pythonhosted.org/packages/c6/2a/4bba3f03f7d07207481fed47f5b35f556c7441acddc368ec43d6643c5777/pandas-2.2.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5de54125a92bb4d1c051c0659e6fcb75256bf799a732a87184e5ea503965bce3", size = 15188645 },
|
||||
{ url = "https://files.pythonhosted.org/packages/38/f8/d8fddee9ed0d0c0f4a2132c1dfcf0e3e53265055da8df952a53e7eaf178c/pandas-2.2.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fffb8ae78d8af97f849404f21411c95062db1496aeb3e56f146f0355c9989319", size = 12739445 },
|
||||
{ url = "https://files.pythonhosted.org/packages/20/e8/45a05d9c39d2cea61ab175dbe6a2de1d05b679e8de2011da4ee190d7e748/pandas-2.2.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6dfcb5ee8d4d50c06a51c2fffa6cff6272098ad6540aed1a76d15fb9318194d8", size = 16359235 },
|
||||
{ url = "https://files.pythonhosted.org/packages/1d/99/617d07a6a5e429ff90c90da64d428516605a1ec7d7bea494235e1c3882de/pandas-2.2.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:062309c1b9ea12a50e8ce661145c6aab431b1e99530d3cd60640e255778bd43a", size = 14056756 },
|
||||
{ url = "https://files.pythonhosted.org/packages/29/d4/1244ab8edf173a10fd601f7e13b9566c1b525c4f365d6bee918e68381889/pandas-2.2.3-cp312-cp312-win_amd64.whl", hash = "sha256:59ef3764d0fe818125a5097d2ae867ca3fa64df032331b7e0917cf5d7bf66b13", size = 11504248 },
|
||||
{ url = "https://files.pythonhosted.org/packages/64/22/3b8f4e0ed70644e85cfdcd57454686b9057c6c38d2f74fe4b8bc2527214a/pandas-2.2.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f00d1345d84d8c86a63e476bb4955e46458b304b9575dcf71102b5c705320015", size = 12477643 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e4/93/b3f5d1838500e22c8d793625da672f3eec046b1a99257666c94446969282/pandas-2.2.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3508d914817e153ad359d7e069d752cdd736a247c322d932eb89e6bc84217f28", size = 11281573 },
|
||||
{ url = "https://files.pythonhosted.org/packages/f5/94/6c79b07f0e5aab1dcfa35a75f4817f5c4f677931d4234afcd75f0e6a66ca/pandas-2.2.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22a9d949bfc9a502d320aa04e5d02feab689d61da4e7764b62c30b991c42c5f0", size = 15196085 },
|
||||
{ url = "https://files.pythonhosted.org/packages/e8/31/aa8da88ca0eadbabd0a639788a6da13bb2ff6edbbb9f29aa786450a30a91/pandas-2.2.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f3a255b2c19987fbbe62a9dfd6cff7ff2aa9ccab3fc75218fd4b7530f01efa24", size = 12711809 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ee/7c/c6dbdb0cb2a4344cacfb8de1c5808ca885b2e4dcfde8008266608f9372af/pandas-2.2.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:800250ecdadb6d9c78eae4990da62743b857b470883fa27f652db8bdde7f6659", size = 16356316 },
|
||||
{ url = "https://files.pythonhosted.org/packages/57/b7/8b757e7d92023b832869fa8881a992696a0bfe2e26f72c9ae9f255988d42/pandas-2.2.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6374c452ff3ec675a8f46fd9ab25c4ad0ba590b71cf0656f8b6daa5202bca3fb", size = 14022055 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3b/bc/4b18e2b8c002572c5a441a64826252ce5da2aa738855747247a971988043/pandas-2.2.3-cp313-cp313-win_amd64.whl", hash = "sha256:61c5ad4043f791b61dd4752191d9f07f0ae412515d59ba8f005832a532f8736d", size = 11481175 },
|
||||
{ url = "https://files.pythonhosted.org/packages/76/a3/a5d88146815e972d40d19247b2c162e88213ef51c7c25993942c39dbf41d/pandas-2.2.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:3b71f27954685ee685317063bf13c7709a7ba74fc996b84fc6821c59b0f06468", size = 12615650 },
|
||||
{ url = "https://files.pythonhosted.org/packages/9c/8c/f0fd18f6140ddafc0c24122c8a964e48294acc579d47def376fef12bcb4a/pandas-2.2.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:38cf8125c40dae9d5acc10fa66af8ea6fdf760b2714ee482ca691fc66e6fcb18", size = 11290177 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ed/f9/e995754eab9c0f14c6777401f7eece0943840b7a9fc932221c19d1abee9f/pandas-2.2.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ba96630bc17c875161df3818780af30e43be9b166ce51c9a18c1feae342906c2", size = 14651526 },
|
||||
{ url = "https://files.pythonhosted.org/packages/25/b0/98d6ae2e1abac4f35230aa756005e8654649d305df9a28b16b9ae4353bff/pandas-2.2.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1db71525a1538b30142094edb9adc10be3f3e176748cd7acc2240c2f2e5aa3a4", size = 11871013 },
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/57/0f72a10f9db6a4628744c8e8f0df4e6e21de01212c7c981d31e50ffc8328/pandas-2.2.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:15c0e1e02e93116177d29ff83e8b1619c93ddc9c49083f237d4312337a61165d", size = 15711620 },
|
||||
{ url = "https://files.pythonhosted.org/packages/ab/5f/b38085618b950b79d2d9164a711c52b10aefc0ae6833b96f626b7021b2ed/pandas-2.2.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:ad5b65698ab28ed8d7f18790a0dc58005c7629f227be9ecc1072aa74c0c1d43a", size = 13098436 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pathspec"
|
||||
version = "0.12.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ca/bc/f35b8446f4531a7cb215605d100cd88b7ac6f44ab3fc94870c120ab3adbf/pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712", size = 51043 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pygments"
|
||||
version = "2.19.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7c/2d/c3338d48ea6cc0feb8446d8e6937e1408088a72a39937982cc6111d17f84/pygments-2.19.1.tar.gz", hash = "sha256:61c16d2a8576dc0649d9f39e089b5f02bcd27fba10d8fb4dcc28173f7a45151f", size = 4968581 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/8a/0b/9fcc47d19c48b59121088dd6da2488a49d5f72dacf8262e2790a1d2c7d15/pygments-2.19.1-py3-none-any.whl", hash = "sha256:9ea1544ad55cecf4b8242fab6dd35a93bbce657034b0611ee383099054ab6d8c", size = 1225293 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "python-dateutil"
|
||||
version = "2.9.0.post0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "six" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/66/c0/0c8b6ad9f17a802ee498c46e004a0eb49bc148f2fd230864601a86dcf6db/python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3", size = 342432 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pytz"
|
||||
version = "2024.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/3a/31/3c70bf7603cc2dca0f19bdc53b4537a797747a58875b552c8c413d963a3f/pytz-2024.2.tar.gz", hash = "sha256:2aa355083c50a0f93fa581709deac0c9ad65cca8a9e9beac660adcbd493c798a", size = 319692 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/11/c3/005fcca25ce078d2cc29fd559379817424e94885510568bc1bc53d7d5846/pytz-2024.2-py2.py3-none-any.whl", hash = "sha256:31c7c1817eb7fae7ca4b8c7ee50c72f93aa2dd863de768e1ef4245d426aa0725", size = 508002 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rich"
|
||||
version = "13.9.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "markdown-it-py" },
|
||||
{ name = "pygments" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ab/3a/0316b28d0761c6734d6bc14e770d85506c986c85ffb239e688eeaab2c2bc/rich-13.9.4.tar.gz", hash = "sha256:439594978a49a09530cff7ebc4b5c7103ef57baf48d5ea3184f21d9a2befa098", size = 223149 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/19/71/39c7c0d87f8d4e6c020a393182060eaefeeae6c01dab6a84ec346f2567df/rich-13.9.4-py3-none-any.whl", hash = "sha256:6049d5e6ec054bf2779ab3358186963bac2ea89175919d699e378b99738c2a90", size = 242424 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "six"
|
||||
version = "1.17.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tzdata"
|
||||
version = "2024.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e1/34/943888654477a574a86a98e9896bae89c7aa15078ec29f490fef2f1e5384/tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc", size = 193282 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/ab/7e5f53c3b9d14972843a647d8d7a853969a58aecc7559cb3267302c94774/tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd", size = 346586 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "work-manager"
|
||||
version = "0.1.0"
|
||||
source = { virtual = "." }
|
||||
dependencies = [
|
||||
{ name = "fpdf" },
|
||||
{ name = "openpyxl" },
|
||||
{ name = "pandas" },
|
||||
{ name = "pathspec" },
|
||||
{ name = "rich" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [
|
||||
{ name = "fpdf", specifier = ">=1.7.2" },
|
||||
{ name = "openpyxl", specifier = ">=3.1.5" },
|
||||
{ name = "pandas", specifier = ">=2.2.3" },
|
||||
{ name = "pathspec", specifier = ">=0.12.1" },
|
||||
{ name = "rich", specifier = ">=13.9.4" },
|
||||
]
|
Loading…
x
Reference in New Issue
Block a user