Changes
4 changed files (+216/-3)
-
-
@@ -21,7 +21,7 @@ const target = b.standardTargetOptions(.{});const optimize = b.standardOptimizeOption(.{}); // Exports Zig API. A project depending on this package imports this module. _ = b.addModule("moo", .{ .root_source_file = b.path("src/lib.zig") }); const module = b.addModule("moo", .{ .root_source_file = b.path("src/lib.zig") }); // Tests const test_step = b.step("test", "Run unit tests");
-
@@ -32,4 +32,35 @@ .target = target,.optimize = optimize, }); test_step.dependOn(&b.addRunArtifact(unit_tests).step); // Zig example // This is not a command due to steps can't trigger lazy dependencies' fetching. // <https://github.com/ziglang/zig/issues/21525> const example = b.option(bool, "example-zig", "Build Zig example program") orelse false; if (example) { const example_zig = b.addExecutable(.{ .name = "example-zig", .root_source_file = b.path("examples/basic.zig"), .target = target, .optimize = optimize, }); example_zig.root_module.addImport("moo", module); if (b.lazyDependency("websocket", .{ .target = target, .optimize = optimize, })) |websocket| { example_zig.root_module.addImport("websocket", websocket.module("websocket")); } if (b.lazyDependency("clap", .{ .target = target, .optimize = optimize, })) |clap| { example_zig.root_module.addImport("clap", clap.module("clap")); } b.installArtifact(example_zig); } }
-
-
build.zig.zon (new)
-
@@ -0,0 +1,36 @@// Copyright 2025 Shota FUJI // // Licensed under the Zero-Clause BSD License or the Apache License, Version 2.0, at your option. // You may not use, copy, modify, or distribute this file except according to those terms. You can // find a copy of the Zero-Clause BSD License at LICENSES/0BSD.txt, and a copy of the Apache License, // Version 2.0 at LICENSES/Apache-2.0.txt. You may also obtain a copy of the Zero-Clause BSD License // at <https://opensource.org/license/0bsd> and a copy of the Apache License, Version 2.0 at // <https://www.apache.org/licenses/LICENSE-2.0> // // SPDX-License-Identifier: 0BSD OR Apache-2.0 .{ .name = .libmoo, .version = "0.0.0", .fingerprint = 0xb49e17e4d2b05a1d, .minimum_zig_version = "0.14.0", .dependencies = .{ // For example program. .websocket = .{ .url = "https://github.com/karlseguin/websocket.zig/archive/c1c53b062eab871b95b70409daadfd6ac3d6df61.zip", .hash = "websocket-0.1.0-ZPISdYBIAwB1yO6AFDHRHLaZSmpdh4Bz4dCmaQUqNNWh", .lazy = true, }, // For example program. .clap = .{ .url = "https://github.com/Hejsil/zig-clap/archive/refs/tags/0.10.0.tar.gz", .hash = "clap-0.10.0-oBajB434AQBDh-Ei3YtoKIRxZacVPF1iSwp3IX_ZB8f0", .lazy = true, }, }, .paths = .{ "build.zig", "build.zig.zon", "src/", "LICENSES/", }, }
-
-
examples/basic.zig (new)
-
@@ -0,0 +1,144 @@// Copyright 2025 Shota FUJI // // Licensed under the Zero-Clause BSD License or the Apache License, Version 2.0, at your option. // You may not use, copy, modify, or distribute this file except according to those terms. You can // find a copy of the Zero-Clause BSD License at LICENSES/0BSD.txt, and a copy of the Apache License, // Version 2.0 at LICENSES/Apache-2.0.txt. You may also obtain a copy of the Zero-Clause BSD License // at <https://opensource.org/license/0bsd> and a copy of the Apache License, Version 2.0 at // <https://www.apache.org/licenses/LICENSE-2.0> // // SPDX-License-Identifier: 0BSD OR Apache-2.0 //! This example code sends info request and displays returned message. //! You have to obtain target Roon Server's IP address and HTTP port. //! An IP address is displayed in Roon's settings page. //! HTTP port can be obtained via Roon Server Discovery. An example program //! in [libsood](https://git.pocka.jp/libsood.git) scans and displays the port. //! Once you got an IP address and HTTP port, supply them via `--host` and //! `--port` arguments. const std = @import("std"); const moo = @import("moo"); const clap = @import("clap"); const websocket = @import("websocket"); const Error = error{ MissingHost, MissingPort, }; pub fn main() !void { var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); defer arena.deinit(); const allocator = arena.allocator(); const params = comptime clap.parseParamsComptime( \\-p, --port <u16> HTTP port of the Roon Server \\-h, --host <str> IPv4 address of the Roon Server \\ ); var diag = clap.Diagnostic{}; var cli = clap.parse(clap.Help, ¶ms, clap.parsers.default, .{ .allocator = allocator, .diagnostic = &diag, }) catch |err| { diag.report(std.io.getStdErr().writer(), err) catch {}; return err; }; defer cli.deinit(); const host = cli.args.host orelse return Error.MissingHost; const port = cli.args.port orelse return Error.MissingPort; try run(allocator, host, port); } fn run(allocator: std.mem.Allocator, host: []const u8, port: u16) !void { var client = try websocket.Client.init(allocator, .{ .port = port, .host = host, }); defer client.deinit(); try client.handshake("/api", .{}); const request = try createRequest(allocator); defer allocator.free(request); try client.writeBin(request); try client.readTimeout(std.time.ms_per_s * 3); while (true) { const response = (try client.read()) orelse { std.debug.print(".", .{}); continue; }; defer client.done(response); try handleResponse(allocator, response.data); try client.close(.{}); break; } } /// Caller owns the returned slice. fn createRequest(allocator: std.mem.Allocator) ![]u8 { const meta = moo.Metadata{ .version = 1, .verb = "REQUEST", .service = "com.roonlabs.registry:1/info", }; const header = moo.WellKnownHeaders{ .content_type = "text/plain", .content_length = 0, .request_id = 1, }; const body = moo.RawBody{ .bytes = "" }; const message_size = meta.getEncodeSize() + header.getEncodeSize() + body.getEncodeSize(); const message = try allocator.alloc(u8, message_size); var fbs = std.io.fixedBufferStream(message); const writer = fbs.writer(); try moo.encode(writer, meta, header, body); return message; } const InfoResponse = struct { core_id: []const u8, display_name: []const u8, display_version: []const u8, }; fn handleResponse(allocator: std.mem.Allocator, message: []const u8) !void { const meta, const meta_ctx = try moo.Metadata.parse(message); const header, const header_ctx = try moo.WellKnownHeaders.parse(message, meta_ctx); const body = try moo.JsonBody(InfoResponse).parse(allocator, message, header_ctx); defer body.deinit(); std.debug.print( \\Metadata: {s} {s} \\Request ID: {d} \\--- \\Core ID: {s} \\Name: {s} \\Version: {s} \\ , .{ meta.verb, meta.service, header.request_id, body.value.core_id, body.value.display_name, body.value.display_version, }, ); }
-
-
-
@@ -950,11 +950,13 @@ arena.* = std.heap.ArenaAllocator.init(allocator);errdefer arena.deinit(); const arena_allocator = arena.allocator(); const parsed = try std.json.parseFromSliceLeaky(T, arena_allocator, bytes, .{}); const value = try arena_allocator.create(T); errdefer arena_allocator.destroy(value); value.* = try std.json.parseFromSliceLeaky(T, arena_allocator, bytes, .{}); return .{ .arena = arena, .value = &parsed, .value = value, }; }
-