spaces->tab, equipper improvements, supertreefarm rewrite, follow improvements, sensor cleanup, milo multiple items allowed in recipes, remote canvas access

This commit is contained in:
kepler155c@gmail.com
2019-06-18 15:23:20 -04:00
parent 3b9b509429
commit 045b32884f
162 changed files with 20448 additions and 20286 deletions

View File

@@ -1,3 +1,3 @@
return {
uptime = os.clock,
uptime = os.clock,
}

View File

@@ -1,68 +1,68 @@
local fs = _G.fs
local function get(path)
while not fs.exists(path) do
path = fs.getDir(path)
end
while not fs.exists(path) do
path = fs.getDir(path)
end
if fs.exists(path) then
local proxy = {
getLabel = function() return fs.getDrive(path) end,
isReadOnly = function() return fs.isReadOnly(path) end,
spaceTotal = function() return fs.getSize(path, true) + fs.getFreeSpace(path) end,
spaceUsed = function() return fs.getSize(path, true) end,
}
return proxy, path
end
if fs.exists(path) then
local proxy = {
getLabel = function() return fs.getDrive(path) end,
isReadOnly = function() return fs.isReadOnly(path) end,
spaceTotal = function() return fs.getSize(path, true) + fs.getFreeSpace(path) end,
spaceUsed = function() return fs.getSize(path, true) end,
}
return proxy, path
end
end
local function mounts()
local t = {
[ fs.getDrive('/') ] = '/'
}
for _,path in pairs(fs.list('/')) do
local label = fs.getDrive(path)
if not t[label] then
t[label] = path
end
end
local t = {
[ fs.getDrive('/') ] = '/'
}
for _,path in pairs(fs.list('/')) do
local label = fs.getDrive(path)
if not t[label] then
t[label] = path
end
end
return function()
local label, path = next(t)
if label then
t[label] = nil
return get(path)
end
end
return function()
local label, path = next(t)
if label then
t[label] = nil
return get(path)
end
end
end
local function list(path)
local success, set = pcall(fs.list, path)
if success then
return function()
local key, value = next(set)
set[key or false] = nil
return value
end
end
return success, set
local success, set = pcall(fs.list, path)
if success then
return function()
local key, value = next(set)
set[key or false] = nil
return value
end
end
return success, set
end
return {
canonical = function(...) return ... end,
concat = fs.combine,
copy = function(...) fs.copy(...) return true end,
exists = fs.exists,
get = get,
isDirectory = fs.isDir,
isLink = function() return false end,
link = function(s, t) return fs.mount(t, 'linkfs', s) end,
list = list,
makeDirectory = fs.makeDir,
mounts = mounts,
name = fs.getName,
open = function(n, m) return fs.open(n, m or 'r') end,
realPath = function(...) return ... end,
remove = function(a) fs.delete(a) return true end,
size = fs.getSize,
canonical = function(...) return ... end,
concat = fs.combine,
copy = function(...) fs.copy(...) return true end,
exists = fs.exists,
get = get,
isDirectory = fs.isDir,
isLink = function() return false end,
link = function(s, t) return fs.mount(t, 'linkfs', s) end,
list = list,
makeDirectory = fs.makeDir,
mounts = mounts,
name = fs.getName,
open = function(n, m) return fs.open(n, m or 'r') end,
realPath = function(...) return ... end,
remove = function(a) fs.delete(a) return true end,
size = fs.getSize,
}

View File

@@ -2,118 +2,118 @@
local M = {_TYPE='module', _NAME='globtopattern', _VERSION='0.2.1.20120406'}
function M.globtopattern(g)
-- Some useful references:
-- - apr_fnmatch in Apache APR. For example,
-- http://apr.apache.org/docs/apr/1.3/group__apr__fnmatch.html
-- which cites POSIX 1003.2-1992, section B.6.
-- Some useful references:
-- - apr_fnmatch in Apache APR. For example,
-- http://apr.apache.org/docs/apr/1.3/group__apr__fnmatch.html
-- which cites POSIX 1003.2-1992, section B.6.
local p = "^" -- pattern being built
local i = 0 -- index in g
local c -- char at index i in g.
local p = "^" -- pattern being built
local i = 0 -- index in g
local c -- char at index i in g.
-- unescape glob char
local function unescape()
if c == '\\' then
i = i + 1; c = g:sub(i,i)
if c == '' then
p = '[^]'
return false
end
end
return true
end
-- unescape glob char
local function unescape()
if c == '\\' then
i = i + 1; c = g:sub(i,i)
if c == '' then
p = '[^]'
return false
end
end
return true
end
-- escape pattern char
local function escape(c)
return c:match("^%w$") and c or '%' .. c
end
-- escape pattern char
local function escape(c)
return c:match("^%w$") and c or '%' .. c
end
-- Convert tokens at end of charset.
local function charset_end()
while 1 do
if c == '' then
p = '[^]'
return false
elseif c == ']' then
p = p .. ']'
break
else
if not unescape() then break end
local c1 = c
i = i + 1; c = g:sub(i,i)
if c == '' then
p = '[^]'
return false
elseif c == '-' then
i = i + 1; c = g:sub(i,i)
if c == '' then
p = '[^]'
return false
elseif c == ']' then
p = p .. escape(c1) .. '%-]'
break
else
if not unescape() then break end
p = p .. escape(c1) .. '-' .. escape(c)
end
elseif c == ']' then
p = p .. escape(c1) .. ']'
break
else
p = p .. escape(c1)
i = i - 1 -- put back
end
end
i = i + 1; c = g:sub(i,i)
end
return true
end
-- Convert tokens at end of charset.
local function charset_end()
while 1 do
if c == '' then
p = '[^]'
return false
elseif c == ']' then
p = p .. ']'
break
else
if not unescape() then break end
local c1 = c
i = i + 1; c = g:sub(i,i)
if c == '' then
p = '[^]'
return false
elseif c == '-' then
i = i + 1; c = g:sub(i,i)
if c == '' then
p = '[^]'
return false
elseif c == ']' then
p = p .. escape(c1) .. '%-]'
break
else
if not unescape() then break end
p = p .. escape(c1) .. '-' .. escape(c)
end
elseif c == ']' then
p = p .. escape(c1) .. ']'
break
else
p = p .. escape(c1)
i = i - 1 -- put back
end
end
i = i + 1; c = g:sub(i,i)
end
return true
end
-- Convert tokens in charset.
local function charset()
i = i + 1; c = g:sub(i,i)
if c == '' or c == ']' then
p = '[^]'
return false
elseif c == '^' or c == '!' then
i = i + 1; c = g:sub(i,i)
if c == ']' then
-- ignored
else
p = p .. '[^'
if not charset_end() then return false end
end
else
p = p .. '['
if not charset_end() then return false end
end
return true
end
-- Convert tokens in charset.
local function charset()
i = i + 1; c = g:sub(i,i)
if c == '' or c == ']' then
p = '[^]'
return false
elseif c == '^' or c == '!' then
i = i + 1; c = g:sub(i,i)
if c == ']' then
-- ignored
else
p = p .. '[^'
if not charset_end() then return false end
end
else
p = p .. '['
if not charset_end() then return false end
end
return true
end
-- Convert tokens.
while 1 do
i = i + 1; c = g:sub(i,i)
if c == '' then
p = p .. '$'
break
elseif c == '?' then
p = p .. '.'
elseif c == '*' then
p = p .. '.*'
elseif c == '[' then
if not charset() then break end
elseif c == '\\' then
i = i + 1; c = g:sub(i,i)
if c == '' then
p = p .. '\\$'
break
end
p = p .. escape(c)
else
p = p .. escape(c)
end
end
return p
-- Convert tokens.
while 1 do
i = i + 1; c = g:sub(i,i)
if c == '' then
p = p .. '$'
break
elseif c == '?' then
p = p .. '.'
elseif c == '*' then
p = p .. '.*'
elseif c == '[' then
if not charset() then break end
elseif c == '\\' then
i = i + 1; c = g:sub(i,i)
if c == '' then
p = p .. '\\$'
break
end
p = p .. escape(c)
else
p = p .. escape(c)
end
end
return p
end
return M

