refactor parallel code

This commit is contained in:
kepler155c@gmail.com
2019-04-05 17:32:22 -04:00
parent d5eb07c3b9
commit 426c856dfb
44 changed files with 3645 additions and 182 deletions

3
openos/apis/computer.lua Normal file
View File

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

View 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
View File

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

6
openos/apis/sh.lua Normal file
View 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
View 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
View File

@@ -0,0 +1 @@
return _G.term

405
openos/apis/text.lua Normal file
View 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
View 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
View 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
View 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
View File

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