mirror of
https://mzte.de/git/LordMZTE/dotfiles.git
synced 2024-12-14 09:13:49 +01:00
feat: add alecor script
This commit is contained in:
parent
aec200b925
commit
975b823e3b
8 changed files with 473 additions and 0 deletions
1
.config/fish/conf.d/50-alecor.fish
Normal file
1
.config/fish/conf.d/50-alecor.fish
Normal file
|
@ -0,0 +1 @@
|
||||||
|
alecor printfish | source
|
1
scripts/alecor/.gitignore
vendored
Normal file
1
scripts/alecor/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
/zig-*
|
25
scripts/alecor/build.zig
Normal file
25
scripts/alecor/build.zig
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn build(b: *std.Build) void {
|
||||||
|
const target = b.standardTargetOptions(.{});
|
||||||
|
const optimize = b.standardOptimizeOption(.{});
|
||||||
|
|
||||||
|
const exe = b.addExecutable(.{
|
||||||
|
.name = "alecor",
|
||||||
|
.root_source_file = .{ .path = "src/main.zig" },
|
||||||
|
.target = target,
|
||||||
|
.optimize = optimize,
|
||||||
|
});
|
||||||
|
|
||||||
|
b.installArtifact(exe);
|
||||||
|
|
||||||
|
const run_cmd = b.addRunArtifact(exe);
|
||||||
|
run_cmd.step.dependOn(b.getInstallStep());
|
||||||
|
|
||||||
|
if (b.args) |args| {
|
||||||
|
run_cmd.addArgs(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
const run_step = b.step("run", "Run the app");
|
||||||
|
run_step.dependOn(&run_cmd.step);
|
||||||
|
}
|
60
scripts/alecor/src/cache.zig
Normal file
60
scripts/alecor/src/cache.zig
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
pub fn commandsCachePath(alloc: std.mem.Allocator) ![]const u8 {
|
||||||
|
return try std.fs.path.join(alloc, &.{
|
||||||
|
std.os.getenv("HOME") orelse return error.HomeNotSet,
|
||||||
|
".cache",
|
||||||
|
"alecor",
|
||||||
|
"commands",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate(alloc: std.mem.Allocator) !void {
|
||||||
|
const cache_path = try commandsCachePath(alloc);
|
||||||
|
defer alloc.free(cache_path);
|
||||||
|
|
||||||
|
if (std.fs.path.dirname(cache_path)) |cache_dir| {
|
||||||
|
try std.fs.cwd().makePath(cache_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
var cache_file = try std.fs.cwd().createFile(cache_path, .{});
|
||||||
|
defer cache_file.close();
|
||||||
|
|
||||||
|
const pipefds = try std.os.pipe();
|
||||||
|
defer std.os.close(pipefds[0]);
|
||||||
|
|
||||||
|
var stdout_buf_reader = std.io.bufferedReader((std.fs.File{ .handle = pipefds[0] }).reader());
|
||||||
|
|
||||||
|
// ChildProcess being useless again...
|
||||||
|
const pid = try std.os.fork();
|
||||||
|
if (pid == 0) {
|
||||||
|
errdefer std.os.exit(1);
|
||||||
|
try std.os.dup2(pipefds[1], 1);
|
||||||
|
std.os.close(pipefds[0]);
|
||||||
|
std.os.close(pipefds[1]);
|
||||||
|
return std.os.execvpeZ(
|
||||||
|
"fish",
|
||||||
|
&[_:null]?[*:0]const u8{ "fish", "-c", "complete -C ''" },
|
||||||
|
@ptrCast(std.os.environ.ptr),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
std.os.close(pipefds[1]);
|
||||||
|
|
||||||
|
var cmd_buf: [1024]u8 = undefined;
|
||||||
|
var fbs = std.io.fixedBufferStream(&cmd_buf);
|
||||||
|
while (true) {
|
||||||
|
fbs.reset();
|
||||||
|
stdout_buf_reader.reader().streamUntilDelimiter(fbs.writer(), '\n', null) catch |e| switch (e) {
|
||||||
|
error.EndOfStream => break,
|
||||||
|
else => return e,
|
||||||
|
};
|
||||||
|
|
||||||
|
// FBS will have <cmd>\tgarbage here
|
||||||
|
var spliter = std.mem.tokenize(u8, fbs.getWritten(), "\t");
|
||||||
|
try cache_file.writeAll(spliter.next() orelse continue);
|
||||||
|
try cache_file.writer().writeByte('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = std.os.waitpid(pid, 0);
|
||||||
|
}
|
217
scripts/alecor/src/correct.zig
Normal file
217
scripts/alecor/src/correct.zig
Normal file
|
@ -0,0 +1,217 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const util = @import("util.zig");
|
||||||
|
|
||||||
|
/// Commands which are prioritized for correction
|
||||||
|
const priority_commands = [_][]const u8{
|
||||||
|
"git",
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Commands which wrap another
|
||||||
|
const wrapper_commands = std.ComptimeStringMap(void, .{
|
||||||
|
.{ "doas", {} },
|
||||||
|
.{ "sudo", {} },
|
||||||
|
.{ "rbg", {} },
|
||||||
|
.{ "rbgd", {} },
|
||||||
|
.{ "pkexec", {} },
|
||||||
|
});
|
||||||
|
|
||||||
|
fn populateArgMap(map: *ArgMap) !void {
|
||||||
|
try map.put(&.{"git"}, .{ .subcommand = &.{ "push", "pull", "reset", "checkout" } });
|
||||||
|
try map.put(&.{ "git", "checkout" }, .file_or_directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ArgRequirement = union(enum) {
|
||||||
|
subcommand: []const []const u8,
|
||||||
|
file,
|
||||||
|
directory,
|
||||||
|
file_or_directory,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ArgMap = std.HashMap(
|
||||||
|
[]const []const u8,
|
||||||
|
ArgRequirement,
|
||||||
|
struct {
|
||||||
|
pub fn hash(self: @This(), v: []const []const u8) u64 {
|
||||||
|
_ = self;
|
||||||
|
var hasher = std.hash.Wyhash.init(0);
|
||||||
|
hasher.update(std.mem.asBytes(&v.len));
|
||||||
|
for (v) |s| {
|
||||||
|
hasher.update(std.mem.asBytes(&v.len));
|
||||||
|
hasher.update(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasher.final();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eql(self: @This(), a: []const []const u8, b: []const []const u8) bool {
|
||||||
|
_ = self;
|
||||||
|
if (a.len != b.len)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (a, b) |va, vb|
|
||||||
|
if (!std.mem.eql(u8, va, vb))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
std.hash_map.default_max_load_percentage,
|
||||||
|
);
|
||||||
|
|
||||||
|
pub fn correctCommand(
|
||||||
|
arena: *std.heap.ArenaAllocator,
|
||||||
|
/// Command to correct in-place
|
||||||
|
cmd: [][]const u8,
|
||||||
|
/// Set of all valid commands
|
||||||
|
commands: *std.StringHashMap(void),
|
||||||
|
) !void {
|
||||||
|
const alloc = arena.child_allocator;
|
||||||
|
|
||||||
|
var subslice = cmd;
|
||||||
|
|
||||||
|
// skip wrapper commands
|
||||||
|
while (subslice.len > 0 and wrapper_commands.has(subslice[0]))
|
||||||
|
subslice = subslice[1..];
|
||||||
|
|
||||||
|
// empty command
|
||||||
|
if (subslice.len == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!commands.contains(subslice[0])) {
|
||||||
|
// correct command
|
||||||
|
var best: ?struct { []const u8, usize } = null;
|
||||||
|
|
||||||
|
// do priority commands first and sub 1 from distance
|
||||||
|
for (priority_commands) |possible_cmd| {
|
||||||
|
const dist = try util.dist(alloc, subslice[0], possible_cmd) -| 1; // prioritize by subtracting 1
|
||||||
|
if (best == null or best.?.@"1" > dist)
|
||||||
|
best = .{ possible_cmd, dist };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (best != null and best.?.@"1" != 0) {
|
||||||
|
var iter = commands.keyIterator();
|
||||||
|
while (iter.next()) |possible_cmd| {
|
||||||
|
const dist = try util.dist(alloc, subslice[0], possible_cmd.*);
|
||||||
|
if (best == null or best.?.@"1" > dist)
|
||||||
|
best = .{ possible_cmd.*, dist };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (best) |b| {
|
||||||
|
if (!std.mem.eql(u8, subslice[0], b.@"0")) {
|
||||||
|
std.log.info("[C] {s} => {s}", .{ subslice[0], b.@"0" });
|
||||||
|
subslice[0] = b.@"0";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subslice.len < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var arg_map = ArgMap.init(alloc);
|
||||||
|
defer arg_map.deinit();
|
||||||
|
try populateArgMap(&arg_map);
|
||||||
|
|
||||||
|
// correct args. loop as long as corrections are made
|
||||||
|
while (true) {
|
||||||
|
var req: ArgRequirement = .file_or_directory;
|
||||||
|
|
||||||
|
var cmd_slice_end = subslice.len - 1;
|
||||||
|
|
||||||
|
while (cmd_slice_end > 1) : (cmd_slice_end -= 1) {
|
||||||
|
if (arg_map.get(subslice[0..cmd_slice_end])) |r| {
|
||||||
|
req = r;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var new_arg = subslice[cmd_slice_end];
|
||||||
|
try correctArgForReq(arena, req, &new_arg);
|
||||||
|
if (!std.mem.eql(u8, subslice[cmd_slice_end], new_arg)) {
|
||||||
|
std.log.info("[A] {s} => {s}", .{ subslice[cmd_slice_end], new_arg });
|
||||||
|
subslice[cmd_slice_end] = new_arg;
|
||||||
|
} else break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn correctArgForReq(arena: *std.heap.ArenaAllocator, req: ArgRequirement, arg: *[]const u8) !void {
|
||||||
|
const alloc = arena.child_allocator;
|
||||||
|
switch (req) {
|
||||||
|
.subcommand => |subcmds| {
|
||||||
|
var best: ?struct { []const u8, usize } = null;
|
||||||
|
for (subcmds) |possible_cmd| {
|
||||||
|
const dist = try util.dist(alloc, arg.*, possible_cmd);
|
||||||
|
if (best == null or best.?.@"1" > dist)
|
||||||
|
best = .{ possible_cmd, dist };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (best) |b|
|
||||||
|
arg.* = b.@"0";
|
||||||
|
},
|
||||||
|
.file, .directory, .file_or_directory => {
|
||||||
|
if (arg.len == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var path_spliter = std.mem.tokenize(u8, arg.*, "/");
|
||||||
|
var path_splits = std.ArrayList([]const u8).init(alloc);
|
||||||
|
defer path_splits.deinit();
|
||||||
|
|
||||||
|
// path is absolute
|
||||||
|
if (arg.*[0] == '/')
|
||||||
|
try path_splits.append("/");
|
||||||
|
|
||||||
|
while (path_spliter.next()) |split| {
|
||||||
|
if (std.mem.eql(u8, split, "~")) {
|
||||||
|
try path_splits.append(std.os.getenv("HOME") orelse return error.HomeNotSet);
|
||||||
|
} else {
|
||||||
|
try path_splits.append(split);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (path_splits.items, 0..) |*split, cur_idx| {
|
||||||
|
const dirs = path_splits.items[0..cur_idx];
|
||||||
|
const dir_subpath = try std.fs.path.join(arena.allocator(), if (dirs.len == 0) &.{"."} else dirs);
|
||||||
|
|
||||||
|
var iterable_dir = try std.fs.cwd().openIterableDir(dir_subpath, .{});
|
||||||
|
defer iterable_dir.close();
|
||||||
|
|
||||||
|
// if the given file already exists, there's no point in iterating the dir
|
||||||
|
if (iterable_dir.dir.statFile(split.*)) |_| continue else |e| switch (e) {
|
||||||
|
error.FileNotFound => {},
|
||||||
|
else => return e,
|
||||||
|
}
|
||||||
|
|
||||||
|
var best: ?struct { []const u8, usize } = null;
|
||||||
|
|
||||||
|
var dir_iter = iterable_dir.iterate();
|
||||||
|
|
||||||
|
var best_buf: [1024]u8 = undefined;
|
||||||
|
|
||||||
|
while (try dir_iter.next()) |entry| {
|
||||||
|
switch (req) {
|
||||||
|
.file => if (entry.kind == .directory) continue,
|
||||||
|
.directory => if (entry.kind != .directory and
|
||||||
|
entry.kind != .sym_link) continue,
|
||||||
|
else => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
const dist = try util.dist(alloc, split.*, entry.name);
|
||||||
|
if (best == null or best.?.@"1" > dist) {
|
||||||
|
if (entry.name.len > best_buf.len)
|
||||||
|
return error.OutOfMemory;
|
||||||
|
const buf_slice = best_buf[0..entry.name.len];
|
||||||
|
@memcpy(buf_slice, entry.name);
|
||||||
|
best = .{ buf_slice, dist };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (best) |b| {
|
||||||
|
split.* = try arena.allocator().dupe(u8, b.@"0");
|
||||||
|
} else break;
|
||||||
|
}
|
||||||
|
|
||||||
|
arg.* = try std.fs.path.join(arena.allocator(), path_splits.items);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
68
scripts/alecor/src/main.zig
Normal file
68
scripts/alecor/src/main.zig
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
const cache = @import("cache.zig");
|
||||||
|
const util = @import("util.zig");
|
||||||
|
|
||||||
|
pub fn main() !void {
|
||||||
|
if (std.os.argv.len < 2)
|
||||||
|
return error.NotEnoughArguments;
|
||||||
|
|
||||||
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
||||||
|
defer _ = gpa.deinit();
|
||||||
|
const alloc = gpa.allocator();
|
||||||
|
|
||||||
|
const subcmd = std.mem.span(std.os.argv[1]);
|
||||||
|
|
||||||
|
if (std.mem.eql(u8, subcmd, "doalec")) {
|
||||||
|
if (std.os.argv.len < 3)
|
||||||
|
return error.NotEnoughArguments;
|
||||||
|
|
||||||
|
var args = std.ArrayList([]const u8).init(alloc);
|
||||||
|
defer args.deinit();
|
||||||
|
|
||||||
|
var spliter = std.mem.tokenize(u8, std.mem.span(std.os.argv[2]), "\n");
|
||||||
|
while (spliter.next()) |arg|
|
||||||
|
try args.append(arg);
|
||||||
|
|
||||||
|
// open and map cache
|
||||||
|
const cache_path = try cache.commandsCachePath(alloc);
|
||||||
|
defer alloc.free(cache_path);
|
||||||
|
|
||||||
|
var cache_file = try std.fs.cwd().openFile(cache_path, .{});
|
||||||
|
defer cache_file.close();
|
||||||
|
|
||||||
|
const cache_content = try std.os.mmap(
|
||||||
|
null,
|
||||||
|
(try cache_file.stat()).size,
|
||||||
|
std.os.PROT.READ,
|
||||||
|
std.os.MAP.PRIVATE,
|
||||||
|
cache_file.handle,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
defer std.os.munmap(cache_content);
|
||||||
|
|
||||||
|
var command_set = std.StringHashMap(void).init(alloc);
|
||||||
|
defer command_set.deinit();
|
||||||
|
|
||||||
|
var cache_tok = std.mem.tokenize(u8, cache_content, "\n");
|
||||||
|
while (cache_tok.next()) |tok|
|
||||||
|
if (tok.len != 0)
|
||||||
|
try command_set.put(tok, {});
|
||||||
|
|
||||||
|
var arena = std.heap.ArenaAllocator.init(alloc);
|
||||||
|
defer arena.deinit();
|
||||||
|
|
||||||
|
try @import("correct.zig").correctCommand(&arena, args.items, &command_set);
|
||||||
|
try std.io.getStdOut().writer().print("{}\n", .{util.fmtCommand(args.items)});
|
||||||
|
} else if (std.mem.eql(u8, subcmd, "printfish")) {
|
||||||
|
try std.io.getStdOut().writer().print(
|
||||||
|
\\function alec --description 'ALEC'
|
||||||
|
\\ commandline (builtin history search -n 1)
|
||||||
|
\\ commandline ({s} doalec (commandline -o | string split0))
|
||||||
|
\\end
|
||||||
|
\\
|
||||||
|
, .{std.os.argv[0]});
|
||||||
|
} else if (std.mem.eql(u8, subcmd, "mkcache")) {
|
||||||
|
try cache.generate(alloc);
|
||||||
|
} else return error.UnknownCommand;
|
||||||
|
}
|
100
scripts/alecor/src/util.zig
Normal file
100
scripts/alecor/src/util.zig
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
const std = @import("std");
|
||||||
|
|
||||||
|
/// A 2-dimensional heap-allocated matrix.
|
||||||
|
pub fn Matrix2D(comptime T: type) type {
|
||||||
|
return struct {
|
||||||
|
data: []T,
|
||||||
|
width: usize,
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
|
pub inline fn init(alloc: std.mem.Allocator, height: usize, width: usize) !Self {
|
||||||
|
return .{ .data = try alloc.alloc(T, height * width), .width = width };
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn deinit(self: Self, alloc: std.mem.Allocator) void {
|
||||||
|
alloc.free(self.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub inline fn el(self: *Self, row: usize, col: usize) *T {
|
||||||
|
return &self.data[row * self.width + col];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculates the Damerau-Levenshtein distance between 2 strings
|
||||||
|
pub fn dist(alloc: std.mem.Allocator, a: []const u8, b: []const u8) !usize {
|
||||||
|
var d = try Matrix2D(usize).init(alloc, a.len + 1, b.len + 1);
|
||||||
|
defer d.deinit(alloc);
|
||||||
|
|
||||||
|
@memset(d.data, 0);
|
||||||
|
|
||||||
|
var i: usize = 0;
|
||||||
|
var j: usize = 0;
|
||||||
|
|
||||||
|
while (i <= a.len) : (i += 1) {
|
||||||
|
d.el(i, 0).* = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (j <= b.len) : (j += 1) {
|
||||||
|
d.el(0, j).* = j;
|
||||||
|
}
|
||||||
|
|
||||||
|
i = 1;
|
||||||
|
while (i <= a.len) : (i += 1) {
|
||||||
|
j = 1;
|
||||||
|
while (j <= b.len) : (j += 1) {
|
||||||
|
const cost = @intFromBool(a[i - 1] != b[j - 1]);
|
||||||
|
d.el(i, j).* = @min(
|
||||||
|
d.el(i - 1, j).* + 1, // deletion
|
||||||
|
@min(
|
||||||
|
d.el(i, j - 1).* + 1, // insertion
|
||||||
|
d.el(i - 1, j - 1).* + cost, // substitution
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// transposition
|
||||||
|
if (i > 1 and j > 1 and a[i - 1] == b[j - 2] and a[i - 2] == b[j - 1])
|
||||||
|
d.el(i, j).* = @min(d.el(i, j).*, d.el(i - 2, j - 2).* + cost);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return d.el(a.len, b.len).*;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn formatCommand(
|
||||||
|
cmd: []const []const u8,
|
||||||
|
comptime fmt: []const u8,
|
||||||
|
options: std.fmt.FormatOptions,
|
||||||
|
writer: anytype,
|
||||||
|
) !void {
|
||||||
|
_ = options;
|
||||||
|
_ = fmt;
|
||||||
|
|
||||||
|
var first = true;
|
||||||
|
for (cmd) |arg| {
|
||||||
|
defer first = false;
|
||||||
|
var needs_quote = false;
|
||||||
|
for (arg) |ch| {
|
||||||
|
if (!std.ascii.isPrint(ch) or ch == '\'' or ch == ' ' or ch == '*' or ch == '$') {
|
||||||
|
needs_quote = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!first)
|
||||||
|
try writer.writeByte(' ');
|
||||||
|
|
||||||
|
if (needs_quote) {
|
||||||
|
try writer.writeByte('"');
|
||||||
|
try writer.print("{}", .{std.fmt.fmtSliceEscapeUpper(arg)});
|
||||||
|
try writer.writeByte('"');
|
||||||
|
} else {
|
||||||
|
try writer.writeAll(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fmtCommand(cmd: []const []const u8) std.fmt.Formatter(formatCommand) {
|
||||||
|
return .{ .data = cmd };
|
||||||
|
}
|
|
@ -20,6 +20,7 @@
|
||||||
(mklink "scripts/use-country-mirrors.sh" (bin-path "use-country-mirrors"))
|
(mklink "scripts/use-country-mirrors.sh" (bin-path "use-country-mirrors"))
|
||||||
|
|
||||||
;; Compile scripts
|
;; Compile scripts
|
||||||
|
(install-zig "scripts/alecor")
|
||||||
(install-rust "scripts/i3status")
|
(install-rust "scripts/i3status")
|
||||||
(install-zig "scripts/mzteinit")
|
(install-zig "scripts/mzteinit")
|
||||||
(install-zig "scripts/openbrowser")
|
(install-zig "scripts/openbrowser")
|
||||||
|
|
Loading…
Reference in a new issue