Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 33 additions & 19 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -87,32 +87,46 @@ pub fn build(b: *std.Build) !void {
var test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_lib_unit_tests.step);
test_step.dependOn(&run_exe_unit_tests.step);
{
const helpgen_exe = b.addExecutable(.{
.name = "helpgen",

// Setup helpgen for generating help_strings module
const helpgen_exe = b.addExecutable(.{
.name = "helpgen",
.root_module = b.createModule(.{
.root_source_file = b.path("src/cli/helpgen.zig"),
.target = b.graph.host,
}),
});
const help_run = b.addRunArtifact(helpgen_exe);
const help_strings_stdout = help_run.captureStdOut();
// Workaround for Zig 0.15 regression: module files need .zig extension
// See: https://github.com/ziglang/zig/issues/24957
const wf = b.addWriteFiles();
const help_strings_output = wf.addCopyFile(help_strings_stdout, "help_strings.zig");
exe_mod.addAnonymousImport(
"help_strings",
.{
.root_source_file = help_strings_output,
},
);
var helpgen_step = b.step("helpgen", "Generate Help Text");
helpgen_step.dependOn(&help_run.step);

const integration_tests_step = step: {
const integration_tests = b.addExecutable(.{
.name = "sparse-integration-tests",
.root_module = b.createModule(.{
.root_source_file = b.path("src/cli/helpgen.zig"),
.target = b.graph.host,
.root_source_file = b.path("test/integration.zig"),
.optimize = optimize,
.target = target,
}),
});
const help_run = b.addRunArtifact(helpgen_exe);
const output = help_run.captureStdOut();
exe.root_module.addAnonymousImport(
integration_tests.root_module.addImport("sparse", exe_mod);
integration_tests.root_module.addAnonymousImport(
"help_strings",
.{
.root_source_file = output,
.root_source_file = help_strings_output,
},
);
}

const integration_tests_step = step: {
const integration_tests = b.addExecutable(.{
.name = "sparse-integration-tests",
.root_source_file = b.path("test/integration.zig"),
.optimize = optimize,
.target = target,
});
integration_tests.root_module.addImport("sparse", exe_mod);

const integration_unit_tests = b.addTest(.{
.root_module = integration_tests.root_module,
Expand Down
7 changes: 5 additions & 2 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
.name = .sparse,
.version = "0.0.0",
.fingerprint = 0x2883adb9d90cf982, // Changing this has security and trust implications.
.minimum_zig_version = "0.14.0",
.minimum_zig_version = "0.15.2",
.dependencies = .{
.libgit2 = .{ .path = "vendored/libgit2" },
.libgit2 = .{
.url = "git+https://github.com/allyourcodebase/libgit2#58dfd002d47a8c9fbd99d4a939cb343172590e1b",
.hash = "libgit2-1.9.0-uizqTQnZAADyPOmBklxzj_lrnfJoRLRDBbbTvi28SrZX",
},
.apple_sdk = .{ .path = "vendored/apple-sdk" },
},

Expand Down
8 changes: 4 additions & 4 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
flake-utils.url = "github:numtide/flake-utils";

# 0.14.0
zig-nixpkgs.url = "github:NixOS/nixpkgs/f6db44a8daa59c40ae41ba6e5823ec77fe0d2124";
zig-nixpkgs.url = "github:NixOS/nixpkgs/f665af0cdb70ed27e1bd8f9fdfecaf451260fc55";
# 1.9.0
# libgit2-nixpkgs.url = "github:NixOS/nixpkgs/f6db44a8daa59c40ae41ba6e5823ec77fe0d2124";
};
Expand Down Expand Up @@ -115,6 +115,7 @@
devShells.default = zig-nixpkgs.mkShell {
packages = [
zig-nixpkgs.zig
zig-nixpkgs.zls
zig-nixpkgs.openssl
];

Expand Down
46 changes: 33 additions & 13 deletions src/cli.zig
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,15 @@ fn getCommandDescription(command_name: []const u8) []const u8 {
return "Unknown command";
}

fn showHelp() void {
const writer = std.io.getStdOut().writer();

fn showHelp() !void {
//const writer = std.fs.File.stdout().writer(stdout)
var buffer: [4096]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&buffer);
const writer = &stdout_writer.interface;
//TODO: add error log to flush
defer {
writer.flush() catch {};
}
writer.print("Sparse - A CLI tool for stacked pull request workflows\n\n", .{}) catch return;
writer.print("USAGE:\n sparse <command> [options]\n\n", .{}) catch return;
writer.print("COMMANDS:\n", .{}) catch return;
Expand All @@ -35,6 +41,13 @@ fn showHelp() void {
}

fn parse(args: [][:0]u8) !Command {
var buffer: [4096]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&buffer);
const writer = &stdout_writer.interface;
// TODO: add err log to defer
defer {
writer.flush() catch {};
}
const my_commands = @typeInfo(Command).@"union".fields;

if (args.len < 2) {
Expand All @@ -43,7 +56,7 @@ fn parse(args: [][:0]u8) !Command {

// Check for global --help flag
if (std.mem.eql(u8, args[1], "--help")) {
showHelp();
try showHelp();
std.process.exit(0);
}

Expand All @@ -56,36 +69,43 @@ fn parse(args: [][:0]u8) !Command {
}

pub fn run(alloc: Allocator) !void {
var buffer: [4096]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&buffer);
var writer = &stdout_writer.interface;
//TODO: add error log to flush
defer {
writer.flush() catch {};
}
const args = try std.process.argsAlloc(alloc);
defer std.process.argsFree(alloc, args);

const command = parse(args) catch |err| switch (err) {
CommandError.UnknownCommand => {
const stdout = std.io.getStdOut().writer();
//const stdout = std.io.getStdOut().writer();
if (args.len >= 2) {
stdout.print("'{s}' is not a sparse command.\n\n", .{args[1]}) catch {};
writer.print("'{s}' is not a sparse command.\n\n", .{args[1]}) catch {};
} else {
stdout.print("No command specified.\n\n", .{}) catch {};
writer.print("No command specified.\n\n", .{}) catch {};
}
stdout.print("Available commands: ", .{}) catch {};
writer.print("Available commands: ", .{}) catch {};

const my_commands = @typeInfo(Command).@"union".fields;
inline for (my_commands, 0..) |c, i| {
if (i > 0) stdout.print(", ", .{}) catch {};
stdout.print("{s}", .{c.name}) catch {};
if (i > 0) writer.print(", ", .{}) catch {};
writer.print("{s}", .{c.name}) catch {};
}
stdout.print("\n\nFor more help: sparse --help\n", .{}) catch {};
writer.print("\n\nFor more help: sparse --help\n", .{}) catch {};
std.process.exit(1);
},
else => return err,
};
const return_code = try command.run(alloc);
std.process.exit(return_code);
}

// TODO: add tests about writer
test "parse a non existent command" {
const expectEqual = std.testing.expectEqual;
const args: [2][:0]const u8 = .{ "sparse", "boo" };
const command = parse(@constCast(@ptrCast(&args))) catch |e| e;
const command = parse(@ptrCast(@constCast(&args))) catch |e| e;
try expectEqual(CommandError.UnknownCommand, command);
}
1 change: 1 addition & 0 deletions src/cli/command.zig
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const std = @import("std");
const log = std.log.scoped(.command);
//const FileWriter = std.Io.Writer;

const Allocator = std.mem.Allocator;

Expand Down
12 changes: 10 additions & 2 deletions src/cli/feature_command.zig
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,13 @@ const Params = struct {
@"-h": *const fn () void = Options.help,

pub fn help() void {
std.io.getStdOut().writer().print(help_strings.sparse_feature, .{}) catch return;
var buffer: [4096]u8 = .{0} ** 4096;
var stdout_writer = std.fs.File.stdout().writer(&buffer);
const stdout = &stdout_writer.interface;
defer {
stdout.flush() catch {};
}
stdout.print(help_strings.sparse_feature, .{}) catch {};
}
} = .{},
};
Expand All @@ -40,7 +46,9 @@ pub const FeatureCommand = struct {
var params = Params{ .feature_name = undefined };
const args = try std.process.argsAlloc(alloc);
defer std.process.argsFree(alloc, args);
log.debug("got cli arguments: {s}", .{args});
for (args) |arg| {
log.debug("got cli arguments: {s}", .{arg});
}

const cli_positionals = command.parseOptions(
@TypeOf(params._options),
Expand Down
49 changes: 28 additions & 21 deletions src/cli/helpgen.zig
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
const std = @import("std");
const Writer = std.Io.Writer;
const Tag = std.zig.Token.Tag;
const Ast = std.zig.Ast;
const Allocator = std.mem.Allocator;

pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const alloc = gpa.allocator();
const stdout = std.io.getStdOut().writer();
var buffer: [4096]u8 = .{0} ** 4096;
var stdout_writer = std.fs.File.stdout().writer(&buffer);
const stdout = &stdout_writer.interface;
defer {
stdout.flush() catch {};
}

try genCommands(alloc, stdout);
}

fn genCommands(alloc: std.mem.Allocator, writer: anytype) !void {
fn genCommands(alloc: std.mem.Allocator, writer: *Writer) !void {
try extractFileIntoHelp(alloc, writer, "feature_command.zig", "sparse_feature");
try extractFileIntoHelp(alloc, writer, "slice_command.zig", "sparse_slice");
try extractFileIntoHelp(alloc, writer, "update_command.zig", "sparse_update");
try extractFileIntoHelp(alloc, writer, "status_command.zig", "sparse_status");
}

fn extractFileIntoHelp(alloc: Allocator, writer: anytype, comptime zig_file: []const u8, comptime const_name: []const u8) !void {
fn extractFileIntoHelp(alloc: Allocator, writer: *Writer, comptime zig_file: []const u8, comptime const_name: []const u8) !void {
var ast = try Ast.parse(alloc, @embedFile(zig_file), .zig);
defer ast.deinit(alloc);
defer {
writer.flush() catch {};
}
const tokens = ast.tokens.items(.tag);
const maybe_params_struct = findToken(ast, tokens, isParams);
if (maybe_params_struct) |params_struct| {
Expand Down Expand Up @@ -65,31 +74,31 @@ fn isParams(tt: []Tag, current_index: usize, a: Ast) bool {

/// First token must be .l_brace
fn extractNextStruct(alloc: Allocator, ast: Ast, start_idx: usize) ![]const u8 {
var stack = std.ArrayList(Tag).init(alloc);
defer stack.deinit();
var lines = std.ArrayList([]const u8).init(alloc);
defer lines.deinit();
var stack = std.ArrayList(Tag).empty;
defer stack.deinit(alloc);
var lines = std.ArrayList([]const u8).empty;
defer lines.deinit(alloc);

const tokens = ast.tokens.items(.tag);
for (tokens[start_idx..], start_idx..) |token, i| {
//std.debug.print("Found {} name: {s}\n", .{ token, ast.tokenSlice(@intCast(i)) });
if (token == .l_brace) _ = try stack.append(token);
if (token == .l_brace) _ = try stack.append(alloc, token);
if (token == .r_brace) _ = stack.pop();
if (stack.items.len == 0) break;

// We only care about identifiers that are preceded by doc comments.
if (token != .identifier) continue;
if (tokens[i - 2] != .doc_comment and tokens[i - 1] != .doc_comment) continue;
const extracted = try extractDocComments(alloc, ast, @intCast(i), tokens);
try lines.append(extracted);
try lines.append(alloc, extracted);
}

var buffer = std.ArrayList(u8).init(alloc);
defer buffer.deinit();
var buffer = std.ArrayList(u8).empty;
defer buffer.deinit(alloc);
for (lines.items) |line| {
try buffer.writer().print("{s}", .{line});
try buffer.print(alloc, "{s}", .{line});
}
return buffer.toOwnedSlice();
return buffer.toOwnedSlice(alloc);
}

fn extractDocComments(
Expand All @@ -107,24 +116,22 @@ fn extractDocComments(
} else unreachable;

// Go through and build up the lines.
var lines = std.ArrayList([]const u8).init(alloc);
defer lines.deinit();
var lines = std.ArrayList([]const u8).empty;
defer lines.deinit(alloc);
for (start_idx..index + 1) |i| {
const token = tokens[i];
if (token != .doc_comment) break;
try lines.append(ast.tokenSlice(@intCast(i))[3..]);
try lines.append(alloc, ast.tokenSlice(@intCast(i))[3..]);
}

// Convert the lines to a multiline string.
var buffer = std.ArrayList(u8).init(alloc);
const writer = buffer.writer();
var buffer = std.ArrayList(u8).empty;
const prefix = findCommonPrefix(lines);
for (lines.items) |line| {
try writer.writeAll(line[@min(prefix, line.len)..]);
try writer.writeAll("\n");
try buffer.print(alloc, "{s}\n", .{line[@min(prefix, line.len)..]});
}

return buffer.toOwnedSlice();
return buffer.toOwnedSlice(alloc);
}

fn findCommonPrefix(lines: std.ArrayList([]const u8)) usize {
Expand Down
14 changes: 12 additions & 2 deletions src/cli/slice_command.zig
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,14 @@ const Params = struct {
@"-h": *const fn () void = Options.help,

pub fn help() void {
std.io.getStdOut().writer().print(help_strings.sparse_slice, .{}) catch return;
var buffer: [4096]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&buffer);
const stdout = &stdout_writer.interface;
// TODO: add err log
defer {
stdout.flush() catch {};
}
stdout.print(help_strings.sparse_slice, .{}) catch {};
}
} = .{},
};
Expand All @@ -38,7 +45,10 @@ pub const SliceCommand = struct {
var params = Params{ .slice_name = undefined };
const args = try std.process.argsAlloc(alloc);
defer std.process.argsFree(alloc, args);
log.debug("run:: args: {s}", .{args});
for (args) |arg| {
log.debug("got cli arguments: {s}", .{arg});
}
//log.debug("run:: args: {any}", .{args});

const cli_positionals = command.parseOptions(
@TypeOf(params._options),
Expand Down
Loading