feat: qol improvements

This commit is contained in:
Barrett Ruth 2025-09-18 21:28:34 -04:00
parent 986e03c974
commit 5bf8c8960b
13 changed files with 810 additions and 0 deletions

View file

@ -64,3 +64,15 @@ jobs:
run: uv tool install ruff
- name: Lint Python files with ruff
run: ruff check scrapers/
python-typecheck:
name: Python Type Checking
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v4
- name: Install dependencies with mypy
run: uv sync --dev
- name: Type check Python files with mypy
run: uv run mypy scrapers/

View file

@ -9,3 +9,8 @@ dependencies = [
"cloudscraper>=1.2.71",
"requests>=2.32.5",
]
[dependency-groups]
dev = [
"mypy>=1.18.2",
]

55
tests/cache_spec.lua Normal file
View file

@ -0,0 +1,55 @@
-- Unit tests for caching system
describe("cp.cache", function()
local cache
local temp_dir
before_each(function()
cache = require("cp.cache")
temp_dir = vim.fn.tempname()
vim.fn.mkdir(temp_dir, "p")
-- Mock cache directory
end)
after_each(function()
-- Clean up temp files
vim.fn.delete(temp_dir, "rf")
end)
describe("contest metadata caching", function()
it("stores contest metadata correctly", function()
-- Test storing contest data
end)
it("retrieves cached contest metadata", function()
-- Test retrieving contest data
end)
it("handles missing cache files gracefully", function()
-- Test missing cache behavior
end)
end)
describe("test case caching", function()
it("stores test cases for problems", function()
-- Test test case storage
end)
it("retrieves cached test cases", function()
-- Test test case retrieval
end)
it("handles cache invalidation", function()
-- Test cache expiry/invalidation
end)
end)
describe("cache persistence", function()
it("persists cache across sessions", function()
-- Test cache file persistence
end)
it("handles corrupted cache files", function()
-- Test corrupted cache recovery
end)
end)
end)

View file

@ -0,0 +1,79 @@
-- Unit tests for command parsing and validation
describe("cp command parsing", function()
local cp
before_each(function()
cp = require("cp")
cp.setup()
end)
describe("platform setup commands", function()
it("parses :CP codeforces correctly", function()
-- Test platform-only command parsing
end)
it("parses :CP codeforces 1800 correctly", function()
-- Test contest setup command parsing
end)
it("parses :CP codeforces 1800 A correctly", function()
-- Test full setup command parsing
end)
it("parses CSES format :CP cses 1068 correctly", function()
-- Test CSES-specific command parsing
end)
end)
describe("action commands", function()
it("parses :CP test correctly", function()
-- Test test panel command
end)
it("parses :CP next correctly", function()
-- Test navigation command
end)
it("parses :CP prev correctly", function()
-- Test navigation command
end)
end)
describe("language flags", function()
it("parses --lang=cpp correctly", function()
-- Test language flag parsing
end)
it("parses --debug flag correctly", function()
-- Test debug flag parsing
end)
it("combines flags correctly", function()
-- Test multiple flag parsing
end)
end)
describe("error handling", function()
it("handles invalid commands gracefully", function()
-- Test error messages for bad commands
end)
it("provides helpful error messages", function()
-- Test error message quality
end)
end)
describe("command completion", function()
it("completes platform names", function()
-- Test tab completion for platforms
end)
it("completes problem IDs from cached contest", function()
-- Test problem ID completion
end)
it("completes action names", function()
-- Test action completion
end)
end)
end)

46
tests/config_spec.lua Normal file
View file

@ -0,0 +1,46 @@
-- Unit tests for configuration management
describe("cp.config", function()
local config
before_each(function()
config = require("cp.config")
end)
describe("setup", function()
it("returns default config when no user config provided", function()
-- Test default configuration values
end)
it("merges user config with defaults", function()
-- Test config merging behavior
end)
it("validates contest configurations", function()
-- Test contest config validation
end)
it("handles invalid config gracefully", function()
-- Test error handling for bad configs
end)
end)
describe("platform validation", function()
it("accepts valid platforms", function()
-- Test platform validation
end)
it("rejects invalid platforms", function()
-- Test platform rejection
end)
end)
describe("language configurations", function()
it("provides correct file extensions for languages", function()
-- Test language -> extension mappings
end)
it("provides correct compile commands", function()
-- Test compile command generation
end)
end)
end)

