srctree

Gregory Mullen parent bfed9fca 90b69b2a
add patch/diff builder

someone [not me] should really [not me] fix up [not me] all this [not me] messy css [not me]. It's starting to get unwieldy.
src/endpoints/repos/commits.zig added: 304, removed: 99, total 205
@@ -15,27 +15,7 @@ const RouteData = Repos.RouteData;
 
const git = @import("../../git.zig");
const Bleach = @import("../../bleach.zig");
 
pub fn diffLine(a: Allocator, diff: []const u8) []HTML.Element {
var dom = DOM.new(a);
 
const count = std.mem.count(u8, diff, "\n");
var litr = std.mem.split(u8, diff, "\n");
for (0..count + 1) |_| {
const a_add = &HTML.Attr.class("add");
const a_del = &HTML.Attr.class("del");
const dirty = litr.next().?;
var clean = a.alloc(u8, dirty.len * 2) catch unreachable;
clean = Bleach.sanitize(dirty, clean, .{}) catch unreachable;
const attr: ?[]const HTML.Attr = if (clean.len > 0 and (clean[0] == '-' or clean[0] == '+'))
if (clean[0] == '-') a_del else a_add
else
null;
dom.dupe(HTML.span(clean, attr));
}
 
return dom.done();
}
const Patch = @import("../../patch.zig");
 
