├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── build.zig ├── licenses.txt ├── src ├── lib.zig └── main.zig ├── test.zig └── zig.mod /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .zig-cache 2 | zig-out 3 | .zigmod 4 | deps.zig 5 | files.zig 6 | zigmod.lock 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Meghan Denny 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # zig-inquirer 2 | 3 | ![loc](https://sloc.xyz/github/nektro/zig-inquirer) 4 | [![license](https://img.shields.io/github/license/nektro/zig-inquirer.svg)](https://github.com/nektro/zig-inquirer/blob/master/LICENSE) 5 | [![nektro @ github sponsors](https://img.shields.io/badge/sponsors-nektro-purple?logo=github)](https://github.com/sponsors/nektro) 6 | [![Zig](https://img.shields.io/badge/Zig-0.14-f7a41d)](https://ziglang.org/) 7 | [![Zigmod](https://img.shields.io/badge/Zigmod-latest-f7a41d)](https://github.com/nektro/zigmod) 8 | 9 | A collection of utilities for prompting information from the user on the CLI 10 | 11 | Adapted from https://github.com/SBoudrias/Inquirer.js 12 | 13 | ## Run example 14 | 15 | ``` 16 | zig build run 17 | ``` 18 | 19 | ## Screenshots 20 | 21 | ![image](https://user-images.githubusercontent.com/5464072/127479686-fda8f860-a705-4fd6-9768-a3e1f53a6bc7.png) 22 | 23 | ## Usage 24 | 25 | - `pub fn answer(writer, reader, comptime prompt: []const u8, value: []const u8) []const u8` 26 | - Prints just the done string. 27 | - `pub fn forEnum(writer, reader, comptime prompt: []const u8, alloc: *std.mem.Allocator, comptime options: enum, default: ?options) !options` 28 | - Accepts an enum and prompts the user to pick on of the fields. 29 | - `pub fn forString(writer, reader, comptime prompt: []const u8, alloc: *std.mem.Allocator, default: ?[]const u8) ![]const u8` 30 | - Base function, asks prompt and returns non-empty answer. 31 | - `pub fn forConfirm(writer, reader, comptime prompt: []const u8, alloc: *std.mem.Allocator) !bool` 32 | - Calls `forEnum` with `y/n` 33 | 34 | ## TODO 35 | 36 | - number (current implementation causes compiler crash) 37 | - list with strings 38 | - string password 39 | - long list with autocomplete 40 | - date 41 | - time 42 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const deps = @import("./deps.zig"); 3 | 4 | pub fn build(b: *std.Build) void { 5 | const target = b.standardTargetOptions(.{}); 6 | const mode = b.option(std.builtin.Mode, "mode", "") orelse .Debug; 7 | const disable_llvm = b.option(bool, "disable_llvm", "use the non-llvm zig codegen") orelse false; 8 | 9 | const exe = b.addExecutable(.{ 10 | .name = "zig-inquirer", 11 | .root_source_file = b.path("src/main.zig"), 12 | .target = target, 13 | .optimize = mode, 14 | }); 15 | deps.addAllTo(exe); 16 | exe.use_llvm = !disable_llvm; 17 | exe.use_lld = !disable_llvm; 18 | b.installArtifact(exe); 19 | 20 | const run_cmd = b.addRunArtifact(exe); 21 | run_cmd.step.dependOn(b.getInstallStep()); 22 | if (b.args) |args| { 23 | run_cmd.addArgs(args); 24 | } 25 | 26 | const run_step = b.step("run", "Run the app"); 27 | run_step.dependOn(&run_cmd.step); 28 | 29 | const tests = b.addTest(.{ 30 | .root_source_file = b.path("test.zig"), 31 | .target = target, 32 | .optimize = mode, 33 | }); 34 | deps.addAllTo(tests); 35 | tests.use_llvm = !disable_llvm; 36 | tests.use_lld = !disable_llvm; 37 | 38 | const test_step = b.step("test", "Run all library tests"); 39 | const tests_run = b.addRunArtifact(tests); 40 | tests_run.has_side_effects = true; 41 | test_step.dependOn(&tests_run.step); 42 | } 43 | -------------------------------------------------------------------------------- /licenses.txt: -------------------------------------------------------------------------------- 1 | MIT: 2 | = https://spdx.org/licenses/MIT 3 | - This 4 | - git https://github.com/nektro/zig-ansi 5 | -------------------------------------------------------------------------------- /src/lib.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const ansi = @import("ansi"); 3 | 4 | pub fn answer(out: anytype, comptime prompt: []const u8, comptime T: type, comptime valfmt: []const u8, value: T) !T { 5 | try out.print(comptime ansi.color.Fg(.Green, "? "), .{}); 6 | try out.print(comptime ansi.color.Bold(prompt ++ " "), .{}); 7 | try out.print(comptime ansi.color.Fg(.Cyan, valfmt ++ "\n"), .{value}); 8 | return value; 9 | } 10 | 11 | const PromptRet = struct { 12 | n: usize, 13 | value: []const u8, 14 | }; 15 | 16 | fn doprompt(out: anytype, in: anytype, alloc: std.mem.Allocator, default: ?[]const u8) !PromptRet { 17 | var n: usize = 1; 18 | var value: []const u8 = undefined; 19 | while (true) : (n += 1) { 20 | try out.print(comptime ansi.color.Faint("> "), .{}); 21 | var input: []const u8 = try in.readUntilDelimiterAlloc(alloc, '\n', 1024); 22 | input = std.mem.trimRight(u8, input, "\r\n"); 23 | 24 | if (input.len == 0) if (default) |d| { 25 | value = d; 26 | break; 27 | }; 28 | if (input.len > 0) { 29 | value = input; 30 | break; 31 | } 32 | } 33 | return PromptRet{ .n = n, .value = value }; 34 | } 35 | 36 | fn clean(out: anytype, n: usize) !void { 37 | for (0..n) |_| { 38 | try out.print(comptime ansi.csi.CursorUp(1), .{}); 39 | try out.print(comptime ansi.csi.EraseInLine(0), .{}); 40 | } 41 | } 42 | 43 | pub fn forEnum(out: anytype, in: anytype, comptime prompt: []const u8, alloc: std.mem.Allocator, comptime E: type, default: ?E) !E { 44 | comptime std.debug.assert(@typeInfo(E) == .@"enum"); 45 | const def: ?[]const u8 = if (default) |d| @tagName(d) else null; 46 | 47 | try out.print(comptime ansi.color.Fg(.Green, "? "), .{}); 48 | try out.print(comptime ansi.color.Bold(prompt ++ " "), .{}); 49 | 50 | try out.print(ansi.style.Faint ++ "(", .{}); 51 | inline for (std.meta.fields(E), 0..) |f, i| { 52 | if (i != 0) try out.print("/", .{}); 53 | if (std.mem.eql(u8, f.name, def orelse "")) { 54 | try out.print(ansi.style.ResetIntensity, .{}); 55 | try out.print(comptime ansi.color.Fg(.Cyan, f.name), .{}); 56 | try out.print(ansi.style.Faint, .{}); 57 | } else try out.print(f.name, .{}); 58 | } 59 | try out.print(")" ++ ansi.style.ResetIntensity ++ " ", .{}); 60 | 61 | var value: E = undefined; 62 | var i: usize = 0; 63 | while (true) { 64 | const p = try doprompt(out, in, alloc, def); 65 | defer if (!std.mem.eql(u8, p.value, def orelse "")) alloc.free(p.value); 66 | 67 | i += p.n; 68 | if (std.meta.stringToEnum(E, p.value)) |cap| { 69 | value = cap; 70 | break; 71 | } 72 | } 73 | try clean(out, i); 74 | _ = try answer(out, prompt, []const u8, "{s}", @tagName(value)); 75 | 76 | return value; 77 | } 78 | 79 | pub fn forString(out: anytype, in: anytype, comptime prompt: []const u8, alloc: std.mem.Allocator, default: ?[]const u8) ![]const u8 { 80 | try out.print(comptime ansi.color.Fg(.Green, "? "), .{}); 81 | try out.print(comptime ansi.color.Bold(prompt ++ " "), .{}); 82 | 83 | if (default != null and default.?.len > 0) { 84 | try out.print(ansi.style.Faint ++ "(", .{}); 85 | try out.print("{s}", .{default.?}); 86 | try out.print(")" ++ ansi.style.ResetIntensity ++ " ", .{}); 87 | } 88 | 89 | const p = try doprompt(out, in, alloc, default); 90 | try clean(out, p.n); 91 | return try answer(out, prompt, []const u8, "{s}", p.value); 92 | } 93 | 94 | pub fn forConfirm(out: anytype, in: anytype, comptime prompt: []const u8, alloc: std.mem.Allocator) !bool { 95 | return (try forEnum(out, in, prompt, alloc, enum { y, n }, .y)) == .y; 96 | } 97 | 98 | // pub fn forNumber(comptime prompt: []const u8, alloc: *std.mem.Allocator, comptime T: type, default: ?T) !T { 99 | // try out.print(comptime ansi.color.Fg(.Green, "? "), .{}); 100 | // try out.print(comptime ansi.color.Bold(prompt ++ " "), .{}); 101 | 102 | // if (default != null) { 103 | // try out.print(ansi.style.Faint ++ "(", .{}); 104 | // try out.print("{d}", .{default.?}); 105 | // try out.print(")" ++ ansi.style.ResetIntensity ++ " ", .{}); 106 | // } 107 | 108 | // var value: T = undefined; 109 | // var i: usize = 0; 110 | // while (true) { 111 | // const n = if (default != null) try std.fmt.allocPrint(alloc, "{d}", .{default}) else null; 112 | // defer if (n != null) alloc.free(n.?); 113 | // const p = try doprompt(stdin, alloc, if (default != null) n else null); 114 | // defer alloc.free(p.value); 115 | // i += p.n; 116 | // switch (@typeInfo(T)) { 117 | // .Int => { 118 | // if (std.fmt.parseInt(T, p.value, 10) catch null) |cap| { 119 | // value = cap; 120 | // break; 121 | // } 122 | // }, 123 | // .Float => { 124 | // if (std.fmt.parseFloat(T, p.value) catch null) |cap| { 125 | // value = cap; 126 | // break; 127 | // } 128 | // }, 129 | // else => @compileError("expected number type instead got: " ++ @typeName(T)), 130 | // } 131 | // } 132 | // clean(i); 133 | // _ = answer(prompt, T, "{d}", value); 134 | 135 | // return value; 136 | // } 137 | -------------------------------------------------------------------------------- /src/main.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const inquirer = @import("inquirer"); 3 | 4 | // Comparison adaptation of https://github.com/SBoudrias/Inquirer.js/blob/master/packages/inquirer/examples/pizza.js 5 | 6 | pub fn main() !void { 7 | std.log.info("All your codebase are belong to us.", .{}); 8 | 9 | var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 10 | const alloc = &gpa.allocator; 11 | 12 | const in = std.io.getStdIn().reader(); 13 | const out = std.io.getStdOut().writer(); 14 | 15 | _ = try inquirer.forConfirm(out, in, "Is this for delivery?", alloc); 16 | 17 | _ = try inquirer.forString(out, in, "What's your phone number?", alloc, null); 18 | 19 | _ = try inquirer.forEnum(out, in, "What size do you need?", alloc, enum { Small, Medium, Large }, null); 20 | 21 | // _ = try inquirer.forNumber(out,in,"How many do you need?", alloc, u32, 1); 22 | // TODO forNumber is causing a compiler crash 23 | 24 | // TODO toppings for string list 25 | 26 | _ = try inquirer.forEnum(out, in, "You also get a free 2L:", alloc, enum { Pepsi, @"7up", Coke }, null); 27 | 28 | const comment = try inquirer.forString(out, in, "Any comments on your purchase experience?", alloc, "Nope, all good!"); 29 | 30 | if (!std.mem.eql(u8, comment, "Nope, all good!")) { 31 | _ = try inquirer.forEnum(out, in, "For leaving a comment, you get a freebie:", alloc, enum { Cake, Fries }, null); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | const inquirer = @import("inquirer"); 3 | 4 | test { 5 | // ensure these have no compile errors 6 | // but don't write anything to stdout/stderr 7 | _ = &inquirer.answer; 8 | _ = &inquirer.forEnum; 9 | _ = &inquirer.forString; 10 | _ = &inquirer.forConfirm; 11 | } 12 | -------------------------------------------------------------------------------- /zig.mod: -------------------------------------------------------------------------------- 1 | id: c1xirp1ota5pzuzs3i1ixyci6jkkr7xekpi516f1e1238a9z 2 | name: inquirer 3 | main: src/lib.zig 4 | license: MIT 5 | description: A collection of utilities for prompting information from the user on the CLI 6 | dependencies: 7 | - src: git https://github.com/nektro/zig-ansi 8 | --------------------------------------------------------------------------------