Merge pull request #12 from jetzig-framework/jetzig-init

Implement init command, get rid of old init script/artifacts
This commit is contained in:
bobf 2024-03-06 23:01:05 +00:00 committed by GitHub
commit d37007f975
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 763 additions and 280 deletions

5
.gitignore vendored
View File

@ -1,9 +1,4 @@
TODO.md
main
get/
zig-out/
zig-cache/
*.core
src/app/views/**/*.compiled.zig
archive.tar.gz
static/

BIN
archive.tar.gz Normal file

Binary file not shown.

View File

@ -22,7 +22,7 @@ pub fn build(b: *std.Build) !void {
const mime_module = try GenerateMimeTypes.generateMimeModule(b);
b.installArtifact(lib);
const zig_args_dep = b.dependency("args", .{ .target = target, .optimize = optimize });
const jetzig_module = b.addModule("jetzig", .{ .root_source_file = .{ .path = "src/jetzig.zig" } });
jetzig_module.addImport("mime_types", mime_module);
@ -39,6 +39,7 @@ pub fn build(b: *std.Build) !void {
lib.root_module.addImport("zmpl", zmpl_dep.module("zmpl"));
jetzig_module.addImport("zmpl", zmpl_dep.module("zmpl"));
lib.root_module.addImport("args", zig_args_dep.module("args"));
// This is the way to make it look nice in the zig build script
// If we would do it the other way around, we would have to do

View File

@ -3,8 +3,12 @@
.version = "0.0.0",
.dependencies = .{
.zmpl = .{
.url = "https://github.com/jetzig-framework/zmpl/archive/aa7147f8a52d927dce6cdd4f5b3bb2de5080f28c.tar.gz",
.hash = "12203d262b39b2328adb981e41c5127507f3d47e977c1a4e69a96688a4213b986d04",
.url = "https://github.com/jetzig-framework/zmpl/archive/ed99a1604b37fb05b0f5843a3288588f3dfe2e63.tar.gz",
.hash = "1220771fe742fc620872051b92082d370549ed857e5a93ae43f92c5767ca2aaf42b1",
},
.args = .{
.url = "https://github.com/MasterQ32/zig-args/archive/89f18a104d9c13763b90e97d6b4ce133da8a3e2b.tar.gz",
.hash = "12203ded54c85878eea7f12744066dcb4397177395ac49a7b2aa365bf6047b623829",
},
},

33
cli/build.zig Normal file
View File

@ -0,0 +1,33 @@
const std = @import("std");
const compile = @import("compile.zig");
pub fn build(b: *std.Build) !void {
const target = b.standardTargetOptions(.{});
const optimize = b.standardOptimizeOption(.{});
const exe = b.addExecutable(.{
.name = "jetzig",
.root_source_file = .{ .path = "cli.zig" },
.target = target,
.optimize = optimize,
});
const zig_args_dep = b.dependency("args", .{ .target = target, .optimize = optimize });
exe.root_module.addImport("args", zig_args_dep.module("args"));
exe.root_module.addImport(
"init_data",
try compile.initDataModule(b),
);
b.installArtifact(exe);
const run_cmd = b.addRunArtifact(exe);
run_cmd.step.dependOn(b.getInstallStep());
if (b.args) |args| {
run_cmd.addArgs(args);
}
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
}

15
cli/build.zig.zon Normal file
View File

@ -0,0 +1,15 @@
.{
.name = "jetzig-cli",
.version = "0.0.0",
.minimum_zig_version = "0.12.0",
.dependencies = .{
.args = .{
.url = "https://github.com/MasterQ32/zig-args/archive/89f18a104d9c13763b90e97d6b4ce133da8a3e2b.tar.gz",
.hash = "12203ded54c85878eea7f12744066dcb4397177395ac49a7b2aa365bf6047b623829",
},
},
.paths = .{
"",
},
}

60
cli/cli.zig Normal file
View File

@ -0,0 +1,60 @@
const std = @import("std");
const args = @import("args");
const init = @import("init.zig");
const Options = struct {
help: bool = false,
pub const shorthands = .{
.h = "help",
};
pub const meta = .{
.option_docs = .{
.init = "Initialize a new project",
.help = "Print help and exit",
},
};
};
const Verb = union(enum) {
init: init.Options,
};
/// Main entrypoint for `jetzig` executable. Parses command line args and generates a new
/// project, scaffolding, etc.
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
const allocator = gpa.allocator();
defer std.debug.assert(gpa.deinit() == .ok);
const options = try args.parseWithVerbForCurrentProcess(Options, Verb, allocator, .print);
defer options.deinit();
const writer = std.io.getStdErr().writer();
if (options.verb) |verb| {
switch (verb) {
.init => |opts| return init.run(
allocator,
opts,
writer,
options.positionals,
.{ .help = options.options.help },
),
}
}
if (options.options.help) {
try args.printHelp(Options, "jetzig", writer);
try writer.writeAll(
\\
\\Commands:
\\
\\ init Initialize a new project.
\\
\\ Pass --help to any command for more information, e.g. `jetzig init --help`
\\
);
}
}

71
cli/compile.zig Normal file
View File

@ -0,0 +1,71 @@
const std = @import("std");
fn base64Encode(allocator: std.mem.Allocator, input: []const u8) []const u8 {
const encoder = std.base64.Base64Encoder.init(
std.base64.url_safe_no_pad.alphabet_chars,
std.base64.url_safe_no_pad.pad_char,
);
const size = encoder.calcSize(input.len);
const ptr = allocator.alloc(u8, size) catch @panic("OOM");
_ = encoder.encode(ptr, input);
return ptr;
}
pub fn initDataModule(build: *std.Build) !*std.Build.Module {
const root_path = build.pathFromRoot("..");
var buf = std.ArrayList(u8).init(build.allocator);
defer buf.deinit();
const writer = buf.writer();
const paths = .{
"demo/build.zig",
"demo/src/main.zig",
"demo/src/app/middleware/DemoMiddleware.zig",
"demo/src/app/views/init.zig",
"demo/src/app/views/init/index.zmpl",
"demo/src/app/views/init/_content.zmpl",
"demo/public/jetzig.png",
"demo/public/zmpl.png",
"demo/public/favicon.ico",
"demo/public/styles.css",
".gitignore",
};
try writer.writeAll(
\\pub const init_data = .{
\\
);
var dir = try std.fs.openDirAbsolute(root_path, .{});
defer dir.close();
inline for (paths) |path| {
const stat = try dir.statFile(path);
const encoded = base64Encode(
build.allocator,
try dir.readFileAlloc(build.allocator, path, stat.size),
);
defer build.allocator.free(encoded);
const output = try std.fmt.allocPrint(
build.allocator,
\\.{{ .path = "{s}", .data = "{s}" }},
,
.{ path, encoded },
);
defer build.allocator.free(output);
try writer.writeAll(output);
}
try writer.writeAll(
\\};
\\
);
const write_files = build.addWriteFiles();
const init_data_source = write_files.add("init_data.zig", buf.items);
return build.createModule(.{ .root_source_file = init_data_source });
}

427
cli/init.zig Normal file
View File

@ -0,0 +1,427 @@
const std = @import("std");
const args = @import("args");
const init_data = @import("init_data").init_data;
fn base64Decode(allocator: std.mem.Allocator, input: []const u8) ![]const u8 {
const decoder = std.base64.Base64Decoder.init(
std.base64.url_safe_no_pad.alphabet_chars,
std.base64.url_safe_no_pad.pad_char,
);
const size = try decoder.calcSizeForSlice(input);
const ptr = try allocator.alloc(u8, size);
try decoder.decode(ptr, input);
return ptr;
}
pub const Options = struct {
path: ?[]const u8 = null,
pub const shorthands = .{
.p = "path",
};
pub const meta = .{
.usage_summary = "[--path PATH]",
.full_text =
\\Initializes a new Jetzig project in the current directory or attempts to
\\create a new directory specified by PATH
\\
\\Creates build.zig, build.zig.zon, src/main.zig, and an example view with a template.
\\
\\Run `zig build run` to launch a development server when complete.
,
.option_docs = .{
.path = "Set the output path relative to the current directory (default: current directory)",
},
};
};
/// Run the `jetzig init` command.
pub fn run(
allocator: std.mem.Allocator,
options: Options,
writer: anytype,
positionals: [][]const u8,
other_options: struct { help: bool },
) !void {
_ = options;
var install_path: ?[]const u8 = null;
for (positionals) |arg| {
if (install_path != null) {
std.debug.print("Unexpected positional argument: {s}\n", .{arg});
return error.JetzigUnexpectedPositionalArgumentsError;
}
install_path = arg;
}
const github_url = try githubUrl(allocator);
defer allocator.free(github_url);
if (other_options.help) {
try args.printHelp(Options, "jetzig init", writer);
return;
}
var install_dir: std.fs.Dir = undefined;
defer install_dir.close();
var project_name: []const u8 = undefined;
defer allocator.free(project_name);
if (install_path) |path| {
install_dir = try std.fs.cwd().makeOpenPath(path, .{});
project_name = try allocator.dupe(u8, std.fs.path.basename(path));
} else {
const cwd_realpath = try std.fs.cwd().realpathAlloc(allocator, ".");
defer allocator.free(cwd_realpath);
const default_project_name = std.fs.path.basename(cwd_realpath);
project_name = try promptInput(allocator, "Project name", .{ .default = default_project_name });
const sub_path = if (std.mem.eql(u8, project_name, default_project_name)) "" else project_name;
const default_install_path = try std.fs.path.join(
allocator,
&[_][]const u8{ cwd_realpath, sub_path },
);
defer allocator.free(default_install_path);
const input_install_path = try promptInput(
allocator,
"Install path",
.{ .default = default_install_path },
);
defer allocator.free(input_install_path);
install_dir = try std.fs.cwd().makeOpenPath(input_install_path, .{});
}
const real_path = try install_dir.realpathAlloc(allocator, ".");
defer allocator.free(real_path);
const output = try std.fmt.allocPrint(allocator, "Creating new project in {s}\n\n", .{real_path});
defer allocator.free(output);
try writer.writeAll(output);
try copySourceFile(
allocator,
install_dir,
"demo/build.zig",
"build.zig",
&[_]Replace{.{ .from = "jetzig-demo", .to = project_name }},
);
try copySourceFile(
allocator,
install_dir,
"demo/src/main.zig",
"src/main.zig",
null,
);
try copySourceFile(
allocator,
install_dir,
"demo/src/app/middleware/DemoMiddleware.zig",
"src/app/middleware/DemoMiddleware.zig",
null,
);
try copySourceFile(
allocator,
install_dir,
"demo/src/app/views/init.zig",
"src/app/views/root.zig",
null,
);
try copySourceFile(
allocator,
install_dir,
"demo/src/app/views/init/index.zmpl",
"src/app/views/root/index.zmpl",
&[_]Replace{
.{ .from = "init/", .to = "root/" },
},
);
try copySourceFile(
allocator,
install_dir,
"demo/src/app/views/init/_content.zmpl",
"src/app/views/root/_content.zmpl",
null,
);
try copySourceFile(
allocator,
install_dir,
"demo/public/jetzig.png",
"public/jetzig.png",
null,
);
try copySourceFile(
allocator,
install_dir,
"demo/public/zmpl.png",
"public/zmpl.png",
null,
);
try copySourceFile(
allocator,
install_dir,
"demo/public/favicon.ico",
"public/favicon.ico",
null,
);
try copySourceFile(
allocator,
install_dir,
"demo/public/styles.css",
"public/styles.css",
null,
);
try copySourceFile(
allocator,
install_dir,
".gitignore",
".gitignore",
null,
);
try runCommand(allocator, install_dir, &[_][]const u8{
"zig",
"fetch",
"--save",
github_url,
});
// TODO: Use arg or interactive prompt to do Git setup in net project, default to no.
// const git_setup = false;
// if (git_setup) try gitSetup(allocator, install_dir);
std.debug.print(
\\
\\Setup complete! ✈️ 🦎
\\
\\Launch your new application:
\\
\\ $ cd {s}
\\
\\ $ zig build run
\\
\\And then browse to http://localhost:8080/
\\
\\
, .{real_path});
}
fn runCommand(allocator: std.mem.Allocator, install_dir: std.fs.Dir, argv: []const []const u8) !void {
const result = try std.process.Child.run(.{ .allocator = allocator, .argv = argv, .cwd_dir = install_dir });
defer allocator.free(result.stdout);
defer allocator.free(result.stderr);
const command = try std.mem.join(allocator, " ", argv);
defer allocator.free(command);
std.debug.print("[exec] {s}", .{command});
if (result.term.Exited != 0) {
printFailure();
std.debug.print(
\\
\\Error running command: {s}
\\
\\[stdout]:
\\
\\{s}
\\
\\[stderr]:
\\
\\{s}
\\
, .{ command, result.stdout, result.stderr });
return error.JetzigRunCommandError;
} else {
printSuccess();
}
}
const Replace = struct {
from: []const u8,
to: []const u8,
};
fn copySourceFile(
allocator: std.mem.Allocator,
install_dir: std.fs.Dir,
src: []const u8,
dest: []const u8,
replace: ?[]const Replace,
) !void {
std.debug.print("[create] {s}", .{dest});
var content: []const u8 = undefined;
if (replace) |capture| {
const initial = readSourceFile(allocator, src) catch |err| {
printFailure();
return err;
};
defer allocator.free(initial);
for (capture) |item| {
content = try std.mem.replaceOwned(u8, allocator, initial, item.from, item.to);
}
} else {
content = readSourceFile(allocator, src) catch |err| {
printFailure();
return err;
};
}
defer allocator.free(content);
writeSourceFile(install_dir, dest, content) catch |err| {
printFailure();
return err;
};
printSuccess();
}
// Read a file from Jetzig source code.
fn readSourceFile(allocator: std.mem.Allocator, path: []const u8) ![]const u8 {
inline for (init_data) |file| {
if (std.mem.eql(u8, path, file.path)) return try base64Decode(allocator, file.data);
}
return error.SourceFileNotFound;
}
// Write a file to the new project's directory.
fn writeSourceFile(install_dir: std.fs.Dir, path: []const u8, content: []const u8) !void {
// TODO: Detect presence and ask for confirmation if necessary.
if (std.fs.path.dirname(path)) |dirname| {
var dir = try install_dir.makeOpenPath(dirname, .{});
defer dir.close();
const file = try dir.createFile(std.fs.path.basename(path), .{ .truncate = true });
defer file.close();
try file.writeAll(content);
} else {
const file = try install_dir.createFile(path, .{ .truncate = true });
defer file.close();
try file.writeAll(content);
}
}
// Generate a full GitHub URL for passing to `zig fetch`.
fn githubUrl(allocator: std.mem.Allocator) ![]const u8 {
var client = std.http.Client{ .allocator = allocator };
defer client.deinit();
const url = "https://api.github.com/repos/jetzig-framework/jetzig/branches/main";
const extra_headers = &[_]std.http.Header{.{ .name = "X-GitHub-Api-Version", .value = "2022-11-28" }};
var response_storage = std.ArrayList(u8).init(allocator);
defer response_storage.deinit();
const fetch_result = try client.fetch(.{
.location = .{ .url = url },
.extra_headers = extra_headers,
.response_storage = .{ .dynamic = &response_storage },
});
if (fetch_result.status != .ok) {
std.debug.print("Error fetching from GitHub: {s}\n", .{url});
return error.JetzigGitHubFetchError;
}
const parsed_response = try std.json.parseFromSlice(
struct { commit: struct { sha: []const u8 } },
allocator,
response_storage.items,
.{ .ignore_unknown_fields = true },
);
defer parsed_response.deinit();
return try std.mem.concat(
allocator,
u8,
&[_][]const u8{
"https://github.com/jetzig-framework/jetzig/archive/",
parsed_response.value.commit.sha,
".tar.gz",
},
);
}
// Prompt a user for input and return the result. Accepts an optional default value.
fn promptInput(
allocator: std.mem.Allocator,
prompt: []const u8,
options: struct { default: ?[]const u8 },
) ![]const u8 {
const stdin = std.io.getStdIn();
const reader = stdin.reader();
const max_read_bytes = 1024;
while (true) {
if (options.default) |default| {
std.debug.print(
\\{s} [default: "{s}"]:
, .{ prompt, default });
} else {
std.debug.print(
\\{s}:
, .{prompt});
}
const input = try reader.readUntilDelimiterOrEofAlloc(allocator, '\n', max_read_bytes);
if (input) |capture| {
defer allocator.free(capture);
if (std.mem.eql(u8, capture, "")) {
if (options.default) |default| return try allocator.dupe(u8, strip(default));
} else return try allocator.dupe(u8, strip(capture));
}
}
}
// Strip leading and trailing whitespace from a u8 slice.
fn strip(input: []const u8) []const u8 {
return std.mem.trim(u8, input, &std.ascii.whitespace);
}
// Initialize a new Git repository when setting up a new project (optional).
fn gitSetup(allocator: std.mem.Allocator, install_dir: *std.fs.Dir) !void {
try runCommand(allocator, install_dir, &[_][]const u8{
"git",
"init",
".",
});
try runCommand(allocator, install_dir, &[_][]const u8{
"git",
"add",
".",
});
try runCommand(allocator, install_dir, &[_][]const u8{
"git",
"commit",
"-m",
"Initialize Jetzig project",
});
}
/// Print a success confirmation.
fn printSuccess() void {
std.debug.print("\n", .{});
}
/// Print a failure confirmation.
fn printFailure() void {
std.debug.print("\n", .{});
}

View File

@ -1,5 +1,5 @@
.{
.name = "sandbox-jetzig",
.name = "jetzig-demo",
.version = "0.0.0",
.minimum_zig_version = "0.12.0",
.dependencies = .{

View File

@ -0,0 +1,10 @@
/* Root stylesheet. Load into your Zmpl template with:
*
* <link rel="stylesheet" href="/styles.css" />
*
*/
.message {
font-weight: bold;
font-size: 3rem;
}

View File

@ -1,26 +0,0 @@
const std = @import("std");
const jetzig = @import("jetzig");
my_data: u8,
const Self = @This();
pub fn init(request: *jetzig.http.Request) !*Self {
var middleware = try request.allocator.create(Self);
middleware.my_data = 42;
return middleware;
}
pub fn beforeRequest(self: *Self, request: *jetzig.http.Request) !void {
request.server.logger.debug("[DemoMiddleware] Before request, custom data: {d}", .{self.my_data});
self.my_data = 43;
}
pub fn afterRequest(self: *Self, request: *jetzig.http.Request, response: *jetzig.http.Response) !void {
request.server.logger.debug("[DemoMiddleware] After request, custom data: {d}", .{self.my_data});
request.server.logger.debug("[DemoMiddleware] content-type: {s}", .{response.content_type});
}
pub fn deinit(self: *Self, request: *jetzig.http.Request) void {
request.allocator.destroy(self);
}

View File

@ -0,0 +1,53 @@
/// Demo middleware. Assign middleware by declaring `pub const middleware` in the
/// `jetzig_options` defined in your application's `src/main.zig`.
///
/// Middleware is called before and after the request, providing full access to the active
/// request, allowing you to execute any custom code for logging, tracking, inserting response
/// headers, etc.
///
/// This middleware is configured in the demo app's `src/main.zig`:
///
/// ```
/// pub const jetzig_options = struct {
/// pub const middleware: []const type = &.{@import("app/middleware/DemoMiddleware.zig")};
/// };
/// ```
const std = @import("std");
const jetzig = @import("jetzig");
/// Define any custom data fields you want to store here. Assigning to these fields in the `init`
/// function allows you to access them in the `beforeRequest` and `afterRequest` functions, where
/// they can also be modified.
my_custom_value: []const u8,
const Self = @This();
/// Initialize middleware.
pub fn init(request: *jetzig.http.Request) !*Self {
var middleware = try request.allocator.create(Self);
middleware.my_custom_value = "initial value";
return middleware;
}
/// Invoked immediately after the request head has been processed, before relevant view function
/// is processed. This gives you access to request headers but not the request body.
pub fn beforeRequest(self: *Self, request: *jetzig.http.Request) !void {
request.server.logger.debug("[DemoMiddleware] my_custom_value: {s}", .{self.my_custom_value});
self.my_custom_value = @tagName(request.method);
}
/// Invoked immediately after the request has finished responding. Provides full access to the
/// response as well as the request.
pub fn afterRequest(self: *Self, request: *jetzig.http.Request, response: *jetzig.http.Response) !void {
request.server.logger.debug(
"[DemoMiddleware] my_custom_value: {s}, response status: {s}",
.{ self.my_custom_value, @tagName(response.status_code) },
);
}
/// Invoked after `afterRequest` is called, use this function to do any clean-up.
/// Note that `request.allocator` is an arena allocator, so any allocations are automatically
/// done before the next request starts processing.
pub fn deinit(self: *Self, request: *jetzig.http.Request) void {
request.allocator.destroy(self);
}

View File

@ -0,0 +1,43 @@
const jetzig = @import("jetzig");
/// `src/app/views/root.zig` represents the root URL `/`
/// The `index` view function is invoked when when the HTTP verb is `GET`.
/// Other view types are invoked either by passing a resource ID value (e.g. `/1234`) or by using
/// a different HTTP verb:
///
/// GET / => index(request, data)
/// GET /1234 => get(id, request, data)
/// POST / => post(request, data)
/// PUT /1234 => put(id, request, data)
/// PATCH /1234 => patch(id, request, data)
/// DELETE /1234 => delete(id, request, data)
pub fn index(request: *jetzig.Request, data: *jetzig.Data) !jetzig.View {
// The first call to `data.object()` or `data.array()` sets the root response data value.
// JSON requests return a JSON string representation of the root data value.
// Zmpl templates can access all values in the root data value.
var root = try data.object();
// Add a string to the root object.
try root.put("message", data.string("Welcome to Jetzig!"));
// Request params have the same type as a `data.object()` so they can be inserted them
// directly into the response data. Fetch `http://localhost:8080/?message=hello` to set the
// param. JSON data is also accepted when the `content-type: application/json` header is
// present.
const params = try request.params();
if (params.get("message")) |value| {
try root.put("message_param", value);
}
// Set arbitrary response headers as required. `content-type` is automatically assigned for
// HTML, JSON responses.
//
// Static files located in `public/` in the root of your project directory are accessible
// from the root path (e.g. `public/jetzig.png`) is available at `/jetzig.png` and the
// content type is inferred from the extension using MIME types.
try request.response.headers.append("x-example-header", "example header value");
// Render the response and set the response code.
return request.render(.ok);
}

View File

@ -0,0 +1,18 @@
// Renders the `message` response data value.
<h3 class="message text-[#39b54a]">{.message}</h3>
<div><img class="p-3 mx-auto" src="/jetzig.png" /></div>
<div>
<a href="https://github.com/jetzig-framework/zmpl">
<img class="p-3 m-3 mx-auto" src="/zmpl.png" />
</a>
</div>
<div>Visit <a class="font-bold text-[#39b54a]" href="https://jetzig.dev/">jetzig.dev</a> to get started.</div>
<div>Join our Discord server and introduce yourself:</div>
<div>
<a class="font-bold text-[#39b54a]" href="https://discord.gg/eufqssz7X6">https://discord.gg/eufqssz7X6</a>
</div>
</div>

View File

@ -0,0 +1,20 @@
<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="/styles.css" />
</head>
<body>
<div class="text-center pt-10 m-auto">
// If present, renders the `message_param` response data value, add `?message=hello` to the
// URL to see the output:
<h2 class="param text-3xl text-[#f7931e]">{.message_param}</h2>
// Renders `src/app/views/init/_content.zmpl` with the same template data available:
<div>{^init/content}</div>
</div>
</body>
</html>

View File

@ -33,7 +33,9 @@ const Quote = struct {
// Quotes taken from: https://gist.github.com/natebass/b0a548425a73bdf8ea5c618149fe1fce
fn randomQuote(allocator: std.mem.Allocator) !Quote {
const json = try std.fs.cwd().readFileAlloc(allocator, "src/app/config/quotes.json", 12684);
const path = "src/app/config/quotes.json";
const stat = try std.fs.cwd().statFile(path);
const json = try std.fs.cwd().readFileAlloc(allocator, path, stat.size);
const quotes = try std.json.parseFromSlice([]Quote, allocator, json, .{});
return quotes.value[std.crypto.random.intRangeLessThan(usize, 0, quotes.value.len)];
}

View File

@ -4,7 +4,7 @@ pub const jetzig = @import("jetzig");
pub const routes = @import("routes").routes;
pub const jetzig_options = struct {
pub const middleware: []const type = &.{@import("DemoMiddleware.zig")};
pub const middleware: []const type = &.{@import("app/middleware/DemoMiddleware.zig")};
};
pub fn main() !void {

View File

@ -1,79 +0,0 @@
#!/bin/bash
printf 'Enter project name (default: "jetzig-demo"): '
read -a project
if [ -z "${project}" ]
then
project="jetzig-demo"
fi
pwd="$(pwd)"
printf "Enter project parent directory (default: \"${pwd}\"): "
read -a dir
if [ -z "${dir}" ]
then
dir="${pwd}"
fi
set -eu
project_path="${dir}/${project}"
echo
echo "Initializing new project in: ${project_path}"
mkdir -p "${project_path}"
do_exit () {
echo "Error fetching '$1':"
echo "$2"
echo "Exiting."
exit 1
}
remote_base=https://raw.githubusercontent.com/jetzig-framework/jetzig/main/src/init
objects=(
'build.zig'
'build.zig.zon'
'src/main.zig'
'src/app/views/index.zig'
'src/app/views/index.zmpl'
'src/app/views/quotes.zig'
'src/app/views/quotes/get.zmpl'
'src/app/config/quotes.json'
'public/jetzig.png'
'.gitignore'
)
for object in "${objects[@]}"
do
printf "Creating output: ${object} "
url="${remote_base}/${object}"
mkdir -p "$(dirname "${project_path}/${object}")"
set +e
output=$(curl -s --fail --output "${project_path}/${object}" "${url}" 2>&1)
set -e
if (($?))
then
do_exit "${url}" "${output}"
else
echo "✅"
fi
done
sed -i.bak -e "s,%%project_name%%,${project},g" "${project_path}/build.zig"
rm "${project_path}/build.zig.bak"
sed -i.bak -e "s,%%project_name%%,${project},g" "${project_path}/build.zig.zon"
rm "${project_path}/build.zig.zon.bak"
echo
echo "Finished creating new project in: ${project_path}"
echo
echo "Run your new project:"
echo
echo " cd '${project_path}'"
echo ' zig build run'
echo
echo "Welcome to Jetzig. ✈️🦎 "
echo

2
src/init/.gitignore vendored
View File

@ -1,2 +0,0 @@
# Compiled Zmpl views:
src/app/views/**/*.compiled.zig

View File

@ -1,86 +0,0 @@
const std = @import("std");
const jetzig_build = @import("jetzig");
// Although this function looks imperative, note that its job is to
// declaratively construct a build graph that will be executed by an external
// runner.
pub fn build(b: *std.Build) void {
// Standard target options allows the person running `zig build` to choose
// what target to build for. Here we do not override the defaults, which
// means any target is allowed, and the default is native. Other options
// for restricting supported target set are available.
const target = b.standardTargetOptions(.{});
// Standard optimization options allow the person running `zig build` to select
// between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not
// set a preferred release mode, allowing the user to decide how to optimize.
const optimize = b.standardOptimizeOption(.{});
const jetzig_dep = b.dependency("jetzig", .{ .optimize = optimize, .target = target });
const compile_view_step = jetzig_build.CompileViewsStep.create(b, .{ .template_path = "src/app/views/" });
// This declares intent for the library to be installed into the standard
// location when the user invokes the "install" step (the default step when
// running `zig build`).
const exe = b.addExecutable(.{
.name = "%%project_name%%",
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
});
exe.root_module.addImport("jetzig", jetzig_dep.module("jetzig"));
exe.root_module.addImport("zmpl", jetzig_dep.module("zmpl"));
// This declares intent for the executable to be installed into the
// standard location when the user invokes the "install" step (the default
// step when running `zig build`).
b.installArtifact(exe);
exe.step.dependOn(&compile_view_step.step);
// This *creates* a Run step in the build graph, to be executed when another
// step is evaluated that depends on it. The next line below will establish
// such a dependency.
const run_cmd = b.addRunArtifact(exe);
// By making the run step depend on the install step, it will be run from the
// installation directory rather than directly from within the cache directory.
// This is not necessary, however, if the application depends on other installed
// files, this ensures they will be present and in the expected location.
run_cmd.step.dependOn(b.getInstallStep());
// This allows the user to pass arguments to the application in the build
// command itself, like this: `zig build run -- arg1 arg2 etc`
if (b.args) |args| {
run_cmd.addArgs(args);
}
// This creates a build step. It will be visible in the `zig build --help` menu,
// and can be selected like this: `zig build run`
// This will evaluate the `run` step rather than the default, which is "install".
const run_step = b.step("run", "Run the app");
run_step.dependOn(&run_cmd.step);
// Creates a step for unit testing. This only builds the test executable
// but does not run it.
const lib_unit_tests = b.addTest(.{
.root_source_file = .{ .path = "src/root.zig" },
.target = target,
.optimize = optimize,
});
const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
const exe_unit_tests = b.addTest(.{
.root_source_file = .{ .path = "src/main.zig" },
.target = target,
.optimize = optimize,
});
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
// Similar to creating the run step earlier, this exposes a `test` step to
// the `zig build --help` menu, providing a way for the user to request
// running the unit tests.
const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_lib_unit_tests.step);
test_step.dependOn(&run_exe_unit_tests.step);
}

View File

@ -1,14 +0,0 @@
.{
.name = "%%project_name%%",
.version = "0.0.0",
.dependencies = .{
.jetzig = .{
.url = "https://github.com/jetzig-framework/jetzig/archive/0395d00b4b0f9e87a4bfab62c73ac047abb2e5da.tar.gz",
.hash = "122016141c39005c2149b3c6029b702332c346e3305f52376e1edbc5ee0295318e4c",
},
},
.paths = .{
"",
},
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@ -1,11 +0,0 @@
const std = @import("std");
const jetzig = @import("jetzig");
const Request = jetzig.http.Request;
const Data = jetzig.data.Data;
const View = jetzig.views.View;
pub fn index(request: *jetzig.http.Request, data: *jetzig.data.Data) anyerror!jetzig.views.View {
var object = try data.object();
try object.put("message", data.string("Welcome to Jetzig!"));
return request.render(.ok);
}

View File

@ -1,34 +0,0 @@
const std = @import("std");
const jetzig = @import("jetzig");
const Request = jetzig.http.Request;
const Data = jetzig.data.Data;
const View = jetzig.views.View;
pub fn get(id: []const u8, request: *Request, data: *Data) anyerror!View {
var body = try data.object();
const random_quote = try randomQuote(request.allocator);
if (std.mem.eql(u8, id, "random")) {
try body.put("quote", data.string(random_quote.quote));
try body.put("author", data.string(random_quote.author));
} else {
try body.put("quote", data.string("If you can dream it, you can achieve it."));
try body.put("author", data.string("Zig Ziglar"));
}
return request.render(.ok);
}
const Quote = struct {
quote: []const u8,
author: []const u8,
};
// Quotes taken from: https://gist.github.com/natebass/b0a548425a73bdf8ea5c618149fe1fce
fn randomQuote(allocator: std.mem.Allocator) !Quote {
const json = try std.fs.cwd().readFileAlloc(allocator, "src/app/config/quotes.json", 12684);
const quotes = try std.json.parseFromSlice([]Quote, allocator, json, .{});
return quotes.value[std.crypto.random.intRangeLessThan(usize, 0, quotes.value.len)];
}

View File

@ -1,2 +0,0 @@
<div>"{.quote}"</div>
<div><b>--{.author}</b></div>

View File

@ -1,15 +0,0 @@
const std = @import("std");
pub const jetzig = @import("jetzig");
pub const routes = @import("routes").routes;
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer std.debug.assert(gpa.deinit() == .ok);
const allocator = gpa.allocator();
const app = try jetzig.init(allocator);
defer app.deinit();
try app.start(comptime jetzig.route(routes));
}