fn commitHtml(r: *Response, sha: []const u8, repo_name: []const u8, repo: git.Repo) Error!void {
var tmpl = Template.find("commit.html");
@@ -63,7 +43,7 @@ fn commitHtml(r: *Response, sha: []const u8, repo_name: []const u8, repo: git.Re
var diff_dom = DOM.new(r.alloc);
diff_dom = diff_dom.open(HTML.element("diff", null, null));
diff_dom = diff_dom.open(HTML.element("patch", null, null));
diff_dom.pushSlice(diffLine(r.alloc, diff));
diff_dom.pushSlice(Patch.diffLine(r.alloc, diff));
diff_dom = diff_dom.close();
diff_dom = diff_dom.close();
_ = tmpl.addElementsFmt(r.alloc, "{pretty}", "diff", diff_dom.done()) catch return error.Unknown;
 
src/endpoints/repos/diffs.zig added: 304, removed: 99, total 205
@@ -12,7 +12,6 @@ const Error = Endpoint.Error;
const UriIter = Endpoint.Router.UriIter;
 
const Repo = @import("../repos.zig");
const diffLine = @import("commits.zig").diffLine;
 
const GET = Endpoint.Router.Methods.GET;
const POST = Endpoint.Router.Methods.POST;
@@ -22,6 +21,7 @@ const Bleach = @import("../../bleach.zig");
const Diffs = Endpoint.Types.Diffs;
const Comments = Endpoint.Types.Comments;
const Comment = Comments.Comment;
const Patch = @import("../../patch.zig");
 
pub const routes = [_]Endpoint.Router.MatchRouter{
.{ .name = "", .methods = GET, .match = .{ .call = list } },
@@ -61,21 +61,20 @@ fn new(r: *Response, _: *UriIter) Error!void {
tmpl.init(r.alloc);
 
var dom = DOM.new(r.alloc);
dom = dom.open(HTML.element("intro", null, null));
dom.push(HTML.text("New Pull Request"));
dom = dom.close();
var fattr = try r.alloc.dupe(HTML.Attr, &[_]HTML.Attr{
.{ .key = "action", .value = "new" },
.{ .key = "method", .value = "POST" },
});
dom = dom.open(HTML.form(null, fattr));
 
dom.push(HTML.element("context", "New Pull Request", null));
dom.push(try HTML.inputAlloc(a, "diff source", .{ .placeholder = "Patch URL" }));
dom.push(try HTML.inputAlloc(a, "title", .{ .placeholder = "Diff Title" }));
dom.push(try HTML.textareaAlloc(a, "desc", .{ .placeholder = "Additional information about this patch suggestion" }));
dom = dom.open(HTML.element("buttons", null, null));
dom.dupe(HTML.btnDupe("Submit", "submit"));
dom.dupe(HTML.btnDupe("Preview", "preview"));
dom = dom.close();
dom = dom.close();
 
_ = try tmpl.addElements(r.alloc, "diff", dom.done());
r.sendTemplate(&tmpl) catch unreachable;
@@ -87,34 +86,6 @@ fn inNetwork(str: []const u8) bool {
return true;
}
 
fn fetch(a: Allocator, uri: []const u8) ![]const u8 {
// Disabled until TLS1.2 is supported
// var client = std.http.Client{
// .allocator = a,
// };
// defer client.deinit();
 
// var request = client.fetch(a, .{
// .location = .{ .url = uri },
// });
// if (request) |*req| {
// defer req.deinit();
// std.debug.print("request code {}\n", .{req.status});
// if (req.body) |b| {
// std.debug.print("request body {s}\n", .{b});
// return a.dupe(u8, b);
// }
// } else |err| {
// std.debug.print("stdlib request failed with error {}\n", .{err});
// }
 
var curl = try CURL.curlRequest(a, uri);
if (curl.code != 200) return error.UnexpectedResponseCode;
 
if (curl.body) |b| return b;
return error.EpmtyReponse;
}
 
fn newPost(r: *Response, uri: *UriIter) Error!void {
const rd = Repo.RouteData.make(uri) orelse return error.Unrouteable;
if (r.usr_data) |usrdata| if (usrdata.post_data) |post| {
@@ -132,7 +103,7 @@ fn newPost(r: *Response, uri: *UriIter) Error!void {
desc.value,
action.name,
});
var data = fetch(r.alloc, src.value) catch unreachable;
var data = Patch.loadRemote(r.alloc, src.value) catch unreachable;
var filename = std.fmt.allocPrint(
r.alloc,
"data/patch/{s}.{x}.patch",
@@ -140,7 +111,7 @@ fn newPost(r: *Response, uri: *UriIter) Error!void {
) catch unreachable;
var file = std.fs.cwd().createFile(filename, .{}) catch unreachable;
defer file.close();
file.writer().writeAll(data) catch unreachable;
file.writer().writeAll(data.patch) catch unreachable;
}
};
 
@@ -152,21 +123,22 @@ fn newPost(r: *Response, uri: *UriIter) Error!void {
 
fn newComment(r: *Response, uri: *UriIter) Error!void {
const rd = Repo.RouteData.make(uri) orelse return error.Unrouteable;
var buf: [2048]u8 = undefined;
if (r.usr_data) |usrdata| if (usrdata.post_data) |post| {
var valid = post.validator();
const diff_id = try valid.require("diff-id");
const msg = try valid.require("comment");
const diff_index = isHex(diff_id.value) orelse return error.Unrouteable;
const loc = try std.fmt.bufPrint(&buf, "/repo/{s}/diffs/{x}", .{ rd.name, diff_index });
 
const msg = try valid.require("comment");
if (msg.value.len < 2) return r.redirect(loc, true) catch unreachable;
 
var diff = Diffs.open(r.alloc, diff_index) catch unreachable orelse return error.Unrouteable;
var c = Comments.new("name", msg.value) catch unreachable;
 
diff.addComment(r.alloc, c) catch {};
diff.writeOut() catch unreachable;
var buf: [2048]u8 = undefined;
const loc = try std.fmt.bufPrint(&buf, "/repo/{s}/diffs/{x}", .{ rd.name, diff_index });
r.redirect(loc, true) catch unreachable;
return;
return r.redirect(loc, true) catch unreachable;
};
return error.Unknown;
}
@@ -244,15 +216,11 @@ fn view(r: *Response, uri: *UriIter) Error!void {
_ = try tmpl.addElements(r.alloc, "form-data", &form_data);
 
const filename = try std.fmt.allocPrint(r.alloc, "data/patch/{s}.{x}.patch", .{ rd.name, diff.index });
std.debug.print("{s}\n", .{filename});
var file: ?std.fs.File = std.fs.cwd().openFile(filename, .{}) catch null;
if (file) |f| {
const fdata = f.readToEndAlloc(r.alloc, 0xFFFFF) catch return error.Unknown;
var patch = DOM.new(r.alloc);
patch = patch.open(HTML.element("patch", null, null));
patch.pushSlice(diffLine(r.alloc, fdata));
patch = patch.close();
_ = try tmpl.addElementsFmt(r.alloc, "{pretty}", "patch", patch.done());
const patch = try Patch.patchHtml(r.alloc, fdata);
_ = try tmpl.addElementsFmt(r.alloc, "{pretty}", "patch", patch);
f.close();
} else try tmpl.addString("patch", "Patch not found");
 
 
src/html/extra.zig added: 304, removed: 99, total 205
@@ -7,6 +7,14 @@ pub fn repo() E {
return element("repo", null, null);
}
 
pub fn diff() E {
return element("diff", null, null);
}
 
pub fn patch() E {
return element("patch", null, null);
}
 
pub fn commit(c: anytype, attr: ?[]const Attr) E {
return element("commit", c, attr);
}
 
filename was Deleted added: 304, removed: 99, total 205
@@ -0,0 +1,198 @@
const std = @import("std");
 
const mem = std.mem;
const Allocator = mem.Allocator;
 
const CURL = @import("curl.zig");
const Bleach = @import("bleach.zig");
const Endpoint = @import("endpoint.zig");
const Response = Endpoint.Response;
const HTML = Endpoint.HTML;
const DOM = Endpoint.DOM;
 
pub const Patch = struct {
patch: []const u8,
 
pub fn isValid(_: Patch) bool {
return true; // lol, you thought this did something :D
}
 
pub fn filesSlice(self: Patch, a: Allocator) ![][]const u8 {
const count = mem.count(u8, self.patch, "\ndiff --git a/") + 1;
if (count == 1 and mem.count(u8, self.patch, "diff --git a/") != 1) return error.PatchInvalid;
var files = try a.alloc([]const u8, count);
errdefer a.free(files);
var fidx: usize = 0;
var start: usize = mem.indexOfPos(u8, self.patch, 0, "diff --git a/") orelse {
return error.PatchInvalid;
};
var end: usize = start;
while (start < self.patch.len) {
end = mem.indexOfPos(u8, self.patch, start + 1, "\ndiff --git a/") orelse self.patch.len;
files[fidx] = self.patch[start..end];
start = end;
fidx += 1;
}
return files;
}
};
 
pub const Header = struct {
data: ?[]const u8 = null,
filename: struct {
left: ?[]const u8 = null,
right: ?[]const u8 = null,
} = .{},
changes: ?[]const u8 = null,
 
pub fn parse(self: *Header) !void {
var d = self.data orelse return error.NoData;
// Filename
const left_s = 7 + (mem.indexOf(u8, d, "\n--- a/") orelse return error.PatchInvalid);
const left_e = mem.indexOfPos(u8, d, left_s, "\n") orelse return error.PatchInvalid;
self.filename.left = d[left_s..left_e];
const right_s = 7 + (mem.indexOf(u8, d, "\n+++ b/") orelse return error.PatchInvalid);
const right_e = mem.indexOfPos(u8, d, right_s, "\n") orelse return error.PatchInvalid;
self.filename.right = d[right_s..right_e];
// Block headers
const block_s = mem.indexOf(u8, d, "\n@@ ") orelse return error.BlockHeaderMissing;
const block_e = mem.indexOf(u8, d, " @@") orelse return error.BlockHeaderMissing;
const block = d[block_s + 4 .. block_e];
var bi = mem.indexOf(u8, block, " ") orelse return error.BlockInvalid;
const left = block[0..bi];
const right = block[bi + 1 ..];
_ = left;
_ = right;
// Changes
const block_nl = mem.indexOfPos(u8, d, block_e, "\n") orelse return error.BlockHeaderInvalid;
if (d.len > block_nl) self.changes = d[block_nl + 1 ..];
}
};
 
fn parseHeader() void {}
 
fn fetch(a: Allocator, uri: []const u8) ![]u8 {
// Disabled until TLS1.2 is supported
// var client = std.http.Client{
// .allocator = a,
// };
// defer client.deinit();
 
// var request = client.fetch(a, .{
// .location = .{ .url = uri },
// });
// if (request) |*req| {
// defer req.deinit();
// std.debug.print("request code {}\n", .{req.status});
// if (req.body) |b| {
// std.debug.print("request body {s}\n", .{b});
// return a.dupe(u8, b);
// }
// } else |err| {
// std.debug.print("stdlib request failed with error {}\n", .{err});
// }
 
var curl = try CURL.curlRequest(a, uri);
if (curl.code != 200) return error.UnexpectedResponseCode;
 
if (curl.body) |b| return b;
return error.EpmtyReponse;
}
 
pub fn loadRemote(a: Allocator, uri: []const u8) !Patch {
return Patch{ .patch = try fetch(a, uri) };
}
 
pub fn patchHtml(a: Allocator, patch: []const u8) ![]HTML.Element {
var p = Patch{ .patch = patch };
var diffs = p.filesSlice(a) catch return &[0]HTML.Element{};
defer a.free(diffs);
 
var dom = DOM.new(a);
 
dom = dom.open(HTML.patch());
for (diffs) |diff| {
var h = Header{ .data = diff };
h.parse() catch continue;
const body = h.changes orelse continue;
 
dom = dom.open(HTML.diff());
dom.push(HTML.element("filename", h.filename.left orelse "File name empty", null));
dom = dom.open(HTML.element("changes", null, null));
dom.pushSlice(diffLine(a, body));
dom = dom.close();
dom = dom.close();
}
dom = dom.close();
return dom.done();
}
 
pub fn diffLine(a: Allocator, diff: []const u8) []HTML.Element {
var dom = DOM.new(a);
 
const count = std.mem.count(u8, diff, "\n");
var litr = std.mem.split(u8, diff, "\n");
for (0..count + 1) |_| {
const a_add = &HTML.Attr.class("add");
const a_del = &HTML.Attr.class("del");
const dirty = litr.next().?;
var clean = a.alloc(u8, dirty.len * 2) catch unreachable;
clean = Bleach.sanitize(dirty, clean, .{}) catch unreachable;
const attr: ?[]const HTML.Attr = if (clean.len > 0 and (clean[0] == '-' or clean[0] == '+'))
if (clean[0] == '-') a_del else a_add
else
null;
dom.dupe(HTML.span(clean, attr));
}
 
return dom.done();
}
 
test "filesSlice" {
var a = std.testing.allocator;
 
const s_patch =
\\diff --git a/src/fs.zig b/src/fs.zig
\\index 948efff..ddac25e 100644
\\--- a/src/fs.zig
\\+++ b/src/fs.zig
\\@@ -86,6 +86,7 @@ pub fn init(a: mem.Allocator, env: std.process.EnvMap) !fs {
\\ var self = fs{
\\ .alloc = a,
\\ .rc = findCoreFile(a, &env, .rc),
\\+ .history = findCoreFile(a, &env, .history),
\\ .dirs = .{
\\ .cwd = try std.fs.cwd().openIterableDir(".", .{}),
\\ },
\\diff --git a/build.zig b/build.zig
\\index d5c30e1..bbf3794 100644
\\--- a/build.zig
\\+++ b/build.zig
\\@@ -13,6 +13,7 @@ pub fn build(b: *std.Build) void {
\\ });
\\
\\ exe.linkSystemLibrary2("curl", .{ .preferred_link_mode = .Static });
\\+ exe.linkLibC();
\\
\\ addSrcTemplates(exe);
\\ b.installArtifact(exe);
\\
;
const a_patch = try a.dupe(u8, s_patch);
defer a.free(a_patch);
var p = Patch{
.patch = a_patch,
};
var files = try p.filesSlice(a);
defer a.free(files);
try std.testing.expect(files.len == 2);
var h: Header = undefined;
for (files) |f| {
h = Header{
.data = @constCast(f),
};
try h.parse();
}
try std.testing.expectEqualStrings(h.filename.left.?, "build.zig");
try std.testing.expectEqualStrings(h.filename.left.?, h.filename.right.?);
}
 
static/main.css added: 304, removed: 99, total 205
@@ -109,7 +109,7 @@ lines {
 
code {
display: inline-block;
font-family: "Ubuntu Mono", sans-serif;
font-family: "Ubuntu Mono", monospace, sans-serif;
text-align: left;
white-space: pre;
width: 100%;
@@ -221,7 +221,7 @@ readme {
 
.treelist {
padding: 0 8px 8px;
font-family: 'Ubuntu Mono', sans-serif;
font-family: "Ubuntu Mono", monospace, sans-serif;
tree, file {
border-top: 1px solid gray;
margin: auto;
@@ -328,7 +328,7 @@ textarea.paste {
min-height: 20em;
}
 
diff {
patchrequest {
border: 1px solid gray;
display: block;
border-radius: 4px 4px 0 0;
@@ -357,6 +357,44 @@ diff {
}
}
 
diff {
text-align: left;
display: block;
margin-bottom: 1em;
 
filename {
background: #333;
border-radius: 3px 3px 0 0;
border: 1px gray solid;
border-bottom: 0px;
display: flex;
justify-content: space-between;
height: 1.5em;
padding: 4px 12px;
}
changes {
font-family: "Ubuntu Mono", monospace, sans-serif;
font-size: 0.8em;
border: 1px solid gray;
display: block;
padding: 8px;
white-space: pre;
text-align: left;
background: #333;
 
.del { background: #5b0000 }
.add { background: #003e00 }
}
.buttons {
background: #333;
border: 1px gray solid;
border-top: 0px;
display: block;
height: 25px;
padding: 4px 0 4px 12px;
}
}
 
patchheader {
display: block;
width: 100%;
@@ -364,7 +402,7 @@ patchheader {
}
 
patch {
/* margin: 2rem 0 10rem; */
/* margin: 2rem 0 10rem; * /
border: 1px solid gray;
border-radius: 4px 4px 0 0;
white-space: pre;
@@ -375,31 +413,44 @@ patch {
font-family: monospace;
 
.del { background: #5b0000 }
.add { background: #003e00 }
.add { background: #003e00 } /* */
}
 
comments {
form {
display: flex;
flex-direction: column;
context {
background: #333;
border-radius: 3px 3px 0 0;
border: 1px gray solid;
border-bottom: 0px;
display: block;
height: 45px;
padding: 4px 0 4px 12px;
}
input, textarea {
display: block;
background: #333;
border: 1px solid gray;
border-radius: 2px;
font-size: 130%;
}
textarea { height: 10em }
form {
display: flex;
flex-direction: column;
gap: 1em;
border: 1px solid gray;
border-bottom-color: gray;
border-bottom: 0;
padding-bottom: 1em;
context {
font-family: 'Titillium Web', sans-serif;
background: #333;
display: block;
height: 35px;
padding: 4px 0 4px 12px;
}
input, textarea {
margin: 0 0.5em;
display: block;
background: #333;
border: 1px solid gray;
border-radius: 0;
font-size: 130%;
padding: 4px;
border-bottom: 0px solid gray;
&:last-of-type {
border-bottom: 1px solid gray;
margin-bottom: 0.5em;
}
}
button {
margin: 0 0.5em;
}
textarea { height: 10em }
}
comments {
 
}
comment {