-
Notifications
You must be signed in to change notification settings - Fork 9
/
text.lua
189 lines (156 loc) · 4.9 KB
/
text.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
-- Localize globals
local assert, math, modlib, setmetatable, string, table
= assert, math, modlib, setmetatable, string, table
-- Set environment
local _ENV = {}
setfenv(1, _ENV)
function upper_first(text) return text:sub(1, 1):upper() .. text:sub(2) end
function lower_first(text) return text:sub(1, 1):lower() .. text:sub(2) end
function starts_with(text, prefix) return text:sub(1, #prefix) == prefix end
function ends_with(text, suffix) return text:sub(-#suffix) == suffix end
function contains(str, substr, plain)
return not not str:find(substr, 1, plain == nil and true or plain)
end
function trim_spacing(text)
return text:match"^%s*(.-)%s*$"
end
local inputstream_metatable = {
__index = {
read = function(self, count)
local cursor = self.cursor + 1
self.cursor = self.cursor + count
local text = self.text:sub(cursor, self.cursor)
return text ~= "" and text or nil
end,
seek = function(self) return self.cursor end
}
}
--> inputstream "handle"; only allows reading characters (given a count), seeking does not accept any arguments
function inputstream(text)
return setmetatable({text = text, cursor = 0}, inputstream_metatable)
end
function hexdump(text)
local dump = {}
for index = 1, text:len() do
dump[index] = ("%02X"):format(text:byte(index))
end
return table.concat(dump)
end
function spliterator(str, delim, plain)
assert(delim ~= "")
local last_delim_end = 0
-- Iterator of possibly empty substrings between two matches of the delimiter
-- To exclude empty strings, filter the iterator or use `:gmatch"[...]+"` instead
return function()
if not last_delim_end then
return
end
local delim_start, delim_end = str:find(delim, last_delim_end + 1, plain)
local substr
if delim_start then
substr = str:sub(last_delim_end + 1, delim_start - 1)
else
substr = str:sub(last_delim_end + 1)
end
last_delim_end = delim_end
return substr
end
end
function split(text, delimiter, limit, plain)
limit = limit or math.huge
local parts = {}
local occurences = 1
local last_index = 1
local index = string.find(text, delimiter, 1, plain)
while index and occurences < limit do
table.insert(parts, string.sub(text, last_index, index - 1))
last_index = index + string.len(delimiter)
index = string.find(text, delimiter, index + string.len(delimiter), plain)
occurences = occurences + 1
end
table.insert(parts, string.sub(text, last_index))
return parts
end
function split_without_limit(text, delimiter, plain)
return split(text, delimiter, nil, plain)
end
split_unlimited = split_without_limit
--! Does not support Macintosh pre-OSX CR-only line endings
--! Deprecated in favor of the `lines` iterator below
function split_lines(text, limit)
return modlib.text.split(text, "\r?\n", limit, true)
end
-- When reading from a file, directly use `io.lines` instead
-- Lines are possibly empty substrings separated by CR, LF or CRLF
-- A trailing linefeed is ignored
function lines(str)
local line_start = 1
-- Line iterator
return function()
if line_start > #str then
return
end
local linefeed_start, _, linefeed = str:find("([\r\n][\r\n]?)", line_start)
local line
if linefeed_start then
line = str:sub(line_start, linefeed_start - 1)
line_start = linefeed_start + (linefeed == "\r\n" and 2 or 1)
else
line = str:sub(line_start)
line_start = #str + 1
end
return line
end
end
local zero = string.byte"0"
local nine = string.byte"9"
local letter_a = string.byte"A"
local letter_f = string.byte"F"
function is_hexadecimal(byte)
return byte >= zero and byte <= nine or byte >= letter_a and byte <= letter_f
end
magic_charset = "[" .. ("%^$+-*?.[]()"):gsub(".", "%%%1") .. "]"
function escape_pattern(text)
return text:gsub(magic_charset, "%%%1")
end
escape_magic_chars = escape_pattern
local keywords = modlib.table.set{"and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while"}
keywords["goto"] = true -- Lua 5.2 (LuaJIT) support
function is_keyword(text)
return keywords[text]
end
function is_identifier(text)
return (not keywords[text]) and text:match"^[A-Za-z_][A-Za-z%d_]*$"
end
local function inextchar(text, i)
if i >= #text then return end
i = i + 1
return i, text:sub(i, i)
end
function ichars(text, start)
-- Iterator over `index, character`
return inextchar, text, (start or 1) - 1
end
local function inextbyte(text, i)
if i >= #text then return end
i = i + 1
return i, text:byte(i, i)
end
function ibytes(text, start)
-- Iterator over `index, byte`
return inextbyte, text, (start or 1) - 1
end
local function _random_bytes(count)
if count == 0 then return end
return math.random(0, 0xFF), _random_bytes(count - 1)
end
function random_bytes(
-- number, how many random bytes the string should have, defaults to 1
-- limited by stack size
count
)
count = count or 1
return string.char(_random_bytes(count))
end
-- Export environment
return _ENV