108
tests/execute_spec.lua Normal file
View file

@ -0,0 +1,108 @@
-- Unit tests for code compilation and execution
describe("cp.execute", function()
local execute
local temp_dir
local test_files = {}
before_each(function()
execute = require("cp.execute")
temp_dir = vim.fn.tempname()
vim.fn.mkdir(temp_dir, "p")
vim.api.nvim_set_current_dir(temp_dir)
-- Create sample source files for testing
test_files.cpp = temp_dir .. "/test.cpp"
test_files.python = temp_dir .. "/test.py"
test_files.rust = temp_dir .. "/test.rs"
-- Write simple test programs
vim.fn.writefile({
'#include <iostream>',
'int main() { std::cout << "Hello" << std::endl; return 0; }'
}, test_files.cpp)
vim.fn.writefile({
'print("Hello")'
}, test_files.python)
end)
after_each(function()
vim.fn.delete(temp_dir, "rf")
end)
describe("compilation", function()
it("compiles C++ code successfully", function()
-- Test C++ compilation
end)
it("compiles Rust code successfully", function()
-- Test Rust compilation
end)
it("handles compilation errors", function()
-- Test error handling for bad code
end)
it("applies optimization flags correctly", function()
-- Test optimization settings
end)
it("handles debug flag correctly", function()
-- Test debug compilation
end)
end)
describe("execution", function()
it("runs compiled programs", function()
-- Test program execution
end)
it("handles runtime errors", function()
-- Test runtime error handling
end)
it("enforces time limits", function()
-- Test timeout handling
end)
it("captures output correctly", function()
-- Test stdout/stderr capture
end)
it("handles large inputs/outputs", function()
-- Test large data handling
end)
end)
describe("test case execution", function()
it("runs single test case", function()
-- Test individual test case execution
end)
it("runs multiple test cases", function()
-- Test batch execution
end)
it("compares outputs correctly", function()
-- Test output comparison logic
end)
it("handles edge cases in output comparison", function()
-- Test whitespace, newlines, etc.
end)
end)
describe("platform-specific execution", function()
it("works on Linux", function()
-- Test Linux-specific behavior
end)
it("works on macOS", function()
-- Test macOS-specific behavior
end)
it("works on Windows", function()
-- Test Windows-specific behavior
end)
end)
end)

54
tests/health_spec.lua Normal file
View file

@ -0,0 +1,54 @@
-- Unit tests for health check functionality
describe("cp.health", function()
local health
before_each(function()
health = require("cp.health")
end)
describe("system checks", function()
it("detects Neovim version correctly", function()
-- Test Neovim version detection
end)
it("detects available compilers", function()
-- Test C++, Rust, etc. compiler detection
end)
it("detects Python installation", function()
-- Test Python availability
end)
it("checks for required external tools", function()
-- Test curl, wget, etc. availability
end)
end)
describe("configuration validation", function()
it("validates contest configurations", function()
-- Test config validation
end)
it("checks directory permissions", function()
-- Test write permissions for directories
end)
it("validates language configurations", function()
-- Test language setup validation
end)
end)
describe("health report generation", function()
it("generates comprehensive health report", function()
-- Test :checkhealth cp output
end)
it("provides actionable recommendations", function()
-- Test that health check gives useful advice
end)
it("handles partial functionality gracefully", function()
-- Test when some features are unavailable
end)
end)
end)

114
tests/integration_spec.lua Normal file
View file

