learn_bash_from_session_data 1.0.0
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.
- package/LICENSE +21 -0
- package/README.md +45 -0
- package/bin/learn-bash.js +328 -0
- package/package.json +23 -0
- package/scripts/__init__.py +34 -0
- package/scripts/analyzer.py +591 -0
- package/scripts/extractor.py +411 -0
- package/scripts/html_generator.py +2029 -0
- package/scripts/knowledge_base.py +1593 -0
- package/scripts/main.py +443 -0
- package/scripts/parser.py +623 -0
- package/scripts/quiz_generator.py +1080 -0
|
@@ -0,0 +1,2029 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
HTML Generator for Bash Learning Report
|
|
4
|
+
|
|
5
|
+
Generates a single self-contained HTML file with all CSS and JS inline.
|
|
6
|
+
No external dependencies - pure Python standard library.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from typing import Any
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
import html
|
|
12
|
+
import json
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def generate_html(analysis_result: dict[str, Any], quizzes: list[dict[str, Any]]) -> str:
|
|
16
|
+
"""
|
|
17
|
+
Generate complete HTML report from analysis results and quizzes.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
analysis_result: Dictionary containing parsed commands, stats, categories
|
|
21
|
+
quizzes: List of quiz question dictionaries
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
Complete HTML string ready to write to file
|
|
25
|
+
"""
|
|
26
|
+
stats = analysis_result.get("stats", {})
|
|
27
|
+
commands = analysis_result.get("commands", [])
|
|
28
|
+
categories = analysis_result.get("categories", {})
|
|
29
|
+
|
|
30
|
+
overview_html = render_overview_tab(stats, commands, categories)
|
|
31
|
+
commands_html = render_commands_tab(commands)
|
|
32
|
+
lessons_html = render_lessons_tab(categories, commands)
|
|
33
|
+
quiz_html = render_quiz_tab(quizzes)
|
|
34
|
+
|
|
35
|
+
inline_css = get_inline_css()
|
|
36
|
+
inline_js = get_inline_js(quizzes)
|
|
37
|
+
|
|
38
|
+
generation_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
39
|
+
|
|
40
|
+
return f'''<!DOCTYPE html>
|
|
41
|
+
<html lang="en">
|
|
42
|
+
<head>
|
|
43
|
+
<meta charset="UTF-8">
|
|
44
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
45
|
+
<title>Bash Command Learning Report</title>
|
|
46
|
+
<style>
|
|
47
|
+
{inline_css}
|
|
48
|
+
</style>
|
|
49
|
+
</head>
|
|
50
|
+
<body>
|
|
51
|
+
<div class="container">
|
|
52
|
+
<header class="header">
|
|
53
|
+
<div class="header-content">
|
|
54
|
+
<h1>Bash Command Learning Report</h1>
|
|
55
|
+
<p class="subtitle">Generated: {generation_time}</p>
|
|
56
|
+
</div>
|
|
57
|
+
<button class="theme-toggle" onclick="toggleTheme()" aria-label="Toggle dark mode">
|
|
58
|
+
<span class="theme-icon light-icon">☀</span>
|
|
59
|
+
<span class="theme-icon dark-icon">☾</span>
|
|
60
|
+
</button>
|
|
61
|
+
</header>
|
|
62
|
+
|
|
63
|
+
<nav class="tabs" role="tablist">
|
|
64
|
+
<button class="tab active" data-tab="overview" role="tab" aria-selected="true" aria-controls="panel-overview">
|
|
65
|
+
<span class="tab-icon">☰</span>
|
|
66
|
+
<span class="tab-label">Overview</span>
|
|
67
|
+
<span class="tab-key">1</span>
|
|
68
|
+
</button>
|
|
69
|
+
<button class="tab" data-tab="commands" role="tab" aria-selected="false" aria-controls="panel-commands">
|
|
70
|
+
<span class="tab-icon">❯</span>
|
|
71
|
+
<span class="tab-label">Commands</span>
|
|
72
|
+
<span class="tab-key">2</span>
|
|
73
|
+
</button>
|
|
74
|
+
<button class="tab" data-tab="lessons" role="tab" aria-selected="false" aria-controls="panel-lessons">
|
|
75
|
+
<span class="tab-icon">📚</span>
|
|
76
|
+
<span class="tab-label">Lessons</span>
|
|
77
|
+
<span class="tab-key">3</span>
|
|
78
|
+
</button>
|
|
79
|
+
<button class="tab" data-tab="quiz" role="tab" aria-selected="false" aria-controls="panel-quiz">
|
|
80
|
+
<span class="tab-icon">❓</span>
|
|
81
|
+
<span class="tab-label">Quiz</span>
|
|
82
|
+
<span class="tab-key">4</span>
|
|
83
|
+
</button>
|
|
84
|
+
</nav>
|
|
85
|
+
|
|
86
|
+
<main class="content">
|
|
87
|
+
<section id="panel-overview" class="panel active" role="tabpanel" aria-labelledby="tab-overview">
|
|
88
|
+
{overview_html}
|
|
89
|
+
</section>
|
|
90
|
+
|
|
91
|
+
<section id="panel-commands" class="panel" role="tabpanel" aria-labelledby="tab-commands">
|
|
92
|
+
{commands_html}
|
|
93
|
+
</section>
|
|
94
|
+
|
|
95
|
+
<section id="panel-lessons" class="panel" role="tabpanel" aria-labelledby="tab-lessons">
|
|
96
|
+
{lessons_html}
|
|
97
|
+
</section>
|
|
98
|
+
|
|
99
|
+
<section id="panel-quiz" class="panel" role="tabpanel" aria-labelledby="tab-quiz">
|
|
100
|
+
{quiz_html}
|
|
101
|
+
</section>
|
|
102
|
+
</main>
|
|
103
|
+
|
|
104
|
+
<footer class="footer">
|
|
105
|
+
<p>Learn Bash from Session Data | Press 1-4 to switch tabs</p>
|
|
106
|
+
</footer>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<script>
|
|
110
|
+
{inline_js}
|
|
111
|
+
</script>
|
|
112
|
+
</body>
|
|
113
|
+
</html>'''
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def render_overview_tab(stats: dict[str, Any], commands: list[dict], categories: dict) -> str:
|
|
117
|
+
"""Render the overview/dashboard tab content."""
|
|
118
|
+
total_commands = stats.get("total_commands", 0)
|
|
119
|
+
unique_commands = stats.get("unique_commands", 0)
|
|
120
|
+
unique_utilities = stats.get("unique_utilities", 0)
|
|
121
|
+
date_range = stats.get("date_range", {"start": "N/A", "end": "N/A"})
|
|
122
|
+
complexity_dist = stats.get("complexity_distribution", {"simple": 0, "intermediate": 0, "advanced": 0})
|
|
123
|
+
|
|
124
|
+
# Calculate percentages for complexity bars
|
|
125
|
+
total_for_pct = sum(complexity_dist.values()) or 1
|
|
126
|
+
simple_pct = (complexity_dist.get("simple", 0) / total_for_pct) * 100
|
|
127
|
+
intermediate_pct = (complexity_dist.get("intermediate", 0) / total_for_pct) * 100
|
|
128
|
+
advanced_pct = (complexity_dist.get("advanced", 0) / total_for_pct) * 100
|
|
129
|
+
|
|
130
|
+
# Top 10 commands by frequency
|
|
131
|
+
sorted_commands = sorted(commands, key=lambda x: x.get("frequency", 0), reverse=True)[:10]
|
|
132
|
+
top_commands_html = ""
|
|
133
|
+
max_freq = sorted_commands[0].get("frequency", 1) if sorted_commands else 1
|
|
134
|
+
|
|
135
|
+
for cmd in sorted_commands:
|
|
136
|
+
freq = cmd.get("frequency", 0)
|
|
137
|
+
bar_width = (freq / max_freq) * 100
|
|
138
|
+
cmd_name = html.escape(cmd.get("base_command", "unknown"))
|
|
139
|
+
top_commands_html += f'''
|
|
140
|
+
<div class="top-command-item">
|
|
141
|
+
<div class="top-command-name">
|
|
142
|
+
<code class="cmd">{cmd_name}</code>
|
|
143
|
+
</div>
|
|
144
|
+
<div class="top-command-bar-container">
|
|
145
|
+
<div class="top-command-bar" style="width: {bar_width}%"></div>
|
|
146
|
+
</div>
|
|
147
|
+
<div class="top-command-count">{freq}</div>
|
|
148
|
+
</div>'''
|
|
149
|
+
|
|
150
|
+
# New commands (first appearances)
|
|
151
|
+
new_commands = [c for c in commands if c.get("is_new", False)][:8]
|
|
152
|
+
new_commands_html = ""
|
|
153
|
+
for cmd in new_commands:
|
|
154
|
+
cmd_name = html.escape(cmd.get("base_command", "unknown"))
|
|
155
|
+
first_seen = cmd.get("first_seen", "")
|
|
156
|
+
new_commands_html += f'''
|
|
157
|
+
<div class="new-command-chip">
|
|
158
|
+
<code class="cmd">{cmd_name}</code>
|
|
159
|
+
<span class="first-seen">{first_seen}</span>
|
|
160
|
+
</div>'''
|
|
161
|
+
|
|
162
|
+
if not new_commands_html:
|
|
163
|
+
new_commands_html = '<p class="empty-state">No new commands detected in this session</p>'
|
|
164
|
+
|
|
165
|
+
# Category breakdown for pie chart
|
|
166
|
+
category_data = []
|
|
167
|
+
cat_colors = [
|
|
168
|
+
"#4285f4", "#ea4335", "#fbbc05", "#34a853", "#ff6d01",
|
|
169
|
+
"#46bdc6", "#7baaf7", "#f07b72", "#fcd04f", "#81c995"
|
|
170
|
+
]
|
|
171
|
+
sorted_cats = sorted(categories.items(), key=lambda x: len(x[1]), reverse=True)[:8]
|
|
172
|
+
|
|
173
|
+
for idx, (cat_name, cat_cmds) in enumerate(sorted_cats):
|
|
174
|
+
color = cat_colors[idx % len(cat_colors)]
|
|
175
|
+
count = len(cat_cmds)
|
|
176
|
+
category_data.append({
|
|
177
|
+
"name": cat_name,
|
|
178
|
+
"count": count,
|
|
179
|
+
"color": color
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
# Generate SVG pie chart
|
|
183
|
+
pie_svg = _generate_pie_chart(category_data)
|
|
184
|
+
|
|
185
|
+
# Category legend
|
|
186
|
+
category_legend = ""
|
|
187
|
+
for cat in category_data:
|
|
188
|
+
category_legend += f'''
|
|
189
|
+
<div class="legend-item">
|
|
190
|
+
<span class="legend-color" style="background: {cat['color']}"></span>
|
|
191
|
+
<span class="legend-label">{html.escape(cat['name'])}</span>
|
|
192
|
+
<span class="legend-count">{cat['count']}</span>
|
|
193
|
+
</div>'''
|
|
194
|
+
|
|
195
|
+
return f'''
|
|
196
|
+
<div class="dashboard">
|
|
197
|
+
<div class="stats-grid">
|
|
198
|
+
<div class="stat-card">
|
|
199
|
+
<div class="stat-value">{total_commands}</div>
|
|
200
|
+
<div class="stat-label">Total Commands</div>
|
|
201
|
+
</div>
|
|
202
|
+
<div class="stat-card">
|
|
203
|
+
<div class="stat-value">{unique_commands}</div>
|
|
204
|
+
<div class="stat-label">Unique Commands</div>
|
|
205
|
+
</div>
|
|
206
|
+
<div class="stat-card">
|
|
207
|
+
<div class="stat-value">{unique_utilities}</div>
|
|
208
|
+
<div class="stat-label">Unique Utilities</div>
|
|
209
|
+
</div>
|
|
210
|
+
<div class="stat-card">
|
|
211
|
+
<div class="stat-value date-value">{date_range.get("start", "N/A")}</div>
|
|
212
|
+
<div class="stat-label">to {date_range.get("end", "N/A")}</div>
|
|
213
|
+
</div>
|
|
214
|
+
</div>
|
|
215
|
+
|
|
216
|
+
<div class="charts-row">
|
|
217
|
+
<div class="chart-card">
|
|
218
|
+
<h3>Complexity Distribution</h3>
|
|
219
|
+
<div class="complexity-bars">
|
|
220
|
+
<div class="complexity-row">
|
|
221
|
+
<span class="complexity-label simple">Simple</span>
|
|
222
|
+
<div class="complexity-bar-bg">
|
|
223
|
+
<div class="complexity-bar simple" style="width: {simple_pct}%"></div>
|
|
224
|
+
</div>
|
|
225
|
+
<span class="complexity-count">{complexity_dist.get("simple", 0)}</span>
|
|
226
|
+
</div>
|
|
227
|
+
<div class="complexity-row">
|
|
228
|
+
<span class="complexity-label intermediate">Intermediate</span>
|
|
229
|
+
<div class="complexity-bar-bg">
|
|
230
|
+
<div class="complexity-bar intermediate" style="width: {intermediate_pct}%"></div>
|
|
231
|
+
</div>
|
|
232
|
+
<span class="complexity-count">{complexity_dist.get("intermediate", 0)}</span>
|
|
233
|
+
</div>
|
|
234
|
+
<div class="complexity-row">
|
|
235
|
+
<span class="complexity-label advanced">Advanced</span>
|
|
236
|
+
<div class="complexity-bar-bg">
|
|
237
|
+
<div class="complexity-bar advanced" style="width: {advanced_pct}%"></div>
|
|
238
|
+
</div>
|
|
239
|
+
<span class="complexity-count">{complexity_dist.get("advanced", 0)}</span>
|
|
240
|
+
</div>
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
|
|
244
|
+
<div class="chart-card">
|
|
245
|
+
<h3>Category Breakdown</h3>
|
|
246
|
+
<div class="pie-container">
|
|
247
|
+
{pie_svg}
|
|
248
|
+
<div class="category-legend">
|
|
249
|
+
{category_legend}
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
|
+
</div>
|
|
254
|
+
|
|
255
|
+
<div class="charts-row">
|
|
256
|
+
<div class="chart-card wide">
|
|
257
|
+
<h3>Top 10 Most-Used Commands</h3>
|
|
258
|
+
<div class="top-commands">
|
|
259
|
+
{top_commands_html}
|
|
260
|
+
</div>
|
|
261
|
+
</div>
|
|
262
|
+
|
|
263
|
+
<div class="chart-card">
|
|
264
|
+
<h3>New Commands</h3>
|
|
265
|
+
<div class="new-commands">
|
|
266
|
+
{new_commands_html}
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
</div>'''
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _generate_pie_chart(category_data: list[dict]) -> str:
|
|
274
|
+
"""Generate an SVG pie chart."""
|
|
275
|
+
if not category_data:
|
|
276
|
+
return '<div class="empty-state">No category data</div>'
|
|
277
|
+
|
|
278
|
+
total = sum(c["count"] for c in category_data)
|
|
279
|
+
if total == 0:
|
|
280
|
+
return '<div class="empty-state">No commands to display</div>'
|
|
281
|
+
|
|
282
|
+
# SVG pie chart
|
|
283
|
+
cx, cy, r = 80, 80, 70
|
|
284
|
+
paths = []
|
|
285
|
+
current_angle = -90 # Start from top
|
|
286
|
+
|
|
287
|
+
for cat in category_data:
|
|
288
|
+
pct = cat["count"] / total
|
|
289
|
+
angle = pct * 360
|
|
290
|
+
|
|
291
|
+
# Calculate arc
|
|
292
|
+
start_rad = current_angle * 3.14159 / 180
|
|
293
|
+
end_rad = (current_angle + angle) * 3.14159 / 180
|
|
294
|
+
|
|
295
|
+
x1 = cx + r * (end_rad - start_rad > 0 and 1 or -1) * __import__('math').cos(start_rad)
|
|
296
|
+
y1 = cy + r * __import__('math').sin(start_rad)
|
|
297
|
+
x2 = cx + r * __import__('math').cos(end_rad)
|
|
298
|
+
y2 = cy + r * __import__('math').sin(end_rad)
|
|
299
|
+
|
|
300
|
+
large_arc = 1 if angle > 180 else 0
|
|
301
|
+
|
|
302
|
+
# Create path
|
|
303
|
+
import math
|
|
304
|
+
x1 = cx + r * math.cos(start_rad)
|
|
305
|
+
y1 = cy + r * math.sin(start_rad)
|
|
306
|
+
x2 = cx + r * math.cos(end_rad)
|
|
307
|
+
y2 = cy + r * math.sin(end_rad)
|
|
308
|
+
|
|
309
|
+
path = f'M {cx} {cy} L {x1:.2f} {y1:.2f} A {r} {r} 0 {large_arc} 1 {x2:.2f} {y2:.2f} Z'
|
|
310
|
+
paths.append(f'<path d="{path}" fill="{cat["color"]}" stroke="#fff" stroke-width="2"><title>{html.escape(cat["name"])}: {cat["count"]}</title></path>')
|
|
311
|
+
|
|
312
|
+
current_angle += angle
|
|
313
|
+
|
|
314
|
+
return f'''<svg viewBox="0 0 160 160" class="pie-chart">
|
|
315
|
+
{''.join(paths)}
|
|
316
|
+
</svg>'''
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def render_commands_tab(commands: list[dict]) -> str:
|
|
320
|
+
"""Render the commands reference tab."""
|
|
321
|
+
# Group by category for filter chips
|
|
322
|
+
categories_set = set()
|
|
323
|
+
for cmd in commands:
|
|
324
|
+
categories_set.add(cmd.get("category", "Other"))
|
|
325
|
+
|
|
326
|
+
category_chips = ""
|
|
327
|
+
for cat in sorted(categories_set):
|
|
328
|
+
category_chips += f'<button class="filter-chip" data-category="{html.escape(cat)}">{html.escape(cat)}</button>'
|
|
329
|
+
|
|
330
|
+
# Generate command cards
|
|
331
|
+
commands_html = ""
|
|
332
|
+
for idx, cmd in enumerate(commands):
|
|
333
|
+
cmd_id = f"cmd-{idx}"
|
|
334
|
+
base_cmd = html.escape(cmd.get("base_command", "unknown"))
|
|
335
|
+
full_cmd = html.escape(cmd.get("full_command", ""))
|
|
336
|
+
category = html.escape(cmd.get("category", "Other"))
|
|
337
|
+
complexity = cmd.get("complexity", "simple")
|
|
338
|
+
frequency = cmd.get("frequency", 0)
|
|
339
|
+
description = html.escape(cmd.get("description", "No description available"))
|
|
340
|
+
|
|
341
|
+
# Syntax highlighted command
|
|
342
|
+
highlighted = _syntax_highlight(cmd.get("full_command", ""))
|
|
343
|
+
|
|
344
|
+
# Flags breakdown
|
|
345
|
+
flags = cmd.get("flags", [])
|
|
346
|
+
flags_html = ""
|
|
347
|
+
if flags:
|
|
348
|
+
flags_html = '<div class="flags-section"><h5>Flags:</h5><ul class="flags-list">'
|
|
349
|
+
for flag in flags:
|
|
350
|
+
flag_name = html.escape(flag.get("flag", ""))
|
|
351
|
+
flag_desc = html.escape(flag.get("description", ""))
|
|
352
|
+
flags_html += f'<li><code class="flag">{flag_name}</code> - {flag_desc}</li>'
|
|
353
|
+
flags_html += '</ul></div>'
|
|
354
|
+
|
|
355
|
+
# Output preview
|
|
356
|
+
output_preview = cmd.get("output_preview", "")
|
|
357
|
+
output_html = ""
|
|
358
|
+
if output_preview:
|
|
359
|
+
output_html = f'''
|
|
360
|
+
<div class="output-section">
|
|
361
|
+
<h5>Example Output:</h5>
|
|
362
|
+
<pre class="output-preview">{html.escape(output_preview)}</pre>
|
|
363
|
+
</div>'''
|
|
364
|
+
|
|
365
|
+
commands_html += f'''
|
|
366
|
+
<div class="command-card" data-category="{category}" data-complexity="{complexity}" data-frequency="{frequency}" data-name="{base_cmd}">
|
|
367
|
+
<div class="command-header" onclick="toggleCommand('{cmd_id}')">
|
|
368
|
+
<div class="command-main">
|
|
369
|
+
<code class="cmd">{base_cmd}</code>
|
|
370
|
+
<span class="complexity-badge {complexity}">{complexity}</span>
|
|
371
|
+
<span class="category-badge">{category}</span>
|
|
372
|
+
</div>
|
|
373
|
+
<div class="command-meta">
|
|
374
|
+
<span class="frequency">Used {frequency}x</span>
|
|
375
|
+
<span class="expand-icon">▼</span>
|
|
376
|
+
</div>
|
|
377
|
+
</div>
|
|
378
|
+
<div class="command-details" id="{cmd_id}">
|
|
379
|
+
<div class="full-command">
|
|
380
|
+
<h5>Full Command:</h5>
|
|
381
|
+
<pre class="syntax-highlighted">{highlighted}</pre>
|
|
382
|
+
</div>
|
|
383
|
+
<div class="description">
|
|
384
|
+
<h5>Description:</h5>
|
|
385
|
+
<p>{description}</p>
|
|
386
|
+
</div>
|
|
387
|
+
{flags_html}
|
|
388
|
+
{output_html}
|
|
389
|
+
</div>
|
|
390
|
+
</div>'''
|
|
391
|
+
|
|
392
|
+
return f'''
|
|
393
|
+
<div class="commands-container">
|
|
394
|
+
<div class="commands-toolbar">
|
|
395
|
+
<div class="search-box">
|
|
396
|
+
<input type="text" id="command-search" placeholder="Search commands..." oninput="filterCommands()">
|
|
397
|
+
</div>
|
|
398
|
+
<div class="sort-controls">
|
|
399
|
+
<label>Sort by:</label>
|
|
400
|
+
<select id="sort-select" onchange="sortCommands()">
|
|
401
|
+
<option value="frequency">Frequency</option>
|
|
402
|
+
<option value="complexity">Complexity</option>
|
|
403
|
+
<option value="category">Category</option>
|
|
404
|
+
<option value="name">Alphabetical</option>
|
|
405
|
+
</select>
|
|
406
|
+
</div>
|
|
407
|
+
</div>
|
|
408
|
+
|
|
409
|
+
<div class="filter-chips">
|
|
410
|
+
<button class="filter-chip active" data-category="all">All</button>
|
|
411
|
+
{category_chips}
|
|
412
|
+
</div>
|
|
413
|
+
|
|
414
|
+
<div class="commands-list" id="commands-list">
|
|
415
|
+
{commands_html}
|
|
416
|
+
</div>
|
|
417
|
+
</div>'''
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def _syntax_highlight(command: str) -> str:
|
|
421
|
+
"""Apply syntax highlighting to a bash command."""
|
|
422
|
+
if not command:
|
|
423
|
+
return ""
|
|
424
|
+
|
|
425
|
+
import re
|
|
426
|
+
|
|
427
|
+
# Escape HTML first
|
|
428
|
+
escaped = html.escape(command)
|
|
429
|
+
|
|
430
|
+
# Patterns for highlighting (order matters)
|
|
431
|
+
patterns = [
|
|
432
|
+
# Strings (single and double quoted)
|
|
433
|
+
(r'("[^&]*?"|'[^&]*?')', r'<span class="string">\1</span>'),
|
|
434
|
+
# Paths (starting with / or ./ or ~/)
|
|
435
|
+
(r'(\s|^)((?:/[\w.-]+)+|\.{1,2}/[\w./-]*|~/[\w./-]*)', r'\1<span class="path">\2</span>'),
|
|
436
|
+
# Flags (long and short)
|
|
437
|
+
(r'(\s)(--?[\w-]+)', r'\1<span class="flag">\2</span>'),
|
|
438
|
+
# Operators and redirects
|
|
439
|
+
(r'(\||&&|>|<|>>|\$\(|\))', r'<span class="operator">\1</span>'),
|
|
440
|
+
# Variables
|
|
441
|
+
(r'(\$[\w{}]+)', r'<span class="variable">\1</span>'),
|
|
442
|
+
]
|
|
443
|
+
|
|
444
|
+
result = escaped
|
|
445
|
+
for pattern, replacement in patterns:
|
|
446
|
+
result = re.sub(pattern, replacement, result)
|
|
447
|
+
|
|
448
|
+
# Highlight the base command (first word)
|
|
449
|
+
result = re.sub(r'^([\w.-]+)', r'<span class="cmd">\1</span>', result)
|
|
450
|
+
|
|
451
|
+
return result
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
def render_lessons_tab(categories: dict, commands: list[dict]) -> str:
|
|
455
|
+
"""Render the categorized lessons tab."""
|
|
456
|
+
if not categories:
|
|
457
|
+
return '<div class="empty-state">No categories found in the session data</div>'
|
|
458
|
+
|
|
459
|
+
# Sort categories by command count
|
|
460
|
+
sorted_cats = sorted(categories.items(), key=lambda x: len(x[1]), reverse=True)
|
|
461
|
+
|
|
462
|
+
lessons_html = ""
|
|
463
|
+
for cat_name, cat_commands in sorted_cats:
|
|
464
|
+
if not cat_commands:
|
|
465
|
+
continue
|
|
466
|
+
|
|
467
|
+
# Get full command data for this category
|
|
468
|
+
cat_cmd_data = [c for c in commands if c.get("category") == cat_name]
|
|
469
|
+
|
|
470
|
+
# Concept overview based on category
|
|
471
|
+
concept = _get_category_concept(cat_name)
|
|
472
|
+
|
|
473
|
+
# Commands in this category
|
|
474
|
+
cat_commands_html = ""
|
|
475
|
+
for cmd in cat_cmd_data[:10]: # Limit to 10 per category
|
|
476
|
+
base_cmd = html.escape(cmd.get("base_command", ""))
|
|
477
|
+
description = html.escape(cmd.get("description", ""))
|
|
478
|
+
complexity = cmd.get("complexity", "simple")
|
|
479
|
+
highlighted = _syntax_highlight(cmd.get("full_command", ""))
|
|
480
|
+
|
|
481
|
+
cat_commands_html += f'''
|
|
482
|
+
<div class="lesson-command">
|
|
483
|
+
<div class="lesson-command-header">
|
|
484
|
+
<code class="cmd">{base_cmd}</code>
|
|
485
|
+
<span class="complexity-badge {complexity}">{complexity}</span>
|
|
486
|
+
</div>
|
|
487
|
+
<pre class="syntax-highlighted">{highlighted}</pre>
|
|
488
|
+
<p class="lesson-description">{description}</p>
|
|
489
|
+
</div>'''
|
|
490
|
+
|
|
491
|
+
# Patterns observed
|
|
492
|
+
patterns = _extract_patterns(cat_cmd_data)
|
|
493
|
+
patterns_html = ""
|
|
494
|
+
if patterns:
|
|
495
|
+
patterns_html = '<div class="patterns"><h4>Patterns Observed:</h4><ul>'
|
|
496
|
+
for pattern in patterns:
|
|
497
|
+
patterns_html += f'<li>{html.escape(pattern)}</li>'
|
|
498
|
+
patterns_html += '</ul></div>'
|
|
499
|
+
|
|
500
|
+
lessons_html += f'''
|
|
501
|
+
<div class="lesson-section">
|
|
502
|
+
<h2 class="lesson-title">
|
|
503
|
+
<span class="lesson-icon">{_get_category_icon(cat_name)}</span>
|
|
504
|
+
{html.escape(cat_name)}
|
|
505
|
+
<span class="lesson-count">({len(cat_commands)} commands)</span>
|
|
506
|
+
</h2>
|
|
507
|
+
<div class="lesson-content">
|
|
508
|
+
<div class="concept-overview">
|
|
509
|
+
<h4>Concept Overview:</h4>
|
|
510
|
+
<p>{html.escape(concept)}</p>
|
|
511
|
+
</div>
|
|
512
|
+
<div class="lesson-commands">
|
|
513
|
+
<h4>Commands:</h4>
|
|
514
|
+
{cat_commands_html}
|
|
515
|
+
</div>
|
|
516
|
+
{patterns_html}
|
|
517
|
+
</div>
|
|
518
|
+
</div>'''
|
|
519
|
+
|
|
520
|
+
return f'''
|
|
521
|
+
<div class="lessons-container">
|
|
522
|
+
{lessons_html}
|
|
523
|
+
</div>'''
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def _get_category_concept(category: str) -> str:
|
|
527
|
+
"""Get concept overview for a category."""
|
|
528
|
+
concepts = {
|
|
529
|
+
"File Management": "Commands for creating, copying, moving, deleting, and organizing files and directories in the filesystem.",
|
|
530
|
+
"Text Processing": "Tools for searching, filtering, transforming, and manipulating text content in files and streams.",
|
|
531
|
+
"System Administration": "Commands for managing system resources, processes, users, and system configuration.",
|
|
532
|
+
"Network": "Utilities for network communication, file transfer, and connectivity diagnostics.",
|
|
533
|
+
"Package Management": "Tools for installing, updating, and managing software packages on the system.",
|
|
534
|
+
"Version Control": "Git and other version control commands for tracking changes and collaborating on code.",
|
|
535
|
+
"Process Management": "Commands for viewing, controlling, and managing running processes.",
|
|
536
|
+
"User Management": "Tools for managing user accounts, permissions, and access control.",
|
|
537
|
+
"Disk Management": "Utilities for managing disk space, partitions, and storage devices.",
|
|
538
|
+
"Shell Scripting": "Built-in shell commands and constructs for scripting and automation.",
|
|
539
|
+
"Development": "Programming tools, compilers, interpreters, and development utilities.",
|
|
540
|
+
"Compression": "Tools for compressing, archiving, and extracting files.",
|
|
541
|
+
"Search": "Commands for finding files, searching content, and locating resources.",
|
|
542
|
+
"Permissions": "Tools for managing file permissions, ownership, and access control.",
|
|
543
|
+
}
|
|
544
|
+
return concepts.get(category, f"Commands related to {category.lower()} operations and utilities.")
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
def _get_category_icon(category: str) -> str:
|
|
548
|
+
"""Get icon for a category."""
|
|
549
|
+
icons = {
|
|
550
|
+
"File Management": "📁",
|
|
551
|
+
"Text Processing": "📄",
|
|
552
|
+
"System Administration": "⚙",
|
|
553
|
+
"Network": "🌐",
|
|
554
|
+
"Package Management": "📦",
|
|
555
|
+
"Version Control": "📊",
|
|
556
|
+
"Process Management": "▶",
|
|
557
|
+
"User Management": "👤",
|
|
558
|
+
"Disk Management": "💿",
|
|
559
|
+
"Shell Scripting": "❯",
|
|
560
|
+
"Development": "💻",
|
|
561
|
+
"Compression": "📦",
|
|
562
|
+
"Search": "🔍",
|
|
563
|
+
"Permissions": "🔒",
|
|
564
|
+
}
|
|
565
|
+
return icons.get(category, "📌")
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
def _extract_patterns(commands: list[dict]) -> list[str]:
|
|
569
|
+
"""Extract common patterns from a list of commands."""
|
|
570
|
+
patterns = []
|
|
571
|
+
|
|
572
|
+
# Check for piping patterns
|
|
573
|
+
piped = [c for c in commands if "|" in c.get("full_command", "")]
|
|
574
|
+
if piped:
|
|
575
|
+
patterns.append(f"Piping output between commands ({len(piped)} instances)")
|
|
576
|
+
|
|
577
|
+
# Check for redirection
|
|
578
|
+
redirected = [c for c in commands if any(r in c.get("full_command", "") for r in [">", ">>", "<"])]
|
|
579
|
+
if redirected:
|
|
580
|
+
patterns.append(f"Output/input redirection ({len(redirected)} instances)")
|
|
581
|
+
|
|
582
|
+
# Check for flag usage
|
|
583
|
+
with_flags = [c for c in commands if c.get("flags")]
|
|
584
|
+
if with_flags:
|
|
585
|
+
common_flags = {}
|
|
586
|
+
for cmd in with_flags:
|
|
587
|
+
for flag in cmd.get("flags", []):
|
|
588
|
+
f = flag.get("flag", "")
|
|
589
|
+
common_flags[f] = common_flags.get(f, 0) + 1
|
|
590
|
+
if common_flags:
|
|
591
|
+
top_flag = max(common_flags.items(), key=lambda x: x[1])
|
|
592
|
+
if top_flag[1] > 1:
|
|
593
|
+
patterns.append(f"Common flag: {top_flag[0]} (used {top_flag[1]} times)")
|
|
594
|
+
|
|
595
|
+
# Check for glob patterns
|
|
596
|
+
globbed = [c for c in commands if any(g in c.get("full_command", "") for g in ["*", "?", "["])]
|
|
597
|
+
if globbed:
|
|
598
|
+
patterns.append(f"Glob/wildcard patterns ({len(globbed)} instances)")
|
|
599
|
+
|
|
600
|
+
return patterns[:4] # Limit to 4 patterns
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
def render_quiz_tab(quizzes: list[dict]) -> str:
|
|
604
|
+
"""Render the quiz tab."""
|
|
605
|
+
if not quizzes:
|
|
606
|
+
return '''
|
|
607
|
+
<div class="quiz-container">
|
|
608
|
+
<div class="empty-state">No quiz questions available</div>
|
|
609
|
+
</div>'''
|
|
610
|
+
|
|
611
|
+
questions_html = ""
|
|
612
|
+
for idx, quiz in enumerate(quizzes):
|
|
613
|
+
q_id = f"q{idx}"
|
|
614
|
+
question = html.escape(quiz.get("question", ""))
|
|
615
|
+
options = quiz.get("options", [])
|
|
616
|
+
correct = quiz.get("correct_answer", 0)
|
|
617
|
+
explanation = html.escape(quiz.get("explanation", ""))
|
|
618
|
+
|
|
619
|
+
options_html = ""
|
|
620
|
+
for opt_idx, option in enumerate(options):
|
|
621
|
+
opt_letter = chr(65 + opt_idx) # A, B, C, D
|
|
622
|
+
options_html += f'''
|
|
623
|
+
<label class="quiz-option" data-question="{q_id}" data-index="{opt_idx}">
|
|
624
|
+
<input type="radio" name="{q_id}" value="{opt_idx}" onchange="checkAnswer('{q_id}', {opt_idx}, {correct})">
|
|
625
|
+
<span class="option-letter">{opt_letter}</span>
|
|
626
|
+
<span class="option-text">{html.escape(option)}</span>
|
|
627
|
+
</label>'''
|
|
628
|
+
|
|
629
|
+
questions_html += f'''
|
|
630
|
+
<div class="quiz-question" id="question-{q_id}">
|
|
631
|
+
<div class="question-number">Question {idx + 1}</div>
|
|
632
|
+
<div class="question-text">{question}</div>
|
|
633
|
+
<div class="quiz-options">
|
|
634
|
+
{options_html}
|
|
635
|
+
</div>
|
|
636
|
+
<div class="quiz-feedback" id="feedback-{q_id}">
|
|
637
|
+
<div class="feedback-result"></div>
|
|
638
|
+
<div class="feedback-explanation">{explanation}</div>
|
|
639
|
+
</div>
|
|
640
|
+
</div>'''
|
|
641
|
+
|
|
642
|
+
return f'''
|
|
643
|
+
<div class="quiz-container">
|
|
644
|
+
<div class="quiz-header">
|
|
645
|
+
<h2>Test Your Knowledge</h2>
|
|
646
|
+
<p>Answer the following questions to test your understanding of the bash commands.</p>
|
|
647
|
+
<div class="quiz-score">
|
|
648
|
+
<span>Score: </span>
|
|
649
|
+
<span id="score-current">0</span>
|
|
650
|
+
<span> / </span>
|
|
651
|
+
<span id="score-total">{len(quizzes)}</span>
|
|
652
|
+
</div>
|
|
653
|
+
</div>
|
|
654
|
+
|
|
655
|
+
<div class="quiz-questions">
|
|
656
|
+
{questions_html}
|
|
657
|
+
</div>
|
|
658
|
+
|
|
659
|
+
<div class="quiz-actions">
|
|
660
|
+
<button class="btn btn-secondary" onclick="resetQuiz()">Try Again</button>
|
|
661
|
+
</div>
|
|
662
|
+
</div>'''
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
def get_inline_css() -> str:
|
|
666
|
+
"""Return all CSS styles."""
|
|
667
|
+
return '''
|
|
668
|
+
/* CSS Reset and Base */
|
|
669
|
+
*, *::before, *::after {
|
|
670
|
+
box-sizing: border-box;
|
|
671
|
+
margin: 0;
|
|
672
|
+
padding: 0;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
:root {
|
|
676
|
+
--bg-primary: #ffffff;
|
|
677
|
+
--bg-secondary: #f8f9fa;
|
|
678
|
+
--bg-tertiary: #e9ecef;
|
|
679
|
+
--text-primary: #212529;
|
|
680
|
+
--text-secondary: #6c757d;
|
|
681
|
+
--text-muted: #adb5bd;
|
|
682
|
+
--border-color: #dee2e6;
|
|
683
|
+
--accent-primary: #4285f4;
|
|
684
|
+
--accent-success: #34a853;
|
|
685
|
+
--accent-warning: #fbbc05;
|
|
686
|
+
--accent-danger: #ea4335;
|
|
687
|
+
--shadow-sm: 0 1px 3px rgba(0,0,0,0.1);
|
|
688
|
+
--shadow-md: 0 4px 6px rgba(0,0,0,0.1);
|
|
689
|
+
--shadow-lg: 0 10px 25px rgba(0,0,0,0.15);
|
|
690
|
+
--radius-sm: 4px;
|
|
691
|
+
--radius-md: 8px;
|
|
692
|
+
--radius-lg: 12px;
|
|
693
|
+
--font-mono: 'SF Mono', 'Fira Code', 'Consolas', monospace;
|
|
694
|
+
--font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
695
|
+
--transition-fast: 150ms ease;
|
|
696
|
+
--transition-normal: 250ms ease;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
[data-theme="dark"] {
|
|
700
|
+
--bg-primary: #1a1a2e;
|
|
701
|
+
--bg-secondary: #16213e;
|
|
702
|
+
--bg-tertiary: #0f3460;
|
|
703
|
+
--text-primary: #eaeaea;
|
|
704
|
+
--text-secondary: #a0a0a0;
|
|
705
|
+
--text-muted: #666666;
|
|
706
|
+
--border-color: #2d2d4a;
|
|
707
|
+
--shadow-sm: 0 1px 3px rgba(0,0,0,0.3);
|
|
708
|
+
--shadow-md: 0 4px 6px rgba(0,0,0,0.3);
|
|
709
|
+
--shadow-lg: 0 10px 25px rgba(0,0,0,0.4);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
html {
|
|
713
|
+
font-size: 16px;
|
|
714
|
+
scroll-behavior: smooth;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
body {
|
|
718
|
+
font-family: var(--font-sans);
|
|
719
|
+
background: var(--bg-secondary);
|
|
720
|
+
color: var(--text-primary);
|
|
721
|
+
line-height: 1.6;
|
|
722
|
+
min-height: 100vh;
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/* Container */
|
|
726
|
+
.container {
|
|
727
|
+
max-width: 1400px;
|
|
728
|
+
margin: 0 auto;
|
|
729
|
+
padding: 20px;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
/* Header */
|
|
733
|
+
.header {
|
|
734
|
+
display: flex;
|
|
735
|
+
justify-content: space-between;
|
|
736
|
+
align-items: center;
|
|
737
|
+
padding: 24px 32px;
|
|
738
|
+
background: var(--bg-primary);
|
|
739
|
+
border-radius: var(--radius-lg);
|
|
740
|
+
box-shadow: var(--shadow-md);
|
|
741
|
+
margin-bottom: 20px;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
.header h1 {
|
|
745
|
+
font-size: 1.75rem;
|
|
746
|
+
font-weight: 700;
|
|
747
|
+
color: var(--text-primary);
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
.subtitle {
|
|
751
|
+
color: var(--text-secondary);
|
|
752
|
+
font-size: 0.9rem;
|
|
753
|
+
margin-top: 4px;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
.theme-toggle {
|
|
757
|
+
background: var(--bg-tertiary);
|
|
758
|
+
border: none;
|
|
759
|
+
border-radius: 50%;
|
|
760
|
+
width: 44px;
|
|
761
|
+
height: 44px;
|
|
762
|
+
cursor: pointer;
|
|
763
|
+
display: flex;
|
|
764
|
+
align-items: center;
|
|
765
|
+
justify-content: center;
|
|
766
|
+
transition: var(--transition-fast);
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
.theme-toggle:hover {
|
|
770
|
+
transform: scale(1.05);
|
|
771
|
+
box-shadow: var(--shadow-sm);
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
.theme-icon {
|
|
775
|
+
font-size: 1.25rem;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
.dark-icon { display: none; }
|
|
779
|
+
[data-theme="dark"] .light-icon { display: none; }
|
|
780
|
+
[data-theme="dark"] .dark-icon { display: inline; }
|
|
781
|
+
|
|
782
|
+
/* Tabs */
|
|
783
|
+
.tabs {
|
|
784
|
+
display: flex;
|
|
785
|
+
gap: 8px;
|
|
786
|
+
padding: 8px;
|
|
787
|
+
background: var(--bg-primary);
|
|
788
|
+
border-radius: var(--radius-lg);
|
|
789
|
+
box-shadow: var(--shadow-sm);
|
|
790
|
+
margin-bottom: 20px;
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
.tab {
|
|
794
|
+
flex: 1;
|
|
795
|
+
display: flex;
|
|
796
|
+
align-items: center;
|
|
797
|
+
justify-content: center;
|
|
798
|
+
gap: 8px;
|
|
799
|
+
padding: 14px 20px;
|
|
800
|
+
background: transparent;
|
|
801
|
+
border: none;
|
|
802
|
+
border-radius: var(--radius-md);
|
|
803
|
+
color: var(--text-secondary);
|
|
804
|
+
font-size: 0.95rem;
|
|
805
|
+
font-weight: 500;
|
|
806
|
+
cursor: pointer;
|
|
807
|
+
transition: var(--transition-fast);
|
|
808
|
+
position: relative;
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
.tab:hover {
|
|
812
|
+
background: var(--bg-tertiary);
|
|
813
|
+
color: var(--text-primary);
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
.tab.active {
|
|
817
|
+
background: var(--accent-primary);
|
|
818
|
+
color: white;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
.tab-icon {
|
|
822
|
+
font-size: 1.1rem;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
.tab-key {
|
|
826
|
+
position: absolute;
|
|
827
|
+
top: 4px;
|
|
828
|
+
right: 8px;
|
|
829
|
+
font-size: 0.7rem;
|
|
830
|
+
opacity: 0.5;
|
|
831
|
+
font-family: var(--font-mono);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/* Content Panels */
|
|
835
|
+
.content {
|
|
836
|
+
background: var(--bg-primary);
|
|
837
|
+
border-radius: var(--radius-lg);
|
|
838
|
+
box-shadow: var(--shadow-md);
|
|
839
|
+
min-height: 500px;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
.panel {
|
|
843
|
+
display: none;
|
|
844
|
+
padding: 32px;
|
|
845
|
+
animation: fadeIn 0.3s ease;
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
.panel.active {
|
|
849
|
+
display: block;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
@keyframes fadeIn {
|
|
853
|
+
from { opacity: 0; transform: translateY(10px); }
|
|
854
|
+
to { opacity: 1; transform: translateY(0); }
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
/* Dashboard / Overview */
|
|
858
|
+
.dashboard {
|
|
859
|
+
display: flex;
|
|
860
|
+
flex-direction: column;
|
|
861
|
+
gap: 24px;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
.stats-grid {
|
|
865
|
+
display: grid;
|
|
866
|
+
grid-template-columns: repeat(4, 1fr);
|
|
867
|
+
gap: 16px;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
.stat-card {
|
|
871
|
+
background: var(--bg-secondary);
|
|
872
|
+
padding: 24px;
|
|
873
|
+
border-radius: var(--radius-md);
|
|
874
|
+
text-align: center;
|
|
875
|
+
transition: var(--transition-fast);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
.stat-card:hover {
|
|
879
|
+
transform: translateY(-2px);
|
|
880
|
+
box-shadow: var(--shadow-sm);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
.stat-value {
|
|
884
|
+
font-size: 2.5rem;
|
|
885
|
+
font-weight: 700;
|
|
886
|
+
color: var(--accent-primary);
|
|
887
|
+
line-height: 1.2;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
.stat-value.date-value {
|
|
891
|
+
font-size: 1rem;
|
|
892
|
+
font-family: var(--font-mono);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
.stat-label {
|
|
896
|
+
font-size: 0.9rem;
|
|
897
|
+
color: var(--text-secondary);
|
|
898
|
+
margin-top: 8px;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
.charts-row {
|
|
902
|
+
display: grid;
|
|
903
|
+
grid-template-columns: repeat(2, 1fr);
|
|
904
|
+
gap: 24px;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
.chart-card {
|
|
908
|
+
background: var(--bg-secondary);
|
|
909
|
+
padding: 24px;
|
|
910
|
+
border-radius: var(--radius-md);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
.chart-card.wide {
|
|
914
|
+
grid-column: span 1;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
.chart-card h3 {
|
|
918
|
+
font-size: 1.1rem;
|
|
919
|
+
font-weight: 600;
|
|
920
|
+
margin-bottom: 20px;
|
|
921
|
+
color: var(--text-primary);
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
/* Complexity Bars */
|
|
925
|
+
.complexity-bars {
|
|
926
|
+
display: flex;
|
|
927
|
+
flex-direction: column;
|
|
928
|
+
gap: 16px;
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
.complexity-row {
|
|
932
|
+
display: grid;
|
|
933
|
+
grid-template-columns: 100px 1fr 50px;
|
|
934
|
+
align-items: center;
|
|
935
|
+
gap: 12px;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
.complexity-label {
|
|
939
|
+
font-size: 0.85rem;
|
|
940
|
+
font-weight: 500;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
.complexity-label.simple { color: var(--accent-success); }
|
|
944
|
+
.complexity-label.intermediate { color: var(--accent-warning); }
|
|
945
|
+
.complexity-label.advanced { color: var(--accent-danger); }
|
|
946
|
+
|
|
947
|
+
.complexity-bar-bg {
|
|
948
|
+
height: 24px;
|
|
949
|
+
background: var(--bg-tertiary);
|
|
950
|
+
border-radius: var(--radius-sm);
|
|
951
|
+
overflow: hidden;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
.complexity-bar {
|
|
955
|
+
height: 100%;
|
|
956
|
+
border-radius: var(--radius-sm);
|
|
957
|
+
transition: width 0.5s ease;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
.complexity-bar.simple { background: var(--accent-success); }
|
|
961
|
+
.complexity-bar.intermediate { background: var(--accent-warning); }
|
|
962
|
+
.complexity-bar.advanced { background: var(--accent-danger); }
|
|
963
|
+
|
|
964
|
+
.complexity-count {
|
|
965
|
+
font-size: 0.9rem;
|
|
966
|
+
font-weight: 600;
|
|
967
|
+
text-align: right;
|
|
968
|
+
color: var(--text-secondary);
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
/* Pie Chart */
|
|
972
|
+
.pie-container {
|
|
973
|
+
display: flex;
|
|
974
|
+
align-items: center;
|
|
975
|
+
gap: 24px;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
.pie-chart {
|
|
979
|
+
width: 160px;
|
|
980
|
+
height: 160px;
|
|
981
|
+
flex-shrink: 0;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
.category-legend {
|
|
985
|
+
display: flex;
|
|
986
|
+
flex-direction: column;
|
|
987
|
+
gap: 8px;
|
|
988
|
+
flex: 1;
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
.legend-item {
|
|
992
|
+
display: flex;
|
|
993
|
+
align-items: center;
|
|
994
|
+
gap: 8px;
|
|
995
|
+
font-size: 0.85rem;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
.legend-color {
|
|
999
|
+
width: 12px;
|
|
1000
|
+
height: 12px;
|
|
1001
|
+
border-radius: 2px;
|
|
1002
|
+
flex-shrink: 0;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
.legend-label {
|
|
1006
|
+
flex: 1;
|
|
1007
|
+
color: var(--text-primary);
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
.legend-count {
|
|
1011
|
+
color: var(--text-secondary);
|
|
1012
|
+
font-weight: 500;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
/* Top Commands */
|
|
1016
|
+
.top-commands {
|
|
1017
|
+
display: flex;
|
|
1018
|
+
flex-direction: column;
|
|
1019
|
+
gap: 12px;
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
.top-command-item {
|
|
1023
|
+
display: grid;
|
|
1024
|
+
grid-template-columns: 120px 1fr 50px;
|
|
1025
|
+
align-items: center;
|
|
1026
|
+
gap: 12px;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
.top-command-name code {
|
|
1030
|
+
font-size: 0.85rem;
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
.top-command-bar-container {
|
|
1034
|
+
height: 20px;
|
|
1035
|
+
background: var(--bg-tertiary);
|
|
1036
|
+
border-radius: var(--radius-sm);
|
|
1037
|
+
overflow: hidden;
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
.top-command-bar {
|
|
1041
|
+
height: 100%;
|
|
1042
|
+
background: linear-gradient(90deg, var(--accent-primary), #7baaf7);
|
|
1043
|
+
border-radius: var(--radius-sm);
|
|
1044
|
+
transition: width 0.5s ease;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
.top-command-count {
|
|
1048
|
+
font-size: 0.9rem;
|
|
1049
|
+
font-weight: 600;
|
|
1050
|
+
text-align: right;
|
|
1051
|
+
color: var(--text-secondary);
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
/* New Commands */
|
|
1055
|
+
.new-commands {
|
|
1056
|
+
display: flex;
|
|
1057
|
+
flex-wrap: wrap;
|
|
1058
|
+
gap: 12px;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
.new-command-chip {
|
|
1062
|
+
display: flex;
|
|
1063
|
+
flex-direction: column;
|
|
1064
|
+
gap: 4px;
|
|
1065
|
+
padding: 12px 16px;
|
|
1066
|
+
background: var(--bg-tertiary);
|
|
1067
|
+
border-radius: var(--radius-md);
|
|
1068
|
+
border-left: 3px solid var(--accent-success);
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
.new-command-chip code {
|
|
1072
|
+
font-size: 0.9rem;
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
.first-seen {
|
|
1076
|
+
font-size: 0.75rem;
|
|
1077
|
+
color: var(--text-muted);
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
/* Commands Tab */
|
|
1081
|
+
.commands-container {
|
|
1082
|
+
display: flex;
|
|
1083
|
+
flex-direction: column;
|
|
1084
|
+
gap: 20px;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
.commands-toolbar {
|
|
1088
|
+
display: flex;
|
|
1089
|
+
gap: 16px;
|
|
1090
|
+
align-items: center;
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
.search-box {
|
|
1094
|
+
flex: 1;
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
.search-box input {
|
|
1098
|
+
width: 100%;
|
|
1099
|
+
padding: 12px 16px;
|
|
1100
|
+
border: 2px solid var(--border-color);
|
|
1101
|
+
border-radius: var(--radius-md);
|
|
1102
|
+
font-size: 0.95rem;
|
|
1103
|
+
background: var(--bg-secondary);
|
|
1104
|
+
color: var(--text-primary);
|
|
1105
|
+
transition: var(--transition-fast);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
.search-box input:focus {
|
|
1109
|
+
outline: none;
|
|
1110
|
+
border-color: var(--accent-primary);
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
.sort-controls {
|
|
1114
|
+
display: flex;
|
|
1115
|
+
align-items: center;
|
|
1116
|
+
gap: 8px;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
.sort-controls label {
|
|
1120
|
+
font-size: 0.9rem;
|
|
1121
|
+
color: var(--text-secondary);
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
.sort-controls select {
|
|
1125
|
+
padding: 10px 14px;
|
|
1126
|
+
border: 2px solid var(--border-color);
|
|
1127
|
+
border-radius: var(--radius-md);
|
|
1128
|
+
background: var(--bg-secondary);
|
|
1129
|
+
color: var(--text-primary);
|
|
1130
|
+
font-size: 0.9rem;
|
|
1131
|
+
cursor: pointer;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
.filter-chips {
|
|
1135
|
+
display: flex;
|
|
1136
|
+
flex-wrap: wrap;
|
|
1137
|
+
gap: 8px;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
.filter-chip {
|
|
1141
|
+
padding: 8px 16px;
|
|
1142
|
+
border: 2px solid var(--border-color);
|
|
1143
|
+
border-radius: 20px;
|
|
1144
|
+
background: transparent;
|
|
1145
|
+
color: var(--text-secondary);
|
|
1146
|
+
font-size: 0.85rem;
|
|
1147
|
+
cursor: pointer;
|
|
1148
|
+
transition: var(--transition-fast);
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
.filter-chip:hover {
|
|
1152
|
+
border-color: var(--accent-primary);
|
|
1153
|
+
color: var(--accent-primary);
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
.filter-chip.active {
|
|
1157
|
+
background: var(--accent-primary);
|
|
1158
|
+
border-color: var(--accent-primary);
|
|
1159
|
+
color: white;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
.commands-list {
|
|
1163
|
+
display: flex;
|
|
1164
|
+
flex-direction: column;
|
|
1165
|
+
gap: 12px;
|
|
1166
|
+
}
|
|
1167
|
+
|
|
1168
|
+
.command-card {
|
|
1169
|
+
border: 1px solid var(--border-color);
|
|
1170
|
+
border-radius: var(--radius-md);
|
|
1171
|
+
overflow: hidden;
|
|
1172
|
+
transition: var(--transition-fast);
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
.command-card:hover {
|
|
1176
|
+
box-shadow: var(--shadow-sm);
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
.command-card.hidden {
|
|
1180
|
+
display: none;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
.command-header {
|
|
1184
|
+
display: flex;
|
|
1185
|
+
justify-content: space-between;
|
|
1186
|
+
align-items: center;
|
|
1187
|
+
padding: 16px 20px;
|
|
1188
|
+
background: var(--bg-secondary);
|
|
1189
|
+
cursor: pointer;
|
|
1190
|
+
transition: var(--transition-fast);
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
.command-header:hover {
|
|
1194
|
+
background: var(--bg-tertiary);
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
.command-main {
|
|
1198
|
+
display: flex;
|
|
1199
|
+
align-items: center;
|
|
1200
|
+
gap: 12px;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
.command-meta {
|
|
1204
|
+
display: flex;
|
|
1205
|
+
align-items: center;
|
|
1206
|
+
gap: 16px;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
.frequency {
|
|
1210
|
+
font-size: 0.85rem;
|
|
1211
|
+
color: var(--text-secondary);
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
.expand-icon {
|
|
1215
|
+
color: var(--text-muted);
|
|
1216
|
+
transition: transform var(--transition-fast);
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
.command-card.expanded .expand-icon {
|
|
1220
|
+
transform: rotate(180deg);
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
.command-details {
|
|
1224
|
+
display: none;
|
|
1225
|
+
padding: 20px;
|
|
1226
|
+
border-top: 1px solid var(--border-color);
|
|
1227
|
+
background: var(--bg-primary);
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
.command-details.show {
|
|
1231
|
+
display: block;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
.command-details h5 {
|
|
1235
|
+
font-size: 0.85rem;
|
|
1236
|
+
font-weight: 600;
|
|
1237
|
+
color: var(--text-secondary);
|
|
1238
|
+
margin-bottom: 8px;
|
|
1239
|
+
text-transform: uppercase;
|
|
1240
|
+
letter-spacing: 0.5px;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
.command-details > div {
|
|
1244
|
+
margin-bottom: 20px;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
.command-details > div:last-child {
|
|
1248
|
+
margin-bottom: 0;
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
.flags-list {
|
|
1252
|
+
list-style: none;
|
|
1253
|
+
display: flex;
|
|
1254
|
+
flex-direction: column;
|
|
1255
|
+
gap: 8px;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1258
|
+
.flags-list li {
|
|
1259
|
+
display: flex;
|
|
1260
|
+
align-items: baseline;
|
|
1261
|
+
gap: 8px;
|
|
1262
|
+
font-size: 0.9rem;
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
.output-preview {
|
|
1266
|
+
background: var(--bg-secondary);
|
|
1267
|
+
padding: 16px;
|
|
1268
|
+
border-radius: var(--radius-md);
|
|
1269
|
+
font-family: var(--font-mono);
|
|
1270
|
+
font-size: 0.85rem;
|
|
1271
|
+
overflow-x: auto;
|
|
1272
|
+
white-space: pre-wrap;
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
/* Syntax Highlighting */
|
|
1276
|
+
code, pre {
|
|
1277
|
+
font-family: var(--font-mono);
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
code.cmd, .syntax-highlighted .cmd {
|
|
1281
|
+
color: #4285f4;
|
|
1282
|
+
font-weight: 600;
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
code.flag, .syntax-highlighted .flag {
|
|
1286
|
+
color: #34a853;
|
|
1287
|
+
}
|
|
1288
|
+
|
|
1289
|
+
.syntax-highlighted .string {
|
|
1290
|
+
color: #ff6d01;
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
.syntax-highlighted .operator {
|
|
1294
|
+
color: #9c27b0;
|
|
1295
|
+
font-weight: 600;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
.syntax-highlighted .path {
|
|
1299
|
+
color: #6c757d;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
.syntax-highlighted .variable {
|
|
1303
|
+
color: #ea4335;
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
.syntax-highlighted {
|
|
1307
|
+
background: var(--bg-secondary);
|
|
1308
|
+
padding: 12px 16px;
|
|
1309
|
+
border-radius: var(--radius-md);
|
|
1310
|
+
font-size: 0.9rem;
|
|
1311
|
+
overflow-x: auto;
|
|
1312
|
+
white-space: pre-wrap;
|
|
1313
|
+
}
|
|
1314
|
+
|
|
1315
|
+
/* Badges */
|
|
1316
|
+
.complexity-badge {
|
|
1317
|
+
padding: 4px 10px;
|
|
1318
|
+
border-radius: 12px;
|
|
1319
|
+
font-size: 0.75rem;
|
|
1320
|
+
font-weight: 600;
|
|
1321
|
+
text-transform: uppercase;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
.complexity-badge.simple {
|
|
1325
|
+
background: rgba(52, 168, 83, 0.15);
|
|
1326
|
+
color: var(--accent-success);
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
.complexity-badge.intermediate {
|
|
1330
|
+
background: rgba(251, 188, 5, 0.15);
|
|
1331
|
+
color: #d49a00;
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
.complexity-badge.advanced {
|
|
1335
|
+
background: rgba(234, 67, 53, 0.15);
|
|
1336
|
+
color: var(--accent-danger);
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
.category-badge {
|
|
1340
|
+
padding: 4px 10px;
|
|
1341
|
+
border-radius: 12px;
|
|
1342
|
+
font-size: 0.75rem;
|
|
1343
|
+
background: var(--bg-tertiary);
|
|
1344
|
+
color: var(--text-secondary);
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
/* Lessons Tab */
|
|
1348
|
+
.lessons-container {
|
|
1349
|
+
display: flex;
|
|
1350
|
+
flex-direction: column;
|
|
1351
|
+
gap: 32px;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
.lesson-section {
|
|
1355
|
+
border: 1px solid var(--border-color);
|
|
1356
|
+
border-radius: var(--radius-lg);
|
|
1357
|
+
overflow: hidden;
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
.lesson-title {
|
|
1361
|
+
display: flex;
|
|
1362
|
+
align-items: center;
|
|
1363
|
+
gap: 12px;
|
|
1364
|
+
padding: 20px 24px;
|
|
1365
|
+
background: var(--bg-secondary);
|
|
1366
|
+
font-size: 1.25rem;
|
|
1367
|
+
font-weight: 600;
|
|
1368
|
+
border-bottom: 1px solid var(--border-color);
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
.lesson-icon {
|
|
1372
|
+
font-size: 1.5rem;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
.lesson-count {
|
|
1376
|
+
font-size: 0.9rem;
|
|
1377
|
+
font-weight: 400;
|
|
1378
|
+
color: var(--text-secondary);
|
|
1379
|
+
margin-left: auto;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
.lesson-content {
|
|
1383
|
+
padding: 24px;
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
.concept-overview {
|
|
1387
|
+
margin-bottom: 24px;
|
|
1388
|
+
padding: 16px;
|
|
1389
|
+
background: var(--bg-secondary);
|
|
1390
|
+
border-radius: var(--radius-md);
|
|
1391
|
+
border-left: 4px solid var(--accent-primary);
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
.concept-overview h4 {
|
|
1395
|
+
font-size: 0.9rem;
|
|
1396
|
+
font-weight: 600;
|
|
1397
|
+
margin-bottom: 8px;
|
|
1398
|
+
color: var(--accent-primary);
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
.concept-overview p {
|
|
1402
|
+
color: var(--text-secondary);
|
|
1403
|
+
line-height: 1.7;
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
.lesson-commands h4 {
|
|
1407
|
+
font-size: 1rem;
|
|
1408
|
+
font-weight: 600;
|
|
1409
|
+
margin-bottom: 16px;
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
.lesson-command {
|
|
1413
|
+
padding: 16px;
|
|
1414
|
+
background: var(--bg-secondary);
|
|
1415
|
+
border-radius: var(--radius-md);
|
|
1416
|
+
margin-bottom: 12px;
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
.lesson-command:last-child {
|
|
1420
|
+
margin-bottom: 0;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
.lesson-command-header {
|
|
1424
|
+
display: flex;
|
|
1425
|
+
align-items: center;
|
|
1426
|
+
gap: 12px;
|
|
1427
|
+
margin-bottom: 12px;
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
.lesson-description {
|
|
1431
|
+
margin-top: 12px;
|
|
1432
|
+
color: var(--text-secondary);
|
|
1433
|
+
font-size: 0.9rem;
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
.patterns {
|
|
1437
|
+
margin-top: 24px;
|
|
1438
|
+
padding-top: 20px;
|
|
1439
|
+
border-top: 1px solid var(--border-color);
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
.patterns h4 {
|
|
1443
|
+
font-size: 0.95rem;
|
|
1444
|
+
font-weight: 600;
|
|
1445
|
+
margin-bottom: 12px;
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
.patterns ul {
|
|
1449
|
+
list-style: none;
|
|
1450
|
+
display: flex;
|
|
1451
|
+
flex-direction: column;
|
|
1452
|
+
gap: 8px;
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
.patterns li {
|
|
1456
|
+
display: flex;
|
|
1457
|
+
align-items: center;
|
|
1458
|
+
gap: 8px;
|
|
1459
|
+
font-size: 0.9rem;
|
|
1460
|
+
color: var(--text-secondary);
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
.patterns li::before {
|
|
1464
|
+
content: "\\2022";
|
|
1465
|
+
color: var(--accent-primary);
|
|
1466
|
+
font-weight: bold;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
/* Quiz Tab */
|
|
1470
|
+
.quiz-container {
|
|
1471
|
+
max-width: 800px;
|
|
1472
|
+
margin: 0 auto;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
.quiz-header {
|
|
1476
|
+
text-align: center;
|
|
1477
|
+
margin-bottom: 32px;
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
.quiz-header h2 {
|
|
1481
|
+
font-size: 1.5rem;
|
|
1482
|
+
margin-bottom: 8px;
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
.quiz-header p {
|
|
1486
|
+
color: var(--text-secondary);
|
|
1487
|
+
margin-bottom: 20px;
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
.quiz-score {
|
|
1491
|
+
display: inline-flex;
|
|
1492
|
+
align-items: center;
|
|
1493
|
+
gap: 4px;
|
|
1494
|
+
padding: 12px 24px;
|
|
1495
|
+
background: var(--bg-secondary);
|
|
1496
|
+
border-radius: var(--radius-md);
|
|
1497
|
+
font-size: 1.1rem;
|
|
1498
|
+
font-weight: 600;
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
#score-current {
|
|
1502
|
+
color: var(--accent-primary);
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
.quiz-questions {
|
|
1506
|
+
display: flex;
|
|
1507
|
+
flex-direction: column;
|
|
1508
|
+
gap: 24px;
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
.quiz-question {
|
|
1512
|
+
background: var(--bg-secondary);
|
|
1513
|
+
border-radius: var(--radius-md);
|
|
1514
|
+
padding: 24px;
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
.question-number {
|
|
1518
|
+
font-size: 0.8rem;
|
|
1519
|
+
font-weight: 600;
|
|
1520
|
+
text-transform: uppercase;
|
|
1521
|
+
color: var(--accent-primary);
|
|
1522
|
+
margin-bottom: 8px;
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
.question-text {
|
|
1526
|
+
font-size: 1.1rem;
|
|
1527
|
+
font-weight: 500;
|
|
1528
|
+
margin-bottom: 20px;
|
|
1529
|
+
line-height: 1.5;
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
.quiz-options {
|
|
1533
|
+
display: flex;
|
|
1534
|
+
flex-direction: column;
|
|
1535
|
+
gap: 12px;
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
.quiz-option {
|
|
1539
|
+
display: flex;
|
|
1540
|
+
align-items: center;
|
|
1541
|
+
gap: 12px;
|
|
1542
|
+
padding: 14px 18px;
|
|
1543
|
+
background: var(--bg-primary);
|
|
1544
|
+
border: 2px solid var(--border-color);
|
|
1545
|
+
border-radius: var(--radius-md);
|
|
1546
|
+
cursor: pointer;
|
|
1547
|
+
transition: var(--transition-fast);
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
.quiz-option:hover {
|
|
1551
|
+
border-color: var(--accent-primary);
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
.quiz-option input {
|
|
1555
|
+
display: none;
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
.option-letter {
|
|
1559
|
+
width: 28px;
|
|
1560
|
+
height: 28px;
|
|
1561
|
+
display: flex;
|
|
1562
|
+
align-items: center;
|
|
1563
|
+
justify-content: center;
|
|
1564
|
+
background: var(--bg-tertiary);
|
|
1565
|
+
border-radius: 50%;
|
|
1566
|
+
font-size: 0.85rem;
|
|
1567
|
+
font-weight: 600;
|
|
1568
|
+
flex-shrink: 0;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
.option-text {
|
|
1572
|
+
flex: 1;
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
.quiz-option.correct {
|
|
1576
|
+
border-color: var(--accent-success);
|
|
1577
|
+
background: rgba(52, 168, 83, 0.1);
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
.quiz-option.correct .option-letter {
|
|
1581
|
+
background: var(--accent-success);
|
|
1582
|
+
color: white;
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
.quiz-option.incorrect {
|
|
1586
|
+
border-color: var(--accent-danger);
|
|
1587
|
+
background: rgba(234, 67, 53, 0.1);
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
.quiz-option.incorrect .option-letter {
|
|
1591
|
+
background: var(--accent-danger);
|
|
1592
|
+
color: white;
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
.quiz-option.disabled {
|
|
1596
|
+
pointer-events: none;
|
|
1597
|
+
opacity: 0.7;
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
.quiz-feedback {
|
|
1601
|
+
display: none;
|
|
1602
|
+
margin-top: 16px;
|
|
1603
|
+
padding: 16px;
|
|
1604
|
+
border-radius: var(--radius-md);
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
.quiz-feedback.show {
|
|
1608
|
+
display: block;
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
.quiz-feedback.correct {
|
|
1612
|
+
background: rgba(52, 168, 83, 0.1);
|
|
1613
|
+
border-left: 4px solid var(--accent-success);
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
.quiz-feedback.incorrect {
|
|
1617
|
+
background: rgba(234, 67, 53, 0.1);
|
|
1618
|
+
border-left: 4px solid var(--accent-danger);
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
.feedback-result {
|
|
1622
|
+
font-weight: 600;
|
|
1623
|
+
margin-bottom: 8px;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
.feedback-explanation {
|
|
1627
|
+
color: var(--text-secondary);
|
|
1628
|
+
font-size: 0.9rem;
|
|
1629
|
+
line-height: 1.6;
|
|
1630
|
+
}
|
|
1631
|
+
|
|
1632
|
+
.quiz-actions {
|
|
1633
|
+
display: flex;
|
|
1634
|
+
justify-content: center;
|
|
1635
|
+
margin-top: 32px;
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
.btn {
|
|
1639
|
+
padding: 14px 28px;
|
|
1640
|
+
border: none;
|
|
1641
|
+
border-radius: var(--radius-md);
|
|
1642
|
+
font-size: 1rem;
|
|
1643
|
+
font-weight: 600;
|
|
1644
|
+
cursor: pointer;
|
|
1645
|
+
transition: var(--transition-fast);
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
.btn-secondary {
|
|
1649
|
+
background: var(--bg-tertiary);
|
|
1650
|
+
color: var(--text-primary);
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1653
|
+
.btn-secondary:hover {
|
|
1654
|
+
background: var(--border-color);
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
/* Footer */
|
|
1658
|
+
.footer {
|
|
1659
|
+
text-align: center;
|
|
1660
|
+
padding: 20px;
|
|
1661
|
+
margin-top: 20px;
|
|
1662
|
+
color: var(--text-muted);
|
|
1663
|
+
font-size: 0.85rem;
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
/* Empty State */
|
|
1667
|
+
.empty-state {
|
|
1668
|
+
text-align: center;
|
|
1669
|
+
padding: 48px 24px;
|
|
1670
|
+
color: var(--text-secondary);
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
/* Print Styles */
|
|
1674
|
+
@media print {
|
|
1675
|
+
body {
|
|
1676
|
+
background: white;
|
|
1677
|
+
color: black;
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
.header, .tabs, .footer, .theme-toggle, .quiz-actions {
|
|
1681
|
+
display: none;
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
.content {
|
|
1685
|
+
box-shadow: none;
|
|
1686
|
+
}
|
|
1687
|
+
|
|
1688
|
+
.panel {
|
|
1689
|
+
display: block !important;
|
|
1690
|
+
page-break-inside: avoid;
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
.panel::before {
|
|
1694
|
+
content: attr(aria-labelledby);
|
|
1695
|
+
display: block;
|
|
1696
|
+
font-size: 1.5rem;
|
|
1697
|
+
font-weight: bold;
|
|
1698
|
+
margin-bottom: 1rem;
|
|
1699
|
+
padding-bottom: 0.5rem;
|
|
1700
|
+
border-bottom: 2px solid #333;
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
.command-details {
|
|
1704
|
+
display: block !important;
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
|
|
1708
|
+
/* Responsive */
|
|
1709
|
+
@media (max-width: 1024px) {
|
|
1710
|
+
.stats-grid {
|
|
1711
|
+
grid-template-columns: repeat(2, 1fr);
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
.charts-row {
|
|
1715
|
+
grid-template-columns: 1fr;
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
.pie-container {
|
|
1719
|
+
flex-direction: column;
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
@media (max-width: 768px) {
|
|
1724
|
+
.container {
|
|
1725
|
+
padding: 12px;
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
.header {
|
|
1729
|
+
padding: 16px 20px;
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
.header h1 {
|
|
1733
|
+
font-size: 1.25rem;
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
.tab-label {
|
|
1737
|
+
display: none;
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
.tab {
|
|
1741
|
+
padding: 12px;
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
.tab-icon {
|
|
1745
|
+
font-size: 1.25rem;
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
.panel {
|
|
1749
|
+
padding: 20px;
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
.stats-grid {
|
|
1753
|
+
grid-template-columns: repeat(2, 1fr);
|
|
1754
|
+
gap: 12px;
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
.stat-value {
|
|
1758
|
+
font-size: 1.75rem;
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
.commands-toolbar {
|
|
1762
|
+
flex-direction: column;
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
.command-header {
|
|
1766
|
+
flex-direction: column;
|
|
1767
|
+
align-items: flex-start;
|
|
1768
|
+
gap: 12px;
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
.command-meta {
|
|
1772
|
+
width: 100%;
|
|
1773
|
+
justify-content: space-between;
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
'''
|
|
1777
|
+
|
|
1778
|
+
|
|
1779
|
+
def get_inline_js(quizzes: list[dict]) -> str:
|
|
1780
|
+
"""Return all JavaScript code."""
|
|
1781
|
+
quiz_data = json.dumps(quizzes)
|
|
1782
|
+
|
|
1783
|
+
return f'''
|
|
1784
|
+
// Quiz data
|
|
1785
|
+
const quizData = {quiz_data};
|
|
1786
|
+
let score = 0;
|
|
1787
|
+
let answeredQuestions = new Set();
|
|
1788
|
+
|
|
1789
|
+
// Tab Navigation
|
|
1790
|
+
document.querySelectorAll('.tab').forEach(tab => {{
|
|
1791
|
+
tab.addEventListener('click', () => {{
|
|
1792
|
+
switchTab(tab.dataset.tab);
|
|
1793
|
+
}});
|
|
1794
|
+
}});
|
|
1795
|
+
|
|
1796
|
+
function switchTab(tabName) {{
|
|
1797
|
+
// Update tabs
|
|
1798
|
+
document.querySelectorAll('.tab').forEach(t => {{
|
|
1799
|
+
t.classList.remove('active');
|
|
1800
|
+
t.setAttribute('aria-selected', 'false');
|
|
1801
|
+
}});
|
|
1802
|
+
document.querySelector(`[data-tab="${{tabName}}"]`).classList.add('active');
|
|
1803
|
+
document.querySelector(`[data-tab="${{tabName}}"]`).setAttribute('aria-selected', 'true');
|
|
1804
|
+
|
|
1805
|
+
// Update panels
|
|
1806
|
+
document.querySelectorAll('.panel').forEach(p => {{
|
|
1807
|
+
p.classList.remove('active');
|
|
1808
|
+
}});
|
|
1809
|
+
document.getElementById(`panel-${{tabName}}`).classList.add('active');
|
|
1810
|
+
}}
|
|
1811
|
+
|
|
1812
|
+
// Keyboard navigation
|
|
1813
|
+
document.addEventListener('keydown', (e) => {{
|
|
1814
|
+
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
|
|
1815
|
+
|
|
1816
|
+
const tabs = ['overview', 'commands', 'lessons', 'quiz'];
|
|
1817
|
+
const key = e.key;
|
|
1818
|
+
|
|
1819
|
+
if (key >= '1' && key <= '4') {{
|
|
1820
|
+
e.preventDefault();
|
|
1821
|
+
switchTab(tabs[parseInt(key) - 1]);
|
|
1822
|
+
}}
|
|
1823
|
+
}});
|
|
1824
|
+
|
|
1825
|
+
// Theme Toggle
|
|
1826
|
+
function toggleTheme() {{
|
|
1827
|
+
const html = document.documentElement;
|
|
1828
|
+
const currentTheme = html.getAttribute('data-theme');
|
|
1829
|
+
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
|
1830
|
+
html.setAttribute('data-theme', newTheme);
|
|
1831
|
+
localStorage.setItem('theme', newTheme);
|
|
1832
|
+
}}
|
|
1833
|
+
|
|
1834
|
+
// Load saved theme
|
|
1835
|
+
(function() {{
|
|
1836
|
+
const savedTheme = localStorage.getItem('theme') || 'light';
|
|
1837
|
+
document.documentElement.setAttribute('data-theme', savedTheme);
|
|
1838
|
+
}})();
|
|
1839
|
+
|
|
1840
|
+
// Command expansion
|
|
1841
|
+
function toggleCommand(cmdId) {{
|
|
1842
|
+
const details = document.getElementById(cmdId);
|
|
1843
|
+
const card = details.closest('.command-card');
|
|
1844
|
+
|
|
1845
|
+
details.classList.toggle('show');
|
|
1846
|
+
card.classList.toggle('expanded');
|
|
1847
|
+
}}
|
|
1848
|
+
|
|
1849
|
+
// Command filtering
|
|
1850
|
+
function filterCommands() {{
|
|
1851
|
+
const searchTerm = document.getElementById('command-search').value.toLowerCase();
|
|
1852
|
+
const activeCategory = document.querySelector('.filter-chip.active').dataset.category;
|
|
1853
|
+
|
|
1854
|
+
document.querySelectorAll('.command-card').forEach(card => {{
|
|
1855
|
+
const name = card.dataset.name.toLowerCase();
|
|
1856
|
+
const category = card.dataset.category;
|
|
1857
|
+
|
|
1858
|
+
const matchesSearch = name.includes(searchTerm);
|
|
1859
|
+
const matchesCategory = activeCategory === 'all' || category === activeCategory;
|
|
1860
|
+
|
|
1861
|
+
card.classList.toggle('hidden', !(matchesSearch && matchesCategory));
|
|
1862
|
+
}});
|
|
1863
|
+
}}
|
|
1864
|
+
|
|
1865
|
+
// Category filter chips
|
|
1866
|
+
document.querySelectorAll('.filter-chip').forEach(chip => {{
|
|
1867
|
+
chip.addEventListener('click', () => {{
|
|
1868
|
+
document.querySelectorAll('.filter-chip').forEach(c => c.classList.remove('active'));
|
|
1869
|
+
chip.classList.add('active');
|
|
1870
|
+
filterCommands();
|
|
1871
|
+
}});
|
|
1872
|
+
}});
|
|
1873
|
+
|
|
1874
|
+
// Command sorting
|
|
1875
|
+
function sortCommands() {{
|
|
1876
|
+
const sortBy = document.getElementById('sort-select').value;
|
|
1877
|
+
const list = document.getElementById('commands-list');
|
|
1878
|
+
const cards = Array.from(list.querySelectorAll('.command-card'));
|
|
1879
|
+
|
|
1880
|
+
cards.sort((a, b) => {{
|
|
1881
|
+
switch(sortBy) {{
|
|
1882
|
+
case 'frequency':
|
|
1883
|
+
return parseInt(b.dataset.frequency) - parseInt(a.dataset.frequency);
|
|
1884
|
+
case 'complexity':
|
|
1885
|
+
const order = {{'simple': 1, 'intermediate': 2, 'advanced': 3}};
|
|
1886
|
+
return order[a.dataset.complexity] - order[b.dataset.complexity];
|
|
1887
|
+
case 'category':
|
|
1888
|
+
return a.dataset.category.localeCompare(b.dataset.category);
|
|
1889
|
+
case 'name':
|
|
1890
|
+
return a.dataset.name.localeCompare(b.dataset.name);
|
|
1891
|
+
default:
|
|
1892
|
+
return 0;
|
|
1893
|
+
}}
|
|
1894
|
+
}});
|
|
1895
|
+
|
|
1896
|
+
cards.forEach(card => list.appendChild(card));
|
|
1897
|
+
}}
|
|
1898
|
+
|
|
1899
|
+
// Quiz functions
|
|
1900
|
+
function checkAnswer(questionId, selectedIndex, correctIndex) {{
|
|
1901
|
+
if (answeredQuestions.has(questionId)) return;
|
|
1902
|
+
answeredQuestions.add(questionId);
|
|
1903
|
+
|
|
1904
|
+
const question = document.getElementById(`question-${{questionId}}`);
|
|
1905
|
+
const options = question.querySelectorAll('.quiz-option');
|
|
1906
|
+
const feedback = document.getElementById(`feedback-${{questionId}}`);
|
|
1907
|
+
|
|
1908
|
+
const isCorrect = selectedIndex === correctIndex;
|
|
1909
|
+
|
|
1910
|
+
// Mark options
|
|
1911
|
+
options.forEach((opt, idx) => {{
|
|
1912
|
+
opt.classList.add('disabled');
|
|
1913
|
+
if (idx === correctIndex) {{
|
|
1914
|
+
opt.classList.add('correct');
|
|
1915
|
+
}} else if (idx === selectedIndex && !isCorrect) {{
|
|
1916
|
+
opt.classList.add('incorrect');
|
|
1917
|
+
}}
|
|
1918
|
+
}});
|
|
1919
|
+
|
|
1920
|
+
// Show feedback
|
|
1921
|
+
feedback.classList.add('show');
|
|
1922
|
+
feedback.classList.add(isCorrect ? 'correct' : 'incorrect');
|
|
1923
|
+
feedback.querySelector('.feedback-result').textContent = isCorrect ? 'Correct!' : 'Incorrect';
|
|
1924
|
+
|
|
1925
|
+
// Update score
|
|
1926
|
+
if (isCorrect) {{
|
|
1927
|
+
score++;
|
|
1928
|
+
document.getElementById('score-current').textContent = score;
|
|
1929
|
+
}}
|
|
1930
|
+
}}
|
|
1931
|
+
|
|
1932
|
+
function resetQuiz() {{
|
|
1933
|
+
score = 0;
|
|
1934
|
+
answeredQuestions.clear();
|
|
1935
|
+
document.getElementById('score-current').textContent = '0';
|
|
1936
|
+
|
|
1937
|
+
document.querySelectorAll('.quiz-question').forEach(q => {{
|
|
1938
|
+
q.querySelectorAll('.quiz-option').forEach(opt => {{
|
|
1939
|
+
opt.classList.remove('correct', 'incorrect', 'disabled');
|
|
1940
|
+
opt.querySelector('input').checked = false;
|
|
1941
|
+
}});
|
|
1942
|
+
|
|
1943
|
+
const feedback = q.querySelector('.quiz-feedback');
|
|
1944
|
+
feedback.classList.remove('show', 'correct', 'incorrect');
|
|
1945
|
+
}});
|
|
1946
|
+
}}
|
|
1947
|
+
|
|
1948
|
+
// Smooth scrolling for internal links
|
|
1949
|
+
document.querySelectorAll('a[href^="#"]').forEach(anchor => {{
|
|
1950
|
+
anchor.addEventListener('click', function(e) {{
|
|
1951
|
+
e.preventDefault();
|
|
1952
|
+
document.querySelector(this.getAttribute('href')).scrollIntoView({{
|
|
1953
|
+
behavior: 'smooth'
|
|
1954
|
+
}});
|
|
1955
|
+
}});
|
|
1956
|
+
}});
|
|
1957
|
+
'''
|
|
1958
|
+
|
|
1959
|
+
|
|
1960
|
+
if __name__ == "__main__":
|
|
1961
|
+
# Test with sample data
|
|
1962
|
+
sample_analysis = {
|
|
1963
|
+
"stats": {
|
|
1964
|
+
"total_commands": 150,
|
|
1965
|
+
"unique_commands": 45,
|
|
1966
|
+
"unique_utilities": 28,
|
|
1967
|
+
"date_range": {"start": "2025-01-01", "end": "2025-02-05"},
|
|
1968
|
+
"complexity_distribution": {"simple": 80, "intermediate": 50, "advanced": 20}
|
|
1969
|
+
},
|
|
1970
|
+
"commands": [
|
|
1971
|
+
{
|
|
1972
|
+
"base_command": "ls",
|
|
1973
|
+
"full_command": "ls -la /home/user",
|
|
1974
|
+
"category": "File Management",
|
|
1975
|
+
"complexity": "simple",
|
|
1976
|
+
"frequency": 25,
|
|
1977
|
+
"description": "List directory contents with details",
|
|
1978
|
+
"flags": [{"flag": "-l", "description": "Long format"}, {"flag": "-a", "description": "Show hidden files"}],
|
|
1979
|
+
"is_new": False
|
|
1980
|
+
},
|
|
1981
|
+
{
|
|
1982
|
+
"base_command": "grep",
|
|
1983
|
+
"full_command": "grep -r 'pattern' ./src",
|
|
1984
|
+
"category": "Text Processing",
|
|
1985
|
+
"complexity": "intermediate",
|
|
1986
|
+
"frequency": 18,
|
|
1987
|
+
"description": "Search for patterns in files",
|
|
1988
|
+
"flags": [{"flag": "-r", "description": "Recursive search"}],
|
|
1989
|
+
"is_new": True,
|
|
1990
|
+
"first_seen": "2025-01-15"
|
|
1991
|
+
},
|
|
1992
|
+
{
|
|
1993
|
+
"base_command": "find",
|
|
1994
|
+
"full_command": "find . -name '*.py' -exec grep 'import' {} +",
|
|
1995
|
+
"category": "Search",
|
|
1996
|
+
"complexity": "advanced",
|
|
1997
|
+
"frequency": 8,
|
|
1998
|
+
"description": "Find files and execute commands on them",
|
|
1999
|
+
"flags": [{"flag": "-name", "description": "Match filename pattern"}, {"flag": "-exec", "description": "Execute command on results"}],
|
|
2000
|
+
"is_new": True,
|
|
2001
|
+
"first_seen": "2025-01-20"
|
|
2002
|
+
}
|
|
2003
|
+
],
|
|
2004
|
+
"categories": {
|
|
2005
|
+
"File Management": ["ls", "cd", "mkdir", "cp", "mv"],
|
|
2006
|
+
"Text Processing": ["grep", "sed", "awk", "cat"],
|
|
2007
|
+
"Search": ["find", "locate"],
|
|
2008
|
+
"Network": ["curl", "wget"]
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
|
|
2012
|
+
sample_quizzes = [
|
|
2013
|
+
{
|
|
2014
|
+
"question": "What does the -l flag do in the 'ls' command?",
|
|
2015
|
+
"options": ["List only files", "Long format with details", "List hidden files", "List in reverse order"],
|
|
2016
|
+
"correct_answer": 1,
|
|
2017
|
+
"explanation": "The -l flag displays files in long format, showing permissions, owner, size, and modification date."
|
|
2018
|
+
},
|
|
2019
|
+
{
|
|
2020
|
+
"question": "Which command is used to search for text patterns in files?",
|
|
2021
|
+
"options": ["find", "grep", "locate", "which"],
|
|
2022
|
+
"correct_answer": 1,
|
|
2023
|
+
"explanation": "grep (Global Regular Expression Print) searches for text patterns in files using regular expressions."
|
|
2024
|
+
}
|
|
2025
|
+
]
|
|
2026
|
+
|
|
2027
|
+
html_output = generate_html(sample_analysis, sample_quizzes)
|
|
2028
|
+
print(f"Generated HTML length: {len(html_output)} characters")
|
|
2029
|
+
print("HTML generation complete!")
|