Luigit
repositories / termux-janitor

termux-janitor

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

owned by admin

tools/janitor/project.zig

Raw
const std = @import("std");
const engine = @import("engine");
const model_types = @import("model.zig");
const output_size_max = engine.project.output_size_max;
const adapters_path = "spec/ADAPTERS.md";
const classification_path = "spec/CLASSIFICATION.md";
const product_path = "spec/PRODUCT.md";
const android_begin = "<!-- generated: android-packages -->";
const android_end = "<!-- end-generated: android-packages -->";
const basenames_begin = "<!-- generated: source-basenames -->";
const basenames_end = "<!-- end-generated: source-basenames -->";
const suffixes_begin = "<!-- generated: source-suffixes -->";
const suffixes_end = "<!-- end-generated: source-suffixes -->";
const roots_begin = "<!-- generated: document-roots -->";
const roots_end = "<!-- end-generated: document-roots -->";
const thresholds_begin = "<!-- generated: thresholds -->";
const thresholds_end = "<!-- end-generated: thresholds -->";

pub fn checkDerived(
    allocator: std.mem.Allocator,
    io: std.Io,
    model: *const model_types.Model,
    stderr: *std.Io.Writer,
) !void {
    try checkAndroidProjection(allocator, io, model.android_packages, stderr);
    try checkClassificationProjection(allocator, io, &model.classification, stderr);
    try checkThresholdProjection(allocator, io, model.thresholds, stderr);
}

fn checkAndroidProjection(
    allocator: std.mem.Allocator,
    io: std.Io,
    packages: []const model_types.AndroidPackage,
    stderr: *std.Io.Writer,
) !void {
    const source = try readProjection(allocator, io, adapters_path);
    defer allocator.free(source);
    var list = std.Io.Writer.Allocating.init(allocator);
    defer list.deinit();
    try renderAndroidPackageList(packages, &list.writer);
    try checkRegion(allocator, source, adapters_path, android_begin, android_end, list.written(), stderr);
}

fn checkClassificationProjection(
    allocator: std.mem.Allocator,
    io: std.Io,
    classification: *const model_types.Classification,
    stderr: *std.Io.Writer,
) !void {
    const source = try readProjection(allocator, io, classification_path);
    defer allocator.free(source);
    try checkValues(allocator, source, classification_path, basenames_begin, basenames_end, classification.source_basenames.values, stderr);
    try checkValues(allocator, source, classification_path, suffixes_begin, suffixes_end, classification.source_suffixes.values, stderr);
    try checkValues(allocator, source, classification_path, roots_begin, roots_end, classification.document_roots.values, stderr);
}

fn checkThresholdProjection(
    allocator: std.mem.Allocator,
    io: std.Io,
    thresholds: []const model_types.Threshold,
    stderr: *std.Io.Writer,
) !void {
    const source = try readProjection(allocator, io, product_path);
    defer allocator.free(source);
    var list = std.Io.Writer.Allocating.init(allocator);
    defer list.deinit();
    try renderThresholds(thresholds, &list.writer);
    try checkRegion(allocator, source, product_path, thresholds_begin, thresholds_end, list.written(), stderr);
}

fn checkValues(
    allocator: std.mem.Allocator,
    source: []const u8,
    path: []const u8,
    begin: []const u8,
    end: []const u8,
    values: []const []const u8,
    stderr: *std.Io.Writer,
) !void {
    var list = std.Io.Writer.Allocating.init(allocator);
    defer list.deinit();
    try renderValues(values, &list.writer);
    try checkRegion(allocator, source, path, begin, end, list.written(), stderr);
}

pub fn runGenerate(
    allocator: std.mem.Allocator,
    io: std.Io,
    model: *const model_types.Model,
    args: []const []const u8,
) !void {
    if (args.len > 1) return error.UnexpectedArgument;
    const target = if (args.len == 1) args[0] else "all";
    if (std.mem.eql(u8, target, "all") or std.mem.eql(u8, target, "adapters")) {
        try generateAndroid(allocator, io, model.android_packages);
    }
    if (std.mem.eql(u8, target, "all") or std.mem.eql(u8, target, "classification")) {
        try generateClassification(allocator, io, &model.classification);
    }
    if (std.mem.eql(u8, target, "all") or std.mem.eql(u8, target, "thresholds")) {
        try generateThresholds(allocator, io, model.thresholds);
    }
}

