learn_bash_from_session_data 1.0.3 → 1.0.5
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/bash-learner-output/run-2026-02-05-154214/index.html +3848 -0
- package/bash-learner-output/run-2026-02-05-154214/summary.json +148 -0
- package/bash-learner-output/run-2026-02-05-155427/index.html +3900 -0
- package/bash-learner-output/run-2026-02-05-155427/summary.json +157 -0
- package/bash-learner-output/run-2026-02-05-155949/index.html +4514 -0
- package/bash-learner-output/run-2026-02-05-155949/summary.json +163 -0
- package/package.json +1 -1
- package/scripts/html_generator.py +124 -37
- package/scripts/main.py +190 -17
- package/scripts/quiz_generator.py +84 -23
|
@@ -397,37 +397,98 @@ def _generate_bash_description(cmd_string: str) -> str:
|
|
|
397
397
|
Generate an educational description focusing on bash concepts.
|
|
398
398
|
|
|
399
399
|
Explains what each part of the command does from a bash perspective.
|
|
400
|
+
Handles: &&, ||, |, 2>&1, 2>/dev/null, and combinations.
|
|
400
401
|
"""
|
|
401
402
|
if not cmd_string:
|
|
402
403
|
return "Runs a command"
|
|
403
404
|
|
|
405
|
+
# Clean up redirections for description (note them but don't clutter)
|
|
406
|
+
has_stderr_to_stdout = '2>&1' in cmd_string
|
|
407
|
+
has_stderr_to_null = '2>/dev/null' in cmd_string
|
|
408
|
+
has_stdout_redirect = re.search(r'>\s*\S+', cmd_string) and '2>' not in cmd_string
|
|
409
|
+
|
|
410
|
+
# Remove redirections for parsing (we'll note them separately)
|
|
411
|
+
clean_cmd = re.sub(r'\s*2>&1\s*', ' ', cmd_string)
|
|
412
|
+
clean_cmd = re.sub(r'\s*2>/dev/null\s*', ' ', clean_cmd)
|
|
413
|
+
clean_cmd = re.sub(r'\s*>\s*\S+\s*', ' ', clean_cmd)
|
|
414
|
+
clean_cmd = ' '.join(clean_cmd.split()) # normalize whitespace
|
|
415
|
+
|
|
404
416
|
parts = []
|
|
405
417
|
|
|
406
|
-
#
|
|
407
|
-
if ' && ' in
|
|
408
|
-
commands =
|
|
418
|
+
# Handle && (run if previous succeeds)
|
|
419
|
+
if ' && ' in clean_cmd:
|
|
420
|
+
commands = clean_cmd.split(' && ')
|
|
409
421
|
for i, cmd in enumerate(commands):
|
|
410
|
-
|
|
411
|
-
if
|
|
412
|
-
|
|
422
|
+
cmd = cmd.strip()
|
|
423
|
+
if not cmd:
|
|
424
|
+
continue
|
|
425
|
+
# Handle nested || or | within && segments
|
|
426
|
+
if ' || ' in cmd:
|
|
427
|
+
parts.append(_describe_or_chain(cmd))
|
|
428
|
+
elif ' | ' in cmd:
|
|
429
|
+
parts.append(_describe_pipe_chain(cmd))
|
|
430
|
+
elif i == 0:
|
|
431
|
+
parts.append(_describe_single_command(cmd))
|
|
413
432
|
else:
|
|
414
|
-
parts.append(f"then {_describe_single_command(cmd
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
parts.append(_describe_single_command(
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
433
|
+
parts.append(f"then {_describe_single_command(cmd)}")
|
|
434
|
+
|
|
435
|
+
# Handle || (run if previous fails)
|
|
436
|
+
elif ' || ' in clean_cmd:
|
|
437
|
+
parts.append(_describe_or_chain(clean_cmd))
|
|
438
|
+
|
|
439
|
+
# Handle | (pipe)
|
|
440
|
+
elif ' | ' in clean_cmd:
|
|
441
|
+
parts.append(_describe_pipe_chain(clean_cmd))
|
|
442
|
+
|
|
443
|
+
else:
|
|
444
|
+
parts.append(_describe_single_command(clean_cmd))
|
|
445
|
+
|
|
446
|
+
result = ', '.join(parts)
|
|
447
|
+
|
|
448
|
+
# Add redirection notes
|
|
449
|
+
if has_stderr_to_null:
|
|
450
|
+
result += " (suppressing errors)"
|
|
451
|
+
elif has_stderr_to_stdout:
|
|
452
|
+
result += " (capturing all output)"
|
|
453
|
+
|
|
454
|
+
return result
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def _describe_or_chain(cmd_string: str) -> str:
|
|
458
|
+
"""Describe an || chain (fallback pattern)."""
|
|
459
|
+
commands = cmd_string.split(' || ')
|
|
460
|
+
parts = []
|
|
461
|
+
for i, cmd in enumerate(commands):
|
|
462
|
+
cmd = cmd.strip()
|
|
463
|
+
if not cmd:
|
|
464
|
+
continue
|
|
465
|
+
# Handle pipes within || segments
|
|
466
|
+
if ' | ' in cmd:
|
|
467
|
+
desc = _describe_pipe_chain(cmd)
|
|
468
|
+
else:
|
|
469
|
+
desc = _describe_single_command(cmd)
|
|
470
|
+
|
|
471
|
+
if i == 0:
|
|
472
|
+
parts.append(desc)
|
|
473
|
+
else:
|
|
474
|
+
parts.append(f"or if that fails, {desc}")
|
|
475
|
+
return ', '.join(parts)
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def _describe_pipe_chain(cmd_string: str) -> str:
|
|
479
|
+
"""Describe a pipe chain."""
|
|
480
|
+
commands = cmd_string.split(' | ')
|
|
481
|
+
parts = []
|
|
482
|
+
for i, cmd in enumerate(commands):
|
|
483
|
+
cmd = cmd.strip()
|
|
484
|
+
if not cmd:
|
|
485
|
+
continue
|
|
486
|
+
desc = _describe_single_command(cmd)
|
|
487
|
+
if i == 0:
|
|
488
|
+
parts.append(desc)
|
|
489
|
+
else:
|
|
490
|
+
parts.append(f"pipes to {desc}")
|
|
491
|
+
return ', '.join(parts)
|
|
431
492
|
|
|
432
493
|
|
|
433
494
|
def _describe_single_command(cmd: str) -> str:
|