Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Markdown #41

Merged
merged 1 commit into from
Mar 26, 2024
Merged
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
4 changes: 4 additions & 0 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ pub fn build(b: *std.Build) !void {
// b.dependency("jetzig",.{}).builder.dependency("zmpl",.{}).module("zmpl");
b.modules.put("zmpl", zmpl_dep.module("zmpl")) catch @panic("Out of memory");

const zmd_dep = b.dependency("zmd", .{ .target = target, .optimize = optimize });

const ZmplBuild = @import("zmpl").ZmplBuild;
const ZmplTemplate = @import("zmpl").Template;
var zmpl_build = ZmplBuild.init(b, lib, template_path);
Expand All @@ -60,6 +62,7 @@ pub fn build(b: *std.Build) !void {
lib.root_module.addImport("zmpl", zmpl_module);
jetzig_module.addImport("zmpl", zmpl_module);
jetzig_module.addImport("args", zig_args_dep.module("args"));
jetzig_module.addImport("zmd", zmd_dep.module("zmd"));

const main_tests = b.addTest(.{
.root_source_file = .{ .path = "src/tests.zig" },
Expand Down Expand Up @@ -148,6 +151,7 @@ pub fn jetzigInit(b: *std.Build, exe: *std.Build.Step.Compile, options: JetzigIn
exe_static_routes.root_module.addImport("routes", routes_module);
exe_static_routes.root_module.addImport("jetzig", jetzig_module);
exe_static_routes.root_module.addImport("zmpl", zmpl_module);
exe_static_routes.root_module.addImport("jetzig_app", &exe.root_module);

const run_static_routes_cmd = b.addRunArtifact(exe_static_routes);
exe.step.dependOn(&run_static_routes_cmd.step);
Expand Down
4 changes: 4 additions & 0 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
.name = "jetzig",
.version = "0.0.0",
.dependencies = .{
.zmd = .{
.url = "https://github.com/jetzig-framework/zmd/archive/1a526ca1cf577789ca15ca1b71d3e943b81fb801.tar.gz",
.hash = "1220bfc5c29bc930b5a524c210712ef65c6cde6770450899bef01164a3089e6707fa",
},
.zmpl = .{
.url = "https://github.com/jetzig-framework/zmpl/archive/9e2df115c9f17e92fb60a4d09bf55ea48d0388b0.tar.gz",
.hash = "1220820b7f5f3e01b7dc976d32cf9ff65d44dee2642533f4b8104e19a824e802d7e1",
Expand Down
3 changes: 3 additions & 0 deletions demo/public/prism.css

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

10 changes: 10 additions & 0 deletions demo/public/prism.js

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions demo/src/app/views/layouts/application.zmpl
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/htmx.org@1.9.10"></script>

<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="/prism.css" />
</head>

<body>
<main>{zmpl.content}</main>
<script src="/prism.js"></script>
</body>
</html>
9 changes: 9 additions & 0 deletions demo/src/app/views/markdown.zig
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const std = @import("std");
const jetzig = @import("jetzig");

pub const layout = "application";

pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
_ = data;
return request.render(.ok);
}
66 changes: 66 additions & 0 deletions demo/src/app/views/markdown/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Markdown Example

![jetzig logo](https://www.jetzig.dev/jetzig.png)

_Markdown_ is rendered by _[zmd](https://github.com/jetzig-framework/zmd)_.

You can use a `StaticRequest` in your view if you prefer to render your _Markdown_ at build time, or use `Request` in development to render at run time without a server restart.

Simply create a `.md` file instead of a `.zmpl` file, e.g. `src/app/views/iguanas/index.md` and _Markdown_ will be rendered.

## An _ordered_ list

1. List item with a [link](https://ziglang.org/)
1. List item with some **bold** and _italic_ text
1. List item 3

## An _unordered_ list

* List item 1
* List item 2
* List item 3

## Define your own formatters in `src/main.zig`

```zig
pub const jetzig_options = struct {
pub const markdown_fragments = struct {
pub const root = .{
"<div class='p-5'>",
"</div>",
};
pub const h1 = .{
"<h1 class='text-2xl mb-3 font-bold'>",
"</h1>",
};
pub const h2 = .{
"<h2 class='text-xl mb-3 font-bold'>",
"</h2>",
};
pub const h3 = .{
"<h3 class='text-lg mb-3 font-bold'>",
"</h3>",
};
pub const paragraph = .{
"<p class='p-3'>",
"</p>",
};
pub const code = .{
"<span class='font-mono bg-gray-900 p-2 text-white'>",
"</span>",
};

pub fn block(allocator: std.mem.Allocator, node: jetzig.zmd.Node) ![]const u8 {
return try std.fmt.allocPrint(allocator,
\\<pre class="w-1/2 font-mono mt-4 ms-3 bg-gray-900 p-2 text-white"><code>{s}</code></pre>
, .{try jetzig.zmd.html.escape(allocator, node.content)});
}

pub fn link(allocator: std.mem.Allocator, node: jetzig.zmd.Node) ![]const u8 {
return try std.fmt.allocPrint(allocator,
\\<a class="underline decoration-sky-500" href="{0s}" title={1s}>{1s}</a>
, .{ node.href.?, node.title.? });
}
};
}
```
52 changes: 52 additions & 0 deletions demo/src/main.zig
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const std = @import("std");

pub const jetzig = @import("jetzig");

pub const routes = @import("routes").routes;

// Override default settings in `jetzig.config` here:
Expand Down Expand Up @@ -30,6 +31,57 @@ pub const jetzig_options = struct {

// HTTP buffer. Must be large enough to store all headers. This should typically not be modified.
// pub const http_buffer_size: usize = std.math.pow(usize, 2, 16);

// Set custom fragments for rendering markdown templates. Any values will fall back to
// defaults provided by Zmd (https://github.com/bobf/zmd/blob/main/src/zmd/html.zig).
pub const markdown_fragments = struct {
pub const root = .{
"<div class='p-5'>",
"</div>",
};
pub const h1 = .{
"<h1 class='text-2xl mb-3 font-bold'>",
"</h1>",
};
pub const h2 = .{
"<h2 class='text-xl mb-3 font-bold'>",
"</h2>",
};
pub const h3 = .{
"<h3 class='text-lg mb-3 font-bold'>",
"</h3>",
};
pub const paragraph = .{
"<p class='p-3'>",
"</p>",
};
pub const code = .{
"<span class='font-mono bg-gray-900 p-2 text-white'>",
"</span>",
};

pub const unordered_list = .{
"<ul class='list-disc ms-8 leading-8'>",
"</ul>",
};

pub const ordered_list = .{
"<ul class='list-decimal ms-8 leading-8'>",
"</ul>",
};

pub fn block(allocator: std.mem.Allocator, node: jetzig.zmd.Node) ![]const u8 {
return try std.fmt.allocPrint(allocator,
\\<pre class="w-1/2 font-mono mt-4 ms-3 bg-gray-900 p-2 text-white"><code class="language-{?s}">{s}</code></pre>
, .{ node.meta, node.content });
}

pub fn link(allocator: std.mem.Allocator, node: jetzig.zmd.Node) ![]const u8 {
return try std.fmt.allocPrint(allocator,
\\<a class="underline decoration-sky-500" href="{0s}" title={1s}>{1s}</a>
, .{ node.href.?, node.title.? });
}
};
};

pub fn main() !void {
Expand Down
76 changes: 58 additions & 18 deletions src/compile_static_routes.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ const std = @import("std");
const jetzig = @import("jetzig");
const routes = @import("routes").routes;
const zmpl = @import("zmpl");
const jetzig_options = @import("jetzig_app").jetzig_options;

pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
Expand Down Expand Up @@ -101,10 +102,60 @@ fn writeContent(

std.debug.print("[jetzig] Compiled static route: {s}\n", .{json_path});

if (zmpl.find(route.template)) |template| {
var content: []const u8 = undefined;
defer allocator.free(content);
const html_content = try renderZmplTemplate(allocator, route, view) orelse
try renderMarkdown(allocator, route, view) orelse
null;
const html_path = try std.mem.concat(
allocator,
u8,
&[_][]const u8{ route.name, index_suffix, ".html" },
);
if (html_content) |content| {
defer allocator.free(html_path);
const html_file = try dir.createFile(html_path, .{ .truncate = true });
try html_file.writeAll(content);
defer html_file.close();
allocator.free(content);
std.debug.print("[jetzig] Compiled static route: {s}\n", .{html_path});
}
}

fn renderMarkdown(
allocator: std.mem.Allocator,
route: jetzig.views.Route,
view: jetzig.views.View,
) !?[]const u8 {
const fragments = if (@hasDecl(jetzig_options, "markdown_fragments"))
jetzig_options.markdown_fragments
else
null;
const content = try jetzig.markdown.render(allocator, &route, fragments) orelse return null;

if (route.layout) |layout_name| {
try view.data.addConst("jetzig_view", view.data.string(route.name));
try view.data.addConst("jetzig_action", view.data.string(@tagName(route.action)));

// TODO: Allow user to configure layouts directory other than src/app/views/layouts/
const prefixed_name = try std.mem.concat(allocator, u8, &[_][]const u8{ "layouts_", layout_name });
defer allocator.free(prefixed_name);
defer allocator.free(prefixed_name);

if (zmpl.find(prefixed_name)) |layout| {
view.data.content = .{ .data = content };
return try layout.render(view.data);
} else {
std.debug.print("Unknown layout: {s}\n", .{layout_name});
return content;
}
} else return null;
}

fn renderZmplTemplate(
allocator: std.mem.Allocator,
route: jetzig.views.Route,
view: jetzig.views.View,
) !?[]const u8 {
if (zmpl.find(route.template)) |template| {
try view.data.addConst("jetzig_view", view.data.string(route.name));
try view.data.addConst("jetzig_action", view.data.string(@tagName(route.action)));

Expand All @@ -114,24 +165,13 @@ fn writeContent(
defer allocator.free(prefixed_name);

if (zmpl.find(prefixed_name)) |layout| {
content = try template.renderWithLayout(layout, view.data);
return try template.renderWithLayout(layout, view.data);
} else {
std.debug.print("Unknown layout: {s}\n", .{layout_name});
content = try allocator.dupe(u8, "");
return try allocator.dupe(u8, "");
}
} else {
content = try template.render(view.data);
return try template.render(view.data);
}

const html_path = try std.mem.concat(
allocator,
u8,
&[_][]const u8{ route.name, index_suffix, ".html" },
);
defer allocator.free(html_path);
const html_file = try dir.createFile(html_path, .{ .truncate = true });
try html_file.writeAll(content);
defer html_file.close();
std.debug.print("[jetzig] Compiled static route: {s}\n", .{html_path});
}
} else return null;
}
5 changes: 5 additions & 0 deletions src/jetzig.zig
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const std = @import("std");

pub const zmpl = @import("zmpl").zmpl;
pub const zmd = @import("zmd").zmd;

pub const http = @import("jetzig/http.zig");
pub const loggers = @import("jetzig/loggers.zig");
Expand All @@ -10,6 +11,7 @@ pub const colors = @import("jetzig/colors.zig");
pub const middleware = @import("jetzig/middleware.zig");
pub const util = @import("jetzig/util.zig");
pub const types = @import("jetzig/types.zig");
pub const markdown = @import("jetzig/markdown.zig");

/// The primary interface for a Jetzig application. Create an `App` in your application's
/// `src/main.zig` and call `start` to launch the application.
Expand Down Expand Up @@ -65,6 +67,9 @@ pub const config = struct {
/// modified.
pub const http_buffer_size: usize = std.math.pow(usize, 2, 16);

/// A struct of fragments to use when rendering Markdown templates.
pub const markdown_fragments = zmd.html.DefaultFragments;

/// Reconciles a configuration value from user-defined values and defaults provided by Jetzig.
pub fn get(T: type, comptime key: []const u8) T {
const self = @This();
Expand Down
Loading
Loading