fn generateAndroid(
    allocator: std.mem.Allocator,
    io: std.Io,
    packages: []const model_types.AndroidPackage,
) !void {
    const source = try readProjection(allocator, io, adapters_path);
    defer allocator.free(source);
    var list = std.Io.Writer.Allocating.init(allocator);
    defer list.deinit();
    try renderAndroidPackageList(packages, &list.writer);
    const output = try engine.project.replaceNamedRegion(
        allocator,
        source,
        android_begin,
        android_end,
        list.written(),
    );
    defer allocator.free(output);
    try writeProjection(io, adapters_path, output);
}

fn generateClassification(
    allocator: std.mem.Allocator,
    io: std.Io,
    classification: *const model_types.Classification,
) !void {
    const source = try readProjection(allocator, io, classification_path);
    defer allocator.free(source);
    const first = try replaceValues(allocator, source, basenames_begin, basenames_end, classification.source_basenames.values);
    defer allocator.free(first);
    const second = try replaceValues(allocator, first, suffixes_begin, suffixes_end, classification.source_suffixes.values);
    defer allocator.free(second);
    const output = try replaceValues(allocator, second, roots_begin, roots_end, classification.document_roots.values);
    defer allocator.free(output);
    try writeProjection(io, classification_path, output);
}

fn generateThresholds(
    allocator: std.mem.Allocator,
    io: std.Io,
    thresholds: []const model_types.Threshold,
) !void {
    const source = try readProjection(allocator, io, product_path);
    defer allocator.free(source);
    var list = std.Io.Writer.Allocating.init(allocator);
    defer list.deinit();
    try renderThresholds(thresholds, &list.writer);
    const output = try engine.project.replaceNamedRegion(
        allocator,
        source,
        thresholds_begin,
        thresholds_end,
        list.written(),
    );
    defer allocator.free(output);
    try writeProjection(io, product_path, output);
}

fn replaceValues(
    allocator: std.mem.Allocator,
    source: []const u8,
    begin: []const u8,
    end: []const u8,
    values: []const []const u8,
) ![]const u8 {
    var list = std.Io.Writer.Allocating.init(allocator);
    defer list.deinit();
    try renderValues(values, &list.writer);
    return engine.project.replaceNamedRegion(allocator, source, begin, end, list.written());
}

fn readProjection(
    allocator: std.mem.Allocator,
    io: std.Io,
    path: []const u8,
) ![]const u8 {
    return std.Io.Dir.cwd().readFileAlloc(io, path, allocator, .limited(output_size_max));
}

fn writeProjection(io: std.Io, path: []const u8, data: []const u8) !void {
    var temporary_buffer: [512]u8 = undefined;
    const temporary = std.fmt.bufPrint(
        &temporary_buffer,
        "{s}.spec-tool.tmp",
        .{path},
    ) catch return error.PathTooLong;
    try engine.project.replaceFile(io, path, temporary, data);
}

fn checkRegion(
    allocator: std.mem.Allocator,
    source: []const u8,
    path: []const u8,
    begin: []const u8,
    end: []const u8,
    replacement: []const u8,
    stderr: *std.Io.Writer,
) !void {
    const expected = try engine.project.replaceNamedRegion(
        allocator,
        source,
        begin,
        end,
        replacement,
    );
    defer allocator.free(expected);
    if (!std.mem.eql(u8, source, expected)) {
        return engine.diag.fail(
            stderr,
            engine.diag.subject.projection,
            "generated projection is stale: {s}",
            .{path},
        );
    }
}

fn renderAndroidPackageList(
    packages: []const model_types.AndroidPackage,
    writer: *std.Io.Writer,
) !void {
    for (packages) |package| {
        try writer.print("- `{s}` ({s})\n", .{
            package.identifier,
            @tagName(package.role),
        });
    }
}

fn renderValues(values: []const []const u8, writer: *std.Io.Writer) !void {
    for (values) |value| try writer.print("- `{s}`\n", .{value});
}

fn renderThresholds(
    thresholds: []const model_types.Threshold,
    writer: *std.Io.Writer,
) !void {
    for (thresholds) |threshold| {
        try writer.print("- `{s}`: {d} {s}\n", .{
            threshold.key,
            threshold.value,
            @tagName(threshold.unit),
        });
    }
}