use location of binary as basis for configuration

This commit is contained in:
Emil Lerch 2025-09-20 14:09:22 -07:00
parent 97aaee50af
commit 2216daed43
Signed by: lobo
GPG key ID: A7B62D657EF764F8
2 changed files with 108 additions and 23 deletions

View file

@ -7,7 +7,7 @@ const DeviceAction = enum {
toggle,
};
fn sendWemoCommand(url_base: []const u8, action: DeviceAction, allocator: std.mem.Allocator) !void {
fn sendWemoCommand(device_entry: std.hash_map.StringHashMap([]const u8).Entry, action: DeviceAction, allocator: std.mem.Allocator) !void {
const state = switch (action) {
.on => "1",
.off => "0",
@ -27,13 +27,17 @@ fn sendWemoCommand(url_base: []const u8, action: DeviceAction, allocator: std.me
, .{state});
defer allocator.free(soap_body);
const url = try std.fmt.allocPrint(allocator, "{s}/upnp/control/basicevent1", .{url_base});
const url = try std.fmt.allocPrint(allocator, "{s}/upnp/control/basicevent1", .{device_entry.value_ptr.*});
defer allocator.free(url);
var client = std.http.Client{ .allocator = allocator };
defer client.deinit();
std.log.debug("sending wemo command to {s}, action={s}", .{ url_base, @tagName(action) });
std.log.debug("sending WeMo {s} command to '{s}' at {s}\n", .{
@tagName(action),
device_entry.key_ptr.*,
device_entry.value_ptr.*,
});
const res = try client.fetch(.{
.method = .POST,
.payload = soap_body,
@ -47,21 +51,32 @@ fn sendWemoCommand(url_base: []const u8, action: DeviceAction, allocator: std.me
if (res.status == .ok) {
var stdout_writer = std.fs.File.stdout().writer(&.{});
const stdout = &stdout_writer.interface;
try stdout.print("WeMo command sent: url={s}, Action={s}\n", .{ url_base, @tagName(action) });
try stdout.print("WeMo {s} command sent to '{s}' at {s}\n", .{
@tagName(action),
device_entry.key_ptr.*,
device_entry.value_ptr.*,
});
} else {
var stderr_writer = std.fs.File.stderr().writer(&.{});
const stderr = &stderr_writer.interface;
try stderr.print("WeMo command sent: url={s}, Action={s}\n", .{ url_base, @tagName(action) });
try stderr.print("ERROR: Could not send WeMo {s} command to '{s}' at {s}\n", .{
@tagName(action),
device_entry.key_ptr.*,
device_entry.value_ptr.*,
});
}
}
fn loadDeviceConfig(allocator: std.mem.Allocator) !std.StringHashMap([]const u8) {
fn loadDeviceConfig(allocator: std.mem.Allocator, bin_dir: []const u8) !std.StringHashMap([]const u8) {
var devices = std.StringHashMap([]const u8).init(allocator);
var stderr_writer = std.fs.File.stderr().writer(&.{});
const stderr = &stderr_writer.interface;
// Try controlData.json first
if (std.fs.cwd().openFile("controlData.json", .{})) |file| {
const json_path = try std.fs.path.join(allocator, &[_][]const u8{ bin_dir, "controlData.json" });
defer allocator.free(json_path);
if (std.fs.openFileAbsolute(json_path, .{})) |file| {
defer file.close();
const content = try file.readToEndAlloc(allocator, 1024 * 1024);
@ -72,7 +87,7 @@ fn loadDeviceConfig(allocator: std.mem.Allocator) !std.StringHashMap([]const u8)
"Failed to parse controlData.json: {}. Ignoring controlData.json, looking for devices.txt",
.{err},
);
return loadDevicesFromTxt(allocator);
return loadDevicesFromTxt(allocator, bin_dir);
};
defer parsed.deinit();
@ -94,14 +109,17 @@ fn loadDeviceConfig(allocator: std.mem.Allocator) !std.StringHashMap([]const u8)
return devices;
} else |_| {
return loadDevicesFromTxt(allocator);
return loadDevicesFromTxt(allocator, bin_dir);
}
}
fn loadDevicesFromTxt(allocator: std.mem.Allocator) !std.StringHashMap([]const u8) {
fn loadDevicesFromTxt(allocator: std.mem.Allocator, bin_dir: []const u8) !std.StringHashMap([]const u8) {
var devices = std.StringHashMap([]const u8).init(allocator);
const file = std.fs.cwd().openFile("devices.txt", .{}) catch |err| switch (err) {
const txt_path = try std.fs.path.join(allocator, &[_][]const u8{ bin_dir, "devices.txt" });
defer allocator.free(txt_path);
const file = std.fs.openFileAbsolute(txt_path, .{}) catch |err| switch (err) {
error.FileNotFound => {
var stderr_writer = std.fs.File.stderr().writer(&.{});
const stderr = &stderr_writer.interface;
@ -148,7 +166,7 @@ fn parseAction(action_words: [][]const u8) ?DeviceAction {
return null;
}
fn extractDevice(allocator: std.mem.Allocator, object_words: [][]const u8, devices: *std.StringHashMap([]const u8)) !?[]const u8 {
fn extractDevice(allocator: std.mem.Allocator, object_words: [][]const u8, devices: *std.StringHashMap([]const u8)) !?std.hash_map.StringHashMap([]const u8).Entry {
if (object_words.len == 0) return null;
var total_size: usize = 0;
@ -157,14 +175,29 @@ fn extractDevice(allocator: std.mem.Allocator, object_words: [][]const u8, devic
var allocating_writer = try std.Io.Writer.Allocating.initCapacity(allocator, total_size);
defer allocating_writer.deinit();
const writer = &allocating_writer.writer;
for (object_words, 1..) |word, i| {
var buf: [256]u8 = undefined;
const lower_string = std.ascii.lowerString(&buf, word);
try writer.print("{s}{s}", .{ lower_string, if (i < object_words.len) " " else "" });
}
for (0..object_words.len) |removed_words| {
defer allocating_writer.clearRetainingCapacity();
const total_words = object_words.len - removed_words;
for (object_words, 1..) |word, i| {
if (i >= total_words + 1) break;
var buf: [256]u8 = undefined;
const lower_string = std.ascii.lowerString(&buf, word);
try writer.print("{s}{s}", .{
lower_string,
if (i < object_words.len - removed_words) " " else "",
});
}
if (devices.get(allocating_writer.written())) |url_base| return url_base;
std.log.warn("Could not find device '{s}'", .{allocating_writer.written()});
std.log.debug(
"Attempting device match '{s}'",
.{allocating_writer.written()},
);
if (devices.getEntry(allocating_writer.written())) |entry| return entry;
std.log.info(
"Could not find device match '{s}'. Falling back to more general object",
.{allocating_writer.written()},
);
}
return null;
}
@ -187,7 +220,16 @@ pub fn main() !void {
}
std.log.debug("loading device config", .{});
var devices = loadDeviceConfig(gpa.allocator()) catch |err| {
// Get binary directory
const bin_path = std.fs.selfExePathAlloc(gpa.allocator()) catch |err| {
stderr.print("Failed to get binary path: {}\n", .{err}) catch {};
std.process.exit(1);
};
defer gpa.allocator().free(bin_path);
const bin_dir = std.fs.path.dirname(bin_path) orelse ".";
var devices = loadDeviceConfig(gpa.allocator(), bin_dir) catch |err| {
stderr.print("Failed to load device configuration: {}\n", .{err}) catch {};
std.process.exit(1);
};
@ -201,7 +243,7 @@ pub fn main() !void {
}
std.log.debug("initializing parser", .{});
var parser = pos.Parser.init(gpa.allocator()) catch |err| {
var parser = pos.Parser.initWithDataDir(gpa.allocator(), bin_dir) catch |err| {
stderr.print("Failed to initialize parser: {}\n", .{err}) catch {};
std.process.exit(1);
};
@ -232,8 +274,8 @@ pub fn main() !void {
std.debug.print("\n", .{});
if (parseAction(action_words)) |action| {
if (try extractDevice(allocator, object_words, &devices)) |url_base| {
try sendWemoCommand(url_base, action, gpa.allocator());
if (try extractDevice(allocator, object_words, &devices)) |entry| {
try sendWemoCommand(entry, action, gpa.allocator());
} else {
try stdout.print("Device not found in configuration\n", .{});
}

View file

@ -115,11 +115,54 @@ pub const Parser = struct {
const opts = c.parse_options_create();
if (opts == null) return error.ParseOptionsCreationFailed;
setOptions(opts);
return Parser{
.dict = dict,
.opts = opts,
.allocator = allocator,
};
}
fn setOptions(opts: anytype) void {
c.parse_options_set_verbosity(opts, 0);
c.parse_options_set_linkage_limit(opts, 100);
c.parse_options_set_disjunct_cost(opts, 2);
c.parse_options_set_min_null_count(opts, 0);
c.parse_options_set_max_null_count(opts, 0);
}
pub fn initWithDataDir(allocator: std.mem.Allocator, data_dir: []const u8) !Parser {
const dict_path = try std.fs.path.join(allocator, &[_][]const u8{ data_dir, "data/4.0.dict" });
defer allocator.free(dict_path);
const knowledge_path = try std.fs.path.join(allocator, &[_][]const u8{ data_dir, "data/4.0.knowledge" });
defer allocator.free(knowledge_path);
const constituent_path = try std.fs.path.join(allocator, &[_][]const u8{ data_dir, "data/4.0.constituent-knowledge" });
defer allocator.free(constituent_path);
const affix_path = try std.fs.path.join(allocator, &[_][]const u8{ data_dir, "data/4.0.affix" });
defer allocator.free(affix_path);
const dict_cstr = try allocator.dupeZ(u8, dict_path);
defer allocator.free(dict_cstr);
const knowledge_cstr = try allocator.dupeZ(u8, knowledge_path);
defer allocator.free(knowledge_cstr);
const constituent_cstr = try allocator.dupeZ(u8, constituent_path);
defer allocator.free(constituent_cstr);
const affix_cstr = try allocator.dupeZ(u8, affix_path);
defer allocator.free(affix_cstr);
const dict = c.dictionary_create(
@ptrCast(@constCast(dict_cstr.ptr)),
@ptrCast(@constCast(knowledge_cstr.ptr)),
@ptrCast(@constCast(constituent_cstr.ptr)),
@ptrCast(@constCast(affix_cstr.ptr)),
);
if (dict == null) return error.DictionaryCreationFailed;
const opts = c.parse_options_create();
if (opts == null) return error.ParseOptionsCreationFailed;
setOptions(opts);
return Parser{
.dict = dict,