jekyll-theme-zer0 0.15.0 → 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.
@@ -5,10 +5,13 @@ require 'yaml'
5
5
  require 'date'
6
6
  require 'fileutils'
7
7
 
8
- # Statistics Generator for Jekyll Site
8
+ # Enhanced Statistics Generator for Jekyll Site
9
9
  # Analyzes site content and generates comprehensive statistics
10
+ # Features: focus areas, skill levels, authors, content types analysis
10
11
 
11
12
  class SiteStatisticsGenerator
13
+ CONFIG_FILE = '_data/statistics_config.yml'
14
+
12
15
  def initialize(site_root = '.')
13
16
  @site_root = File.expand_path(site_root)
14
17
  @posts_dir = File.join(@site_root, '_posts')
@@ -19,15 +22,63 @@ class SiteStatisticsGenerator
19
22
  File.join(@site_root, '_projects')
20
23
  ]
21
24
  @output_file = File.join(@site_root, '_data', 'content_statistics.yml')
25
+ @config_file = File.join(@site_root, CONFIG_FILE)
26
+
27
+ # Load configuration
28
+ @config = load_config
22
29
 
23
30
  @stats = {
24
31
  'generated_at' => Time.now.strftime('%Y-%m-%d %H:%M:%S'),
25
32
  'overview' => {},
26
33
  'categories' => {},
27
34
  'tags' => {},
35
+ 'authors' => {},
36
+ 'years' => {},
28
37
  'content_breakdown' => {},
29
38
  'monthly_distribution' => {},
30
- 'word_statistics' => {}
39
+ 'word_statistics' => {},
40
+ 'focus_areas' => {},
41
+ 'skill_levels' => {},
42
+ 'content_types' => {},
43
+ 'date_range' => {},
44
+ 'drafts' => 0,
45
+ 'published' => 0
46
+ }
47
+ end
48
+
49
+ def load_config
50
+ if File.exist?(@config_file)
51
+ puts "📋 Loading configuration from #{CONFIG_FILE}..."
52
+ YAML.load_file(@config_file)
53
+ else
54
+ puts "⚠️ Configuration file not found: #{CONFIG_FILE}"
55
+ puts "📝 Using default configuration..."
56
+ default_config
57
+ end
58
+ end
59
+
60
+ def default_config
61
+ {
62
+ 'focus_areas' => {
63
+ 'AI & Machine Learning' => ['ai', 'ml', 'machine-learning'],
64
+ 'Web Development' => ['web', 'javascript', 'jekyll'],
65
+ 'DevOps & Infrastructure' => ['devops', 'docker', 'ci/cd'],
66
+ 'System Administration' => ['linux', 'windows', 'system'],
67
+ 'Programming & Scripting' => ['programming', 'python', 'bash', 'ruby'],
68
+ 'Data & Analytics' => ['data', 'database', 'analytics']
69
+ },
70
+ 'skill_levels' => {
71
+ 'beginner' => ['beginner', 'intro', 'basics', 'quickstart'],
72
+ 'intermediate' => ['intermediate', 'practical', 'hands-on'],
73
+ 'advanced' => ['advanced', 'expert', 'architecture'],
74
+ 'expert' => ['expert', 'research', 'innovation']
75
+ },
76
+ 'content_types' => {
77
+ 'Tutorial' => ['tutorial', 'guide', 'how-to'],
78
+ 'Article' => ['article', 'analysis', 'insights'],
79
+ 'Journal Entry' => ['journal', 'learning-journey', 'post'],
80
+ 'Documentation' => ['documentation', 'reference', 'docs']
81
+ }
31
82
  }
32
83
  end
33
84
 
@@ -38,6 +89,7 @@ class SiteStatisticsGenerator
38
89
  analyze_pages
39
90
  analyze_collections
40
91
  calculate_overview_metrics
92
+ calculate_derived_stats
41
93
  sort_and_finalize_data
42
94
  write_statistics_file
