~clarity/sitegen

237d1d20b92b0636b7e1e6def0c468ac8d2356b5 — Clarity Flowers 1 year, 11 months ago b552015
Move command parsing into separate stage
1 files changed, 94 insertions(+), 79 deletions(-)

M src/main.zig
M src/main.zig => src/main.zig +94 -79
@@ 261,9 261,13 @@ fn renderFile(
    };
    const src_file = try targets.src.openFile(filename, .{});
    defer src_file.close();
    const lines = try readLines(src_file.reader(), &arena.allocator);
    const doc = try parseDocument(lines, &arena.allocator, file_info);
    if (doc.info.private and !include_private) return;
    const lines = blk: {
        var lines_list = std.ArrayList([]const u8).init(&arena.allocator);
        try readLines(src_file.reader(), &lines_list);
        break :blk lines_list.toOwnedSlice();
    };

    const doc = (try parseDocument(lines, &arena.allocator, file_info)) orelse return;
    const stdout = std.io.getStdOut().writer();
    if (!silent) {
        if (targets.dirname) |dirname| {


@@ 382,10 386,15 @@ fn parseDocument(
    lines: []const []const u8,
    allocator: *std.mem.Allocator,
    file: FileInfo,
) !Document {
) !?Document {
    const info_res = try parseInfo(lines, file.name, allocator);
    if (info_res.data.private and !include_private) return null;
    const context = ParseContext{ .file = file, .allocator = allocator, .info = info_res.data };
    const blocks = try parseBlocks(lines, info_res.new_pos + 1, context);

    const blocks = if (info_res.new_pos + 1 < lines.len) blk: {
        const expanded_lines = try expand(lines[info_res.new_pos + 1 ..], allocator, context.info.shell, file.name);
        break :blk try parseBlocks(expanded_lines, 0, context);
    } else &[_]Block{};

    const result: Document = .{
        .blocks = blocks,


@@ 488,13 497,7 @@ fn parseBlocks(
    var blocks = std.ArrayList(Block).init(context.allocator);
    var spans = std.ArrayList(Span).init(context.allocator);
    while (index < lines.len) {
        if (try parseCommand(lines, index, context)) |res| {
            if (spans.items.len > 0) {
                try blocks.append(.{ .paragraph = spans.toOwnedSlice() });
            }
            try blocks.appendSlice(res.data);
            index = res.new_pos;
        } else if (try parseBlock(lines, index, context.allocator)) |res| {
        if (try parseBlock(lines, index, context.allocator)) |res| {
            if (spans.items.len > 0) {
                try blocks.append(.{ .paragraph = spans.toOwnedSlice() });
            }


@@ 516,68 519,6 @@ fn parseBlocks(
    return blocks.toOwnedSlice();
}

/// This is the fun stuff. Everything written inside of a command block is piped
/// into the shell, and the output is then parsed as blocks like any other
/// content. Not very much code and A LOT of power.
fn parseCommand(
    lines: []const []const u8,
    start: usize,
    context: ParseContext,
) !?ParseResult([]const Block) {
    if (try parsePrefixedLines(
        lines,
        start,
        ":",
        context.allocator,
    )) |res| {
        defer context.allocator.free(res.data);
        const shell = context.info.shell orelse
            try std.process.getEnvVarOwned(context.allocator, "SHELL");

        var process = try std.ChildProcess.init(
            &[_][]const u8{shell},
            context.allocator,
        );
        defer process.deinit();
        try env_map.put("FILE", context.file.name);
        process.env_map = &env_map;
        process.stdin_behavior = .Pipe;
        process.stdout_behavior = .Pipe;

        try process.spawn();
        errdefer _ = process.kill() catch |err| {
            logger.warn("Had trouble cleaning up process: {}", .{err});
        };
        const writer = process.stdin.?.writer();
        for (res.data) |line| {
            try writer.print("{s}\n", .{line});
        }
        process.stdin.?.close();
        process.stdin = null;

        const result_lines = try readLines(
            process.stdout.?.reader(),
            context.allocator,
        );
        switch (try process.wait()) {
            .Exited => |status| {
                if (status != 0) {
                    logger.alert("Process ended unexpectedly on line {d}", .{
                        res.new_pos,
                    });
                    return error.ProcessEndedUnexpectedly;
                }
                return ok(
                    try parseBlocks(result_lines, 0, context),
                    res.new_pos,
                );
            },
            else => return error.ProcessEndedUnexpectedly,
        }
    }
    return null;
}

fn parseBlock(
    lines: []const []const u8,
    line: usize,


@@ 1428,10 1369,9 @@ fn readLine(reader: anytype, array_list: *std.ArrayList(u8)) !bool {
/// Caller owns result
fn readLines(
    reader: anytype,
    allocator: *std.mem.Allocator,
) ![]const []const u8 {
    var lines = std.ArrayList([]const u8).init(allocator);
    var current_line = std.ArrayList(u8).init(allocator);
    lines: *std.ArrayList([]const u8),
) !void {
    var current_line = std.ArrayList(u8).init(lines.allocator);
    while (try readLine(reader, &current_line)) {
        if (std.mem.startsWith(u8, current_line.items, "; ")) {
            if (std.mem.endsWith(u8, current_line.items, "\r")) {


@@ 1447,7 1387,82 @@ fn readLines(
            try lines.append(current_line.toOwnedSlice());
        }
    }
    return lines.toOwnedSlice();
}

/// This is the fun stuff. Everything written inside of a command block is piped
/// into the shell, and the output is then parsed as blocks like any other
/// content. Not very much code and A LOT of power.
fn expand(
    original_lines: []const []const u8,
    allocator: *std.mem.Allocator,
    custom_shell: ?[]const u8,
    filename: []const u8,
) ![]const []const u8 {
    // TODO this feels hacky still
    var lines = original_lines;
    var result = std.ArrayList([]const u8).init(allocator);
    errdefer result.deinit();
    while (true) {
        var i: usize = 0;
        defer {
            const tmp = lines;
            lines = result.toOwnedSlice();
            if (tmp.ptr != original_lines.ptr) {
                allocator.free(tmp);
            }
        }
        var expanded = false;
        while (i < lines.len) : (i += 1) {
            const start = i;
            while (i < lines.len) : (i += 1) {
                if (!std.mem.startsWith(u8, lines[i], ": ")) break;
            }
            if (i > start) {
                expanded = true;
                const shell = custom_shell orelse
                    try std.process.getEnvVarOwned(allocator, "SHELL");

                var process = try std.ChildProcess.init(
                    &[_][]const u8{shell},
                    allocator,
                );
                defer process.deinit();
                try env_map.put("FILE", filename);
                process.env_map = &env_map;
                process.stdin_behavior = .Pipe;
                process.stdout_behavior = .Pipe;

                try process.spawn();
                errdefer _ = process.kill() catch |err| {
                    logger.warn("Had trouble cleaning up process: {}", .{err});
                };
                const writer = process.stdin.?.writer();
                logger.info("Running command:", .{});
                for (lines[start..i]) |line| {
                    logger.info("{s}", .{line});
                    try writer.print("{s}\n", .{line[2..]});
                }
                process.stdin.?.close();
                process.stdin = null;

                try readLines(process.stdout.?.reader(), &result);

                switch (try process.wait()) {
                    .Exited => |status| {
                        if (status != 0) {
                            logger.alert("Process ended unexpectedly", .{});
                            return error.ProcessEndedUnexpectedly;
                        }
                    },
                    else => return error.ProcessEndedUnexpectedly,
                }
            } else {
                try result.append(lines[i]);
            }
        }
        if (!expanded) break;
    }
    return lines;
}

fn parseLiteral(