spaces->tabs + cleanup + pathing fixes

This commit is contained in:
kepler155c@gmail.com 2019-06-18 15:19:24 -04:00
parent 82ec4db50f
commit 3c22a872b0
37 changed files with 1948 additions and 1703 deletions

View File

@ -5,29 +5,29 @@ local parallel = _G.parallel
local BulkGet = { }
function BulkGet.download(list, callback)
local t = { }
local failed = false
local t = { }
local failed = false
for _ = 1, 5 do
table.insert(t, function()
while true do
local entry = table.remove(list)
if not entry then
break
end
local s, m = Util.download(entry.url, entry.path)
if not s then
failed = true
end
callback(entry, s, m)
if failed then
break
end
end
end)
end
for _ = 1, 5 do
table.insert(t, function()
while true do
local entry = table.remove(list)
if not entry then
break
end
local s, m = Util.download(entry.url, entry.path)
if not s then
failed = true
end
callback(entry, s, m)
if failed then
break
end
end
end)
end
parallel.waitForAll(table.unpack(t))
parallel.waitForAll(table.unpack(t))
end
return BulkGet

View File

@ -1,61 +0,0 @@
--[[
Mount a readonly file system from another computer across rednet. The
target computer must be running OpusOS or redserver. Dissimlar to samba
in that a snapshot of the target is taken upon mounting - making this
faster.
Useful for mounting a non-changing directory tree.
Syntax:
rttp://<id>/directory/subdir
Examples:
rttp://12/usr/etc
rttp://8/usr
]]--
local rttp = require('rttp')
local fs = _G.fs
local redfs = { }
local function getListing(uri)
local success, response = rttp.get(uri .. '?recursive=true')
if not success then
error(response)
end
if response.statusCode ~= 200 then
error('Received response ' .. response.statusCode)
end
local list = { }
for _,v in pairs(response.data) do
if not v.isDir then
list[v.path] = {
url = uri .. '/' .. v.path,
size = v.size,
}
end
end
return list
end
function redfs.mount(dir, uri)
if not uri then
error('redfs syntax: uri')
end
local list = getListing(uri)
for path, entry in pairs(list) do
if not fs.exists(fs.combine(dir, path)) then
local node = fs.mount(fs.combine(dir, path), 'urlfs', entry.url)
node.size = entry.size
end
end
end
return redfs

View File

@ -1,4 +1,4 @@
local rttp = require('rttp')
--local rttp = require('rttp')
local Util = require('util')
local fs = _G.fs
@ -39,7 +39,6 @@ function urlfs.getDrive()
end
function urlfs.open(node, fn, fl)
if fl == 'w' or fl == 'wb' then
fs.delete(fn)
return fs.open(fn, fl)
@ -51,12 +50,15 @@ function urlfs.open(node, fn, fl)
local c = node.cache
if not c then
--[[
if node.url:match("^(rttps?:)") then
local s, response = rttp.get(node.url)
c = s and response.statusCode == 200 and response.data
else
c = Util.httpGet(node.url)
end
]]--
c = Util.httpGet(node.url)
if c then
node.cache = c
node.size = #c

View File

