From 5bf8c8960b7493a15b5fb198b23d26f4f6c6e57f Mon Sep 17 00:00:00 2001 From: Barrett Ruth Date: Thu, 18 Sep 2025 21:28:34 -0400 Subject: [PATCH] feat: qol improvements --- .github/workflows/ci.yml | 12 ++++ readme.md => README.md | 0 pyproject.toml | 5 ++ tests/cache_spec.lua | 55 ++++++++++++++++ tests/command_parsing_spec.lua | 79 +++++++++++++++++++++++ tests/config_spec.lua | 46 +++++++++++++ tests/execute_spec.lua | 108 +++++++++++++++++++++++++++++++ tests/health_spec.lua | 54 ++++++++++++++++ tests/integration_spec.lua | 114 +++++++++++++++++++++++++++++++++ tests/problem_spec.lua | 81 +++++++++++++++++++++++ tests/scraper_spec.lua | 86 +++++++++++++++++++++++++ tests/test_panel_spec.lua | 106 ++++++++++++++++++++++++++++++ uv.lock | 64 ++++++++++++++++++ 13 files changed, 810 insertions(+) rename readme.md => README.md (100%) create mode 100644 tests/cache_spec.lua create mode 100644 tests/command_parsing_spec.lua create mode 100644 tests/config_spec.lua create mode 100644 tests/execute_spec.lua create mode 100644 tests/health_spec.lua create mode 100644 tests/integration_spec.lua create mode 100644 tests/problem_spec.lua create mode 100644 tests/scraper_spec.lua create mode 100644 tests/test_panel_spec.lua diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ecde609..50c7b00 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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/ diff --git a/readme.md b/README.md similarity index 100% rename from readme.md rename to README.md diff --git a/pyproject.toml b/pyproject.toml index 6bd65d5..f14a8f4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,3 +9,8 @@ dependencies = [ "cloudscraper>=1.2.71", "requests>=2.32.5", ] + +[dependency-groups] +dev = [ + "mypy>=1.18.2", +] diff --git a/tests/cache_spec.lua b/tests/cache_spec.lua new file mode 100644 index 0000000..141b3be --- /dev/null +++ b/tests/cache_spec.lua @@ -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) \ No newline at end of file diff --git a/tests/command_parsing_spec.lua b/tests/command_parsing_spec.lua new file mode 100644 index 0000000..4c657f8 --- /dev/null +++ b/tests/command_parsing_spec.lua @@ -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) \ No newline at end of file diff --git a/tests/config_spec.lua b/tests/config_spec.lua new file mode 100644 index 0000000..2baf3af --- /dev/null +++ b/tests/config_spec.lua @@ -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) \ No newline at end of file diff --git a/tests/execute_spec.lua b/tests/execute_spec.lua new file mode 100644 index 0000000..c8260f6 --- /dev/null +++ b/tests/execute_spec.lua @@ -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 ', + '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) \ No newline at end of file diff --git a/tests/health_spec.lua b/tests/health_spec.lua new file mode 100644 index 0000000..503b87a --- /dev/null +++ b/tests/health_spec.lua @@ -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) \ No newline at end of file diff --git a/tests/integration_spec.lua b/tests/integration_spec.lua new file mode 100644 index 0000000..569de7e --- /dev/null +++ b/tests/integration_spec.lua @@ -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) \ No newline at end of file diff --git a/tests/problem_spec.lua b/tests/problem_spec.lua new file mode 100644 index 0000000..e9740d3 --- /dev/null +++ b/tests/problem_spec.lua @@ -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) \ No newline at end of file diff --git a/tests/scraper_spec.lua b/tests/scraper_spec.lua new file mode 100644 index 0000000..1c44e97 --- /dev/null +++ b/tests/scraper_spec.lua @@ -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 = [[ +
+
Problem A
+
Problem B
+
+ ]] + + mock_responses.codeforces_problem = [[ +
Sample Input
+
Sample Output
+ ]] + 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) \ No newline at end of file diff --git a/tests/test_panel_spec.lua b/tests/test_panel_spec.lua new file mode 100644 index 0000000..022c395 --- /dev/null +++ b/tests/test_panel_spec.lua @@ -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) \ No newline at end of file diff --git a/uv.lock b/uv.lock index c6a66b0..873bb31 100644 --- a/uv.lock +++ b/uv.lock @@ -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"