@ -0,0 +1,114 @@
-- Integration tests for complete workflows
describe("cp.nvim integration", function()
local cp
local temp_dir
before_each(function()
cp = require("cp")
temp_dir = vim.fn.tempname()
vim.fn.mkdir(temp_dir, "p")
vim.api.nvim_set_current_dir(temp_dir)
-- Set up with minimal config
cp.setup({
scrapers = {}, -- Disable scraping for integration tests
contests = {
codeforces = {
dir = temp_dir,
url = "mock://codeforces.com",
languages = {
cpp = { extension = "cpp", compile = "g++ -o %s %s" }
}
}
}
})
end)
after_each(function()
vim.fn.delete(temp_dir, "rf")
vim.cmd("silent! %bwipeout!")
end)
describe("complete problem setup workflow", function()
it("handles :CP codeforces 1800 A workflow", function()
-- Test complete setup from command to file creation
-- 1. Parse command
-- 2. Set up directory structure
-- 3. Create source file
-- 4. Apply template
-- 5. Switch to buffer
end)
it("handles CSES workflow", function()
-- Test CSES-specific complete workflow
end)
it("handles language switching", function()
-- Test switching languages for same problem
end)
end)
describe("problem navigation workflow", function()
it("navigates between problems in contest", function()
-- Test :CP next/:CP prev workflow
-- Requires cached contest metadata
end)
it("maintains state across navigation", function()
-- Test that work isn't lost when switching problems
end)
end)
describe("test panel workflow", function()
it("handles complete testing workflow", function()
-- 1. Set up problem
-- 2. Write solution
-- 3. Open test panel (:CP test)
-- 4. Compile and run tests
-- 5. View results
-- 6. Close panel
end)
it("handles debug workflow", function()
-- Test :CP test --debug workflow
end)
end)
describe("file system integration", function()
it("maintains proper directory structure", function()
-- Test that files are organized correctly
end)
it("handles existing files appropriately", function()
-- Test behavior when problem already exists
end)
it("cleans up temporary files", function()
-- Test cleanup of build artifacts
end)
end)
describe("error recovery", function()
it("recovers from network failures gracefully", function()
-- Test behavior when scraping fails
end)
it("recovers from compilation failures", function()
-- Test error handling in compilation
end)
it("handles corrupted cache gracefully", function()
-- Test cache corruption recovery
end)
end)
describe("multi-session behavior", function()
it("persists state across Neovim restarts", function()
-- Test that contest/problem state persists
end)
it("handles concurrent usage", function()
-- Test multiple Neovim instances
end)
end)
end)

81
tests/problem_spec.lua Normal file
View file

@ -0,0 +1,81 @@
-- Unit tests for problem context and file management
describe("cp.problem", function()
local problem
local temp_dir
before_each(function()
problem = require("cp.problem")
temp_dir = vim.fn.tempname()
vim.fn.mkdir(temp_dir, "p")
-- Change to temp directory for testing
vim.api.nvim_set_current_dir(temp_dir)
end)
after_each(function()
vim.fn.delete(temp_dir, "rf")
end)
describe("context creation", function()
it("creates context for Codeforces problems", function()
-- Test context creation with proper paths
end)
it("creates context for CSES problems", function()
-- Test CSES-specific context
end)
it("generates correct file paths", function()
-- Test source file path generation
end)
it("generates correct build paths", function()
-- Test build directory structure
end)
end)
describe("template handling", function()
it("applies language templates correctly", function()
-- Test template application
end)
it("handles custom templates", function()
-- Test user-defined templates
end)
it("supports snippet integration", function()
-- Test LuaSnip integration
end)
end)
describe("file operations", function()
it("creates directory structure", function()
-- Test directory creation (build/, io/)
end)
it("handles existing files gracefully", function()
-- Test behavior when files exist
end)
it("sets up input/output files", function()
-- Test I/O file creation
end)
end)
describe("language support", function()
it("supports C++ compilation", function()
-- Test C++ setup and compilation
end)
it("supports Python execution", function()
-- Test Python setup
end)
it("supports Rust compilation", function()
-- Test Rust setup
end)
it("supports custom language configurations", function()
-- Test user-defined language support
end)
end)
end)

