cpscribe 0.1.0__tar.gz → 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. {cpscribe-0.1.0 → cpscribe-0.1.2}/.github/workflows/ci.yml +2 -2
  2. {cpscribe-0.1.0 → cpscribe-0.1.2}/.github/workflows/release-trigger.yml +3 -3
  3. {cpscribe-0.1.0 → cpscribe-0.1.2}/.github/workflows/release.yml +7 -7
  4. {cpscribe-0.1.0 → cpscribe-0.1.2}/CHANGELOG.md +14 -0
  5. {cpscribe-0.1.0 → cpscribe-0.1.2}/PKG-INFO +1 -1
  6. {cpscribe-0.1.0 → cpscribe-0.1.2}/pyproject.toml +1 -1
  7. cpscribe-0.1.2/src/cpscribe/__init__.py +1 -0
  8. {cpscribe-0.1.0 → cpscribe-0.1.2}/src/cpscribe/cli.py +6 -2
  9. {cpscribe-0.1.0 → cpscribe-0.1.2}/src/cpscribe/generator.py +1 -1
  10. {cpscribe-0.1.0 → cpscribe-0.1.2}/src/cpscribe/scraper.py +10 -3
  11. cpscribe-0.1.0/src/cpscribe/__init__.py +0 -1
  12. {cpscribe-0.1.0 → cpscribe-0.1.2}/.githooks/pre-commit +0 -0
  13. {cpscribe-0.1.0 → cpscribe-0.1.2}/.github/CONTRIBUTING.md +0 -0
  14. {cpscribe-0.1.0 → cpscribe-0.1.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  15. {cpscribe-0.1.0 → cpscribe-0.1.2}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  16. {cpscribe-0.1.0 → cpscribe-0.1.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  17. {cpscribe-0.1.0 → cpscribe-0.1.2}/.gitignore +0 -0
  18. {cpscribe-0.1.0 → cpscribe-0.1.2}/LICENSE +0 -0
  19. {cpscribe-0.1.0 → cpscribe-0.1.2}/README.md +0 -0
  20. {cpscribe-0.1.0 → cpscribe-0.1.2}/src/cpscribe/__main__.py +0 -0
  21. {cpscribe-0.1.0 → cpscribe-0.1.2}/src/cpscribe/config.py +0 -0
  22. {cpscribe-0.1.0 → cpscribe-0.1.2}/tests/conftest.py +0 -0
  23. {cpscribe-0.1.0 → cpscribe-0.1.2}/tests/test_generator.py +0 -0
  24. {cpscribe-0.1.0 → cpscribe-0.1.2}/tests/test_scraper.py +0 -0
@@ -10,8 +10,8 @@ jobs:
10
10
  check:
11
11
  runs-on: ubuntu-latest
12
12
  steps:
13
- - uses: actions/checkout@v4
14
- - uses: actions/setup-python@v5
13
+ - uses: actions/checkout@v7
14
+ - uses: actions/setup-python@v6
15
15
  with:
16
16
  python-version: "3.12"
17
17
  - run: pip install -e ".[dev]" -q
@@ -18,7 +18,7 @@ jobs:
18
18
  steps:
19
19
  - name: Check write permission
20
20
  if: github.event_name == 'issue_comment'
21
- uses: actions/github-script@v8
21
+ uses: actions/github-script@v9
22
22
  with:
23
23
  script: |
24
24
  const { data } = await github.rest.repos.getCollaboratorPermissionLevel({
@@ -31,7 +31,7 @@ jobs:
31
31
  }
32
32
 
33
33
  - name: Checkout
34
- uses: actions/checkout@v6
34
+ uses: actions/checkout@v7
35
35
  with:
36
36
  fetch-depth: 0
37
37
  token: ${{ secrets.RELEASE_PAT }}
@@ -64,7 +64,7 @@ jobs:
64
64
 
65
65
  - name: React with rocket
66
66
  if: success() && github.event_name == 'issue_comment'
67
- uses: actions/github-script@v8
67
+ uses: actions/github-script@v9
68
68
  with:
69
69
  script: |
70
70
  await github.rest.reactions.createForIssueComment({
@@ -17,7 +17,7 @@ jobs:
17
17
  version: ${{ steps.version.outputs.version }}
18
18
 
19
19
  steps:
20
- - uses: actions/checkout@v6
20
+ - uses: actions/checkout@v7
21
21
 
22
22
  - name: Read version
23
23
  id: version
@@ -41,8 +41,8 @@ jobs:
41
41
  runs-on: ubuntu-latest
42
42
 
43
43
  steps:
44
- - uses: actions/checkout@v6
45
- - uses: actions/setup-python@v5
44
+ - uses: actions/checkout@v7
45
+ - uses: actions/setup-python@v6
46
46
  with:
47
47
  python-version: "3.12"
48
48
  - run: pip install -e ".[dev]" -q
@@ -62,9 +62,9 @@ jobs:
62
62
  id-token: write
63
63
 
64
64
  steps:
65
- - uses: actions/checkout@v6
65
+ - uses: actions/checkout@v7
66
66
 
67
- - uses: actions/setup-python@v5
67
+ - uses: actions/setup-python@v6
68
68
  with:
69
69
  python-version: "3.12"
70
70
 
@@ -92,7 +92,7 @@ jobs:
92
92
  runs-on: ubuntu-latest
93
93
 
94
94
  steps:
95
- - uses: actions/checkout@v6
95
+ - uses: actions/checkout@v7
96
96
  with:
97
97
  fetch-depth: 0
98
98
 
@@ -107,7 +107,7 @@ jobs:
107
107
 
108
108
  - name: Post release status to issue
109
109
  if: steps.ctx.outputs.issue != ''
110
- uses: actions/github-script@v8
110
+ uses: actions/github-script@v9
111
111
  env:
112
112
  COMMENT_ID: ${{ steps.ctx.outputs.comment }}
113
113
  ISSUE_NUM: ${{ steps.ctx.outputs.issue }}
@@ -5,6 +5,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.1.2] - 2026-06-26
9
+
10
+ ### Fixed
11
+
12
+ - CF URL comment lines are stripped from the solution file before embedding in the blog post
13
+
14
+ ## [0.1.1] - 2026-06-26
15
+
16
+ ### Fixed
17
+
18
+ - Crash on group and gym contest URLs (`/group/...`): scraper now uses the original URL instead of rewriting to `/problemset/problem/...`
19
+ - Clear error message when problem statement is not found (e.g. page requires login)
20
+ - Blog post link now correctly reflects the original URL rather than always pointing to `/problemset/`
21
+
8
22
  ## [0.1.0] - 2026-06-25
9
23
 
10
24
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cpscribe
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: Generate blog posts for competitive programming solutions
5
5
  Project-URL: Repository, https://github.com/shravanngoswamii/cpscribe
6
6
  Project-URL: Issues, https://github.com/shravanngoswamii/cpscribe/issues
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "cpscribe"
7
- version = "0.1.0"
7
+ version = "0.1.2"
8
8
  description = "Generate blog posts for competitive programming solutions"
9
9
  authors = [{ name = "Shravan Goswami", email = "contact@shravangoswami.com" }]
10
10
  license = { text = "MIT" }
@@ -0,0 +1 @@
1
+ __version__ = "0.1.2"
@@ -31,10 +31,14 @@ def cmd_post(args) -> None:
31
31
 
32
32
  if cpp_file is None:
33
33
  cpp_file = Path(f"{index}.cpp")
34
- cpp_code = cpp_file.read_text().strip() if cpp_file.exists() else "// paste your solution here"
34
+ if cpp_file.exists():
35
+ lines = [ln for ln in cpp_file.read_text().splitlines() if not scraper.CF_URL_RE.search(ln)]
36
+ cpp_code = "\n".join(lines).strip()
37
+ else:
38
+ cpp_code = "// paste your solution here"
35
39
 
36
40
  print(f"fetching CF{contest_id}{index}...")
37
- problem = scraper.scrape(contest_id, index)
41
+ problem = scraper.scrape(contest_id, index, url)
38
42
  print(f" {index}. {problem['name']} {problem['rating']} {problem['contest']}")
39
43
  print(f" {len(problem['samples'])} sample(s) {problem['time_lim']} {problem['mem_lim']}")
40
44
 
@@ -28,7 +28,7 @@ def build(
28
28
  contest = problem["contest"]
29
29
  note_block = f"\n\n### Note\n\n{problem['note']}" if problem.get("note") else ""
30
30
  pub_dt = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
31
- cf_url = f"https://codeforces.com/problemset/problem/{contest_id}/{index}"
31
+ cf_url = problem.get("url") or f"https://codeforces.com/problemset/problem/{contest_id}/{index}"
32
32
 
33
33
  return f"""\
34
34
  ---
@@ -67,14 +67,20 @@ def _sample_pre(div) -> str:
67
67
  return pre.get_text().strip()
68
68
 
69
69
 
70
- def scrape(contest_id: str, index: str) -> dict:
71
- url = f"https://codeforces.com/problemset/problem/{contest_id}/{index}"
72
- resp = cloudscraper.create_scraper().get(url, timeout=15)
70
+ def scrape(contest_id: str, index: str, page_url: str | None = None) -> dict:
71
+ if page_url is None:
72
+ page_url = f"https://codeforces.com/problemset/problem/{contest_id}/{index}"
73
+ resp = cloudscraper.create_scraper().get(page_url, timeout=15)
73
74
  if resp.status_code != 200:
74
75
  sys.exit(f"problem page returned {resp.status_code}")
75
76
 
76
77
  soup = BeautifulSoup(resp.content, "html.parser")
77
78
  ps = soup.find("div", class_="problem-statement")
79
+ if ps is None:
80
+ sys.exit(
81
+ "could not find problem statement -- "
82
+ "the page may require login (e.g. group or gym contests)"
83
+ )
78
84
 
79
85
  header = ps.find("div", class_="header")
80
86
  title_div = header.find("div", class_="title") if header else None
@@ -108,6 +114,7 @@ def scrape(contest_id: str, index: str) -> dict:
108
114
  "name": name,
109
115
  "rating": rating,
110
116
  "contest": contest,
117
+ "url": page_url,
111
118
  "time_lim": time_lim,
112
119
  "mem_lim": mem_lim,
113
120
  "body": _section_md(body_div),
@@ -1 +0,0 @@
1
- __version__ = "0.1.0"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes