diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..90e7af499d68aa5e8dbd03d86fbad54cf9493ab3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/zig-* +/.zigmod +/deps.zig diff --git a/build.zig b/build.zig new file mode 100644 index 0000000000000000000000000000000000000000..37f4a6db429649571d815104be1d39e333c071d3 --- /dev/null +++ b/build.zig @@ -0,0 +1,23 @@ +const std = @import("std"); +const deps = @import("./deps.zig"); + +pub fn build(b: *std.build.Builder) void { + const target = b.standardTargetOptions(.{}); + + const mode = b.standardReleaseOptions(); + + const exe = b.addExecutable("zig-inquirer", "src/main.zig"); + exe.setTarget(target); + exe.setBuildMode(mode); + deps.addAllTo(exe); + exe.install(); + + const run_cmd = exe.run(); + 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); +} diff --git a/src/lib.zig b/src/lib.zig new file mode 100644 index 0000000000000000000000000000000000000000..e6db6642e5ad068ed10981c3f981897fa125d829 --- /dev/null +++ b/src/lib.zig @@ -0,0 +1,135 @@ +const std = @import("std"); +const ansi = @import("ansi"); +const range = @import("range").range; + +pub fn answer(comptime prompt: []const u8, comptime T: type, comptime valfmt: []const u8, value: T) T { + std.debug.print(comptime ansi.color.Fg(.Green, "? "), .{}); + std.debug.print(comptime ansi.color.Bold(prompt ++ " "), .{}); + std.debug.print(comptime ansi.color.Fg(.Cyan, valfmt ++ "\n"), .{value}); + return value; +} + +const PromptRet = struct { + n: usize, + value: []const u8, +}; + +fn doprompt(in: std.fs.File.Reader, alloc: *std.mem.Allocator, default: ?[]const u8) !PromptRet { + var n: usize = 1; + var value: []const u8 = undefined; + while (true) : (n += 1) { + std.debug.print(comptime ansi.color.Faint("> "), .{}); + const input = try in.readUntilDelimiterAlloc(alloc, '\n', 100); + + if (input.len == 0) if (default) |d| { + value = d; + break; + }; + if (input.len > 0) { + value = input; + break; + } + } + return PromptRet{ .n = n, .value = value }; +} + +fn clean(n: usize) void { + for (range(n)) |_| { + std.debug.print(comptime ansi.csi.CursorUp(1), .{}); + std.debug.print(comptime ansi.csi.EraseInLine(0), .{}); + } +} + +pub fn forEnum(comptime prompt: []const u8, alloc: *std.mem.Allocator, comptime options: type, default: ?options) !options { + comptime std.debug.assert(@typeInfo(options) == .Enum); + + std.debug.print(comptime ansi.color.Fg(.Green, "? "), .{}); + std.debug.print(comptime ansi.color.Bold(prompt ++ " "), .{}); + + std.debug.print(ansi.style.Faint ++ "(", .{}); + inline for (std.meta.fields(options)) |f, i| { + if (i != 0) std.debug.print("/", .{}); + std.debug.print(f.name, .{}); + } + std.debug.print(")" ++ ansi.style.ResetIntensity ++ " ", .{}); + + const stdin = std.io.getStdIn().reader(); + var value: options = undefined; + var i: usize = 0; + while (true) { + const def: ?[]const u8 = if (default) |d| @tagName(d) else null; + const p = try doprompt(stdin, alloc, def); + defer if (!std.mem.eql(u8, p.value, def orelse "")) alloc.free(p.value); + + i += p.n; + if (std.meta.stringToEnum(options, p.value)) |cap| { + value = cap; + break; + } + } + clean(i); + _ = answer(prompt, []const u8, "{s}", @tagName(value)); + + return value; +} + +pub fn forString(comptime prompt: []const u8, alloc: *std.mem.Allocator, default: ?[]const u8) ![]const u8 { + std.debug.print(comptime ansi.color.Fg(.Green, "? "), .{}); + std.debug.print(comptime ansi.color.Bold(prompt ++ " "), .{}); + + if (default != null) { + std.debug.print(ansi.style.Faint ++ "(", .{}); + std.debug.print("{s}", .{default.?}); + std.debug.print(")" ++ ansi.style.ResetIntensity ++ " ", .{}); + } + + const stdin = std.io.getStdIn().reader(); + const p = try doprompt(stdin, alloc, default); + clean(p.n); + return answer(prompt, []const u8, "{s}", p.value); +} + +pub fn forConfirm(comptime prompt: []const u8, alloc: *std.mem.Allocator) !bool { + return (try forEnum(prompt, alloc, enum { y, n }, .y)) == .y; +} + +// pub fn forNumber(comptime prompt: []const u8, alloc: *std.mem.Allocator, comptime T: type, default: ?T) !T { +// std.debug.print(comptime ansi.color.Fg(.Green, "? "), .{}); +// std.debug.print(comptime ansi.color.Bold(prompt ++ " "), .{}); + +// if (default != null) { +// std.debug.print(ansi.style.Faint ++ "(", .{}); +// std.debug.print("{d}", .{default.?}); +// std.debug.print(")" ++ ansi.style.ResetIntensity ++ " ", .{}); +// } + +// const stdin = std.io.getStdIn().reader(); +// var value: T = undefined; +// var i: usize = 0; +// while (true) { +// const n = if (default != null) try std.fmt.allocPrint(alloc, "{d}", .{default}) else null; +// defer if (n != null) alloc.free(n.?); +// const p = try doprompt(stdin, alloc, if (default != null) n else null); +// defer alloc.free(p.value); +// i += p.n; +// switch (@typeInfo(T)) { +// .Int => { +// if (std.fmt.parseInt(T, p.value, 10) catch null) |cap| { +// value = cap; +// break; +// } +// }, +// .Float => { +// if (std.fmt.parseFloat(T, p.value) catch null) |cap| { +// value = cap; +// break; +// } +// }, +// else => @compileError("expected number type instead got: " ++ @typeName(T)), +// } +// } +// clean(i); +// _ = answer(prompt, T, "{d}", value); + +// return value; +// } diff --git a/src/main.zig b/src/main.zig new file mode 100644 index 0000000000000000000000000000000000000000..4c0b594e84b6a182952bf0b08073fb26ba8d4214 --- /dev/null +++ b/src/main.zig @@ -0,0 +1,30 @@ +const std = @import("std"); +const inquirer = @import("inquirer"); + +// Comparison adaptation of https://github.com/SBoudrias/Inquirer.js/blob/master/packages/inquirer/examples/pizza.js + +pub fn main() !void { + std.log.info("All your codebase are belong to us.", .{}); + + var gpa = std.heap.GeneralPurposeAllocator(.{}){}; + const alloc = &gpa.allocator; + + _ = try inquirer.forConfirm("Is this for delivery?", alloc); + + _ = try inquirer.forString("What's your phone number?", alloc, null); + + _ = try inquirer.forEnum("What size do you need?", alloc, enum { Small, Medium, Large }, null); + + // _ = try inquirer.forNumber("How many do you need?", alloc, u32, 1); + // TODO forNumber is causing a compiler crash + + // TODO toppings for string list + + _ = try inquirer.forEnum("You also get a free 2L:", alloc, enum { Pepsi, @"7up", Coke }, null); + + const comment = try inquirer.forString("Any comments on your purchase experience?", alloc, "Nope, all good!"); + + if (std.mem.eql(u8, comment, "")) { + _ = try inquirer.forEnum("For leaving a comment, you get a freebie:", alloc, enum { Cake, Fries }, null); + } +} diff --git a/zig.mod b/zig.mod new file mode 100644 index 0000000000000000000000000000000000000000..bb0ced17da0f32c574bf15b5c7a401daf6e84f83 --- /dev/null +++ b/zig.mod @@ -0,0 +1,8 @@ +id: c1xirp1ota5pzuzs3i1ixyci6jkkr7xekpi516f1e1238a9z +name: inquirer +main: src/lib.zig +license: MIT +description: A collection of utilities for prompting information from the user on the CLI +dependencies: + - src: git https://github.com/nektro/zig-ansi + - src: git https://github.com/nektro/zig-range diff --git a/zigmod.lock b/zigmod.lock new file mode 100644 index 0000000000000000000000000000000000000000..c23434bf5ba0bcc02e8c4c56f40c8c5b9596d5d1 --- /dev/null +++ b/zigmod.lock @@ -0,0 +1,3 @@ +2 +git https://github.com/nektro/zig-ansi commit-d4a53bcac5b87abecc65491109ec22aaf5f3dc2f +git https://github.com/nektro/zig-range commit-890ca308fe09b3d5c866d5cfb3b3d7a95dbf939f