Luigit
repositories / termux-janitor

termux-janitor

Interactive cleanup assistant for Termux: transparent, safe, confirmed disk reclamation.

owned by admin

tools/janitor/codegen.zig

Raw
const std = @import("std");
const engine = @import("engine");
const model_types = @import("model.zig");

/// Compose the generated Zig projection from generic and product registries.
pub fn render(model: *const model_types.Model, writer: *std.Io.Writer) !void {
    try writer.writeAll(
        \\// Generated from spec/model/*.zon by spec-tool. Do not edit.
        \\const std = @import("std");
        \\
    );
    try renderPackage(&model.package, writer);
    try engine.codegen.renderConcepts(model.graph.concepts, writer);
    try renderAndroidPackages(model.android_packages, writer);
    try renderClassification(&model.classification, writer);
    try renderThresholds(model.thresholds, writer);
    try renderLimits(model.limits, writer);
    try renderCli(&model.cli, writer);
    try engine.codegen.renderVerification(model.graph.verification, writer);
    try engine.codegen.renderImplementationUnits(model.graph.implementations, writer);
    try engine.codegen.renderTestSpecs(model.graph.test_specs, writer);
    try writer.writeAll(
        \\test "generated specification registries are populated and indexed" {
        \\    try std.testing.expect(concepts.len > 0);
        \\    try std.testing.expect(limits.len > 0);
        \\    try std.testing.expect(android_packages.len > 0);
        \\    try std.testing.expect(classification_source_basenames.len > 0);
        \\    try std.testing.expect(classification_source_suffixes.len > 0);
        \\    try std.testing.expect(classification_document_roots.len > 0);
        \\    try std.testing.expect(thresholds.len > 0);
        \\    try std.testing.expect(cli_options.len > 0);
        \\    try std.testing.expect(cli_exit_statuses.len > 0);
        \\    try std.testing.expect(verification_obligations.len > 0);
        \\    try std.testing.expect(implementation_units.len > 0);
        \\    try std.testing.expect(test_specs.len > 0);
        \\    for (concepts, 0..) |item, index| {
        \\        try std.testing.expectEqual(index, @intFromEnum(item.id));
        \\    }
        \\    for (limits, 0..) |item, index| {
        \\        try std.testing.expectEqual(index, @intFromEnum(item.id));
        \\    }
        \\    for (android_packages, 0..) |item, index| {
        \\        try std.testing.expectEqual(index, @intFromEnum(item.id));
        \\    }
        \\    for (thresholds, 0..) |item, index| {
        \\        try std.testing.expectEqual(index, @intFromEnum(item.id));
        \\    }
        \\    for (cli_options, 0..) |item, index| {
        \\        try std.testing.expectEqual(index, @intFromEnum(item.id));
        \\    }
        \\    for (cli_exit_statuses, 0..) |item, index| {
        \\        try std.testing.expectEqual(index, @intFromEnum(item.id));
        \\    }
        \\    for (verification_obligations, 0..) |item, index| {
        \\        try std.testing.expectEqual(index, @intFromEnum(item.id));
        \\    }
        \\    for (implementation_units, 0..) |item, index| {
        \\        try std.testing.expectEqual(index, @intFromEnum(item.id));
        \\    }
        \\    for (test_specs, 0..) |item, index| {
        \\        try std.testing.expectEqual(index, @intFromEnum(item.id));
        \\    }
        \\}
        \\
    );
}

/// Project the one canonical version reported by the executable and the manual.
pub fn renderPackage(package: *const model_types.Package, writer: *std.Io.Writer) !void {
    try writer.writeAll("pub const artifact_version = ");
    try engine.codegen.writeZigString(writer, package.version);
    try writer.writeAll(";\n\n");
}