@ -1,215 +1,584 @@
-- credit ElvishJerricco
-- http://pastebin.com/raw.php?i=4nRg9CHU
-- Module options:
local register_global_module_table = false
local global_module_name = 'json'
local json = { }
--[==[
NOTE: Modified to reduce file size.
See https://github.com/LuaDist/dkjson/blob/master/dkjson.lua
for full version.
------------------------------------------------------------------ utils
local controls = {["\n"]="\\n", ["\r"]="\\r", ["\t"]="\\t", ["\b"]="\\b", ["\f"]="\\f", ["\""]="\\\"", ["\\"]="\\\\"}
David Kolf's JSON module for Lua 5.1/5.2
Version 2.5
local function isArray(t)
local max = 0
for k,v in pairs(t) do
if type(k) ~= "number" then
return false
elseif k > max then
max = k
end
end
return max == #t
For the documentation see the corresponding readme.txt or visit
<http://dkolf.de/src/dkjson-lua.fsl/>.
You can contact the author by sending an e-mail to 'david' at the
domain 'dkolf.de'.
Copyright (C) 2010-2014 David Heiko Kolf
Refer to license located at https://github.com/LuaDist/dkjson/blob/master/dkjson.lua
--]==]
-- global dependencies:
local pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset =
pairs, type, tostring, tonumber, getmetatable, setmetatable, rawset
local error, require, pcall, select = error, require, pcall, select
local floor, huge = math.floor, math.huge
local strrep, gsub, strsub, strbyte, strchar, strfind, strlen, strformat =
string.rep, string.gsub, string.sub, string.byte, string.char,
string.find, string.len, string.format
local strmatch = string.match
local concat = table.concat
local json = { version = "dkjson 2.5" }
if register_global_module_table then
_G[global_module_name] = json
end
local whites = {['\n']=true; ['\r']=true; ['\t']=true; [' ']=true; [',']=true; [':']=true}
local function removeWhite(str)
while whites[str:sub(1, 1)] do
str = str:sub(2)
end
return str
end
local _ENV = nil -- blocking globals in Lua 5.2
------------------------------------------------------------------ encoding
pcall (function()
-- Enable access to blocked metatables.
-- Don't worry, this module doesn't change anything in them.
local debmeta = require "debug".getmetatable
if debmeta then getmetatable = debmeta end
end)
local function encodeCommon(val, pretty, tabLevel, tTracking)
local str = ""
json.null = setmetatable ({}, {
__tojson = function () return "null" end
})
-- Tabbing util
local function tab(s)
str = str .. ("\t"):rep(tabLevel) .. s
end
local function arrEncoding(val, bracket, closeBracket, iterator, loopFunc)
str = str .. bracket
if pretty then
str = str .. "\n"
tabLevel = tabLevel + 1
end
for k,v in iterator(val) do
tab("")
loopFunc(k,v)
str = str .. ","
if pretty then str = str .. "\n" end
end
if pretty then
tabLevel = tabLevel - 1
end
if str:sub(-2) == ",\n" then
str = str:sub(1, -3) .. "\n"
elseif str:sub(-1) == "," then
str = str:sub(1, -2)
end
tab(closeBracket)
end
-- Table encoding
if type(val) == "table" then
assert(not tTracking[val], "Cannot encode a table holding itself recursively")
tTracking[val] = true
if isArray(val) then
arrEncoding(val, "[", "]", ipairs, function(k,v)
str = str .. encodeCommon(v, pretty, tabLevel, tTracking)
end)
local function isarray (tbl)
local max, n, arraylen = 0, 0, 0
for k,v in pairs (tbl) do
if k == 'n' and type(v) == 'number' then
arraylen = v
if v > max then
max = v
end
else
arrEncoding(val, "{", "}", pairs, function(k,v)
assert(type(k) == "string", "JSON object keys must be strings", 2)
str = str .. encodeCommon(k, pretty, tabLevel, tTracking)
str = str .. (pretty and ": " or ":") .. encodeCommon(v, pretty, tabLevel, tTracking)
end)
if type(k) ~= 'number' or k < 1 or floor(k) ~= k then
return false
end
if k > max then
max = k
end
n = n + 1
end
-- String encoding
elseif type(val) == "string" then
str = '"' .. val:gsub("[%c\"\\]", controls) .. '"'
-- Number encoding
elseif type(val) == "number" or type(val) == "boolean" then
str = tostring(val)
end
if max > 10 and max > arraylen and max > n * 2 then
return false -- don't create an array with too many holes
end
return true, max
end
local escapecodes = {
["\""] = "\\\"", ["\\"] = "\\\\", ["\b"] = "\\b", ["\f"] = "\\f",
["\n"] = "\\n", ["\r"] = "\\r", ["\t"] = "\\t"
}
local function escapeutf8 (uchar)
local value = escapecodes[uchar]
if value then
return value
end
local a, b, c, d = strbyte (uchar, 1, 4)
a, b, c, d = a or 0, b or 0, c or 0, d or 0
if a <= 0x7f then
value = a
elseif 0xc0 <= a and a <= 0xdf and b >= 0x80 then
value = (a - 0xc0) * 0x40 + b - 0x80
elseif 0xe0 <= a and a <= 0xef and b >= 0x80 and c >= 0x80 then
value = ((a - 0xe0) * 0x40 + b - 0x80) * 0x40 + c - 0x80
elseif 0xf0 <= a and a <= 0xf7 and b >= 0x80 and c >= 0x80 and d >= 0x80 then
value = (((a - 0xf0) * 0x40 + b - 0x80) * 0x40 + c - 0x80) * 0x40 + d - 0x80
else
error("JSON only supports arrays, objects, numbers, booleans, and strings", 2)
return ""
end
return str
end
function json.encode(val)
return encodeCommon(val, false, 0, {})
end
function json.encodePretty(val)
return encodeCommon(val, true, 0, {})
end
function json.encodeToFile(path, val)
local file = io.open(path, "w")
assert(file, "Unable to open file")
file:write(json.encodePretty(val))
file:close()
end
------------------------------------------------------------------ decoding
local decodeControls = {}
for k,v in pairs(controls) do
decodeControls[v] = k
end
local function parseBoolean(str)
if str:sub(1, 4) == "true" then
return true, removeWhite(str:sub(5))
if value <= 0xffff then
return strformat ("\\u%.4x", value)
elseif value <= 0x10ffff then
-- encode as UTF-16 surrogate pair
value = value - 0x10000
local highsur, lowsur = 0xD800 + floor (value/0x400), 0xDC00 + (value % 0x400)
return strformat ("\\u%.4x\\u%.4x", highsur, lowsur)
else
return false, removeWhite(str:sub(6))
return ""
end
end
local function parseNull(str)
return nil, removeWhite(str:sub(5))
end
local numChars = {['e']=true; ['E']=true; ['+']=true; ['-']=true; ['.']=true}
local function parseNumber(str)
local i = 1
while numChars[str:sub(i, i)] or tonumber(str:sub(i, i)) do
i = i + 1
local function fsub (str, pattern, repl)
-- gsub always builds a new string in a buffer, even when no match
-- exists. First using find should be more efficient when most strings
-- don't contain the pattern.
if strfind (str, pattern) then
return gsub (str, pattern, repl)
else
return str
end
local val = tonumber(str:sub(1, i - 1))
str = removeWhite(str:sub(i))
return val, str
end
local function parseString(str)
str = str:sub(2)
local s = ""
while str:sub(1,1) ~= "\"" do
local next = str:sub(1,1)
str = str:sub(2)
assert(next ~= "\n", "Unclosed string")
local function quotestring (value)
-- based on the regexp "escapable" in https://github.com/douglascrockford/JSON-js
value = fsub (value, "[%z\1-\31\"\\\127]", escapeutf8)
if strfind (value, "[\194\216\220\225\226\239]") then
value = fsub (value, "\194[\128-\159\173]", escapeutf8)
value = fsub (value, "\216[\128-\132]", escapeutf8)
value = fsub (value, "\220\143", escapeutf8)
value = fsub (value, "\225\158[\180\181]", escapeutf8)
value = fsub (value, "\226\128[\140-\143\168-\175]", escapeutf8)
value = fsub (value, "\226\129[\160-\175]", escapeutf8)
value = fsub (value, "\239\187\191", escapeutf8)
value = fsub (value, "\239\191[\176-\191]", escapeutf8)
end
return "\"" .. value .. "\""
end
json.quotestring = quotestring
if next == "\\" then
local escape = str:sub(1,1)
str = str:sub(2)
local function replace(str, o, n)
local i, j = strfind (str, o, 1, true)
if i then
return strsub(str, 1, i-1) .. n .. strsub(str, j+1, -1)
else
return str
end
end
next = assert(decodeControls[next..escape], "Invalid escape character")
-- locale independent num2str and str2num functions
local decpoint, numfilter
local function updatedecpoint ()
decpoint = strmatch(tostring(0.5), "([^05+])")
-- build a filter that can be used to remove group separators
numfilter = "[^0-9%-%+eE" .. gsub(decpoint, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") .. "]+"
end
updatedecpoint()
local function num2str (num)
return replace(fsub(tostring(num), numfilter, ""), decpoint, ".")
end
local function str2num (str)
local num = tonumber(replace(str, ".", decpoint))
if not num then
updatedecpoint()
num = tonumber(replace(str, ".", decpoint))
end
return num
end
local function addnewline2 (level, buffer, buflen)
buffer[buflen+1] = "\n"
buffer[buflen+2] = strrep (" ", level)
buflen = buflen + 2
return buflen
end
function json.addnewline (state)
if state.indent then
state.bufferlen = addnewline2 (state.level or 0,
state.buffer, state.bufferlen or #(state.buffer))
end
end
local encode2 -- forward declaration
local function addpair (key, value, prev, indent, level, buffer, buflen, tables, globalorder, state)
local kt = type (key)
if kt ~= 'string' and kt ~= 'number' then
return nil, "type '" .. kt .. "' is not supported as a key by JSON."
end
if prev then
buflen = buflen + 1
buffer[buflen] = ","
end
if indent then
buflen = addnewline2 (level, buffer, buflen)
end
buffer[buflen+1] = quotestring (key)
buffer[buflen+2] = ":"
return encode2 (value, indent, level, buffer, buflen + 2, tables, globalorder, state)
end
local function appendcustom(res, buffer, state)
local buflen = state.bufferlen
if type (res) == 'string' then
buflen = buflen + 1
buffer[buflen] = res
end
return buflen
end
local function exception(reason, value, state, buffer, buflen, defaultmessage)
defaultmessage = defaultmessage or reason
local handler = state.exception
if not handler then
return nil, defaultmessage
else
state.bufferlen = buflen
local ret, msg = handler (reason, value, state, defaultmessage)
if not ret then return nil, msg or defaultmessage end
return appendcustom(ret, buffer, state)
end
end
function json.encodeexception(reason, value, state, defaultmessage)
return quotestring("<" .. defaultmessage .. ">")
end
encode2 = function (value, indent, level, buffer, buflen, tables, globalorder, state)
local valtype = type (value)
local valmeta = getmetatable (value)
valmeta = type (valmeta) == 'table' and valmeta -- only tables
local valtojson = valmeta and valmeta.__tojson
if valtojson then
if tables[value] then
return exception('reference cycle', value, state, buffer, buflen)
end
s = s .. next
tables[value] = true
state.bufferlen = buflen
local ret, msg = valtojson (value, state)
if not ret then return exception('custom encoder failed', value, state, buffer, buflen, msg) end
tables[value] = nil
buflen = appendcustom(ret, buffer, state)
elseif value == nil then
buflen = buflen + 1
buffer[buflen] = "null"
elseif valtype == 'number' then
local s
if value ~= value or value >= huge or -value >= huge then
-- This is the behaviour of the original JSON implementation.
s = "null"
else
s = num2str (value)
end
buflen = buflen + 1
buffer[buflen] = s
elseif valtype == 'boolean' then
buflen = buflen + 1
buffer[buflen] = value and "true" or "false"
elseif valtype == 'string' then
buflen = buflen + 1
buffer[buflen] = quotestring (value)
elseif valtype == 'table' then
if tables[value] then
return exception('reference cycle', value, state, buffer, buflen)
end
tables[value] = true
level = level + 1
local isa, n = isarray (value)
if n == 0 and valmeta and valmeta.__jsontype == 'object' then
isa = false
end
local msg
if isa then -- JSON array
buflen = buflen + 1
buffer[buflen] = "["
for i = 1, n do
buflen, msg = encode2 (value[i], indent, level, buffer, buflen, tables, globalorder, state)
if not buflen then return nil, msg end
if i < n then
buflen = buflen + 1
buffer[buflen] = ","
end
end
buflen = buflen + 1
buffer[buflen] = "]"
else -- JSON object
local prev = false
buflen = buflen + 1
buffer[buflen] = "{"
local order = valmeta and valmeta.__jsonorder or globalorder
if order then
local used = {}
n = #order
for i = 1, n do
local k = order[i]
local v = value[k]
if v then
used[k] = true
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
prev = true -- add a seperator before the next element
end
end
for k,v in pairs (value) do
if not used[k] then
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
if not buflen then return nil, msg end
prev = true -- add a seperator before the next element
end
end
else -- unordered
for k,v in pairs (value) do
buflen, msg = addpair (k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
if not buflen then return nil, msg end
prev = true -- add a seperator before the next element
end
end
if indent then
buflen = addnewline2 (level - 1, buffer, buflen)
end
buflen = buflen + 1
buffer[buflen] = "}"
end
tables[value] = nil
else
return exception ('unsupported type', value, state, buffer, buflen,
"type '" .. valtype .. "' is not supported by JSON.")
end
return s, removeWhite(str:sub(2))
return buflen
end
function json.parseArray(str)
str = removeWhite(str:sub(2))
local val = {}
local i = 1
while str:sub(1, 1) ~= "]" do
local v
v, str = json.parseValue(str)
val[i] = v
i = i + 1
str = removeWhite(str)
end
str = removeWhite(str:sub(2))
return val, str
end
function json.parseValue(str)
local fchar = str:sub(1, 1)
if fchar == "{" then
return json.parseObject(str)
elseif fchar == "[" then
return json.parseArray(str)
elseif tonumber(fchar) ~= nil or numChars[fchar] then
return parseNumber(str)
elseif str:sub(1, 4) == "true" or str:sub(1, 5) == "false" then
return parseBoolean(str)
elseif fchar == "\"" then
return parseString(str)
elseif str:sub(1, 4) == "null" then
return parseNull(str)
function json.encode (value, state)
state = state or {}
local oldbuffer = state.buffer
local buffer = oldbuffer or {}
state.buffer = buffer
updatedecpoint()
local ret, msg = encode2 (value, state.indent, state.level or 0,
buffer, state.bufferlen or 0, state.tables or {}, state.keyorder, state)
if not ret then
error (msg, 2)
elseif oldbuffer == buffer then
state.bufferlen = ret
return true
else
state.bufferlen = nil
state.buffer = nil
return concat (buffer)
end
end
function json.parseMember(str)
local k, val
k, str = json.parseValue(str)
val, str = json.parseValue(str)
return k, val, str
end
function json.parseObject(str)
str = removeWhite(str:sub(2))
local val = {}
while str:sub(1, 1) ~= "}" do
local k, v
k, v, str = json.parseMember(str)
val[k] = v
str = removeWhite(str)
local function loc (str, where)
local line, pos, linepos = 1, 1, 0
while true do
pos = strfind (str, "\n", pos, true)
if pos and pos < where then
line = line + 1
linepos = pos
pos = pos + 1
else
break
end
end
str = removeWhite(str:sub(2))
return val, str
return "line " .. line .. ", column " .. (where - linepos)
end
function json.decode(str)
str = removeWhite(str)
return json.parseValue(str)
local function unterminated (str, what, where)
return nil, strlen (str) + 1, "unterminated " .. what .. " at " .. loc (str, where)
end
local function scanwhite (str, pos)
while true do
pos = strfind (str, "%S", pos)
if not pos then return nil end
local sub2 = strsub (str, pos, pos + 1)
if sub2 == "\239\187" and strsub (str, pos + 2, pos + 2) == "\191" then
-- UTF-8 Byte Order Mark
pos = pos + 3
elseif sub2 == "//" then
pos = strfind (str, "[\n\r]", pos + 2)
if not pos then return nil end
elseif sub2 == "/*" then
pos = strfind (str, "*/", pos + 2)
if not pos then return nil end
pos = pos + 2
else
return pos
end
end
end
local escapechars = {
["\""] = "\"", ["\\"] = "\\", ["/"] = "/", ["b"] = "\b", ["f"] = "\f",
["n"] = "\n", ["r"] = "\r", ["t"] = "\t"
}
local function unichar (value)
if value < 0 then
return nil
elseif value <= 0x007f then
return strchar (value)
elseif value <= 0x07ff then
return strchar (0xc0 + floor(value/0x40),
0x80 + (floor(value) % 0x40))
elseif value <= 0xffff then
return strchar (0xe0 + floor(value/0x1000),
0x80 + (floor(value/0x40) % 0x40),
0x80 + (floor(value) % 0x40))
elseif value <= 0x10ffff then
return strchar (0xf0 + floor(value/0x40000),
0x80 + (floor(value/0x1000) % 0x40),
0x80 + (floor(value/0x40) % 0x40),
0x80 + (floor(value) % 0x40))
else
return nil
end
end
local function scanstring (str, pos)
local lastpos = pos + 1
local buffer, n = {}, 0
while true do
local nextpos = strfind (str, "[\"\\]", lastpos)
if not nextpos then
return unterminated (str, "string", pos)
end
if nextpos > lastpos then
n = n + 1
buffer[n] = strsub (str, lastpos, nextpos - 1)
end
if strsub (str, nextpos, nextpos) == "\"" then
lastpos = nextpos + 1
break
else
local escchar = strsub (str, nextpos + 1, nextpos + 1)
local value
if escchar == "u" then
value = tonumber (strsub (str, nextpos + 2, nextpos + 5), 16)
if value then
local value2
if 0xD800 <= value and value <= 0xDBff then
-- we have the high surrogate of UTF-16. Check if there is a
-- low surrogate escaped nearby to combine them.
if strsub (str, nextpos + 6, nextpos + 7) == "\\u" then
value2 = tonumber (strsub (str, nextpos + 8, nextpos + 11), 16)
if value2 and 0xDC00 <= value2 and value2 <= 0xDFFF then
value = (value - 0xD800) * 0x400 + (value2 - 0xDC00) + 0x10000
else
value2 = nil -- in case it was out of range for a low surrogate
end
end
end
value = value and unichar (value)
if value then
if value2 then
lastpos = nextpos + 12
else
lastpos = nextpos + 6
end
end
end
end
if not value then
value = escapechars[escchar] or escchar
lastpos = nextpos + 2
end
n = n + 1
buffer[n] = value
end
end
if n == 1 then
return buffer[1], lastpos
elseif n > 1 then
return concat (buffer), lastpos
else
return "", lastpos
end
end
local scanvalue -- forward declaration
local function scantable (what, closechar, str, startpos, nullval, objectmeta, arraymeta)
local len = strlen (str)
local tbl, n = {}, 0
local pos = startpos + 1
if what == 'object' then
setmetatable (tbl, objectmeta)
else
setmetatable (tbl, arraymeta)
end
while true do
pos = scanwhite (str, pos)
if not pos then return unterminated (str, what, startpos) end
local char = strsub (str, pos, pos)
if char == closechar then
return tbl, pos + 1
end
local val1, err
val1, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta)
if err then return nil, pos, err end
pos = scanwhite (str, pos)
if not pos then return unterminated (str, what, startpos) end
char = strsub (str, pos, pos)
if char == ":" then
if val1 == nil then
return nil, pos, "cannot use nil as table index (at " .. loc (str, pos) .. ")"
end
pos = scanwhite (str, pos + 1)
if not pos then return unterminated (str, what, startpos) end
local val2
val2, pos, err = scanvalue (str, pos, nullval, objectmeta, arraymeta)
if err then return nil, pos, err end
tbl[val1] = val2
pos = scanwhite (str, pos)
if not pos then return unterminated (str, what, startpos) end
char = strsub (str, pos, pos)
else
n = n + 1
tbl[n] = val1
end
if char == "," then
pos = pos + 1
end
end
end
scanvalue = function (str, pos, nullval, objectmeta, arraymeta)
pos = pos or 1
pos = scanwhite (str, pos)
if not pos then
return nil, strlen (str) + 1, "no valid JSON value (reached the end)"
end
local char = strsub (str, pos, pos)
if char == "{" then
return scantable ('object', "}", str, pos, nullval, objectmeta, arraymeta)
elseif char == "[" then
return scantable ('array', "]", str, pos, nullval, objectmeta, arraymeta)
elseif char == "\"" then
return scanstring (str, pos)
else
local pstart, pend = strfind (str, "^%-?[%d%.]+[eE]?[%+%-]?%d*", pos)
if pstart then
local number = str2num (strsub (str, pstart, pend))
if number then
return number, pend + 1
end
end
pstart, pend = strfind (str, "^%a%w*", pos)
if pstart then
local name = strsub (str, pstart, pend)
if name == "true" then
return true, pend + 1
elseif name == "false" then
return false, pend + 1
elseif name == "null" then
return nullval, pend + 1
end
end
return nil, pos, "no valid JSON value at " .. loc (str, pos)
end
end
local function optionalmetatables(...)
if select("#", ...) > 0 then
return ...
else
return {__jsontype = 'object'}, {__jsontype = 'array'}
end
end
function json.decode (str, pos, nullval, ...)
local objectmeta, arraymeta = optionalmetatables(...)
return scanvalue (str, pos, nullval, objectmeta, arraymeta)
end
-- NOTE: added method - not in original source
function json.decodeFromFile(path)
local file = assert(fs.open(path, "r"))
local decoded = json.decode(file.readAll())

View File

@ -19,9 +19,9 @@ function Map.removeMatches(t, values)
return true
end
for k,v in pairs(t) do
if matchAll(v) then
t[k] = nil
for _, key in pairs(Util.keys(t)) do
if matchAll(t[key]) then
t[key] = nil
end
end
end

View File

@ -106,6 +106,17 @@ local function selectDestination(pts, box, grid)
end
end
local function updateCanvas(path)
local t = { }
for node in path:nodes() do
table.insert(t, { x = node.x, y = node.y, z = node.z })
end
os.queueEvent('canvas', {
type = 'canvas_path',
data = t,
})
end
local function pathTo(dest, options)
local blocks = options.blocks or turtle.getState().blocks or { }
local dests = options.dest or { dest } -- support alternative destinations
@ -156,6 +167,8 @@ local function pathTo(dest, options)
if not path then
Util.removeByValue(dests, dest)
else
updateCanvas(path)
path:filter()
for node in path:nodes() do
@ -173,11 +186,19 @@ local function pathTo(dest, options)
-- use single turn method so the turtle doesn't turn around
-- when encountering obstacles
if not turtle.gotoSingleTurn(pt.x, pt.y, pt.z, pt.heading) then
--if not turtle.goto(pt) then
--if not turtle.gotoSingleTurn(pt.x, pt.y, pt.z, pt.heading) then
pt.heading = nil
if not turtle.go(pt) then
local bpt = Point.nearestTo(turtle.point, pt)
if turtle.getFuelLevel() == 0 then
return false, 'Out of fuel'
end
table.insert(blocks, bpt)
os.queueEvent('canvas', {
type = 'canvas_barrier',
data = { bpt },
})
-- really need to check if the block we ran into was a turtle.
-- if so, this block should be temporary (1-2 secs)

View File

@ -131,8 +131,11 @@ end
function Point.calculateMoves(pta, ptb, distance)
local heading = pta.heading
local moves = distance or Point.turtleDistance(pta, ptb)
local weighted = moves
if (pta.heading % 2) == 0 and pta.z ~= ptb.z then
moves = moves + 1
weighted = weighted + .9
if ptb.heading and (ptb.heading % 2 == 1) then
heading = ptb.heading
elseif ptb.z > pta.z then
@ -142,6 +145,7 @@ function Point.calculateMoves(pta, ptb, distance)
end
elseif (pta.heading % 2) == 1 and pta.x ~= ptb.x then
moves = moves + 1
weighted = weighted + .9
if ptb.heading and (ptb.heading % 2 == 0) then
heading = ptb.heading
elseif ptb.x > pta.x then
@ -152,15 +156,18 @@ function Point.calculateMoves(pta, ptb, distance)
end
if not ptb.heading then
return moves, heading, moves
return moves, heading, weighted
end
-- need to know if we are in digging mode
-- if so, we need to face blocks -- need a no-backwards flag
-- calc turns as slightly less than moves
local weighted = moves
-- local weighted = moves
if heading ~= ptb.heading then
local turns = Point.calculateTurns(heading, ptb.heading)
moves = moves + turns
local wturns = { [0] = 0, [1] = .9, [2] = 1.9 }
local wturns = { [0] = 0, [1] = .9, [2] = 1.8 }
weighted = weighted + wturns[turns]
heading = ptb.heading
end
@ -233,7 +240,7 @@ end
function Point.nearestTo(pta, ptb)
local heading
if pta.x < ptb.x then
if pta.x < ptb.x then
heading = 0
elseif pta.z < ptb.z then
heading = 1

View File

@ -1,32 +0,0 @@
local Socket = require('socket')
local Proxy = { }
function Proxy.create(remoteId, uri)
local socket, msg = Socket.connect(remoteId, 188)
if not socket then
error(msg)
end
socket.co = coroutine.running()
socket:write(uri)
local methods = socket:read(2) or error('Timed out')
local hijack = { }
for _,method in pairs(methods) do
hijack[method] = function(...)
socket:write({ method, ... })
local resp = socket:read()
if not resp then
error('timed out: ' .. method)
end
return table.unpack(resp)
end
end
return hijack, socket
end
return Proxy

View File

@ -1,95 +0,0 @@
local device = _G.device
local os = _G.os
local rttp = { }
local computerId = os.getComputerID()
local function parse(url, default)
-- initialize default parameters
local parsed = {}
local authority
for i,v in pairs(default or parsed) do parsed[i] = v end
-- remove whitespace
-- url = string.gsub(url, "%s", "")
-- Decode unreserved characters
url = string.gsub(url, "%%(%x%x)", function(hex)
local char = string.char(tonumber(hex, 16))
if string.match(char, "[a-zA-Z0-9._~-]") then
return char
end
-- Hex encodings that are not unreserved must be preserved.
return nil
end)
-- get fragment
url = string.gsub(url, "#(.*)$", function(f)
parsed.fragment = f
return ""
end)
-- get scheme. Lower-case according to RFC 3986 section 3.1.
url = string.gsub(url, "^(%w[%w.+-]*):",
function(s) parsed.scheme = string.lower(s); return "" end)
-- get authority
url = string.gsub(url, "^//([^/]*)", function(n)
authority = n
return ""
end)
-- get query stringing
url = string.gsub(url, "%?(.*)", function(q)
parsed.query = q
return ""
end)
-- get params
url = string.gsub(url, "%;(.*)", function(p)
parsed.params = p
return ""
end)
-- path is whatever was left
parsed.path = url
-- Represents host:port, port = nil if not used.
if authority then
authority = string.gsub(authority, ":(%d+)$",
function(p) parsed.port = tonumber(p); return "" end)
if authority ~= "" then
parsed.host = authority
end
end
return parsed
end
function rttp.get(url)
local modem = device.wireless_modem or error('Modem not found')
local parsed = parse(url, { port = 80 })
parsed.host = tonumber(parsed.host) or error('Invalid url')
for i = 16384, 32767 do
if not modem.isOpen(i) then
modem.open(i)
local path = parsed.query and parsed.path .. '?' .. parsed.query or parsed.path
modem.transmit(parsed.port, parsed.host, {
method = 'GET',
replyAddress = computerId,
replyPort = i,
path = path,
})
local timerId = os.startTimer(3)
repeat
local event, id, dport, dhost, response = os.pullEvent()
if event == 'modem_message' and
dport == i and
dhost == computerId and
type(response) == 'table' then
modem.close(i)
return true, response
end
until event == 'timer' and id == timerId
return false, 'timeout'
end
end
end
return rttp

200
sys/apis/sha2.lua Normal file
View File

@ -0,0 +1,200 @@
-- SHA-256, HMAC and PBKDF2 functions in ComputerCraft
-- By Anavrins
local bit = _G.bit
local mod32 = 2^32
local band = bit32 and bit32.band or bit.band
local bnot = bit32 and bit32.bnot or bit.bnot
local bxor = bit32 and bit32.bxor or bit.bxor
local blshift = bit32 and bit32.lshift or bit.blshift
local upack = unpack
local function rrotate(n, b)
local s = n/(2^b)
local f = s%1
return (s-f) + f*mod32
end
local function brshift(int, by)
local s = int / (2^by)
return s - s%1
end
local H = {
0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19,
}
local K = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
}
local function counter(incr)
local t1, t2 = 0, 0
if 0xFFFFFFFF - t1 < incr then
t2 = t2 + 1
t1 = incr - (0xFFFFFFFF - t1) - 1
else t1 = t1 + incr
end
return t2, t1
end
local function BE_toInt(bs, i)
return blshift((bs[i] or 0), 24) + blshift((bs[i+1] or 0), 16) + blshift((bs[i+2] or 0), 8) + (bs[i+3] or 0)
end
local function preprocess(data)
local len = #data
local proc = {}
data[#data+1] = 0x80
while #data%64~=56 do data[#data+1] = 0 end
local blocks = math.ceil(#data/64)
for i = 1, blocks do
proc[i] = {}
for j = 1, 16 do
proc[i][j] = BE_toInt(data, 1+((i-1)*64)+((j-1)*4))
end
end
proc[blocks][15], proc[blocks][16] = counter(len*8)
return proc
end
local function digestblock(w, C)
for j = 17, 64 do
--local v = w[j-15]
local s0 = bxor(bxor(rrotate(w[j-15], 7), rrotate(w[j-15], 18)), brshift(w[j-15], 3))
local s1 = bxor(bxor(rrotate(w[j-2], 17), rrotate(w[j-2], 19)), brshift(w[j-2], 10))
w[j] = (w[j-16] + s0 + w[j-7] + s1)%mod32
end
local a, b, c, d, e, f, g, h = upack(C)
for j = 1, 64 do
local S1 = bxor(bxor(rrotate(e, 6), rrotate(e, 11)), rrotate(e, 25))
local ch = bxor(band(e, f), band(bnot(e), g))
local temp1 = (h + S1 + ch + K[j] + w[j])%mod32
local S0 = bxor(bxor(rrotate(a, 2), rrotate(a, 13)), rrotate(a, 22))
local maj = bxor(bxor(band(a, b), band(a, c)), band(b, c))
local temp2 = (S0 + maj)%mod32
h, g, f, e, d, c, b, a = g, f, e, (d+temp1)%mod32, c, b, a, (temp1+temp2)%mod32
end
C[1] = (C[1] + a)%mod32
C[2] = (C[2] + b)%mod32
C[3] = (C[3] + c)%mod32
C[4] = (C[4] + d)%mod32
C[5] = (C[5] + e)%mod32
C[6] = (C[6] + f)%mod32
C[7] = (C[7] + g)%mod32
C[8] = (C[8] + h)%mod32
return C
end
local mt = {
__tostring = function(a) return string.char(unpack(a)) end,
__index = {
toHex = function(self) return ("%02x"):rep(#self):format(unpack(self)) end,
isEqual = function(self, t)
if type(t) ~= "table" then return false end
if #self ~= #t then return false end
local ret = 0
for i = 1, #self do
ret = bit32.bor(ret, bxor(self[i], t[i]))
end
return ret == 0
end
}
}
local function toBytes(t, n)
local b = {}
for i = 1, n do
b[(i-1)*4+1] = band(brshift(t[i], 24), 0xFF)
b[(i-1)*4+2] = band(brshift(t[i], 16), 0xFF)
b[(i-1)*4+3] = band(brshift(t[i], 8), 0xFF)
b[(i-1)*4+4] = band(t[i], 0xFF)
end
return setmetatable(b, mt)
end
local function digest(data)
data = data or ""
data = type(data) == "table" and {upack(data)} or {tostring(data):byte(1,-1)}
data = preprocess(data)
local C = {upack(H)}
for i = 1, #data do C = digestblock(data[i], C) end
return toBytes(C, 8)
end
local function hmac(data, key)
data = type(data) == "table" and {upack(data)} or {tostring(data):byte(1,-1)}
key = type(key) == "table" and {upack(key)} or {tostring(key):byte(1,-1)}
local blocksize = 64
key = #key > blocksize and digest(key) or key
local ipad = {}
local opad = {}
local padded_key = {}
for i = 1, blocksize do
ipad[i] = bxor(0x36, key[i] or 0)
opad[i] = bxor(0x5C, key[i] or 0)
end
for i = 1, #data do
ipad[blocksize+i] = data[i]
end
ipad = digest(ipad)
for i = 1, blocksize do
padded_key[i] = opad[i]
padded_key[blocksize+i] = ipad[i]
end
return digest(padded_key)
end
local function pbkdf2(pass, salt, iter, dklen)
local hashlen = 32
local block = 1
local out = {}
dklen = dklen or 32
salt = type(salt) == "table" and salt or {tostring(salt):byte(1,-1)}
while dklen > 0 do
local ikey = {}
local isalt = {upack(salt)}
local clen = dklen > hashlen and hashlen or dklen
isalt[#isalt+1] = band(brshift(block, 24), 0xFF)
isalt[#isalt+1] = band(brshift(block, 16), 0xFF)
isalt[#isalt+1] = band(brshift(block, 8), 0xFF)
isalt[#isalt+1] = band(block, 0xFF)
for j = 1, iter do
isalt = hmac(isalt, pass)
for k = 1, clen do ikey[k] = bxor(isalt[k], ikey[k] or 0) end
if j % 200 == 0 then os.queueEvent("PBKDF2", j) coroutine.yield("PBKDF2") end
end
dklen = dklen - clen
block = block+1
for k = 1, clen do out[#out+1] = ikey[k] end
end
return setmetatable(out, mt)
end
return {
digest = digest,
hmac = hmac,
pbkdf2 = pbkdf2,
}

View File

@ -1,61 +1,61 @@
local Sync = {
syncLocks = { }
syncLocks = { }
}
local os = _G.os
function Sync.sync(obj, fn)
local key = tostring(obj)
if Sync.syncLocks[key] then
local cos = tostring(coroutine.running())
table.insert(Sync.syncLocks[key], cos)
repeat
local _, co = os.pullEvent('sync_lock')
until co == cos
else
Sync.syncLocks[key] = { }
end
local s, m = pcall(fn)
local co = table.remove(Sync.syncLocks[key], 1)
if co then
os.queueEvent('sync_lock', co)
else
Sync.syncLocks[key] = nil
end
if not s then
error(m)
end
local key = tostring(obj)
if Sync.syncLocks[key] then
local cos = tostring(coroutine.running())
table.insert(Sync.syncLocks[key], cos)
repeat
local _, co = os.pullEvent('sync_lock')
until co == cos
else
Sync.syncLocks[key] = { }
end
local s, m = pcall(fn)
local co = table.remove(Sync.syncLocks[key], 1)
if co then
os.queueEvent('sync_lock', co)
else
Sync.syncLocks[key] = nil
end
if not s then
error(m)
end
end
function Sync.lock(obj)
local key = tostring(obj)
if Sync.syncLocks[key] then
local cos = tostring(coroutine.running())
table.insert(Sync.syncLocks[key], cos)
repeat
local _, co = os.pullEvent('sync_lock')
until co == cos
else
Sync.syncLocks[key] = { }
end
local key = tostring(obj)
if Sync.syncLocks[key] then
local cos = tostring(coroutine.running())
table.insert(Sync.syncLocks[key], cos)
repeat
local _, co = os.pullEvent('sync_lock')
until co == cos
else
Sync.syncLocks[key] = { }
end
end
function Sync.release(obj)
local key = tostring(obj)
if not Sync.syncLocks[key] then
error('Sync.release: Lock was not obtained', 2)
end
local co = table.remove(Sync.syncLocks[key], 1)
if co then
os.queueEvent('sync_lock', co)
else
Sync.syncLocks[key] = nil
end
local key = tostring(obj)
if not Sync.syncLocks[key] then
error('Sync.release: Lock was not obtained', 2)
end
local co = table.remove(Sync.syncLocks[key], 1)
if co then
os.queueEvent('sync_lock', co)
else
Sync.syncLocks[key] = nil
end
end
function Sync.isLocked(obj)
local key = tostring(obj)
return not not Sync.syncLocks[key]
local key = tostring(obj)
return not not Sync.syncLocks[key]
end
return Sync

View File

@ -5,106 +5,106 @@ local type = type
local debug_traceback = type(debug) == "table" and type(debug.traceback) == "function" and debug.traceback
local function traceback(x)
-- Attempt to detect error() and error("xyz", 0).
-- This probably means they're erroring the program intentionally and so we
-- shouldn't display anything.
if x == nil or (type(x) == "string" and not x:find(":%d+:")) then
return x
end
-- Attempt to detect error() and error("xyz", 0).
-- This probably means they're erroring the program intentionally and so we
-- shouldn't display anything.
if x == nil or (type(x) == "string" and not x:find(":%d+:")) then
return x
end
if debug_traceback then
-- The parens are important, as they prevent a tail call occuring, meaning
-- the stack level is preserved. This ensures the code behaves identically
-- on LuaJ and PUC Lua.
return (debug_traceback(tostring(x), 2))
else
local level = 3
local out = { tostring(x), "stack traceback:" }
while true do
local _, msg = pcall(error, "", level)
if msg == "" then break end
if debug_traceback then
-- The parens are important, as they prevent a tail call occuring, meaning
-- the stack level is preserved. This ensures the code behaves identically
-- on LuaJ and PUC Lua.
return (debug_traceback(tostring(x), 2))
else
local level = 3
local out = { tostring(x), "stack traceback:" }
while true do
local _, msg = pcall(error, "", level)
if msg == "" then break end
out[#out + 1] = " " .. msg
level = level + 1
end
out[#out + 1] = " " .. msg
level = level + 1
end
return table.concat(out, "\n")
end
return table.concat(out, "\n")
end
end
local function trim_traceback(target, marker)
local ttarget, tmarker = {}, {}
for line in target:gmatch("([^\n]*)\n?") do ttarget[#ttarget + 1] = line end
for line in marker:gmatch("([^\n]*)\n?") do tmarker[#tmarker + 1] = line end
local ttarget, tmarker = {}, {}
for line in target:gmatch("([^\n]*)\n?") do ttarget[#ttarget + 1] = line end
for line in marker:gmatch("([^\n]*)\n?") do tmarker[#tmarker + 1] = line end
-- Trim identical suffixes
local t_len, m_len = #ttarget, #tmarker
while t_len >= 3 and ttarget[t_len] == tmarker[m_len] do
table.remove(ttarget, t_len)
t_len, m_len = t_len - 1, m_len - 1
end
-- Trim identical suffixes
local t_len, m_len = #ttarget, #tmarker
while t_len >= 3 and ttarget[t_len] == tmarker[m_len] do
table.remove(ttarget, t_len)
t_len, m_len = t_len - 1, m_len - 1
end
-- Trim elements from this file and xpcall invocations
while t_len >= 1 and ttarget[t_len]:find("^\tstack_trace%.lua:%d+:") or
ttarget[t_len] == "\t[C]: in function 'xpcall'" or ttarget[t_len] == " xpcall: " do
table.remove(ttarget, t_len)
t_len = t_len - 1
end
-- Trim elements from this file and xpcall invocations
while t_len >= 1 and ttarget[t_len]:find("^\tstack_trace%.lua:%d+:") or
ttarget[t_len] == "\t[C]: in function 'xpcall'" or ttarget[t_len] == " xpcall: " do
table.remove(ttarget, t_len)
t_len = t_len - 1
end
ttarget[#ttarget] = nil -- remove 2 calls added by the added xpcall
ttarget[#ttarget] = nil
ttarget[#ttarget] = nil -- remove 2 calls added by the added xpcall
ttarget[#ttarget] = nil
return ttarget
return ttarget
end
--- Run a function with
return function (fn, ...)
-- So this is rather grim: we need to get the full traceback and current one and remove
-- the common prefix
local trace
local args = { ... }
-- So this is rather grim: we need to get the full traceback and current one and remove
-- the common prefix
local trace
local args = { ... }
-- xpcall in Lua 5.1 does not accept parameters
-- which is not ideal
local res = table.pack(xpcall(function()
return fn(table.unpack(args))
end, traceback))
-- xpcall in Lua 5.1 does not accept parameters
-- which is not ideal
local res = table.pack(xpcall(function()
return fn(table.unpack(args))
end, traceback))
if not res[1] then
trace = traceback("trace.lua:1:")
end
local ok, err = res[1], res[2]
if not res[1] then
trace = traceback("trace.lua:1:")
end
local ok, err = res[1], res[2]
if not ok and err ~= nil then
trace = trim_traceback(err, trace)
if not ok and err ~= nil then
trace = trim_traceback(err, trace)
-- Find the position where the stack traceback actually starts
local trace_starts
for i = #trace, 1, -1 do
if trace[i] == "stack traceback:" then trace_starts = i; break end
end
-- Find the position where the stack traceback actually starts
local trace_starts
for i = #trace, 1, -1 do
if trace[i] == "stack traceback:" then trace_starts = i; break end
end
for _, line in pairs(trace) do
_G._syslog(line)
end
for _, line in pairs(trace) do
_G._syslog(line)
end
-- If this traceback is more than 15 elements long, keep the first 9, last 5
-- and put an ellipsis between the rest
local max = 10
if trace_starts and #trace - trace_starts > max then
local keep_starts = trace_starts + 7
for i = #trace - trace_starts - max, 0, -1 do
table.remove(trace, keep_starts + i)
end
table.insert(trace, keep_starts, " ...")
end
-- If this traceback is more than 15 elements long, keep the first 9, last 5
-- and put an ellipsis between the rest
local max = 10
if trace_starts and #trace - trace_starts > max then
local keep_starts = trace_starts + 7
for i = #trace - trace_starts - max, 0, -1 do
table.remove(trace, keep_starts + i)
end
table.insert(trace, keep_starts, " ...")
end
for k, line in pairs(trace) do
trace[k] = line:gsub("in function", " in")
end
for k, line in pairs(trace) do
trace[k] = line:gsub("in function", " in")
end
return false, table.remove(trace, 1), table.concat(trace, "\n")
end
return false, table.remove(trace, 1), table.concat(trace, "\n")
end
return table.unpack(res, 1, res.n)
return table.unpack(res, 1, res.n)
end

View File

@ -329,23 +329,13 @@ function UI.Grid:drawRows()
local rawRow = self.values[key]
local row = self:getDisplayValues(rawRow, key)
local ind = ' '
if self.focused and index == self.index and not self.inactive then
ind = self.focusIndicator
end
local selected = index == self.index and not self.inactive
local bg = self:getRowBackgroundColor(rawRow, selected)
local fg = self:getRowTextColor(rawRow, selected)
local focused = self.focused and selected
self:drawRow(sb, row, focused, bg, fg)
for _,col in pairs(self.columns) do
sb:write(ind .. safeValue(row[col.key] or ''),
col.cw + 1,
col.align,
bg,
fg)
ind = ' '
end
sb:finish(bg)
end
@ -354,6 +344,19 @@ function UI.Grid:drawRows()
end
end
function UI.Grid:drawRow(sb, row, focused, bg, fg)
local ind = focused and self.focusIndicator or ' '
for _,col in pairs(self.columns) do
sb:write(ind .. safeValue(row[col.key] or ''),
col.cw + 1,
col.align,
bg,
fg)
ind = ' '
end
end
function UI.Grid:getRowTextColor(row, selected)
if selected then
if self.focused then

View File

@ -560,7 +560,7 @@ function Util.insertString(str, istr, pos)
end
function Util.split(str, pattern)
if not str then error('Util.split: Invalid parameters', 2) end
if not str or type(str) ~= 'string' then error('Util.split: Invalid parameters', 2) end
pattern = pattern or "(.-)\n"
local t = {}
local function helper(line) table.insert(t, line) return "" end

View File

@ -15,11 +15,11 @@ local FILE = 1
UI:configure('Files', ...)
local config = Config.load('Files', {
showHidden = false,
showDirSizes = false,
showHidden = false,
showDirSizes = false,
})
config.associations = config.associations or {
nft = 'pain',
nft = 'pain',
}
local copied = { }
@ -28,517 +28,517 @@ local directories = { }
local cutMode = false
local function formatSize(size)
if size >= 1000000 then
return string.format('%dM', math.floor(size/1000000, 2))
elseif size >= 1000 then
return string.format('%dK', math.floor(size/1000, 2))
end
return size
if size >= 1000000 then
return string.format('%dM', math.floor(size/1000000, 2))
elseif size >= 1000 then
return string.format('%dK', math.floor(size/1000, 2))
end
return size
end
local Browser = UI.Page {
menuBar = UI.MenuBar {
buttons = {
{ text = '^-', event = 'updir' },
{ text = 'File', dropdown = {
{ text = 'Run', event = 'run', flags = FILE },
{ text = 'Edit e', event = 'edit', flags = FILE },
{ text = 'Cloud edit c', event = 'cedit', flags = FILE },
{ text = 'Pastebin put p', event = 'pastebin', flags = FILE },
{ text = 'Shell s', event = 'shell' },
{ spacer = true },
{ text = 'Quit q', event = 'quit' },
} },
{ text = 'Edit', dropdown = {
{ text = 'Cut ^x', event = 'cut' },
{ text = 'Copy ^c', event = 'copy' },
{ text = 'Copy path ', event = 'copy_path' },
{ text = 'Paste ^v', event = 'paste' },
{ spacer = true },
{ text = 'Mark m', event = 'mark' },
{ text = 'Unmark all u', event = 'unmark' },
{ spacer = true },
{ text = 'Delete del', event = 'delete' },
} },
{ text = 'View', dropdown = {
{ text = 'Refresh r', event = 'refresh' },
{ text = 'Hidden ^h', event = 'toggle_hidden' },
{ text = 'Dir Size ^s', event = 'toggle_dirSize' },
} },
{ text = '\187',
x = -3,
dropdown = {
{ text = 'Associations', event = 'associate' },
} },
},
},
grid = UI.ScrollingGrid {
columns = {
{ heading = 'Name', key = 'name' },
{ key = 'flags', width = 2 },
{ heading = 'Size', key = 'fsize', width = 5 },
},
sortColumn = 'name',
y = 2, ey = -2,
},
statusBar = UI.StatusBar {
columns = {
{ key = 'status' },
{ key = 'totalSize', width = 6 },
},
},
notification = UI.Notification { },
associations = UI.SlideOut {
backgroundColor = colors.cyan,
menuBar = UI.MenuBar {
buttons = {
{ text = 'Save', event = 'save' },
{ text = 'Cancel', event = 'cancel' },
},
},
grid = UI.ScrollingGrid {
x = 2, ex = -6, y = 3, ey = -5,
columns = {
{ heading = 'Extension', key = 'name' },
{ heading = 'Program', key = 'value' },
},
autospace = true,
sortColumn = 'name',
accelerators = {
delete = 'remove_entry',
},
},
remove = UI.Button {
x = -4, y = 6,
text = '-', event = 'remove_entry', help = 'Remove',
},
form = UI.Form {
x = 3, y = -3, ey = -2,
margin = 1,
manualControls = true,
[1] = UI.TextEntry {
width = 20,
formLabel = 'Extension', formKey = 'name',
shadowText = 'extension',
required = true,
limit = 64,
},
[2] = UI.TextEntry {
width = 20,
formLabel = 'Program', formKey = 'value',
shadowText = 'program',
required = true,
limit = 128,
},
add = UI.Button {
x = -11, y = 1,
text = 'Add', event = 'add_association',
},
},
statusBar = UI.StatusBar {
backgroundColor = colors.cyan,
},
},
accelerators = {
q = 'quit',
c = 'cedit',
e = 'edit',
s = 'shell',
p = 'pastebin',
r = 'refresh',
[ ' ' ] = 'mark',
m = 'mark',
backspace = 'updir',
u = 'unmark',
d = 'delete',
delete = 'delete',
[ 'control-h' ] = 'toggle_hidden',
[ 'control-s' ] = 'toggle_dirSize',
[ 'control-x' ] = 'cut',
[ 'control-c' ] = 'copy',
paste = 'paste',
},
menuBar = UI.MenuBar {
buttons = {
{ text = '^-', event = 'updir' },
{ text = 'File', dropdown = {
{ text = 'Run', event = 'run', flags = FILE },
{ text = 'Edit e', event = 'edit', flags = FILE },
{ text = 'Cloud edit c', event = 'cedit', flags = FILE },
{ text = 'Pastebin put p', event = 'pastebin', flags = FILE },
{ text = 'Shell s', event = 'shell' },
{ spacer = true },
{ text = 'Quit q', event = 'quit' },
} },
{ text = 'Edit', dropdown = {
{ text = 'Cut ^x', event = 'cut' },
{ text = 'Copy ^c', event = 'copy' },
{ text = 'Copy path ', event = 'copy_path' },
{ text = 'Paste ^v', event = 'paste' },
{ spacer = true },
{ text = 'Mark m', event = 'mark' },
{ text = 'Unmark all u', event = 'unmark' },
{ spacer = true },
{ text = 'Delete del', event = 'delete' },
} },
{ text = 'View', dropdown = {
{ text = 'Refresh r', event = 'refresh' },
{ text = 'Hidden ^h', event = 'toggle_hidden' },
{ text = 'Dir Size ^s', event = 'toggle_dirSize' },
} },
{ text = '\187',
x = -3,
dropdown = {
{ text = 'Associations', event = 'associate' },
} },
},
},
grid = UI.ScrollingGrid {
columns = {
{ heading = 'Name', key = 'name' },
{ key = 'flags', width = 2 },
{ heading = 'Size', key = 'fsize', width = 5 },
},
sortColumn = 'name',
y = 2, ey = -2,
},
statusBar = UI.StatusBar {
columns = {
{ key = 'status' },
{ key = 'totalSize', width = 6 },
},
},
notification = UI.Notification { },
associations = UI.SlideOut {
backgroundColor = colors.cyan,
menuBar = UI.MenuBar {
buttons = {
{ text = 'Save', event = 'save' },
{ text = 'Cancel', event = 'cancel' },
},
},
grid = UI.ScrollingGrid {
x = 2, ex = -6, y = 3, ey = -5,
columns = {
{ heading = 'Extension', key = 'name' },
{ heading = 'Program', key = 'value' },
},
autospace = true,
sortColumn = 'name',
accelerators = {
delete = 'remove_entry',
},
},
remove = UI.Button {
x = -4, y = 6,
text = '-', event = 'remove_entry', help = 'Remove',
},
form = UI.Form {
x = 3, y = -3, ey = -2,
margin = 1,
manualControls = true,
[1] = UI.TextEntry {
width = 20,
formLabel = 'Extension', formKey = 'name',
shadowText = 'extension',
required = true,
limit = 64,
},
[2] = UI.TextEntry {
width = 20,
formLabel = 'Program', formKey = 'value',
shadowText = 'program',
required = true,
limit = 128,
},
add = UI.Button {
x = -11, y = 1,
text = 'Add', event = 'add_association',
},
},
statusBar = UI.StatusBar {
backgroundColor = colors.cyan,
},
},
accelerators = {
q = 'quit',
c = 'cedit',
e = 'edit',
s = 'shell',
p = 'pastebin',
r = 'refresh',
[ ' ' ] = 'mark',
m = 'mark',
backspace = 'updir',
u = 'unmark',
d = 'delete',
delete = 'delete',
[ 'control-h' ] = 'toggle_hidden',
[ 'control-s' ] = 'toggle_dirSize',
[ 'control-x' ] = 'cut',
[ 'control-c' ] = 'copy',
paste = 'paste',
},
}
function Browser:enable()
UI.Page.enable(self)
self:setFocus(self.grid)
UI.Page.enable(self)
self:setFocus(self.grid)
end
function Browser.menuBar:getActive(menuItem)
local file = Browser.grid:getSelected()
if menuItem.flags == FILE then
return file and not file.isDir
end
return true
local file = Browser.grid:getSelected()
if menuItem.flags == FILE then
return file and not file.isDir
end
return true
end
function Browser.grid:sortCompare(a, b)
if self.sortColumn == 'fsize' then
return a.size < b.size
elseif self.sortColumn == 'flags' then
return a.flags < b.flags
end
if a.isDir == b.isDir then
return a.name:lower() < b.name:lower()
end
return a.isDir
if self.sortColumn == 'fsize' then
return a.size < b.size
elseif self.sortColumn == 'flags' then
return a.flags < b.flags
end
if a.isDir == b.isDir then
return a.name:lower() < b.name:lower()
end
return a.isDir
end
function Browser.grid:getRowTextColor(file)
if file.marked then
return colors.green
end
if file.isDir then
return colors.cyan
end
if file.isReadOnly then
return colors.pink
end
return colors.white
if file.marked then
return colors.green
end
if file.isDir then
return colors.cyan
end
if file.isReadOnly then
return colors.pink
end
return colors.white
end
function Browser.grid:eventHandler(event)
if event.type == 'copy' then -- let copy be handled by parent
return false
end
return UI.ScrollingGrid.eventHandler(self, event)
if event.type == 'copy' then -- let copy be handled by parent
return false
end
return UI.ScrollingGrid.eventHandler(self, event)
end
function Browser.statusBar:draw()
if self.parent.dir then
local info = '#:' .. Util.size(self.parent.dir.files)
local numMarked = Util.size(marked)
if numMarked > 0 then
info = info .. ' M:' .. numMarked
end
self:setValue('info', info)
self:setValue('totalSize', formatSize(self.parent.dir.totalSize))
UI.StatusBar.draw(self)
end
if self.parent.dir then
local info = '#:' .. Util.size(self.parent.dir.files)
local numMarked = Util.size(marked)
if numMarked > 0 then
info = info .. ' M:' .. numMarked
end
self:setValue('info', info)
self:setValue('totalSize', formatSize(self.parent.dir.totalSize))
UI.StatusBar.draw(self)
end
end
function Browser:setStatus(status, ...)
self.notification:info(string.format(status, ...))
self.notification:info(string.format(status, ...))
end
function Browser:unmarkAll()
for _,m in pairs(marked) do
m.marked = false
end
Util.clear(marked)
for _,m in pairs(marked) do
m.marked = false
end
Util.clear(marked)
end
function Browser:getDirectory(directory)
local s, dir = pcall(function()
local s, dir = pcall(function()
local dir = directories[directory]
if not dir then
dir = {
name = directory,
size = 0,
files = { },
totalSize = 0,
index = 1
}
directories[directory] = dir
end
local dir = directories[directory]
if not dir then
dir = {
name = directory,
size = 0,
files = { },
totalSize = 0,
index = 1
}
directories[directory] = dir
end
self:updateDirectory(dir)
self:updateDirectory(dir)
return dir
end)
return dir
end)
return s, dir
return s, dir
end
function Browser:updateDirectory(dir)
dir.size = 0
dir.totalSize = 0
Util.clear(dir.files)
dir.size = 0
dir.totalSize = 0
Util.clear(dir.files)
local files = fs.listEx(dir.name)
if files then
dir.size = #files
for _, file in pairs(files) do
file.fullName = fs.combine(dir.name, file.name)
file.flags = ''
if not file.isDir then
dir.totalSize = dir.totalSize + file.size
file.fsize = formatSize(file.size)
else
if config.showDirSizes then
file.size = fs.getSize(file.fullName, true)
local files = fs.listEx(dir.name)
if files then
dir.size = #files
for _, file in pairs(files) do
file.fullName = fs.combine(dir.name, file.name)
file.flags = ''
if not file.isDir then
dir.totalSize = dir.totalSize + file.size
file.fsize = formatSize(file.size)
else
if config.showDirSizes then
file.size = fs.getSize(file.fullName, true)
dir.totalSize = dir.totalSize + file.size
file.fsize = formatSize(file.size)
end
file.flags = 'D'
end
if file.isReadOnly then
file.flags = file.flags .. 'R'
end
if config.showHidden or file.name:sub(1, 1) ~= '.' then
dir.files[file.fullName] = file
end
end
end
dir.totalSize = dir.totalSize + file.size
file.fsize = formatSize(file.size)
end
file.flags = 'D'
end
if file.isReadOnly then
file.flags = file.flags .. 'R'
end
if config.showHidden or file.name:sub(1, 1) ~= '.' then
dir.files[file.fullName] = file
end
end
end
-- self.grid:update()
-- self.grid:setIndex(dir.index)
self.grid:setValues(dir.files)
self.grid:setValues(dir.files)
end
function Browser:setDir(dirName, noStatus)
self:unmarkAll()
self:unmarkAll()
if self.dir then
self.dir.index = self.grid:getIndex()
end
local DIR = fs.combine('', dirName)
shell.setDir(DIR)
local s, dir = self:getDirectory(DIR)
if s then
self.dir = dir
elseif noStatus then
error(dir)
else
self:setStatus(dir)
self:setDir('', true)
return
end
if self.dir then
self.dir.index = self.grid:getIndex()
end
local DIR = fs.combine('', dirName)
shell.setDir(DIR)
local s, dir = self:getDirectory(DIR)
if s then
self.dir = dir
elseif noStatus then
error(dir)
else
self:setStatus(dir)
self:setDir('', true)
return
end
if not noStatus then
self.statusBar:setValue('status', '/' .. self.dir.name)
self.statusBar:draw()
end
self.grid:setIndex(self.dir.index)
if not noStatus then
self.statusBar:setValue('status', '/' .. self.dir.name)
self.statusBar:draw()
end
self.grid:setIndex(self.dir.index)
end
function Browser:run(...)
if multishell then
local tabId = shell.openTab(...)
multishell.setFocus(tabId)
else
shell.run(...)
Event.terminate = false
self:draw()
end
if multishell then
local tabId = shell.openTab(...)
multishell.setFocus(tabId)
else
shell.run(...)
Event.terminate = false
self:draw()
end
end
function Browser:hasMarked()
if Util.size(marked) == 0 then
local file = self.grid:getSelected()
if file then
file.marked = true
marked[file.fullName] = file
self.grid:draw()
end
end
return Util.size(marked) > 0
if Util.size(marked) == 0 then
local file = self.grid:getSelected()
if file then
file.marked = true
marked[file.fullName] = file
self.grid:draw()
end
end
return Util.size(marked) > 0
end
function Browser:eventHandler(event)
local file = self.grid:getSelected()
local file = self.grid:getSelected()
if event.type == 'quit' then
Event.exitPullEvents()
if event.type == 'quit' then
Event.exitPullEvents()
elseif event.type == 'edit' and file then
self:run('edit', file.name)
elseif event.type == 'edit' and file then
self:run('edit', file.name)
elseif event.type == 'cedit' and file then
self:run('cedit', file.name)
self:setStatus('Started cloud edit')
elseif event.type == 'cedit' and file then
self:run('cedit', file.name)
self:setStatus('Started cloud edit')
elseif event.type == 'shell' then
self:run('sys/apps/shell.lua')
elseif event.type == 'shell' then
self:run('sys/apps/shell.lua')
elseif event.type == 'refresh' then
self:updateDirectory(self.dir)
self.grid:draw()
self:setStatus('Refreshed')
elseif event.type == 'refresh' then
self:updateDirectory(self.dir)
self.grid:draw()
self:setStatus('Refreshed')
elseif event.type == 'associate' then
self.associations:show()
elseif event.type == 'associate' then
self.associations:show()
elseif event.type == 'pastebin' then
if file and not file.isDir then
local s, m = pastebin.put(file.fullName)
if s then
os.queueEvent('clipboard_copy', s)
self.notification:success(string.format('Uploaded as %s', s), 0)
else
self.notification:error(m)
end
end
elseif event.type == 'pastebin' then
if file and not file.isDir then
local s, m = pastebin.put(file.fullName)
if s then
os.queueEvent('clipboard_copy', s)
self.notification:success(string.format('Uploaded as %s', s), 0)
else
self.notification:error(m)
end
end
elseif event.type == 'toggle_hidden' then
config.showHidden = not config.showHidden
Config.update('Files', config)
elseif event.type == 'toggle_hidden' then
config.showHidden = not config.showHidden
Config.update('Files', config)
self:updateDirectory(self.dir)
self.grid:draw()
if not config.showHidden then
self:setStatus('Hiding hidden')
else
self:setStatus('Displaying hidden')
end
self:updateDirectory(self.dir)
self.grid:draw()
if not config.showHidden then
self:setStatus('Hiding hidden')
else
self:setStatus('Displaying hidden')
end
elseif event.type == 'toggle_dirSize' then
config.showDirSizes = not config.showDirSizes
Config.update('Files', config)
elseif event.type == 'toggle_dirSize' then
config.showDirSizes = not config.showDirSizes
Config.update('Files', config)
self:updateDirectory(self.dir)
self.grid:draw()
if config.showDirSizes then
self:setStatus('Displaying dir sizes')
end
self:updateDirectory(self.dir)
self.grid:draw()
if config.showDirSizes then
self:setStatus('Displaying dir sizes')
end
elseif event.type == 'mark' and file then
file.marked = not file.marked
if file.marked then
marked[file.fullName] = file
else
marked[file.fullName] = nil
end
self.grid:draw()
self.statusBar:draw()
elseif event.type == 'mark' and file then
file.marked = not file.marked
if file.marked then
marked[file.fullName] = file
else
marked[file.fullName] = nil
end
self.grid:draw()
self.statusBar:draw()
elseif event.type == 'unmark' then
self:unmarkAll()
self.grid:draw()
self:setStatus('Marked files cleared')
elseif event.type == 'unmark' then
self:unmarkAll()
self.grid:draw()
self:setStatus('Marked files cleared')
elseif event.type == 'grid_select' or event.type == 'run' then
if file then
if file.isDir then
self:setDir(file.fullName)
else
local ext = file.name:match('%.(%w+)$')
if ext and config.associations[ext] then
self:run(config.associations[ext], '/' .. file.fullName)
else
self:run(file.name)
end
end
end
elseif event.type == 'grid_select' or event.type == 'run' then
if file then
if file.isDir then
self:setDir(file.fullName)
else
local ext = file.name:match('%.(%w+)$')
if ext and config.associations[ext] then
self:run(config.associations[ext], '/' .. file.fullName)
else
self:run(file.name)
end
end
end
elseif event.type == 'updir' then
local dir = (self.dir.name:match("(.*/)"))
self:setDir(dir or '/')
elseif event.type == 'updir' then
local dir = (self.dir.name:match("(.*/)"))
self:setDir(dir or '/')
elseif event.type == 'delete' then
if self:hasMarked() then
local width = self.statusBar:getColumnWidth('status')
self.statusBar:setColumnWidth('status', UI.term.width)
self.statusBar:setValue('status', 'Delete marked? (y/n)')
self.statusBar:draw()
self.statusBar:sync()
local _, ch = os.pullEvent('char')
if ch == 'y' or ch == 'Y' then
for _,m in pairs(marked) do
pcall(function()
fs.delete(m.fullName)
end)
end
end
marked = { }
self.statusBar:setColumnWidth('status', width)
self.statusBar:setValue('status', '/' .. self.dir.name)
self:updateDirectory(self.dir)
elseif event.type == 'delete' then
if self:hasMarked() then
local width = self.statusBar:getColumnWidth('status')
self.statusBar:setColumnWidth('status', UI.term.width)
self.statusBar:setValue('status', 'Delete marked? (y/n)')
self.statusBar:draw()
self.statusBar:sync()
local _, ch = os.pullEvent('char')
if ch == 'y' or ch == 'Y' then
for _,m in pairs(marked) do
pcall(function()
fs.delete(m.fullName)
end)
end
end
marked = { }
self.statusBar:setColumnWidth('status', width)
self.statusBar:setValue('status', '/' .. self.dir.name)
self:updateDirectory(self.dir)
self.statusBar:draw()
self.grid:draw()
self:setFocus(self.grid)
end
self.statusBar:draw()
self.grid:draw()
self:setFocus(self.grid)
end
elseif event.type == 'copy' or event.type == 'cut' then
if self:hasMarked() then
cutMode = event.type == 'cut'
Util.clear(copied)
Util.merge(copied, marked)
--self:unmarkAll()
self.grid:draw()
self:setStatus('Copied %d file(s)', Util.size(copied))
end
elseif event.type == 'copy' or event.type == 'cut' then
if self:hasMarked() then
cutMode = event.type == 'cut'
Util.clear(copied)
Util.merge(copied, marked)
--self:unmarkAll()
self.grid:draw()
self:setStatus('Copied %d file(s)', Util.size(copied))
end
elseif event.type == 'copy_path' then
if file then
os.queueEvent('clipboard_copy', file.fullName)
end
elseif event.type == 'copy_path' then
if file then
os.queueEvent('clipboard_copy', file.fullName)
end
elseif event.type == 'paste' then
for _,m in pairs(copied) do
local s, m = pcall(function()
if cutMode then
fs.move(m.fullName, fs.combine(self.dir.name, m.name))
else
fs.copy(m.fullName, fs.combine(self.dir.name, m.name))
end
end)
end
self:updateDirectory(self.dir)
self.grid:draw()
self:setStatus('Pasted ' .. Util.size(copied) .. ' file(s)')
elseif event.type == 'paste' then
for _,m in pairs(copied) do
local s, m = pcall(function()
if cutMode then
fs.move(m.fullName, fs.combine(self.dir.name, m.name))
else
fs.copy(m.fullName, fs.combine(self.dir.name, m.name))
end
end)
end
self:updateDirectory(self.dir)
self.grid:draw()
self:setStatus('Pasted ' .. Util.size(copied) .. ' file(s)')
else
return UI.Page.eventHandler(self, event)
end
self:setFocus(self.grid)
return true
else
return UI.Page.eventHandler(self, event)
end
self:setFocus(self.grid)
return true
end
--[[ Associations slide out ]] --
function Browser.associations:show()
self.grid.values = { }
for k, v in pairs(config.associations) do
table.insert(self.grid.values, {
name = k,
value = v,
})
end
self.grid:update()
UI.SlideOut.show(self)
self:setFocus(self.form[1])
self.grid.values = { }
for k, v in pairs(config.associations) do
table.insert(self.grid.values, {
name = k,
value = v,
})
end
self.grid:update()
UI.SlideOut.show(self)
self:setFocus(self.form[1])
end
function Browser.associations:eventHandler(event)
if event.type == 'remove_entry' then
local row = self.grid:getSelected()
if row then
Util.removeByValue(self.grid.values, row)
self.grid:update()
self.grid:draw()
end
if event.type == 'remove_entry' then
local row = self.grid:getSelected()
if row then
Util.removeByValue(self.grid.values, row)
self.grid:update()
self.grid:draw()
end
elseif event.type == 'add_association' then
if self.form:save() then
local entry = Util.find(self.grid.values, 'name', self.form[1].value) or { }
entry.name = self.form[1].value
entry.value = self.form[2].value
table.insert(self.grid.values, entry)
self.form[1]:reset()
self.form[2]:reset()
self.grid:update()
self.grid:draw()
end
elseif event.type == 'add_association' then
if self.form:save() then
local entry = Util.find(self.grid.values, 'name', self.form[1].value) or { }
entry.name = self.form[1].value
entry.value = self.form[2].value
table.insert(self.grid.values, entry)
self.form[1]:reset()
self.form[2]:reset()
self.grid:update()
self.grid:draw()
end
elseif event.type == 'cancel' then
self:hide()
elseif event.type == 'cancel' then
self:hide()
elseif event.type == 'save' then
config.associations = { }
for _, v in pairs(self.grid.values) do
config.associations[v.name] = v.value
end
Config.update('Files', config)
self:hide()
elseif event.type == 'save' then
config.associations = { }
for _, v in pairs(self.grid.values) do
config.associations[v.name] = v.value
end
Config.update('Files', config)
self:hide()
else
return UI.SlideOut.eventHandler(self, event)
end
return true
else
return UI.SlideOut.eventHandler(self, event)
end
return true
end
--[[-- Startup logic --]]--

View File

@ -294,7 +294,6 @@ end
function page:rawExecute(s)
local fn, m
local wrapped
local t = os.clock()
fn = load('return (' ..s.. ')', 'lua', nil, sandboxEnv)
@ -303,6 +302,7 @@ function page:rawExecute(s)
wrapped = true
end
local t = os.clock()
if fn then
fn, m = pcall(fn)
if #m <= 1 and wrapped then
@ -311,19 +311,24 @@ function page:rawExecute(s)
else
fn, m = load(s, 'lua', nil, sandboxEnv)
if fn then
t = os.clock()
fn, m = pcall(fn)
end
end
if fn then
t = os.clock() - t
local bg, fg = term.getBackgroundColor(), term.getTextColor()
term.setTextColor(colors.cyan)
term.setBackgroundColor(colors.black)
term.write(string.format('out [%.2f]: ', t))
term.setBackgroundColor(bg)
term.setTextColor(fg)
if m or wrapped then
local bg, fg = term.getBackgroundColor(), term.getTextColor()
term.setTextColor(colors.cyan)
term.setBackgroundColor(colors.black)
term.write(string.format('out [%.2f]: ', os.clock() - t))
term.setBackgroundColor(bg)
term.setTextColor(fg)
Util.print(m or 'nil')
else
print()
end
else
_G.printError(m)

View File

@ -127,12 +127,14 @@ local function sendCommand(host, command)
end
end
--[[
function page.ports:eventHandler(event)
if event.type == 'grid_select' then
shell.openForegroundTab('sniff ' .. event.selected.port)
end
return UI.SlideOut.eventHandler(self, event)
end
]]
function page.ports.grid:update()
local function findConnection(port)

View File

@ -5,23 +5,23 @@ local shell = _ENV.shell
local launcherTab = kernel.getCurrent()
kernel.hook('kernel_focus', function(_, eventData)
local focusTab = eventData and eventData[1]
if focusTab == launcherTab.uid then
local previousTab = eventData[2]
local nextTab = launcherTab
if not previousTab then
for _, v in pairs(kernel.routines) do
if not v.hidden and v.uid > nextTab.uid then
nextTab = v
end
end
end
if nextTab == launcherTab then
shell.switchTab(shell.openTab('sys/apps/shell.lua'))
else
shell.switchTab(nextTab.uid)
end
end
local focusTab = eventData and eventData[1]
if focusTab == launcherTab.uid then
local previousTab = eventData[2]
local nextTab = launcherTab
if not previousTab then
for _, v in pairs(kernel.routines) do
if not v.hidden and v.uid > nextTab.uid then
nextTab = v
end
end
end
if nextTab == launcherTab then
shell.switchTab(shell.openTab('sys/apps/shell.lua'))
else
shell.switchTab(nextTab.uid)
end
end
end)
while os.pullEventRaw() do end
os.pullEventRaw('kernel_halt')

View File

@ -23,115 +23,115 @@ local packagesIntro = [[Setup Complete
local page = UI.Page {
wizard = UI.Wizard {
ey = -2,
ey = -2,
pages = {
splash = UI.WizardPage {
index = 1,
intro = UI.TextArea {
textColor = colors.yellow,
inactive = true,
x = 3, ex = -3, y = 2, ey = -2,
value = string.format(splashIntro, Ansi.white),
},
},
index = 1,
intro = UI.TextArea {
textColor = colors.yellow,
inactive = true,
x = 3, ex = -3, y = 2, ey = -2,
value = string.format(splashIntro, Ansi.white),
},
},
label = UI.WizardPage {
index = 2,
labelText = UI.Text {
x = 3, y = 2,
value = 'Label'
},
label = UI.TextEntry {
x = 9, y = 2, ex = -3,
limit = 32,
value = os.getComputerLabel(),
},
intro = UI.TextArea {
textColor = colors.yellow,
inactive = true,
x = 3, ex = -3, y = 4, ey = -3,
value = string.format(labelIntro, Ansi.white),
},
},
index = 2,
labelText = UI.Text {
x = 3, y = 2,
value = 'Label'
},
label = UI.TextEntry {
x = 9, y = 2, ex = -3,
limit = 32,
value = os.getComputerLabel(),
},
intro = UI.TextArea {
textColor = colors.yellow,
inactive = true,
x = 3, ex = -3, y = 4, ey = -3,
value = string.format(labelIntro, Ansi.white),
},
},
password = UI.WizardPage {
index = 3,
passwordLabel = UI.Text {
x = 3, y = 2,
value = 'Password'
},
newPass = UI.TextEntry {
x = 12, ex = -3, y = 2,
limit = 32,
mask = true,
shadowText = 'password',
},
passwordLabel = UI.Text {
x = 3, y = 2,
value = 'Password'
},
newPass = UI.TextEntry {
x = 12, ex = -3, y = 2,
limit = 32,
mask = true,
shadowText = 'password',
},
--[[
groupLabel = UI.Text {
x = 3, y = 3,
value = 'Group'
},
group = UI.TextEntry {
x = 12, ex = -3, y = 3,
limit = 32,
shadowText = 'network group',
},
groupLabel = UI.Text {
x = 3, y = 3,
value = 'Group'
},
group = UI.TextEntry {
x = 12, ex = -3, y = 3,
limit = 32,
shadowText = 'network group',
},
]]
intro = UI.TextArea {
textColor = colors.yellow,
inactive = true,
x = 3, ex = -3, y = 5, ey = -3,
value = string.format(passwordIntro, Ansi.white),
},
intro = UI.TextArea {
textColor = colors.yellow,
inactive = true,
x = 3, ex = -3, y = 5, ey = -3,
value = string.format(passwordIntro, Ansi.white),
},
},
packages = UI.WizardPage {
index = 4,
button = UI.Button {
x = 3, y = -3,
text = 'Open Package Manager',
event = 'packages',
},
intro = UI.TextArea {
textColor = colors.yellow,
inactive = true,
x = 3, ex = -3, y = 2, ey = -4,
value = string.format(packagesIntro, Ansi.white),
},
button = UI.Button {
x = 3, y = -3,
text = 'Open Package Manager',
event = 'packages',
},
intro = UI.TextArea {
textColor = colors.yellow,
inactive = true,
x = 3, ex = -3, y = 2, ey = -4,
value = string.format(packagesIntro, Ansi.white),
},
},
},
},
notification = UI.Notification { },
},
notification = UI.Notification { },
}
function page.wizard.pages.label:validate()
os.setComputerLabel(self.label.value)
return true
os.setComputerLabel(self.label.value)
return true
end
function page.wizard.pages.password:validate()
if #self.newPass.value > 0 then
Security.updatePassword(SHA1.sha1(self.newPass.value))
end
--[[
if #self.group.value > 0 then
local config = Config.load('os')
config.group = self.group.value
Config.update('os', config)
end
]]
return true
if #self.newPass.value > 0 then
Security.updatePassword(SHA1.sha1(self.newPass.value))
end
--[[
if #self.group.value > 0 then
local config = Config.load('os')
config.group = self.group.value
Config.update('os', config)
end
]]
return true
end
function page:eventHandler(event)
if event.type == 'skip' then
self.wizard:emit({ type = 'nextView' })
if event.type == 'skip' then
self.wizard:emit({ type = 'nextView' })
elseif event.type == 'view_enabled' then
event.view:focusFirst()
elseif event.type == 'view_enabled' then
event.view:focusFirst()
elseif event.type == 'packages' then
shell.openForegroundTab('PackageManager')
elseif event.type == 'packages' then
shell.openForegroundTab('PackageManager')
elseif event.type == 'wizard_complete' or event.type == 'cancel' then
UI.exitPullEvents()
elseif event.type == 'wizard_complete' or event.type == 'cancel' then
UI.exitPullEvents()
else
return UI.Page.eventHandler(self, event)

View File

@ -11,60 +11,60 @@ local term = _G.term
local success = true
local function runDir(directory)
if not fs.exists(directory) then
return true
end
if not fs.exists(directory) then
return true
end
local files = fs.list(directory)
table.sort(files)
local files = fs.list(directory)
table.sort(files)
for _,file in ipairs(files) do
os.sleep(0)
local result, err = shell.run(directory .. '/' .. file)
for _,file in ipairs(files) do
os.sleep(0)
local result, err = shell.run(directory .. '/' .. file)
if result then
if term.isColor() then
term.setTextColor(colors.green)
end
term.write('[PASS] ')
term.setTextColor(colors.white)
term.write(fs.combine(directory, file))
print()
else
if term.isColor() then
term.setTextColor(colors.red)
end
term.write('[FAIL] ')
term.setTextColor(colors.white)
term.write(fs.combine(directory, file))
if err then
_G.printError('\n' .. err)
end
print()
success = false
end
end
if result then
if term.isColor() then
term.setTextColor(colors.green)
end
term.write('[PASS] ')
term.setTextColor(colors.white)
term.write(fs.combine(directory, file))
print()
else
if term.isColor() then
term.setTextColor(colors.red)
end
term.write('[FAIL] ')
term.setTextColor(colors.white)
term.write(fs.combine(directory, file))
if err then
_G.printError('\n' .. err)
end
print()
success = false
end
end
end
runDir('sys/autorun')
for name in pairs(Packages:installed()) do
local packageDir = 'packages/' .. name .. '/autorun'
runDir(packageDir)
local packageDir = 'packages/' .. name .. '/autorun'
runDir(packageDir)
end
runDir('usr/autorun')
if not success then
if multishell then
multishell.setFocus(multishell.getCurrent())
end
_G.printError('A startup program has errored')
print('Press enter to continue')
if multishell then
multishell.setFocus(multishell.getCurrent())
end
_G.printError('A startup program has errored')
print('Press enter to continue')
while true do
local e, code = os.pullEventRaw('key')
if e == 'terminate' or e == 'key' and code == keys.enter then
break
end
end
while true do
local e, code = os.pullEventRaw('key')
if e == 'terminate' or e == 'key' and code == keys.enter then
break
end
end
end

View File

@ -7,32 +7,32 @@ local shell = _ENV.shell
local args = { ... }
if not args[1] then
error('Syntax: cedit <filename>')
error('Syntax: cedit <filename>')
end
if not _G.http.websocket then
error('Requires CC: Tweaked')
error('Requires CC: Tweaked')
end
if not _G.cloud_catcher then
local key = Config.load('cloud').key
local key = Config.load('cloud').key
if not key then
print('Visit https://cloud-catcher.squiddev.cc')
print('Paste key: ')
key = read()
if #key == 0 then
return
end
end
if not key then
print('Visit https://cloud-catcher.squiddev.cc')
print('Paste key: ')
key = read()
if #key == 0 then
return
end
end
-- open an unfocused tab
local id = shell.openTab('cloud ' .. key)
print('Connecting...')
while not _G.cloud_catcher do
os.sleep(.2)
end
multishell.setTitle(id, 'Cloud')
-- open an unfocused tab
local id = shell.openTab('cloud ' .. key)
print('Connecting...')
while not _G.cloud_catcher do
os.sleep(.2)
end
multishell.setTitle(id, 'Cloud')
end
shell.run('cloud edit ' .. table.unpack({ ... }))

View File

@ -4,20 +4,20 @@ local read = _G.read
local shell = _ENV.shell
if not _G.http.websocket then
error('Requires CC: Tweaked')
error('Requires CC: Tweaked')
end
if not _G.cloud_catcher then
local key = Config.load('cloud').key
local key = Config.load('cloud').key
if not key then
print('Visit https://cloud-catcher.squiddev.cc')
print('Paste key: ')
key = read()
if #key == 0 then
return
end
end
print('Connecting...')
shell.run('cloud ' .. key)
if not key then
print('Visit https://cloud-catcher.squiddev.cc')
print('Paste key: ')
key = read()
if #key == 0 then
return
end
end
print('Connecting...')
shell.run('cloud ' .. key)
end

View File

@ -1,115 +0,0 @@
local Event = require('event')
local Util = require('util')
local fs = _G.fs
local modem = _G.device.wireless_modem
local os = _G.os
local computerId = os.getComputerID()
--modem.open(80)
-- https://github.com/golgote/neturl/blob/master/lib/net/url.lua
local function parseQuery(str)
local sep = '&'
local values = {}
for key,val in str:gmatch(string.format('([^%q=]+)(=*[^%q=]*)', sep, sep)) do
--local key = decode(key)
local keys = {}
key = key:gsub('%[([^%]]*)%]', function(v)
-- extract keys between balanced brackets
if string.find(v, "^-?%d+$") then
v = tonumber(v)
--else
--v = decode(v)
end
table.insert(keys, v)
return "="
end)
key = key:gsub('=+.*$', "")
key = key:gsub('%s', "_") -- remove spaces in parameter name
val = val:gsub('^=+', "")
if not values[key] then
values[key] = {}
end
if #keys > 0 and type(values[key]) ~= 'table' then
values[key] = {}
elseif #keys == 0 and type(values[key]) == 'table' then
values[key] = val --decode(val)
end
local t = values[key]
for i,k in ipairs(keys) do
if type(t) ~= 'table' then
t = {}
end
if k == "" then
k = #t+1
end
if not t[k] then
t[k] = {}
end
if i == #keys then
t[k] = val --decode(val)
end
t = t[k]
end
end
return values
end
local function getListing(path, recursive)
local list = { }
local function listing(p)
for _, f in pairs(fs.listEx(p)) do
local abs = fs.combine(p, f.name)
table.insert(list, {
isDir = f.isDir,
path = string.sub(abs, #path + 1),
size = f.size,
})
if recursive and f.isDir then
listing(abs)
end
end
end
listing(path)
return list
end
--[[
Event.on('modem_message', function(_, _, dport, dhost, request)
if dport == 80 and dhost == computerId and type(request) == 'table' then
if request.method == 'GET' then
local query
if not request.path or type(request.path) ~= 'string' then
return
end
local path = request.path:gsub('%?(.*)', function(v)
query = parseQuery(v)
return ''
end)
if fs.isDir(path) then
-- TODO: more validation
modem.transmit(request.replyPort, request.replyAddress, {
statusCode = 200,
contentType = 'table/directory',
data = getListing(path, query and query.recursive == 'true'),
})
elseif fs.exists(path) then
modem.transmit(request.replyPort, request.replyAddress, {
statusCode = 200,
contentType = 'table/file',
data = Util.readFile(path),
})
else
modem.transmit(request.replyPort, request.replyAddress, {
statusCode = 404,
})
end
end
end
end)
]]

View File

@ -156,7 +156,7 @@ local function sendInfo()
info.label = os.getComputerLabel()
info.uptime = math.floor(os.clock())
info.group = network.getGroup()
if turtle then
if turtle and turtle.getStatus then
info.fuel = turtle.getFuelLevel()
info.status = turtle.getStatus()
info.point = turtle.point

View File

@ -1,69 +0,0 @@
local Event = require('event')
local Terminal = require('terminal')
local Util = require('util')
local colors = _G.colors
local modem = _G.device.wireless_modem
local term = _G.term
local textutils = _G.textutils
local terminal = Terminal.window(term.current())
terminal.setMaxScroll(300)
local oldTerm = term.redirect(terminal)
local function syntax()
error('Syntax: sniff [port]')
end
local port = ({ ... })[1] or syntax()
port = tonumber(port) or syntax()
Event.on('modem_message',
function(_, _, dport, _, data, _)
if dport == port then
terminal.scrollBottom()
terminal.setTextColor(colors.white)
print(textutils.serialize(data))
end
end)
Event.on('mouse_scroll', function(_, direction)
if direction == -1 then
terminal.scrollUp()
else
terminal.scrollDown()
end
end)
local function sniffer(_, _, data)
terminal.scrollBottom()
terminal.setTextColor(colors.yellow)
local ot = term.redirect(terminal)
print(textutils.serialize(data))
term.redirect(ot)
end
local socket = _G.transport.sockets[port]
if socket then
if not socket.sniffers then
socket.sniffers = { modem.transmit }
socket.transmit = function(...)
for _,v in pairs(socket.sniffers) do
v(...)
end
end
end
table.insert(socket.sniffers, sniffer)
end
local s, m = pcall(Event.pullEvents)
if socket then
Util.removeByValue(socket.sniffers, sniffer)
end
term.redirect(oldTerm)
if not s and m then
error(m)
end

View File

@ -7,51 +7,51 @@ local colors = _G.colors
-- -t80x30
if _G.http.websocket then
local config = Config.load('cloud')
local config = Config.load('cloud')
local tab = UI.Tab {
tabTitle = 'Cloud',
description = 'Cloud catcher options',
key = UI.TextEntry {
x = 3, ex = -3, y = 2,
limit = 32,
value = config.key,
shadowText = 'Cloud key',
accelerators = {
enter = 'update_key',
},
},
button = UI.Button {
x = 3, y = 4,
text = 'Update',
event = 'update_key',
},
labelText = UI.TextArea {
x = 3, ex = -3, y = 6,
textColor = colors.yellow,
marginLeft = 0, marginRight = 0,
value = string.format(
local tab = UI.Tab {
tabTitle = 'Cloud',
description = 'Cloud catcher options',
key = UI.TextEntry {
x = 3, ex = -3, y = 2,
limit = 32,
value = config.key,
shadowText = 'Cloud key',
accelerators = {
enter = 'update_key',
},
},
button = UI.Button {
x = 3, y = 4,
text = 'Update',
event = 'update_key',
},
labelText = UI.TextArea {
x = 3, ex = -3, y = 6,
textColor = colors.yellow,
marginLeft = 0, marginRight = 0,
value = string.format(
[[Use a non-changing cloud key. Note that only a single computer can use this session at one time.
To obtain a key, visit:
%shttps://cloud-catcher.squiddev.cc%s then bookmark:
%shttps://cloud-catcher.squiddev.cc/?id=KEY
]],
Ansi.white, Ansi.reset, Ansi.white),
},
}
]],
Ansi.white, Ansi.reset, Ansi.white),
},
}
function tab:eventHandler(event)
if event.type == 'update_key' then
if #self.key.value > 0 then
config.key = self.key.value
else
config.key = nil
end
Config.update('cloud', config)
self:emit({ type = 'success_message', message = 'Updated' })
end
end
function tab:eventHandler(event)
if event.type == 'update_key' then
if #self.key.value > 0 then
config.key = self.key.value
else
config.key = nil
end
Config.update('cloud', config)
self:emit({ type = 'success_message', message = 'Updated' })
end
end
return tab
return tab
end

View File

@ -14,13 +14,13 @@ local tab = UI.Tab {
formLabel = 'Monitor', formKey = 'monitor',
},
textScale = UI.Chooser {
formLabel = 'Font Size', formKey = 'textScale',
nochoice = 'Small',
choices = {
{ name = 'Small', value = '.5' },
{ name = 'Large', value = '1' },
},
help = 'Adjust text scaling',
formLabel = 'Font Size', formKey = 'textScale',
nochoice = 'Small',
choices = {
{ name = 'Small', value = '.5' },
{ name = 'Large', value = '1' },
},
help = 'Adjust text scaling',
},
labelText = UI.TextArea {
x = 2, ex = -2, y = 5,

View File

@ -9,76 +9,76 @@ local config = Config.load('multishell')
local tab = UI.Tab {
tabTitle = 'Launcher',
description = 'Set the application launcher',
launcherLabel = UI.Text {
x = 3, y = 2,
value = 'Launcher',
},
launcher = UI.Chooser {
x = 13, y = 2, width = 12,
choices = {
{ name = 'Overview', value = 'sys/apps/Overview.lua' },
{ name = 'Shell', value = 'sys/apps/ShellLauncher.lua' },
{ name = 'Custom', value = 'custom' },
},
},
custom = UI.TextEntry {
x = 13, ex = -3, y = 3,
limit = 128,
shadowText = 'File name',
},
button = UI.Button {
x = 3, y = 5,
text = 'Update',
event = 'update',
},
labelText = UI.TextArea {
x = 3, ex = -3, y = 7,
textColor = colors.yellow,
value = 'Choose an application launcher',
},
launcherLabel = UI.Text {
x = 3, y = 2,
value = 'Launcher',
},
launcher = UI.Chooser {
x = 13, y = 2, width = 12,
choices = {
{ name = 'Overview', value = 'sys/apps/Overview.lua' },
{ name = 'Shell', value = 'sys/apps/ShellLauncher.lua' },
{ name = 'Custom', value = 'custom' },
},
},
custom = UI.TextEntry {
x = 13, ex = -3, y = 3,
limit = 128,
shadowText = 'File name',
},
button = UI.Button {
x = 3, y = 5,
text = 'Update',
event = 'update',
},
labelText = UI.TextArea {
x = 3, ex = -3, y = 7,
textColor = colors.yellow,
value = 'Choose an application launcher',
},
}
function tab:enable()
local launcher = config.launcher and 'custom' or 'sys/apps/Overview.lua'
local launcher = config.launcher and 'custom' or 'sys/apps/Overview.lua'
for _, v in pairs(self.launcher.choices) do
if v.value == config.launcher then
launcher = v.value
break
end
end
for _, v in pairs(self.launcher.choices) do
if v.value == config.launcher then
launcher = v.value
break
end
end
UI.Tab.enable(self)
self.launcher.value = launcher
self.custom.enabled = launcher == 'custom'
self.launcher.value = launcher
self.custom.enabled = launcher == 'custom'
end
function tab:eventHandler(event)
if event.type == 'choice_change' then
self.custom.enabled = event.value == 'custom'
if self.custom.enabled then
self.custom.value = config.launcher
end
self:draw()
self.custom.enabled = event.value == 'custom'
if self.custom.enabled then
self.custom.value = config.launcher
end
self:draw()
elseif event.type == 'update' then
local launcher
elseif event.type == 'update' then
local launcher
if self.launcher.value ~= 'custom' then
launcher = self.launcher.value
elseif fs.exists(self.custom.value) and not fs.isDir(self.custom.value) then
launcher = self.custom.value
end
if self.launcher.value ~= 'custom' then
launcher = self.launcher.value
elseif fs.exists(self.custom.value) and not fs.isDir(self.custom.value) then
launcher = self.custom.value
end
if launcher then
config.launcher = launcher
Config.update('multishell', config)
self:emit({ type = 'success_message', message = 'Updated' })
else
self:emit({ type = 'error_message', message = 'Invalid file' })
end
end
if launcher then
config.launcher = launcher
Config.update('multishell', config)
self:emit({ type = 'success_message', message = 'Updated' })
else
self:emit({ type = 'error_message', message = 'Invalid file' })
end
end
end
return tab

View File

@ -13,91 +13,91 @@ local tab = UI.Tab {
accelerators = {
enter = 'update_path',
},
help = 'add a new path',
help = 'add a new path',
},
grid = UI.Grid {
y = 4, ey = -3,
disableHeader = true,
columns = { { key = 'value' } },
autospace = true,
sortColumn = 'index',
help = 'double-click to remove, shift-arrow to move',
accelerators = {
delete = 'remove',
},
sortColumn = 'index',
help = 'double-click to remove, shift-arrow to move',
accelerators = {
delete = 'remove',
},
},
statusBar = UI.StatusBar { },
accelerators = {
[ 'shift-up' ] = 'move_up',
[ 'shift-down' ] = 'move_down',
},
statusBar = UI.StatusBar { },
accelerators = {
[ 'shift-up' ] = 'move_up',
[ 'shift-down' ] = 'move_down',
},
}
function tab:updateList(path)
self.grid.values = { }
for k,v in ipairs(Util.split(path, '(.-):')) do
table.insert(self.grid.values, { index = k, value = v })
end
self.grid:update()
end
self.grid:update()
end
function tab:enable()
local env = Config.load('shell')
self:updateList(env.path)
local env = Config.load('shell')
self:updateList(env.path)
UI.Tab.enable(self)
end
function tab:save()
local t = { }
for _, v in ipairs(self.grid.values) do
table.insert(t, v.value)
end
local env = Config.load('shell')
env.path = table.concat(t, ':')
self:updateList(env.path)
Config.update('shell', env)
local t = { }
for _, v in ipairs(self.grid.values) do
table.insert(t, v.value)
end
local env = Config.load('shell')
env.path = table.concat(t, ':')
self:updateList(env.path)
Config.update('shell', env)
end
function tab:eventHandler(event)
if event.type == 'update_path' then
table.insert(self.grid.values, {
value = self.entry.value,
})
if event.type == 'update_path' then
table.insert(self.grid.values, {
value = self.entry.value,
})
self:save()
self.entry:reset()
self.entry:draw()
self.grid:draw()
self.entry:draw()
self.grid:draw()
return true
elseif event.type == 'grid_select' or event.type == 'remove' then
local selected = self.grid:getSelected()
if selected then
table.remove(self.grid.values, selected.index)
self:save()
self.grid:draw()
end
elseif event.type == 'grid_select' or event.type == 'remove' then
local selected = self.grid:getSelected()
if selected then
table.remove(self.grid.values, selected.index)
self:save()
self.grid:draw()
end
elseif event.type == 'focus_change' then
elseif event.type == 'focus_change' then
self.statusBar:setStatus(event.focused.help)
elseif event.type == 'move_up' then
local entry = self.grid:getSelected()
if entry.index > 1 then
table.insert(self.grid.values, entry.index - 1, table.remove(self.grid.values, entry.index))
self.grid:setIndex(entry.index - 1)
self:save()
self.grid:draw()
end
elseif event.type == 'move_up' then
local entry = self.grid:getSelected()
if entry.index > 1 then
table.insert(self.grid.values, entry.index - 1, table.remove(self.grid.values, entry.index))
self.grid:setIndex(entry.index - 1)
self:save()
self.grid:draw()
end
elseif event.type == 'move_down' then
local entry = self.grid:getSelected()
if entry.index < #self.grid.values then
table.insert(self.grid.values, entry.index + 1, table.remove(self.grid.values, entry.index))
self.grid:setIndex(entry.index + 1)
self:save()
self.grid:draw()
end
end
elseif event.type == 'move_down' then
local entry = self.grid:getSelected()
if entry.index < #self.grid.values then
table.insert(self.grid.values, entry.index + 1, table.remove(self.grid.values, entry.index))
self.grid:setIndex(entry.index + 1)
self:save()
self.grid:draw()
end
end
end
return tab

View File

@ -13,91 +13,91 @@ local tab = UI.Tab {
accelerators = {
enter = 'update_path',
},
help = 'add a new path (reboot required)',
help = 'add a new path (reboot required)',
},
grid = UI.Grid {
y = 4, ey = -3,
disableHeader = true,
columns = { { key = 'value' } },
autospace = true,
sortColumn = 'index',
help = 'double-click to remove, shift-arrow to move',
accelerators = {
delete = 'remove',
},
},
statusBar = UI.StatusBar { },
accelerators = {
[ 'shift-up' ] = 'move_up',
[ 'shift-down' ] = 'move_down',
},
autospace = true,
sortColumn = 'index',
help = 'double-click to remove, shift-arrow to move',
accelerators = {
delete = 'remove',
},
},
statusBar = UI.StatusBar { },
accelerators = {
[ 'shift-up' ] = 'move_up',
[ 'shift-down' ] = 'move_down',
},
}
function tab:updateList(lua_path)
self.grid.values = { }
for k,v in ipairs(Util.split(lua_path, '(.-);')) do
table.insert(self.grid.values, { index = k, value = v })
end
self.grid:update()
end
self.grid:update()
end
function tab:enable()
local env = Config.load('shell')
self:updateList(env.lua_path)
local env = Config.load('shell')
self:updateList(env.lua_path)
UI.Tab.enable(self)
end
function tab:save()
local t = { }
for _, v in ipairs(self.grid.values) do
table.insert(t, v.value)
end
local env = Config.load('shell')
env.lua_path = table.concat(t, ';')
self:updateList(env.lua_path)
Config.update('shell', env)
local t = { }
for _, v in ipairs(self.grid.values) do
table.insert(t, v.value)
end
local env = Config.load('shell')
env.lua_path = table.concat(t, ';')
self:updateList(env.lua_path)
Config.update('shell', env)
end
function tab:eventHandler(event)
if event.type == 'update_path' then
table.insert(self.grid.values, {
value = self.entry.value,
})
self:save()
if event.type == 'update_path' then
table.insert(self.grid.values, {
value = self.entry.value,
})
self:save()
self.entry:reset()
self.entry:draw()
self.grid:draw()
self.entry:draw()
self.grid:draw()
return true
elseif event.type == 'grid_select' or event.type == 'remove' then
local selected = self.grid:getSelected()
if selected then
table.remove(self.grid.values, selected.index)
self:save()
self.grid:draw()
end
elseif event.type == 'grid_select' or event.type == 'remove' then
local selected = self.grid:getSelected()
if selected then
table.remove(self.grid.values, selected.index)
self:save()
self.grid:draw()
end
elseif event.type == 'focus_change' then
elseif event.type == 'focus_change' then
self.statusBar:setStatus(event.focused.help)
elseif event.type == 'move_up' then
local entry = self.grid:getSelected()
if entry.index > 1 then
table.insert(self.grid.values, entry.index - 1, table.remove(self.grid.values, entry.index))
self.grid:setIndex(entry.index - 1)
self:save()
self.grid:draw()
end
elseif event.type == 'move_up' then
local entry = self.grid:getSelected()
if entry.index > 1 then
table.insert(self.grid.values, entry.index - 1, table.remove(self.grid.values, entry.index))
self.grid:setIndex(entry.index - 1)
self:save()
self.grid:draw()
end
elseif event.type == 'move_down' then
local entry = self.grid:getSelected()
if entry.index < #self.grid.values then
table.insert(self.grid.values, entry.index + 1, table.remove(self.grid.values, entry.index))
self.grid:setIndex(entry.index + 1)
self:save()
self.grid:draw()
end
end
elseif event.type == 'move_down' then
local entry = self.grid:getSelected()
if entry.index < #self.grid.values then
table.insert(self.grid.values, entry.index + 1, table.remove(self.grid.values, entry.index))
self.grid:setIndex(entry.index + 1)
self:save()
self.grid:draw()
end
end
end
return tab

View File

@ -9,134 +9,134 @@ local config = Config.load('shellprompt')
local allColors = { }
for k,v in pairs(colors) do
if type(v) == 'number' then
table.insert(allColors, { name = k, value = v })
end
if type(v) == 'number' then
table.insert(allColors, { name = k, value = v })
end
end
local defaults = {
textColor = colors.white,
commandTextColor = colors.yellow,
directoryTextColor = colors.orange,
directoryBackgroundColor = colors.black,
promptTextColor = colors.blue,
promptBackgroundColor = colors.black,
directoryColor = colors.green,
fileColor = colors.white,
backgroundColor = colors.black,
textColor = colors.white,
commandTextColor = colors.yellow,
directoryTextColor = colors.orange,
directoryBackgroundColor = colors.black,
promptTextColor = colors.blue,
promptBackgroundColor = colors.black,
directoryColor = colors.green,
fileColor = colors.white,
backgroundColor = colors.black,
}
local _colors = config.color or Util.shallowCopy(defaults)
local allSettings = { }
for k, v in pairs(defaults) do
table.insert(allSettings, { name = k })
table.insert(allSettings, { name = k })
end
-- temp
if not _colors.backgroundColor then
_colors.backgroundColor = colors.black
_colors.fileColor = colors.white
_colors.backgroundColor = colors.black
_colors.fileColor = colors.white
end
local tab = UI.Tab {
tabTitle = 'Shell',
description = 'Shell options',
grid1 = UI.ScrollingGrid {
y = 2, ey = -10, x = 3, ex = -16,
disableHeader = true,
columns = { { key = 'name' } },
values = allSettings,
sortColumn = 'name',
},
grid2 = UI.ScrollingGrid {
y = 2, ey = -10, x = -14, ex = -3,
description = 'Shell options',
grid1 = UI.ScrollingGrid {
y = 2, ey = -10, x = 3, ex = -16,
disableHeader = true,
columns = { { key = 'name' } },
values = allColors,
sortColumn = 'name',
values = allSettings,
sortColumn = 'name',
},
grid2 = UI.ScrollingGrid {
y = 2, ey = -10, x = -14, ex = -3,
disableHeader = true,
columns = { { key = 'name' } },
values = allColors,
sortColumn = 'name',
},
directoryLabel = UI.Text {
x = 2, y = -2,
value = 'Display directory',
},
directory = UI.Checkbox {
x = 20, y = -2,
value = config.displayDirectory
},
reset = UI.Button {
x = -18, y = -2,
text = 'Reset',
event = 'reset',
},
button = UI.Button {
x = -9, y = -2,
text = 'Update',
event = 'update',
},
display = UI.Window {
x = 3, ex = -3, y = -8, height = 5,
},
directoryLabel = UI.Text {
x = 2, y = -2,
value = 'Display directory',
},
directory = UI.Checkbox {
x = 20, y = -2,
value = config.displayDirectory
},
reset = UI.Button {
x = -18, y = -2,
text = 'Reset',
event = 'reset',
},
button = UI.Button {
x = -9, y = -2,
text = 'Update',
event = 'update',
},
display = UI.Window {
x = 3, ex = -3, y = -8, height = 5,
},
}
function tab.grid2:getRowTextColor(row)
local selected = tab.grid1:getSelected()
if _colors[selected.name] == row.value then
return colors.yellow
end
return UI.Grid.getRowTextColor(self, row)
local selected = tab.grid1:getSelected()
if _colors[selected.name] == row.value then
return colors.yellow
end
return UI.Grid.getRowTextColor(self, row)
end
function tab.display:draw()
self:clear(_colors.backgroundColor)
local offset = 0
if config.displayDirectory then
self:write(1, 1,
'==' .. os.getComputerLabel() .. ':/dir/etc',
_colors.directoryBackgroundColor, _colors.directoryTextColor)
offset = 1
end
self:clear(_colors.backgroundColor)
local offset = 0
if config.displayDirectory then
self:write(1, 1,
'==' .. os.getComputerLabel() .. ':/dir/etc',
_colors.directoryBackgroundColor, _colors.directoryTextColor)
offset = 1
end
self:write(1, 1 + offset, '$ ',
_colors.promptBackgroundColor, _colors.promptTextColor)
self:write(1, 1 + offset, '$ ',
_colors.promptBackgroundColor, _colors.promptTextColor)
self:write(3, 1 + offset, 'ls /',
_colors.backgroundColor, _colors.commandTextColor)
self:write(3, 1 + offset, 'ls /',
_colors.backgroundColor, _colors.commandTextColor)
self:write(1, 2 + offset, 'sys usr',
_colors.backgroundColor, _colors.directoryColor)
self:write(1, 2 + offset, 'sys usr',
_colors.backgroundColor, _colors.directoryColor)
self:write(1, 3 + offset, 'startup',
_colors.backgroundColor, _colors.fileColor)
self:write(1, 3 + offset, 'startup',
_colors.backgroundColor, _colors.fileColor)
end
function tab:eventHandler(event)
if event.type =='checkbox_change' then
config.displayDirectory = not not event.checked
self.display:draw()
if event.type =='checkbox_change' then
config.displayDirectory = not not event.checked
self.display:draw()
elseif event.type == 'grid_focus_row' and event.element == self.grid1 then
self.grid2:draw()
elseif event.type == 'grid_focus_row' and event.element == self.grid1 then
self.grid2:draw()
elseif event.type == 'grid_select' and event.element == self.grid2 then
_colors[tab.grid1:getSelected().name] = event.selected.value
self.display:draw()
self.grid2:draw()
elseif event.type == 'grid_select' and event.element == self.grid2 then
_colors[tab.grid1:getSelected().name] = event.selected.value
self.display:draw()
self.grid2:draw()
elseif event.type == 'reset' then
config.color = defaults
config.displayDirectory = true
self.directory.value = true
_colors = Util.shallowCopy(defaults)
elseif event.type == 'reset' then
config.color = defaults
config.displayDirectory = true
self.directory.value = true
_colors = Util.shallowCopy(defaults)
Config.update('shellprompt', config)
self:draw()
Config.update('shellprompt', config)
self:draw()
elseif event.type == 'update' then
config.color = _colors
Config.update('shellprompt', config)
elseif event.type == 'update' then
config.color = _colors
Config.update('shellprompt', config)
end
return UI.Tab.eventHandler(self, event)
end
return UI.Tab.eventHandler(self, event)
end
return tab

View File

@ -1,22 +1,22 @@
local function completeMultipleChoice(sText, tOptions, bAddSpaces)
local tResults = { }
for n = 1,#tOptions do
local sOption = tOptions[n]
if #sOption + (bAddSpaces and 1 or 0) > #sText and string.sub(sOption, 1, #sText) == sText then
local sResult = string.sub(sOption, #sText + 1)
if bAddSpaces then
table.insert(tResults, sResult .. " ")
else
table.insert(tResults, sResult)
end
end
end
return tResults
local tResults = { }
for n = 1,#tOptions do
local sOption = tOptions[n]
if #sOption + (bAddSpaces and 1 or 0) > #sText and string.sub(sOption, 1, #sText) == sText then
local sResult = string.sub(sOption, #sText + 1)
if bAddSpaces then
table.insert(tResults, sResult .. " ")
else
table.insert(tResults, sResult)
end
end
end
return tResults
end
_ENV.shell.setCompletionFunction("sys/apps/package.lua",
function(_, index, text)
if index == 1 then
return completeMultipleChoice(text, { "install ", "update ", "uninstall " })
end
end)
function(_, index, text)
if index == 1 then
return completeMultipleChoice(text, { "install ", "update ", "uninstall " })
end
end)

View File

@ -4,8 +4,8 @@ local shell = _ENV.shell
local config = Config.load('os')
if not config.welcomed and shell.openForegroundTab then
config.welcomed = true
Config.update('os', config)
config.welcomed = true
Config.update('os', config)
shell.openForegroundTab('Welcome')
shell.openForegroundTab('Welcome')
end

View File

@ -6,47 +6,47 @@ local os = _G.os
local peripheral = _G.peripheral
local containers = {
manipulator = true,
neuralInterface = true,
manipulator = true,
neuralInterface = true,
}
local function getModules(dev, side)
local list = { }
local list = { }
if dev then
for _, module in pairs(dev.listModules()) do
list[module] = Util.shallowCopy(dev)
list[module].name = module
list[module].type = module
list[module].side = side
end
end
return list
if dev then
for _, module in pairs(dev.listModules()) do
list[module] = Util.shallowCopy(dev)
list[module].name = module
list[module].type = module
list[module].side = side
end
end
return list
end
for _,v in pairs(device) do
if containers[v.type] then
local list = getModules(v, v.side)
for k, dev in pairs(list) do
-- neural and attached modules have precedence over manipulator modules
if not device[k] or v.type ~= 'manipulator' then
device[k] = dev
end
end
end
if containers[v.type] then
local list = getModules(v, v.side)
for k, dev in pairs(list) do
-- neural and attached modules have precedence over manipulator modules
if not device[k] or v.type ~= 'manipulator' then
device[k] = dev
end
end
end
end
-- register modules as peripherals
kernel.hook('device_attach', function(_, eventData)
local dev = eventData[2]
local dev = eventData[2]
if dev and containers[dev.type] then
local list = getModules(peripheral.wrap(dev.side), dev.side)
for k,v in pairs(list) do
if not device[k] or dev.type ~= 'manipulator' then
device[k] = v
os.queueEvent('device_attach', k, v)
end
end
end
if dev and containers[dev.type] then
local list = getModules(peripheral.wrap(dev.side), dev.side)
for k,v in pairs(list) do
if not device[k] or dev.type ~= 'manipulator' then
device[k] = v
os.queueEvent('device_attach', k, v)
end
end
end
end)

View File

@ -2,26 +2,26 @@ local device = _G.device
local kernel = _G.kernel
local function register(v)
if v and v.isWireless and v.isAccessPoint and v.getNamesRemote then
v._children = { }
for _, name in pairs(v.getNamesRemote()) do
local dev = v.getMethodsRemote(name)
if dev then
dev.name = name
dev.side = name
dev.type = v.getTypeRemote(name)
device[name] = dev
table.insert(v._children, dev)
end
end
end
if v and v.isWireless and v.isAccessPoint and v.getNamesRemote then
v._children = { }
for _, name in pairs(v.getNamesRemote()) do
local dev = v.getMethodsRemote(name)
if dev then
dev.name = name
dev.side = name
dev.type = v.getTypeRemote(name)
device[name] = dev
table.insert(v._children, dev)
end
end
end
end
for _,v in pairs(device) do
register(v)
register(v)
end
-- register oc devices as peripherals
kernel.hook('device_attach', function(_, eventData)
register(device[eventData[2]])
register(device[eventData[2]])
end)

View File

@ -6,7 +6,9 @@ local help = _G.help
local shell = _ENV.shell
if not fs.exists('usr/config/packages') then
Packages:downloadList()
pcall(function()
Packages:downloadList()
end)
end
local appPaths = Util.split(shell.path(), '(.-):')

View File

@ -52,6 +52,7 @@ function turtle.resetState()
state.movePolicy = _defaultMove
state.moveCallback = noop
state.blacklist = nil
state.reference = nil -- gps reference when converting to relative coords
Pathing.reset()
return true
end
@ -77,8 +78,6 @@ local function _dig(name, inspect, dig)
return dig()
end
-- override dig
-- optionally check that the block is a certain type
function turtle.dig(s)
return _dig(s, turtle.inspect, turtle.native.dig)
end
@ -359,7 +358,7 @@ function turtle.set(args)
turtle.setDigPolicy(turtle.getPolicy(v))
elseif k == 'movePolicy' then
turtle.setMovePolicy(turtle.getPolicy(v))
state.movePolicy = turtle.getPolicy(v)
elseif k == 'movementStrategy' then
turtle.setMovementStrategy(v)
@ -379,6 +378,9 @@ function turtle.set(args)
elseif k == 'blacklist' then
state.blacklist = v
elseif k == 'reference' then
state.reference = v
else
error('Invalid turle.set: ' .. tostring(k))
end
@ -426,6 +428,10 @@ function turtle.setHeading(heading)
return false, 'Invalid heading'
end
if heading == turtle.point.heading then
return turtle.point
end
local fi = Point.facings[heading]
if not fi then
return false, 'Invalid heading'