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, 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) { const state = switch (action) {
.on => "1", .on => "1",
.off => "0", .off => "0",
@ -27,13 +27,17 @@ fn sendWemoCommand(url_base: []const u8, action: DeviceAction, allocator: std.me
, .{state}); , .{state});
defer allocator.free(soap_body); 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); defer allocator.free(url);
var client = std.http.Client{ .allocator = allocator }; var client = std.http.Client{ .allocator = allocator };
defer client.deinit(); 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(.{ const res = try client.fetch(.{
.method = .POST, .method = .POST,
.payload = soap_body, .payload = soap_body,
@ -47,21 +51,32 @@ fn sendWemoCommand(url_base: []const u8, action: DeviceAction, allocator: std.me
if (res.status == .ok) { if (res.status == .ok) {
var stdout_writer = std.fs.File.stdout().writer(&.{}); var stdout_writer = std.fs.File.stdout().writer(&.{});
const stdout = &stdout_writer.interface; 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 { } else {
var stderr_writer = std.fs.File.stderr().writer(&.{}); var stderr_writer = std.fs.File.stderr().writer(&.{});
const stderr = &stderr_writer.interface; 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 devices = std.StringHashMap([]const u8).init(allocator);
var stderr_writer = std.fs.File.stderr().writer(&.{}); var stderr_writer = std.fs.File.stderr().writer(&.{});
const stderr = &stderr_writer.interface; const stderr = &stderr_writer.interface;
// Try controlData.json first // 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(); defer file.close();
const content = try file.readToEndAlloc(allocator, 1024 * 1024); 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", "Failed to parse controlData.json: {}. Ignoring controlData.json, looking for devices.txt",
.{err}, .{err},
); );
return loadDevicesFromTxt(allocator); return loadDevicesFromTxt(allocator, bin_dir);
}; };
defer parsed.deinit(); defer parsed.deinit();
@ -94,14 +109,17 @@ fn loadDeviceConfig(allocator: std.mem.Allocator) !std.StringHashMap([]const u8)
return devices; return devices;
} else |_| { } 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); 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 => { error.FileNotFound => {
var stderr_writer = std.fs.File.stderr().writer(&.{}); var stderr_writer = std.fs.File.stderr().writer(&.{});
const stderr = &stderr_writer.interface; const stderr = &stderr_writer.interface;
@ -148,7 +166,7 @@ fn parseAction(action_words: [][]const u8) ?DeviceAction {
return null; 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; if (object_words.len == 0) return null;
var total_size: usize = 0; 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); var allocating_writer = try std.Io.Writer.Allocating.initCapacity(allocator, total_size);
defer allocating_writer.deinit(); defer allocating_writer.deinit();
const writer = &allocating_writer.writer; const writer = &allocating_writer.writer;
for (object_words, 1..) |word, i| { for (0..object_words.len) |removed_words| {
var buf: [256]u8 = undefined; defer allocating_writer.clearRetainingCapacity();
const lower_string = std.ascii.lowerString(&buf, word); const total_words = object_words.len - removed_words;
try writer.print("{s}{s}", .{ lower_string, if (i < object_words.len) " " else "" }); 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.debug(
std.log.warn("Could not find device '{s}'", .{allocating_writer.written()}); "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; return null;
} }
@ -187,7 +220,16 @@ pub fn main() !void {
} }
std.log.debug("loading device config", .{}); 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 {}; stderr.print("Failed to load device configuration: {}\n", .{err}) catch {};
std.process.exit(1); std.process.exit(1);
}; };
@ -201,7 +243,7 @@ pub fn main() !void {
} }
std.log.debug("initializing parser", .{}); 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 {}; stderr.print("Failed to initialize parser: {}\n", .{err}) catch {};
std.process.exit(1); std.process.exit(1);
}; };
@ -232,8 +274,8 @@ pub fn main() !void {
std.debug.print("\n", .{}); std.debug.print("\n", .{});
if (parseAction(action_words)) |action| { if (parseAction(action_words)) |action| {
if (try extractDevice(allocator, object_words, &devices)) |url_base| { if (try extractDevice(allocator, object_words, &devices)) |entry| {
try sendWemoCommand(url_base, action, gpa.allocator()); try sendWemoCommand(entry, action, gpa.allocator());
} else { } else {
try stdout.print("Device not found in configuration\n", .{}); 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(); const opts = c.parse_options_create();
if (opts == null) return error.ParseOptionsCreationFailed; 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_verbosity(opts, 0);
c.parse_options_set_linkage_limit(opts, 100); c.parse_options_set_linkage_limit(opts, 100);
c.parse_options_set_disjunct_cost(opts, 2); c.parse_options_set_disjunct_cost(opts, 2);
c.parse_options_set_min_null_count(opts, 0); c.parse_options_set_min_null_count(opts, 0);
c.parse_options_set_max_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{ return Parser{
.dict = dict, .dict = dict,