pub fn renderAndroidPackages(
    packages: []const model_types.AndroidPackage,
    writer: *std.Io.Writer,
) !void {
    try writer.writeAll(
        \\pub const AndroidPackageId = enum {
        \\
    );
    for (packages) |package| try writer.print("    {s},\n", .{package.id});
    try writer.writeAll(
        \\};
        \\pub const AndroidPackageRole = enum { base, add_on };
        \\pub const AndroidPackage = struct {
        \\    id: AndroidPackageId,
        \\    revision: u32,
        \\    identifier: []const u8,
        \\    role: AndroidPackageRole,
        \\};
        \\
        \\pub const android_packages = [_]AndroidPackage{
        \\
    );
    for (packages) |package| {
        try writer.print(
            "    .{{ .id = .{s}, .revision = {d}, .identifier = ",
            .{ package.id, package.revision },
        );
        try engine.codegen.writeZigString(writer, package.identifier);
        try writer.print(", .role = .{s} }},\n", .{@tagName(package.role)});
    }
    try writer.writeAll(
        \\};
        \\
        \\pub fn androidPackage(id: AndroidPackageId) *const AndroidPackage {
        \\    return &android_packages[@intFromEnum(id)];
        \\}
        \\
    );
}

pub fn renderClassification(
    classification: *const model_types.Classification,
    writer: *std.Io.Writer,
) !void {
    try renderClassificationSet("source_basenames", &classification.source_basenames, writer);
    try renderClassificationSet("source_suffixes", &classification.source_suffixes, writer);
    try renderClassificationSet("document_roots", &classification.document_roots, writer);
}

fn renderClassificationSet(
    name: []const u8,
    set: *const model_types.ClassificationSet,
    writer: *std.Io.Writer,
) !void {
    try writer.print("pub const classification_{s} = [_][]const u8{{\n", .{name});
    for (set.values) |value| {
        try writer.writeAll("    ");
        try engine.codegen.writeZigString(writer, value);
        try writer.writeAll(",\n");
    }
    try writer.writeAll("};\n\n");
}

pub fn renderThresholds(
    thresholds: []const model_types.Threshold,
    writer: *std.Io.Writer,
) !void {
    try writer.writeAll("pub const ThresholdId = enum {\n");
    for (thresholds) |threshold| try writer.print("    {s},\n", .{threshold.id});
    try writer.writeAll(
        "};\npub const ThresholdUnit = enum { bytes, seconds };\n" ++
            "pub const Threshold = struct { id: ThresholdId, key: []const u8, " ++
            "value: u64, unit: ThresholdUnit };\n\npub const thresholds = [_]Threshold{\n",
    );
    for (thresholds) |threshold| {
        try writer.print(
            "    .{{ .id = .{s}, .key = ",
            .{threshold.id},
        );
        try engine.codegen.writeZigString(writer, threshold.key);
        try writer.print(", .value = {d}, .unit = .{s} }},\n", .{
            threshold.value,
            @tagName(threshold.unit),
        });
    }
    try writer.writeAll("};\n\npub const threshold_value = struct {\n");
    for (thresholds) |threshold| {
        try writer.print("    pub const {s}: u64 = {d};\n", .{ threshold.id, threshold.value });
    }
    try writer.writeAll("};\n\n");
}