View File

@@ -1,3 +1,3 @@
return {
keys = _G.keys,
keys = _G.keys,
}

View File

@@ -1,6 +1,6 @@
local shell = _ENV.shell
return {
execute = function(_, ...) return shell.run(...) end,
getLastExitCode = function() return 0 end,
execute = function(_, ...) return shell.run(...) end,
getLastExitCode = function() return 0 end,
}

View File

@@ -3,8 +3,8 @@ local Util = require('util')
local shell = _ENV.shell
return {
getWorkingDirectory = shell.dir,
resolve = shell.resolve,
resolveProgram = shell.resolveProgram,
parse = Util.parse,
getWorkingDirectory = shell.dir,
resolve = shell.resolve,
resolveProgram = shell.resolveProgram,
parse = Util.parse,
}

View File

@@ -7,138 +7,138 @@ text.internal = {}
text.syntax = {"^%d?>>?&%d+","^%d?>>?",">>?","<%&%d+","<",";","&&","||?"}
local function checkArg(n, have, ...)
have = type(have)
local function check(want, ...)
if not want then
return false
else
return have == want or check(...)
end
end
if not check(...) then
local msg = string.format("bad argument #%d (%s expected, got %s)",
n, table.concat({...}, " or "), have)
error(msg, 3)
end
have = type(have)
local function check(want, ...)
if not want then
return false
else
return have == want or check(...)
end
end
if not check(...) then
local msg = string.format("bad argument #%d (%s expected, got %s)",
n, table.concat({...}, " or "), have)
error(msg, 3)
end
end
function text.trim(value) -- from http://lua-users.org/wiki/StringTrim
local from = string.match(value, "^%s*()")
return from > #value and "" or string.match(value, ".*%S", from)
local from = string.match(value, "^%s*()")
return from > #value and "" or string.match(value, ".*%S", from)
end
-- used by lib/sh
function text.escapeMagic(txt)
return txt:gsub('[%(%)%.%%%+%-%*%?%[%^%$]', '%%%1')
return txt:gsub('[%(%)%.%%%+%-%*%?%[%^%$]', '%%%1')
end
function text.removeEscapes(txt)
return txt:gsub("%%([%(%)%.%%%+%-%*%?%[%^%$])","%1")
return txt:gsub("%%([%(%)%.%%%+%-%*%?%[%^%$])","%1")
end
function text.internal.tokenize(value, options)
checkArg(1, value, "string")
checkArg(2, options, "table", "nil")
options = options or {}
local delimiters = options.delimiters
local custom = not not options.delimiters
delimiters = delimiters or text.syntax
checkArg(1, value, "string")
checkArg(2, options, "table", "nil")
options = options or {}
local delimiters = options.delimiters
local custom = not not options.delimiters
delimiters = delimiters or text.syntax
local words, reason = text.internal.words(value, options)
local words, reason = text.internal.words(value, options)
local splitter = text.escapeMagic(custom and table.concat(delimiters) or "<>|;&")
if type(words) ~= "table" or
#splitter == 0 or
not value:find("["..splitter.."]") then
return words, reason
end
local splitter = text.escapeMagic(custom and table.concat(delimiters) or "<>|;&")
if type(words) ~= "table" or
#splitter == 0 or
not value:find("["..splitter.."]") then
return words, reason
end
return text.internal.splitWords(words, delimiters)
return text.internal.splitWords(words, delimiters)
end
-- tokenize input by quotes and whitespace
function text.internal.words(input, options)
checkArg(1, input, "string")
checkArg(2, options, "table", "nil")
options = options or {}
local quotes = options.quotes
local show_escapes = options.show_escapes
local qr = nil
quotes = quotes or {{"'","'",true},{'"','"'},{'`','`'}}
local function append(dst, txt, _qr)
local size = #dst
if size == 0 or dst[size].qr ~= _qr then
dst[size+1] = {txt=txt, qr=_qr}
else
dst[size].txt = dst[size].txt..txt
end
end
-- token meta is {string,quote rule}
local tokens, token = {}, {}
local escaped, start = false, -1
for i = 1, unicode.len(input) do
local char = unicode.sub(input, i, i)
if escaped then -- escaped character
escaped = false
-- include escape char if show_escapes
-- or the followwing are all true
-- 1. qr active
-- 2. the char escaped is NOT the qr closure
-- 3. qr is not literal
if show_escapes or (qr and not qr[3] and qr[2] ~= char) then
append(token, '\\', qr)
end
append(token, char, qr)
elseif char == "\\" and (not qr or not qr[3]) then
escaped = true
elseif qr and qr[2] == char then -- end of quoted string
-- if string is empty, we can still capture a quoted empty arg
if #token == 0 or #token[#token] == 0 then
append(token, '', qr)
end
qr = nil
elseif not qr and tx.first(quotes,function(Q)
qr=Q[1]==char and Q or nil return qr end) then
start = i
elseif not qr and string.find(char, "%s") then
if #token > 0 then
table.insert(tokens, token)
end
token = {}
else -- normal char
append(token, char, qr)
end
end
if qr then
return nil, "unclosed quote at index " .. start
end
checkArg(1, input, "string")
checkArg(2, options, "table", "nil")
options = options or {}
local quotes = options.quotes
local show_escapes = options.show_escapes
local qr = nil
quotes = quotes or {{"'","'",true},{'"','"'},{'`','`'}}
local function append(dst, txt, _qr)
local size = #dst
if size == 0 or dst[size].qr ~= _qr then
dst[size+1] = {txt=txt, qr=_qr}
else
dst[size].txt = dst[size].txt..txt
end
end
-- token meta is {string,quote rule}
local tokens, token = {}, {}
local escaped, start = false, -1
for i = 1, unicode.len(input) do
local char = unicode.sub(input, i, i)
if escaped then -- escaped character
escaped = false
-- include escape char if show_escapes
-- or the followwing are all true
-- 1. qr active
-- 2. the char escaped is NOT the qr closure
-- 3. qr is not literal
if show_escapes or (qr and not qr[3] and qr[2] ~= char) then
append(token, '\\', qr)
end
append(token, char, qr)
elseif char == "\\" and (not qr or not qr[3]) then
escaped = true
elseif qr and qr[2] == char then -- end of quoted string
-- if string is empty, we can still capture a quoted empty arg
if #token == 0 or #token[#token] == 0 then
append(token, '', qr)
end
qr = nil
elseif not qr and tx.first(quotes,function(Q)
qr=Q[1]==char and Q or nil return qr end) then
start = i
elseif not qr and string.find(char, "%s") then
if #token > 0 then
table.insert(tokens, token)
end
token = {}
else -- normal char
append(token, char, qr)
end
end
if qr then
return nil, "unclosed quote at index " .. start
end
if #token > 0 then
table.insert(tokens, token)
end
if #token > 0 then
table.insert(tokens, token)
end
return tokens
return tokens
end
-- separate string value into an array of words delimited by whitespace
-- groups by quotes
-- options is a table used for internal undocumented purposes
function text.tokenize(value, options)
checkArg(1, value, "string")
checkArg(2, options, "table", "nil")
options = options or {}
checkArg(1, value, "string")
checkArg(2, options, "table", "nil")
options = options or {}
local tokens, reason = text.internal.tokenize(value, options)
local tokens, reason = text.internal.tokenize(value, options)
if type(tokens) ~= "table" then
return nil, reason
end
if type(tokens) ~= "table" then
return nil, reason
end
if options.doNotNormalize then
return tokens
end
if options.doNotNormalize then
return tokens
end
return text.internal.normalize(tokens)
return text.internal.normalize(tokens)
end
-------------------------------------------------------------------------------
@@ -146,41 +146,41 @@ end
-- splits input into an array for sub strings delimited by delimiters
-- delimiters are included in the result if not dropDelims
function text.split(input, delimiters, dropDelims, di)
checkArg(1, input, "string")
checkArg(2, delimiters, "table")
checkArg(3, dropDelims, "boolean", "nil")
checkArg(4, di, "number", "nil")
checkArg(1, input, "string")
checkArg(2, delimiters, "table")
checkArg(3, dropDelims, "boolean", "nil")
checkArg(4, di, "number", "nil")
if #input == 0 then return {} end
di = di or 1
local result = {input}
if di > #delimiters then return result end
if #input == 0 then return {} end
di = di or 1
local result = {input}
if di > #delimiters then return result end
local function add(part, index, r, s, e)
local sub = part:sub(s,e)
if #sub == 0 then return index end
local subs = r and text.split(sub,delimiters,dropDelims,r) or {sub}
for i=1,#subs do
table.insert(result, index+i-1, subs[i])
end
return index+#subs
end
local function add(part, index, r, s, e)
local sub = part:sub(s,e)
if #sub == 0 then return index end
local subs = r and text.split(sub,delimiters,dropDelims,r) or {sub}
for i=1,#subs do
table.insert(result, index+i-1, subs[i])
end
return index+#subs
end
local i,d=1,delimiters[di]
while true do
local next = table.remove(result,i)
if not next then break end
local si,ei = next:find(d)
if si and ei and ei~=0 then -- delim found
i=add(next, i, di+1, 1, si-1)
i=dropDelims and i or add(next, i, false, si, ei)
i=add(next, i, di, ei+1)
else
i=add(next, i, di+1, 1, #next)
end
end
local i,d=1,delimiters[di]
while true do
local next = table.remove(result,i)
if not next then break end
local si,ei = next:find(d)
if si and ei and ei~=0 then -- delim found
i=add(next, i, di+1, 1, si-1)
i=dropDelims and i or add(next, i, false, si, ei)
i=add(next, i, di, ei+1)
else
i=add(next, i, di+1, 1, #next)
end
end
return result
return result
end
-----------------------------------------------------------------------------
@@ -189,217 +189,217 @@ end
-- delimiters are kept as their own words
-- quoted word parts are not split
function text.internal.splitWords(words, delimiters)
checkArg(1,words,"table")
checkArg(2,delimiters,"table")
checkArg(1,words,"table")
checkArg(2,delimiters,"table")
local split_words = {}
local next_word
local function add_part(part)
if next_word then
split_words[#split_words+1] = {}
end
table.insert(split_words[#split_words], part)
next_word = false
end
for wi=1,#words do local word = words[wi]
next_word = true
for pi=1,#word do local part = word[pi]
local qr = part.qr
if qr then
add_part(part)
else
local part_text_splits = text.split(part.txt, delimiters)
tx.foreach(part_text_splits, function(sub_txt)
local delim = #text.split(sub_txt, delimiters, true) == 0
next_word = next_word or delim
add_part({txt=sub_txt,qr=qr})
next_word = delim
end)
end
end
end
local split_words = {}
local next_word
local function add_part(part)
if next_word then
split_words[#split_words+1] = {}
end
table.insert(split_words[#split_words], part)
next_word = false
end
for wi=1,#words do local word = words[wi]
next_word = true
for pi=1,#word do local part = word[pi]
local qr = part.qr
if qr then
add_part(part)
else
local part_text_splits = text.split(part.txt, delimiters)
tx.foreach(part_text_splits, function(sub_txt)
local delim = #text.split(sub_txt, delimiters, true) == 0
next_word = next_word or delim
add_part({txt=sub_txt,qr=qr})
next_word = delim
end)
end
end
end
return split_words
return split_words
end
function text.internal.normalize(words, omitQuotes)
checkArg(1, words, "table")
checkArg(2, omitQuotes, "boolean", "nil")
local norms = {}
for _,word in ipairs(words) do
local norm = {}
for _,part in ipairs(word) do
norm = tx.concat(norm, not omitQuotes and part.qr and {part.qr[1], part.txt, part.qr[2]} or {part.txt})
end
norms[#norms+1]=table.concat(norm)
end
return norms
checkArg(1, words, "table")
checkArg(2, omitQuotes, "boolean", "nil")
local norms = {}
for _,word in ipairs(words) do
local norm = {}
for _,part in ipairs(word) do
norm = tx.concat(norm, not omitQuotes and part.qr and {part.qr[1], part.txt, part.qr[2]} or {part.txt})
end
norms[#norms+1]=table.concat(norm)
end
return norms
end
function text.internal.stream_base(binary)
return
{
binary = binary,
plen = binary and string.len or unicode.len,
psub = binary and string.sub or unicode.sub,
seek = function (handle, whence, to)
if not handle.txt then
return nil, "bad file descriptor"
end
to = to or 0
local offset = handle:indexbytes()
if whence == "cur" then
offset = offset + to
elseif whence == "set" then
offset = to
elseif whence == "end" then
offset = handle.len + to
end
offset = math.max(0, math.min(offset, handle.len))
handle:byteindex(offset)
return offset
end,
indexbytes = function (handle)
return handle.psub(handle.txt, 1, handle.index):len()
end,
byteindex = function (handle, offset)
local sub = string.sub(handle.txt, 1, offset)
handle.index = handle.plen(sub)
end,
}
return
{
binary = binary,
plen = binary and string.len or unicode.len,
psub = binary and string.sub or unicode.sub,
seek = function (handle, whence, to)
if not handle.txt then
return nil, "bad file descriptor"
end
to = to or 0
local offset = handle:indexbytes()
if whence == "cur" then
offset = offset + to
elseif whence == "set" then
offset = to
elseif whence == "end" then
offset = handle.len + to
end
offset = math.max(0, math.min(offset, handle.len))
handle:byteindex(offset)
return offset
end,
indexbytes = function (handle)
return handle.psub(handle.txt, 1, handle.index):len()
end,
byteindex = function (handle, offset)
local sub = string.sub(handle.txt, 1, offset)
handle.index = handle.plen(sub)
end,
}
end
function text.internal.reader(txt, mode)
checkArg(1, txt, "string")
local reader = setmetatable(
{
txt = txt,
len = string.len(txt),
index = 0,
read = function(_, n)
checkArg(1, n, "number")
if not _.txt then
return nil, "bad file descriptor"
end
if _.index >= _.plen(_.txt) then
return nil
end
local next = _.psub(_.txt, _.index + 1, _.index + n)
_.index = _.index + _.plen(next)
return next
end,
close = function(_)
if not _.txt then
return nil, "bad file descriptor"
end
_.txt = nil
return true
end,
}, {__index=text.internal.stream_base(mode:match("b"))})
checkArg(1, txt, "string")
local reader = setmetatable(
{
txt = txt,
len = string.len(txt),
index = 0,
read = function(_, n)
checkArg(1, n, "number")
if not _.txt then
return nil, "bad file descriptor"
end
if _.index >= _.plen(_.txt) then
return nil
end
local next = _.psub(_.txt, _.index + 1, _.index + n)
_.index = _.index + _.plen(next)
return next
end,
close = function(_)
if not _.txt then
return nil, "bad file descriptor"
end
_.txt = nil
return true
end,
}, {__index=text.internal.stream_base(mode:match("b"))})
return require("buffer").new("r", reader)
return require("buffer").new("r", reader)
end
function text.internal.writer(ostream, mode, append_txt)
if type(ostream) == "table" then
local mt = getmetatable(ostream) or {}
checkArg(1, mt.__call, "function")
end
checkArg(1, ostream, "function", "table")
checkArg(2, append_txt, "string", "nil")
local writer = setmetatable(
{
txt = "",
index = 0, -- last location of write
len = 0,
write = function(_, ...)
if not _.txt then
return nil, "bad file descriptor"
end
local pre = _.psub(_.txt, 1, _.index)
local vs = {}
local pos = _.psub(_.txt, _.index + 1)
for _,v in ipairs({...}) do
table.insert(vs, v)
end
vs = table.concat(vs)
_.index = _.index + _.plen(vs)
_.txt = pre .. vs .. pos
_.len = string.len(_.txt)
return true
end,
close = function(_)
if not _.txt then
return nil, "bad file descriptor"
end
ostream((append_txt or "") .. _.txt)
_.txt = nil
return true
end,
}, {__index=text.internal.stream_base(mode:match("b"))})
if type(ostream) == "table" then
local mt = getmetatable(ostream) or {}
checkArg(1, mt.__call, "function")
end
checkArg(1, ostream, "function", "table")
checkArg(2, append_txt, "string", "nil")
local writer = setmetatable(
{
txt = "",
index = 0, -- last location of write
len = 0,
write = function(_, ...)
if not _.txt then
return nil, "bad file descriptor"
end
local pre = _.psub(_.txt, 1, _.index)
local vs = {}
local pos = _.psub(_.txt, _.index + 1)
for _,v in ipairs({...}) do
table.insert(vs, v)
end
vs = table.concat(vs)
_.index = _.index + _.plen(vs)
_.txt = pre .. vs .. pos
_.len = string.len(_.txt)
return true
end,
close = function(_)
if not _.txt then
return nil, "bad file descriptor"
end
ostream((append_txt or "") .. _.txt)
_.txt = nil
return true
end,
}, {__index=text.internal.stream_base(mode:match("b"))})
return require("buffer").new("w", writer)
return require("buffer").new("w", writer)
end
function text.detab(value, tabWidth)
checkArg(1, value, "string")
checkArg(2, tabWidth, "number", "nil")
tabWidth = tabWidth or 8
local function rep(match)
local spaces = tabWidth - match:len() % tabWidth
return match .. string.rep(" ", spaces)
end
local result = value:gsub("([^\n]-)\t", rep) -- truncate results
return result
checkArg(1, value, "string")
checkArg(2, tabWidth, "number", "nil")
tabWidth = tabWidth or 8
local function rep(match)
local spaces = tabWidth - match:len() % tabWidth
return match .. string.rep(" ", spaces)
end
local result = value:gsub("([^\n]-)\t", rep) -- truncate results
return result
end
function text.padLeft(value, length)
checkArg(1, value, "string", "nil")
checkArg(2, length, "number")
if not value or unicode.wlen(value) == 0 then
return string.rep(" ", length)
else
return string.rep(" ", length - unicode.wlen(value)) .. value
end
checkArg(1, value, "string", "nil")
checkArg(2, length, "number")
if not value or unicode.wlen(value) == 0 then
return string.rep(" ", length)
else
return string.rep(" ", length - unicode.wlen(value)) .. value
end
end
function text.padRight(value, length)
checkArg(1, value, "string", "nil")
checkArg(2, length, "number")
if not value or unicode.wlen(value) == 0 then
return string.rep(" ", length)
else
return value .. string.rep(" ", length - unicode.wlen(value))
end
checkArg(1, value, "string", "nil")
checkArg(2, length, "number")
if not value or unicode.wlen(value) == 0 then
return string.rep(" ", length)
else
return value .. string.rep(" ", length - unicode.wlen(value))
end
end
function text.wrap(value, width, maxWidth)
checkArg(1, value, "string")
checkArg(2, width, "number")
checkArg(3, maxWidth, "number")
local line, nl = value:match("([^\r\n]*)(\r?\n?)") -- read until newline
if unicode.wlen(line) > width then -- do we even need to wrap?
local partial = unicode.wtrunc(line, width)
local wrapped = partial:match("(.*[^a-zA-Z0-9._()'`=])")
if wrapped or unicode.wlen(line) > maxWidth then
partial = wrapped or partial
return partial, unicode.sub(value, unicode.len(partial) + 1), true
else
return "", value, true -- write in new line.
end
end
local start = unicode.len(line) + unicode.len(nl) + 1
return line, start <= unicode.len(value) and unicode.sub(value, start) or nil, unicode.len(nl) > 0
checkArg(1, value, "string")
checkArg(2, width, "number")
checkArg(3, maxWidth, "number")
local line, nl = value:match("([^\r\n]*)(\r?\n?)") -- read until newline
if unicode.wlen(line) > width then -- do we even need to wrap?
local partial = unicode.wtrunc(line, width)
local wrapped = partial:match("(.*[^a-zA-Z0-9._()'`=])")
if wrapped or unicode.wlen(line) > maxWidth then
partial = wrapped or partial
return partial, unicode.sub(value, unicode.len(partial) + 1), true
else
return "", value, true -- write in new line.
end
end
local start = unicode.len(line) + unicode.len(nl) + 1
return line, start <= unicode.len(value) and unicode.sub(value, start) or nil, unicode.len(nl) > 0
end
function text.wrappedLines(value, width, maxWidth)
local line
return function()
if value then
line, value = text.wrap(value, width, maxWidth)
return line
end
end
local line
return function()
if value then
line, value = text.wrap(value, width, maxWidth)
return line
end
end
end
return text

View File

@@ -5,269 +5,269 @@ local text = require("shellex.text")
local lib = {}
local function perr(ops, format, ...)
if format then
io.stderr:write(ops.cmd .. string.format(": " .. format, ...) .. "\n")
ops.exit_code = 1
return 1
end
if format then
io.stderr:write(ops.cmd .. string.format(": " .. format, ...) .. "\n")
ops.exit_code = 1
return 1
end
end
local function contents_check(arg, options, bMustExist)
if arg == "" then
return perr(options, "cannot create regular file '' No such file or directory")
end
local path = shell.resolve(arg)
local content_pattern = "^(%.*)(.?)"
local contents_of, of_dir = arg:reverse():match(content_pattern)
of_dir = of_dir:match("^/?$")
local dots = contents_of and contents_of:len() or 0
contents_of = of_dir and ({true,true})[dots]
if arg == "" then
return perr(options, "cannot create regular file '' No such file or directory")
end
local path = shell.resolve(arg)
local content_pattern = "^(%.*)(.?)"
local contents_of, of_dir = arg:reverse():match(content_pattern)
of_dir = of_dir:match("^/?$")
local dots = contents_of and contents_of:len() or 0
contents_of = of_dir and ({true,true})[dots]
if (not bMustExist or fs.exists(path)) and of_dir and not fs.isDirectory(path) then
perr(options, "'%s' is not a directory", arg)
os.exit(1)
end
if (not bMustExist or fs.exists(path)) and of_dir and not fs.isDirectory(path) then
perr(options, "'%s' is not a directory", arg)
os.exit(1)
end
return contents_of, path
return contents_of, path
end
local function areEqual(path1, path2)
local f1, f2 = fs.open(path1, "rb")
local result = true
if f1 then
f2 = fs.open(path2, "rb")
if f2 then
local chunkSize = 4 * 1024
repeat
local s1, s2 = f1:read(chunkSize), f2:read(chunkSize)
if s1 ~= s2 then
result = false
break
end
until not s1 or not s2
f2:close()
end
f1:close()
end
assert(f1 and f2, "could not open files for reading: " .. path1 .. ", " .. path2)
return result
local f1, f2 = fs.open(path1, "rb")
local result = true
if f1 then
f2 = fs.open(path2, "rb")
if f2 then
local chunkSize = 4 * 1024
repeat
local s1, s2 = f1:read(chunkSize), f2:read(chunkSize)
if s1 ~= s2 then
result = false
break
end
until not s1 or not s2
f2:close()
end
f1:close()
end
assert(f1 and f2, "could not open files for reading: " .. path1 .. ", " .. path2)
return result
end
local function status(verbose, from, to)
if verbose then
to = to and (" -> " .. to) or ""
io.write(from .. to .. "\n")
end
os.sleep(0) -- allow interrupting
if verbose then
to = to and (" -> " .. to) or ""
io.write(from .. to .. "\n")
end
os.sleep(0) -- allow interrupting
end
local function prompt(message)
io.write(message .. " [Y/n] ")
local result = io.read()
if not result then -- closed pipe
os.exit(1)
end
return result and (result == "" or result:sub(1, 1):lower() == "y")
io.write(message .. " [Y/n] ")
local result = io.read()
if not result then -- closed pipe
os.exit(1)
end
return result and (result == "" or result:sub(1, 1):lower() == "y")
end
local function stat(path, ops, P)
local real, reason = fs.realPath(path)
if not real and not P then
perr(ops, "cannot read '%s': '%s'", path, reason)
return false
end
local isLink, linkTarget = fs.isLink(path)
return true,
real,
reason,
isLink,
linkTarget,
fs.exists(path),
fs.get(path),
real and fs.isDirectory(real)
local real, reason = fs.realPath(path)
if not real and not P then
perr(ops, "cannot read '%s': '%s'", path, reason)
return false
end
local isLink, linkTarget = fs.isLink(path)
return true,
real,
reason,
isLink,
linkTarget,
fs.exists(path),
fs.get(path),
real and fs.isDirectory(real)
end
function lib.recurse(fromPath, toPath, options, origin, top)
fromPath = fromPath:gsub("/+", "/")
toPath = toPath:gsub("/+", "/")
local fromPathFull = shell.resolve(fromPath)
local toPathFull = shell.resolve(toPath)
local mv = options.cmd == "mv"
local verbose = options.v and (not mv or top)
if select(2, fromPathFull:find(options.skip)) == #fromPathFull then
status(verbose, string.format("skipping %s", fromPath))
return true
end
local function release(result, reason)
if result and mv and top then
local rm_result = not fs.get(fromPathFull).isReadOnly() and fs.remove(fromPathFull)
if not rm_result then
perr(options, "cannot remove '%s': filesystem is readonly", fromPath)
result = false
end
end
return result, reason
end
fromPath = fromPath:gsub("/+", "/")
toPath = toPath:gsub("/+", "/")
local fromPathFull = shell.resolve(fromPath)
local toPathFull = shell.resolve(toPath)
local mv = options.cmd == "mv"
local verbose = options.v and (not mv or top)
if select(2, fromPathFull:find(options.skip)) == #fromPathFull then
status(verbose, string.format("skipping %s", fromPath))
return true
end
local function release(result, reason)
if result and mv and top then
local rm_result = not fs.get(fromPathFull).isReadOnly() and fs.remove(fromPathFull)
if not rm_result then
perr(options, "cannot remove '%s': filesystem is readonly", fromPath)
result = false
end
end
return result, reason
end
local
ok,
fromReal,
_, --fromError,
fromIsLink,
fromLinkTarget,
fromExists,
fromFs,
fromIsDir = stat(fromPathFull, options, options.P)
if not ok then return nil end
local
ok,
toReal,
_,--toError,
toIsLink,
_,--toLinkTarget,
toExists,
toFs,
toIsDir = stat(toPathFull, options)
if not ok then os.exit(1) end
if toFs.isReadOnly() then
perr(options, "cannot create target '%s': filesystem is readonly", toPath)
return
end
local
ok,
fromReal,
_, --fromError,
fromIsLink,
fromLinkTarget,
fromExists,
fromFs,
fromIsDir = stat(fromPathFull, options, options.P)
if not ok then return nil end
local
ok,
toReal,
_,--toError,
toIsLink,
_,--toLinkTarget,
toExists,
toFs,
toIsDir = stat(toPathFull, options)
if not ok then os.exit(1) end
if toFs.isReadOnly() then
perr(options, "cannot create target '%s': filesystem is readonly", toPath)
return
end
local same_path = fromReal == toReal
local same_path = fromReal == toReal
local same_fs = fromFs == toFs
local is_mount = origin[fromReal]
local same_fs = fromFs == toFs
local is_mount = origin[fromReal]
if mv and is_mount then
return false, string.format("cannot move '%s', it is a mount point", fromPath)
end
if mv and is_mount then
return false, string.format("cannot move '%s', it is a mount point", fromPath)
end
if fromIsLink and options.P and not (toExists and same_path and not toIsLink) then
if toExists and options.n then
return true
end
fs.remove(toPathFull)
if toExists then
status(verbose, string.format("removed '%s'", toPath))
end
status(verbose, fromPath, toPath)
return release(fs.link(fromLinkTarget, toPathFull))
elseif fromIsDir then
if not options.r then
status(true, string.format("omitting directory '%s'", fromPath))
options.exit_code = 1
return true
end
if toExists and not toIsDir then
-- my real cp always does this, even with -f, -n or -i.
return nil, "cannot overwrite non-directory '" .. toPath .. "' with directory '" .. fromPath .. "'"
end
if options.x and not top and is_mount then
return true
end
if same_fs then
if (toReal.."/"):find(fromReal.."/",1,true) then
return nil, "cannot write a directory, '" .. fromPath .. "', into itself, '" .. toPath .. "'"
end
end
if mv then
if fs.list(toReal)() then -- to is NOT empty
return nil, "cannot move '" .. fromPath .. "' to '" .. toPath .. "': Directory not empty"
end
status(verbose, fromPath, toPath)
end
if not toExists then
status(verbose, fromPath, toPath)
fs.makeDirectory(toPathFull)
end
for file in fs.list(fromPathFull) do
local result, reason = lib.recurse(fromPath .."/".. file, toPath.."/"..file, options, origin, false)
-- false, no longer top
if not result then
return false, reason
end
end
return release(true)
elseif fromExists then
if toExists then
if same_path then
return nil, "'" .. fromPath .. "' and '" .. toPath .. "' are the same file"
end
if options.n then
return true
end
if options.u and not toIsDir and areEqual(fromReal, toReal) then
return true
end
if options.i then
if not prompt("overwrite '" .. toPath .. "'?") then
return true
end
end
if toIsDir then
return nil, "cannot overwrite directory '" .. toPath .. "' with non-directory"
end
fs.remove(toReal)
end
status(verbose, fromPath, toPath)
return release(fs.copy(fromPathFull, toPathFull))
else
return nil, "'" .. fromPath .. "': No such file or directory"
end
if fromIsLink and options.P and not (toExists and same_path and not toIsLink) then
if toExists and options.n then
return true
end
fs.remove(toPathFull)
if toExists then
status(verbose, string.format("removed '%s'", toPath))
end
status(verbose, fromPath, toPath)
return release(fs.link(fromLinkTarget, toPathFull))
elseif fromIsDir then
if not options.r then
status(true, string.format("omitting directory '%s'", fromPath))
options.exit_code = 1
return true
end
if toExists and not toIsDir then
-- my real cp always does this, even with -f, -n or -i.
return nil, "cannot overwrite non-directory '" .. toPath .. "' with directory '" .. fromPath .. "'"
end
if options.x and not top and is_mount then
return true
end
if same_fs then
if (toReal.."/"):find(fromReal.."/",1,true) then
return nil, "cannot write a directory, '" .. fromPath .. "', into itself, '" .. toPath .. "'"
end
end
if mv then
if fs.list(toReal)() then -- to is NOT empty
return nil, "cannot move '" .. fromPath .. "' to '" .. toPath .. "': Directory not empty"
end
status(verbose, fromPath, toPath)
end
if not toExists then
status(verbose, fromPath, toPath)
fs.makeDirectory(toPathFull)
end
for file in fs.list(fromPathFull) do
local result, reason = lib.recurse(fromPath .."/".. file, toPath.."/"..file, options, origin, false)
-- false, no longer top
if not result then
return false, reason
end
end
return release(true)
elseif fromExists then
if toExists then
if same_path then
return nil, "'" .. fromPath .. "' and '" .. toPath .. "' are the same file"
end
if options.n then
return true
end
if options.u and not toIsDir and areEqual(fromReal, toReal) then
return true
end
if options.i then
if not prompt("overwrite '" .. toPath .. "'?") then
return true
end
end
if toIsDir then
return nil, "cannot overwrite directory '" .. toPath .. "' with non-directory"
end
fs.remove(toReal)
end
status(verbose, fromPath, toPath)
return release(fs.copy(fromPathFull, toPathFull))
else
return nil, "'" .. fromPath .. "': No such file or directory"
end
end
function lib.batch(args, options)
options.exit_code = 0
options.exit_code = 0
-- standardized options
options.i = options.i and not options.f
options.P = options.P or options.r
options.skip = text.escapeMagic(options.skip or "")
-- standardized options
options.i = options.i and not options.f
options.P = options.P or options.r
options.skip = text.escapeMagic(options.skip or "")
local origin = {}
for dev,path in fs.mounts() do
origin[path] = dev
end
local origin = {}
for dev,path in fs.mounts() do
origin[path] = dev
end
local toArg = table.remove(args)
local _, ok = contents_check(toArg, options)
if not ok then
return 1
end
local originalToIsDir = fs.isDirectory(ok)
local toArg = table.remove(args)
local _, ok = contents_check(toArg, options)
if not ok then
return 1
end
local originalToIsDir = fs.isDirectory(ok)
for _, arg in ipairs(args) do
local files = glob.matches(shell.getWorkingDirectory(), arg)
for _, fromArg in pairs(files) do
-- a "contents of" copy is where src path ends in . or ..
-- a source path ending with . is not sufficient - could be the source filename
local contents_of
contents_of, ok = contents_check(fromArg, options, true)
if ok then
-- we do not append fromPath name to toPath in case of contents_of copy
local toPath = toArg
if contents_of and options.cmd == "mv" then
perr(options, "invalid move path '%s'", fromArg)
else
if not contents_of and originalToIsDir then
local fromName = fs.name(fromArg)
if fromName then
toPath = toPath .. "/" .. fromName
end
end
for _, arg in ipairs(args) do
local files = glob.matches(shell.getWorkingDirectory(), arg)
for _, fromArg in pairs(files) do
-- a "contents of" copy is where src path ends in . or ..
-- a source path ending with . is not sufficient - could be the source filename
local contents_of
contents_of, ok = contents_check(fromArg, options, true)
if ok then
-- we do not append fromPath name to toPath in case of contents_of copy
local toPath = toArg
if contents_of and options.cmd == "mv" then
perr(options, "invalid move path '%s'", fromArg)
else
if not contents_of and originalToIsDir then
local fromName = fs.name(fromArg)
if fromName then
toPath = toPath .. "/" .. fromName
end
end
local result, reason = lib.recurse(fromArg, toPath, options, origin, true)
local result, reason = lib.recurse(fromArg, toPath, options, origin, true)
if not result then
perr(options, reason)
end
end
end
end
end
if not result then
perr(options, reason)
end
end
end
end
end
return options.exit_code
return options.exit_code
end
return lib

View File

@@ -2,198 +2,198 @@ local lib={}
lib.internal={}
local function checkArg(n, have, ...)
have = type(have)
local function check(want, ...)
if not want then
return false
else
return have == want or check(...)
end
end
if not check(...) then
local msg = string.format("bad argument #%d (%s expected, got %s)",
n, table.concat({...}, " or "), have)
error(msg, 3)
end
have = type(have)
local function check(want, ...)
if not want then
return false
else
return have == want or check(...)
end
end
if not check(...) then
local msg = string.format("bad argument #%d (%s expected, got %s)",
n, table.concat({...}, " or "), have)
error(msg, 3)
end
end
function lib.internal.range_adjust(f,l,s)
checkArg(1,f,'number','nil')
checkArg(2,l,'number','nil')
checkArg(3,s,'number')
if f==nil then f=1 elseif f<0 then f=s+f+1 end
if l==nil then l=s elseif l<0 then l=s+l+1 end
return f,l
checkArg(1,f,'number','nil')
checkArg(2,l,'number','nil')
checkArg(3,s,'number')
if f==nil then f=1 elseif f<0 then f=s+f+1 end
if l==nil then l=s elseif l<0 then l=s+l+1 end
return f,l
end
function lib.internal.table_view(tbl,f,l)
return setmetatable({},
{
__index = function(_, key)
return (type(key) ~= 'number' or (key >= f and key <= l)) and tbl[key] or nil
end,
__len = function(_)
return l
end,
})
return setmetatable({},
{
__index = function(_, key)
return (type(key) ~= 'number' or (key >= f and key <= l)) and tbl[key] or nil
end,
__len = function(_)
return l
end,
})
end
local adjust=lib.internal.range_adjust
local view=lib.internal.table_view
-- first(p1,p2) searches for the first range in p1 that satisfies p2
function lib.first(tbl,pred,f,l)
checkArg(1,tbl,'table')
checkArg(2,pred,'function','table')
if type(pred)=='table'then
local set;set,pred=pred,function(_,fi,tbl)
for vi=1,#set do
local v=set[vi]
if lib.begins(tbl,v,fi) then return true,#v end
end
end
end
local s=#tbl
f,l=adjust(f,l,s)
tbl=view(tbl,f,l)
for i=f,l do
local si,ei=pred(tbl[i],i,tbl)
if si then
return i,i+(ei or 1)-1
end
end
checkArg(1,tbl,'table')
checkArg(2,pred,'function','table')
if type(pred)=='table'then
local set;set,pred=pred,function(_,fi,tbl)
for vi=1,#set do
local v=set[vi]
if lib.begins(tbl,v,fi) then return true,#v end
end
end
end
local s=#tbl
f,l=adjust(f,l,s)
tbl=view(tbl,f,l)
for i=f,l do
local si,ei=pred(tbl[i],i,tbl)
if si then
return i,i+(ei or 1)-1
end
end
end
-- returns true if p1 at first p3 equals element for element p2
function lib.begins(tbl,v,f,l)
checkArg(1,tbl,'table')
checkArg(2,v,'table')
local vs=#v
f,l=adjust(f,l,#tbl)
if vs>(l-f+1)then return end
for i=1,vs do
if tbl[f+i-1]~=v[i] then return end
end
return true
checkArg(1,tbl,'table')
checkArg(2,v,'table')
local vs=#v
f,l=adjust(f,l,#tbl)
if vs>(l-f+1)then return end
for i=1,vs do
if tbl[f+i-1]~=v[i] then return end
end
return true
end
function lib.concat(...)
local r,rn,k={},0
for _,tbl in ipairs({...})do
if type(tbl)~='table'then
return nil,'parameter '..tostring(_)..' to concat is not a table'
end
local n=tbl.n or #tbl
k=k or tbl.n
for i=1,n do
rn=rn+1;r[rn]=tbl[i]
end
end
r.n=k and rn or nil
return r
local r,rn,k={},0
for _,tbl in ipairs({...})do
if type(tbl)~='table'then
return nil,'parameter '..tostring(_)..' to concat is not a table'
end
local n=tbl.n or #tbl
k=k or tbl.n
for i=1,n do
rn=rn+1;r[rn]=tbl[i]
end
end
r.n=k and rn or nil
return r
end
-- works like string.sub but on elements of an indexed table
function lib.sub(tbl,f,l)
checkArg(1,tbl,'table')
local r,s={},#tbl
f,l=adjust(f,l,s)
l=math.min(l,s)
for i=math.max(f,1),l do
r[#r+1]=tbl[i]
end
return r
checkArg(1,tbl,'table')
local r,s={},#tbl
f,l=adjust(f,l,s)
l=math.min(l,s)
for i=math.max(f,1),l do
r[#r+1]=tbl[i]
end
return r
end
-- Returns a list of subsets of tbl where partitioner acts as a delimiter.
function lib.partition(tbl,partitioner,dropEnds,f,l)
checkArg(1,tbl,'table')
checkArg(2,partitioner,'function','table')
checkArg(3,dropEnds,'boolean','nil')
if type(partitioner)=='table'then
return lib.partition(tbl,function(_,i,tbl)
return lib.first(tbl,partitioner,i)
end,dropEnds,f,l)
end
local s=#tbl
f,l=adjust(f,l,s)
local cut=view(tbl,f,l)
local result={}
local need=true
local exp=function()if need then result[#result+1]={}need=false end end
local i=f
while i<=l do
local e=cut[i]
local ds,de=partitioner(e,i,cut)
-- true==partition here
if ds==true then ds,de=i,i
elseif ds==false then ds,de=nil,nil end
if ds~=nil then
ds,de=adjust(ds,de,l)
ds=ds>=i and ds--no more
end
if not ds then -- false or nil
exp()
table.insert(result[#result],e)
else
local sub=lib.sub(cut,i,not dropEnds and de or (ds-1))
if #sub>0 then
exp()
result[#result+math.min(#result[#result],1)]=sub
end
-- ensure i moves forward
local ensured=math.max(math.max(de or ds,ds),i)
if de and ds and de<ds and ensured==i then
if #result==0 then result[1]={} end
table.insert(result[#result],e)
end
i=ensured
need=true
end
i=i+1
end
checkArg(1,tbl,'table')
checkArg(2,partitioner,'function','table')
checkArg(3,dropEnds,'boolean','nil')
if type(partitioner)=='table'then
return lib.partition(tbl,function(_,i,tbl)
return lib.first(tbl,partitioner,i)
end,dropEnds,f,l)
end
local s=#tbl
f,l=adjust(f,l,s)
local cut=view(tbl,f,l)
local result={}
local need=true
local exp=function()if need then result[#result+1]={}need=false end end
local i=f
while i<=l do
local e=cut[i]
local ds,de=partitioner(e,i,cut)
-- true==partition here
if ds==true then ds,de=i,i
elseif ds==false then ds,de=nil,nil end
if ds~=nil then
ds,de=adjust(ds,de,l)
ds=ds>=i and ds--no more
end
if not ds then -- false or nil
exp()
table.insert(result[#result],e)
else
local sub=lib.sub(cut,i,not dropEnds and de or (ds-1))
if #sub>0 then
exp()
result[#result+math.min(#result[#result],1)]=sub
end
-- ensure i moves forward
local ensured=math.max(math.max(de or ds,ds),i)
if de and ds and de<ds and ensured==i then
if #result==0 then result[1]={} end
table.insert(result[#result],e)
end
i=ensured
need=true
end
i=i+1
end
return result
return result
end
-- calls callback(e,i,tbl) for each ith element e in table tbl from first
function lib.foreach(tbl,c,f,l)
checkArg(1,tbl,'table')
checkArg(2,c,'function','string')
local ck=c
c=type(c)=="string" and function(e) return e[ck] end or c
local s=#tbl
f,l=adjust(f,l,s)
tbl=view(tbl,f,l)
local r={}
for i=f,l do
local n,k=c(tbl[i],i,tbl)
if n~=nil then
if k then r[k]=n
else r[#r+1]=n end
end
end
return r
checkArg(1,tbl,'table')
checkArg(2,c,'function','string')
local ck=c
c=type(c)=="string" and function(e) return e[ck] end or c
local s=#tbl
f,l=adjust(f,l,s)
tbl=view(tbl,f,l)
local r={}
for i=f,l do
local n,k=c(tbl[i],i,tbl)
if n~=nil then
if k then r[k]=n
else r[#r+1]=n end
end
end
return r
end
function lib.where(tbl,p,f,l)
return lib.foreach(tbl,
function(e,i,tbl)
return p(e,i,tbl)and e or nil
end,f,l)
return lib.foreach(tbl,
function(e,i,tbl)
return p(e,i,tbl)and e or nil
end,f,l)
end
-- works with pairs on tables
-- returns the kv pair, or nil and the number of pairs iterated
function lib.at(tbl, index)
checkArg(1, tbl, "table")
checkArg(2, index, "number", "nil")
local current_index = 1
for k,v in pairs(tbl) do
if current_index == index then
return k,v
end
current_index = current_index + 1
end
return nil, current_index - 1 -- went one too far
checkArg(1, tbl, "table")
checkArg(2, index, "number", "nil")
local current_index = 1
for k,v in pairs(tbl) do
if current_index == index then
return k,v
end
current_index = current_index + 1
end
return nil, current_index - 1 -- went one too far
end
return lib

View File

@@ -4,31 +4,31 @@ local term = _G.term
local w, h = term.getSize()
local cmap = {
[ 0xCC2200 ] = colors.red,
[ 0x44CC00 ] = colors.lime,
[ 0xB0B00F ] = colors.yellow,
[ 0xFFFFFF ] = colors.white,
[ 0xCC2200 ] = colors.red,
[ 0x44CC00 ] = colors.lime,
[ 0xB0B00F ] = colors.yellow,
[ 0xFFFFFF ] = colors.white,
[ 0xb000b0 ] = colors.purple,
[ 0x00FF00 ] = colors.green,
[ 0xFF0000 ] = colors.red,
[ 0x00FFFF ] = colors.cyan,
[ 0x000000 ] = colors.black,
[ 0xb000b0 ] = colors.purple,
[ 0x00FF00 ] = colors.green,
[ 0xFF0000 ] = colors.red,
[ 0x00FFFF ] = colors.cyan,
[ 0x000000 ] = colors.black,
}
return {
gpu = function()
local current = 0xFFFFFF
return {
setForeground = function(c)
current = c
term.setTextColor(cmap[c])
end,
getForeground = function() return current end,
}
end,
getViewport = term.getSize,
window = {
width = w, height = h,
}
gpu = function()
local current = 0xFFFFFF
return {
setForeground = function(c)
current = c
term.setTextColor(cmap[c])
end,
getForeground = function() return current end,
}
end,
getViewport = term.getSize,
window = {
width = w, height = h,
}
}

View File

@@ -1,3 +1,3 @@
return {
wlen = string.len
wlen = string.len
}