aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2025-03-19 16:17:44 -0400
committerBruce Hill <bruce@bruce-hill.com>2025-03-19 16:17:44 -0400
commitf3b8529e01f66f4743d26a62237706a09a2f1f71 (patch)
treef71748ab096e22016e529c601a5ec2e0e1c61170
parente7dc0ec85c8b591ae27fba369d35e26ec6c1b7d3 (diff)
Add command and shell :by_line()
-rw-r--r--examples/commands/commands.c101
-rw-r--r--examples/commands/commands.tm5
-rw-r--r--examples/shell/shell.tm6
3 files changed, 111 insertions, 1 deletions
diff --git a/examples/commands/commands.c b/examples/commands/commands.c
index f07b5c32..d7bdeee6 100644
--- a/examples/commands/commands.c
+++ b/examples/commands/commands.c
@@ -11,10 +11,11 @@
#include <sys/param.h>
#include <sys/wait.h>
#include <unistd.h>
+#include <unistr.h>
#define READ_END 0
#define WRITE_END 1
-public int run_command(Text_t exe, Array_t arg_array, Table_t env_table,
+int run_command(Text_t exe, Array_t arg_array, Table_t env_table,
Array_t input_bytes, Array_t *output_bytes, Array_t *error_bytes)
{
pthread_testcancel();
@@ -173,5 +174,103 @@ public int run_command(Text_t exe, Array_t arg_array, Table_t env_table,
return status;
}
+typedef struct {
+ pid_t pid;
+ FILE *out;
+} child_info_t;
+
+static void _line_reader_cleanup(child_info_t *child)
+{
+ if (child && child->out) {
+ fclose(child->out);
+ child->out = NULL;
+ }
+ if (child->pid) {
+ kill(child->pid, SIGTERM);
+ child->pid = 0;
+ }
+}
+
+static Text_t _next_line(child_info_t *child)
+{
+ if (!child || !child->out) return NONE_TEXT;
+
+ char *line = NULL;
+ size_t size = 0;
+ ssize_t len = getline(&line, &size, child->out);
+ if (len <= 0) {
+ _line_reader_cleanup(child);
+ return NONE_TEXT;
+ }
+
+ while (len > 0 && (line[len-1] == '\r' || line[len-1] == '\n'))
+ --len;
+
+ if (u8_check((uint8_t*)line, (size_t)len) != NULL)
+ fail("Invalid UTF8!");
+
+ Text_t line_text = Text$from_strn(line, len);
+ free(line);
+ return line_text;
+}
+
+OptionalClosure_t command_by_line(Text_t exe, Array_t arg_array, Table_t env_table)
+{
+ posix_spawnattr_t attr;
+ posix_spawnattr_init(&attr);
+
+ int child_outpipe[2];
+ pipe(child_outpipe);
+
+ posix_spawn_file_actions_t actions;
+ posix_spawn_file_actions_init(&actions);
+ posix_spawn_file_actions_adddup2(&actions, child_outpipe[WRITE_END], STDOUT_FILENO);
+ posix_spawn_file_actions_addclose(&actions, child_outpipe[READ_END]);
+
+ const char *exe_str = Text$as_c_string(exe);
+
+ Array_t arg_strs = {};
+ Array$insert_value(&arg_strs, exe_str, I(0), sizeof(char*));
+ for (int64_t i = 0; i < arg_array.length; i++)
+ Array$insert_value(&arg_strs, Text$as_c_string(*(Text_t*)(arg_array.data + i*arg_array.stride)), I(0), sizeof(char*));
+ Array$insert_value(&arg_strs, NULL, I(0), sizeof(char*));
+ char **args = arg_strs.data;
+
+ extern char **environ;
+ char **env = environ;
+ if (env_table.entries.length > 0) {
+ Array_t env_array = {}; // Array of const char*
+ for (char **e = environ; *e; e++)
+ Array$insert(&env_array, e, I(0), sizeof(char*));
+
+ for (int64_t i = 0; i < env_table.entries.length; i++) {
+ struct { Text_t key, value; } *entry = env_table.entries.data + env_table.entries.stride*i;
+ const char *env_entry = heap_strf("%k=%k", &entry->key, &entry->value);
+ Array$insert(&env_array, &env_entry, I(0), sizeof(char*));
+ }
+ Array$insert_value(&env_array, NULL, I(0), sizeof(char*));
+ assert(env_array.stride == sizeof(char*));
+ env = env_array.data;
+ }
+
+ pid_t pid;
+ int ret = exe_str[0] == '/' ?
+ posix_spawn(&pid, exe_str, &actions, &attr, args, env)
+ : posix_spawnp(&pid, exe_str, &actions, &attr, args, env);
+ if (ret != 0)
+ return NONE_CLOSURE;
+
+ posix_spawnattr_destroy(&attr);
+ posix_spawn_file_actions_destroy(&actions);
+
+ close(child_outpipe[WRITE_END]);
+
+ child_info_t *child_info = GC_MALLOC(sizeof(child_info_t));
+ child_info->out = fdopen(child_outpipe[READ_END], "r");
+ child_info->pid = pid;
+ GC_register_finalizer(child_info, (void*)_line_reader_cleanup, NULL, NULL, NULL);
+ return (Closure_t){.fn=(void*)_next_line, .userdata=child_info};
+}
+
#undef READ_END
#undef WRITE_END
diff --git a/examples/commands/commands.tm b/examples/commands/commands.tm
index a0e73908..c70d8592 100644
--- a/examples/commands/commands.tm
+++ b/examples/commands/commands.tm
@@ -1,8 +1,10 @@
# Functions for running system commands
use ./commands.c
+use libunistring.so
extern run_command:func(exe:Text, args:[Text], env:{Text,Text}, input:[Byte], output:&[Byte], error:&[Byte] -> Int32)
+extern command_by_line:func(exe:Text, args:[Text], env:{Text,Text} -> func(->Text?)?)
enum ExitType(Exited(status:Int32), Signaled(signal:Int32), Failed)
@@ -61,6 +63,9 @@ struct Command(command:Text, args=[:Text], env={:Text,Text}):
return none
else: return none
+ func by_line(command:Command -> func(->Text?)?):
+ return command_by_line(command.command, command.args, command.env)
+
func main(command:Text, args:[Text], input=""):
cmd := Command(command, args)
say(cmd:get_output(input=input)!)
diff --git a/examples/shell/shell.tm b/examples/shell/shell.tm
index cf5fcd2e..f1e2308d 100644
--- a/examples/shell/shell.tm
+++ b/examples/shell/shell.tm
@@ -33,3 +33,9 @@ lang Shell:
func get_output_bytes(shell:Shell, input="", input_bytes=[:Byte] -> [Byte]?):
return shell:command():get_output_bytes(input=input, input_bytes=input_bytes)
+ func by_line(shell:Shell -> func(->Text?)?):
+ return shell:command():by_line()
+
+func main(command:Shell):
+ for line in command:by_line()!:
+ >> line