~mil/mepo

ref: 0bfdd22eb4f3c4b04f83fa464f57cdaf973aa48f mepo/src/api/shellpipe.zig -rw-r--r-- 5.0 KiB
0bfdd22eMiles Alan shellpipe: break out env vars map creation to helper fn 3 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
const Mepo = @import("../Mepo.zig");
const types = @import("../types.zig");
const std = @import("std");
const utilconversion = @import("../util/utilconversion.zig");
const utilplatform = @import("../util/utilplatform.zig");
const utildbg = @import("../util/utildbg.zig");
const utilsdl = @import("../util/utilsdl.zig");
const sdl = @import("../sdlshim.zig");

const ShellpipeRequest = struct {
    mepo: *Mepo,
    cmd: []const u8,
};

pub const spec = .{
    .name = "shellpipe",
    .desc = "Run a system (shell) command and pipe; STDOUT returned from command will be executed back as mepolang",
    // TODO: document ENV vars usable in shell scripts context
    .args = (&[_]types.MepoFnSpecArg{
        .{ .tag = .Number, .name = "as_async", .desc = "Set to 1 to run asynchronously (e.g. don't wait for process to complete)" },
        .{ .tag = .Text, .name = "shell_statement", .desc = "Shell statement to execute" },
    })[0..],
    .execute = execute,
};

fn execute(mepo: *Mepo, args: [types.MepoFnNargs]types.MepoArg) !void {
    const as_async = args[0].Number == 1;
    const cmd = args[1].Text;
    try shellpipe(mepo, as_async, cmd);
}

fn shellpipe(mepo: *Mepo, as_async: bool, cmd: []const u8) !void {
    if (as_async) {
        var sp_req = try mepo.allocator.create(ShellpipeRequest);
        sp_req.cmd = try mepo.allocator.dupeZ(u8, cmd);
        sp_req.mepo = mepo;

        _ = sdl.SDL_CreateThread(
            async_shellpipe_run,
            "async_shellpipe",
            sp_req,
        );
    } else {
        try shellpipe_run(mepo, cmd, false);
    }
}

fn async_shellpipe_run(userdata: ?*anyopaque) callconv(.C) c_int {
    var shellpipe_request: *ShellpipeRequest = @ptrCast(*ShellpipeRequest, @alignCast(@alignOf(*ShellpipeRequest), userdata.?));
    // TODO: defer free cmd
    shellpipe_run(shellpipe_request.mepo, shellpipe_request.cmd, true) catch return 1;
    return 0;
}

fn shellpipe_run(mepo: *Mepo, cmd: []const u8, as_async: bool) !void {
    var arena = std.heap.ArenaAllocator.init(mepo.allocator);
    defer arena.deinit();

    if (as_async) mepo.idle_mutex.lock();
    defer {
        if (as_async) mepo.idle_mutex.unlock();
    }

    try mepo.update_debug_message(
        try std.fmt.allocPrint(arena.allocator(), "Shellpipe ({s}) in progress - run!", .{cmd}),
    );
    try refresh_ui(mepo, as_async);
    const env_vars = try get_env_vars(mepo, arena.allocator());

    const args = [_][]const u8{ "sh", "-c", cmd };
    const process_result = try std.ChildProcess.exec(.{
        .allocator = arena.allocator(),
        .argv = args[0..],
        .env_map = &env_vars,
        .max_output_bytes = 10000 * 1024,
    });
    const exitcode = process_result.term.Exited;
    try mepo.update_debug_message(
        try std.fmt.allocPrint(arena.allocator(), "Shellpipe ({s}) completed - execute, {d}!", .{ cmd, process_result.stdout.len }),
    );
    try refresh_ui(mepo, as_async);
    if (exitcode == 0) {
        try mepo.mepolang_execute(process_result.stdout);
        try mepo.update_debug_message(null);
    } else {
        utildbg.log("shellpipe error: exited with code {d}\n{s}", .{ exitcode, process_result.stderr });
        try mepo.update_debug_message(process_result.stderr);
    }
}

fn refresh_ui(mepo: *Mepo, as_async: bool) !void {
    if (as_async) utilsdl.sdl_push_resize_event() else try mepo.blit();
}

fn get_env_vars(mepo: *Mepo, allocator: std.mem.Allocator) !std.BufMap {
    var v = try std.process.getEnvMap(allocator);
    const bbox = mepo.bounding_box();

    // TODO: maybe this should be in seperate/helper fn?
    var cursor_x: c_int = undefined;
    var cursor_y: c_int = undefined;
    _ = sdl.SDL_GetMouseState(&cursor_x, &cursor_y);
    const cursor_lat = utilconversion.px_y_to_lat(mepo.get_y() - @divTrunc(@intCast(i32, mepo.win_h), 2) + cursor_y, mepo.zoom);
    const cursor_lon = utilconversion.px_x_to_lon(mepo.get_x() - @divTrunc(@intCast(i32, mepo.win_w), 2) + cursor_x, mepo.zoom);

    // Window and zoom info
    try v.put("MEPO_WIN_W", try std.fmt.allocPrint(allocator, "{d}", .{mepo.win_w}));
    try v.put("MEPO_WIN_H", try std.fmt.allocPrint(allocator, "{d}", .{mepo.win_h}));
    try v.put("MEPO_ZOOM", try std.fmt.allocPrint(allocator, "{d}", .{mepo.zoom}));

    // Center and bbox
    try v.put("MEPO_CENTER_LAT", try std.fmt.allocPrint(allocator, "{d}", .{mepo.lat}));
    try v.put("MEPO_CENTER_LON", try std.fmt.allocPrint(allocator, "{d}", .{mepo.lon}));
    try v.put("MEPO_TL_LAT", try std.fmt.allocPrint(allocator, "{d}", .{bbox.topleft_lat}));
    try v.put("MEPO_TL_LON", try std.fmt.allocPrint(allocator, "{d}", .{bbox.topleft_lon}));
    try v.put("MEPO_BR_LAT", try std.fmt.allocPrint(allocator, "{d}", .{bbox.bottomright_lat}));
    try v.put("MEPO_BR_LON", try std.fmt.allocPrint(allocator, "{d}", .{bbox.bottomright_lon}));

    // Cursor position
    try v.put("MEPO_CURSOR_LAT", try std.fmt.allocPrint(allocator, "{d}", .{cursor_lat}));
    try v.put("MEPO_CURSOR_LON", try std.fmt.allocPrint(allocator, "{d}", .{cursor_lon}));

    return v;
}