documentation: semi-minimally-functional zed
It can load things from files now, but the cmark dependency needs to be properly integrated into the build still.
This commit is contained in:
parent
95aa6d01c6
commit
faa43a1941
5
documentation/templates/post-body.fragment.html
Normal file
5
documentation/templates/post-body.fragment.html
Normal file
@ -0,0 +1,5 @@
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
4
documentation/templates/post-nav.fragment.html
Normal file
4
documentation/templates/post-nav.fragment.html
Normal file
@ -0,0 +1,4 @@
|
||||
</nav>
|
||||
</aside>
|
||||
<main class="doc">
|
||||
<div class="doc-padding">
|
14
documentation/templates/pre-nav.fragment.html
Normal file
14
documentation/templates/pre-nav.fragment.html
Normal file
@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{[0]s}</title>
|
||||
<style>{[1]s}</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="layout">
|
||||
<aside class="sidebar">
|
||||
<!-- <div class="header">NOCLIP</div> -->
|
||||
<input id="destroyer-of-navs" class="collapse-toggle" type="checkbox">
|
||||
<label for="destroyer-of-navs" class="header collapse-label" tabindex="0">{[0]s}</label>
|
||||
<nav class="nav collapse-view">
|
@ -140,6 +140,22 @@ body .layout .doc {
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
p code {
|
||||
background: var(--sidebar-color);
|
||||
color: var(--gray-4);
|
||||
/* border-radius: 10px;*/
|
||||
}
|
||||
|
||||
body .layout .doc .doc-padding {
|
||||
padding-bottom: 80vh;
|
||||
max-width: 100%;
|
||||
@ -230,7 +246,10 @@ body .layout .doc section .example .code-markup .line {
|
||||
.code-markup .string { color: var(--green); }
|
||||
.code-markup .comment { color: var(--gray-3); }
|
||||
.code-markup .literal { color: var(--orange); }
|
||||
.code-markup .name { color: var(--red); }
|
||||
.code-markup .label { color: var(--yellow); }
|
||||
.code-markup .field-name { color: var(--red); }
|
||||
.code-markup .variable { color: var(--red); }
|
||||
.code-markup .function { color: var(--blue); }
|
||||
|
||||
/*It turns out these have to come after the directives they override.*/
|
||||
@media (max-width: 1820px) {
|
@ -1,4 +1,12 @@
|
||||
const std = @import("std");
|
||||
const noclip = @import("noclip");
|
||||
const tokenator = @import("./tokenator.zig");
|
||||
|
||||
const cmark = @cImport({
|
||||
@cInclude("cmark.h");
|
||||
@cInclude("cmark_version.h");
|
||||
@cInclude("cmark_export.h");
|
||||
});
|
||||
|
||||
const Directive = enum {
|
||||
section,
|
||||
@ -245,19 +253,39 @@ const DirectiveLine = union(Directive) {
|
||||
}
|
||||
};
|
||||
|
||||
const Body = union(enum) {
|
||||
in_line: []const u8,
|
||||
include: []const u8,
|
||||
};
|
||||
fn slugify(allocator: std.mem.Allocator, source: []const u8) ![]const u8 {
|
||||
const buf = try allocator.alloc(u8, source.len);
|
||||
for (source, 0..) |char, idx| {
|
||||
if (std.ascii.isAlphanumeric(char)) {
|
||||
buf[idx] = std.ascii.toLower(char);
|
||||
} else {
|
||||
buf[idx] = '-';
|
||||
}
|
||||
}
|
||||
|
||||
const Example = struct {
|
||||
format: ExampleFormat,
|
||||
body: Body,
|
||||
};
|
||||
return buf;
|
||||
}
|
||||
|
||||
const Section = struct {
|
||||
name: []const u8,
|
||||
id: []const u8,
|
||||
segments: []const Segment,
|
||||
|
||||
fn emit(self: Section, allocator: std.mem.Allocator, writer: anytype) !void {
|
||||
try writer.print(
|
||||
\\<section>
|
||||
\\<div id="{s}" class = "header">{s}</div>
|
||||
\\
|
||||
, .{ self.id, self.name });
|
||||
|
||||
for (self.segments) |segment| {
|
||||
switch (segment) {
|
||||
inline else => |seg| try seg.emit(allocator, writer),
|
||||
}
|
||||
}
|
||||
|
||||
try writer.writeAll("</section>");
|
||||
}
|
||||
};
|
||||
|
||||
const Segment = union(enum) {
|
||||
@ -265,9 +293,68 @@ const Segment = union(enum) {
|
||||
example: Example,
|
||||
};
|
||||
|
||||
const Example = struct {
|
||||
format: ExampleFormat,
|
||||
body: Body,
|
||||
fn emit(self: Example, allocator: std.mem.Allocator, writer: anytype) !void {
|
||||
try writer.writeAll(
|
||||
\\<div class="example">
|
||||
\\<div class="codebox">
|
||||
\\
|
||||
);
|
||||
switch (self.format) {
|
||||
.zig => switch (self.body) {
|
||||
.in_line => |buf| try tokenator.tokenize_buffer(buf, allocator, writer, false),
|
||||
.include => |fln| try tokenator.tokenize_file(fln, allocator, writer, false),
|
||||
},
|
||||
.console => switch (self.body) {
|
||||
.in_line => |buf| try writer.print(
|
||||
\\<pre class="code-markup"><code class="lang-console">{s}</code></pre>
|
||||
\\
|
||||
, .{std.mem.trim(u8, buf, " \n")}),
|
||||
.include => @panic("included console example not supported"),
|
||||
},
|
||||
}
|
||||
try writer.writeAll(
|
||||
\\</div>
|
||||
\\</div>
|
||||
\\
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const Description = struct {
|
||||
format: DescriptionFormat,
|
||||
body: Body,
|
||||
|
||||
fn emit(self: Description, allocator: std.mem.Allocator, writer: anytype) !void {
|
||||
try writer.writeAll(
|
||||
\\<div class="description">
|
||||
\\
|
||||
);
|
||||
|
||||
_ = allocator;
|
||||
switch (self.format) {
|
||||
.markdown => switch (self.body) {
|
||||
.in_line => |buf| {
|
||||
const converted = cmark.cmark_markdown_to_html(buf.ptr, buf.len, 0);
|
||||
if (converted == null) return error.OutOfMemory;
|
||||
try writer.writeAll(std.mem.sliceTo(converted, 0));
|
||||
},
|
||||
.include => |fln| {
|
||||
_ = fln;
|
||||
@panic("include description not implemented");
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
try writer.writeAll("</div>\n");
|
||||
}
|
||||
};
|
||||
|
||||
const Body = union(enum) {
|
||||
in_line: []const u8,
|
||||
include: []const u8,
|
||||
};
|
||||
|
||||
const Document = []const Section;
|
||||
@ -290,20 +377,26 @@ const ParserState = enum {
|
||||
any_directive,
|
||||
};
|
||||
|
||||
fn slice_to_next_directive(lines: *std.mem.TokenIterator(u8)) []const u8 {
|
||||
fn slice_to_next_directive(lines: *std.mem.TokenIterator(u8)) ![]const u8 {
|
||||
const start = lines.index + 1;
|
||||
|
||||
// the directive is the last line in the file
|
||||
if (start >= lines.buffer.len) return "";
|
||||
|
||||
while (lines.peek()) |line| : (_ = lines.next()) {
|
||||
// this approach is likely too sloppy
|
||||
if (DirectiveLine.from_line(line)) |_| {
|
||||
return lines.buffer[start..lines.index];
|
||||
} else |_| {}
|
||||
} else |err| switch (err) {
|
||||
error.ExpectedDirectivePrefix => {},
|
||||
else => return err,
|
||||
}
|
||||
}
|
||||
|
||||
// we hit EOF
|
||||
return lines.buffer[start..lines.buffer.len];
|
||||
}
|
||||
|
||||
pub fn parse(allocator: std.mem.Allocator, input: []const u8) !Document {
|
||||
pub fn parse(allocator: std.mem.Allocator, input: []const u8, directory: []const u8) !Document {
|
||||
var lines = std.mem.tokenize(u8, input, "\n");
|
||||
|
||||
var doc_builder = std.ArrayList(Section).init(allocator);
|
||||
@ -311,7 +404,11 @@ pub fn parse(allocator: std.mem.Allocator, input: []const u8) !Document {
|
||||
|
||||
var state: ParserState = .section_or_include;
|
||||
|
||||
var current_section: Section = undefined;
|
||||
var current_section: Section = .{
|
||||
.name = undefined,
|
||||
.id = undefined,
|
||||
.segments = undefined,
|
||||
};
|
||||
|
||||
while (lines.next()) |line| {
|
||||
const dline = try DirectiveLine.from_line(line);
|
||||
@ -319,11 +416,12 @@ pub fn parse(allocator: std.mem.Allocator, input: []const u8) !Document {
|
||||
.section_or_include => switch (dline) {
|
||||
.section => |sline| {
|
||||
current_section.name = sline.name;
|
||||
current_section.id = try slugify(allocator, sline.name);
|
||||
state = .any_directive;
|
||||
},
|
||||
.include => |iline| {
|
||||
// read the file at iline.path
|
||||
const doc = try parse(allocator, iline.path);
|
||||
const doc = try parse(allocator, iline.path, try std.fs.path.join(allocator, &[_][]const u8{directory}));
|
||||
defer allocator.free(doc);
|
||||
try doc_builder.appendSlice(doc);
|
||||
},
|
||||
@ -334,9 +432,10 @@ pub fn parse(allocator: std.mem.Allocator, input: []const u8) !Document {
|
||||
current_section.segments = try section_builder.toOwnedSlice();
|
||||
try doc_builder.append(current_section);
|
||||
current_section.name = sline.name;
|
||||
current_section.id = try slugify(allocator, sline.name);
|
||||
},
|
||||
.include => |iline| {
|
||||
const doc = try parse(allocator, iline.path);
|
||||
const doc = try parse(allocator, iline.path, try std.fs.path.join(allocator, &[_][]const u8{directory}));
|
||||
defer allocator.free(doc);
|
||||
try doc_builder.appendSlice(doc);
|
||||
state = .section_or_include;
|
||||
@ -345,18 +444,18 @@ pub fn parse(allocator: std.mem.Allocator, input: []const u8) !Document {
|
||||
try section_builder.append(.{ .example = .{
|
||||
.format = exline.format,
|
||||
.body = if (exline.include) |incl|
|
||||
.{ .include = incl }
|
||||
.{ .include = try std.fs.path.join(allocator, &[_][]const u8{ directory, incl }) }
|
||||
else
|
||||
.{ .in_line = slice_to_next_directive(&lines) },
|
||||
.{ .in_line = try slice_to_next_directive(&lines) },
|
||||
} });
|
||||
},
|
||||
.description => |desline| {
|
||||
try section_builder.append(.{ .description = .{
|
||||
.format = desline.format,
|
||||
.body = if (desline.include) |incl|
|
||||
.{ .include = incl }
|
||||
.{ .include = try std.fs.path.join(allocator, &[_][]const u8{ directory, incl }) }
|
||||
else
|
||||
.{ .in_line = slice_to_next_directive(&lines) },
|
||||
.{ .in_line = try slice_to_next_directive(&lines) },
|
||||
} });
|
||||
},
|
||||
},
|
||||
@ -368,6 +467,14 @@ pub fn parse(allocator: std.mem.Allocator, input: []const u8) !Document {
|
||||
return doc_builder.toOwnedSlice();
|
||||
}
|
||||
|
||||
pub fn free_doc(doc: Document, allocator: std.mem.Allocator) void {
|
||||
for (doc) |section| {
|
||||
allocator.free(section.id);
|
||||
allocator.free(section.segments);
|
||||
}
|
||||
allocator.free(doc);
|
||||
}
|
||||
|
||||
test "parser" {
|
||||
const doc = try parse(
|
||||
std.testing.allocator,
|
||||
@ -385,6 +492,8 @@ test "parser" {
|
||||
,
|
||||
);
|
||||
|
||||
defer free_doc(doc, std.testing.allocator);
|
||||
|
||||
for (doc) |section| {
|
||||
std.debug.print("section: {s}\n", .{section.name});
|
||||
for (section.segments) |seg| {
|
||||
@ -400,7 +509,103 @@ test "parser" {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (doc) |section| std.testing.allocator.free(section.segments);
|
||||
std.testing.allocator.free(doc);
|
||||
}
|
||||
|
||||
const pre_nav = @embedFile("./templates/pre-nav.fragment.html");
|
||||
const style = @embedFile("./templates/style.css");
|
||||
const post_nav = @embedFile("./templates/post-nav.fragment.html");
|
||||
const post_body = @embedFile("./templates/post-body.fragment.html");
|
||||
const nav_item_template =
|
||||
\\<a href="#{s}"><div class="item">{s}</div></a>
|
||||
\\
|
||||
;
|
||||
|
||||
const dezed_cmd = cmd: {
|
||||
var cmd = noclip.CommandBuilder(*ZedCtx){
|
||||
.description =
|
||||
\\Convert a ZED file into HTML
|
||||
\\
|
||||
,
|
||||
};
|
||||
cmd.string_option(.{
|
||||
.name = "output",
|
||||
.short_tag = "-o",
|
||||
.long_tag = "--output",
|
||||
.description = "write output to file (- to write to stdout). If omitted, output will be written to <input>.html",
|
||||
});
|
||||
cmd.string_argument(.{ .name = "input" });
|
||||
break :cmd cmd;
|
||||
};
|
||||
|
||||
const ZedCtx = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
};
|
||||
|
||||
fn dezed_cli(context: *ZedCtx, parameters: dezed_cmd.Output()) !void {
|
||||
const outname = parameters.output orelse if (std.mem.eql(u8, parameters.input, "-"))
|
||||
"-"
|
||||
else
|
||||
try std.mem.join(
|
||||
context.allocator,
|
||||
".",
|
||||
&[_][]const u8{ parameters.input, "html" },
|
||||
);
|
||||
|
||||
// this theoretically leaks the file handle, though we should be able to extract it
|
||||
// from the reader/writer
|
||||
const input = blk: {
|
||||
if (std.mem.eql(u8, parameters.input, "-")) {
|
||||
break :blk std.io.getStdIn().reader();
|
||||
} else {
|
||||
break :blk (try std.fs.cwd().openFile(parameters.input, .{ .mode = .read_only })).reader();
|
||||
}
|
||||
};
|
||||
|
||||
const output = blk: {
|
||||
if (std.mem.eql(u8, outname, "-")) {
|
||||
break :blk std.io.getStdOut().writer();
|
||||
} else {
|
||||
break :blk (try std.fs.cwd().createFile(outname, .{})).writer();
|
||||
}
|
||||
};
|
||||
|
||||
const cwd = try std.process.getCwdAlloc(context.allocator);
|
||||
const filedir = try std.fs.path.join(context.allocator, &[_][]const u8{ cwd, std.fs.path.dirname(outname) orelse return error.OutOfMemory });
|
||||
|
||||
const data = try input.readAllAlloc(context.allocator, 1_000_000);
|
||||
const doc = try parse(context.allocator, data, filedir);
|
||||
defer free_doc(doc, context.allocator);
|
||||
|
||||
try output.print(pre_nav, .{ "NOCLIP", style });
|
||||
|
||||
for (doc) |section| {
|
||||
try output.print(nav_item_template, .{ section.id, section.name });
|
||||
}
|
||||
|
||||
try output.writeAll(post_nav);
|
||||
|
||||
for (doc) |section| {
|
||||
try section.emit(context.allocator, output);
|
||||
}
|
||||
|
||||
try output.writeAll(post_body);
|
||||
}
|
||||
|
||||
pub fn cli() !u8 {
|
||||
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||
defer _ = gpa.deinit();
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(gpa.allocator());
|
||||
defer arena.deinit();
|
||||
|
||||
var ctx: ZedCtx = .{ .allocator = arena.allocator() };
|
||||
|
||||
var cli_parser = dezed_cmd.create_parser(dezed_cli, ctx.allocator);
|
||||
try cli_parser.execute(&ctx);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
pub fn main() !u8 {
|
||||
return try cli();
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user