From d3a3582136187072fa00e5c19d6ea791686cdbdb Mon Sep 17 00:00:00 2001 From: Bob Farrell Date: Sun, 3 Mar 2024 20:48:25 +0000 Subject: [PATCH] Implement init command, get rid of old init script/artifacts Remove old bash script for setting up a new project, do everything in Zig to make it platform agnostic and give us an easy place to add scaffolding commands in future. --- .gitignore | 5 - archive.tar.gz | Bin 0 -> 14909 bytes build.zig | 3 +- build.zig.zon | 8 +- cli/build.zig | 33 ++ cli/build.zig.zon | 15 + cli/cli.zig | 60 +++ cli/compile.zig | 71 ++++ cli/init.zig | 427 +++++++++++++++++++++ demo/build.zig.zon | 2 +- demo/public/styles.css | 10 + demo/src/DemoMiddleware.zig | 26 -- demo/src/app/middleware/DemoMiddleware.zig | 53 +++ demo/src/app/views/init.zig | 43 +++ demo/src/app/views/init/_content.zmpl | 18 + demo/src/app/views/init/index.zmpl | 20 + demo/src/app/views/quotes.zig | 4 +- demo/src/main.zig | 2 +- init.bash | 79 ---- src/init/.gitignore | 2 - src/init/build.zig | 86 ----- src/init/build.zig.zon | 14 - src/init/public/jetzig.png | Bin 22828 -> 0 bytes src/init/src/app/views/index.zig | 11 - src/init/src/app/views/quotes.zig | 34 -- src/init/src/app/views/quotes/get.zmpl | 2 - src/init/src/main.zig | 15 - 27 files changed, 763 insertions(+), 280 deletions(-) create mode 100644 archive.tar.gz create mode 100644 cli/build.zig create mode 100644 cli/build.zig.zon create mode 100644 cli/cli.zig create mode 100644 cli/compile.zig create mode 100644 cli/init.zig delete mode 100644 demo/src/DemoMiddleware.zig create mode 100644 demo/src/app/middleware/DemoMiddleware.zig create mode 100644 demo/src/app/views/init.zig create mode 100644 demo/src/app/views/init/_content.zmpl create mode 100644 demo/src/app/views/init/index.zmpl delete mode 100644 init.bash delete mode 100644 src/init/.gitignore delete mode 100644 src/init/build.zig delete mode 100644 src/init/build.zig.zon delete mode 100644 src/init/public/jetzig.png delete mode 100644 src/init/src/app/views/index.zig delete mode 100644 src/init/src/app/views/quotes.zig delete mode 100644 src/init/src/app/views/quotes/get.zmpl delete mode 100644 src/init/src/main.zig diff --git a/.gitignore b/.gitignore index 7d38e30..05a3a49 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,4 @@ -TODO.md -main -get/ zig-out/ zig-cache/ *.core -src/app/views/**/*.compiled.zig -archive.tar.gz static/ diff --git a/archive.tar.gz b/archive.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..b288267a86f12c187cfc1ac5d785b74539b232cb GIT binary patch literal 14909 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 literal 0 HcmV?d00001 diff --git a/build.zig b/build.zig index 0902c8f..d5c92fc 100644 --- a/build.zig +++ b/build.zig @@ -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 diff --git a/build.zig.zon b/build.zig.zon index 6a6f26e..489696b 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -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", }, }, diff --git a/cli/build.zig b/cli/build.zig new file mode 100644 index 0000000..051e610 --- /dev/null +++ b/cli/build.zig @@ -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); +} diff --git a/cli/build.zig.zon b/cli/build.zig.zon new file mode 100644 index 0000000..9350709 --- /dev/null +++ b/cli/build.zig.zon @@ -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 = .{ + "", + }, +} diff --git a/cli/cli.zig b/cli/cli.zig new file mode 100644 index 0000000..65ef87d --- /dev/null +++ b/cli/cli.zig @@ -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` + \\ + ); + } +} diff --git a/cli/compile.zig b/cli/compile.zig new file mode 100644 index 0000000..b1bb2ef --- /dev/null +++ b/cli/compile.zig @@ -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 }); +} diff --git a/cli/init.zig b/cli/init.zig new file mode 100644 index 0000000..2be0815 --- /dev/null +++ b/cli/init.zig @@ -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", .{}); +} diff --git a/demo/build.zig.zon b/demo/build.zig.zon index 2e43c0f..321158c 100644 --- a/demo/build.zig.zon +++ b/demo/build.zig.zon @@ -1,5 +1,5 @@ .{ - .name = "sandbox-jetzig", + .name = "jetzig-demo", .version = "0.0.0", .minimum_zig_version = "0.12.0", .dependencies = .{ diff --git a/demo/public/styles.css b/demo/public/styles.css index e69de29..1755d47 100644 --- a/demo/public/styles.css +++ b/demo/public/styles.css @@ -0,0 +1,10 @@ +/* Root stylesheet. Load into your Zmpl template with: + * + * + * + */ + +.message { + font-weight: bold; + font-size: 3rem; +} diff --git a/demo/src/DemoMiddleware.zig b/demo/src/DemoMiddleware.zig deleted file mode 100644 index 2209795..0000000 --- a/demo/src/DemoMiddleware.zig +++ /dev/null @@ -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); -} diff --git a/demo/src/app/middleware/DemoMiddleware.zig b/demo/src/app/middleware/DemoMiddleware.zig new file mode 100644 index 0000000..06822c7 --- /dev/null +++ b/demo/src/app/middleware/DemoMiddleware.zig @@ -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); +} diff --git a/demo/src/app/views/init.zig b/demo/src/app/views/init.zig new file mode 100644 index 0000000..0fa146c --- /dev/null +++ b/demo/src/app/views/init.zig @@ -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); +} diff --git a/demo/src/app/views/init/_content.zmpl b/demo/src/app/views/init/_content.zmpl new file mode 100644 index 0000000..08973a1 --- /dev/null +++ b/demo/src/app/views/init/_content.zmpl @@ -0,0 +1,18 @@ +// Renders the `message` response data value. +

