aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build.zig22
-rw-r--r--flake.nix2
-rw-r--r--src/api/root.zig2
-rw-r--r--src/api/sketch.zig29
-rw-r--r--src/frontend/File.zig78
-rw-r--r--src/frontend/Minifier.zig168
-rw-r--r--src/frontend/root.zig3
-rw-r--r--src/frontend/web/hello-world/index.html3
-rw-r--r--src/frontend/web/index.html4
-rw-r--r--src/frontend/z-component.js3
-rw-r--r--src/http/Server.zig20
-rw-r--r--src/http/Url.zig39
-rw-r--r--src/http/root.zig3
-rw-r--r--src/main.zig4
-rw-r--r--src/root.zig6
15 files changed, 315 insertions, 71 deletions
diff --git a/build.zig b/build.zig
index 81eadfb..db5a525 100644
--- a/build.zig
+++ b/build.zig
@@ -32,6 +32,8 @@ pub fn build(b: *std.Build) void {
.target = target,
});
+ api_mod.addImport("http", http_mod);
+
const exe = b.addExecutable(.{
.name = "memora",
.root_module = b.createModule(.{
@@ -43,7 +45,7 @@ pub fn build(b: *std.Build) void {
.{ .name = "api", .module = api_mod },
.{ .name = "frontend", .module = frontend_mod },
.{ .name = "http", .module = http_mod },
- },
+ }
}),
});
@@ -51,9 +53,9 @@ pub fn build(b: *std.Build) void {
const run_step = b.step("run", "Run the app");
+
const run_cmd = b.addRunArtifact(exe);
run_step.dependOn(&run_cmd.step);
-
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
@@ -67,6 +69,7 @@ pub fn build(b: *std.Build) void {
const db_test_step = b.step("test-db", "Run database tests");
db_test_step.dependOn(&run_db_tests.step);
+
const api_tests = b.addTest(.{
.root_module = api_mod,
});
@@ -74,6 +77,7 @@ pub fn build(b: *std.Build) void {
const api_test_step = b.step("test-api", "Run api tests");
api_test_step.dependOn(&run_api_tests.step);
+
const frontend_tests = b.addTest(.{
.root_module = frontend_mod,
});
@@ -81,6 +85,7 @@ pub fn build(b: *std.Build) void {
const frontend_test_step = b.step("test-frontend", "Run frontend tests");
frontend_test_step.dependOn(&run_frontend_tests.step);
+
const http_tests = b.addTest(.{
.root_module = http_mod,
});
@@ -88,11 +93,13 @@ pub fn build(b: *std.Build) void {
const http_test_step = b.step("test-http", "Run http tests");
http_test_step.dependOn(&run_http_tests.step);
+
const exe_tests = b.addTest(.{
.root_module = exe.root_module,
});
const run_exe_tests = b.addRunArtifact(exe_tests);
+
const test_step = b.step("test", "Run all tests");
test_step.dependOn(&run_db_tests.step);
test_step.dependOn(&run_api_tests.step);
@@ -100,16 +107,27 @@ pub fn build(b: *std.Build) void {
test_step.dependOn(&run_http_tests.step);
test_step.dependOn(&run_exe_tests.step);
+
const fmt_step = b.step("fmt", "Format");
const do_fmt = b.addFmt(.{
.paths = &.{ "src" },
});
fmt_step.dependOn(&do_fmt.step);
+
const check_fmt_step = b.step("check", "Check format and syntax");
const do_check_fmt = b.addFmt(.{
.paths = &.{ "src" },
.check = true,
});
check_fmt_step.dependOn(&do_check_fmt.step);
+
+
+ const docs_step = b.step("docs", "Emit documentation");
+ const docs_install = b.addInstallDirectory(.{
+ .install_dir = .prefix,
+ .install_subdir = "docs",
+ .source_dir = exe.getEmittedDocs(),
+ });
+ docs_step.dependOn(&docs_install.step);
}
diff --git a/flake.nix b/flake.nix
index c164e8e..a461455 100644
--- a/flake.nix
+++ b/flake.nix
@@ -21,12 +21,14 @@
buildInputs = [
pkgs.zig
+ pkgs.sqlite
];
};
devShells.x86_64-linux.default = pkgs.mkShell {
packages = [
pkgs.zig
+ pkgs.sqlite
];
};
};
diff --git a/src/api/root.zig b/src/api/root.zig
index 5128aa1..c28991c 100644
--- a/src/api/root.zig
+++ b/src/api/root.zig
@@ -3,6 +3,8 @@
const std = @import("std");
+pub const _ = @import("sketch.zig");
+
test {
_ = std.testing.refAllDecls(@This());
}
diff --git a/src/api/sketch.zig b/src/api/sketch.zig
new file mode 100644
index 0000000..409606f
--- /dev/null
+++ b/src/api/sketch.zig
@@ -0,0 +1,29 @@
+const std = @import("std");
+const http = @import("http");
+
+pub const Url = http.Url.Template(
+ "/api/images/{id}/metadata/{name}",
+ .{
+ .guard = Auth(.admin),
+ }
+);
+
+const Query = struct {
+ condition: []const u8,
+ start: usize,
+ end: usize,
+}
+
+pub fn post(request: http.Request(Url)) !http.Response {
+ const query = try request.body.json(Query, request.arena);
+
+ return .string(request.params.id);
+}
+
+pub fn get(request: http.Request(Url)) !http.Response {
+ return .file("./some/path.txt");
+}
+
+test {
+ _ = std.testing.refAllDecls(@This());
+}
diff --git a/src/frontend/File.zig b/src/frontend/File.zig
index e7c3b6d..fac6a03 100644
--- a/src/frontend/File.zig
+++ b/src/frontend/File.zig
@@ -1,6 +1,6 @@
-const std = @import("std");
+const Minifier = @import("Minifier.zig");
-pub fn File(comptime tag: ?[]const u8, Iterator: type) type {
+pub fn File(comptime tag: ?[]const u8, comptime minifier: Minifier) type {
const opening = if (tag) |t| "<" ++ t ++ ">" else "";
const closing = if (tag) |t| "</" ++ t ++ ">" else "";
@@ -22,70 +22,20 @@ pub fn File(comptime tag: ?[]const u8, Iterator: type) type {
};
}
- fn content(comptime self: @This()) []const u8 {
- return opening ++ @embedFile(self.file) ++ closing;
- }
-
- fn content_minified(comptime self: @This()) []const u8 {
- var minified: []const u8 = "";
- var iterator: Iterator = .init(@embedFile(self.file));
-
- while (iterator.next()) |segment| {
- minified = minified ++ segment;
- }
-
- return opening ++ minified ++ closing;
- }
-
pub fn to_html_string(comptime self: @This()) []const u8 {
- return if (self.do_minify) self.content_minified() else self.content();
- }
- };
-}
-
-fn StringIterator(comptime quotes: []const u8) type {
- return struct {
- content: []const u8,
-
- pub fn init(content: []const u8) @This() {
- return .{ .content = content };
- }
-
- pub fn next(self: *@This()) ?[]const u8 {
- @setEvalBranchQuota(self.content.len * 20);
-
- self.content = std.mem.trimStart(u8, self.content, &std.ascii.whitespace);
-
- if (self.content.len == 0) return null;
-
- var index: usize = 0;
-
- while (self.content.len > index) {
- const char = self.content[index];
-
- if (std.mem.indexOfScalar(u8, quotes, char) != null) {
- index += 1;
- while (self.content.len > index) {
- if (self.content[index] == char) {
- index += 1;
- break;
- }
- index += 1;
- }
- } else if (std.ascii.isWhitespace(char)) {
- break;
- }
- index += 1;
- }
-
- const block = self.content[0..index];
- self.content = self.content[index..];
-
- return block ++ " ";
+ return opening ++ (if (self.do_minify)
+ minifier.minify(@embedFile(self.file))
+ else
+ @embedFile(self.file)) ++ closing;
}
};
}
-pub const Style = File("style", StringIterator("\"'"));
-pub const Script = File("script", StringIterator("\"'`"));
-pub const Html = File(null, StringIterator("\"'"));
+/// When using regex in Javascript the spaces
+/// might get trimmed since /re/ is a awful syntax
+/// I don't what to add a rule for it currently,
+/// Since a single `/` is a operator but if there are two on one line
+/// it is a regex.
+pub const Script = File("script", .js);
+pub const Style = File("style", .css);
+pub const Html = File(null, .html);
diff --git a/src/frontend/Minifier.zig b/src/frontend/Minifier.zig
new file mode 100644
index 0000000..65dd9fb
--- /dev/null
+++ b/src/frontend/Minifier.zig
@@ -0,0 +1,168 @@
+const std = @import("std");
+
+/// A string contained in quotes will not be changed in any form.
+quotes: []const u8,
+
+/// This character is used to escape in strings.
+escape: ?u8,
+
+/// A delimiter does not need any padding around it.
+delimiters: []const u8,
+
+/// Kinds of comments, will be removed in the output.
+comments: []const struct { []const u8, []const u8 },
+
+pub const html: @This() = .{
+ .quotes = "\"'",
+ .escape = null,
+ .delimiters = "<>",
+ .comments = &.{.{ "<!--", "-->" }},
+};
+
+pub const js: @This() = .{
+ .quotes = "\"'`",
+ .escape = '\\',
+ .delimiters = "+-*,.:;[](){}=&|^",
+ .comments = &.{ .{ "//", "\n" }, .{ "/*", "*/" } },
+};
+
+pub const css: @This() = .{
+ .quotes = "\"'",
+ .escape = '\\',
+ .delimiters = ":;{}",
+ .comments = &.{.{ "/*", "*/" }},
+};
+
+fn skip(comptime source: *[]const u8, comptime len: usize) void {
+ source.* = (source.*)[len..];
+}
+
+fn push(
+ comptime source: *[]const u8,
+ comptime target: *[]const u8,
+ comptime len: usize,
+) void {
+ target.* = target.* ++ (source.*)[0..len];
+ skip(source, len);
+}
+
+pub fn minify(
+ comptime self: *const @This(),
+ comptime c: []const u8,
+) []const u8 {
+ @setEvalBranchQuota(c.len * 100);
+ var content = std.mem.trimStart(u8, c, &std.ascii.whitespace);
+ var block: []const u8 = "";
+
+ while (content.len > 0) {
+ const had_comment: bool = cmt: {
+ inline for (self.comments) |comment| {
+ const start, const end = comment;
+
+ if (std.mem.startsWith(u8, content, start)) {
+ skip(&content, start.len);
+ skip(&content, if (std.mem.indexOf(u8, content, end)) |l| l + end.len else content.len);
+ break :cmt true;
+ }
+ }
+ break :cmt false;
+ };
+
+ if (had_comment) {} else if (std.mem.indexOfScalar(u8, self.quotes, content[0])) |_| {
+ const quote = content[0];
+ push(&content, &block, 1);
+ while (content.len > 0) {
+ if (content[0] == self.escape) {
+ push(&content, &block, 2);
+ } else if (content[0] == quote) {
+ push(&content, &block, 1);
+ break;
+ }
+ push(&content, &block, 1);
+ }
+ } else {
+ push(&content, &block, 1);
+ }
+
+ const was_trimmed = content.len > 0 and std.ascii.isWhitespace(content[0]);
+ content = std.mem.trimStart(u8, content, &std.ascii.whitespace);
+ if (was_trimmed and
+ block.len > 0 and
+ content.len > 0 and
+ std.mem.indexOfScalar(u8, self.delimiters, block[block.len - 1]) == null and
+ std.mem.indexOfScalar(u8, self.delimiters, content[0]) == null)
+ {
+ block = block ++ " ";
+ }
+ }
+
+ return block;
+}
+
+test "html" {
+ const buffer: []const u8 =
+ \\<html>
+ \\ <head>
+ \\ <title>Some Title</title>
+ \\ </head>
+ \\ <body>
+ \\ <z-component type="something here" tag="1 2 3"> 123 </z-component>
+ \\ <!-- some comment here -->
+ \\ Hello World!
+ \\ </body>
+ \\</html>
+ ;
+
+ try std.testing.expectEqualStrings(
+ "<html><head><title>Some Title</title></head><body><z-component type=\"something here\" tag=\"1 2 3\">123</z-component>Hello World!</body></html>",
+ comptime html.minify(buffer),
+ );
+}
+
+test "css" {
+ const buffer: []const u8 =
+ \\body {
+ \\ background: #fff;
+ \\ color: red;
+ \\}
+ \\
+ \\#content:first {
+ \\ user-select: none;
+ \\ /* comment */
+ \\ content: ' ';
+ \\ padding: 5px 5px;
+ \\}
+ ;
+
+ try std.testing.expectEqualStrings(
+ "body{background:#fff;color:red;}#content:first{user-select:none;content:' ';padding:5px 5px;}",
+ comptime css.minify(buffer),
+ );
+}
+
+test "js" {
+ const buffer: []const u8 =
+ \\class extends HTMLElement {
+ \\ // comment here
+ \\ constructor() {
+ \\ super();
+ \\ this.attachShadow({ mode: 'open' });
+ \\ }
+ \\
+ \\ /*
+ \\ * Mulitline
+ \\ */
+ \\ connectedCallback() {
+ \\ const type = this.getAttribute('type');
+ \\ const template = document.getElementById(`--${type}`);
+ \\ const node = document.importNode(template.content, true);
+ \\ this.shadowRoot.appendChild(node);
+ \\ }
+ \\}
+ ;
+
+ try std.testing.expectEqualStrings(
+ "class extends HTMLElement{constructor(){super();this.attachShadow({mode:'open'});}connectedCallback(){const type=this.getAttribute('type');const template=document.getElementById(`--${type}`);const node=document.importNode(template.content,true);this.shadowRoot.appendChild(node);}}",
+ comptime js.minify(buffer),
+ );
+}
diff --git a/src/frontend/root.zig b/src/frontend/root.zig
index 39908b5..8ff1055 100644
--- a/src/frontend/root.zig
+++ b/src/frontend/root.zig
@@ -5,7 +5,8 @@ const std = @import("std");
pub const Component = @import("Component.zig");
pub const Document = @import("Document.zig");
pub const File = @import("File.zig");
-pub const Head = @import("File.zig");
+pub const Head = @import("Head.zig");
+pub const Minifier = @import("Minifier.zig");
test {
_ = std.testing.refAllDecls(@This());
diff --git a/src/frontend/web/hello-world/index.html b/src/frontend/web/hello-world/index.html
index a9b0be7..047a283 100644
--- a/src/frontend/web/hello-world/index.html
+++ b/src/frontend/web/hello-world/index.html
@@ -1,3 +1,6 @@
<div id="container" onclick="clicked()">
Click Me!
+ <z-component type="box">
+ <span slot="title">Hello World!<span>
+ </z-component>
</div>
diff --git a/src/frontend/web/index.html b/src/frontend/web/index.html
index 885f2c5..646cdb0 100644
--- a/src/frontend/web/index.html
+++ b/src/frontend/web/index.html
@@ -1 +1,3 @@
-<z-component type="hello-world"></z-component>
+<z-component type="hello-world">
+ <p slot="ur-mom">lulululu</p>
+</z-component>
diff --git a/src/frontend/z-component.js b/src/frontend/z-component.js
index e9b63ff..5e430b5 100644
--- a/src/frontend/z-component.js
+++ b/src/frontend/z-component.js
@@ -3,14 +3,13 @@ customElements.define(
class extends HTMLElement {
constructor() {
super();
- this.attachShadow({ mode: 'open' })
+ this.attachShadow({ mode: 'open' });
}
connectedCallback() {
const type = this.getAttribute('type');
const template = document.getElementById(`--${type}`);
const node = document.importNode(template.content, true);
- this.shadowRoot.id = 'root';
this.shadowRoot.appendChild(node);
}
}
diff --git a/src/http/Server.zig b/src/http/Server.zig
new file mode 100644
index 0000000..b6e2d90
--- /dev/null
+++ b/src/http/Server.zig
@@ -0,0 +1,20 @@
+const std = @import("std");
+
+const Self = @This();
+
+server: std.Io.net.Server,
+io: std.Io.Threaded,
+
+pub fn init(
+ gpa: std.mem.Allocator,
+ address: std.Io.net.IpAddress,
+) !Self {
+ var io: std.Io.Threaded = .init(gpa, .{});
+
+ return .{
+ .io = io,
+ .server = try address.listen(io.io(), .{ .reuse_address = true }),
+ };
+}
+
+pub fn serve(_: *Self) !void {}
diff --git a/src/http/Url.zig b/src/http/Url.zig
new file mode 100644
index 0000000..4a0a2b0
--- /dev/null
+++ b/src/http/Url.zig
@@ -0,0 +1,39 @@
+const std = @import("std");
+
+const Segment = union(enum) {
+ literal: []const u8,
+ parameter: []const u8,
+
+ pub fn format(self: @This(), writer: *std.Io.Writer) !void {
+ try writer.print("{s}{{ {s} }}", .{ @tagName(self), switch (self) {
+ inline else => |s| s,
+ } });
+ }
+};
+
+pub fn Template(comptime template: []const u8) []const Segment {
+ var segments: []const Segment = &.{};
+ var t = template;
+
+ if (template.len == 0 or template[0] != '/') {
+ @compileError("template needs to be absolute");
+ }
+
+ while (t.len > 0) {
+ const index = std.mem.indexOfScalar(u8, t, '/');
+ const segment = t[0 .. index orelse t.len];
+ t = t[(index orelse t.len - 1) + 1 ..];
+
+ if (segment.len > 0) {
+ const s: []const Segment = &.{seg: {
+ if (segment[0] == '{' and segment[segment.len - 1] == '}') {
+ break :seg .{ .parameter = segment[1 .. segment.len - 1] };
+ } else {
+ break :seg .{ .literal = segment };
+ }
+ }};
+ segments = segments ++ s;
+ }
+ }
+ return segments;
+}
diff --git a/src/http/root.zig b/src/http/root.zig
index 743e1c4..317dacd 100644
--- a/src/http/root.zig
+++ b/src/http/root.zig
@@ -2,6 +2,9 @@
const std = @import("std");
+pub const Server = @import("Server.zig");
+pub const Url = @import("Url.zig");
+
test {
_ = std.testing.refAllDecls(@This());
}
diff --git a/src/main.zig b/src/main.zig
index 1a66b63..df0c061 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -4,9 +4,11 @@
//! but also the actual running of the server / webapp.
const std = @import("std");
+const http = @import("http");
pub fn main(init: std.process.Init) !void {
- _ = init;
+ var server: http.Server = try .init(init.gpa, try .parseLiteral("127.0.0.1:8080"));
+ try server.serve();
}
test {
diff --git a/src/root.zig b/src/root.zig
new file mode 100644
index 0000000..8d85eb4
--- /dev/null
+++ b/src/root.zig
@@ -0,0 +1,6 @@
+//! Memora Software Stack
+
+pub const db = @import("db");
+pub const api = @import("api");
+pub const frontend = @import("frontend");
+pub const http = @import("http");