pub fn renderLimits(limits: []const model_types.Limit, writer: *std.Io.Writer) !void {
    try writer.writeAll(
        \\pub const LimitId = enum {
        \\
    );
    for (limits) |limit| try writer.print("    {s},\n", .{limit.id});
    try writer.writeAll(
        \\};
        \\
        \\pub const LimitCategory = enum { scan, process, queue, terminal, logging };
        \\pub const LimitUnit = enum {
        \\    bytes,
        \\    cells,
        \\    components,
        \\    count,
        \\    milliseconds,
        \\    minutes,
        \\    seconds,
        \\};
        \\pub const ScalarLimit = struct { value: u64, unit: LimitUnit };
        \\pub const DimensionsLimit = struct { columns: u16, rows: u16 };
        \\pub const AlternativeLimit = struct { items: u32, milliseconds: u32 };
        \\pub const LimitValue = union(enum) {
        \\    scalar: ScalarLimit,
        \\    dimensions: DimensionsLimit,
        \\    alternative: AlternativeLimit,
        \\};
        \\pub const Limit = struct {
        \\    id: LimitId,
        \\    category: LimitCategory,
        \\    label: []const u8,
        \\    value: LimitValue,
        \\};
        \\
        \\pub const limits = [_]Limit{
        \\
    );
    for (limits) |limit| try renderLimit(limit, writer);
    try writer.writeAll(
        \\};
        \\
        \\pub fn limit(id: LimitId) *const Limit {
        \\    return &limits[@intFromEnum(id)];
        \\}
        \\
        \\pub const limit_value = struct {
        \\
    );
    for (limits) |limit| try renderLimitConstant(limit, writer);
    try writer.writeAll("};\n\n");
}

pub fn renderLimit(limit: model_types.Limit, writer: *std.Io.Writer) !void {
    try writer.print(
        "    .{{\n        .id = .{s},\n        .category = .{s},\n        .label = ",
        .{ limit.id, @tagName(limit.category) },
    );
    try engine.codegen.writeZigString(writer, limit.label);
    try writer.writeAll(",\n        .value = ");
    switch (limit.value) {
        .scalar => |scalar| try writer.print(
            ".{{ .scalar = .{{ .value = {d}, .unit = .{s} }} }}",
            .{ scalar.value, @tagName(scalar.unit) },
        ),
        .dimensions => |dimensions| try writer.print(
            ".{{ .dimensions = .{{ .columns = {d}, .rows = {d} }} }}",
            .{ dimensions.columns, dimensions.rows },
        ),
        .alternative => |alternative| try writer.print(
            ".{{ .alternative = .{{ .items = {d}, .milliseconds = {d} }} }}",
            .{ alternative.items, alternative.milliseconds },
        ),
    }
    try writer.writeAll(",\n    },\n");
}

pub fn renderLimitConstant(limit: model_types.Limit, writer: *std.Io.Writer) !void {
    switch (limit.value) {
        .scalar => |scalar| try writer.print(
            "    pub const {s}: u64 = {d};\n",
            .{ limit.id, scalar.value },
        ),
        .dimensions => |dimensions| try writer.print(
            "    pub const {s}_columns: u16 = {d};\n" ++
                "    pub const {s}_rows: u16 = {d};\n",
            .{ limit.id, dimensions.columns, limit.id, dimensions.rows },
        ),
        .alternative => |alternative| try writer.print(
            "    pub const {s}_items: u32 = {d};\n" ++
                "    pub const {s}_milliseconds: u32 = {d};\n",
            .{ limit.id, alternative.items, limit.id, alternative.milliseconds },
        ),
    }
}

pub fn renderCli(cli: *const model_types.Cli, writer: *std.Io.Writer) !void {
    try writer.writeAll("pub const cli_executable = ");
    try engine.codegen.writeZigString(writer, cli.executable);
    try writer.writeAll(";\npub const cli_description =\n    ");
    try engine.codegen.writeZigString(writer, cli.description);
    try writer.writeAll(";\npub const cli_default_mode = ");
    try engine.codegen.writeZigString(writer, cli.default_mode);
    try writer.print(
        ";\npub const cli_long_options_only = {};\n" ++
            "pub const cli_option_terminator = {};\n" ++
            "pub const cli_operands_allowed = {};\n" ++
            "pub const cli_duplicate_options_allowed = {};\n" ++
            "pub const cli_unattended_cleanup = {};\n" ++
            "pub const OutputStream = enum {{ stdout, stderr }};\n" ++
            "pub const cli_informational_stream: OutputStream = .{s};\n" ++
            "pub const cli_usage_stream: OutputStream = .{s};\n" ++
            "pub const cli_diagnostic_stream: OutputStream = .{s};\n\n",
        .{
            cli.long_options_only,
            cli.option_terminator,
            cli.operands_allowed,
            cli.duplicate_options_allowed,
            cli.unattended_cleanup,
            @tagName(cli.informational_stream),
            @tagName(cli.usage_stream),
            @tagName(cli.diagnostic_stream),
        },
    );
    try renderCliOptions(cli.options, writer);
    try renderCliArgumentRules(cli.argument_rules, writer);
    try renderCliExitStatuses(cli.exit_statuses, writer);
    try renderCliHelp(cli, writer);
}

pub fn renderCliOptions(options: []const model_types.CliOption, writer: *std.Io.Writer) !void {
    try writer.writeAll(
        \\pub const CliOptionId = enum {
        \\
    );
    for (options) |option| try writer.print("    {s},\n", .{option.id});
    try writer.writeAll(
        \\};
        \\pub const CliArgument = enum { none, path };
        \\pub const StartupSubsystem = enum {
        \\    configuration,
        \\    logging,
        \\    terminal_initialization,
        \\    scanning,
        \\};
        \\pub const CliOption = struct {
        \\    id: CliOptionId,
        \\    spelling: []const u8,
        \\    argument: CliArgument,
        \\    summary: []const u8,
        \\    must_appear_alone: bool,
        \\    bypasses_startup: []const StartupSubsystem,
        \\    requires_terminal: bool,
        \\};
        \\pub const cli_options = [_]CliOption{
        \\
    );
    for (options) |option| try renderCliOption(option, writer);
    try writer.writeAll(
        \\};
        \\pub fn cliOption(id: CliOptionId) *const CliOption {
        \\    return &cli_options[@intFromEnum(id)];
        \\}
        \\
    );
}

pub fn renderCliOption(option: model_types.CliOption, writer: *std.Io.Writer) !void {
    try writer.print("    .{{\n        .id = .{s},\n        .spelling = ", .{option.id});
    try engine.codegen.writeZigString(writer, option.spelling);
    try writer.print(
        ",\n        .argument = .{s},\n        .summary = ",
        .{@tagName(option.argument)},
    );
    try engine.codegen.writeZigString(writer, option.summary);
    try writer.print(",\n        .must_appear_alone = {},\n", .{option.must_appear_alone});
    if (option.bypasses_startup.len == 0) {
        try writer.writeAll("        .bypasses_startup = &.{},\n");
    } else {
        try writer.writeAll("        .bypasses_startup = &.{\n");
        for (option.bypasses_startup) |subsystem| {
            try writer.print("            .{s},\n", .{@tagName(subsystem)});
        }
        try writer.writeAll("        },\n");
    }
    try writer.writeAll("        .requires_terminal = ");
    try writer.print("{},\n    }},\n", .{option.requires_terminal});
}

pub fn renderCliArgumentRules(
    rules: []const model_types.CliArgumentRule,
    writer: *std.Io.Writer,
) !void {
    try writer.writeAll(
        \\pub const ArgumentSyntax = enum { separate };
        \\pub const RelativeBase = enum { initial_working_directory };
        \\pub const CliArgumentRule = struct {
        \\    option: CliOptionId,
        \\    argument: []const u8,
        \\    arity: u8,
        \\    syntax: ArgumentSyntax,
        \\    nonempty: bool,
        \\    relative_base: RelativeBase,
        \\};
        \\pub const cli_argument_rules = [_]CliArgumentRule{
        \\
    );
    for (rules) |rule| {
        try writer.print("    .{{\n        .option = .{s},\n        .argument = ", .{rule.option});
        try engine.codegen.writeZigString(writer, rule.argument);
        try writer.print(
            ",\n        .arity = {d},\n        .syntax = .{s},\n" ++
                "        .nonempty = {},\n        .relative_base = .{s},\n    }},\n",
            .{
                rule.arity,
                @tagName(rule.syntax),
                rule.nonempty,
                @tagName(rule.relative_base),
            },
        );
    }
    try writer.writeAll("};\n\n");
}

pub fn renderCliExitStatuses(
    statuses: []const model_types.CliExitStatus,
    writer: *std.Io.Writer,
) !void {
    try writer.writeAll(
        \\pub const CliExitStatusId = enum {
        \\
    );
    for (statuses) |status| try writer.print("    {s},\n", .{status.id});
    try writer.writeAll(
        \\};
        \\pub const CliExitStatus = struct {
        \\    id: CliExitStatusId,
        \\    code: u8,
        \\    condition: []const u8,
        \\};
        \\pub const cli_exit_statuses = [_]CliExitStatus{
        \\
    );
    for (statuses) |status| {
        try writer.print(
            "    .{{\n        .id = .{s},\n        .code = {d},\n        .condition = ",
            .{ status.id, status.code },
        );
        try engine.codegen.writeZigStringLines(writer, status.condition, "            ", 75);
        try writer.writeAll("    },\n");
    }
    try writer.writeAll(
        \\};
        \\pub fn cliExitCode(id: CliExitStatusId) u8 {
        \\    return cli_exit_statuses[@intFromEnum(id)].code;
        \\}
        \\
    );
}

pub fn renderCliHelp(cli: *const model_types.Cli, writer: *std.Io.Writer) !void {
    const display_width = cliOptionDisplayWidth(cli);
    try writer.writeAll(
        \\pub const cli_help =
        \\    "Usage: " ++ cli_executable ++ " [OPTIONS]\n\n" ++
        \\    cli_description ++ "\n\n" ++
        \\    "Options:\n" ++
        \\
    );
    for (cli.options) |option| {
        try writer.writeAll("    \"  \" ++ ");
        try engine.codegen.writeZigString(writer, option.spelling);
        const argument = findCliArgumentRule(cli.argument_rules, option.id);
        if (argument) |rule| {
            try writer.writeAll(" ++ \" \" ++ ");
            try engine.codegen.writeZigString(writer, rule.argument);
        }
        const option_width = cliOptionWidth(&option, argument);
        try writer.writeAll(" ++ ");
        try writeRepeatedSpaces(writer, display_width - option_width + 2);
        try writer.writeAll(" ++\n    ");
        try engine.codegen.writeZigString(writer, option.summary);
        if (option.must_appear_alone) {
            try writer.writeAll(" ++\n    \" Must appear alone.\"");
        }
        try writer.writeAll(" ++ \"\\n\" ++\n");
    }
    if (!cli.duplicate_options_allowed) {
        try writer.writeAll("    \"\\nEach option may appear at most once.\\n\" ++\n");
    }
    if (!cli.operands_allowed) {
        try writer.writeAll("    \"Operands are not supported.\\n\" ++\n");
    }
    if (!cli.unattended_cleanup) {
        try writer.writeAll("    \"Unattended cleanup is not supported.\\n\";\n\n");
    } else {
        try writer.writeAll("    \"\";\n\n");
    }
}

pub fn cliOptionDisplayWidth(cli: *const model_types.Cli) usize {
    var width: usize = 0;
    for (cli.options) |option| {
        const argument = findCliArgumentRule(cli.argument_rules, option.id);
        width = @max(width, cliOptionWidth(&option, argument));
    }
    return width;
}

pub fn cliOptionWidth(
    option: *const model_types.CliOption,
    argument: ?model_types.CliArgumentRule,
) usize {
    if (argument) |rule| return option.spelling.len + 1 + rule.argument.len;
    return option.spelling.len;
}

pub fn findCliArgumentRule(
    rules: []const model_types.CliArgumentRule,
    option_id: []const u8,
) ?model_types.CliArgumentRule {
    for (rules) |rule| if (std.mem.eql(u8, rule.option, option_id)) return rule;
    return null;
}

pub fn writeRepeatedSpaces(writer: *std.Io.Writer, count: usize) !void {
    try writer.writeByte('"');
    for (0..count) |_| try writer.writeByte(' ');
    try writer.writeByte('"');
}