const std = @import("std"); const mem = std.mem; const fs = std.fs; const Step = std.Build.Step; const Build = std.Build; /// ScdocStep generates man pages using scdoc(1). /// /// It reads all the raw pages from src_path and writes them to out_path. /// src_path is typically "docs/" relative to the build root and out_path is /// the build cache. /// /// The man pages can be installed by calling install() on the step. const ScdocStep = @This(); step: Step, builder: *Build, /// path to read man page sources from, defaults to the "doc/" subdirectory /// from the build.zig file. This must be an absolute path. src_path: []const u8, /// path where the generated man pages will be written (NOT installed). This /// defaults to build cache root. out_path: []const u8, pub fn create(builder: *Build) *ScdocStep { const self = builder.allocator.create(ScdocStep) catch unreachable; self.* = init(builder); return self; } pub fn init(builder: *Build) ScdocStep { return ScdocStep{ .builder = builder, .step = Step.init(.{ .id = .custom, .name = "generate man pages", .owner = builder, .makeFn = make, }), .src_path = builder.pathFromRoot("docs/"), .out_path = builder.cache_root.join(builder.allocator, &[_][]const u8{ "man", }) catch unreachable, }; } fn make(step: *std.Build.Step, _: std.Progress.Node) !void { const self: *ScdocStep = @fieldParentPtr("step", step); // Create our cache path // TODO(mitchellh): ideally this would be pure zig { const command = try std.fmt.allocPrint( self.builder.allocator, "rm -f {[path]s}/* && mkdir -p {[path]s}", .{ .path = self.out_path }, ); _ = self.builder.run(&[_][]const u8{ "sh", "-c", command }); } // Find all our man pages which are in our src path ending with ".scd". var dir = try fs.openDirAbsolute(self.src_path, .{ .iterate = true }); defer dir.close(); var iter = dir.iterate(); while (try iter.next()) |*entry| { // We only want "scd" files to generate. if (!mem.eql(u8, fs.path.extension(entry.name), ".scd")) { continue; } const src = try fs.path.join( self.builder.allocator, &[_][]const u8{ self.src_path, entry.name }, ); const dst = try fs.path.join( self.builder.allocator, &[_][]const u8{ self.out_path, entry.name[0..(entry.name.len - 4)] }, ); const command = try std.fmt.allocPrint( self.builder.allocator, "scdoc < {s} > {s}", .{ src, dst }, ); _ = self.builder.run(&[_][]const u8{ "sh", "-c", command }); } } pub fn install(self: *ScdocStep) !void { // Ensure that `zig build install` depends on our generation step first. self.builder.getInstallStep().dependOn(&self.step); // Then run our install step which looks at what we made out of our // generation and moves it to the install prefix. const install_step = InstallStep.create(self.builder, self); self.builder.getInstallStep().dependOn(&install_step.step); } /// Install man pages, create using install() on ScdocStep. const InstallStep = struct { step: Step, builder: *Build, scdoc: *ScdocStep, pub fn create(builder: *Build, scdoc: *ScdocStep) *InstallStep { const self = builder.allocator.create(InstallStep) catch unreachable; self.* = InstallStep.init(builder, scdoc); self.step.dependOn(&scdoc.step); return self; } fn init(builder: *Build, scdoc: *ScdocStep) InstallStep { return InstallStep{ .builder = builder, .step = Step.init(.{ .id = .custom, .name = "install man pages", .owner = builder, .makeFn = InstallStep.make, }), .scdoc = scdoc, }; } fn make(step: *Step, progress: std.Progress.Node) !void { const self: *InstallStep = @fieldParentPtr("step", step); // Get our absolute output path var path = self.scdoc.out_path; if (!fs.path.isAbsolute(path)) { path = self.builder.pathFromRoot(path); } // Find all our man pages which are in our src path ending with ".scd". var dir = try fs.openDirAbsolute(path, .{ .iterate = true }); defer dir.close(); var iter = dir.iterate(); while (try iter.next()) |*entry| { // We expect filenames to be "foo.3" and this gets us "3" const section = entry.name[(entry.name.len - 1)..]; const src = try fs.path.join( self.builder.allocator, &[_][]const u8{ path, entry.name }, ); const output = try std.fmt.allocPrint( self.builder.allocator, "share/man/man{s}/{s}", .{ section, entry.name }, ); const fileStep = self.builder.addInstallFile( .{ .cwd_relative = src }, output, ); try fileStep.step.make(progress); } } };