Skip to content

Commit 665ff33

Browse files
committed
initial implementation
Signed-off-by: Maria Teresa Ortega <teresa.ortega0903@gmail.com>
1 parent 46ff299 commit 665ff33

File tree

3 files changed

+76
-1
lines changed

3 files changed

+76
-1
lines changed

src/lambkin/core/ctx/context.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
from types import SimpleNamespace
2424
from typing import Any
2525

26+
from lambkin.core.shell import ShellProxy
27+
2628
from .source import Source
2729

2830

@@ -176,7 +178,7 @@ def __init__(
176178
# as parameters.
177179

178180
# ctx.shell
179-
# TODO(teresa-ortega): self.shell = Shell(self)
181+
self.shell = ShellProxy()
180182

181183
def _setup_directories(self) -> None:
182184
"""Create variant and iteration directories."""

src/lambkin/core/shell/__init__.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Copyright 2026 Ekumen, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Shell subpackage for the lambkin SDK.
16+
17+
Provides shell abstractions for executing system commands within benchmarks.
18+
Each shell implementation exposes the same interface, allowing benchmarks to
19+
switch between dry-run and real execution without changing any benchmark code.
20+
"""
21+
22+
from .proxy import ShellProxy
23+
24+
__all__ = [
25+
"ShellProxy",
26+
]

src/lambkin/core/shell/proxy.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,50 @@
1717
Provides an abstraction over shell command dispatch, allowing benchmark
1818
processes to be launched and managed through a consistent interface.
1919
"""
20+
21+
from __future__ import annotations
22+
23+
import shlex
24+
from typing import Any
25+
26+
27+
class _CommandProxy:
28+
"""Builds a shell command lazily by chaining attribute access and calls."""
29+
30+
def __init__(self, parts: list[str]) -> None:
31+
"""Initialize the proxy with the command words accumulated so far."""
32+
self._parts = parts
33+
34+
def __getattr__(self, name: str) -> _CommandProxy:
35+
"""Append a new word to the command and return a new proxy."""
36+
return _CommandProxy(self._parts + [name])
37+
38+
def __call__(self, *args: Any, **kwargs: Any) -> None:
39+
"""Finalize and print the command.
40+
41+
Positional args are appended as plain tokens. Keyword args are
42+
converted to --flag value pairs, with underscores replaced by hyphens.
43+
Boolean True values produce a standalone flag, False values are ignored.
44+
"""
45+
extra = []
46+
47+
for arg in args:
48+
extra.append(str(arg))
49+
50+
for key, value in kwargs.items():
51+
flag = "--" + key.replace("_", "-")
52+
if value is True:
53+
extra.append(flag)
54+
elif value is not False:
55+
extra.extend([flag, shlex.quote(str(value))])
56+
57+
command = " ".join(self._parts + extra)
58+
print(f"[CMD]: {command}")
59+
60+
61+
class ShellProxy:
62+
"""Dry-run mock shell that prints commands instead of executing them."""
63+
64+
def __getattr__(self, name: str) -> _CommandProxy:
65+
"""Start building a new command from the given top-level tool name."""
66+
return _CommandProxy([name])

0 commit comments

Comments
 (0)