86
tests/scraper_spec.lua Normal file
View file

@ -0,0 +1,86 @@
-- Unit tests for web scraping functionality
describe("cp.scrape", function()
local scrape
local mock_responses = {}
before_each(function()
scrape = require("cp.scrape")
-- Mock HTTP responses for different platforms
mock_responses.codeforces_contest = [[
<div class="problems">
<div class="problem" data-problem-id="A">Problem A</div>
<div class="problem" data-problem-id="B">Problem B</div>
</div>
]]
mock_responses.codeforces_problem = [[
<div class="input">Sample Input</div>
<div class="output">Sample Output</div>
]]
end)
describe("contest metadata scraping", function()
it("scrapes Codeforces contest problems", function()
-- Mock HTTP request, test problem list extraction
end)
it("scrapes Atcoder contest problems", function()
-- Test Atcoder format
end)
it("scrapes CSES problem list", function()
-- Test CSES format
end)
it("handles network errors gracefully", function()
-- Test error handling for failed requests
end)
it("handles parsing errors gracefully", function()
-- Test error handling for malformed HTML
end)
end)
describe("problem scraping", function()
it("extracts test cases from Codeforces problems", function()
-- Test test case extraction
end)
it("handles multiple test cases correctly", function()
-- Test multiple sample inputs/outputs
end)
it("handles problems with no sample cases", function()
-- Test edge case handling
end)
it("extracts problem metadata (time limits, etc.)", function()
-- Test metadata extraction
end)
end)
describe("platform-specific parsing", function()
it("handles Codeforces HTML structure", function()
-- Test Codeforces-specific parsing
end)
it("handles Atcoder HTML structure", function()
-- Test Atcoder-specific parsing
end)
it("handles CSES HTML structure", function()
-- Test CSES-specific parsing
end)
end)
describe("rate limiting and caching", function()
it("respects rate limits", function()
-- Test rate limiting behavior
end)
it("uses cached results when appropriate", function()
-- Test caching integration
end)
end)
end)

106
tests/test_panel_spec.lua Normal file
View file

@ -0,0 +1,106 @@
-- UI/buffer tests for the interactive test panel
describe("cp test panel", function()
local cp
before_each(function()
cp = require("cp")
cp.setup()
-- Set up a clean Neovim environment
vim.cmd("silent! %bwipeout!")
end)
after_each(function()
-- Clean up test panel state
vim.cmd("silent! %bwipeout!")
end)
describe("panel creation", function()
it("creates test panel buffers", function()
-- Test buffer creation for tab, expected, actual views
end)
it("sets up correct window layout", function()
-- Test 3-pane layout creation
end)
it("applies correct buffer settings", function()
-- Test buffer options (buftype, filetype, etc.)
end)
it("sets up keymaps correctly", function()
-- Test navigation keymaps (Ctrl+N, Ctrl+P, q)
end)
end)
describe("test case display", function()
it("renders test case tabs correctly", function()
-- Test tab line rendering with status indicators
end)
it("displays input correctly", function()
-- Test input pane content
end)
it("displays expected output correctly", function()
-- Test expected output pane
end)
it("displays actual output correctly", function()
-- Test actual output pane
end)
it("shows diff when test fails", function()
-- Test diff mode activation
end)
end)
describe("navigation", function()
it("navigates to next test case", function()
-- Test Ctrl+N navigation
end)
it("navigates to previous test case", function()
-- Test Ctrl+P navigation
end)
it("wraps around at boundaries", function()
-- Test navigation wrapping
end)
it("updates display on navigation", function()
-- Test content updates when switching tests
end)
end)
describe("test execution integration", function()
it("compiles and runs tests automatically", function()
-- Test automatic compilation and execution
end)
it("updates results in real-time", function()
-- Test live result updates
end)
it("handles compilation failures", function()
-- Test error display when compilation fails
end)
it("shows execution time", function()
-- Test timing display
end)
end)
describe("session management", function()
it("saves and restores session correctly", function()
-- Test session save/restore when opening/closing panel
end)
it("handles multiple panels gracefully", function()
-- Test behavior with multiple test panels
end)
it("cleans up resources on close", function()
-- Test proper cleanup when closing panel
end)
end)
end)

