cc.shellback-kit 0.3.0__tar.gz → 0.4.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/PKG-INFO +27 -5
  2. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/README.md +26 -4
  3. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/pyproject.toml +1 -1
  4. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/src/cc.shellback_kit.egg-info/PKG-INFO +27 -5
  5. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/src/cc_shellback_kit/capsule/FileLogObserver.py +18 -16
  6. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/src/cc_shellback_kit/capsule/JSONFileObserver.py +6 -5
  7. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/src/cc_shellback_kit/core/ArgumentBuilder.py +2 -1
  8. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/src/cc_shellback_kit/core/Command.py +3 -2
  9. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/src/cc_shellback_kit/core/Shell.py +13 -9
  10. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/tests/test_ArgumentBuilder.py +10 -10
  11. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/tests/test_Bash.py +12 -5
  12. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/tests/test_Command.py +6 -6
  13. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/tests/test_CommandResult.py +14 -7
  14. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/tests/test_ConsoleLogObserver.py +13 -20
  15. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/tests/test_FileLogObserver.py +25 -19
  16. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/tests/test_JSONFileObserver.py +11 -5
  17. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/tests/test_MultiObserver.py +11 -3
  18. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/tests/test_SessionContext.py +13 -13
  19. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/tests/test_ShellObserver.py +6 -4
  20. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/tests/test_Shell_handle_cd.py +16 -12
  21. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/tests/test_Shell_handle_export.py +20 -13
  22. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/tests/test_Shell_notify_error.py +10 -7
  23. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/tests/test_Shell_parse_env_vars.py +14 -14
  24. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/tests/test_Shell_resolve_path.py +16 -11
  25. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/tests/test_Shell_run.py +30 -23
  26. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/tests/test_Shell_run_external.py +31 -19
  27. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/tests/test_Shell_validate_executable.py +12 -9
  28. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/tests/test_SilentObserver.py +10 -8
  29. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/setup.cfg +0 -0
  30. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/src/cc.shellback_kit.egg-info/SOURCES.txt +0 -0
  31. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/src/cc.shellback_kit.egg-info/dependency_links.txt +0 -0
  32. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/src/cc.shellback_kit.egg-info/requires.txt +0 -0
  33. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/src/cc.shellback_kit.egg-info/top_level.txt +0 -0
  34. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/src/cc_shellback_kit/__init__.py +0 -0
  35. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/src/cc_shellback_kit/capsule/Bash.py +0 -0
  36. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/src/cc_shellback_kit/capsule/ConsoleLogObserver.py +0 -0
  37. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/src/cc_shellback_kit/capsule/MultiObserver.py +0 -0
  38. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/src/cc_shellback_kit/capsule/SilentObserver.py +0 -0
  39. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/src/cc_shellback_kit/capsule/__init__.py +0 -0
  40. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/src/cc_shellback_kit/core/CommandResult.py +0 -0
  41. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/src/cc_shellback_kit/core/SessionContext.py +0 -0
  42. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/src/cc_shellback_kit/core/ShellObserver.py +0 -0
  43. {cc_shellback_kit-0.3.0 → cc_shellback_kit-0.4.0}/src/cc_shellback_kit/core/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cc.shellback-kit
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Add your description here
5
5
  Requires-Python: >=3.14
6
6
  Description-Content-Type: text/markdown
@@ -10,18 +10,40 @@ Requires-Dist: pytest>=9.0.2
10
10
  Requires-Dist: pytest-timeout>=2.4.0
11
11
  Requires-Dist: ruff>=0.15.6
12
12
 
