Skip to content

Commit 726fdb5

Browse files
committed
Try putting minified library on the site and repo
1 parent 431552b commit 726fdb5

File tree

4 files changed

+382
-1
lines changed

4 files changed

+382
-1
lines changed

.github/workflows/verify.yml

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ on:
1111

1212
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
1313
permissions:
14-
contents: read
14+
contents: write
1515
pages: write
1616
id-token: write
1717

@@ -156,6 +156,10 @@ jobs:
156156
env:
157157
SRCDIR: .artifacts/Bundled-${{ runner.os }}
158158

159+
- name: Generate minified versions
160+
shell: bash
161+
run: python3 .verify-helper/scripts/generate_minified.py
162+
159163
- name: Set up competitive-verifier
160164
uses: adamant-pwn/actions/setup@patch-1
161165
with:
@@ -172,6 +176,9 @@ jobs:
172176
verify-result: ${{github.workspace}}/merged-result.json
173177
destination: _jekyll
174178
write-summary: true
179+
- name: Inject minified code into documentation
180+
shell: bash
181+
run: python3 .verify-helper/scripts/inject_minified_docs.py
175182
- name: Save result
176183
uses: actions/cache/save@v4
177184
with:
@@ -191,6 +198,19 @@ jobs:
191198
with:
192199
path: _site
193200

201+
- name: Commit minified versions
202+
shell: bash
203+
run: |
204+
if [ -n "$(git status cp-algo/min/ --porcelain)" ]; then
205+
git config user.name "github-actions[bot]"
206+
git config user.email "github-actions[bot]@users.noreply.github.com"
207+
git add cp-algo/min/
208+
git commit -m "chore: update minified library versions"
209+
git push
210+
else
211+
echo "No changes to minified versions"
212+
fi
213+
194214
- name: Check
195215
uses: competitive-verifier/actions/check@v2
196216
with:

.verify-helper/scripts/README.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Minified Versions Setup
2+
3+
This directory contains scripts to generate and manage minified code versions for the library.
4+
5+
## Files
6+
7+
- `generate_minified.py`: Generates minified versions from source files
8+
- `inject_minified_docs.py`: Injects minified code into documentation markdown files
9+
10+
## How it works
11+
12+
1. **generate_minified.py**: Reads all files from `cp-algo/` and creates minified versions in:
13+
- `.competitive-verifier/minified/cp-algo/` (for CI/documentation)
14+
- `cp-algo/min/` (committed to repo for direct access)
15+
2. **inject_minified_docs.py**: Takes the minified files from `cp-algo/min/` and adds them to the documentation front matter as `minifiedCode` fields
16+
3. The Jekyll template in `_includes/document_body.html` displays the minified code when available
17+
18+
## GitHub Actions Integration
19+
20+
The workflow in `.github/workflows/verify.yml` automatically:
21+
1. Generates bundled files (via `oj-resolve`)
22+
2. Calls `generate_minified.py` to create minified versions in both locations
23+
3. Calls `inject_minified_docs.py` to add them to documentation
24+
4. Commits and pushes the `cp-algo/min/` directory updates to the repository
25+
5. Builds the Jekyll site with all three code versions displayed
26+
27+
## Website Display
28+
29+
On the library website (https://lib.cp-algorithms.com), each file will now show three code tabs:
30+
- **default**: Original source code
31+
- **bundled**: Bundled version with all dependencies inlined
32+
- **minified**: Minified version (typically 35-50% size reduction from source)
33+
34+
## Local Usage
35+
36+
After cloning the repository, you can directly use minified versions from the `cp-algo/min/` directory:
37+
38+
```bash
39+
# Copy minified version of a file
40+
cp cp-algo/min/graph/mst.hpp your_solution.hpp
41+
42+
# Or use it inline in competitive programming
43+
#include "cp-algo/min/graph/mst.hpp"
44+
```
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Generate minified versions of source library files.
4+
This script processes source files from cp-algo/ and creates
5+
minified versions in two places:
6+
1. .competitive-verifier/minified/ (for CI/documentation)
7+
2. cp-algo/min/ (committed to repo for direct access)
8+
9+
Unlike bundled versions, minified versions preserve the original file structure
10+
without inlining dependencies.
11+
"""
12+
13+
import os
14+
import re
15+
import sys
16+
from pathlib import Path
17+
18+
19+
def minify_cpp(code):
20+
"""Minify C++ code while preserving header guards and removing unnecessary whitespace."""
21+
lines = code.split('\n')
22+
result = []
23+
in_multiline_comment = False
24+
header_guard = None
25+
endif_lines = []
26+
27+
for i, line in enumerate(lines):
28+
stripped = line.strip()
29+
30+
# Handle multiline comments
31+
if '/*' in stripped:
32+
in_multiline_comment = True
33+
if '*/' in stripped:
34+
in_multiline_comment = False
35+
continue
36+
if in_multiline_comment:
37+
continue
38+
39+
# Remove single-line comments
40+
if '//' in stripped:
41+
stripped = stripped.split('//')[0].strip()
42+
43+
# Skip empty lines
44+
if not stripped:
45+
continue
46+
47+
# Detect and preserve header guards
48+
if stripped.startswith('#ifndef '):
49+
header_guard = stripped
50+
result.append(stripped)
51+
continue
52+
elif stripped.startswith('#define ') and header_guard and len(result) == 1:
53+
result.append(stripped)
54+
continue
55+
elif stripped == '#endif' and header_guard:
56+
endif_lines.append(stripped)
57+
continue
58+
59+
# Keep preprocessor directives as-is (they need to be on own lines)
60+
if stripped.startswith('#'):
61+
result.append(stripped)
62+
continue
63+
64+
# Compress spaces in code lines (but preserve strings)
65+
def compress_line(line):
66+
# Split by strings to preserve their content
67+
parts = re.split(r'("(?:[^"\\]|\\.)*")', line)
68+
for i in range(0, len(parts), 2):
69+
# Compress multiple spaces to one
70+
parts[i] = re.sub(r'\s+', ' ', parts[i])
71+
# Remove spaces around operators (but keep space for clarity in some cases)
72+
parts[i] = re.sub(r'\s*([+\-*/%=<>!&|^~,;:?(){}[\]])\s*', r'\1', parts[i])
73+
# Remove leading/trailing spaces
74+
parts[i] = parts[i].strip()
75+
return ''.join(parts)
76+
77+
compressed = compress_line(stripped)
78+
if compressed:
79+
result.append(compressed)
80+
81+
# Join lines - try to put on same line when possible
82+
code = '\n'.join(result)
83+
84+
# Remove newlines after opening braces and before closing braces
85+
code = re.sub(r'\{\n', '{', code)
86+
code = re.sub(r'\n\}', '}', code)
87+
88+
# Remove newlines around colons in class/struct definitions
89+
code = re.sub(r'\n:', ':', code)
90+
code = re.sub(r':\n', ':', code)
91+
92+
# Remove multiple consecutive newlines (keep max 1)
93+
code = re.sub(r'\n\n+', '\n', code)
94+
95+
# Add back endif if we had header guards
96+
if endif_lines:
97+
code = code + '\n' + '\n'.join(endif_lines)
98+
99+
return code
100+
101+
102+
def process_file(bundled_path, minified_path, committed_path=None):
103+
"""Process a single file and create minified version(s)."""
104+
try:
105+
with open(bundled_path, 'r', encoding='utf-8') as f:
106+
code = f.read()
107+
108+
minified_code = minify_cpp(code)
109+
110+
# Create minified version in .competitive-verifier/minified/
111+
minified_path.parent.mkdir(parents=True, exist_ok=True)
112+
with open(minified_path, 'w', encoding='utf-8') as f:
113+
f.write(minified_code)
114+
115+
# Also create in committed minified/ directory if path provided
116+
if committed_path:
117+
committed_path.parent.mkdir(parents=True, exist_ok=True)
118+
with open(committed_path, 'w', encoding='utf-8') as f:
119+
f.write(minified_code)
120+
121+
original_size = len(code)
122+
minified_size = len(minified_code)
123+
reduction = original_size - minified_size
124+
reduction_pct = 100 * (1 - minified_size / original_size) if original_size > 0 else 0
125+
126+
print(f" {bundled_path.name}: {original_size:,}{minified_size:,} bytes (-{reduction_pct:.1f}%)")
127+
return True
128+
except Exception as e:
129+
print(f" ERROR processing {bundled_path}: {e}", file=sys.stderr)
130+
return False
131+
132+
133+
def main():
134+
# Source directory to minify
135+
source_dir = Path('cp-algo')
136+
137+
# Output directories
138+
minified_ci_dir = Path('.competitive-verifier/minified')
139+
minified_committed_dir = Path('cp-algo/min')
140+
141+
# Verify source directory exists
142+
if not source_dir.exists():
143+
print(f"Error: {source_dir} does not exist", file=sys.stderr)
144+
sys.exit(1)
145+
146+
# Clear output directories
147+
if minified_ci_dir.exists():
148+
import shutil
149+
shutil.rmtree(minified_ci_dir)
150+
151+
minified_ci_dir.mkdir(parents=True, exist_ok=True)
152+
minified_committed_dir.mkdir(parents=True, exist_ok=True)
153+
154+
print("Generating minified versions from source files...")
155+
156+
total_files = 0
157+
processed_files = 0
158+
159+
# Process all source files in cp-algo (but not in cp-algo/min itself)
160+
for src_file in source_dir.rglob('*'):
161+
# Skip files in min directory
162+
if 'min' in src_file.parts:
163+
continue
164+
165+
if src_file.is_file() and src_file.suffix in ['.hpp', '.cpp', '.h']:
166+
total_files += 1
167+
168+
# Calculate relative path within cp-algo
169+
rel_path = src_file.relative_to(source_dir)
170+
171+
# Output paths
172+
minified_ci_file = minified_ci_dir / 'cp-algo' / rel_path
173+
minified_committed_file = minified_committed_dir / rel_path
174+
175+
if process_file(src_file, minified_ci_file, minified_committed_file):
176+
processed_files += 1
177+
178+
print(f"\nProcessed {processed_files}/{total_files} files")
179+
print(f"Generated in:")
180+
print(f" - .competitive-verifier/minified/cp-algo/")
181+
print(f" - cp-algo/min/")
182+
183+
if processed_files < total_files and total_files > 0:
184+
sys.exit(1)
185+
186+
187+
if __name__ == '__main__':
188+
main()

0 commit comments

Comments
 (0)