Skip to content

Commit 842994d

Browse files
authored
Add command block to execute shell commands (#124)
1 parent b9591d6 commit 842994d

File tree

5 files changed

+41
-4
lines changed

5 files changed

+41
-4
lines changed

pdl-live/src/pdl_ast.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2163,7 +2163,7 @@ export type Kind15 = "code";
21632163
* Programming language of the code.
21642164
*
21652165
*/
2166-
export type Lang = "python";
2166+
export type Lang = "python" | "command";
21672167
/**
21682168
* Code to execute.
21692169
*

src/pdl/pdl-schema.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2555,10 +2555,10 @@
25552555
"type": "string"
25562556
},
25572557
"lang": {
2558-
"const": "python",
25592558
"description": "Programming language of the code.\n ",
25602559
"enum": [
2561-
"python"
2560+
"python",
2561+
"command"
25622562
],
25632563
"title": "Lang",
25642564
"type": "string"

src/pdl/pdl_ast.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ class CodeBlock(Block):
278278
"""Execute a piece of code."""
279279

280280
kind: Literal[BlockKind.CODE] = BlockKind.CODE
281-
lang: Literal["python"]
281+
lang: Literal["python", "command"]
282282
"""Programming language of the code.
283283
"""
284284
code: "BlocksType"

src/pdl/pdl_interpreter.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import json
22
import logging
33
import re
4+
import shlex
5+
import subprocess
46
import sys
57
import types
68

@@ -1222,6 +1224,16 @@ def step_call_code(
12221224
loc=loc,
12231225
trace=block.model_copy(update={"code": code_s}),
12241226
) from exc
1227+
case "command":
1228+
try:
1229+
result = call_command(code_s)
1230+
background = [{"role": state.role, "content": result}]
1231+
except Exception as exc:
1232+
raise PDLRuntimeError(
1233+
f"Code error: {repr(exc)}",
1234+
loc=loc,
1235+
trace=block.model_copy(update={"code": code_s}),
1236+
) from exc
12251237
case _:
12261238
message = f"Unsupported language: {block.lan}"
12271239
raise PDLRuntimeError(
@@ -1244,6 +1256,17 @@ def call_python(code: str, scope: dict) -> Any:
12441256
return result
12451257

12461258

1259+
def call_command(code: str) -> str:
1260+
args = shlex.split(code)
1261+
p = subprocess.run(args, capture_output=True, text=True, check=False)
1262+
if p.stderr != "":
1263+
print(p.stderr, file=sys.stderr)
1264+
if p.returncode != 0:
1265+
raise ValueError(f"command exited with non zero code: {p.returncode}")
1266+
output = p.stdout
1267+
return output
1268+
1269+
12471270
def step_call(
12481271
state: InterpreterState, scope: ScopeType, block: CallBlock, loc: LocationType
12491272
) -> Generator[YieldMessage, Any, tuple[Any, Messages, ScopeType, CallBlock]]:

tests/test_code.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,17 @@ def test_contribute_false():
5757
data = Program.model_validate(show_result_data([]))
5858
text, _, _, _ = process_prog(state, empty_scope, data)
5959
assert text == ""
60+
61+
62+
command_data = [
63+
{"def": "world", "lang": "command", "code": "echo -n World", "contribute": []},
64+
"Hello ${ world }!",
65+
]
66+
67+
68+
def test_command():
69+
state = InterpreterState()
70+
data = Program.model_validate(command_data)
71+
document, _, scope, _ = process_prog(state, empty_scope, data)
72+
assert document == "Hello World!"
73+
assert scope["world"] == "World"

0 commit comments

Comments
 (0)