43
95
 
@@ -123,7 +175,12 @@ class SiteStatisticsGenerator
123
175
  end
124
176
 
125
177
  # Skip drafts
126
- return if front_matter['draft'] == true
178
+ if front_matter['draft'] == true
179
+ @stats['drafts'] += 1
180
+ return
181
+ else
182
+ @stats['published'] += 1
183
+ end
127
184
 
128
185
  # Count this content
129
186
  @stats['content_breakdown'][content_type] ||= 0
@@ -153,7 +210,10 @@ class SiteStatisticsGenerator
153
210
  end
154
211
  end
155
212
 
156
- # Process dates for monthly distribution
213
+ # Process authors
214
+ analyze_authors(front_matter['author'])
215
+
216
+ # Process dates for monthly distribution and year tracking
157
217
  if front_matter['date']
158
218
  begin
159
219
  # Handle different date formats
@@ -169,12 +229,26 @@ class SiteStatisticsGenerator
169
229
  month_key = date.strftime('%Y-%m')
170
230
  @stats['monthly_distribution'][month_key] ||= 0
171
231
  @stats['monthly_distribution'][month_key] += 1
232
+
233
+ # Track years
234
+ year = date.year.to_s
235
+ @stats['years'][year] ||= 0
236
+ @stats['years'][year] += 1
172
237
  rescue => e
173
238
  puts "⚠️ Date parsing error in #{file_path}: #{e.message} (date: #{front_matter['date']})"
174
239
  # Skip this date
175
240
  end
176
241
  end
177
242
 
243
+ # Analyze skill levels (from tags, categories, and content)
244
+ analyze_skill_levels(front_matter)
245
+
246
+ # Analyze focus areas (from categories and tags)
247
+ analyze_focus_areas(front_matter)
248
+
249
+ # Analyze content types (from layout, categories, or inferred)
250
+ analyze_content_types(front_matter)
251
+
178
252
  # Count words in content
179
253
  word_count = count_words(body_content)
180
254
  @stats['word_statistics'][File.basename(file_path)] = {
@@ -184,6 +258,86 @@ class SiteStatisticsGenerator
184
258
  }
185
259
  end
186
260
 
261
+ def analyze_authors(author)
262
+ return unless author.is_a?(String) && !author.strip.empty?
263
+
264
+ clean_author = author.strip
265
+ @stats['authors'][clean_author] ||= 0
266
+ @stats['authors'][clean_author] += 1
267
+ end
268
+
269
+ def analyze_skill_levels(front_matter)
270
+ return unless @config['skill_levels']
271
+
272
+ all_text = [
273
+ front_matter['title'],
274
+ front_matter['description'],
275
+ front_matter['categories'],
276
+ front_matter['tags']
277
+ ].flatten.compact.join(' ').downcase
278
+
279
+ @config['skill_levels'].each do |level, indicators|
280
+ if indicators.any? { |indicator| all_text.include?(indicator) }
281
+ @stats['skill_levels'][level] ||= 0
282
+ @stats['skill_levels'][level] += 1
283
+ end
284
+ end
285
+ end
286
+
287
+ def analyze_focus_areas(front_matter)
288
+ return unless @config['focus_areas']
289
+
290
+ all_text = [
291
+ front_matter['title'],
292
+ front_matter['description'],
293
+ front_matter['categories'],
294
+ front_matter['tags']
295
+ ].flatten.compact.join(' ').downcase
296
+
297
+ @config['focus_areas'].each do |area, keywords|
298
+ if keywords.any? { |keyword| all_text.include?(keyword) }
299
+ @stats['focus_areas'][area] ||= 0
300
+ @stats['focus_areas'][area] += 1
301
+ end
302
+ end
303
+ end
304
+
305
+ def analyze_content_types(front_matter)
306
+ return unless @config['content_types']
307
+
308
+ # Check layout first
309
+ layout = front_matter['layout']
310
+ if layout
311
+ case layout.downcase
312
+ when 'journals', 'journal', 'blog'
313
+ increment_content_type('Journal Entry')
314
+ when 'tutorial', 'guide'
315
+ increment_content_type('Tutorial')
316
+ when 'article'
317
+ increment_content_type('Article')
318
+ when 'documentation', 'docs'
319
+ increment_content_type('Documentation')
320
+ end
321
+ end
322
+
323
+ # Check title and description
324
+ all_text = [
325
+ front_matter['title'],
326
+ front_matter['description']
327
+ ].compact.join(' ').downcase
328
+
329
+ @config['content_types'].each do |type, keywords|
330
+ if keywords.any? { |keyword| all_text.include?(keyword) }
331
+ increment_content_type(type)
332
+ end
333
+ end
334
+ end
335
+
336
+ def increment_content_type(type)
337
+ @stats['content_types'][type] ||= 0
338
+ @stats['content_types'][type] += 1
339
+ end
340
+
187
341
  def count_words(content)
