jekyll-theme-zer0 0.10.6 → 0.15.2

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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +428 -0
  3. data/README.md +79 -31
  4. data/_data/README.md +419 -17
  5. data/_data/generate_statistics.rb +216 -9
  6. data/_data/generate_statistics.sh +106 -0
  7. data/_data/github-actions-example.yml +210 -0
  8. data/_data/navigation/about.yml +39 -11
  9. data/_data/navigation/docs.yml +53 -23
  10. data/_data/navigation/home.yml +27 -9
  11. data/_data/navigation/main.yml +27 -8
  12. data/_data/navigation/posts.yml +22 -6
  13. data/_data/navigation/quickstart.yml +19 -6
  14. data/_data/posts_organization.yml +153 -0
  15. data/_data/prerequisites.yml +112 -0
  16. data/_data/statistics_config.yml +203 -0
  17. data/_data/ui-text.yml +321 -0
  18. data/_data/update_statistics.sh +126 -0
  19. data/_includes/README.md +2 -0
  20. data/_includes/components/js-cdn.html +4 -1
  21. data/_includes/components/post-card.html +2 -11
  22. data/_includes/components/preview-image.html +32 -0
  23. data/_includes/content/intro.html +9 -10
  24. data/_includes/core/header.html +14 -0
  25. data/_includes/navigation/sidebar-categories.html +20 -9
  26. data/_includes/navigation/sidebar-folders.html +8 -7
  27. data/_includes/navigation/sidebar-right.html +16 -10
  28. data/_layouts/blog.html +15 -45
  29. data/_layouts/category.html +4 -24
  30. data/_layouts/collection.html +2 -12
  31. data/_layouts/default.html +1 -1
  32. data/_layouts/journals.html +2 -12
  33. data/_layouts/notebook.html +296 -0
  34. data/_sass/core/_docs.scss +1 -1
  35. data/_sass/custom.scss +54 -17
  36. data/_sass/notebooks.scss +458 -0
  37. data/assets/images/notebooks/test-notebook_files/test-notebook_4_0.png +0 -0
  38. data/assets/js/sidebar.js +511 -0
  39. data/scripts/README.md +131 -105
  40. data/scripts/analyze-commits.sh +9 -311
  41. data/scripts/bin/build +22 -22
  42. data/scripts/build +7 -111
  43. data/scripts/convert-notebooks.sh +415 -0
  44. data/scripts/features/validate_preview_urls.py +500 -0
  45. data/scripts/fix-markdown-format.sh +8 -262
  46. data/scripts/generate-preview-images.sh +7 -787
  47. data/scripts/install-preview-generator.sh +8 -528
  48. data/scripts/lib/README.md +5 -5
  49. data/scripts/lib/changelog.sh +89 -57
  50. data/scripts/lib/gem.sh +19 -7
  51. data/scripts/release +7 -236
  52. data/scripts/setup.sh +9 -153
  53. data/scripts/test/lib/run_tests.sh +1 -2
  54. data/scripts/test-auto-version.sh +7 -256
  55. data/scripts/test-mermaid.sh +7 -287
  56. data/scripts/test.sh +9 -154
  57. metadata +16 -10
  58. data/scripts/features/preview_generator.py +0 -646
  59. data/scripts/lib/test/run_tests.sh +0 -140
  60. data/scripts/lib/test/test_changelog.sh +0 -87
  61. data/scripts/lib/test/test_gem.sh +0 -68
  62. data/scripts/lib/test/test_git.sh +0 -82
  63. data/scripts/lib/test/test_validation.sh +0 -72
  64. data/scripts/lib/test/test_version.sh +0 -96
  65. data/scripts/version.sh +0 -178
data/scripts/build CHANGED
@@ -1,115 +1,11 @@
1
1
  #!/bin/bash
2
2
 
