-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
45
-
46
-
47
-
48
-
49
-
50
-
51
-
52
-
53
-
54
-
55
-
56
-
57
-
58
-
59
-
60
-
61
-
62
-
63
-
64
-
65
-
66
-
67
-
68
-
69
-
70
-
71
-
72
-
73
-
74
-
75
-
76
-
77
-
78
-
79
-
80
-
81
-
82
-
83
-
84
-
85
-
86
-
87
-
88
-
89
-
90
-
91
-
92
-
93
-
94
-
95
-
96
-
97
-
98
-
99
-
100
-
101
-
102
-
103
-
104
-
105
-
106
-
107
-
108
-
109
-
110
-
111
-
112
-
113
-
114
-
115
-
116
-
117
-
118
-
119
-
120
-
121
-
122
-
123
-
124
-
125
-
126
-
127
-
128
-
129
-
130
-
131
-
132
-
133
-
134
-
135
-
136
-
137
-
138
-
139
-
140
-
141
-
142
-
143
-
144
-
145
-
146
-
147
-
148
-
149
-
150
-
151
-
152
-
153
-
154
-
155
-
156
// Copyright 2025 Shota FUJI
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0
const std = @import("std");
const clap = @import("clap");
const build_config = @import("build_config");
const core = @import("core");
const app = @import("./app.zig");
const ExitCode = @import("./exit.zig").ExitCode;
const server = @import("./commands/server.zig");
const register = @import("./commands/register.zig");
const playback = @import("./commands/playback.zig");
const version = "0.0.0";
pub const std_options = std.Options{
.log_level = .debug,
.logFn = log,
};
var log_level: std.log.Level = .debug;
pub fn log(
comptime level: std.log.Level,
comptime scope: @Type(.enum_literal),
comptime format: []const u8,
args: anytype,
) void {
if (@intFromEnum(level) <= @intFromEnum(log_level)) {
std.log.defaultLog(level, scope, format, args);
}
}
const Commands = enum {
server,
register,
playback,
version,
};
const global_parser = .{
.command = clap.parsers.enumeration(Commands),
.id = clap.parsers.string,
.path = clap.parsers.string,
.level = clap.parsers.enumeration(std.log.Level),
};
const global_params = clap.parseParamsComptime(
\\-h, --help Prints this message to stdout and exits.
\\-l, --log-level <level> Log level to output.
\\-v, --verbose Enables debug logging.
\\-c, --core-id <id> Roon Server's ID.
\\-s, --state <path> Path to state file to load from and save to.
\\<command>
\\Available commands:
\\* version ... Prints version to stdout and exits.
\\* server ... Lists or gets information of Roon Server on network
\\* register ... Register extension to Roon Server and exists.
\\* playback ... Display playback state of a zone.
\\
);
pub fn main() !u8 {
const allocator: std.mem.Allocator, const mem = if (build_config.memcheck) memcheck: {
var da = std.heap.DebugAllocator(.{ .safety = true }){
.backing_allocator = std.heap.c_allocator,
};
break :memcheck .{ da.allocator(), &da };
} else arena: {
var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator);
break :arena .{ arena.allocator(), &arena };
};
defer {
_ = mem.deinit();
}
var iter = try std.process.ArgIterator.initWithAllocator(allocator);
defer iter.deinit();
// Skip process name.
_ = iter.next();
var diag = clap.Diagnostic{};
var res = clap.parseEx(clap.Help, &global_params, global_parser, &iter, .{
.diagnostic = &diag,
.allocator = allocator,
.terminating_positional = 0,
}) catch |err| {
diag.report(std.io.getStdErr().writer(), err) catch {};
return @intFromEnum(ExitCode.incorrect_usage);
};
defer res.deinit();
log_level = res.args.@"log-level" orelse .info;
if (res.args.help > 0) {
clap.help(std.io.getStdOut().writer(), clap.Help, &global_params, .{}) catch {};
return @intFromEnum(ExitCode.ok);
}
const command = res.positionals[0] orelse {
clap.help(std.io.getStdErr().writer(), clap.Help, &global_params, .{}) catch {};
return @intFromEnum(ExitCode.incorrect_usage);
};
switch (command) {
.server => return @intFromEnum(server.run(allocator, &iter)),
.version => {
try std.fmt.format(std.io.getStdOut().writer(), "{s}\n", .{version});
return @intFromEnum(ExitCode.ok);
},
else => |main_command| {
const inputs = app.Inputs.parse(.{
.core_id = res.args.@"core-id",
.file_path = res.args.state,
}) catch |err| {
std.log.err("Invalid usage: {s}", .{@errorName(err)});
clap.help(std.io.getStdErr().writer(), clap.Help, &global_params, .{}) catch {};
return @intFromEnum(ExitCode.incorrect_usage);
};
var plac = app.App.init(allocator, inputs) catch |err| {
std.log.err("Unable to initialize app: {s}", .{@errorName(err)});
return @intFromEnum(switch (err) {
error.OutOfMemory => ExitCode.out_of_memory,
else => ExitCode.not_ok,
});
};
defer plac.deinit();
return switch (main_command) {
.register => @intFromEnum(register.run(allocator, &iter, &plac)),
.playback => @intFromEnum(playback.run(allocator, &iter, &plac)),
// Zig does not support narrowing, thus main_command is still type of `Commands`.
else => @intFromEnum(ExitCode.incorrect_usage),
};
},
}
}