commit
55a1697e71
16 changed files with 838 additions and 0 deletions
79
src/cgi.zig
Executable file
79
src/cgi.zig
Executable file
|
|
@ -0,0 +1,79 @@
|
|||
const std = @import("std");
|
||||
const Io = std.Io;
|
||||
const File = Io.File;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||
const ArrayList = std.ArrayList;
|
||||
const StringArrayHashMap = std.StringArrayHashMapUnmanaged;
|
||||
|
||||
const HTMLua = @import("htmlua");
|
||||
|
||||
const Config = struct {
|
||||
component_path: []const u8,
|
||||
template_path: []const u8,
|
||||
};
|
||||
|
||||
var component_dir: Io.Dir = undefined;
|
||||
|
||||
pub fn main(init: std.process.Init) !void {
|
||||
const io = init.io;
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(init.gpa);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
|
||||
var args = init.minimal.args.iterate();
|
||||
|
||||
const cwd = std.Io.Dir.cwd();
|
||||
|
||||
if (!args.skip()) unreachable;
|
||||
const config_file_path = args.next().?;
|
||||
const config_buf = try cwd.readFileAllocOptions(io, config_file_path, alloc, .unlimited, std.mem.Alignment.@"1", '\x00');
|
||||
const config = try std.zon.parse.fromSliceAlloc(
|
||||
Config,
|
||||
alloc,
|
||||
config_buf,
|
||||
null,
|
||||
.{},
|
||||
);
|
||||
|
||||
component_dir = try cwd.openDir(io, config.component_path, .{});
|
||||
const template_dir = try cwd.openDir(io, config.template_path, .{});
|
||||
|
||||
var state: HTMLua.Parser = .{
|
||||
.tags = &[_]HTMLua.ParserTag{
|
||||
HTMLua.tags.Include,
|
||||
HTMLua.tags.Export,
|
||||
HTMLua.tags.Use,
|
||||
HTMLua.tags.Syntax,
|
||||
},
|
||||
.tag_stack = try ArrayList([]const u8).initCapacity(alloc, 256),
|
||||
.exported_parts = try StringArrayHashMap([]const u8).init(alloc, &[0][]const u8{}, &[0][]const u8{}),
|
||||
.component_resolver = struct {
|
||||
pub fn resolve(arena_inner: *ArenaAllocator, io_inner: Io, path: []const u8) anyerror!*Io.Reader {
|
||||
const read_buff = try arena_inner.allocator().alloc(u8, 16384);
|
||||
const clean_path = try HTMLua.utils.sanitizePath(arena_inner.allocator(), path);
|
||||
var file = try component_dir.openFile(io_inner, clean_path, .{ .mode = .read_only });
|
||||
const reader = try arena_inner.allocator().create(File.Reader);
|
||||
reader.* = file.reader(io_inner, read_buff);
|
||||
return &reader.interface;
|
||||
}
|
||||
}.resolve,
|
||||
};
|
||||
|
||||
const request_path = init.environ_map.get("PATH_INFO").?;
|
||||
const clean_request_path = try HTMLua.utils.sanitizePath(alloc, request_path);
|
||||
const file = try template_dir.openFile(io, clean_request_path, .{ .mode = .read_only });
|
||||
defer file.close(io);
|
||||
|
||||
const in_buf = try alloc.alloc(u8, 16384);
|
||||
var in = file.reader(io, in_buf);
|
||||
|
||||
const out_buf = try alloc.alloc(u8, 16384);
|
||||
var out = File.stdout().writer(io, out_buf);
|
||||
|
||||
try out.interface.writeAll("Content-type: text/html\n");
|
||||
try HTMLua.parseTemplate(&arena, io, &state, &in.interface, &out.interface);
|
||||
|
||||
try out.flush();
|
||||
}
|
||||
59
src/main.zig
Executable file
59
src/main.zig
Executable file
|
|
@ -0,0 +1,59 @@
|
|||
const std = @import("std");
|
||||
const Io = std.Io;
|
||||
const File = Io.File;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||
const ArrayList = std.ArrayList;
|
||||
const StringArrayHashMap = std.StringArrayHashMapUnmanaged;
|
||||
|
||||
const HTMLua = @import("root.zig");
|
||||
|
||||
pub fn main(init: std.process.Init) !void {
|
||||
const io = init.io;
|
||||
|
||||
var arena = std.heap.ArenaAllocator.init(init.gpa);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
|
||||
var state: HTMLua.Parser = .{
|
||||
.component_resolver = &resolver,
|
||||
.tags = &[_]HTMLua.ParserTag{
|
||||
HTMLua.tags.Include,
|
||||
HTMLua.tags.Export,
|
||||
HTMLua.tags.Use,
|
||||
},
|
||||
.tag_stack = try ArrayList([]const u8).initCapacity(alloc, 256),
|
||||
.exported_parts = try StringArrayHashMap([]const u8).init(alloc, &[0][]const u8{}, &[0][]const u8{}),
|
||||
};
|
||||
|
||||
// I dont like this but this is temporary so its ok
|
||||
const args = try init.minimal.args.toSlice(alloc);
|
||||
const file_path = if (args.len != 1) args[1] else null;
|
||||
const file = if (file_path) |fp|
|
||||
try Io.Dir.cwd().openFile(io, fp, .{ .mode = .read_only })
|
||||
else
|
||||
null;
|
||||
defer if (file) |f| f.close(io);
|
||||
|
||||
const in_buf = try alloc.alloc(u8, 1024);
|
||||
var in =
|
||||
if (file) |f| f.reader(io, in_buf) else File.stdin().reader(io, in_buf);
|
||||
|
||||
const out_buf = try alloc.alloc(u8, 1024);
|
||||
var out = File.stdout().writer(io, out_buf);
|
||||
|
||||
try HTMLua.parseTemplate(&arena, io, &state, &in.interface, &out.interface);
|
||||
|
||||
try out.flush();
|
||||
}
|
||||
|
||||
// https://ziggit.dev/t/zig-0-15-1-reader-writer-dont-make-copies-of-fieldparentptr-based-interfaces/11719
|
||||
fn resolver(arena: *ArenaAllocator, io: Io, path: []const u8) anyerror!*Io.Reader {
|
||||
const read_buff = try arena.allocator().alloc(u8, 1024);
|
||||
const cwd = std.Io.Dir.cwd();
|
||||
|
||||
var file = try cwd.openFile(io, path, .{ .mode = .read_only });
|
||||
const reader = try arena.allocator().create(Io.File.Reader);
|
||||
reader.* = file.reader(io, read_buff);
|
||||
return &reader.interface;
|
||||
}
|
||||
76
src/root.zig
Executable file
76
src/root.zig
Executable file
|
|
@ -0,0 +1,76 @@
|
|||
const std = @import("std");
|
||||
const Io = std.Io;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||
const SplitIterator = std.mem.SplitIterator;
|
||||
const DelimiterType = std.mem.DelimiterType;
|
||||
const ArrayList = std.ArrayList;
|
||||
const StringArrayHashMap = std.StringArrayHashMapUnmanaged;
|
||||
|
||||
pub const tags = @import("tags.zig");
|
||||
pub const utils = @import("utils.zig");
|
||||
|
||||
pub const ParserFn =
|
||||
fn (
|
||||
arena: *ArenaAllocator,
|
||||
io: Io,
|
||||
state: *Parser,
|
||||
reader: *Io.Reader,
|
||||
writer: *Io.Writer,
|
||||
attrs: *SplitIterator(u8, DelimiterType.scalar),
|
||||
) anyerror!void;
|
||||
|
||||
pub const ParserTag = struct {
|
||||
name: []const u8,
|
||||
parse_fn: *const ParserFn,
|
||||
};
|
||||
|
||||
pub const Parser = struct {
|
||||
component_resolver: *const fn (alloc: *ArenaAllocator, io: Io, path: []const u8) anyerror!*Io.Reader,
|
||||
exported_parts: StringArrayHashMap([]const u8),
|
||||
tags: []const ParserTag,
|
||||
tag_stack: ArrayList([]const u8),
|
||||
};
|
||||
|
||||
pub fn parseTemplate(arena: *ArenaAllocator, io: Io, state: *Parser, reader: *Io.Reader, writer: *Io.Writer) !void {
|
||||
outer: while (true) {
|
||||
_ = reader.streamDelimiter(writer, '<') catch |err| {
|
||||
if (err == Io.Reader.Error.EndOfStream) break else return err;
|
||||
};
|
||||
// consume '<'
|
||||
_ = try reader.takeByte();
|
||||
if (try reader.peekByte() == '/') {
|
||||
// consume '/'
|
||||
_ = try reader.takeByte();
|
||||
const last_tag = state.tag_stack.getLastOrNull() orelse "";
|
||||
// if reached closing tag, send execution back up the stack
|
||||
if (std.mem.eql(u8, last_tag, try reader.peekDelimiterExclusive('>'))) {
|
||||
// consume closing tag
|
||||
_ = try reader.takeDelimiterInclusive('>');
|
||||
return;
|
||||
} else {
|
||||
try writer.writeAll("</");
|
||||
continue :outer;
|
||||
}
|
||||
}
|
||||
const inside = reader.takeDelimiterExclusive('>') catch |err| {
|
||||
if (err == error.StreamTooLong) {
|
||||
try writer.writeByte('<');
|
||||
break :outer;
|
||||
} else return err;
|
||||
};
|
||||
var it = std.mem.splitScalar(u8, inside, ' ');
|
||||
const tag_name = it.first();
|
||||
for (state.tags) |tag| {
|
||||
if (std.mem.eql(u8, tag.name, tag_name) == true) {
|
||||
// consume '>'
|
||||
_ = try reader.takeByte();
|
||||
try tag.parse_fn(arena, io, state, reader, writer, &it);
|
||||
continue :outer;
|
||||
}
|
||||
}
|
||||
|
||||
try writer.writeByte('<');
|
||||
try writer.writeAll(inside);
|
||||
}
|
||||
}
|
||||
4
src/tags.zig
Executable file
4
src/tags.zig
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
pub const Include = @import("tags/Include.zig").Include;
|
||||
pub const Export = @import("tags/Export.zig").Export;
|
||||
pub const Use = @import("tags/Use.zig").Use;
|
||||
pub const Syntax = @import("tags/Syntax.zig").Syntax;
|
||||
52
src/tags/Export.zig
Executable file
52
src/tags/Export.zig
Executable file
|
|
@ -0,0 +1,52 @@
|
|||
const std = @import("std");
|
||||
const Io = std.Io;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||
const SplitIterator = std.mem.SplitIterator;
|
||||
const DelimiterType = std.mem.DelimiterType;
|
||||
|
||||
const parser = @import("../root.zig");
|
||||
const Parser = parser.Parser;
|
||||
|
||||
pub const ExportTagError = error{
|
||||
NoPartAttribute,
|
||||
};
|
||||
|
||||
pub const Export: parser.ParserTag = .{
|
||||
.name = "export",
|
||||
.parse_fn = &parse,
|
||||
};
|
||||
|
||||
fn parse(
|
||||
arena: *ArenaAllocator,
|
||||
io: Io,
|
||||
state: *Parser,
|
||||
reader: *Io.Reader,
|
||||
writer: *Io.Writer,
|
||||
attrs: *SplitIterator(u8, DelimiterType.scalar),
|
||||
) anyerror!void {
|
||||
_ = writer;
|
||||
|
||||
const raw_part_attr: []const u8 = while (attrs.next()) |attr| {
|
||||
var it = std.mem.splitScalar(u8, attr, '=');
|
||||
if (std.mem.eql(u8, it.first(), "part")) {
|
||||
break it.next() orelse return ExportTagError.NoPartAttribute;
|
||||
}
|
||||
} else return ExportTagError.NoPartAttribute;
|
||||
|
||||
const part = try arena.allocator().dupe(u8, raw_part_attr);
|
||||
|
||||
var part_buffer = try Io.Writer.Allocating.initCapacity(arena.allocator(), 1024);
|
||||
|
||||
try state.tag_stack.append(arena.allocator(), "export");
|
||||
try parser.parseTemplate(
|
||||
arena,
|
||||
io,
|
||||
state,
|
||||
reader,
|
||||
&part_buffer.writer,
|
||||
);
|
||||
_ = state.tag_stack.pop();
|
||||
|
||||
try state.exported_parts.put(arena.allocator(), part, try part_buffer.toOwnedSlice());
|
||||
}
|
||||
58
src/tags/Include.zig
Executable file
58
src/tags/Include.zig
Executable file
|
|
@ -0,0 +1,58 @@
|
|||
const std = @import("std");
|
||||
const Io = std.Io;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||
const SplitIterator = std.mem.SplitIterator;
|
||||
const DelimiterType = std.mem.DelimiterType;
|
||||
|
||||
const parser = @import("../root.zig");
|
||||
const Parser = parser.Parser;
|
||||
|
||||
pub const IncludeTagError = error{
|
||||
NoPathAttribute,
|
||||
PathOpenFailure,
|
||||
};
|
||||
|
||||
pub const Include: parser.ParserTag = .{
|
||||
.name = "include",
|
||||
.parse_fn = &parse,
|
||||
};
|
||||
|
||||
fn parse(
|
||||
arena: *ArenaAllocator,
|
||||
io: Io,
|
||||
state: *Parser,
|
||||
reader: *Io.Reader,
|
||||
writer: *Io.Writer,
|
||||
attrs: *SplitIterator(u8, DelimiterType.scalar),
|
||||
) anyerror!void {
|
||||
const raw_path_attr: []const u8 = while (attrs.next()) |attr| {
|
||||
var it = std.mem.splitScalar(u8, attr, '=');
|
||||
if (std.mem.eql(u8, it.first(), "path")) {
|
||||
const raw_path = it.next() orelse return IncludeTagError.NoPathAttribute;
|
||||
if (raw_path.len == 0) return IncludeTagError.NoPathAttribute;
|
||||
if ((raw_path[0] == '"' and raw_path[raw_path.len - 1] == '"') or
|
||||
(raw_path[0] == '\'' and raw_path[raw_path.len - 1] == '\''))
|
||||
{
|
||||
if (raw_path.len <= 2) return IncludeTagError.NoPathAttribute;
|
||||
break raw_path[1 .. raw_path.len - 1];
|
||||
} else {
|
||||
break raw_path;
|
||||
}
|
||||
}
|
||||
} else return IncludeTagError.NoPathAttribute;
|
||||
|
||||
const path = try arena.allocator().dupe(u8, raw_path_attr);
|
||||
|
||||
// parse through to gather <export> tags
|
||||
try state.tag_stack.append(arena.allocator(), "include");
|
||||
try parser.parseTemplate(arena, io, state, reader, writer);
|
||||
_ = state.tag_stack.pop();
|
||||
|
||||
const included_reader = state.component_resolver(arena, io, path) catch {
|
||||
std.log.err("Include Tag: failed to open file {s}", .{path});
|
||||
return IncludeTagError.PathOpenFailure;
|
||||
};
|
||||
|
||||
try parser.parseTemplate(arena, io, state, included_reader, writer);
|
||||
}
|
||||
169
src/tags/Syntax.zig
Executable file
169
src/tags/Syntax.zig
Executable file
|
|
@ -0,0 +1,169 @@
|
|||
const std = @import("std");
|
||||
const Io = std.Io;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||
const SplitIterator = std.mem.SplitIterator;
|
||||
const DelimiterType = std.mem.DelimiterType;
|
||||
|
||||
const ezts = @import("easy-zig-tree-sitter");
|
||||
const ts = @import("tree-sitter");
|
||||
|
||||
const parser = @import("../root.zig");
|
||||
const Parser = parser.Parser;
|
||||
|
||||
pub const SyntaxTagError = error{InvalidLanguage};
|
||||
pub const Syntax: parser.ParserTag = .{
|
||||
.name = "syntax",
|
||||
.parse_fn = &parse,
|
||||
};
|
||||
|
||||
fn parse(
|
||||
arena: *ArenaAllocator,
|
||||
io: Io,
|
||||
state: *Parser,
|
||||
reader: *Io.Reader,
|
||||
writer: *Io.Writer,
|
||||
attrs: *SplitIterator(u8, DelimiterType.scalar),
|
||||
) anyerror!void {
|
||||
_ = state;
|
||||
_ = io;
|
||||
|
||||
const raw_lang_attr: []const u8 = while (attrs.next()) |attr| {
|
||||
var it = std.mem.splitScalar(u8, attr, '=');
|
||||
if (std.mem.eql(u8, it.first(), "lang")) {
|
||||
break it.next() orelse "";
|
||||
}
|
||||
} else "";
|
||||
|
||||
const lang_key = try arena.allocator().dupe(u8, raw_lang_attr);
|
||||
|
||||
_ = try reader.takeDelimiterInclusive('\n');
|
||||
const first_line = try reader.peekDelimiterInclusive('\n');
|
||||
const whitespace_chr = for (first_line, 0..) |chr, idx| {
|
||||
if (!(chr == ' ' or chr == '\t')) break idx;
|
||||
} else 0;
|
||||
|
||||
var code = try std.ArrayList(u8).initCapacity(arena.allocator(), 1024);
|
||||
|
||||
while (true) {
|
||||
const line = try reader.takeDelimiterInclusive('\n');
|
||||
if (std.mem.indexOf(u8, line, "</syntax>")) |idx| {
|
||||
try code.appendSlice(arena.allocator(), line[0..idx]);
|
||||
// TODO: tags at the end will break the parser
|
||||
break;
|
||||
}
|
||||
const whitespace_lem = @min(line.len, whitespace_chr);
|
||||
|
||||
if (std.mem.countScalar(u8, line[0..whitespace_lem], ' ') + std.mem.countScalar(u8, line[0..whitespace_lem], '\t') < whitespace_lem) {
|
||||
try code.appendSlice(arena.allocator(), line);
|
||||
} else {
|
||||
try code.appendSlice(arena.allocator(), line[whitespace_lem..line.len]);
|
||||
}
|
||||
}
|
||||
|
||||
// Alignment issues?
|
||||
// ts.setAllocator(arena.allocator());
|
||||
// TODO: TS Thread Mutex
|
||||
|
||||
// TODO: null handeling
|
||||
var hlp = HLParser.create(lang_key, code.items) orelse {
|
||||
std.log.warn("Failed to make Parser for {s}", .{lang_key});
|
||||
return;
|
||||
};
|
||||
defer hlp.destroy();
|
||||
var next_hl = hlp.nextHL();
|
||||
|
||||
var end_stack = try std.ArrayList(u32).initCapacity(arena.allocator(), 1024);
|
||||
var code_idx: u32 = 0;
|
||||
|
||||
try writer.writeAll("<pre class=\"hl\"><code>");
|
||||
|
||||
while (code_idx != code.items.len) {
|
||||
var next_idx: u32 = @intCast(code.items.len);
|
||||
if (end_stack.getLastOrNull()) |last| {
|
||||
if (last == code_idx) {
|
||||
try writer.writeAll("</span>");
|
||||
_ = end_stack.pop();
|
||||
continue;
|
||||
} else if (last < next_idx) {
|
||||
next_idx = last;
|
||||
}
|
||||
}
|
||||
if (next_hl) |hl| {
|
||||
if (hl.start == code_idx) {
|
||||
try writer.writeAll("<span class=\"hl");
|
||||
// no '.' in css class names
|
||||
var it = std.mem.splitScalar(u8, hl.name, '.');
|
||||
while (it.next()) |n| {
|
||||
try writer.writeByte('-');
|
||||
try writer.writeAll(n);
|
||||
}
|
||||
try writer.writeAll("\">");
|
||||
try end_stack.append(arena.allocator(), hl.end);
|
||||
next_hl = hlp.nextHL();
|
||||
continue;
|
||||
} else if (hl.start < next_idx) {
|
||||
next_idx = hl.start;
|
||||
}
|
||||
}
|
||||
try writer.writeAll(code.items[code_idx..next_idx]);
|
||||
code_idx = next_idx;
|
||||
}
|
||||
|
||||
try writer.writeAll("</code></pre>");
|
||||
}
|
||||
|
||||
const HLGroup = struct {
|
||||
name: []const u8,
|
||||
start: u32,
|
||||
end: u32,
|
||||
};
|
||||
|
||||
const HLParser = struct {
|
||||
lang: *ts.Language,
|
||||
tree: *ts.Tree,
|
||||
query: *ts.Query,
|
||||
cursor: *ts.QueryCursor,
|
||||
fn create(lang_key: []const u8, code: []const u8) ?HLParser {
|
||||
const lang = ezts.getLang(lang_key) orelse return null;
|
||||
const query_string = ezts.getQuery(lang_key, "highlights") orelse return null;
|
||||
|
||||
const ps = ts.Parser.create();
|
||||
|
||||
ps.setLanguage(lang) catch return null;
|
||||
const tree = ps.parseString(code, null) orelse return null;
|
||||
|
||||
var error_offset: u32 = 0;
|
||||
const query = ts.Query.create(lang, query_string, &error_offset) catch return null;
|
||||
|
||||
const cursor = ts.QueryCursor.create();
|
||||
cursor.exec(query, tree.rootNode());
|
||||
|
||||
return .{
|
||||
.lang = lang,
|
||||
.tree = tree,
|
||||
.query = query,
|
||||
.cursor = cursor,
|
||||
};
|
||||
}
|
||||
|
||||
fn destroy(self: *HLParser) void {
|
||||
self.cursor.destroy();
|
||||
self.query.destroy();
|
||||
self.tree.destroy();
|
||||
self.lang.destroy();
|
||||
}
|
||||
|
||||
fn nextHL(self: *HLParser) ?HLGroup {
|
||||
if (self.cursor.nextCapture()) |data| {
|
||||
const idx, const match = data;
|
||||
const c = match.captures[idx];
|
||||
|
||||
return .{
|
||||
.start = c.node.startByte(),
|
||||
.end = c.node.endByte(),
|
||||
.name = self.query.captureNameForId(c.index) orelse return null,
|
||||
};
|
||||
} else return null;
|
||||
}
|
||||
};
|
||||
48
src/tags/Use.zig
Executable file
48
src/tags/Use.zig
Executable file
|
|
@ -0,0 +1,48 @@
|
|||
const std = @import("std");
|
||||
const Io = std.Io;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||
const SplitIterator = std.mem.SplitIterator;
|
||||
const DelimiterType = std.mem.DelimiterType;
|
||||
|
||||
const parser = @import("../root.zig");
|
||||
const Parser = parser.Parser;
|
||||
|
||||
pub const UseTagError = error{ NoPartAttribute, NoPartExported };
|
||||
|
||||
pub const Use: parser.ParserTag = .{
|
||||
.name = "use",
|
||||
.parse_fn = &parse,
|
||||
};
|
||||
|
||||
fn parse(
|
||||
arena: *ArenaAllocator,
|
||||
io: Io,
|
||||
state: *Parser,
|
||||
reader: *Io.Reader,
|
||||
writer: *Io.Writer,
|
||||
attrs: *SplitIterator(u8, DelimiterType.scalar),
|
||||
) anyerror!void {
|
||||
_ = io;
|
||||
|
||||
const raw_part_attr: []const u8 = while (attrs.next()) |attr| {
|
||||
var it = std.mem.splitScalar(u8, attr, '=');
|
||||
if (std.mem.eql(u8, it.first(), "part")) {
|
||||
break it.next() orelse return UseTagError.NoPartAttribute;
|
||||
}
|
||||
} else return UseTagError.NoPartAttribute;
|
||||
|
||||
const part = try arena.allocator().dupe(u8, raw_part_attr);
|
||||
|
||||
const part_text = state.exported_parts.get(part) orelse return UseTagError.NoPartExported;
|
||||
|
||||
var part_reader = Io.Reader.fixed(part_text);
|
||||
|
||||
_ = try part_reader.streamRemaining(writer);
|
||||
|
||||
while (true) {
|
||||
_ = try reader.discardDelimiterInclusive('<');
|
||||
const inside = try reader.takeDelimiterInclusive('>');
|
||||
if (std.mem.eql(u8, "/use>", inside)) return;
|
||||
}
|
||||
}
|
||||
23
src/utils.zig
Executable file
23
src/utils.zig
Executable file
|
|
@ -0,0 +1,23 @@
|
|||
const std = @import("std");
|
||||
const Io = std.Io;
|
||||
const File = Io.File;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||
const ArrayList = std.ArrayList;
|
||||
const StringArrayHashMap = std.StringArrayHashMapUnmanaged;
|
||||
|
||||
pub fn sanitizePath(alloc: Allocator, path: []const u8) ![]const u8 {
|
||||
const clean = try alloc.alloc(u8, path.len);
|
||||
var list = ArrayList(u8).initBuffer(clean);
|
||||
var pit = std.mem.splitScalar(u8, path, '/');
|
||||
while (pit.next()) |part| {
|
||||
if (part.len == 0) continue;
|
||||
if (std.mem.eql(u8, part, "..")) continue;
|
||||
// This should never actually allocate
|
||||
try list.appendSlice(alloc, part);
|
||||
try list.append(alloc, '/');
|
||||
}
|
||||
const new_len = list.items.len - 1;
|
||||
_ = alloc.resize(clean, new_len);
|
||||
return clean[0..new_len];
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue