Commit a73c7bcaf9

Luna <git@l4.pm>
2019-10-02 21:30:12
add lib/std/progress.zig
1 parent 17aa8c3
Changed files (1)
lib
lib/std/progress.zig
@@ -0,0 +1,107 @@
+const std = @import("std");
+const testing = std.testing;
+
+pub const PrintConfig = struct {
+    /// If the current node (and its children) should
+    /// print to stderr on update()
+    flag: bool = false,
+
+    /// If all output should be suppressed instead
+    /// serves the same practical purpose as `flag` but supposed to be used
+    /// by separate parts of the user program.
+    suppress: bool = false,
+};
+
+pub const ProgressNode = struct {
+    completed_items: usize = 0,
+    total_items: usize,
+
+    print_config: PrintConfig,
+
+    // TODO maybe instead of keeping a prefix field, we could
+    // select the proper prefix at the time of update(), and if we're not
+    // in a terminal, we use warn("/r{}", lots_of_whitespace).
+    prefix: []const u8,
+
+    /// Create a new progress node.
+    pub fn start(
+        parent_opt: ?ProgressNode,
+        total_items_opt: ?usize,
+    ) !ProgressNode {
+
+        // inherit the last set print "configuration" from the parent node
+        var print_config = PrintConfig{};
+        if (parent_opt) |parent| {
+            print_config = parent.print_config;
+        }
+
+        var stderr = try std.io.getStdErr();
+        const is_term = std.os.isatty(stderr.handle);
+
+        // if we're in a terminal, use vt100 escape codes
+        // for the progress.
+        var prefix: []const u8 = undefined;
+        if (is_term) {
+            prefix = "\x21[2K\r";
+        } else {
+            prefix = "\n";
+        }
+
+        return ProgressNode{
+            .total_items = total_items_opt orelse 0,
+            .print_config = print_config,
+            .prefix = prefix,
+        };
+    }
+
+    /// Signal an update on the progress node.
+    /// The user of this function is supposed to modify
+    /// ProgressNode.PrintConfig.flag when update() is supposed to print.
+    pub fn update(
+        self: *ProgressNode,
+        current_action: ?[]const u8,
+        items_done_opt: ?usize,
+    ) void {
+        if (items_done_opt) |items_done| {
+            self.completed_items = items_done;
+
+            if (items_done > self.total_items) {
+                self.total_items = items_done;
+            }
+        }
+
+        var cfg = self.print_config;
+        if (cfg.flag and !cfg.suppress and current_action != null) {
+            std.debug.warn(
+                "{}[{}/{}] {}",
+                self.prefix,
+                self.completed_items,
+                self.total_items,
+                current_action,
+            );
+        }
+    }
+
+    pub fn end(self: *ProgressNode) void {
+        if (!self.print_config.flag) return;
+
+        // TODO emoji?
+        std.debug.warn("\n[V] done!");
+    }
+};
+
+test "basic functionality" {
+    var node = try ProgressNode.start(null, 100);
+
+    var buf: [100]u8 = undefined;
+
+    var i: usize = 0;
+    while (i < 100) : (i += 6) {
+        if (i > 50) node.print_config.flag = true;
+        const msg = try std.fmt.bufPrint(buf[0..], "action at i={}", i);
+        node.update(msg, i);
+        std.time.sleep(10 * std.time.millisecond);
+    }
+
+    node.end();
+}