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 = { } local BulkGet = { }
function BulkGet.download(list, callback) function BulkGet.download(list, callback)
local t = { } local t = { }
local failed = false local failed = false
for _ = 1, 5 do for _ = 1, 5 do
table.insert(t, function() table.insert(t, function()
while true do while true do
local entry = table.remove(list) local entry = table.remove(list)
if not entry then if not entry then
break break
end end
local s, m = Util.download(entry.url, entry.path) local s, m = Util.download(entry.url, entry.path)
if not s then if not s then
failed = true failed = true
end end
callback(entry, s, m) callback(entry, s, m)
if failed then if failed then
break break
end end
end end
end) end)
end end
parallel.waitForAll(table.unpack(t)) parallel.waitForAll(table.unpack(t))
end end
return BulkGet 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 Util = require('util')
local fs = _G.fs local fs = _G.fs
@ -39,7 +39,6 @@ function urlfs.getDrive()
end end
function urlfs.open(node, fn, fl) function urlfs.open(node, fn, fl)
if fl == 'w' or fl == 'wb' then if fl == 'w' or fl == 'wb' then
fs.delete(fn) fs.delete(fn)
return fs.open(fn, fl) return fs.open(fn, fl)
@ -51,12 +50,15 @@ function urlfs.open(node, fn, fl)
local c = node.cache local c = node.cache
if not c then if not c then
--[[
if node.url:match("^(rttps?:)") then if node.url:match("^(rttps?:)") then
local s, response = rttp.get(node.url) local s, response = rttp.get(node.url)
c = s and response.statusCode == 200 and response.data c = s and response.statusCode == 200 and response.data
else else
c = Util.httpGet(node.url) c = Util.httpGet(node.url)
end end
]]--
c = Util.httpGet(node.url)
if c then if c then
node.cache = c node.cache = c
node.size = #c node.size = #c

View File