13
- # CapsuleCore shellback
13
+ # cc-shellback-kit
14
+
15
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
16
+ ![Version](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Rick-torrellas/cc-shellback-kit/badges/version.json)
17
+ [![CI CD](https://github.com/Rick-torrellas/cc-shellback-kit/actions/workflows/main.yaml/badge.svg)](https://github.com/Rick-torrellas/cc-shellback-kit/actions/workflows/main.yaml)
18
+ [![Python Version](https://img.shields.io/badge/python-3.11+-blue?logo=python&logoColor=white)](https://www.python.org/)
19
+ [![Download](https://img.shields.io/github/v/release/Rick-torrellas/cc-shellback-kit?label=Download&color=orange)](https://github.com/Rick-torrellas/cc-shellback-kit/releases)
20
+ [![Ask DeepWiki](https://img.shields.io/badge/DeepWiki-Documentation-blue?logo=gitbook&logoColor=white)](https://deepwiki.com/Rick-torrellas/cc-shellback-kit)
21
+ [![docs](https://img.shields.io/badge/docs-read_now-blue?style=flat-square)](https://rick-torrellas.github.io/cc-shellback-kit/)
14
22
 
15
23
  Shellback is a robust, architecturally-agnostic Python library designed to bridge terminal environments (Bash, CMD, PowerShell) with Python scripts. It provides a clean, decoupled abstraction layer to execute system commands while maintaining persistent session state and cross-platform compatibility.
16
24
 
17
25
  Built with Hexagonal Architecture (Ports and Adapters) principles, Shellback ensures that your domain logic remains independent of the specific shell or operating system being used.
18
26
 
27
+ ---
28
+
29
+ ## 📍 Contenido
30
+ * [Installation](#installation)
31
+ * [Usage](#usage)
32
+
33
+ ---
34
+
35
+ ## Installation
36
+
37
+ You can install cc-shellback-kit using pip:
38
+
39
+ ```bash
40
+ pip install cc.shellback-kit
41
+ ```
42
+
19
43
  ## Usage
20
44
 
21
45
  ```python
22
- from cc_shellback_kit.capsule import Bash
23
- from cc_shellback_kit.core import Command, SessionContext
24
- from cc_shellback_kit.observers import ConsoleLogObserver
46
+ from cc_shellback_kit import Bash, ConsoleLogObserver, Command, SessionContext
25
47
 
26
48
  # 1. Configuramos el observador para ver la actividad en consola
27
49
  observer = ConsoleLogObserver()
@@ -1,15 +1,37 @@
1
- # CapsuleCore shellback
1
+ # cc-shellback-kit
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
4
+ ![Version](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Rick-torrellas/cc-shellback-kit/badges/version.json)
5
+ [![CI CD](https://github.com/Rick-torrellas/cc-shellback-kit/actions/workflows/main.yaml/badge.svg)](https://github.com/Rick-torrellas/cc-shellback-kit/actions/workflows/main.yaml)
6
+ [![Python Version](https://img.shields.io/badge/python-3.11+-blue?logo=python&logoColor=white)](https://www.python.org/)
7
+ [![Download](https://img.shields.io/github/v/release/Rick-torrellas/cc-shellback-kit?label=Download&color=orange)](https://github.com/Rick-torrellas/cc-shellback-kit/releases)
8
+ [![Ask DeepWiki](https://img.shields.io/badge/DeepWiki-Documentation-blue?logo=gitbook&logoColor=white)](https://deepwiki.com/Rick-torrellas/cc-shellback-kit)
9
+ [![docs](https://img.shields.io/badge/docs-read_now-blue?style=flat-square)](https://rick-torrellas.github.io/cc-shellback-kit/)
2
10
 
3
11
  Shellback is a robust, architecturally-agnostic Python library designed to bridge terminal environments (Bash, CMD, PowerShell) with Python scripts. It provides a clean, decoupled abstraction layer to execute system commands while maintaining persistent session state and cross-platform compatibility.
4
12
 
5
13
  Built with Hexagonal Architecture (Ports and Adapters) principles, Shellback ensures that your domain logic remains independent of the specific shell or operating system being used.
6
14
 
15
+ ---
16
+
17
+ ## 📍 Contenido
18
+ * [Installation](#installation)
19
+ * [Usage](#usage)
20
+
21
+ ---
22
+
23
+ ## Installation
24
+
25
+ You can install cc-shellback-kit using pip:
26
+
27
+ ```bash
28
+ pip install cc.shellback-kit
29
+ ```
30
+
7
31
  ## Usage
8
32
 
9
33
  ```python
10
- from cc_shellback_kit.capsule import Bash
11
- from cc_shellback_kit.core import Command, SessionContext
12
- from cc_shellback_kit.observers import ConsoleLogObserver
34
+ from cc_shellback_kit import Bash, ConsoleLogObserver, Command, SessionContext
13
35
 
14
36
  # 1. Configuramos el observador para ver la actividad en consola
15
37
  observer = ConsoleLogObserver()
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "cc.shellback-kit"
3
- version = "0.3.0"
3
+ version = "0.4.0"
4
4
  description = "Add your description here"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.14"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cc.shellback-kit
3
- Version: 0.3.0
3
+ Version: 0.4.0
4
4
  Summary: Add your description here
5
5
  Requires-Python: >=3.14
6
6
  Description-Content-Type: text/markdown
@@ -10,18 +10,40 @@ Requires-Dist: pytest>=9.0.2
10
10
  Requires-Dist: pytest-timeout>=2.4.0
11
11
  Requires-Dist: ruff>=0.15.6
12
12
 
13
- # CapsuleCore shellback
13
+ # cc-shellback-kit
14
+
15
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
16
+ ![Version](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/Rick-torrellas/cc-shellback-kit/badges/version.json)
17
+ [![CI CD](https://github.com/Rick-torrellas/cc-shellback-kit/actions/workflows/main.yaml/badge.svg)](https://github.com/Rick-torrellas/cc-shellback-kit/actions/workflows/main.yaml)
18
+ [![Python Version](https://img.shields.io/badge/python-3.11+-blue?logo=python&logoColor=white)](https://www.python.org/)
19
+ [![Download](https://img.shields.io/github/v/release/Rick-torrellas/cc-shellback-kit?label=Download&color=orange)](https://github.com/Rick-torrellas/cc-shellback-kit/releases)
20
+ [![Ask DeepWiki](https://img.shields.io/badge/DeepWiki-Documentation-blue?logo=gitbook&logoColor=white)](https://deepwiki.com/Rick-torrellas/cc-shellback-kit)
21
+ [![docs](https://img.shields.io/badge/docs-read_now-blue?style=flat-square)](https://rick-torrellas.github.io/cc-shellback-kit/)
14
22
 
15
23
  Shellback is a robust, architecturally-agnostic Python library designed to bridge terminal environments (Bash, CMD, PowerShell) with Python scripts. It provides a clean, decoupled abstraction layer to execute system commands while maintaining persistent session state and cross-platform compatibility.
16
24
 
17
25
  Built with Hexagonal Architecture (Ports and Adapters) principles, Shellback ensures that your domain logic remains independent of the specific shell or operating system being used.
18
26
 
27
+ ---
28
+
29
+ ## 📍 Contenido
30
+ * [Installation](#installation)
31
+ * [Usage](#usage)
32
+
33
+ ---
34
+
35
+ ## Installation
36
+
37
+ You can install cc-shellback-kit using pip:
38
+
39
+ ```bash
40
+ pip install cc.shellback-kit
41
+ ```
42
+
19
43
  ## Usage
20
44
 
21
45
  ```python
22
- from cc_shellback_kit.capsule import Bash
23
- from cc_shellback_kit.core import Command, SessionContext
24
- from cc_shellback_kit.observers import ConsoleLogObserver
46
+ from cc_shellback_kit import Bash, ConsoleLogObserver, Command, SessionContext
25
47
 
26
48
  # 1. Configuramos el observador para ver la actividad en consola
27
49
  observer = ConsoleLogObserver()
@@ -7,22 +7,24 @@ class FileLogObserver(ShellObserver):
7
7
  """Guarda toda la actividad de la Shell en un archivo físico."""
8
8
 
9
9
  def __init__(self, log_path: str = "shell_activity.log"):
10
- self.log_path = Path(log_path)
11
-
12
- self.logger = logging.getLogger("ShellFileLogger")
13
- self.logger.setLevel(logging.INFO)
14
- self.logger.propagate = False # Evita que los logs salgan por consola en los tests
15
-
16
- if self.logger.handlers:
17
- for h in self.logger.handlers[:]:
18
- h.close()
19
- self.logger.removeHandler(h)
20
-
21
- # Ahora creamos el nuevo handler con la ruta correcta
22
- handler = logging.FileHandler(self.log_path, encoding="utf-8")
23
- formatter = logging.Formatter("%(asctime)s | %(levelname)s | %(message)s")
24
- handler.setFormatter(formatter)
25
- self.logger.addHandler(handler)
10
+ self.log_path = Path(log_path)
11
+
12
+ self.logger = logging.getLogger("ShellFileLogger")
13
+ self.logger.setLevel(logging.INFO)
14
+ self.logger.propagate = (
15
+ False # Evita que los logs salgan por consola en los tests
16
+ )
17
+
18
+ if self.logger.handlers:
19
+ for h in self.logger.handlers[:]:
20
+ h.close()
21
+ self.logger.removeHandler(h)
22
+
23
+ # Ahora creamos el nuevo handler con la ruta correcta
24
+ handler = logging.FileHandler(self.log_path, encoding="utf-8")
25
+ formatter = logging.Formatter("%(asctime)s | %(levelname)s | %(message)s")
26
+ handler.setFormatter(formatter)
27
+ self.logger.addHandler(handler)
26
28
 
27
29
  def on_session_start(self, shell_name: str):
28
30
  self.logger.info(f"=== INICIO DE SESIÓN: {shell_name} ===")
@@ -4,6 +4,7 @@ from pathlib import Path
4
4
  from typing import Any, List, Optional, Dict
5
5
  from ..core import ShellObserver, CommandResult
6
6
 
7
+
7
8
  class JSONFileObserver(ShellObserver):
8
9
  """
9
10
  Registra toda la actividad de la Shell en un archivo JSON.
@@ -20,7 +21,7 @@ class JSONFileObserver(ShellObserver):
20
21
  try:
21
22
  with open(self.log_path, "r", encoding="utf-8") as f:
22
23
  return json.load(f)
23
- except (json.JSONDecodeError, FileNotFoundError):
24
+ except json.JSONDecodeError, FileNotFoundError:
24
25
  return []
25
26
 
26
27
  def _write_logs(self, logs: List[Dict[str, Any]]):
@@ -41,7 +42,7 @@ class JSONFileObserver(ShellObserver):
41
42
  "exit_code": result.return_code,
42
43
  "duration": round(result.execution_time, 4),
43
44
  "stdout_len": len(result.standard_output),
44
- "stderr": result.standard_error.strip() if result.standard_error else None
45
+ "stderr": result.standard_error.strip() if result.standard_error else None,
45
46
  }
46
47
  self._append_entry(entry)
47
48
 
@@ -49,7 +50,7 @@ class JSONFileObserver(ShellObserver):
49
50
  entry = {
50
51
  "timestamp": time.time(),
51
52
  "event": "context_mutation",
52
- "change": {key: str(value)}
53
+ "change": {key: str(value)},
53
54
  }
54
55
  self._append_entry(entry)
55
56
 
@@ -58,6 +59,6 @@ class JSONFileObserver(ShellObserver):
58
59
  "timestamp": time.time(),
59
60
  "event": "internal_error",
60
61
  "message": message,
61
- "exception": str(error) if error else None
62
+ "exception": str(error) if error else None,
62
63
  }
63
- self._append_entry(entry)
64
+ self._append_entry(entry)
@@ -1,5 +1,6 @@
1
1
  from typing import Any, List
2
2
 
3
+
3
4
  class ArgumentBuilder:
4
5
  """Garantiza la consistencia de los argumentos para el Sistema Operativo."""
5
6
 
@@ -27,4 +28,4 @@ class ArgumentBuilder:
27
28
  return self
28
29
 
29
30
  def build(self) -> List[str]:
30
- return self._args
31
+ return self._args
@@ -1,5 +1,6 @@
1
1
  from .ArgumentBuilder import ArgumentBuilder
2
2
 
3
+
3
4
  class Command:
4
5
  """Representa un comando ejecutable con sus argumentos."""
5
6
 
@@ -9,7 +10,7 @@ class Command:
9
10
 
10
11
  def add_args(self, *args) -> "Command":
11
12
  """
12
- Añade argumentos posicionales.
13
+ Añade argumentos posicionales.
13
14
  Soporta elementos sueltos o listas gracias al nuevo builder.
14
15
  """
15
16
  for arg in args:
@@ -19,4 +20,4 @@ class Command:
19
20
  @property
20
21
  def args(self) -> list[str]:
21
22
  """Obtiene la lista de argumentos construida y validada."""
22
- return self.builder.build()
23
+ return self.builder.build()
@@ -15,6 +15,7 @@ from .ArgumentBuilder import ArgumentBuilder
15
15
 
16
16
  class CommandNotFoundError(Exception):
17
17
  """Lanzada cuando el binario no existe en el PATH."""
18
+
18
19
  pass
19
20
 
20
21
 
@@ -70,7 +71,9 @@ class Shell(ABC):
70
71
  self.context = replace(self.context, cwd=new_path)
71
72
  self.observer.on_context_change("cwd", new_path)
72
73
 
73
- return CommandResult(standard_output=f"Cambiado a: {new_path}", return_code=0)
74
+ return CommandResult(
75
+ standard_output=f"Cambiado a: {new_path}", return_code=0
76
+ )
74
77
  except Exception as e:
75
78
  return self._notify_error("Error crítico en cd", e)
76
79
 
@@ -87,8 +90,7 @@ class Shell(ABC):
87
90
 
88
91
  self.context = replace(self.context, env=new_env)
89
92
  return CommandResult(
90
- standard_output=f"Variables actualizadas: {len(updates)}",
91
- return_code=0
93
+ standard_output=f"Variables actualizadas: {len(updates)}", return_code=0
92
94
  )
93
95
  except Exception as e:
94
96
  return self._notify_error("Fallo al exportar variables", e)
@@ -100,7 +102,7 @@ class Shell(ABC):
100
102
  try:
101
103
  # 1. Preparación y validación del ejecutable
102
104
  full_path = self._validate_executable(command.executable)
103
-
105
+
104
106
  # 2. HELPER: Garantizamos consistencia de argumentos finales
105
107
  # Usamos el ArgumentBuilder para aplanar cualquier lista residual
106
108
  builder = ArgumentBuilder()
@@ -112,7 +114,7 @@ class Shell(ABC):
112
114
  self.observer.on_command_start(command.executable, final_args)
113
115
 
114
116
  start_time = time.perf_counter()
115
-
117
+
116
118
  process = subprocess.run(
117
119
  final_args,
118
120
  cwd=self.context.cwd,
@@ -130,7 +132,7 @@ class Shell(ABC):
130
132
  execution_time=time.perf_counter() - start_time,
131
133
  command_sent=final_args,
132
134
  )
133
-
135
+
134
136
  self.observer.on_command_result(result)
135
137
  return result
136
138
 
@@ -159,7 +161,7 @@ class Shell(ABC):
159
161
  if not target.is_absolute():
160
162
  return (self.context.cwd / target).resolve()
161
163
  return target.resolve()
162
-
164
+
163
165
  def _parse_env_vars(self, args: list[str]) -> Dict[str, str]:
164
166
  updates = {}
165
167
  for arg in args:
@@ -168,7 +170,9 @@ class Shell(ABC):
168
170
  updates[key] = value
169
171
  return updates
170
172
 
171
- def _notify_error(self, message: str, error: Exception = None, return_code: int = 1) -> CommandResult:
173
+ def _notify_error(
174
+ self, message: str, error: Exception = None, return_code: int = 1
175
+ ) -> CommandResult:
172
176
  """Estandariza la notificación de errores y la respuesta."""
173
177
  self.observer.on_error(message, error)
174
- return CommandResult(standard_error=message, return_code=return_code)
178
+ return CommandResult(standard_error=message, return_code=return_code)
@@ -1,7 +1,7 @@
1
1
  from cc_shellback_kit import ArgumentBuilder
2
2
 
3
- class TestArgumentBuilder:
4
3
 
4
+ class TestArgumentBuilder:
5
5
  def test_add_single_argument(self):
6
6
  """Verifica que se añadan argumentos simples correctamente."""
7
7
  builder = ArgumentBuilder()
@@ -15,7 +15,7 @@ class TestArgumentBuilder:
15
15
  builder.add_arg(["git", "commit"])
16
16
  builder.add_arg("-m")
17
17
  builder.add_arg(["Mensaje con espacios"])
18
-
18
+
19
19
  assert builder.build() == ["git", "commit", "-m", "Mensaje con espacios"]
20
20
 
21
21
  def test_ignore_none_and_empty_values(self):
@@ -23,16 +23,16 @@ class TestArgumentBuilder:
23
23
  builder = ArgumentBuilder()
24
24
  builder.add_arg(None)
25
25
  builder.add_arg("")
26
- builder.add_arg(" ") # Espacios en blanco
26
+ builder.add_arg(" ") # Espacios en blanco
27
27
  builder.add_arg("python")
28
-
28
+
29
29
  assert builder.build() == ["python"]
30
30
 
31
31
  def test_fluent_interface(self):
32
32
  """Verifica que los métodos sean encadenables (return self)."""
33
33
  builder = ArgumentBuilder()
34
34
  result = builder.add_arg("cmd").add_flag("v").add_arg("file.txt")
35
-
35
+
36
36
  assert isinstance(result, ArgumentBuilder)
37
37
  assert builder.build() == ["cmd", "--v", "file.txt"]
38
38
 
@@ -41,7 +41,7 @@ class TestArgumentBuilder:
41
41
  builder = ArgumentBuilder(style="unix")
42
42
  builder.add_flag("force")
43
43
  builder.add_flag("output", "results.txt")
44
-
44
+
45
45
  expected = ["--force", "--output", "results.txt"]
46
46
  assert builder.build() == expected
47
47
 
@@ -50,7 +50,7 @@ class TestArgumentBuilder:
50
50
  builder = ArgumentBuilder(style="windows")
51
51
  builder.add_flag("all")
52
52
  builder.add_flag("limit", 10)
53
-
53
+
54
54
  expected = ["/all", "/limit", "10"]
55
55
  assert builder.build() == expected
56
56
 
@@ -58,13 +58,13 @@ class TestArgumentBuilder:
58
58
  """Verifica que los espacios en los nombres de flags se conviertan en underscores."""
59
59
  builder = ArgumentBuilder()
60
60
  builder.add_flag("ignore case")
61
-
61
+
62
62
  assert builder.build() == ["--ignore_case"]
63
63
 
64
64
  def test_complex_nesting(self):
65
65
  """Verifica un caso complejo con anidación profunda de listas."""
66
66
  builder = ArgumentBuilder()
67
67
  builder.add_arg(["docker", ["run", ["-d", "--name"]], "my_container"])
68
-
68
+
69
69
  expected = ["docker", "run", "-d", "--name", "my_container"]
70
- assert builder.build() == expected
70
+ assert builder.build() == expected
@@ -1,12 +1,14 @@
1
1
  import pytest
2
2
  from cc_shellback_kit import Command
3
3
 
4
+
4
5
  def test_bash_format_command(bash_shell):
5
6
  """Verifica que Bash no altere los argumentos (la lógica de unión está en Shell)."""
6
7
  # Bash._format_command según tu archivo solo devuelve los args
7
8
  result = bash_shell._format_command("ls", ["-l", "-a"])
8
9
  assert result == ["-l", "-a"]
9
10
 
11
+
10
12
  def test_bash_run_external_success(bash_shell, mock_observer):
11
13
  """Prueba la ejecución de un comando real (ls o dir) y la notificación al observer."""
12
14
  cmd = Command("echo").add_args("Hola Mundo")
@@ -18,6 +20,7 @@ def test_bash_run_external_success(bash_shell, mock_observer):
18
20
  mock_observer.on_command_start.assert_called_once()
19
21
  mock_observer.on_command_result.assert_called_once()
20
22
 
23
+
21
24
  def test_bash_command_not_found(bash_shell, mock_observer):
22
25
  """Verifica el comportamiento cuando el binario no existe."""
23
26
  cmd = Command("comando_que_no_existe_12345")
@@ -27,11 +30,12 @@ def test_bash_command_not_found(bash_shell, mock_observer):
27
30
  assert "Comando no encontrado" in result.standard_error
28
31
  mock_observer.on_error.assert_called_once()
29
32
 
33
+
30
34
  def test_bash_virtual_cd(bash_shell, tmp_path, mock_observer):
31
35
  """Verifica que el comando virtual 'cd' cambie el contexto de la sesión."""
32
36
  subdir = tmp_path / "test_dir"
33
37
  subdir.mkdir()
34
-
38
+
35
39
  cmd = Command("cd").add_args(str(subdir))
36
40
  result = bash_shell.run(cmd)
37
41
 
@@ -40,6 +44,7 @@ def test_bash_virtual_cd(bash_shell, tmp_path, mock_observer):
40
44
  # Verificar que se notificó el cambio de contexto
41
45
  mock_observer.on_context_change.assert_called_with("cwd", subdir)
42
46
 
47
+
43
48
  def test_bash_virtual_export(bash_shell, mock_observer):
44
49
  """Verifica que 'export' actualice las variables de entorno en el contexto."""
45
50
  cmd = Command("export").add_args("VERSION=1.0.0", "DEBUG=true")
@@ -51,22 +56,24 @@ def test_bash_virtual_export(bash_shell, mock_observer):
51
56
  # Verificar que se notificó cada cambio
52
57
  assert mock_observer.on_context_change.call_count == 2
53
58
 
59
+
54
60
  def test_bash_session_hooks(bash_shell, mock_observer):
55
61
  """Prueba que los hooks de inicio y fin de sesión funcionen con el context manager."""
56
62
  with bash_shell as sh:
57
63
  sh.run(Command("echo").add_args("test"))
58
-
64
+
59
65
  mock_observer.on_session_start.assert_called_once_with("Bash")
60
66
  mock_observer.on_session_end.assert_called_once()
61
67
 
68
+
62
69
  @pytest.mark.timeout(2)
63
70
  def test_bash_timeout(bash_shell):
64
71
  """Verifica que el timeout funcione correctamente (usando sleep)."""
65
72
  # Nota: Este test depende de que 'sleep' esté disponible en el sistema
66
73
  cmd = Command("sleep").add_args("5")
67
-
74
+
68
75
  # Ejecutamos con un timeout corto
69
76
  result = bash_shell.run(cmd, timeout=0.1)
70
-
77
+
71
78
  assert result.return_code == 1
72
- assert "Tiempo de espera agotado" in result.standard_error
79
+ assert "Tiempo de espera agotado" in result.standard_error
@@ -1,7 +1,7 @@
1
1
  from cc_shellback_kit import Command
2
2
 
3
+
3
4
  class TestCommand:
4
-
5
5
  def test_command_initialization(self):
6
6
  """Verifica que el comando se inicializa con el ejecutable correcto."""
7
7
  cmd = Command("ls")
@@ -23,7 +23,7 @@ class TestCommand:
23
23
 
24
24
  def test_add_list_of_arguments(self):
25
25
  """
26
- Verifica que el builder aplane correctamente las listas
26
+ Verifica que el builder aplane correctamente las listas
27
27
  pasadas como argumentos (gracias a ArgumentBuilder).
28
28
  """
29
29
  cmd = Command("tar")
@@ -35,7 +35,7 @@ class TestCommand:
35
35
  """Verifica que add_args devuelva la instancia (patrón Fluent Interface)."""
36
36
  cmd = Command("docker")
37
37
  returned_cmd = cmd.add_args("ps").add_args("-a")
38
-
38
+
39
39
  assert cmd is returned_cmd
40
40
  assert cmd.args == ["ps", "-a"]
41
41
 
@@ -44,13 +44,13 @@ class TestCommand:
44
44
  cmd = Command("echo")
45
45
  # El ArgumentBuilder convierte internamente a string y aplana
46
46
  cmd.add_args("User ID:", 1001, ["--force", "yes"])
47
-
47
+
48
48
  assert cmd.args == ["User ID:", "1001", "--force", "yes"]
49
49
 
50
50
  def test_empty_and_none_arguments(self):
51
51
  """Verifica que se ignoren valores None o vacíos si el builder así lo gestiona."""
52
52
  cmd = Command("ls")
53
53
  cmd.add_args(None, "", " ", "-l")
54
-
54
+
55
55
  # Según ArgumentBuilder.py, ignora None y strings que queden vacíos tras .strip()
56
- assert cmd.args == ["-l"]
56
+ assert cmd.args == ["-l"]
@@ -2,6 +2,7 @@ import pytest
2
2
  import json
3
3
  from cc_shellback_kit import CommandResult
4
4
 
5
+
5
6
  def test_command_result_initialization():
6
7
  """Verifica que los valores por defecto se asignen correctamente."""
7
8
  result = CommandResult()
@@ -11,34 +12,39 @@ def test_command_result_initialization():
11
12
  assert result.execution_time == 0.0
12
13
  assert result.command_sent == []
13
14
 
15
+
14
16
  def test_is_success_true():
15
17
  """Verifica que is_success() sea True cuando el código de retorno es 0."""
16
18
  result = CommandResult(return_code=0)
17
19
  assert result.is_success() is True
18
20
 
21
+
19
22
  def test_is_success_false():
20
23
  """Verifica que is_success() sea False cuando el código de retorno no es 0."""
21
24
  result = CommandResult(return_code=1)
22
25
  assert result.is_success() is False
23
-
26
+
24
27
  result_error = CommandResult(return_code=127)
25
28
  assert result_error.is_success() is False
26
29
 
30
+
27
31
  def test_json_parsing_success():
28
32
  """Verifica que el método json() parsee correctamente un string JSON."""
29
33
  data = {"status": "ok", "items": [1, 2, 3]}
30
34
  json_str = json.dumps(data)
31
35
  result = CommandResult(standard_output=json_str)
32
-
36
+
33
37
  assert result.json() == data
34
38
  assert result.json()["status"] == "ok"
35
39
 
40
+
36
41
  def test_json_parsing_failure():
37
42
  """Verifica que el método json() lance un error con salida no válida."""
38
43
  result = CommandResult(standard_output="No soy un JSON")
39
44
  with pytest.raises(json.JSONDecodeError):
40
45
  result.json()
41
46
 
47
+
42
48
  def test_pipe_operator_syntax():
43
49
  """
44
50
  Verifica el comportamiento del operador pipe (|).
@@ -46,13 +52,14 @@ def test_pipe_operator_syntax():
46
52
  """
47
53
  stdout_content = "datos de salida"
48
54
  result = CommandResult(standard_output=stdout_content)
49
-
55
+
50
56
  # En la implementación actual: CommandResult | Any -> str (standard_output)
51
57
  output = result | "otro_comando"
52
-
58
+
53
59
  assert output == stdout_content
54
60
  assert isinstance(output, str)
55
61
 
62
+
56
63
  def test_complex_result_data():
57
64
  """Verifica la consistencia de un objeto con todos los campos llenos."""
58
65
  cmd = ["ls", "-la"]
@@ -61,10 +68,10 @@ def test_complex_result_data():
61
68
  standard_error="warning: low disk space",
62
69
  return_code=0,
63
70
  execution_time=0.1234,
64
- command_sent=cmd
71
+ command_sent=cmd,
65
72
  )
66
-
73
+
67
74
  assert result.command_sent == cmd
68
75
  assert "total" in result.standard_output
69
76
  assert "warning" in result.standard_error
70
- assert result.execution_time == 0.1234
77
+ assert result.execution_time == 0.1234