233 lines
7.4 KiB
Zig
233 lines
7.4 KiB
Zig
const std = @import("std");
|
|
const kf = @import("known-folders");
|
|
const ini = @import("ini");
|
|
const c = @import("ffi.zig").c;
|
|
|
|
pub var global_config: ?*Config = null;
|
|
|
|
pub const Config = struct {
|
|
shutdown_command: []const []const u8 = &[_][]const u8{ "systemctl", "poweroff" },
|
|
reboot_command: []const []const u8 = &[_][]const u8{ "systemctl", "reboot" },
|
|
suspend_command: []const []const u8 = &[_][]const u8{ "systemctl", "suspend" },
|
|
hibernate_command: []const []const u8 = &[_][]const u8{ "systemctl", "hibernate" },
|
|
|
|
alloc: std.mem.Allocator,
|
|
command_arena: std.heap.ArenaAllocator,
|
|
|
|
pub fn parse(alloc: std.mem.Allocator) !Config {
|
|
var self = Config{
|
|
.alloc = alloc,
|
|
.command_arena = std.heap.ArenaAllocator.init(alloc),
|
|
};
|
|
|
|
var config_dir = (kf.open(
|
|
alloc,
|
|
.roaming_configuration,
|
|
.{},
|
|
) catch return self) orelse return self;
|
|
defer config_dir.close();
|
|
|
|
const file = config_dir.openFile("gpower2/config.ini", .{}) catch |err| {
|
|
std.log.warn("Failed to open config file ({e}). Skipping config!", .{err});
|
|
return self;
|
|
};
|
|
|
|
var parser = ini.parse(std.heap.c_allocator, file.reader());
|
|
defer parser.deinit();
|
|
|
|
var current_section: ?[]const u8 = null;
|
|
while (try parser.next()) |rec| {
|
|
switch (rec) {
|
|
.section => |sec| {
|
|
if (current_section) |prev| {
|
|
std.heap.c_allocator.free(prev);
|
|
}
|
|
current_section = try std.heap.c_allocator.dupe(u8, sec);
|
|
},
|
|
.property => |kv| {
|
|
if (current_section == null or
|
|
!std.mem.eql(u8, "commands", current_section.?))
|
|
{
|
|
std.log.err(
|
|
\\Config contained property outside of 'commands' section!
|
|
,
|
|
.{},
|
|
);
|
|
return error.PropertyOutsideCommandSection;
|
|
}
|
|
|
|
if (std.mem.eql(u8, "shutdown", kv.key)) {
|
|
self.shutdown_command = try CommandParser.parse(
|
|
kv.value,
|
|
self.command_arena.allocator(),
|
|
);
|
|
} else if (std.mem.eql(u8, "reboot", kv.key)) {
|
|
self.reboot_command = try CommandParser.parse(
|
|
kv.value,
|
|
self.command_arena.allocator(),
|
|
);
|
|
} else if (std.mem.eql(u8, "suspend", kv.key)) {
|
|
self.suspend_command = try CommandParser.parse(
|
|
kv.value,
|
|
self.command_arena.allocator(),
|
|
);
|
|
} else if (std.mem.eql(u8, "hibernate", kv.key)) {
|
|
self.hibernate_command = try CommandParser.parse(
|
|
kv.value,
|
|
self.command_arena.allocator(),
|
|
);
|
|
} else {
|
|
std.log.err("Unknown config property '{s}'!", .{kv.key});
|
|
return error.UnknownProperty;
|
|
}
|
|
},
|
|
.enumeration => {
|
|
std.log.err(
|
|
\\Found enumeration in config file!
|
|
\\Only sections and properties are allowed!
|
|
,
|
|
.{},
|
|
);
|
|
return error.EnumerationInConfig;
|
|
},
|
|
}
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
pub fn deinit(self: *Config) void {
|
|
self.command_arena.deinit();
|
|
self.* = undefined;
|
|
}
|
|
};
|
|
|
|
const CommandParser = struct {
|
|
const State = enum {
|
|
default,
|
|
single_quote,
|
|
double_quote,
|
|
};
|
|
|
|
str: []const u8,
|
|
offset: usize = 0,
|
|
state: State = .default,
|
|
component: std.ArrayList(u8),
|
|
|
|
/// Parses a command and returns the argv array, allocated using alloc.
|
|
fn parse(str: []const u8, alloc: std.mem.Allocator) ![][]u8 {
|
|
var parser = init(str, alloc);
|
|
defer parser.deinit();
|
|
|
|
var out = std.ArrayList([]u8).init(alloc);
|
|
errdefer out.deinit();
|
|
|
|
while (try parser.next()) |part| {
|
|
try out.append(part);
|
|
}
|
|
|
|
return out.toOwnedSlice();
|
|
}
|
|
|
|
fn init(str: []const u8, alloc: std.mem.Allocator) CommandParser {
|
|
return .{
|
|
.str = str,
|
|
.component = std.ArrayList(u8).init(alloc),
|
|
};
|
|
}
|
|
|
|
fn deinit(self: *CommandParser) void {
|
|
self.component.deinit();
|
|
self.* = undefined;
|
|
}
|
|
|
|
/// Parses the next argv item.
|
|
/// The returned slice will be owned by the provided alloc.
|
|
/// Returns null if there are no more items left.
|
|
fn next(self: *CommandParser) !?[]u8 {
|
|
if (self.offset >= self.str.len)
|
|
return null;
|
|
|
|
self.component.clearRetainingCapacity();
|
|
|
|
while (self.offset < self.str.len) {
|
|
const ch = self.str[self.offset];
|
|
self.offset += 1;
|
|
|
|
switch (self.state) {
|
|
.default => {
|
|
if (std.ascii.isSpace(ch)) {
|
|
// double space safety
|
|
if (self.component.items.len > 0)
|
|
break;
|
|
continue;
|
|
}
|
|
|
|
switch (ch) {
|
|
'\'' => self.state = .single_quote,
|
|
'\"' => self.state = .double_quote,
|
|
'\\' => try self.escapeSeq(),
|
|
else => try self.component.append(ch),
|
|
}
|
|
},
|
|
|
|
.single_quote => switch (ch) {
|
|
'\'' => self.state = .default,
|
|
'\\' => try self.escapeSeq(),
|
|
else => try self.component.append(ch),
|
|
},
|
|
|
|
.double_quote => switch (ch) {
|
|
'\"' => self.state = .default,
|
|
'\\' => try self.escapeSeq(),
|
|
else => try self.component.append(ch),
|
|
},
|
|
}
|
|
}
|
|
|
|
if (self.state != .default) {
|
|
return error.UnclosedDelimeter;
|
|
}
|
|
|
|
return try self.component.allocator.dupe(u8, self.component.items);
|
|
}
|
|
|
|
fn escapeSeq(self: *CommandParser) !void {
|
|
if (self.offset >= self.str.len)
|
|
return error.UnfinishedEscape;
|
|
|
|
const ch = self.str[self.offset];
|
|
self.offset += 1;
|
|
|
|
if (self.state == .single_quote) {
|
|
if (ch != '\'')
|
|
try self.component.append('\\');
|
|
try self.component.append(ch);
|
|
} else {
|
|
try self.component.append(ch);
|
|
}
|
|
}
|
|
};
|
|
|
|
fn assertParserNext(p: *CommandParser, expected: []const u8) !void {
|
|
const n = try p.next();
|
|
try std.testing.expect(n != null);
|
|
|
|
defer p.component.allocator.free(n.?);
|
|
|
|
try std.testing.expectEqualStrings(expected, n.?);
|
|
}
|
|
|
|
test "CommandParser" {
|
|
var p1 = CommandParser.init(
|
|
\\"foo\" "bar 'baz\'' \\
|
|
,
|
|
std.testing.allocator,
|
|
);
|
|
defer p1.deinit();
|
|
|
|
try assertParserNext(&p1, "foo\" bar");
|
|
try assertParserNext(&p1, "baz'");
|
|
try assertParserNext(&p1, "\\");
|
|
}
|