first commit

This commit is contained in:
tcsenpai 2025-01-15 14:31:35 +01:00
commit 49d337b04a
8 changed files with 1036 additions and 0 deletions

17
.gitignore vendored Normal file
View 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
View File

@ -0,0 +1 @@
3.11

138
README.md Normal file
View 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.
![overseer](./screenshot.png)
## 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
View 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
View 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
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 MiB

234
uv.lock generated Normal file
View 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" },
]