diff --git a/doc/cp.txt b/doc/cp.txt index 6b06dd9..e4fc5a7 100644 --- a/doc/cp.txt +++ b/doc/cp.txt @@ -290,6 +290,10 @@ Usage examples: > for multi-test case problems commonly found in contests. + AtCoder Heuristic Contests (AHC) are excluded + from the contest list as they don't have + standard sample test cases. + Codeforces ~ *cp-codeforces* URL format: https://codeforces.com/contest/1234/problem/A diff --git a/lua/cp/pickers/fzf_lua.lua b/lua/cp/pickers/fzf_lua.lua index 8ca106d..d8acce9 100644 --- a/lua/cp/pickers/fzf_lua.lua +++ b/lua/cp/pickers/fzf_lua.lua @@ -8,9 +8,13 @@ local function problem_picker(platform, contest_id) if #problems == 0 then vim.notify( - ('No problems found for contest: %s %s'):format(platform_display_name, contest_id), + ("Contest %s %s hasn't started yet or has no available problems"):format( + platform_display_name, + contest_id + ), vim.log.levels.WARN ) + contest_picker(platform) return end diff --git a/lua/cp/pickers/init.lua b/lua/cp/pickers/init.lua index 947210a..f8cac85 100644 --- a/lua/cp/pickers/init.lua +++ b/lua/cp/pickers/init.lua @@ -59,6 +59,8 @@ local function get_contests_for_platform(platform) 'contests', } + logger.progress(('running: %s'):format(table.concat(cmd, ' '))) + local result = vim .system(cmd, { cwd = plugin_path, @@ -67,6 +69,11 @@ local function get_contests_for_platform(platform) }) :wait() + logger.progress(('exit code: %d, stdout length: %d'):format(result.code, #(result.stdout or ''))) + if result.stderr and #result.stderr > 0 then + logger.progress(('stderr: %s'):format(result.stderr:sub(1, 200))) + end + if result.code ~= 0 then logger.log( ('Failed to load contests: %s'):format(result.stderr or 'unknown error'), @@ -75,9 +82,18 @@ local function get_contests_for_platform(platform) return {} end + logger.progress(('stdout preview: %s'):format(result.stdout:sub(1, 100))) + local ok, data = pcall(vim.json.decode, result.stdout) - if not ok or not data.success then - logger.log('Failed to parse contest data', vim.log.levels.ERROR) + if not ok then + logger.log(('JSON parse error: %s'):format(tostring(data)), vim.log.levels.ERROR) + return {} + end + if not data.success then + logger.log( + ('Scraper returned success=false: %s'):format(data.error or 'no error message'), + vim.log.levels.ERROR + ) return {} end @@ -151,10 +167,14 @@ local function get_problems_for_contest(platform, contest_id) end local ok, data = pcall(vim.json.decode, result.stdout) - if not ok or not data.success then + if not ok then logger.log('Failed to parse contest data', vim.log.levels.ERROR) return problems end + if not data.success then + logger.log(data.error or 'Contest scraping failed', vim.log.levels.ERROR) + return problems + end if not data.problems or #data.problems == 0 then logger.log('Contest has no problems available', vim.log.levels.WARN) diff --git a/lua/cp/pickers/telescope.lua b/lua/cp/pickers/telescope.lua index 1417cc3..21350bd 100644 --- a/lua/cp/pickers/telescope.lua +++ b/lua/cp/pickers/telescope.lua @@ -13,9 +13,13 @@ local function problem_picker(opts, platform, contest_id) if #problems == 0 then vim.notify( - ('No problems found for contest: %s %s'):format(platform_display_name, contest_id), + ("Contest %s %s hasn't started yet or has no available problems"):format( + platform_display_name, + contest_id + ), vim.log.levels.WARN ) + contest_picker(opts, platform) return end diff --git a/scrapers/__init__.py b/scrapers/__init__.py index f0cfd45..2babd81 100644 --- a/scrapers/__init__.py +++ b/scrapers/__init__.py @@ -1,15 +1,45 @@ -from .atcoder import AtCoderScraper -from .base import BaseScraper, ScraperConfig -from .codeforces import CodeforcesScraper -from .cses import CSESScraper -from .models import ( - ContestListResult, - ContestSummary, - MetadataResult, - ProblemSummary, - TestCase, - TestsResult, -) +# Lazy imports to avoid module loading conflicts when running scrapers with -m +def __getattr__(name): + if name == "AtCoderScraper": + from .atcoder import AtCoderScraper + + return AtCoderScraper + elif name == "BaseScraper": + from .base import BaseScraper + + return BaseScraper + elif name == "ScraperConfig": + from .base import ScraperConfig + + return ScraperConfig + elif name == "CodeforcesScraper": + from .codeforces import CodeforcesScraper + + return CodeforcesScraper + elif name == "CSESScraper": + from .cses import CSESScraper + + return CSESScraper + elif name in [ + "ContestListResult", + "ContestSummary", + "MetadataResult", + "ProblemSummary", + "TestCase", + "TestsResult", + ]: + from .models import ( + ContestListResult, + ContestSummary, + MetadataResult, + ProblemSummary, + TestCase, + TestsResult, + ) + + return locals()[name] + raise AttributeError(f"module 'scrapers' has no attribute '{name}'") + __all__ = [ "AtCoderScraper", diff --git a/scrapers/atcoder.py b/scrapers/atcoder.py index 20cc3d3..cd72613 100644 --- a/scrapers/atcoder.py +++ b/scrapers/atcoder.py @@ -272,7 +272,11 @@ def scrape_contests() -> list[ContestSummary]: r"[\uff01-\uff5e]", lambda m: chr(ord(m.group()) - 0xFEE0), name ) - contests.append(ContestSummary(id=contest_id, name=name, display_name=name)) + # Skip AtCoder Heuristic Contests (AHC) as they don't have standard sample tests + if not contest_id.startswith("ahc"): + contests.append( + ContestSummary(id=contest_id, name=name, display_name=name) + ) return contests diff --git a/tests/scrapers/test_atcoder.py b/tests/scrapers/test_atcoder.py index dcde406..dc8b591 100644 --- a/tests/scrapers/test_atcoder.py +++ b/tests/scrapers/test_atcoder.py @@ -129,3 +129,71 @@ def test_scrape_contests_network_error(mocker): result = scrape_contests() assert result == [] + + +def test_scrape_contests_filters_ahc(mocker): + def mock_get_side_effect(url, **kwargs): + if url == "https://atcoder.jp/contests/archive": + mock_response = Mock() + mock_response.raise_for_status.return_value = None + mock_response.text = """ + +
| 2025-01-15 21:00:00+0900 | +AtCoder Beginner Contest 350 | +01:40 | +- 1999 | +
| 2025-01-14 21:00:00+0900 | +AtCoder Heuristic Contest 044 | +05:00 | +- | +
| 2025-01-13 21:00:00+0900 | +AtCoder Regular Contest 170 | +02:00 | +1000 - 2799 | +