3
- # Simplified build command for zer0-mistakes Jekyll theme
4
- # Usage: ./scripts/build [options]
5
- #
6
- # Quick gem building without the full release workflow.
3
+ # ============================================================================
4
+ # WRAPPER: This script forwards to scripts/bin/build
5
+ #
6
+ # The canonical location is scripts/bin/build. This wrapper exists for
7
+ # backward compatibility with existing workflows and VS Code tasks.
8
+ # ============================================================================
7
9
 
8
- set -euo pipefail
9
-
10
- # Get script and library directories
11
10
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
12
- LIB_DIR="$SCRIPT_DIR/lib"
13
-
14
- # Source required libraries
15
- source "$LIB_DIR/common.sh"
16
- source "$LIB_DIR/validation.sh"
17
- source "$LIB_DIR/version.sh"
18
- source "$LIB_DIR/gem.sh"
19
-
20
- # Parse arguments
21
- while [[ $# -gt 0 ]]; do
22
- case $1 in
23
- --dry-run)
24
- export DRY_RUN=true
25
- shift
26
- ;;
27
- --verbose)
28
- export VERBOSE=true
29
- shift
30
- ;;
31
- --help|-h)
32
- show_usage
33
- exit 0
34
- ;;
35
- *)
36
- error "Unknown option: $1 (use --help for usage)"
37
- ;;
38
- esac
39
- done
40
-
41
- # Show usage
42
- show_usage() {
43
- cat << EOF
44
- 🔨 Simplified Build Command for zer0-mistakes
45
-
46
- USAGE:
47
- ./scripts/build [OPTIONS]
48
-
49
- DESCRIPTION:
50
- Builds the Jekyll theme gem without running the full release workflow.
51
- Useful for testing gem packaging or preparing for manual publication.
52
-
53
- OPTIONS:
54
- --dry-run Preview build without creating files
55
- --verbose Show detailed debug output
56
- --help, -h Show this help message
57
-
58
- EXAMPLES:
59
- ./scripts/build # Build gem with current version
60
- ./scripts/build --dry-run # Preview what would be built
61
- ./scripts/build --verbose # Build with debug output
62
-
63
- ENVIRONMENT VARIABLES:
64
- DRY_RUN=true Enable dry run mode
65
- VERBOSE=true Enable debug output
66
-
67
- OUTPUT:
68
- Creates: jekyll-theme-zer0-VERSION.gem in current directory
69
-
70
- For full release workflow, use: ./scripts/release
71
- EOF
72
- }
73
-
74
- # Main build workflow
75
- main() {
76
- print_header "🔨 Gem Build"
77
-
78
- # Step 1: Basic validation (no RubyGems auth needed for build)
79
- step "Validating environment..."
80
- validate_git_repo
81
- validate_required_files
82
- validate_gemspec
83
- success "Environment validated"
84
- echo ""
85
-
86
- # Step 2: Get current version
87
- step "Reading version..."
88
- local version
89
- version=$(get_current_version)
90
- info "Current version: $version"
91
- info "Gem file: jekyll-theme-zer0-${version}.gem"
92
- echo ""
93
-
94
- # Step 3: Build gem
95
- build_gem "$version"
96
- echo ""
97
-
98
- # Success summary
99
- if [[ "$DRY_RUN" != "true" ]]; then
100
- print_summary "Build Complete" \
101
- "✅ Built: jekyll-theme-zer0-${version}.gem" \
102
- "" \
103
- "Next steps:" \
104
- " • Test: gem install jekyll-theme-zer0-${version}.gem" \
105
- " • Publish: gem push jekyll-theme-zer0-${version}.gem" \
106
- " • Or use: ./scripts/release for full workflow"
107
- else
108
- info "Dry run complete - no files created"
109
- fi
110
-
111
- success "Build completed successfully!"
112
- }
113
-
114
- # Run main function
115
- main "$@"
11
+ exec "$SCRIPT_DIR/bin/build" "$@"
@@ -0,0 +1,415 @@
1
+ #!/bin/bash
2
+ #
3
+ # Script Name: convert-notebooks.sh
4
+ # Description: Converts Jupyter notebooks (.ipynb) to Jekyll-compatible Markdown
5
+ # Adds proper front matter, extracts images, and prepares for Jekyll build
6
+ #
7
+ # Usage: ./scripts/convert-notebooks.sh [options]
8
+ #
9
+ # Options:
10
+ # -h, --help Show this help message
11
+ # -d, --dry-run Preview what would be converted (no actual changes)
12
+ # -v, --verbose Enable verbose output
13
+ # -f, --file FILE Convert a specific notebook file only
14
+ # --output-dir DIR Output directory for converted files (default: pages/_notebooks)
15
+ # --image-dir DIR Output directory for extracted images (default: assets/images/notebooks)
16
+ # --force Reconvert notebooks even if .md exists
17
+ # --clean Remove converted .md files before converting
18
+ # --list Only list notebooks to be converted
19
+ #
20
+ # Dependencies:
21
+ # - bash 4.0+
22
+ # - python3 with jupyter nbconvert installed
23
+ # - yq or python (for YAML parsing)
24
+ #
25
+ # Environment Variables:
26
+ # NOTEBOOKS_DIR Source directory for .ipynb files (default: pages/_notebooks)
27
+ # OUTPUT_DIR Output directory for .md files (default: pages/_notebooks)
28
+ # IMAGE_DIR Output directory for images (default: assets/images/notebooks)
29
+ #
30
+ # Examples:
31
+ # ./scripts/convert-notebooks.sh --dry-run
32
+ # ./scripts/convert-notebooks.sh --file pages/_notebooks/my-notebook.ipynb
33
+ # ./scripts/convert-notebooks.sh --verbose --force
34
+ # ./scripts/convert-notebooks.sh --clean
35
+ #
36
+
37
+ set -euo pipefail
38
+
39
+ # Get script directory and source common utilities
40
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
41
+ PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
42
+
43
+ # Load environment variables from .env file if it exists
44
+ if [[ -f "$PROJECT_ROOT/.env" ]]; then
45
+ while IFS='=' read -r key value; do
46
+ [[ -z "$key" || "$key" =~ ^# ]] && continue
47
+ value="${value%\"}"
48
+ value="${value#\"}"
49
+ value="${value%\'}"
50
+ value="${value#\'}"
51
+ export "$key=$value"
52
+ done < "$PROJECT_ROOT/.env"
53
+ fi
54
+
55
+ # Source common library if available
56
+ if [[ -f "$SCRIPT_DIR/lib/common.sh" ]]; then
57
+ source "$SCRIPT_DIR/lib/common.sh"
58
+ else
59
+ # Fallback logging functions
60
+ RED='\033[0;31m'
61
+ GREEN='\033[0;32m'
62
+ YELLOW='\033[1;33m'
63
+ BLUE='\033[0;34m'
64
+ CYAN='\033[0;36m'
65
+ NC='\033[0m'
66
+
67
+ log() { echo -e "${GREEN}[LOG]${NC} $1"; }
68
+ info() { echo -e "${BLUE}[INFO]${NC} $1"; }
69
+ step() { echo -e "${CYAN}[STEP]${NC} $1"; }
70
+ success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
71
+ warn() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
72
+ error() { echo -e "${RED}[ERROR]${NC} $1" >&2; exit 1; }
73
+ fi
74
+
75
+ # Default configuration
76
+ NOTEBOOKS_DIR="${NOTEBOOKS_DIR:-pages/_notebooks}"
77
+ OUTPUT_DIR="${OUTPUT_DIR:-pages/_notebooks}"
78
+ IMAGE_DIR="${IMAGE_DIR:-assets/images/notebooks}"
79
+ DRY_RUN=false
80
+ VERBOSE=false
81
+ FORCE=false
82
+ CLEAN=false
83
+ LIST_ONLY=false
84
+ SPECIFIC_FILE=""
85
+
86
+ # Parse command line arguments
87
+ show_help() {
88
+ head -n 40 "$0" | grep "^#" | sed 's/^# //; s/^#//'
89
+ }
90
+
91
+ while [[ $# -gt 0 ]]; do
92
+ case $1 in
93
+ -h|--help)
94
+ show_help
95
+ exit 0
96
+ ;;
97
+ -d|--dry-run)
98
+ DRY_RUN=true
99
+ shift
100
+ ;;
101
+ -v|--verbose)
102
+ VERBOSE=true
103
+ shift
104
+ ;;
105
+ -f|--file)
106
+ SPECIFIC_FILE="$2"
107
+ shift 2
108
+ ;;
109
+ --output-dir)
110
+ OUTPUT_DIR="$2"
111
+ shift 2
112
+ ;;
113
+ --image-dir)
114
+ IMAGE_DIR="$2"
115
+ shift 2
116
+ ;;
117
+ --force)
118
+ FORCE=true
119
+ shift
120
+ ;;
121
+ --clean)
122
+ CLEAN=true
123
+ shift
124
+ ;;
125
+ --list)
126
+ LIST_ONLY=true
127
+ shift
128
+ ;;
129
+ *)
130
+ error "Unknown option: $1"
131
+ ;;
132
+ esac
133
+ done
134
+
135
+ # Validate dependencies
136
+ check_dependencies() {
137
+ step "Checking dependencies..."
138
+
139
+ if ! command -v python3 &> /dev/null; then
140
+ error "python3 is not installed"
141
+ fi
142
+
143
+ if ! python3 -c "import nbconvert" 2>/dev/null; then
144
+ error "jupyter nbconvert is not installed. Install with: pip3 install jupyter nbconvert"
145
+ fi
146
+
147
+ success "All dependencies satisfied"
148
+ }
149
+
150
+ # Extract front matter from notebook metadata or filename
151
+ extract_front_matter() {
152
+ local notebook_file="$1"
153
+ local basename
154
+ basename=$(basename "$notebook_file" .ipynb)
155
+
156
+ # Try to extract metadata from notebook JSON
157
+ local title description date
158
+
159
+ # Use Python to parse notebook and extract metadata
160
+ # Output as JSON to avoid delimiter issues with multiline content
161
+ local metadata_json
162
+ metadata_json=$(python3 -c "
163
+ import json
164
+ from datetime import datetime
165
+
166
+ with open('${notebook_file}', 'r') as f:
167
+ nb = json.load(f)
168
+
169
+ metadata = nb.get('metadata', {})
170
+
171
+ # Try to get title from notebook metadata
172
+ title = metadata.get('title', '')
173
+ if not title:
174
+ # Try to extract from first markdown cell
175
+ for cell in nb.get('cells', []):
176
+ if cell.get('cell_type') == 'markdown':
177
+ source = ''.join(cell.get('source', []))
178
+ if source.startswith('# '):
179
+ title = source.split('\n')[0].replace('# ', '').strip()
180
+ break
181
+
182
+ if not title:
183
+ title = '${basename}'.replace('-', ' ').replace('_', ' ').title()
184
+
185
+ # Extract description (first non-heading markdown content)
186
+ description = metadata.get('description', '')
187
+ if not description:
188
+ for cell in nb.get('cells', []):
189
+ if cell.get('cell_type') == 'markdown':
190
+ source = ''.join(cell.get('source', []))
191
+ # Skip heading lines and empty lines
192
+ for line in source.split('\n'):
193
+ if line.strip() and not line.startswith('#'):
194
+ description = line.strip()[:200]
195
+ break
196
+ if description:
197
+ break
198
+
199
+ if not description:
200
+ description = 'Jupyter notebook demonstration'
201
+
202
+ # Format date in ISO 8601 format
203
+ date_str = metadata.get('date', datetime.now().strftime('%Y-%m-%dT%H:%M:%S.000Z'))
204
+
205
+ # Generate permalink from basename
206
+ permalink = '/notebooks/${basename}/'
207
+
208
+ # Output as JSON to avoid parsing issues
209
+ result = {
210
+ 'title': title,
211
+ 'description': description,
212
+ 'date': date_str,
213
+ 'permalink': permalink
214
+ }
215
+ print(json.dumps(result))
216
+ " 2>/dev/null)
217
+
218
+ # Parse JSON output
219
+ local title description date permalink
220
+ if [[ -n "$metadata_json" ]]; then
221
+ title=$(echo "$metadata_json" | python3 -c "import sys, json; print(json.load(sys.stdin)['title'])")
222
+ description=$(echo "$metadata_json" | python3 -c "import sys, json; print(json.load(sys.stdin)['description'])")
223
+ date=$(echo "$metadata_json" | python3 -c "import sys, json; print(json.load(sys.stdin)['date'])")
224
+ permalink=$(echo "$metadata_json" | python3 -c "import sys, json; print(json.load(sys.stdin)['permalink'])")
225
+ else
226
+ # Fallback if Python parsing fails
227
+ title="${basename//-/ }"
228
+ description="Jupyter notebook"
229
+ date=$(date -u +%Y-%m-%dT%H:%M:%S.000Z)
230
+ permalink="/notebooks/${basename}/"
231
+ fi
232
+
233
+ # Generate front matter
234
+ cat <<EOF
235
+ ---
236
+ title: "${title}"
237
+ description: "${description}"
238
+ layout: notebook
239
+ collection: notebooks
240
+ date: ${date}
241
+ categories: [Notebooks]
242
+ tags: [jupyter, python]
243
+ comments: true
244
+ jupyter_metadata: true
245
+ lastmod: $(date -u +%Y-%m-%dT%H:%M:%S.000Z)
246
+ permalink: ${permalink}
247
+ ---
248
+
249
+ EOF
250
+ }
251
+
252
+ # Convert a single notebook
253
+ convert_notebook() {
254
+ local notebook_file="$1"
255
+ local basename
256
+ basename=$(basename "$notebook_file" .ipynb)
257
+ local output_file="$OUTPUT_DIR/${basename}.md"
258
+
259
+ # Check if conversion is needed
260
+ if [[ -f "$output_file" && "$FORCE" != true ]]; then
261
+ if [[ "$VERBOSE" == true ]]; then
262
+ info "Skipping $notebook_file (output exists, use --force to reconvert)"
263
+ fi
264
+ return 0
265
+ fi
266
+
267
+ step "Converting: $notebook_file"
268
+
269
+ if [[ "$DRY_RUN" == true ]]; then
270
+ info "Would convert: $notebook_file -> $output_file"
271
+ return 0
272
+ fi
273
+
274
+ # Create output directories
275
+ mkdir -p "$OUTPUT_DIR"
276
+ mkdir -p "$IMAGE_DIR"
277
+
278
+ # Create temp directory for conversion
279
+ local temp_dir
280
+ temp_dir=$(mktemp -d "${TMPDIR:-/tmp}/notebook-XXXXXXXXXX")
281
+ local temp_md="${temp_dir}/${basename}.md"
282
+
283
+ if [[ "$VERBOSE" == true ]]; then
284
+ info "Running nbconvert..."
285
+ fi
286
+
287
+ # Use nbconvert to convert to markdown with image extraction
288
+ python3 -m nbconvert \
289
+ --to markdown \
290
+ --output-dir="$temp_dir" \
291
+ --output="${basename}" \
292
+ "$notebook_file" 2>&1 | \
293
+ if [[ "$VERBOSE" == true ]]; then cat; else cat > /dev/null; fi
294
+
295
+ # Move extracted images to assets directory
296
+ local notebook_images="${temp_dir}/${basename}_files"
297
+ if [[ -d "$notebook_images" ]]; then
298
+ mkdir -p "$IMAGE_DIR/${basename}_files"
299
+ cp -r "$notebook_images"/* "$IMAGE_DIR/${basename}_files/"
300
+ if [[ "$VERBOSE" == true ]]; then
301
+ info "Copied images to $IMAGE_DIR/${basename}_files/"
302
+ fi
303
+ fi
304
+
305
+ # Extract front matter
306
+ local front_matter
307
+ front_matter=$(extract_front_matter "$notebook_file")
308
+
309
+ # Combine front matter with converted content
310
+ {
311
+ echo "$front_matter"
312
+ cat "$temp_md"
313
+ } > "$output_file"
314
+
315
+ # Fix image paths in the markdown to use Jekyll's asset path
316
+ sed -i.bak "s|${basename}_files/|/assets/images/notebooks/${basename}_files/|g" "$output_file"
317
+ rm -f "${output_file}.bak"
318
+
319
+ # Clean up temp directory
320
+ rm -rf "$temp_dir"
321
+
322
+ success "Converted: $output_file"
323
+ }
324
+
325
+ # Clean converted files
326
+ clean_converted() {
327
+ step "Cleaning converted markdown files..."
328
+
329
+ local count=0
330
+ while IFS= read -r -d '' md_file; do
331
+ local basename
332
+ basename=$(basename "$md_file" .md)
333
+
334
+ # Only remove .md files if corresponding .ipynb exists
335
+ if [[ -f "$NOTEBOOKS_DIR/${basename}.ipynb" ]]; then
336
+ if [[ "$DRY_RUN" == true ]]; then
337
+ info "Would remove: $md_file"
338
+ else
339
+ rm -f "$md_file"
340
+ info "Removed: $md_file"
341
+ fi
342
+ ((count++))
343
+ fi
344
+ done < <(find "$OUTPUT_DIR" -name "*.md" -print0 2>/dev/null)
345
+
346
+ if [[ $count -eq 0 ]]; then
347
+ info "No converted files to clean"
348
+ else
349
+ success "Cleaned $count converted file(s)"
350
+ fi
351
+ }
352
+
353
+ # List notebooks to be converted
354
+ list_notebooks() {
355
+ step "Listing notebooks to convert..."
356
+
357
+ local count=0
358
+
359
+ if [[ -n "$SPECIFIC_FILE" ]]; then
360
+ if [[ -f "$SPECIFIC_FILE" ]]; then
361
+ echo "$SPECIFIC_FILE"
362
+ count=1
363
+ fi
364
+ else
365
+ while IFS= read -r -d '' notebook_file; do
366
+ echo "$notebook_file"
367
+ ((count++))
368
+ done < <(find "$NOTEBOOKS_DIR" -name "*.ipynb" -print0 2>/dev/null)
369
+ fi
370
+
371
+ info "Found $count notebook(s)"
372
+ }
373
+
374
+ # Main conversion process
375
+ main() {
376
+ info "Jupyter Notebook Converter for Jekyll"
377
+ info "======================================"
378
+
379
+ if [[ "$CLEAN" == true ]]; then
380
+ clean_converted
381
+ exit 0
382
+ fi
383
+
384
+ if [[ "$LIST_ONLY" == true ]]; then
385
+ list_notebooks
386
+ exit 0
387
+ fi
388
+
389
+ check_dependencies
390
+
391
+ # Convert specific file or all notebooks
392
+ if [[ -n "$SPECIFIC_FILE" ]]; then
393
+ if [[ ! -f "$SPECIFIC_FILE" ]]; then
394
+ error "File not found: $SPECIFIC_FILE"
395
+ fi
396
+ convert_notebook "$SPECIFIC_FILE"
397
+ else
398
+ step "Scanning for notebooks in: $NOTEBOOKS_DIR"
399
+
400
+ local count=0
401
+ while IFS= read -r -d '' notebook_file; do
402
+ convert_notebook "$notebook_file"
403
+ ((count++))
404
+ done < <(find "$NOTEBOOKS_DIR" -name "*.ipynb" -print0 2>/dev/null)
405
+
406
+ if [[ $count -eq 0 ]]; then
407
+ warn "No notebooks found in $NOTEBOOKS_DIR"
408
+ else
409
+ success "Converted $count notebook(s)"
410
+ fi
411
+ fi
412
+ }
413
+
414
+ # Run main function
415
+ main