@ -1,215 +1,584 @@
-- credit ElvishJerricco -- Module options:
-- http://pastebin.com/raw.php?i=4nRg9CHU 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 David Kolf's JSON module for Lua 5.1/5.2
local controls = {["\n"]="\\n", ["\r"]="\\r", ["\t"]="\\t", ["\b"]="\\b", ["\f"]="\\f", ["\""]="\\\"", ["\\"]="\\\\"} Version 2.5
local function isArray(t) For the documentation see the corresponding readme.txt or visit
local max = 0 <http://dkolf.de/src/dkjson-lua.fsl/>.
for k,v in pairs(t) do
if type(k) ~= "number" then You can contact the author by sending an e-mail to 'david' at the
return false domain 'dkolf.de'.
elseif k > max then
max = k Copyright (C) 2010-2014 David Heiko Kolf
end
end Refer to license located at https://github.com/LuaDist/dkjson/blob/master/dkjson.lua
return max == #t
--]==]
-- 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 end
local whites = {['\n']=true; ['\r']=true; ['\t']=true; [' ']=true; [',']=true; [':']=true} local _ENV = nil -- blocking globals in Lua 5.2
local function removeWhite(str)
while whites[str:sub(1, 1)] do
str = str:sub(2)
end
return str
end
------------------------------------------------------------------ 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) json.null = setmetatable ({}, {
local str = "" __tojson = function () return "null" end
})
-- Tabbing util local function isarray (tbl)
local function tab(s) local max, n, arraylen = 0, 0, 0
str = str .. ("\t"):rep(tabLevel) .. s for k,v in pairs (tbl) do
end if k == 'n' and type(v) == 'number' then
arraylen = v
local function arrEncoding(val, bracket, closeBracket, iterator, loopFunc) if v > max then
str = str .. bracket max = v
if pretty then end
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)
else else
arrEncoding(val, "{", "}", pairs, function(k,v) if type(k) ~= 'number' or k < 1 or floor(k) ~= k then
assert(type(k) == "string", "JSON object keys must be strings", 2) return false
str = str .. encodeCommon(k, pretty, tabLevel, tTracking) end
str = str .. (pretty and ": " or ":") .. encodeCommon(v, pretty, tabLevel, tTracking) if k > max then
end) max = k
end
n = n + 1
end end
-- String encoding end
elseif type(val) == "string" then if max > 10 and max > arraylen and max > n * 2 then
str = '"' .. val:gsub("[%c\"\\]", controls) .. '"' return false -- don't create an array with too many holes
-- Number encoding end
elseif type(val) == "number" or type(val) == "boolean" then return true, max
str = tostring(val) 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 else
error("JSON only supports arrays, objects, numbers, booleans, and strings", 2) return ""
end end
return str if value <= 0xffff then
end return strformat ("\\u%.4x", value)
elseif value <= 0x10ffff then
function json.encode(val) -- encode as UTF-16 surrogate pair
return encodeCommon(val, false, 0, {}) value = value - 0x10000
end local highsur, lowsur = 0xD800 + floor (value/0x400), 0xDC00 + (value % 0x400)
return strformat ("\\u%.4x\\u%.4x", highsur, lowsur)
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))
else else
return false, removeWhite(str:sub(6)) return ""
end end
end end
local function parseNull(str) local function fsub (str, pattern, repl)
return nil, removeWhite(str:sub(5)) -- gsub always builds a new string in a buffer, even when no match
end -- exists. First using find should be more efficient when most strings
-- don't contain the pattern.
local numChars = {['e']=true; ['E']=true; ['+']=true; ['-']=true; ['.']=true} if strfind (str, pattern) then
local function parseNumber(str) return gsub (str, pattern, repl)
local i = 1 else
while numChars[str:sub(i, i)] or tonumber(str:sub(i, i)) do return str
i = i + 1
end end
local val = tonumber(str:sub(1, i - 1))
str = removeWhite(str:sub(i))
return val, str
end end
local function parseString(str) local function quotestring (value)
str = str:sub(2) -- based on the regexp "escapable" in https://github.com/douglascrockford/JSON-js
local s = "" value = fsub (value, "[%z\1-\31\"\\\127]", escapeutf8)
while str:sub(1,1) ~= "\"" do if strfind (value, "[\194\216\220\225\226\239]") then
local next = str:sub(1,1) value = fsub (value, "\194[\128-\159\173]", escapeutf8)
str = str:sub(2) value = fsub (value, "\216[\128-\132]", escapeutf8)
assert(next ~= "\n", "Unclosed string") 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 function replace(str, o, n)
local escape = str:sub(1,1) local i, j = strfind (str, o, 1, true)
str = str:sub(2) 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 end
tables[value] = true
s = s .. next 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 end
return s, removeWhite(str:sub(2)) return buflen
end end
function json.parseArray(str) function json.encode (value, state)
str = removeWhite(str:sub(2)) state = state or {}
local oldbuffer = state.buffer
local val = {} local buffer = oldbuffer or {}
local i = 1 state.buffer = buffer
while str:sub(1, 1) ~= "]" do updatedecpoint()
local v local ret, msg = encode2 (value, state.indent, state.level or 0,
v, str = json.parseValue(str) buffer, state.bufferlen or 0, state.tables or {}, state.keyorder, state)
val[i] = v if not ret then
i = i + 1 error (msg, 2)
str = removeWhite(str) elseif oldbuffer == buffer then
end state.bufferlen = ret
str = removeWhite(str:sub(2)) return true
return val, str else
end state.bufferlen = nil
state.buffer = nil
function json.parseValue(str) return concat (buffer)
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)
end end
end end
function json.parseMember(str) local function loc (str, where)
local k, val local line, pos, linepos = 1, 1, 0
k, str = json.parseValue(str) while true do
val, str = json.parseValue(str) pos = strfind (str, "\n", pos, true)
return k, val, str if pos and pos < where then
end line = line + 1
linepos = pos
function json.parseObject(str) pos = pos + 1
str = removeWhite(str:sub(2)) else
break
local val = {} end
while str:sub(1, 1) ~= "}" do
local k, v
k, v, str = json.parseMember(str)
val[k] = v
str = removeWhite(str)
end end
str = removeWhite(str:sub(2)) return "line " .. line .. ", column " .. (where - linepos)
return val, str
end end
function json.decode(str) local function unterminated (str, what, where)
str = removeWhite(str) return nil, strlen (str) + 1, "unterminated " .. what .. " at " .. loc (str, where)
return json.parseValue(str)
end 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) function json.decodeFromFile(path)
local file = assert(fs.open(path, "r")) local file = assert(fs.open(path, "r"))
local decoded = json.decode(file.readAll()) local decoded = json.decode(file.readAll())

View File

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

View File

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

View File

@ -131,8 +131,11 @@ end
function Point.calculateMoves(pta, ptb, distance) function Point.calculateMoves(pta, ptb, distance)
local heading = pta.heading local heading = pta.heading
local moves = distance or Point.turtleDistance(pta, ptb) local moves = distance or Point.turtleDistance(pta, ptb)
local weighted = moves
if (pta.heading % 2) == 0 and pta.z ~= ptb.z then if (pta.heading % 2) == 0 and pta.z ~= ptb.z then
moves = moves + 1 moves = moves + 1
weighted = weighted + .9
if ptb.heading and (ptb.heading % 2 == 1) then if ptb.heading and (ptb.heading % 2 == 1) then
heading = ptb.heading heading = ptb.heading
elseif ptb.z > pta.z then elseif ptb.z > pta.z then
@ -142,6 +145,7 @@ function Point.calculateMoves(pta, ptb, distance)
end end
elseif (pta.heading % 2) == 1 and pta.x ~= ptb.x then elseif (pta.heading % 2) == 1 and pta.x ~= ptb.x then
moves = moves + 1 moves = moves + 1
weighted = weighted + .9
if ptb.heading and (ptb.heading % 2 == 0) then if ptb.heading and (ptb.heading % 2 == 0) then
heading = ptb.heading heading = ptb.heading
elseif ptb.x > pta.x then elseif ptb.x > pta.x then
@ -152,15 +156,18 @@ function Point.calculateMoves(pta, ptb, distance)
end end
if not ptb.heading then if not ptb.heading then
return moves, heading, moves return moves, heading, weighted
end 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 -- calc turns as slightly less than moves
local weighted = moves -- local weighted = moves
if heading ~= ptb.heading then if heading ~= ptb.heading then
local turns = Point.calculateTurns(heading, ptb.heading) local turns = Point.calculateTurns(heading, ptb.heading)
moves = moves + turns 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] weighted = weighted + wturns[turns]
heading = ptb.heading heading = ptb.heading
end end
@ -233,7 +240,7 @@ end
function Point.nearestTo(pta, ptb) function Point.nearestTo(pta, ptb)
local heading local heading
if pta.x < ptb.x then if pta.x < ptb.x then
heading = 0 heading = 0
elseif pta.z < ptb.z then elseif pta.z < ptb.z then
heading = 1 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 = { local Sync = {
syncLocks = { } syncLocks = { }
} }
local os = _G.os local os = _G.os
function Sync.sync(obj, fn) function Sync.sync(obj, fn)
local key = tostring(obj) local key = tostring(obj)
if Sync.syncLocks[key] then if Sync.syncLocks[key] then
local cos = tostring(coroutine.running()) local cos = tostring(coroutine.running())
table.insert(Sync.syncLocks[key], cos) table.insert(Sync.syncLocks[key], cos)
repeat repeat
local _, co = os.pullEvent('sync_lock') local _, co = os.pullEvent('sync_lock')
until co == cos until co == cos
else else
Sync.syncLocks[key] = { } Sync.syncLocks[key] = { }
end end
local s, m = pcall(fn) local s, m = pcall(fn)
local co = table.remove(Sync.syncLocks[key], 1) local co = table.remove(Sync.syncLocks[key], 1)
if co then if co then
os.queueEvent('sync_lock', co) os.queueEvent('sync_lock', co)
else else
Sync.syncLocks[key] = nil Sync.syncLocks[key] = nil
end end
if not s then if not s then
error(m) error(m)
end end
end end
function Sync.lock(obj) function Sync.lock(obj)
local key = tostring(obj) local key = tostring(obj)
if Sync.syncLocks[key] then if Sync.syncLocks[key] then
local cos = tostring(coroutine.running()) local cos = tostring(coroutine.running())
table.insert(Sync.syncLocks[key], cos) table.insert(Sync.syncLocks[key], cos)
repeat repeat
local _, co = os.pullEvent('sync_lock') local _, co = os.pullEvent('sync_lock')
until co == cos until co == cos
else else
Sync.syncLocks[key] = { } Sync.syncLocks[key] = { }
end end
end end
function Sync.release(obj) function Sync.release(obj)
local key = tostring(obj) local key = tostring(obj)
if not Sync.syncLocks[key] then if not Sync.syncLocks[key] then
error('Sync.release: Lock was not obtained', 2) error('Sync.release: Lock was not obtained', 2)
end end
local co = table.remove(Sync.syncLocks[key], 1) local co = table.remove(Sync.syncLocks[key], 1)
if co then if co then
os.queueEvent('sync_lock', co) os.queueEvent('sync_lock', co)
else else
Sync.syncLocks[key] = nil Sync.syncLocks[key] = nil
end end
end end
function Sync.isLocked(obj) function Sync.isLocked(obj)
local key = tostring(obj) local key = tostring(obj)
return not not Sync.syncLocks[key] return not not Sync.syncLocks[key]
end end
return Sync 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 debug_traceback = type(debug) == "table" and type(debug.traceback) == "function" and debug.traceback
local function traceback(x) local function traceback(x)
-- Attempt to detect error() and error("xyz", 0). -- Attempt to detect error() and error("xyz", 0).
-- This probably means they're erroring the program intentionally and so we -- This probably means they're erroring the program intentionally and so we
-- shouldn't display anything. -- shouldn't display anything.
if x == nil or (type(x) == "string" and not x:find(":%d+:")) then if x == nil or (type(x) == "string" and not x:find(":%d+:")) then
return x return x
end end
if debug_traceback then if debug_traceback then
-- The parens are important, as they prevent a tail call occuring, meaning -- The parens are important, as they prevent a tail call occuring, meaning
-- the stack level is preserved. This ensures the code behaves identically -- the stack level is preserved. This ensures the code behaves identically
-- on LuaJ and PUC Lua. -- on LuaJ and PUC Lua.
return (debug_traceback(tostring(x), 2)) return (debug_traceback(tostring(x), 2))
else else
local level = 3 local level = 3
local out = { tostring(x), "stack traceback:" } local out = { tostring(x), "stack traceback:" }
while true do while true do
local _, msg = pcall(error, "", level) local _, msg = pcall(error, "", level)
if msg == "" then break end if msg == "" then break end
out[#out + 1] = " " .. msg out[#out + 1] = " " .. msg
level = level + 1 level = level + 1
end end
return table.concat(out, "\n") return table.concat(out, "\n")
end end
end end
local function trim_traceback(target, marker) local function trim_traceback(target, marker)
local ttarget, tmarker = {}, {} local ttarget, tmarker = {}, {}
for line in target:gmatch("([^\n]*)\n?") do ttarget[#ttarget + 1] = line end 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 for line in marker:gmatch("([^\n]*)\n?") do tmarker[#tmarker + 1] = line end
-- Trim identical suffixes -- Trim identical suffixes
local t_len, m_len = #ttarget, #tmarker local t_len, m_len = #ttarget, #tmarker
while t_len >= 3 and ttarget[t_len] == tmarker[m_len] do while t_len >= 3 and ttarget[t_len] == tmarker[m_len] do
table.remove(ttarget, t_len) table.remove(ttarget, t_len)
t_len, m_len = t_len - 1, m_len - 1 t_len, m_len = t_len - 1, m_len - 1
end end
-- Trim elements from this file and xpcall invocations -- Trim elements from this file and xpcall invocations
while t_len >= 1 and ttarget[t_len]:find("^\tstack_trace%.lua:%d+:") or 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 ttarget[t_len] == "\t[C]: in function 'xpcall'" or ttarget[t_len] == " xpcall: " do
table.remove(ttarget, t_len) table.remove(ttarget, t_len)
t_len = t_len - 1 t_len = t_len - 1
end end
ttarget[#ttarget] = nil -- remove 2 calls added by the added xpcall ttarget[#ttarget] = nil -- remove 2 calls added by the added xpcall
ttarget[#ttarget] = nil ttarget[#ttarget] = nil
return ttarget return ttarget
end end
--- Run a function with --- Run a function with
return function (fn, ...) return function (fn, ...)
-- So this is rather grim: we need to get the full traceback and current one and remove -- So this is rather grim: we need to get the full traceback and current one and remove
-- the common prefix -- the common prefix
local trace local trace
local args = { ... } local args = { ... }
-- xpcall in Lua 5.1 does not accept parameters -- xpcall in Lua 5.1 does not accept parameters
-- which is not ideal -- which is not ideal
local res = table.pack(xpcall(function() local res = table.pack(xpcall(function()
return fn(table.unpack(args)) return fn(table.unpack(args))
end, traceback)) end, traceback))
if not res[1] then if not res[1] then
trace = traceback("trace.lua:1:") trace = traceback("trace.lua:1:")
end end
local ok, err = res[1], res[2] local ok, err = res[1], res[2]
if not ok and err ~= nil then if not ok and err ~= nil then
trace = trim_traceback(err, trace) trace = trim_traceback(err, trace)
-- Find the position where the stack traceback actually starts -- Find the position where the stack traceback actually starts
local trace_starts local trace_starts
for i = #trace, 1, -1 do for i = #trace, 1, -1 do
if trace[i] == "stack traceback:" then trace_starts = i; break end if trace[i] == "stack traceback:" then trace_starts = i; break end
end end
for _, line in pairs(trace) do for _, line in pairs(trace) do
_G._syslog(line) _G._syslog(line)
end end
-- If this traceback is more than 15 elements long, keep the first 9, last 5 -- If this traceback is more than 15 elements long, keep the first 9, last 5
-- and put an ellipsis between the rest -- and put an ellipsis between the rest
local max = 10 local max = 10
if trace_starts and #trace - trace_starts > max then if trace_starts and #trace - trace_starts > max then
local keep_starts = trace_starts + 7 local keep_starts = trace_starts + 7
for i = #trace - trace_starts - max, 0, -1 do for i = #trace - trace_starts - max, 0, -1 do
table.remove(trace, keep_starts + i) table.remove(trace, keep_starts + i)
end end
table.insert(trace, keep_starts, " ...") table.insert(trace, keep_starts, " ...")
end end
for k, line in pairs(trace) do for k, line in pairs(trace) do
trace[k] = line:gsub("in function", " in") trace[k] = line:gsub("in function", " in")
end end
return false, table.remove(trace, 1), table.concat(trace, "\n") return false, table.remove(trace, 1), table.concat(trace, "\n")
end end
return table.unpack(res, 1, res.n) return table.unpack(res, 1, res.n)
end end

View File

@ -329,23 +329,13 @@ function UI.Grid:drawRows()
local rawRow = self.values[key] local rawRow = self.values[key]
local row = self:getDisplayValues(rawRow, 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 selected = index == self.index and not self.inactive
local bg = self:getRowBackgroundColor(rawRow, selected) local bg = self:getRowBackgroundColor(rawRow, selected)
local fg = self:getRowTextColor(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) sb:finish(bg)
end end
@ -354,6 +344,19 @@ function UI.Grid:drawRows()
end end
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) function UI.Grid:getRowTextColor(row, selected)
if selected then if selected then
if self.focused then if self.focused then

View File

@ -560,7 +560,7 @@ function Util.insertString(str, istr, pos)
end end
function Util.split(str, pattern) 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" pattern = pattern or "(.-)\n"
local t = {} local t = {}
local function helper(line) table.insert(t, line) return "" end local function helper(line) table.insert(t, line) return "" end

View File

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

View File

@ -294,7 +294,6 @@ end
function page:rawExecute(s) function page:rawExecute(s)
local fn, m local fn, m
local wrapped local wrapped
local t = os.clock()
fn = load('return (' ..s.. ')', 'lua', nil, sandboxEnv) fn = load('return (' ..s.. ')', 'lua', nil, sandboxEnv)
@ -303,6 +302,7 @@ function page:rawExecute(s)
wrapped = true wrapped = true
end end
local t = os.clock()
if fn then if fn then
fn, m = pcall(fn) fn, m = pcall(fn)
if #m <= 1 and wrapped then if #m <= 1 and wrapped then
@ -311,19 +311,24 @@ function page:rawExecute(s)
else else
fn, m = load(s, 'lua', nil, sandboxEnv) fn, m = load(s, 'lua', nil, sandboxEnv)
if fn then if fn then
t = os.clock()
fn, m = pcall(fn) fn, m = pcall(fn)
end end
end end
if fn then 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 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') Util.print(m or 'nil')
else
print()
end end
else else
_G.printError(m) _G.printError(m)

View File

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

View File

@ -5,23 +5,23 @@ local shell = _ENV.shell
local launcherTab = kernel.getCurrent() local launcherTab = kernel.getCurrent()
kernel.hook('kernel_focus', function(_, eventData) kernel.hook('kernel_focus', function(_, eventData)
local focusTab = eventData and eventData[1] local focusTab = eventData and eventData[1]
if focusTab == launcherTab.uid then if focusTab == launcherTab.uid then
local previousTab = eventData[2] local previousTab = eventData[2]
local nextTab = launcherTab local nextTab = launcherTab
if not previousTab then if not previousTab then
for _, v in pairs(kernel.routines) do for _, v in pairs(kernel.routines) do
if not v.hidden and v.uid > nextTab.uid then if not v.hidden and v.uid > nextTab.uid then
nextTab = v nextTab = v
end end
end end
end end
if nextTab == launcherTab then if nextTab == launcherTab then
shell.switchTab(shell.openTab('sys/apps/shell.lua')) shell.switchTab(shell.openTab('sys/apps/shell.lua'))
else else
shell.switchTab(nextTab.uid) shell.switchTab(nextTab.uid)
end end
end 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 { local page = UI.Page {
wizard = UI.Wizard { wizard = UI.Wizard {
ey = -2, ey = -2,
pages = { pages = {
splash = UI.WizardPage { splash = UI.WizardPage {
index = 1, index = 1,
intro = UI.TextArea { intro = UI.TextArea {
textColor = colors.yellow, textColor = colors.yellow,
inactive = true, inactive = true,
x = 3, ex = -3, y = 2, ey = -2, x = 3, ex = -3, y = 2, ey = -2,
value = string.format(splashIntro, Ansi.white), value = string.format(splashIntro, Ansi.white),
}, },
}, },
label = UI.WizardPage { label = UI.WizardPage {
index = 2, index = 2,
labelText = UI.Text { labelText = UI.Text {
x = 3, y = 2, x = 3, y = 2,
value = 'Label' value = 'Label'
}, },
label = UI.TextEntry { label = UI.TextEntry {
x = 9, y = 2, ex = -3, x = 9, y = 2, ex = -3,
limit = 32, limit = 32,
value = os.getComputerLabel(), value = os.getComputerLabel(),
}, },
intro = UI.TextArea { intro = UI.TextArea {
textColor = colors.yellow, textColor = colors.yellow,
inactive = true, inactive = true,
x = 3, ex = -3, y = 4, ey = -3, x = 3, ex = -3, y = 4, ey = -3,
value = string.format(labelIntro, Ansi.white), value = string.format(labelIntro, Ansi.white),
}, },
}, },
password = UI.WizardPage { password = UI.WizardPage {
index = 3, index = 3,
passwordLabel = UI.Text { passwordLabel = UI.Text {
x = 3, y = 2, x = 3, y = 2,
value = 'Password' value = 'Password'
}, },
newPass = UI.TextEntry { newPass = UI.TextEntry {
x = 12, ex = -3, y = 2, x = 12, ex = -3, y = 2,
limit = 32, limit = 32,
mask = true, mask = true,
shadowText = 'password', shadowText = 'password',
}, },
--[[ --[[
groupLabel = UI.Text { groupLabel = UI.Text {
x = 3, y = 3, x = 3, y = 3,
value = 'Group' value = 'Group'
}, },
group = UI.TextEntry { group = UI.TextEntry {
x = 12, ex = -3, y = 3, x = 12, ex = -3, y = 3,
limit = 32, limit = 32,
shadowText = 'network group', shadowText = 'network group',
}, },
]] ]]
intro = UI.TextArea { intro = UI.TextArea {
textColor = colors.yellow, textColor = colors.yellow,
inactive = true, inactive = true,
x = 3, ex = -3, y = 5, ey = -3, x = 3, ex = -3, y = 5, ey = -3,
value = string.format(passwordIntro, Ansi.white), value = string.format(passwordIntro, Ansi.white),
}, },
}, },
packages = UI.WizardPage { packages = UI.WizardPage {
index = 4, index = 4,
button = UI.Button { button = UI.Button {
x = 3, y = -3, x = 3, y = -3,
text = 'Open Package Manager', text = 'Open Package Manager',
event = 'packages', event = 'packages',
}, },
intro = UI.TextArea { intro = UI.TextArea {
textColor = colors.yellow, textColor = colors.yellow,
inactive = true, inactive = true,
x = 3, ex = -3, y = 2, ey = -4, x = 3, ex = -3, y = 2, ey = -4,
value = string.format(packagesIntro, Ansi.white), value = string.format(packagesIntro, Ansi.white),
}, },
}, },
}, },
}, },
notification = UI.Notification { }, notification = UI.Notification { },
} }
function page.wizard.pages.label:validate() function page.wizard.pages.label:validate()
os.setComputerLabel(self.label.value) os.setComputerLabel(self.label.value)
return true return true
end end
function page.wizard.pages.password:validate() function page.wizard.pages.password:validate()
if #self.newPass.value > 0 then if #self.newPass.value > 0 then
Security.updatePassword(SHA1.sha1(self.newPass.value)) Security.updatePassword(SHA1.sha1(self.newPass.value))
end end
--[[ --[[
if #self.group.value > 0 then if #self.group.value > 0 then
local config = Config.load('os') local config = Config.load('os')
config.group = self.group.value config.group = self.group.value
Config.update('os', config) Config.update('os', config)
end end
]] ]]
return true return true
end end
function page:eventHandler(event) function page:eventHandler(event)
if event.type == 'skip' then if event.type == 'skip' then
self.wizard:emit({ type = 'nextView' }) self.wizard:emit({ type = 'nextView' })
elseif event.type == 'view_enabled' then elseif event.type == 'view_enabled' then
event.view:focusFirst() event.view:focusFirst()
elseif event.type == 'packages' then elseif event.type == 'packages' then
shell.openForegroundTab('PackageManager') shell.openForegroundTab('PackageManager')
elseif event.type == 'wizard_complete' or event.type == 'cancel' then elseif event.type == 'wizard_complete' or event.type == 'cancel' then
UI.exitPullEvents() UI.exitPullEvents()
else else
return UI.Page.eventHandler(self, event) return UI.Page.eventHandler(self, event)

View File

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

View File

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

View File

@ -4,20 +4,20 @@ local read = _G.read
local shell = _ENV.shell local shell = _ENV.shell
if not _G.http.websocket then if not _G.http.websocket then
error('Requires CC: Tweaked') error('Requires CC: Tweaked')
end end
if not _G.cloud_catcher then if not _G.cloud_catcher then
local key = Config.load('cloud').key local key = Config.load('cloud').key
if not key then if not key then
print('Visit https://cloud-catcher.squiddev.cc') print('Visit https://cloud-catcher.squiddev.cc')
print('Paste key: ') print('Paste key: ')
key = read() key = read()
if #key == 0 then if #key == 0 then
return return
end end
end end
print('Connecting...') print('Connecting...')
shell.run('cloud ' .. key) shell.run('cloud ' .. key)
end 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.label = os.getComputerLabel()
info.uptime = math.floor(os.clock()) info.uptime = math.floor(os.clock())
info.group = network.getGroup() info.group = network.getGroup()
if turtle then if turtle and turtle.getStatus then
info.fuel = turtle.getFuelLevel() info.fuel = turtle.getFuelLevel()
info.status = turtle.getStatus() info.status = turtle.getStatus()
info.point = turtle.point 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 -- -t80x30
if _G.http.websocket then if _G.http.websocket then
local config = Config.load('cloud') local config = Config.load('cloud')
local tab = UI.Tab { local tab = UI.Tab {
tabTitle = 'Cloud', tabTitle = 'Cloud',
description = 'Cloud catcher options', description = 'Cloud catcher options',
key = UI.TextEntry { key = UI.TextEntry {
x = 3, ex = -3, y = 2, x = 3, ex = -3, y = 2,
limit = 32, limit = 32,
value = config.key, value = config.key,
shadowText = 'Cloud key', shadowText = 'Cloud key',
accelerators = { accelerators = {
enter = 'update_key', enter = 'update_key',
}, },
}, },
button = UI.Button { button = UI.Button {
x = 3, y = 4, x = 3, y = 4,
text = 'Update', text = 'Update',
event = 'update_key', event = 'update_key',
}, },
labelText = UI.TextArea { labelText = UI.TextArea {
x = 3, ex = -3, y = 6, x = 3, ex = -3, y = 6,
textColor = colors.yellow, textColor = colors.yellow,
marginLeft = 0, marginRight = 0, marginLeft = 0, marginRight = 0,
value = string.format( value = string.format(
[[Use a non-changing cloud key. Note that only a single computer can use this session at one time. [[Use a non-changing cloud key. Note that only a single computer can use this session at one time.
To obtain a key, visit: To obtain a key, visit:
%shttps://cloud-catcher.squiddev.cc%s then bookmark: %shttps://cloud-catcher.squiddev.cc%s then bookmark:
%shttps://cloud-catcher.squiddev.cc/?id=KEY %shttps://cloud-catcher.squiddev.cc/?id=KEY
]], ]],
Ansi.white, Ansi.reset, Ansi.white), Ansi.white, Ansi.reset, Ansi.white),
}, },
} }
function tab:eventHandler(event) function tab:eventHandler(event)
if event.type == 'update_key' then if event.type == 'update_key' then
if #self.key.value > 0 then if #self.key.value > 0 then
config.key = self.key.value config.key = self.key.value
else else
config.key = nil config.key = nil
end end
Config.update('cloud', config) Config.update('cloud', config)
self:emit({ type = 'success_message', message = 'Updated' }) self:emit({ type = 'success_message', message = 'Updated' })
end end
end end
return tab return tab
end end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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