refactor parallel code
This commit is contained in:
3
openos/apis/computer.lua
Normal file
3
openos/apis/computer.lua
Normal file
@@ -0,0 +1,3 @@
|
||||
return {
|
||||
uptime = os.clock,
|
||||
}
|
||||
59
openos/apis/filesystem.lua
Normal file
59
openos/apis/filesystem.lua
Normal file
@@ -0,0 +1,59 @@
|
||||
local fs = _G.fs
|
||||
|
||||
local function get(path)
|
||||
local label = fs.getDrive(path)
|
||||
|
||||
if label then
|
||||
local proxy = {
|
||||
getLabel = function() return label 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
|
||||
|
||||
return function()
|
||||
local label, path = next(t)
|
||||
if label then
|
||||
t[label] = nil
|
||||
return get(path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function list(path)
|
||||
local set = fs.list(path)
|
||||
return function()
|
||||
local key, value = next(set)
|
||||
set[key or false] = nil
|
||||
return value
|
||||
end
|
||||
end
|
||||
|
||||
return {
|
||||
canonical = function(...) return ... end,
|
||||
concat = fs.combine,
|
||||
exists = fs.exists,
|
||||
get = get,
|
||||
isDirectory = fs.isDir,
|
||||
isLink = function() return false end,
|
||||
list = list,
|
||||
mounts = mounts,
|
||||
name = fs.getName,
|
||||
open = function(n, m) return fs.open(n, m or 'r') end,
|
||||
realPath = function(...) return ... end,
|
||||
size = fs.getSize,
|
||||
}
|
||||
3
openos/apis/keyboard.lua
Normal file
3
openos/apis/keyboard.lua
Normal file
@@ -0,0 +1,3 @@
|
||||
return {
|
||||
keys = _G.keys,
|
||||
}
|
||||
6
openos/apis/sh.lua
Normal file
6
openos/apis/sh.lua
Normal file
@@ -0,0 +1,6 @@
|
||||
local shell = _ENV.shell
|
||||
|
||||
return {
|
||||
execute = function(_, ...) return shell.run(...) end,
|
||||
getLastExitCode = function() return 0 end,
|
||||
}
|
||||
9
openos/apis/shell.lua
Normal file
9
openos/apis/shell.lua
Normal file
@@ -0,0 +1,9 @@
|
||||
local Util = require('util')
|
||||
|
||||
local shell = _ENV.shell
|
||||
|
||||
return {
|
||||
getWorkingDirectory = shell.dir,
|
||||
resolve = shell.resolve,
|
||||
parse = Util.parse,
|
||||
}
|
||||
1
openos/apis/term.lua
Normal file
1
openos/apis/term.lua
Normal file
@@ -0,0 +1 @@
|
||||
return _G.term
|
||||
405
openos/apis/text.lua
Normal file
405
openos/apis/text.lua
Normal file
@@ -0,0 +1,405 @@
|
||||
local unicode = require("openos.unicode")
|
||||
local tx = require("openos.transforms")
|
||||
|
||||
local text = {}
|
||||
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
|
||||
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)
|
||||
end
|
||||
|
||||
-- used by lib/sh
|
||||
function text.escapeMagic(txt)
|
||||
return txt:gsub('[%(%)%.%%%+%-%*%?%[%^%$]', '%%%1')
|
||||
end
|
||||
|
||||
function text.removeEscapes(txt)
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
if #token > 0 then
|
||||
table.insert(tokens, token)
|
||||
end
|
||||
|
||||
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 {}
|
||||
|
||||
local tokens, reason = text.internal.tokenize(value, options)
|
||||
|
||||
if type(tokens) ~= "table" then
|
||||
return nil, reason
|
||||
end
|
||||
|
||||
if options.doNotNormalize then
|
||||
return tokens
|
||||
end
|
||||
|
||||
return text.internal.normalize(tokens)
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- like tokenize, but does not drop any text such as whitespace
|
||||
-- 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")
|
||||
|
||||
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 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
|
||||
end
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
-- splits each word into words at delimiters
|
||||
-- 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")
|
||||
|
||||
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
|
||||
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
|
||||
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,
|
||||
}
|
||||
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"))})
|
||||
|
||||
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"))})
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
end
|
||||
|
||||
return text
|
||||
269
openos/apis/transfer.lua
Normal file
269
openos/apis/transfer.lua
Normal file
@@ -0,0 +1,269 @@
|
||||
local fs = require("openos.filesystem")
|
||||
local shell = require("openos.shell")
|
||||
local text = require("openos.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
|
||||
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 (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
|
||||
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
|
||||
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
|
||||
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")
|
||||
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)
|
||||
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
|
||||
|
||||
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_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 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
|
||||
|
||||
-- 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 toArg = table.remove(args)
|
||||
local _, ok = contents_check(toArg, options)
|
||||
if not ok then
|
||||
return 1
|
||||
end
|
||||
local originalToIsDir = fs.isDirectory(ok)
|
||||
|
||||
for _, fromArg in ipairs(args) 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)
|
||||
|
||||
if not result then
|
||||
perr(options, reason)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return options.exit_code
|
||||
end
|
||||
|
||||
return lib
|
||||
200
openos/apis/transforms.lua
Normal file
200
openos/apis/transforms.lua
Normal file
@@ -0,0 +1,200 @@
|
||||
|
||||
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
|
||||
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
|
||||
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,
|
||||
})
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
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
|
||||
|
||||
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
|
||||
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)
|
||||
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
|
||||
end
|
||||
|
||||
return lib
|
||||
23
openos/apis/tty.lua
Normal file
23
openos/apis/tty.lua
Normal file
@@ -0,0 +1,23 @@
|
||||
local colors = _G.colors
|
||||
local term = _G.term
|
||||
|
||||
local w, h = term.getSize()
|
||||
|
||||
local cmap = {
|
||||
[ 0xCC2200 ] = colors.red,
|
||||
[ 0x44CC00 ] = colors.lime,
|
||||
[ 0xB0B00F ] = colors.yellow,
|
||||
[ 0xFFFFFF ] = colors.white,
|
||||
}
|
||||
|
||||
return {
|
||||
gpu = function()
|
||||
return {
|
||||
setForeground = function(c) term.setTextColor(cmap[c]) end,
|
||||
}
|
||||
end,
|
||||
getViewport = term.getSize,
|
||||
window = {
|
||||
width = w, height = h,
|
||||
}
|
||||
}
|
||||
3
openos/apis/unicode.lua
Normal file
3
openos/apis/unicode.lua
Normal file
@@ -0,0 +1,3 @@
|
||||
return {
|
||||
wlen = string.len
|
||||
}
|
||||
Reference in New Issue
Block a user