188
342
  # Remove markdown syntax and count words
189
343
  text = content.gsub(/[#*_`\[\](){}]/, ' ') # Remove markdown syntax
@@ -208,19 +362,48 @@ class SiteStatisticsGenerator
208
362
  'total_content' => total_content,
209
363
  'total_categories' => @stats['categories'].keys.length,
210
364
  'total_tags' => @stats['tags'].keys.length,
365
+ 'total_authors' => @stats['authors'].keys.length,
211
366
  'total_words' => total_words,
212
- 'average_words_per_post' => total_content > 0 ? (total_words.to_f / total_content).round(1) : 0
367
+ 'average_words_per_post' => total_content > 0 ? (total_words.to_f / total_content).round(1) : 0,
368
+ 'published' => @stats['published'],
369
+ 'drafts' => @stats['drafts']
213
370
  }
214
371
  end
215
372
 
373
+ def calculate_derived_stats
374
+ # Calculate date range
375
+ years = @stats['years'].keys.map(&:to_i).sort
376
+ if years.any?
377
+ @stats['date_range'] = {
378
+ 'earliest' => years.first,
379
+ 'latest' => years.last,
380
+ 'span_years' => years.last - years.first + 1
381
+ }
382
+ end
383
+
384
+ # Calculate counts
385
+ @stats['category_count'] = @stats['categories'].length
386
+ @stats['tag_count'] = @stats['tags'].length
387
+ @stats['author_count'] = @stats['authors'].length
388
+ end
389
+
216
390
  def sort_and_finalize_data
217
391
  # Sort categories and tags by count (descending)
218
392
  @stats['categories'] = @stats['categories'].sort_by { |_, count| -count }.to_h
219
393
  @stats['tags'] = @stats['tags'].sort_by { |_, count| -count }.to_h
394
+ @stats['authors'] = @stats['authors'].sort_by { |_, count| -count }.to_h
395
+ @stats['focus_areas'] = @stats['focus_areas'].sort_by { |_, count| -count }.to_h
396
+ @stats['skill_levels'] = @stats['skill_levels'].sort_by { |_, count| -count }.to_h
397
+ @stats['content_types'] = @stats['content_types'].sort_by { |_, count| -count }.to_h
220
398
 
221
399
  # Sort monthly distribution by date
222
400
  @stats['monthly_distribution'] = @stats['monthly_distribution'].sort.to_h
223
401
 
402
+ # Create top lists
403
+ @stats['top_categories'] = @stats['categories'].first(5).to_h
404
+ @stats['top_tags'] = @stats['tags'].first(10).to_h
405
+ @stats['top_authors'] = @stats['authors'].first(5).to_h
406
+
224
407
  # Convert to arrays for Jekyll (categories and tags)
225
408
  @stats['categories'] = @stats['categories'].map { |name, count| [name, count] }
226
409
  @stats['tags'] = @stats['tags'].map { |name, count| [name, count] }
@@ -242,25 +425,49 @@ class SiteStatisticsGenerator
242
425
  puts "📝 Total Posts: #{@stats['overview']['total_posts']}"
243
426
  puts "📄 Total Pages: #{@stats['overview']['total_pages']}"
244
427
  puts "📚 Total Content: #{@stats['overview']['total_content']}"
428
+ puts "✅ Published: #{@stats['overview']['published']}"
429
+ puts "📝 Drafts: #{@stats['overview']['drafts']}"
245
430
  puts "📂 Categories: #{@stats['overview']['total_categories']}"
246
431
  puts "🏷️ Tags: #{@stats['overview']['total_tags']}"
432
+ puts "👥 Authors: #{@stats['overview']['total_authors']}"
247
433
  puts "📝 Total Words: #{@stats['overview']['total_words'].to_s.reverse.gsub(/(\d{3})(?=\d)/, '\\1,').reverse}"
248
434
  puts "📊 Average Words/Post: #{@stats['overview']['average_words_per_post']}"
249
435
 
250
- if @stats['categories'].any?
436
+ if @stats['date_range'].any?
437
+ puts "\n📅 DATE RANGE:"
438
+ puts " Earliest: #{@stats['date_range']['earliest']}"
439
+ puts " Latest: #{@stats['date_range']['latest']}"
440
+ puts " Span: #{@stats['date_range']['span_years']} year(s)"
441
+ end
442
+
443
+ if @stats['top_categories'].any?
251
444
  puts "\n🏆 TOP CATEGORIES:"
252
- @stats['categories'].first(5).each_with_index do |(name, count), index|
445
+ @stats['top_categories'].each_with_index do |(name, count), index|
253
446
  puts " #{index + 1}. #{name}: #{count} posts"
254
447
  end
255
448
  end
256
449
 
257
- if @stats['tags'].any?
450
+ if @stats['top_tags'].any?
258
451
  puts "\n🏷️ TOP TAGS:"
259
- @stats['tags'].first(10).each_with_index do |(name, count), index|
452
+ @stats['top_tags'].first(5).each_with_index do |(name, count), index|
260
453
  puts " #{index + 1}. #{name}: #{count} uses"
261
454
  end
262
455
  end
263
456
 
457
+ if @stats['focus_areas'].any?
458
+ puts "\n🎯 FOCUS AREAS:"
459
+ @stats['focus_areas'].first(5).each_with_index do |(area, count), index|
460
+ puts " #{index + 1}. #{area}: #{count} posts"
461
+ end
462
+ end
463
+
464
+ if @stats['skill_levels'].any?
465
+ puts "\n📈 SKILL LEVELS:"
466
+ @stats['skill_levels'].each do |level, count|
467
+ puts " #{level.capitalize}: #{count} posts"
468
+ end
469
+ end
470
+
264
471
  puts "\n✅ Statistics generation complete!"
265
472
  end
266
473
  end
@@ -0,0 +1,106 @@
1
+ #!/bin/bash
2
+
3
+ # Generate Content Statistics Script
4
+ # Used by: Manual execution or CI/CD workflows
5
+ # Purpose: Run the Ruby statistics generator with proper feedback
6
+
7
+ set -euo pipefail
8
+
9
+ # Colors for output
10
+ RED='\033[0;31m'
11
+ GREEN='\033[0;32m'
12
+ YELLOW='\033[1;33m'
13
+ BLUE='\033[0;34m'
14
+ NC='\033[0m' # No Color
15
+
16
+ # Script directory
17
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
18
+ PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
19
+ RUBY_SCRIPT="$SCRIPT_DIR/generate_statistics.rb"
20
+ OUTPUT_FILE="$SCRIPT_DIR/content_statistics.yml"
21
+ CONFIG_FILE="$SCRIPT_DIR/statistics_config.yml"
22
+
23
+ echo -e "${BLUE}📊 Zer0-Mistakes Content Statistics Generator${NC}"
24
+ echo "=================================================="
25
+
26
+ # Check if Ruby is available
27
+ if ! command -v ruby &> /dev/null; then
28
+ echo -e "${RED}❌ Error: Ruby is not installed or not in PATH${NC}"
29
+ echo "Please install Ruby to run the statistics generator."
30
+ exit 1
31
+ fi
32
+
33
+ echo -e "${GREEN}✅ Ruby found: $(ruby --version)${NC}"
34
+
35
+ # Check if the Ruby script exists
36
+ if [[ ! -f "$RUBY_SCRIPT" ]]; then
37
+ echo -e "${RED}❌ Error: Ruby script not found at $RUBY_SCRIPT${NC}"
38
+ exit 1
39
+ fi
40
+
41
+ echo -e "${GREEN}✅ Statistics generator script found${NC}"
42
+
43
+ # Check if config file exists
44
+ if [[ -f "$CONFIG_FILE" ]]; then
45
+ echo -e "${GREEN}✅ Configuration file found: statistics_config.yml${NC}"
46
+ else
47
+ echo -e "${YELLOW}⚠️ Configuration file not found, using defaults${NC}"
48
+ fi
49
+
50
+ # Check for content directories
51
+ POSTS_DIR="$PROJECT_ROOT/pages/_posts"
52
+ if [[ -d "$POSTS_DIR" ]]; then
53
+ POST_COUNT=$(find "$POSTS_DIR" -name "*.md" -type f | wc -l | tr -d ' ')
54
+ echo -e "${GREEN}✅ Found $POST_COUNT posts in pages/_posts${NC}"
55
+ fi
56
+
57
+ PAGES_DIR="$PROJECT_ROOT/pages"
58
+ if [[ -d "$PAGES_DIR" ]]; then
59
+ PAGE_COUNT=$(find "$PAGES_DIR" -name "*.md" -type f ! -path "*/_posts/*" | wc -l | tr -d ' ')
60
+ echo -e "${GREEN}✅ Found $PAGE_COUNT pages in pages/${NC}"
61
+ fi
62
+
63
+ # Run the Ruby script from project root
64
+ echo -e "${YELLOW}🔄 Generating statistics...${NC}"
65
+ cd "$PROJECT_ROOT"
66
+ if ruby "$RUBY_SCRIPT"; then
67
+ echo -e "${GREEN}✅ Statistics generated successfully!${NC}"
68
+ else
69
+ echo -e "${RED}❌ Error: Failed to generate statistics${NC}"
70
+ exit 1
71
+ fi
72
+
73
+ # Check if output file was created
74
+ if [[ -f "$OUTPUT_FILE" ]]; then
75
+ FILE_SIZE=$(du -h "$OUTPUT_FILE" | cut -f1)
76
+ echo -e "${GREEN}✅ Output file created: $OUTPUT_FILE ($FILE_SIZE)${NC}"
77
+
78
+ # Show a preview of the generated statistics
79
+ echo -e "${BLUE}📋 Statistics Preview:${NC}"
80
+ echo "------------------------"
81
+ if command -v yq &> /dev/null; then
82
+ echo "Generated at: $(yq '.generated_at' "$OUTPUT_FILE")"
83
+ echo "Total Posts: $(yq '.overview.total_posts' "$OUTPUT_FILE")"
84
+ echo "Total Pages: $(yq '.overview.total_pages' "$OUTPUT_FILE")"
85
+ echo "Published: $(yq '.overview.published' "$OUTPUT_FILE")"
86
+ echo "Categories: $(yq '.overview.total_categories' "$OUTPUT_FILE")"
87
+ echo "Tags: $(yq '.overview.total_tags' "$OUTPUT_FILE")"
88
+ else
89
+ # Fallback to head if yq is not available
90
+ echo "Preview of generated data:"
91
+ head -25 "$OUTPUT_FILE"
92
+ fi
93
+ else
94
+ echo -e "${RED}❌ Error: Output file was not created${NC}"
95
+ exit 1
96
+ fi
97
+
98
+ echo ""
99
+ echo "=========================="
100
+ echo -e "${GREEN}🎉 Content statistics generation complete!${NC}"
101
+ echo ""
102
+ echo "📝 The data is now available in _data/content_statistics.yml"
103
+ echo "📊 Use the stats layout for display: layout: stats"
104
+ echo ""
105
+ echo "🔄 To regenerate statistics, run this script again:"
106
+ echo " bash _data/generate_statistics.sh"
@@ -0,0 +1,210 @@
1
+ # Example GitHub Actions workflow for automated content statistics
2
+ # Save this as .github/workflows/update-statistics.yml
3
+ #
4
+ # This workflow automatically updates content_statistics.yml when:
5
+ # - Posts or documentation content changes
6
+ # - The statistics configuration is updated
7
+ # - Manually triggered via workflow_dispatch
8
+ # - Weekly scheduled run for maintenance
9
+ #
10
+ # For the Zer0-Mistakes Jekyll theme
11
+
12
+ name: Update Content Statistics
13
+
14
+ on:
15
+ # Run when content changes
16
+ push:
17
+ paths:
18
+ - 'pages/_posts/**'
19
+ - 'pages/_docs/**'
20
+ - 'pages/_quickstart/**'
21
+ - '_data/statistics_config.yml'
22
+ branches: [main]
23
+
24
+ # Allow manual trigger with options
25
+ workflow_dispatch:
26
+ inputs:
27
+ force_update:
28
+ description: 'Force update even if no changes detected'
29
+ required: false
30
+ default: 'false'
31
+ type: boolean
32
+ skip_push:
33
+ description: 'Generate stats but do not commit/push'
34
+ required: false
35
+ default: 'false'
36
+ type: boolean
37
+
38
+ # Run weekly to catch any missed updates
39
+ schedule:
40
+ - cron: '0 6 * * 1' # Every Monday at 6 AM UTC
41
+
42
+ env:
43
+ RUBY_VERSION: '3.2'
44
+ YQ_VERSION: 'v4.40.5'
45
+
46
+ jobs:
47
+ update-statistics:
48
+ runs-on: ubuntu-latest
49
+
50
+ permissions:
51
+ contents: write # Allow the workflow to commit changes
52
+
53
+ steps:
54
+ - name: Checkout repository
55
+ uses: actions/checkout@v4
56
+ with:
57
+ token: ${{ secrets.GITHUB_TOKEN }}
58
+ fetch-depth: 0
59
+
60
+ - name: Setup Ruby
61
+ uses: ruby/setup-ruby@v1
62
+ with:
63
+ ruby-version: ${{ env.RUBY_VERSION }}
64
+ bundler-cache: true
65
+
66
+ - name: Install yq (for YAML processing)
67
+ run: |
68
+ sudo wget -qO /usr/local/bin/yq \
69
+ https://github.com/mikefarah/yq/releases/download/${{ env.YQ_VERSION }}/yq_linux_amd64
70
+ sudo chmod +x /usr/local/bin/yq
71
+ yq --version
72
+
73
+ - name: Verify required files exist
74
+ run: |
75
+ echo "🔍 Checking required files..."
76
+
77
+ if [ ! -f "_data/generate_statistics.rb" ]; then
78
+ echo "❌ Missing: _data/generate_statistics.rb"
79
+ exit 1
80
+ fi
81
+
82
+ if [ ! -f "_data/update_statistics.sh" ]; then
83
+ echo "❌ Missing: _data/update_statistics.sh"
84
+ exit 1
85
+ fi
86
+
87
+ echo "✅ All required files present"
88
+
89
+ - name: Update content statistics
90
+ env:
91
+ CI: true
92
+ AUTO_PUSH: ${{ github.event.inputs.skip_push != 'true' }}
93
+ FORCE_UPDATE: ${{ github.event.inputs.force_update == 'true' }}
94
+ run: |
95
+ # Make scripts executable
96
+ chmod +x _data/update_statistics.sh
97
+ chmod +x _data/generate_statistics.sh 2>/dev/null || true
98
+
99
+ # Run the update script
100
+ bash _data/update_statistics.sh
101
+
102
+ - name: Display statistics summary
103
+ if: always()
104
+ run: |
105
+ if [ -f "_data/content_statistics.yml" ]; then
106
+ echo ""
107
+ echo "📊 Content Statistics Summary"
108
+ echo "════════════════════════════════════════════════════"
109
+ echo ""
110
+
111
+ # Basic counts
112
+ echo "📝 Content Overview:"
113
+ echo " Total Posts: $(yq '.total_posts // 0' _data/content_statistics.yml)"
114
+ echo " Published: $(yq '.published // 0' _data/content_statistics.yml)"
115
+ echo " Drafts: $(yq '.drafts // 0' _data/content_statistics.yml)"
116
+ echo " Categories: $(yq '.category_count // 0' _data/content_statistics.yml)"
117
+ echo " Tags: $(yq '.tag_count // 0' _data/content_statistics.yml)"
118
+ echo ""
119
+
120
+ # Focus areas if present
121
+ if yq -e '.focus_areas' _data/content_statistics.yml > /dev/null 2>&1; then
122
+ echo "🎯 Top Focus Areas:"
123
+ yq '.focus_areas | to_entries | sort_by(-.value) | .[0:5] | .[] | " • " + .key + ": " + (.value | tostring)' \
124
+ _data/content_statistics.yml 2>/dev/null || echo " (none)"
125
+ echo ""
126
+ fi
127
+
128
+ # Skill levels if present
129
+ if yq -e '.skill_levels' _data/content_statistics.yml > /dev/null 2>&1; then
130
+ echo "📈 Skill Level Distribution:"
131
+ yq '.skill_levels | to_entries | .[] | " • " + .key + ": " + (.value | tostring)' \
132
+ _data/content_statistics.yml 2>/dev/null || echo " (none)"
133
+ echo ""
134
+ fi
135
+
136
+ # Authors if present
137
+ if yq -e '.authors' _data/content_statistics.yml > /dev/null 2>&1; then
138
+ echo "✍️ Top Authors:"
139
+ yq '.authors | to_entries | sort_by(-.value) | .[0:3] | .[] | " • " + .key + ": " + (.value | tostring) + " posts"' \
140
+ _data/content_statistics.yml 2>/dev/null || echo " (none)"
141
+ echo ""
142
+ fi
143
+
144
+ # Content freshness
145
+ if yq -e '.content_freshness' _data/content_statistics.yml > /dev/null 2>&1; then
146
+ echo "🕐 Content Freshness:"
147
+ yq '.content_freshness | to_entries | .[] | " • " + .key + ": " + (.value | tostring)' \
148
+ _data/content_statistics.yml 2>/dev/null || echo " (none)"
149
+ echo ""
150
+ fi
151
+
152
+ # Derived stats
153
+ if yq -e '.derived_stats' _data/content_statistics.yml > /dev/null 2>&1; then
154
+ echo "📊 Derived Statistics:"
155
+ AVG_TAGS=$(yq '.derived_stats.avg_tags_per_post // "N/A"' _data/content_statistics.yml)
156
+ AVG_CATS=$(yq '.derived_stats.avg_categories_per_post // "N/A"' _data/content_statistics.yml)
157
+ TOP_CAT=$(yq '.derived_stats.most_common_category // "N/A"' _data/content_statistics.yml)
158
+ echo " • Avg tags/post: ${AVG_TAGS}"
159
+ echo " • Avg categories/post: ${AVG_CATS}"
160
+ echo " • Most common category: ${TOP_CAT}"
161
+ echo ""
162
+ fi
163
+
164
+ # Last updated
165
+ echo "🕒 Last Updated: $(yq '.generated_at // "unknown"' _data/content_statistics.yml)"
166
+ echo ""
167
+ echo "════════════════════════════════════════════════════"
168
+ else
169
+ echo "⚠️ Statistics file not found: _data/content_statistics.yml"
170
+ fi
171
+
172
+ - name: Check for changes
173
+ id: changes
174
+ run: |
175
+ if git diff --quiet _data/content_statistics.yml; then
176
+ echo "has_changes=false" >> $GITHUB_OUTPUT
177
+ else
178
+ echo "has_changes=true" >> $GITHUB_OUTPUT
179
+ fi
180
+
181
+ - name: Commit and push changes
182
+ if: steps.changes.outputs.has_changes == 'true' && github.event.inputs.skip_push != 'true'
183
+ run: |
184
+ git config --local user.email "github-actions[bot]@users.noreply.github.com"
185
+ git config --local user.name "github-actions[bot]"
186
+
187
+ git add _data/content_statistics.yml
188
+ git commit -m "chore(stats): update content statistics [skip ci]"
189
+ git push
190
+
191
+ echo "✅ Statistics updated and pushed to repository"
192
+
193
+ - name: Job summary
194
+ run: |
195
+ echo "## 📊 Content Statistics Update" >> $GITHUB_STEP_SUMMARY
196
+ echo "" >> $GITHUB_STEP_SUMMARY
197
+
198
+ if [ -f "_data/content_statistics.yml" ]; then
199
+ echo "| Metric | Value |" >> $GITHUB_STEP_SUMMARY
200
+ echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY
201
+ echo "| Total Posts | $(yq '.total_posts // 0' _data/content_statistics.yml) |" >> $GITHUB_STEP_SUMMARY
202
+ echo "| Published | $(yq '.published // 0' _data/content_statistics.yml) |" >> $GITHUB_STEP_SUMMARY
203
+ echo "| Drafts | $(yq '.drafts // 0' _data/content_statistics.yml) |" >> $GITHUB_STEP_SUMMARY
204
+ echo "| Categories | $(yq '.category_count // 0' _data/content_statistics.yml) |" >> $GITHUB_STEP_SUMMARY
205
+ echo "| Tags | $(yq '.tag_count // 0' _data/content_statistics.yml) |" >> $GITHUB_STEP_SUMMARY
206
+ echo "" >> $GITHUB_STEP_SUMMARY
207
+ echo "_Last updated: $(yq '.generated_at // "unknown"' _data/content_statistics.yml)_" >> $GITHUB_STEP_SUMMARY
208
+ else
209
+ echo "⚠️ Statistics file not generated" >> $GITHUB_STEP_SUMMARY
210
+ fi
@@ -1,17 +1,25 @@
1
1
  # Quick Start Navigation Configuration
2
2
  # Used by: Quick Start sidebar and related pages
3
3
  # Purpose: Step-by-step guide navigation for new users
4
+ # Order: Overview → Machine → Jekyll → GitHub → Personalization
4
5
 
5
6
  - title: "Quick Start"
6
7
  icon: bi-flag
7
8
  url: /quickstart/
8
9
  sublinks:
9
- - title: "Machine Setup"
10
+ - title: "1. Machine Setup"
10
11
  url: /quickstart/machine-setup/
11
12
  icon: bi-laptop
12
- - title: "Jekyll Setup"
13
+ description: "Install Docker, Git, and development tools"
14
+ - title: "2. Jekyll Setup"
13
15
  url: /quickstart/jekyll-setup/
14
16
  icon: bi-code-square
15
- - title: "GitHub Setup"
17
+ description: "Configure Jekyll development environment"
18
+ - title: "3. GitHub Setup"
16
19
  url: /quickstart/github-setup/
17
20
  icon: bi-github
21
+ description: "Version control and deployment"
22
+ - title: "4. Personalization"
23
+ url: /quickstart/personalization/
24
+ icon: bi-palette
25
+ description: "Customize site identity and branding"