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 = ""; const android_end = ""; const basenames_begin = ""; const basenames_end = ""; const suffixes_begin = ""; const suffixes_end = ""; const roots_begin = ""; const roots_end = ""; const thresholds_begin = ""; const thresholds_end = ""; 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), }); } }