64
uv.lock generated
View file

@ -100,6 +100,62 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload-time = "2024-09-15T18:07:37.964Z" },
]
[[package]]
name = "mypy"
version = "1.18.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "mypy-extensions" },
{ name = "pathspec" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/c0/77/8f0d0001ffad290cef2f7f216f96c814866248a0b92a722365ed54648e7e/mypy-1.18.2.tar.gz", hash = "sha256:06a398102a5f203d7477b2923dda3634c36727fa5c237d8f859ef90c42a9924b", size = 3448846, upload-time = "2025-09-19T00:11:10.519Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/88/87/cafd3ae563f88f94eec33f35ff722d043e09832ea8530ef149ec1efbaf08/mypy-1.18.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:807d9315ab9d464125aa9fcf6d84fde6e1dc67da0b6f80e7405506b8ac72bc7f", size = 12731198, upload-time = "2025-09-19T00:09:44.857Z" },
{ url = "https://files.pythonhosted.org/packages/0f/e0/1e96c3d4266a06d4b0197ace5356d67d937d8358e2ee3ffac71faa843724/mypy-1.18.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:776bb00de1778caf4db739c6e83919c1d85a448f71979b6a0edd774ea8399341", size = 11817879, upload-time = "2025-09-19T00:09:47.131Z" },
{ url = "https://files.pythonhosted.org/packages/72/ef/0c9ba89eb03453e76bdac5a78b08260a848c7bfc5d6603634774d9cd9525/mypy-1.18.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1379451880512ffce14505493bd9fe469e0697543717298242574882cf8cdb8d", size = 12427292, upload-time = "2025-09-19T00:10:22.472Z" },
{ url = "https://files.pythonhosted.org/packages/1a/52/ec4a061dd599eb8179d5411d99775bec2a20542505988f40fc2fee781068/mypy-1.18.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1331eb7fd110d60c24999893320967594ff84c38ac6d19e0a76c5fd809a84c86", size = 13163750, upload-time = "2025-09-19T00:09:51.472Z" },
{ url = "https://files.pythonhosted.org/packages/c4/5f/2cf2ceb3b36372d51568f2208c021870fe7834cf3186b653ac6446511839/mypy-1.18.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3ca30b50a51e7ba93b00422e486cbb124f1c56a535e20eff7b2d6ab72b3b2e37", size = 13351827, upload-time = "2025-09-19T00:09:58.311Z" },
{ url = "https://files.pythonhosted.org/packages/c8/7d/2697b930179e7277529eaaec1513f8de622818696857f689e4a5432e5e27/mypy-1.18.2-cp311-cp311-win_amd64.whl", hash = "sha256:664dc726e67fa54e14536f6e1224bcfce1d9e5ac02426d2326e2bb4e081d1ce8", size = 9757983, upload-time = "2025-09-19T00:10:09.071Z" },
{ url = "https://files.pythonhosted.org/packages/07/06/dfdd2bc60c66611dd8335f463818514733bc763e4760dee289dcc33df709/mypy-1.18.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:33eca32dd124b29400c31d7cf784e795b050ace0e1f91b8dc035672725617e34", size = 12908273, upload-time = "2025-09-19T00:10:58.321Z" },
{ url = "https://files.pythonhosted.org/packages/81/14/6a9de6d13a122d5608e1a04130724caf9170333ac5a924e10f670687d3eb/mypy-1.18.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a3c47adf30d65e89b2dcd2fa32f3aeb5e94ca970d2c15fcb25e297871c8e4764", size = 11920910, upload-time = "2025-09-19T00:10:20.043Z" },
{ url = "https://files.pythonhosted.org/packages/5f/a9/b29de53e42f18e8cc547e38daa9dfa132ffdc64f7250e353f5c8cdd44bee/mypy-1.18.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d6c838e831a062f5f29d11c9057c6009f60cb294fea33a98422688181fe2893", size = 12465585, upload-time = "2025-09-19T00:10:33.005Z" },
{ url = "https://files.pythonhosted.org/packages/77/ae/6c3d2c7c61ff21f2bee938c917616c92ebf852f015fb55917fd6e2811db2/mypy-1.18.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01199871b6110a2ce984bde85acd481232d17413868c9807e95c1b0739a58914", size = 13348562, upload-time = "2025-09-19T00:10:11.51Z" },
{ url = "https://files.pythonhosted.org/packages/4d/31/aec68ab3b4aebdf8f36d191b0685d99faa899ab990753ca0fee60fb99511/mypy-1.18.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a2afc0fa0b0e91b4599ddfe0f91e2c26c2b5a5ab263737e998d6817874c5f7c8", size = 13533296, upload-time = "2025-09-19T00:10:06.568Z" },
{ url = "https://files.pythonhosted.org/packages/9f/83/abcb3ad9478fca3ebeb6a5358bb0b22c95ea42b43b7789c7fb1297ca44f4/mypy-1.18.2-cp312-cp312-win_amd64.whl", hash = "sha256:d8068d0afe682c7c4897c0f7ce84ea77f6de953262b12d07038f4d296d547074", size = 9828828, upload-time = "2025-09-19T00:10:28.203Z" },
{ url = "https://files.pythonhosted.org/packages/5f/04/7f462e6fbba87a72bc8097b93f6842499c428a6ff0c81dd46948d175afe8/mypy-1.18.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:07b8b0f580ca6d289e69209ec9d3911b4a26e5abfde32228a288eb79df129fcc", size = 12898728, upload-time = "2025-09-19T00:10:01.33Z" },
{ url = "https://files.pythonhosted.org/packages/99/5b/61ed4efb64f1871b41fd0b82d29a64640f3516078f6c7905b68ab1ad8b13/mypy-1.18.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:ed4482847168439651d3feee5833ccedbf6657e964572706a2adb1f7fa4dfe2e", size = 11910758, upload-time = "2025-09-19T00:10:42.607Z" },
{ url = "https://files.pythonhosted.org/packages/3c/46/d297d4b683cc89a6e4108c4250a6a6b717f5fa96e1a30a7944a6da44da35/mypy-1.18.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c3ad2afadd1e9fea5cf99a45a822346971ede8685cc581ed9cd4d42eaf940986", size = 12475342, upload-time = "2025-09-19T00:11:00.371Z" },
{ url = "https://files.pythonhosted.org/packages/83/45/4798f4d00df13eae3bfdf726c9244bcb495ab5bd588c0eed93a2f2dd67f3/mypy-1.18.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a431a6f1ef14cf8c144c6b14793a23ec4eae3db28277c358136e79d7d062f62d", size = 13338709, upload-time = "2025-09-19T00:11:03.358Z" },
{ url = "https://files.pythonhosted.org/packages/d7/09/479f7358d9625172521a87a9271ddd2441e1dab16a09708f056e97007207/mypy-1.18.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7ab28cc197f1dd77a67e1c6f35cd1f8e8b73ed2217e4fc005f9e6a504e46e7ba", size = 13529806, upload-time = "2025-09-19T00:10:26.073Z" },
{ url = "https://files.pythonhosted.org/packages/71/cf/ac0f2c7e9d0ea3c75cd99dff7aec1c9df4a1376537cb90e4c882267ee7e9/mypy-1.18.2-cp313-cp313-win_amd64.whl", hash = "sha256:0e2785a84b34a72ba55fb5daf079a1003a34c05b22238da94fcae2bbe46f3544", size = 9833262, upload-time = "2025-09-19T00:10:40.035Z" },
{ url = "https://files.pythonhosted.org/packages/5a/0c/7d5300883da16f0063ae53996358758b2a2df2a09c72a5061fa79a1f5006/mypy-1.18.2-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:62f0e1e988ad41c2a110edde6c398383a889d95b36b3e60bcf155f5164c4fdce", size = 12893775, upload-time = "2025-09-19T00:10:03.814Z" },
{ url = "https://files.pythonhosted.org/packages/50/df/2cffbf25737bdb236f60c973edf62e3e7b4ee1c25b6878629e88e2cde967/mypy-1.18.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8795a039bab805ff0c1dfdb8cd3344642c2b99b8e439d057aba30850b8d3423d", size = 11936852, upload-time = "2025-09-19T00:10:51.631Z" },
{ url = "https://files.pythonhosted.org/packages/be/50/34059de13dd269227fb4a03be1faee6e2a4b04a2051c82ac0a0b5a773c9a/mypy-1.18.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6ca1e64b24a700ab5ce10133f7ccd956a04715463d30498e64ea8715236f9c9c", size = 12480242, upload-time = "2025-09-19T00:11:07.955Z" },
{ url = "https://files.pythonhosted.org/packages/5b/11/040983fad5132d85914c874a2836252bbc57832065548885b5bb5b0d4359/mypy-1.18.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d924eef3795cc89fecf6bedc6ed32b33ac13e8321344f6ddbf8ee89f706c05cb", size = 13326683, upload-time = "2025-09-19T00:09:55.572Z" },
{ url = "https://files.pythonhosted.org/packages/e9/ba/89b2901dd77414dd7a8c8729985832a5735053be15b744c18e4586e506ef/mypy-1.18.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:20c02215a080e3a2be3aa50506c67242df1c151eaba0dcbc1e4e557922a26075", size = 13514749, upload-time = "2025-09-19T00:10:44.827Z" },
{ url = "https://files.pythonhosted.org/packages/25/bc/cc98767cffd6b2928ba680f3e5bc969c4152bf7c2d83f92f5a504b92b0eb/mypy-1.18.2-cp314-cp314-win_amd64.whl", hash = "sha256:749b5f83198f1ca64345603118a6f01a4e99ad4bf9d103ddc5a3200cc4614adf", size = 9982959, upload-time = "2025-09-19T00:10:37.344Z" },
{ url = "https://files.pythonhosted.org/packages/87/e3/be76d87158ebafa0309946c4a73831974d4d6ab4f4ef40c3b53a385a66fd/mypy-1.18.2-py3-none-any.whl", hash = "sha256:22a1748707dd62b58d2ae53562ffc4d7f8bcc727e8ac7cbc69c053ddc874d47e", size = 2352367, upload-time = "2025-09-19T00:10:15.489Z" },
]
[[package]]
name = "mypy-extensions"
version = "1.1.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/6e/371856a3fb9d31ca8dac321cda606860fa4548858c0cc45d9d1d4ca2628b/mypy_extensions-1.1.0.tar.gz", hash = "sha256:52e68efc3284861e772bbcd66823fde5ae21fd2fdb51c62a211403730b916558", size = 6343, upload-time = "2025-04-22T14:54:24.164Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl", hash = "sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505", size = 4963, upload-time = "2025-04-22T14:54:22.983Z" },
]
[[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, upload-time = "2023-12-10T22:30:45Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/cc/20/ff623b09d963f88bfde16306a54e12ee5ea43e9b597108672ff3a408aad6/pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08", size = 31191, upload-time = "2023-12-10T22:30:43.14Z" },
]
[[package]]
name = "pyparsing"
version = "3.2.3"
@ -146,6 +202,11 @@ dependencies = [
{ name = "requests" },
]
[package.dev-dependencies]
dev = [
{ name = "mypy" },
]
[package.metadata]
requires-dist = [
{ name = "beautifulsoup4", specifier = ">=4.13.5" },
@ -153,6 +214,9 @@ requires-dist = [
{ name = "requests", specifier = ">=2.32.5" },
]
[package.metadata.requires-dev]
dev = [{ name = "mypy", specifier = ">=1.18.2" }]
[[package]]
name = "soupsieve"
version = "2.8"