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.
@@ -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
- # Check for command chaining
407
- if ' && ' in cmd_string:
408
- commands = cmd_string.split(' && ')
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
- base = cmd.strip().split()[0] if cmd.strip() else ''
411
- if i == 0:
412
- parts.append(_describe_single_command(cmd.strip()))
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.strip())}")
415
- return ', '.join(parts)
416
-
417
- if ' || ' in cmd_string:
418
- commands = cmd_string.split(' || ')
419
- parts.append(_describe_single_command(commands[0].strip()))
420
- parts.append(f"or if that fails, {_describe_single_command(commands[1].strip())}")
421
- return ', '.join(parts)
422
-
423
- if ' | ' in cmd_string:
424
- commands = cmd_string.split(' | ')
425
- parts.append(_describe_single_command(commands[0].strip()))
426
- for cmd in commands[1:]:
427
- parts.append(f"pipes output to {_describe_single_command(cmd.strip())}")
428
- return ', '.join(parts)
429
-
430
- return _describe_single_command(cmd_string)
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: