repositories / termux-janitor
termux-janitor
Interactive cleanup assistant for Termux: transparent, safe, confirmed disk reclamation.
owned by admin
tools/janitor/project.zig
Rawconst 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),
});
}
}