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