4 Commits

4 changed files with 85 additions and 71 deletions

View File

@@ -3,6 +3,10 @@
Have you ever wanted to back up all your .env files in case your hard drive gets Have you ever wanted to back up all your .env files in case your hard drive gets
nuked? `envr` makes it easier. nuked? `envr` makes it easier.
> [!CAUTION]
> The Zig community is quite anti-AI. Please read the [AI Disclaimer](#ai-disclaimer)
> before wasting your time.
`envr` is a binary application that tracks your `.env` files `envr` is a binary application that tracks your `.env` files
in an encyrpted sqlite database. Changes can be effortlessly synced with in an encyrpted sqlite database. Changes can be effortlessly synced with
`envr sync`, and restored with `envr restore`. `envr sync`, and restored with `envr restore`.
@@ -132,3 +136,13 @@ This project is licensed under the [MIT License](./LICENSE).
For issues, feature requests, or questions, please For issues, feature requests, or questions, please
[open an issue](https://github.com/sbrow/envr/issues). [open an issue](https://github.com/sbrow/envr/issues).
## AI Disclaimer
Unless noted here, you can be assured that I have personally written and reviewed
every line of code in this software.
- Many compiler errors that couldn't be solved with a quick google search were
solved by passing errors to AI and transcribing the suggestions.
- The "Pre-Zig" version of this readme was written by AI and then edited by me.
- The Go code was mostly written using opencode, and manually tested by me.

View File

@@ -6,7 +6,8 @@ pub const Command = struct {
name: []const u8, name: []const u8,
short: ?[]const u8 = null, short: ?[]const u8 = null,
long: ?[]const u8 = null, long: ?[]const u8 = null,
subcommands: []const Command = &[0]Command{}, subcommands: []const Command = &.{},
examples: [][]const u8 = &.{},
Type: type, Type: type,
pub fn new(cmd: CommandOptions) Command { pub fn new(cmd: CommandOptions) Command {
@@ -27,10 +28,11 @@ pub const Command = struct {
}; };
} }
pub fn parse(comptime self: @This(), args: []const []const u8) ParseError!self.Type { pub fn parse(comptime self: @This(), args: []const []const u8) self.Type {
if (args.len == 0) { if (args.len == 0) {
return ParseError.InvalidType; return @enumFromInt(0);
} }
const target = args[0]; const target = args[0];
inline for (self.subcommands, 1..) |cmd, idx| { inline for (self.subcommands, 1..) |cmd, idx| {
@@ -39,7 +41,7 @@ pub const Command = struct {
} }
} }
return @enumFromInt(0); return @enumFromInt(self.subcommands.len + 1);
} }
}; };
@@ -54,8 +56,8 @@ const CommandOptions = struct {
subcommands: []const CommandOptions = &[0]CommandOptions{}, subcommands: []const CommandOptions = &[0]CommandOptions{},
fn as_enum(self: @This()) type { fn as_enum(self: @This()) type {
var field_names: [self.subcommands.len + 1][]const u8 = undefined; var field_names: [self.subcommands.len + 2][]const u8 = undefined;
var field_values: [self.subcommands.len + 1]u32 = undefined; var field_values: [self.subcommands.len + 2]u32 = undefined;
field_names[0] = self.name; field_names[0] = self.name;
field_values[0] = 0; field_values[0] = 0;
@@ -65,6 +67,9 @@ const CommandOptions = struct {
field_values[idx] = idx; field_values[idx] = idx;
} }
field_names[self.subcommands.len + 1] = "unknown";
field_values[self.subcommands.len + 1] = self.subcommands.len + 1;
return @Enum( return @Enum(
u32, u32,
.exhaustive, .exhaustive,

View File

@@ -14,20 +14,20 @@ pub fn main(init: std.process.Init) !void {
const args = try init.minimal.args.toSlice(arena); const args = try init.minimal.args.toSlice(arena);
try run(init.io, init.environ_map, args); //catch return fallback_to_go(init.io, arena, args); try run(init.environ_map, init.io, arena, args);
} }
/// Attempt to run the requested command. /// Attempt to run the requested command.
fn run( fn run(
io: Io,
environ_map: *std.process.Environ.Map, environ_map: *std.process.Environ.Map,
io: Io,
arena: std.mem.Allocator,
args: []const [:0]const u8, args: []const [:0]const u8,
) !void { ) !void {
const cmd = try envr.root.parse(args[1..]); const cmd = envr.root.parse(args[1..]);
switch (cmd) { switch (cmd) {
.envr => { .envr, .unknown => {
// TODO: Print help return fallback_to_go(io, arena, args);
return comma.ParseError.InvalidType;
}, },
.version => { .version => {
var stdout_buffer: [1024]u8 = undefined; var stdout_buffer: [1024]u8 = undefined;
@@ -61,64 +61,9 @@ fn deps(
writer: *Io.Writer, writer: *Io.Writer,
path: []const u8, path: []const u8,
) !void { ) !void {
var feats: Features = .{}; const feats: Features = try .scan(io, path);
// try writer.print("path: {s}\n\n", .{path});
var dirs = std.mem.splitScalar(u8, path, std.fs.path.delimiter);
// std.debug.print("feats: {b}\n", .{@as(u2, @bitCast(feats))});
// std.debug.print("all feats: {b}\n", .{@as(u2, @bitCast(Features.all_features))});
loop: while (dirs.next()) |dir| {
// try writer.print("dir: {s}\n", .{dir});
// try writer.flush();
// FIXME: Need to handle this failure
const dirt = try Io.Dir.openDir(Io.Dir.cwd(), io, dir, .{ .follow_symlinks = true, .iterate = true });
defer dirt.close(io);
var dir_paths = dirt.iterate();
while (try dir_paths.next(io)) |file| {
// FIXME: Check if executable
// switch (file.kind) {
// .file, .sym_link => {
if (std.mem.eql(u8, std.fs.path.basename(file.name), "git")) {
feats.git = true;
// try writer.print("file: {s}\n", .{file.name});
// try writer.flush();
if (feats == Features.all_features) {
break :loop;
}
}
if (std.mem.eql(u8, std.fs.path.basename(file.name), "fd")) {
feats.fd = true;
// try writer.print("file: {s}\n", .{file.name});
// try writer.flush();
if (feats == Features.all_features) {
break :loop;
}
}
// },
// else => {},
// }
}
}
// if (path_contains(path, "git")) {
// feats.git = true;
// }
// if (path_contains(path, "fd")) {
// feats.fd = true;
// }
// FIXME: Draw as a table
try writer.print("features: {}", .{feats}); try writer.print("features: {}", .{feats});
try writer.flush(); try writer.flush();
} }
@@ -130,6 +75,41 @@ const Features = packed struct {
.git = true, .git = true,
.fd = true, .fd = true,
}; };
/// Scans your PATH variable for programs.
pub fn scan(io: Io, path: []const u8) !@This() {
var feats: Features = .{};
var dirs = std.mem.splitScalar(u8, path, std.fs.path.delimiter);
loop: while (dirs.next()) |dir| {
const dirt = Io.Dir.openDir(Io.Dir.cwd(), io, dir, .{ .follow_symlinks = true, .iterate = true }) catch continue;
defer dirt.close(io);
var dir_paths = dirt.iterate();
while (try dir_paths.next(io)) |file| {
// FIXME: Check if executable
if (std.mem.eql(u8, std.fs.path.basename(file.name), "git")) {
feats.git = true;
if (feats == Features.all_features) {
break :loop;
}
}
if (std.mem.eql(u8, std.fs.path.basename(file.name), "fd")) {
feats.fd = true;
if (feats == Features.all_features) {
break :loop;
}
}
}
}
return feats;
}
}; };
fn fallback_to_go( fn fallback_to_go(

View File

@@ -8,13 +8,21 @@ const Command = comma.Command;
pub const root: Command = .new(.{ pub const root: Command = .new(.{
.name = "envr", .name = "envr",
.subcommands = &.{ .subcommands = &.{
.{ .name = "deps" }, .{
.name = "deps",
.short = "Check for missing binaries",
.long =
\\envr relies on external binaries for certain functionality.
\\
\\ The deps command reports which binaries are available and which are not."
,
},
.{ .name = "version" }, .{ .name = "version" },
}, },
}); });
test "enum type" { test "enum type" {
const got: root.Type = @enumFromInt(1); const got: root.Type = @enumFromInt(2);
try std.testing.expectEqual(.version, got); try std.testing.expectEqual(.version, got);
} }
@@ -25,3 +33,10 @@ test "parse version" {
try std.testing.expectEqual(.version, cmd); try std.testing.expectEqual(.version, cmd);
} }
test "parse unknown" {
const args = &[_][]const u8{"bad", "value"};
const cmd = root.parse(args);
try std.testing.expectEqual(.unknown, cmd);
}