diff --git a/src/main/resources/data/computercraft/lua/rom/modules/main/cc/strings.lua b/src/main/resources/data/computercraft/lua/rom/modules/main/cc/strings.lua new file mode 100644 index 000000000..1e6e6efc8 --- /dev/null +++ b/src/main/resources/data/computercraft/lua/rom/modules/main/cc/strings.lua @@ -0,0 +1,102 @@ +--- Various utilities for working with strings and text. +-- +-- @see textutils For additional string related utilities. + +local expect = require "cc.expect".expect + +--- Wraps a block of text, so that each line fits within the given width. +-- +-- This may be useful if you want to wrap text before displaying it to a +-- @{monitor} or @{printer} without using @{_G.print|print}. +-- +-- @tparam string text The string to wrap. +-- @tparam[opt] number width The width to constrain to, defaults to the width of +-- the terminal. +-- +-- @treturn { string... } The wrapped input string. +-- @usage require "cc.strings".wrap("This is a long piece of text", 10) +local function wrap(text, width) + expect(1, text, "string") + expect(2, width, "number", "nil") + width = width or term.getSize() + + + local lines, lines_n, current_line = {}, 0, "" + local function push_line() + lines_n = lines_n + 1 + lines[lines_n] = current_line + current_line = "" + end + + local pos, length = 1, #text + local sub, match = string.sub, string.match + while pos <= length do + local head = sub(text, pos, pos) + if head == " " or head == "\t" then + local whitespace = match(text, "^[ \t]+", pos) + current_line = current_line .. whitespace + pos = pos + #whitespace + elseif head == "\n" then + push_line() + pos = pos + 1 + else + local word = match(text, "^[^ \t\n]+", pos) + pos = pos + #word + if #word > width then + -- Print a multiline word + while #word > 0 do + local space_remaining = width - #current_line - 1 + if space_remaining <= 0 then + push_line() + space_remaining = width + end + + current_line = current_line .. sub(word, 1, space_remaining) + word = sub(word, space_remaining + 1) + end + else + -- Print a word normally + if width - #current_line < #word then push_line() end + current_line = current_line .. word + end + end + end + + push_line() + + -- Trim whitespace longer than width. + for k, line in pairs(lines) do + line = line:sub(1, width) + lines[k] = line + end + + return lines +end + +--- Makes the input string a fixed width. This either truncates it, or pads it +-- with spaces. +-- +-- @tparam string line The string to normalise. +-- @tparam[opt] number width The width to constrain to, defaults to the width of +-- the terminal. +-- +-- @treturn string The string with a specific width. +-- @usage require "cc.strings".ensure_width("a short string", 20) +-- @usage require "cc.strings".ensure_width("a rather long string which is truncated", 20) +local function ensure_width(line, width) + expect(1, line, "string") + expect(2, width, "number", "nil") + width = width or term.getSize() + + line = line:sub(1, width) + if #line < width then + line = line .. (" "):rep(width - #line) + end + + return line +end + +return { + wrap = wrap, + ensure_width = ensure_width, +} diff --git a/src/test/resources/test-rom/spec/modules/cc/strings_spec.lua b/src/test/resources/test-rom/spec/modules/cc/strings_spec.lua new file mode 100644 index 000000000..900c6fe6f --- /dev/null +++ b/src/test/resources/test-rom/spec/modules/cc/strings_spec.lua @@ -0,0 +1,41 @@ +describe("cc.pretty", function() + local str = require("cc.strings") + + describe("wrap", function() + it("validates arguments", function() + str.wrap("test string is long") + str.wrap("test string is long", 11) + expect.error(str.wrap, nil):eq("bad argument #1 (expected string, got nil)") + expect.error(str.wrap, "", false):eq("bad argument #2 (expected number, got boolean)") + end) + + it("wraps lines", function() + expect(str.wrap("test string is long")[1]):eq("test string is long") + + expect(str.wrap("test string is long", 15)[1]):eq("test string is ") + expect(str.wrap("test string is long", 15)[2]):eq("long") + + expect(str.wrap("test string is long", 12)[1]):eq("test string ") + expect(str.wrap("test string is long", 12)[2]):eq("is long") + + expect(str.wrap("test string is long", 11)[1]):eq("test string") + expect(str.wrap("test string is long", 11)[2]):eq("is long") + end) + end) + + describe("ensure_width", function() + it("validates arguments", function() + str.wrap("test string is long") + str.wrap("test string is long", 11) + expect.error(str.ensure_width, nil):eq("bad argument #1 (expected string, got nil)") + expect.error(str.ensure_width, "", false):eq("bad argument #2 (expected number, got boolean)") + end) + + it("pads lines", function() + expect(str.ensure_width("test string is long", 25)):eq("test string is long ") + end) + it("truncates lines", function() + expect(str.ensure_width("test string is long", 15)):eq("test string is ") + end) + end) +end)