{.message}

+ +
+ +
+ +
Visit jetzig.dev to get started.
+
Join our Discord server and introduce yourself:
+ + diff --git a/demo/src/app/views/init/index.zmpl b/demo/src/app/views/init/index.zmpl new file mode 100644 index 0000000..c7c3cb9 --- /dev/null +++ b/demo/src/app/views/init/index.zmpl @@ -0,0 +1,20 @@ + + + + + + + + + + +
+ // If present, renders the `message_param` response data value, add `?message=hello` to the + // URL to see the output: +

{.message_param}

+ + // Renders `src/app/views/init/_content.zmpl` with the same template data available: +
{^init/content}
+
+ + diff --git a/demo/src/app/views/quotes.zig b/demo/src/app/views/quotes.zig index 76eac25..27cace8 100644 --- a/demo/src/app/views/quotes.zig +++ b/demo/src/app/views/quotes.zig @@ -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)]; } diff --git a/demo/src/main.zig b/demo/src/main.zig index 8159618..c73e865 100644 --- a/demo/src/main.zig +++ b/demo/src/main.zig @@ -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 { diff --git a/init.bash b/init.bash deleted file mode 100644 index d2b17dd..0000000 --- a/init.bash +++ /dev/null @@ -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 diff --git a/src/init/.gitignore b/src/init/.gitignore deleted file mode 100644 index 7b36f9d..0000000 --- a/src/init/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# Compiled Zmpl views: -src/app/views/**/*.compiled.zig diff --git a/src/init/build.zig b/src/init/build.zig deleted file mode 100644 index f60f3e4..0000000 --- a/src/init/build.zig +++ /dev/null @@ -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); -} diff --git a/src/init/build.zig.zon b/src/init/build.zig.zon deleted file mode 100644 index a326967..0000000 --- a/src/init/build.zig.zon +++ /dev/null @@ -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 = .{ - "", - }, -} diff --git a/src/init/public/jetzig.png b/src/init/public/jetzig.png deleted file mode 100644 index 314d70c06242be3c69f1c1f5e50d26d3e9eda56a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22828 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"{.quote}" -
--{.author}
diff --git a/src/init/src/main.zig b/src/init/src/main.zig deleted file mode 100644 index a434403..0000000 --- a/src/init/src/main.zig +++ /dev/null @@ -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)); -}