~leon_plickat/public-inbox

zig-spoon: Add rgb and 256 colours mode support v2 APPLIED

Hugo Machet: 2
 Add rgb and 256 colours mode support
 Add colour description parser

 7 files changed, 283 insertions(+), 2 deletions(-)
#761949 alpine.yml success
zig-spoon/patches/alpine.yml: SUCCESS in 1m19s

[Add rgb and 256 colours mode support][0] v2 from [Hugo Machet][1]

[0]: https://lists.sr.ht/~leon_plickat/public-inbox/patches/32393
[1]: mailto:mail@hmachet.com

✓ #761949 SUCCESS zig-spoon/patches/alpine.yml https://builds.sr.ht/~leon_plickat/job/761949
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~leon_plickat/public-inbox/patches/32393/mbox | git am -3
Learn more about email & git

[PATCH zig-spoon v2 1/2] Add rgb and 256 colours mode support Export this patch

Implements: https://todo.sr.ht/~leon_plickat/zig-spoon/4
---
v1 -> v2:
  * Remove useless '\x1b' before the 256 and rgb colours.
  * Move ';' in strings to be consistent with ansi ones.

 lib/Attribute.zig | 29 ++++++++++++++++++++++++++++-
 1 file changed, 28 insertions(+), 1 deletion(-)

diff --git a/lib/Attribute.zig b/lib/Attribute.zig
index 2aa3d17c2c4a..c7e3672d2165 100644
--- a/lib/Attribute.zig
+++ b/lib/Attribute.zig
@@ -6,7 +6,7 @@

const Self = @This();

const Colour = enum {
const Colour = union(enum) {
    none,
    black,
    red,
@@ -24,6 +24,9 @@ const Colour = enum {
    bright_magenta,
    bright_cyan,
    bright_white,

    @"256": u8,
    rgb: [3]u8,
};

fg: Colour = .white,
@@ -75,6 +78,18 @@ pub fn dump(self: Self, writer: anytype) !void {
        .bright_magenta => try writer.writeAll(";95"),
        .bright_cyan => try writer.writeAll(";96"),
        .bright_white => try writer.writeAll(";97"),
        .@"256" => {
            try writer.writeAll(";38;5");
            try writer.print(";{d}", .{self.fg.@"256"});
        },
        .rgb => {
            try writer.writeAll(";38;2");
            try writer.print(";{d};{d};{d}", .{
                self.fg.rgb[0],
                self.fg.rgb[1],
                self.fg.rgb[2],
            });
        },
    }
    switch (self.bg) {
        .none => {},
@@ -94,6 +109,18 @@ pub fn dump(self: Self, writer: anytype) !void {
        .bright_magenta => try writer.writeAll(";105"),
        .bright_cyan => try writer.writeAll(";106"),
        .bright_white => try writer.writeAll(";107"),
        .@"256" => {
            try writer.writeAll(";48;5");
            try writer.print(";{d}", .{self.bg.@"256"});
        },
        .rgb => {
            try writer.writeAll(";48;2");
            try writer.print(";{d};{d};{d}", .{
                self.bg.rgb[0],
                self.bg.rgb[1],
                self.bg.rgb[2],
            });
        },
    }
    try writer.writeAll("m");
}
-- 
2.36.1
Thanks!


Friendly greetings,
Leon Henrik Plickat

[PATCH zig-spoon v2 2/2] Add colour description parser Export this patch

Examples:
  var rgb = try parseColourDescription("0x2b45a7");
  var 256 = try parseColourDescription("234");
  var ansi = try parseColourDescription("red");
---
v1 -> v2:
  * Move the parser to its own file.
  * Rename the function to parseColourDescription().
  * Fix if parsed input is empty.
  * Fix if parsed input is "0".
  * Add example table-256-colours.

 build.zig                     |   8 ++
 example/colours.zig           |   5 ++
 example/table-256-colours.zig |  73 +++++++++++++++
 lib/Attribute.zig             |   4 +-
 lib/colour_description.zig    | 165 ++++++++++++++++++++++++++++++++++
 test_main.zig                 |   1 +
 6 files changed, 255 insertions(+), 1 deletion(-)
 create mode 100644 example/table-256-colours.zig
 create mode 100644 lib/colour_description.zig

diff --git a/build.zig b/build.zig
index 1642c4bba50a..1aa1634c66fe 100644
--- a/build.zig
@@ -34,4 +34,12 @@ pub fn build(b: *Builder) void {
        exe.addPackagePath("spoon", "import.zig");
        exe.install();
    }

    {
        const exe = b.addExecutable("table-256-colours", "example/table-256-colours.zig");
        exe.setTarget(target);
        exe.setBuildMode(mode);
        exe.addPackagePath("spoon", "import.zig");
        exe.install();
    }
}
diff --git a/example/colours.zig b/example/colours.zig
index 431a3a6073fa..3c4d8326cbcd 100644
--- a/example/colours.zig
+++ b/example/colours.zig
@@ -10,6 +10,9 @@ const red = spoon.Attribute{ .fg = .red, .italic = true };
const green = spoon.Attribute{ .fg = .green, .blinking = true };
const blue = spoon.Attribute{ .fg = .blue, .bold = true };
const cyan = spoon.Attribute{ .fg = .cyan, .reverse = true };
const parsed = spoon.Attribute.Colour.fromDescription("magenta") catch
    @compileError("bad colour description");
const magenta = spoon.Attribute{ .fg = parsed, .dimmed = true };
const reset = spoon.Attribute{};

pub fn main() !void {
@@ -21,6 +24,8 @@ pub fn main() !void {
    try writer.writeAll("bar ");
    try blue.dump(writer);
    try writer.writeAll("baz ");
    try magenta.dump(writer);
    try writer.writeAll("zig ");
    try cyan.dump(writer);
    try writer.writeAll("spoon\n");

diff --git a/example/table-256-colours.zig b/example/table-256-colours.zig
new file mode 100644
index 000000000000..d9221fec9659
--- /dev/null
+++ b/example/table-256-colours.zig
@@ -0,0 +1,73 @@
const std = @import("std");
const io = std.io;

const spoon = @import("spoon");

const title_colour = spoon.Attribute.Colour.fromDescription("7") catch
    @compileError("bad colour description");
const title = spoon.Attribute{ .fg = title_colour, .bold = true };
const reset = spoon.Attribute{};

pub fn main() !void {
    const writer = io.getStdOut().writer();

    var colour: u8 = 0;
    var column: usize = 0;

    try writeTitle(writer, "Standard colours (0 to 15)");
    while (colour < 16) : (colour += 1) {
        const attr = spoon.Attribute{ .bg = .{ .@"256" = colour } };

        try attr.dump(writer);
        try writer.writeAll("    ");
        try reset.dump(writer);

        column += 1;
    }

    try writeTitle(writer, "\n6x6x6 cubic palette (16 to 231)");
    column = 0;
    while (colour < 232) : (colour += 1) {
        const attr = spoon.Attribute{ .bg = .{ .@"256" = colour } };

        if (column == 16) {
            column = 0;
            try writer.writeByte('\n');
        }

        try attr.dump(writer);
        try writer.writeAll("    ");
        try reset.dump(writer);

        column += 1;
    }

    try writeTitle(writer, "\nGrayscale (232 to 255)");
    column = 0;
    while (colour < 256) : (colour += 1) {
        const attr = spoon.Attribute{ .bg = .{ .@"256" = colour } };

        if (column == 16) {
            column = 0;
            try writer.writeByte('\n');
        }

        try attr.dump(writer);
        try writer.writeAll("    ");
        try reset.dump(writer);

        column += 1;

        if (colour == 255) break;
    }

    try writer.writeByte('\n');
}

fn writeTitle(writer: anytype, bytes: []const u8) !void {
    try title.dump(writer);
    try writer.writeByte('\n');
    try writer.writeAll(bytes);
    try writer.writeByte('\n');
    try reset.dump(writer);
}
diff --git a/lib/Attribute.zig b/lib/Attribute.zig
index c7e3672d2165..0cc16fbe6aa1 100644
--- a/lib/Attribute.zig
+++ b/lib/Attribute.zig
@@ -6,7 +6,9 @@

const Self = @This();

const Colour = union(enum) {
pub const Colour = union(enum) {
    pub const fromDescription = @import("colour_description.zig").parseColourDescription;

    none,
    black,
    red,
diff --git a/lib/colour_description.zig b/lib/colour_description.zig
new file mode 100644
index 000000000000..def77485fa20
--- /dev/null
+++ b/lib/colour_description.zig
@@ -0,0 +1,165 @@
// Copyright © 2022 Hugo Machet
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

const std = @import("std");
const fmt = std.fmt;
const mem = std.mem;

const Colour = @import("Attribute.zig").Colour;

/// A parser to convert an UTF8 string to an Attribute.Colour union.
/// RGB colours: "0xRRGGBB"
/// 256 colours: "154"
/// ANSI colours: A string equal to one of Colour fields (case sensitive)
///
/// For Example:
///     var rgb = try parseColourDescription("0x2b45a7");
///         -> Attribute{ .rgb = { 43, 69, 167 } };
///     var 256 = try parseColourDescription("234");
///         -> Attribute{ .@"256" = 234 };
///     var ansi = try parseColourDescription("red");
///         -> Attribute{ .red };
pub fn parseColourDescription(s: []const u8) !Colour {
    if (s.len == 0) return error.BadColourDescription;

    var ret: Colour = .none;
    switch (s[0]) {
        '0'...'9' => {
            if (s.len > 1 and s[0] == '0' and s[1] == 'x') {
                const colour = try hexToRgb(s);
                ret = .{ .rgb = colour };
            } else {
                const colour = fmt.parseUnsigned(u8, s, 10) catch |err| switch (err) {
                    error.Overflow => return error.BadColourDescription,
                    else => return err,
                };
                ret = .{ .@"256" = colour };
            }
        },
        'a'...'z' => {
            if (mem.eql(u8, s, "none")) {
                ret = .none;
            } else if (mem.eql(u8, s, "black")) {
                ret = .black;
            } else if (mem.eql(u8, s, "red")) {
                ret = .red;
            } else if (mem.eql(u8, s, "green")) {
                ret = .green;
            } else if (mem.eql(u8, s, "yellow")) {
                ret = .yellow;
            } else if (mem.eql(u8, s, "blue")) {
                ret = .blue;
            } else if (mem.eql(u8, s, "magenta")) {
                ret = .magenta;
            } else if (mem.eql(u8, s, "cyan")) {
                ret = .cyan;
            } else if (mem.eql(u8, s, "white")) {
                ret = .white;
            } else if (mem.eql(u8, s, "bright_black")) {
                ret = .bright_black;
            } else if (mem.eql(u8, s, "bright_red")) {
                ret = .bright_red;
            } else if (mem.eql(u8, s, "bright_green")) {
                ret = .bright_green;
            } else if (mem.eql(u8, s, "bright_yellow")) {
                ret = .bright_yellow;
            } else if (mem.eql(u8, s, "bright_blue")) {
                ret = .bright_blue;
            } else if (mem.eql(u8, s, "bright_magenta")) {
                ret = .bright_magenta;
            } else if (mem.eql(u8, s, "bright_cyan")) {
                ret = .bright_cyan;
            } else if (mem.eql(u8, s, "bright_white")) {
                ret = .bright_white;
            } else {
                return error.BadColourDescription;
            }
        },
        else => return error.BadColourDescription,
    }

    return ret;
}

/// Convert a string in the format "0xRRGGBB" to [3]u8.
fn hexToRgb(s: []const u8) ![3]u8 {
    if (s.len != 8) return error.BadRgbFormat;
    if (s[0] != '0' or s[1] != 'x') return error.BadRgbFormat;

    var colour = try fmt.parseUnsigned(u32, s[2..], 16);
    colour <<= 8;
    colour |= 0xff;

    const bytes = @bitCast([4]u8, colour);
    return [3]u8{ bytes[3], bytes[2], bytes[1] };
}

test "parse colours string (good input)" {
    const testing = std.testing;

    try testing.expectEqual(
        Colour{ .rgb = .{ 255, 255, 255 } },
        try parseColourDescription("0xffffff"),
    );
    try testing.expectEqual(
        Colour{ .rgb = .{ 0, 169, 143 } },
        try parseColourDescription("0x00a98f"),
    );
    try testing.expectEqual(
        Colour{ .@"256" = 0 },
        try parseColourDescription("0"),
    );
    try testing.expectEqual(
        Colour{ .@"256" = 7 },
        try parseColourDescription("007"),
    );
    try testing.expectEqual(
        Colour{ .@"256" = 237 },
        try parseColourDescription("237"),
    );
    try testing.expectEqual(
        Colour.bright_black,
        try parseColourDescription("bright_black"),
    );
    try testing.expectEqual(
        Colour.blue,
        try parseColourDescription("blue"),
    );
}

test "parse colours string (bad input)" {
    const testing = std.testing;

    try testing.expectError(
        error.BadRgbFormat,
        parseColourDescription("0xfff"),
    );

    try testing.expectError(
        error.BadColourDescription,
        parseColourDescription(""),
    );
    try testing.expectError(
        error.BadColourDescription,
        parseColourDescription("xffffff"),
    );
    try testing.expectError(
        error.BadColourDescription,
        parseColourDescription("ffffff"),
    );
    try testing.expectError(
        error.BadColourDescription,
        parseColourDescription("blu"),
    );
    try testing.expectError(
        error.BadColourDescription,
        parseColourDescription("bLue"),
    );
    try testing.expectError(
        error.BadColourDescription,
        parseColourDescription("256"),
    );
}
diff --git a/test_main.zig b/test_main.zig
index 4dba7ce47076..c7401c18f8c7 100644
--- a/test_main.zig
+++ b/test_main.zig
@@ -1,4 +1,5 @@
test {
    _ = @import("lib/input.zig");
    _ = @import("lib/input_description.zig");
    _ = @import("lib/colour_description.zig");
}
-- 
2.36.1
zig-spoon/patches/alpine.yml: SUCCESS in 1m19s

[Add rgb and 256 colours mode support][0] v2 from [Hugo Machet][1]

[0]: https://lists.sr.ht/~leon_plickat/public-inbox/patches/32393
[1]: mailto:mail@hmachet.com

✓ #761949 SUCCESS zig-spoon/patches/alpine.yml https://builds.sr.ht/~leon_plickat/job/761949