├── data └── computercraft │ └── lua │ ├── rom │ ├── apis │ │ ├── colours.lua │ │ ├── gps.lua │ │ ├── disk.lua │ │ ├── multishell.lua │ │ ├── keys.lua │ │ ├── parallel.lua │ │ ├── vector.lua │ │ ├── colors.lua │ │ ├── help.lua │ │ ├── settings.lua │ │ ├── rednet.lua │ │ ├── paintutils.lua │ │ ├── shell.lua │ │ ├── textutils.lua │ │ └── window.lua │ ├── programs │ │ ├── clear.lua │ │ ├── paint.lua │ │ ├── about.lua │ │ ├── reboot.lua │ │ ├── programs.lua │ │ ├── shutdown.lua │ │ ├── mkdir.lua │ │ ├── edit.lua │ │ ├── bg.lua │ │ ├── fg.lua │ │ ├── peripherals.lua │ │ ├── threads.lua │ │ ├── delete.lua │ │ ├── help.lua │ │ ├── alias.lua │ │ ├── copy.lua │ │ ├── move.lua │ │ ├── wget.lua │ │ ├── lua.lua │ │ ├── list.lua │ │ ├── set.lua │ │ ├── craftos.lua │ │ ├── shell.lua │ │ ├── redstone.lua │ │ └── devbin.lua │ ├── completions │ │ ├── cd.lua │ │ ├── paint.lua │ │ ├── edit.lua │ │ ├── alias.lua │ │ ├── bedit.lua │ │ ├── list.lua │ │ ├── set.lua │ │ ├── mkdir.lua │ │ ├── delete.lua │ │ ├── reboot.lua │ │ ├── shutdown.lua │ │ ├── bg.lua │ │ ├── fg.lua │ │ ├── help.lua │ │ ├── copy.lua │ │ ├── move.lua │ │ ├── devbin.lua │ │ └── redstone.lua │ ├── startup │ │ ├── 20_io.lua │ │ ├── 40_commands.lua │ │ ├── 90_settings.lua │ │ ├── 10_package.lua │ │ ├── 30_peripheral.lua │ │ ├── 35_http.lua │ │ ├── 00_fs.lua │ │ └── 15_term.lua │ ├── help │ │ └── about.hlp │ ├── modules │ │ └── main │ │ │ ├── rc │ │ │ ├── copy.lua │ │ │ ├── io.lua │ │ │ ├── thread.lua │ │ │ └── json.lua │ │ │ ├── cc │ │ │ ├── completion.lua │ │ │ ├── expect.lua │ │ │ ├── shell │ │ │ │ └── completion.lua │ │ │ ├── strings.lua │ │ │ └── audio │ │ │ │ └── dfpwm.lua │ │ │ └── edit │ │ │ ├── syntax │ │ │ └── lua.lua │ │ │ └── syntax.lua │ ├── keymaps │ │ ├── lwjgl2.lua │ │ └── lwjgl3.lua │ ├── update.lua │ └── editors │ │ ├── basic.lua │ │ └── advanced.lua │ └── bios.lua ├── wishlist.txt ├── pack.mcmeta ├── README.md ├── LICENSE ├── NEW_FEATURES.txt ├── installer.lua ├── updater.lua └── unbios.lua /data/computercraft/lua/rom/apis/colours.lua: -------------------------------------------------------------------------------- 1 | return require("colors") 2 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/programs/clear.lua: -------------------------------------------------------------------------------- 1 | require("term").at(1,1).clear() 2 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/programs/paint.lua: -------------------------------------------------------------------------------- 1 | -- BIMG editor 2 | 3 | local term = require("term") 4 | -------------------------------------------------------------------------------- /wishlist.txt: -------------------------------------------------------------------------------- 1 | Feature wishlist: 2 | 3 | - virtual filesystem layer 4 | - change turtle.craft behavior 5 | -------------------------------------------------------------------------------- /pack.mcmeta: -------------------------------------------------------------------------------- 1 | { 2 | "pack": { 3 | "description": "A saner CraftOS implementation", 4 | "pack_format": 9 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/completions/cd.lua: -------------------------------------------------------------------------------- 1 | local shell = require("shell") 2 | local completion = require("cc.shell.completion") 3 | 4 | shell.setCompletionFunction("cd", completion.build(completion.dir)) 5 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/completions/paint.lua: -------------------------------------------------------------------------------- 1 | local shell = require("shell") 2 | local completion = require("cc.shell.completion") 3 | 4 | shell.setCompletionFunction("paint", completion.build( 5 | 6 | )) 7 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/completions/edit.lua: -------------------------------------------------------------------------------- 1 | local shell = require("shell") 2 | local completion = require("cc.shell.completion") 3 | 4 | shell.setCompletionFunction("edit", completion.build( 5 | completion.dirOrFile 6 | )) 7 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/completions/alias.lua: -------------------------------------------------------------------------------- 1 | local shell = require("shell") 2 | local completion = require("cc.shell.completion") 3 | 4 | shell.setCompletionFunction("alias", completion.build( 5 | nil, completion.program 6 | )) 7 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/completions/bedit.lua: -------------------------------------------------------------------------------- 1 | local shell = require("shell") 2 | local completion = require("cc.shell.completion") 3 | 4 | shell.setCompletionFunction("bedit", completion.build( 5 | completion.dirOrFile 6 | )) 7 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/completions/list.lua: -------------------------------------------------------------------------------- 1 | local shell = require("shell") 2 | local completion = require("cc.shell.completion") 3 | 4 | shell.setCompletionFunction("list", completion.build( 5 | {completion.dir, many = true} 6 | )) 7 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/completions/set.lua: -------------------------------------------------------------------------------- 1 | local shell = require("shell") 2 | local completion = require("cc.shell.completion") 3 | 4 | shell.setCompletionFunction("set", completion.build( 5 | { completion.setting, true } 6 | )) 7 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/completions/mkdir.lua: -------------------------------------------------------------------------------- 1 | local shell = require("shell") 2 | local completion = require("cc.shell.completion") 3 | 4 | shell.setCompletionFunction("mkdir", completion.build( 5 | {completion.dir, many = true} 6 | )) 7 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/completions/delete.lua: -------------------------------------------------------------------------------- 1 | local shell = require("shell") 2 | local completion = require("cc.shell.completion") 3 | 4 | shell.setCompletionFunction("delete", completion.build( 5 | {completion.dirOrFile, many = true} 6 | )) 7 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/completions/reboot.lua: -------------------------------------------------------------------------------- 1 | local shell = require("shell") 2 | local completion = require("cc.shell.completion") 3 | 4 | shell.setCompletionFunction("reboot", completion.build( 5 | { completion.choice, { "now" } } 6 | )) 7 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/completions/shutdown.lua: -------------------------------------------------------------------------------- 1 | local shell = require("shell") 2 | local completion = require("cc.shell.completion") 3 | 4 | shell.setCompletionFunction("shutdown", completion.build( 5 | { completion.choice, { "now" } } 6 | )) 7 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/programs/about.lua: -------------------------------------------------------------------------------- 1 | -- about 2 | 3 | local rc = require("rc") 4 | local colors = require("colors") 5 | local textutils = require("textutils") 6 | 7 | textutils.coloredPrint(colors.yellow, rc.version() .. " on " .. _HOST) 8 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/completions/bg.lua: -------------------------------------------------------------------------------- 1 | local shell = require("shell") 2 | local completion = require("cc.shell.completion") 3 | 4 | shell.setCompletionFunction("bg", completion.build( 5 | { completion.programWithArgs, 1, many = true } 6 | )) 7 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/completions/fg.lua: -------------------------------------------------------------------------------- 1 | local shell = require("shell") 2 | local completion = require("cc.shell.completion") 3 | 4 | shell.setCompletionFunction("fg", completion.build( 5 | { completion.programWithArgs, 1, many = true } 6 | )) 7 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/completions/help.lua: -------------------------------------------------------------------------------- 1 | local help = require("help") 2 | local shell = require("shell") 3 | local completion = require("cc.shell.completion") 4 | 5 | shell.setCompletionFunction("help", completion.build( 6 | {help.completeTopic, many = true} 7 | )) 8 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/completions/copy.lua: -------------------------------------------------------------------------------- 1 | local shell = require("shell") 2 | local completion = require("cc.shell.completion") 3 | 4 | shell.setCompletionFunction("copy", completion.build( 5 | {completion.dirOrFile, true}, 6 | {completion.dirOrFile, many = true} 7 | )) 8 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/completions/move.lua: -------------------------------------------------------------------------------- 1 | local shell = require("shell") 2 | local completion = require("cc.shell.completion") 3 | 4 | shell.setCompletionFunction("move", completion.build( 5 | {completion.dirOrFile, true}, 6 | {completion.dirOrFile, many = true} 7 | )) 8 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/programs/reboot.lua: -------------------------------------------------------------------------------- 1 | local rc = require("rc") 2 | local term = require("term") 3 | local colors = require("colors") 4 | 5 | term.setTextColor(colors.yellow) 6 | print("Restarting") 7 | 8 | if (...) ~= "now" then rc.sleep(1) end 9 | 10 | rc.reboot() 11 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/programs/programs.lua: -------------------------------------------------------------------------------- 1 | local shell = require("shell") 2 | local colors = require("colors") 3 | local textutils = require("textutils") 4 | 5 | textutils.coloredPrint(colors.yellow, "available programs\n", colors.white) 6 | textutils.pagedTabulate(shell.programs()) 7 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/programs/shutdown.lua: -------------------------------------------------------------------------------- 1 | local rc = require("rc") 2 | local term = require("term") 3 | local colors = require("colors") 4 | 5 | term.setTextColor(colors.yellow) 6 | print("Shutting down") 7 | 8 | if (...) ~= "now" then rc.sleep(1) end 9 | 10 | rc.shutdown() 11 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/programs/mkdir.lua: -------------------------------------------------------------------------------- 1 | local fs = require("fs") 2 | local shell = require("shell") 3 | local args = {...} 4 | 5 | if #args == 0 then 6 | io.stderr:write("usage: mkdir \n") 7 | return 8 | end 9 | 10 | for i=1, #args, 1 do 11 | fs.makeDir(shell.resolve(args[i])) 12 | end 13 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/startup/20_io.lua: -------------------------------------------------------------------------------- 1 | _G.io = require("rc.io") 2 | 3 | function _G.print(...) 4 | local args = table.pack(...) 5 | 6 | for i=1, args.n, 1 do 7 | args[i] = tostring(args[i]) 8 | end 9 | 10 | io.stdout:write(table.concat(args, "\t"), "\n") 11 | 12 | return true 13 | end 14 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/completions/devbin.lua: -------------------------------------------------------------------------------- 1 | local shell = require("shell") 2 | local completion = require("cc.shell.completion") 3 | 4 | shell.setCompletionFunction("devbin", completion.build( 5 | { completion.choice, { "put", "get", "run" }, true }, 6 | function(cur, prev) 7 | if prev[1] == "put" then 8 | return completion.dirOrFile(cur, prev) 9 | end 10 | end 11 | )) 12 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/programs/edit.lua: -------------------------------------------------------------------------------- 1 | -- launch different editors based on computer capabilities 2 | 3 | local term = require("term") 4 | local settings = require("settings") 5 | 6 | local df = function(f, ...) return assert(loadfile(f))(...) end 7 | 8 | if term.isColor() or settings.get("edit.force_highlight") then 9 | df("/rc/editors/advanced.lua", ...) 10 | else 11 | df("/rc/editors/basic.lua", ...) 12 | end 13 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/programs/bg.lua: -------------------------------------------------------------------------------- 1 | -- fg 2 | 3 | local args = {...} 4 | 5 | if #args == 0 then 6 | error("command not provided", 0) 7 | end 8 | 9 | local shell = require("shell") 10 | local thread = require("rc.thread") 11 | 12 | local path, err = shell.resolveProgram(args[1]) 13 | if not path then 14 | error(err, 0) 15 | end 16 | 17 | thread.launchTab(function() 18 | shell.exec(path, table.unpack(args, 2)) 19 | end, args[1]) 20 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/apis/gps.lua: -------------------------------------------------------------------------------- 1 | -- rc.gps 2 | 3 | error("gps is not fully implemented", 0) 4 | 5 | local expect = require("cc.expect").expect 6 | local rednet = require("rednet") 7 | 8 | local gps = {} 9 | gps.CHANNEL_GPS = 65534 10 | 11 | function gps.locate(timeout, debug) 12 | timeout = expect(1, timeout, "number", "nil") or 2 13 | expect(2, debug, "boolean", "nil") 14 | 15 | rednet.broadcast() 16 | end 17 | 18 | return gps 19 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/programs/fg.lua: -------------------------------------------------------------------------------- 1 | -- fg 2 | 3 | local args = {...} 4 | 5 | if #args == 0 then 6 | error("command not provided", 0) 7 | end 8 | 9 | local shell = require("shell") 10 | local thread = require("rc.thread") 11 | 12 | local path, err = shell.resolveProgram(args[1]) 13 | if not path then 14 | error(err, 0) 15 | end 16 | 17 | thread.setFocus((thread.launchTab(function() 18 | shell.exec(path, table.unpack(args, 2)) 19 | end, args[1]))) 20 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/completions/redstone.lua: -------------------------------------------------------------------------------- 1 | local shell = require("shell") 2 | local complete = require("cc.completion") 3 | local completion = require("cc.shell.completion") 4 | 5 | shell.setCompletionFunction("redstone", completion.build( 6 | { completion.choice, {"probe", "set", "pulse"}, {false, true, true} }, 7 | completion.side, 8 | function(cur, prev) 9 | if prev[1] == "set" then 10 | return complete.color(cur, true) 11 | end 12 | end 13 | )) 14 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/programs/peripherals.lua: -------------------------------------------------------------------------------- 1 | local term = require("term") 2 | local colors = require("colors") 3 | local peripheral = require("peripheral") 4 | 5 | term.setTextColor(colors.yellow) 6 | print("attached peripherals") 7 | term.setTextColor(colors.white) 8 | local names = peripheral.getNames() 9 | 10 | if #names == 0 then 11 | io.stderr:write("none\n") 12 | else 13 | for i=1, #names, 1 do 14 | print(string.format("%s (%s)", names[i], peripheral.getType(names[i]))) 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/programs/threads.lua: -------------------------------------------------------------------------------- 1 | -- threads 2 | 3 | local colors = require("colors") 4 | local thread = require("rc.thread") 5 | local strings = require("cc.strings") 6 | local textutils = require("textutils") 7 | 8 | textutils.coloredPrint(colors.yellow, "id tab name", colors.white) 9 | 10 | local info = thread.info() 11 | for i=1, #info, 1 do 12 | local inf = info[i] 13 | textutils.pagedPrint(string.format("%s %s %s", 14 | strings.ensure_width(tostring(inf.id), 4), 15 | strings.ensure_width(tostring(inf.tab), 4), inf.name)) 16 | end 17 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/programs/delete.lua: -------------------------------------------------------------------------------- 1 | local fs = require("fs") 2 | local shell = require("shell") 3 | 4 | local args = {...} 5 | if #args == 0 then 6 | io.stderr:write("usage: delete \n") 7 | return 8 | end 9 | 10 | for i=1, #args, 1 do 11 | local files = fs.find(shell.resolve(args[i])) 12 | if not files then 13 | io.stderr:write("file(s) not found\n") 14 | return 15 | end 16 | 17 | for n=1, #files, 1 do 18 | if fs.isReadOnly(files[n]) then 19 | io.stderr:write(files[n] .. ": cannot remove read-only file\n") 20 | return 21 | end 22 | fs.delete(files[n]) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/help/about.hlp: -------------------------------------------------------------------------------- 1 | Recrafted is ComputerCraft's CraftOS but with saner API design. It's also not licensed under the CCPL, but rather the MIT license -- so you can freely use Recrafted's code in other projects without being legally bound to license them under the CCPL. 2 | 3 | 4 | All APIs are implemented as described on the CC: Tweaked wiki at 5 | >>color blue 6 | https://tweaked.cc 7 | >>color white 8 | , with slight modifications to fit Recrafted's API design. Certain modules not written by Dan200 have been adapted from CraftOS, relicensed under the MIT license with permission from their authors. 9 | 10 | See 11 | >>color blue 12 | https://ocaweso.me/recrafted 13 | >>color white 14 | for more information on Recrafted. 15 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/programs/help.lua: -------------------------------------------------------------------------------- 1 | -- help 2 | 3 | local help = require("help") 4 | local textutils = require("textutils") 5 | 6 | local args = {...} 7 | 8 | if #args == 0 then 9 | args[1] = "help" 10 | end 11 | 12 | local function view(name)--path) 13 | textutils.coloredPagedPrint(table.unpack(help.loadTopic(name))) 14 | --local lines = {} 15 | --for l in io.lines(path) do lines[#lines+1] = l end 16 | --textutils.pagedPrint(table.concat(require("cc.strings").wrap(table.concat(lines,"\n"), require("term").getSize()), "\n")) 17 | end 18 | 19 | for i=1, #args, 1 do 20 | local path = help.lookup(args[i]) 21 | if not path then 22 | error("No help topic for " .. args[i], 0) 23 | end 24 | view(args[i])--path) 25 | end 26 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/programs/alias.lua: -------------------------------------------------------------------------------- 1 | -- alias 2 | 3 | local args = {...} 4 | 5 | local shell = require("shell") 6 | local colors = require("colors") 7 | local textutils = require("textutils") 8 | 9 | if #args == 0 then 10 | textutils.coloredPrint(colors.yellow, "shell aliases", colors.white) 11 | 12 | local aliases = shell.aliases() 13 | 14 | local _aliases = {} 15 | for k, v in pairs(aliases) do 16 | table.insert(_aliases, {colors.cyan, k, colors.white, ":", v}) 17 | end 18 | 19 | textutils.pagedTabulate(_aliases) 20 | 21 | elseif #args == 1 then 22 | shell.clearAlias(args[1]) 23 | 24 | elseif #args == 2 then 25 | shell.setAlias(args[1], args[2]) 26 | 27 | else 28 | error("this program takes a maximum of two arguments", 0) 29 | end 30 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/apis/disk.lua: -------------------------------------------------------------------------------- 1 | -- rc.disk 2 | 3 | local p = require("peripheral") 4 | 5 | local disk = {} 6 | 7 | local function wrap(method) 8 | return function(name, ...) 9 | if not p.isPresent(name) then 10 | return nil 11 | end 12 | 13 | return p.call(name, method, ...) 14 | end 15 | end 16 | 17 | local methods = { 18 | isPresent = "isDiskPresent", 19 | getLabel = "getDiskLabel", 20 | setLabel = "setDiskLabel", 21 | hasData = false, 22 | getMountPath = false, 23 | hasAudio = false, 24 | getAudioTitle = false, 25 | playAudio = false, 26 | stopAudio = false, 27 | eject = "ejectDisk", 28 | getID = "getDiskID" 29 | } 30 | 31 | for k, v in pairs(methods) do 32 | disk[k] = wrap(v or k) 33 | end 34 | 35 | return disk 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Recrafted 2 | 3 | ###### look, mom, a proper Markdown readme! 4 | 5 | Recrafted is a reimplementation of [CC: Tweaked](https://github.com/CC-Tweaked/CC-Tweaked)'s CraftOS, intended to be cleaner and easier than CraftOS. 6 | 7 | Key changes: 8 | 9 | - No. More. CCPL!! 10 | - All previously global APIs (with the exception of the standard Lua ones) have been removed. 11 | - Non-standard `os` API functions are now in the `rc` table, e.g. `os.sleep` becomes `rc.sleep` or `os.pullEvent` becomes `rc.pullEvent`. 12 | - Native support for proper thread management (`parallel` implementation builds on this) 13 | - Multishell works even on standard computers, and is navigable with keyboard shortcuts! 14 | 15 | See [the Recrafted website](https://ocaweso.me/recrafted) for more details. 16 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/apis/multishell.lua: -------------------------------------------------------------------------------- 1 | -- multishell API 2 | 3 | local thread = require("rc.thread") 4 | 5 | local ms = {} 6 | 7 | ms.getFocus = thread.getFocusedTab 8 | ms.setFocus = thread.setFocusedTab 9 | ms.getCurrent = thread.getCurrentTab 10 | 11 | ms.setTitle = function() end 12 | ms.getTitle = function() return "???" end 13 | 14 | ms.getCount = function() return #thread.info() end 15 | 16 | function ms.launch(...) 17 | local env = _G 18 | local args = table.pack(...) 19 | 20 | if type(args[1]) == "table" then 21 | env = table.remove(args, 1) 22 | end 23 | 24 | local function func() 25 | return assert(loadfile(args[1], "bt", env))(table.unpack(args, 2, args.n)) 26 | end 27 | 28 | return (thread.launchTab(func, args[1]:sub(-8))) 29 | end 30 | 31 | return ms 32 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/apis/keys.lua: -------------------------------------------------------------------------------- 1 | -- rc.keys 2 | 3 | local expect = require("cc.expect").expect 4 | 5 | -- automatic keymap detection :) 6 | -- this uses a fair bit of magic 7 | local kmap = "lwjgl3" 8 | local mcver = tonumber(_HOST:match("%b()"):sub(2,-2):match("1%.(%d+)")) or 0 9 | if _HOST:match("CCEmuX") then 10 | -- use the 1.16.5 keymap 11 | kmap = "lwjgl3" 12 | elseif mcver <= 12 or _HOST:match("CraftOS%-PC") then 13 | -- use the 1.12.2 keymap 14 | kmap = "lwjgl2" 15 | end 16 | 17 | local base = dofile("/rc/keymaps/"..kmap..".lua") 18 | local lib = {} 19 | 20 | -- reverse-index it! 21 | for k, v in pairs(base) do lib[k] = v; lib[v] = k end 22 | lib["return"] = lib.enter 23 | 24 | function lib.getName(code) 25 | expect(1, code, "number") 26 | return lib[code] 27 | end 28 | 29 | return lib 30 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/programs/copy.lua: -------------------------------------------------------------------------------- 1 | local fs = require("fs") 2 | local shell = require("shell") 3 | 4 | local args = {...} 5 | 6 | if #args < 2 then 7 | io.stderr:write("usage: copy \n") 8 | return 9 | end 10 | 11 | local source, destination = shell.resolve(args[1]), shell.resolve(args[2]) 12 | local files = fs.find(source) 13 | 14 | if #files > 0 then 15 | local dir = fs.isDir(destination) 16 | 17 | if #files > 1 and not dir then 18 | io.stderr:write("destination must be a directory\n") 19 | return 20 | end 21 | 22 | for i=1, #files, 1 do 23 | if dir then 24 | fs.copy(files[i], fs.combine(destination, fs.getName(files[i]))) 25 | elseif #files == 1 then 26 | if fs.exists(destination) then 27 | io.stderr:write("file already exists\n") 28 | return 29 | else 30 | fs.copy(files[i], destination) 31 | end 32 | end 33 | end 34 | else 35 | io.stderr:write("no such file(s)\n") 36 | end 37 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/programs/move.lua: -------------------------------------------------------------------------------- 1 | local fs = require("fs") 2 | local shell = require("shell") 3 | 4 | local args = {...} 5 | 6 | if #args < 2 then 7 | io.stderr:write("usage: move \n") 8 | return 9 | end 10 | 11 | local source, destination = shell.resolve(args[1]), shell.resolve(args[2]) 12 | local files = fs.find(source) 13 | 14 | if #files > 0 then 15 | local dir = fs.isDir(destination) 16 | 17 | if #files > 1 and not dir then 18 | io.stderr:write("destination must be a directory\n") 19 | return 20 | end 21 | 22 | for i=1, #files, 1 do 23 | if dir then 24 | fs.move(files[i], fs.combine(destination, fs.getName(files[i]))) 25 | elseif #files == 1 then 26 | if fs.exists(destination) then 27 | io.stderr:write("file already exists\n") 28 | return 29 | else 30 | fs.move(files[i], destination) 31 | end 32 | end 33 | end 34 | else 35 | io.stderr:write("no such file(s)\n") 36 | end 37 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/startup/40_commands.lua: -------------------------------------------------------------------------------- 1 | -- rc.commands 2 | 3 | local expect = require("cc.expect").expect 4 | 5 | if not package.loaded.commands then return end 6 | 7 | local native = package.loaded.commands 8 | local c = {} 9 | package.loaded.commands = c 10 | 11 | c.native = native 12 | c.async = {} 13 | 14 | for k, v in pairs(native) do c[k] = v end 15 | 16 | local command_list = native.list() 17 | 18 | for i=1, #command_list, 1 do 19 | local command = command_list[i] 20 | c.async[command] = function(...) 21 | return c.execAsync(command, ...) 22 | end 23 | c[command] = c[command] or function(...) 24 | return c.exec(command, ...) 25 | end 26 | end 27 | 28 | function c.exec(command, ...) 29 | expect(1, command, "string") 30 | return c.native.exec(table.concat(table.pack(command, ...), " ")) 31 | end 32 | 33 | function c.execAsync(command, ...) 34 | expect(1, command, "string") 35 | return c.native.execAsync(table.concat(table.pack(command, ...), " ")) 36 | end 37 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/programs/wget.lua: -------------------------------------------------------------------------------- 1 | -- wget 2 | 3 | if not package.loaded.http then 4 | error("HTTP is not enabled in the ComputerCraft configuration", 0) 5 | end 6 | 7 | local http = require("http") 8 | 9 | local args = {...} 10 | 11 | if #args == 0 then 12 | io.stderr:write([[Usage: 13 | wget [filename] 14 | wget run 15 | ]]) 16 | return 17 | end 18 | 19 | local function get(url) 20 | local handle, err = http.get(url, nil, true) 21 | if not handle then 22 | error(err, 0) 23 | end 24 | 25 | local data = handle.readAll() 26 | handle.close() 27 | 28 | return data 29 | end 30 | 31 | if args[1] == "run" then 32 | local data = get(args[2]) 33 | assert(load(data, "=", "t", _G))() 34 | else 35 | local filename = args[2] or (args[1]:match("[^/]+$")) or 36 | error("could not determine file name", 0) 37 | local data = get(args[1]) 38 | local handle, err = io.open(filename, "w") 39 | if not handle then 40 | error(err, 0) 41 | end 42 | handle:write(data) 43 | handle:close() 44 | end 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2022 Ocawesome101 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/modules/main/rc/copy.lua: -------------------------------------------------------------------------------- 1 | -- rc.copy - table copier 2 | 3 | -- from https://lua-users.org/wiki/CopyTable 4 | local function deepcopy(orig, copies, dont_copy) 5 | copies = copies or {} 6 | local orig_type = type(orig) 7 | local copy 8 | 9 | if orig_type == 'table' and not dont_copy[orig] then 10 | if copies[orig] then 11 | copy = copies[orig] 12 | else 13 | copy = {} 14 | copies[orig] = copy 15 | 16 | for orig_key, orig_value in next, orig, nil do 17 | copy[deepcopy(orig_key, copies, dont_copy)] = deepcopy(orig_value, 18 | copies, dont_copy) 19 | end 20 | 21 | setmetatable(copy, deepcopy(getmetatable(orig), copies, dont_copy)) 22 | end 23 | else -- number, string, boolean, etc 24 | copy = orig 25 | end 26 | 27 | return copy 28 | end 29 | 30 | return { copy = function(original, ...) 31 | local dont_copy = {} 32 | for _, thing in pairs({...}) do 33 | dont_copy[thing] = true 34 | end 35 | return deepcopy(original, nil, dont_copy) 36 | end } 37 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/programs/lua.lua: -------------------------------------------------------------------------------- 1 | -- lua REPL 2 | 3 | local term = require("term") 4 | local copy = require("rc.copy").copy 5 | local colors = require("colors") 6 | local pretty = require("cc.pretty") 7 | local textutils = require("textutils") 8 | 9 | local env = copy(_ENV, package.loaded) 10 | 11 | local run = true 12 | function env.exit() run = false end 13 | 14 | term.setTextColor(colors.yellow) 15 | 16 | print("Recrafted Lua REPL.\nCall exit() to exit.") 17 | 18 | local history = {} 19 | while run do 20 | term.setTextColor(colors.white) 21 | io.write("lua> ") 22 | local data = term.read(nil, history, function(text) 23 | return textutils.complete(text, env) 24 | end) 25 | if #data > 0 then 26 | history[#history+1] = data 27 | end 28 | 29 | local ok, err = load("return " .. data, "=stdin", "t", env) 30 | if not ok then 31 | ok, err = load(data, "=stdin", "t", env) 32 | end 33 | 34 | if ok then 35 | local result = table.pack(pcall(ok)) 36 | if not result[1] then 37 | io.stderr:write(result[2], "\n") 38 | elseif result.n > 1 then 39 | for i=2, result.n, 1 do 40 | pretty.pretty_print(result[i]) 41 | end 42 | end 43 | else 44 | io.stderr:write(err, "\n") 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/programs/list.lua: -------------------------------------------------------------------------------- 1 | -- list 2 | 3 | local args = {...} 4 | 5 | local fs = require("fs") 6 | local shell = require("shell") 7 | local colors = require("colors") 8 | local settings = require("settings") 9 | local textutils = require("textutils") 10 | 11 | if #args == 0 then args[1] = shell.dir() end 12 | 13 | local show_hidden = settings.get("list.show_hidden") 14 | 15 | local function list_dir(dir) 16 | if not fs.exists(dir) then 17 | error(dir .. ": that directory does not exist", 0) 18 | elseif not fs.isDir(dir) then 19 | error(dir .. ": not a directory", 0) 20 | end 21 | 22 | local raw_files = fs.list(dir) 23 | local files, dirs = {}, {} 24 | 25 | for i=1, #raw_files, 1 do 26 | local full = fs.combine(dir, raw_files[i]) 27 | 28 | if raw_files[i]:sub(1,1) ~= "." or show_hidden then 29 | if fs.isDir(full) then 30 | dirs[#dirs+1] = raw_files[i] 31 | 32 | else 33 | files[#files+1] = raw_files[i] 34 | end 35 | end 36 | end 37 | 38 | textutils.pagedTabulate(colors.green, dirs, colors.white, files) 39 | end 40 | 41 | for i=1, #args, 1 do 42 | if #args > 1 then 43 | textutils.coloredPrint(colors.yellow, args[i]..":\n", colors.white) 44 | end 45 | list_dir(args[i]) 46 | end 47 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/apis/parallel.lua: -------------------------------------------------------------------------------- 1 | -- 'parallel' implementation 2 | -- uses Recrafted's native threading 3 | 4 | local parallel = {} 5 | 6 | local thread = require("rc.thread") 7 | local expect = require("cc.expect").expect 8 | 9 | local function rand_id() 10 | local id = "parallel-" 11 | for _=1, 8, 1 do 12 | id = id .. string.char(math.random(33, 126)) 13 | end 14 | return id 15 | end 16 | 17 | local function waitForN(num, ...) 18 | local funcs = table.pack(...) 19 | 20 | local threads = {} 21 | for i=1, #funcs, 1 do 22 | expect(i, funcs[i], "function") 23 | end 24 | 25 | for i=1, #funcs, 1 do 26 | threads[i] = thread.spawn(funcs[i], rand_id()) 27 | end 28 | 29 | local dead = 0 30 | repeat 31 | coroutine.yield() 32 | for i=#threads, 1, -1 do 33 | if not thread.exists(threads[i]) then 34 | table.remove(threads, i) 35 | dead = dead + 1 36 | end 37 | end 38 | until dead >= num 39 | 40 | -- clean up excess 41 | for i=1, #threads, 1 do 42 | thread.remove(threads[i]) 43 | end 44 | end 45 | 46 | function parallel.waitForAny(...) 47 | return waitForN(1, ...) 48 | end 49 | 50 | function parallel.waitForAll(...) 51 | return waitForN(select("#", ...), ...) 52 | end 53 | 54 | return parallel 55 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/modules/main/cc/completion.lua: -------------------------------------------------------------------------------- 1 | -- cc.completion 2 | 3 | local expect = require("cc.expect").expect 4 | local settings = require("settings") 5 | local peripheral = require("peripheral") 6 | 7 | local c = {} 8 | 9 | -- choices and options! 10 | -- extension: add_space can be a table of booleans 11 | function c.choice(text, choices, add_space) 12 | expect(1, text, "string") 13 | expect(2, choices, "table") 14 | expect(3, add_space, "boolean", "table", "nil") 15 | 16 | local options = {} 17 | 18 | for i=1, #choices, 1 do 19 | local add = add_space 20 | if type(add_space) == "table" then 21 | add = add_space[i] or add_space[1] 22 | end 23 | 24 | if choices[i]:sub(0, #text) == text then 25 | options[#options+1] = choices[i]:sub(#text+1) .. (add and " " or "") 26 | end 27 | end 28 | 29 | return options 30 | end 31 | 32 | function c.peripheral(text, add_space) 33 | return c.choice(text, peripheral.getNames(), add_space) 34 | end 35 | 36 | local sides = {"front", "back", "top", "bottom", "left", "right"} 37 | function c.side(text, add_space) 38 | return c.choice(text, sides, add_space) 39 | end 40 | 41 | function c.setting(text, add_space) 42 | return c.choice(text, settings.getNames(), add_space) 43 | end 44 | 45 | return c 46 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/programs/set.lua: -------------------------------------------------------------------------------- 1 | -- 'set' program 2 | 3 | local colors = require("colors") 4 | local settings = require("settings") 5 | local textutils = require("textutils") 6 | 7 | local args = {...} 8 | 9 | local function coerce(val) 10 | if val == "nil" then 11 | return nil 12 | elseif val == "false" then 13 | return false 14 | elseif val == "true" then 15 | return true 16 | else 17 | return tonumber(val) or val 18 | end 19 | end 20 | 21 | local col = { 22 | string = colors.red, 23 | table = colors.green, 24 | number = colors.magenta, 25 | boolean = colors.lightGray 26 | } 27 | 28 | if #args == 0 then 29 | for _, setting in ipairs(settings.getNames()) do 30 | local value = settings.get(setting) 31 | textutils.coloredPrint(colors.cyan, setting, colors.white, " is ", 32 | col[type(value)], string.format("%q", value)) 33 | end 34 | elseif #args == 1 then 35 | local setting = args[1] 36 | local value = settings.get(setting) 37 | textutils.coloredPrint(colors.cyan, setting, colors.white, " is ", 38 | col[type(value)], string.format("%q", value)) 39 | local def = settings.getDetails(setting) 40 | if def then 41 | print(def.description) 42 | end 43 | else 44 | local setting, value = args[1], args[2] 45 | settings.set(setting, coerce(value)) 46 | settings.save() 47 | end 48 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/modules/main/cc/expect.lua: -------------------------------------------------------------------------------- 1 | -- cc.expect 2 | 3 | local _expect = {} 4 | 5 | local function checkType(index, valueType, value, ...) 6 | local expected = table.pack(...) 7 | local isType = false 8 | 9 | for i=1, expected.n, 1 do 10 | if type(value) == expected[i] then 11 | isType = true 12 | break 13 | end 14 | end 15 | 16 | if not isType then 17 | error(string.format("bad %s %s (%s expected, got %s)", valueType, 18 | index, table.concat(expected, " or "), type(value)), 3) 19 | end 20 | 21 | return value 22 | end 23 | 24 | function _expect.expect(index, value, ...) 25 | return checkType(("#%d"):format(index), "argument", value, ...) 26 | end 27 | 28 | function _expect.field(tbl, index, ...) 29 | _expect.expect(1, tbl, "table") 30 | _expect.expect(2, index, "string") 31 | return checkType(("%q"):format(index), "field", tbl[index], ...) 32 | end 33 | 34 | function _expect.range(num, min, max) 35 | _expect.expect(1, num, "number") 36 | _expect.expect(2, min, "number", "nil") 37 | _expect.expect(3, max, "number", "nil") 38 | min = min or -math.huge 39 | max = max or math.huge 40 | if num < min or num > max then 41 | error(("number outside of range (expected %d to be within %d and %d") 42 | :format(num, min, max), 2) 43 | end 44 | end 45 | 46 | setmetatable(_expect, {__call = function(_, ...) 47 | return _expect.expect(...) 48 | end}) 49 | 50 | return _expect 51 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/programs/craftos.lua: -------------------------------------------------------------------------------- 1 | -- CraftOS compatibility, in theory 2 | 3 | local rc = require("rc") 4 | local settings = require("settings") 5 | 6 | if not settings.get("bios.compat_mode") then 7 | error("compatibility mode is disabled", 0) 8 | end 9 | 10 | if os.version then 11 | error("you are already in compatibility mode", 0) 12 | end 13 | 14 | local libs = { 15 | "peripheral", "fs", "settings", "http", "term", "colors", "multishell", 16 | "keys", "parallel", "shell", "textutils", "window", "paintutils" 17 | } 18 | 19 | local move = { 20 | "queueEvent", "startTimer", "cancelTimer", "setAlarm", "cancelAlarm", "getComputerID", 21 | "computerID", "getComputerLabel", "setComputerLabel", "computerLabel", "day", "epoch", 22 | "pullEvent", "sleep" 23 | } 24 | 25 | local nEnv = setmetatable({}, {__index=_G}) 26 | nEnv.os = setmetatable({}, {__index=os}) 27 | 28 | for i=1, #libs, 1 do 29 | nEnv[libs[i]] = select(2, pcall(require, libs[i])) 30 | end 31 | 32 | for i=1, #move do 33 | nEnv.os[move[i]] = rc[move[i]] 34 | end 35 | 36 | function nEnv.printError(text) 37 | io.stderr:write(text, "\n") 38 | end 39 | 40 | nEnv.write = rc.write 41 | 42 | nEnv.unpack = table.unpack 43 | if rc.lua51 then 44 | for k, v in pairs(rc.lua51) do 45 | nEnv[k] = v 46 | end 47 | end 48 | 49 | nEnv.read = nEnv.term.read 50 | nEnv.sleep = nEnv.os.sleep 51 | 52 | function nEnv.os.version() 53 | return "CraftOS 1.8" 54 | end 55 | 56 | local func, err = loadfile("/rc/programs/shell.lua", "t", nEnv) 57 | if not func then error(err, 0) end 58 | func() 59 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/keymaps/lwjgl2.lua: -------------------------------------------------------------------------------- 1 | -- keymap for minecraft 1.12.2 and older 2 | 3 | return { 4 | nil, 5 | "one", "two", "three", "four", "five", "six", "seven", "eight", "nine", 6 | "zero", "minus", "equals", "backspace", "tab", "q", "w", "e", "r", "t", "y", 7 | "u", "i", "o", "p", "leftBracket", "rightBracket", "enter", "leftCtrl", 8 | "a", "s", "d", "f", "g", "h", "j", "k", "l", "semicolon", "apostrophe", 9 | "grave", "leftShift", "backslash", "z", "x", "c", "v", "b", "n", "m", 10 | "comma", "period", "slash", "rightShift", "multiply", "leftAlt", "space", 11 | "capsLock", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10", 12 | "numLock", "scrollLock", "numpad7", "numpad8", "numpad9", "numpadSubtract", 13 | "numpad4", "numpad5", "numpad6", "numpadAdd", "numpad1", "numpad2", "numpad3", 14 | "numpad0", "numpadDot", nil, nil, nil, "f11", "f12", nil, nil, nil, nil, nil, 15 | nil, nil, nil, nil, nil, nil, "f13", "f14", "f15", nil, nil, nil, nil, nil, 16 | nil, nil, nil, nil, "kana", nil, nil, nil, nil, nil, nil, nil, nil, "convert", 17 | nil, "noconvert", nil, "yen", nil, nil, nil, nil, nil, nil, nil, nil, nil, 18 | nil, nil, nil, nil, nil, nil, "numpadEquals", nil, nil, "circumflex", "at", 19 | "colon", "underscore", "kanji", "stop", "ax", nil, nil, nil, nil, nil, 20 | "numpadEnter", "rightCtrl", nil, nil, nil, nil, nil, nil, nil, nil, nil, 21 | nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, "numpadComma", 22 | nil, "numpadDivide", nil, nil, "rightAlt", nil, nil, nil, nil, nil, nil, 23 | nil, nil, nil, nil, nil, nil, "pause", nil, "home", "up", "pageUp", nil, 24 | "left", nil, "right", nil, "end", "down", "pageDown", "insert", "delete" 25 | } 26 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/apis/vector.lua: -------------------------------------------------------------------------------- 1 | -- rc.vector 2 | 3 | local vector = {} 4 | local Vector = {} 5 | 6 | function Vector:add(o) 7 | return vector.new(self.x + o.x, self.y + o.y, self.z + o.z) 8 | end 9 | 10 | function Vector:sub(o) 11 | return vector.new(self.x - o.x, self.y - o.y, self.z - o.z) 12 | end 13 | 14 | function Vector:mul(m) 15 | return vector.new(self.x * m, self.y * m, self.z * m) 16 | end 17 | 18 | function Vector:div(m) 19 | return vector.new(self.x / m, self.y / m, self.z / m) 20 | end 21 | 22 | function Vector:unm() 23 | return vector.new(-self.x, -self.y, -self.z) 24 | end 25 | 26 | function Vector:dot(o) 27 | return (self.x * o.x) + (self.y * o.y) + (self.z * o.z) 28 | end 29 | 30 | function Vector:cross(o) 31 | return vector.new( 32 | (self.y * o.z) - (self.z * o.y), 33 | (self.z * o.x) - (self.x * o.z), 34 | (self.x * o.y) - (self.y * o.x)) 35 | end 36 | 37 | function Vector:length() 38 | return math.sqrt((self.x * self.x) + (self.y * self.y) + (self.z * self.z)) 39 | end 40 | 41 | function Vector:normalize() 42 | return self:div(self:length()) 43 | end 44 | 45 | function Vector:round(tolerance) 46 | tolerance = tolerance or 1 47 | local squared = tolerance * tolerance 48 | return vector.new( 49 | math.floor(self.x + (tolerance * 0.5)) / squared, 50 | math.floor(self.y + (tolerance * 0.5)) / squared, 51 | math.floor(self.z + (tolerance * 0.5)) / squared) 52 | end 53 | 54 | function Vector:tostring() 55 | return string.format("%d,%d,%d", self.x, self.y, self.z) 56 | end 57 | 58 | function Vector:equals(o) 59 | return self.x == o.x and self.y == o.y and self.z == o.z 60 | end 61 | 62 | Vector.eq = Vector.equals 63 | 64 | local vect_mt = { 65 | __index = Vector 66 | } 67 | 68 | for k, v in pairs(Vector) do 69 | vect_mt["__"..k] = v 70 | end 71 | 72 | function vector.new(x, y, z) 73 | return setmetatable({x = x or 0, y = y or 0, z = z or 0}, vect_mt) 74 | end 75 | 76 | return vector 77 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/update.lua: -------------------------------------------------------------------------------- 1 | -- update: download a new copy of Recrafted 2 | 3 | local rc = require("rc") 4 | local term = require("term") 5 | local colors = require("colors") 6 | local textutils = require("textutils") 7 | 8 | if not package.loaded.http then 9 | io.stderr:write("The HTTP API is disabled and the updater cannot continue. Please enable the HTTP API in the ComputerCraft configuration and try again.\n") 10 | return 11 | end 12 | 13 | term.at(1,1).clear() 14 | 15 | textutils.coloredPrint(colors.yellow, 16 | "Recrafted Updater (Stage 1)\n===========================") 17 | 18 | print("Checking for update...") 19 | 20 | local http = require("http") 21 | local base = "https://raw.githubusercontent.com/ocawesome101/recrafted/primary/" 22 | 23 | local Bhandle, Berr = http.get(base .. "data/computercraft/lua/bios.lua") 24 | if not Bhandle then 25 | error(Berr, 0) 26 | end 27 | 28 | local first = Bhandle.readLine() 29 | Bhandle.close() 30 | 31 | local oldVersion = rc.version():gsub("Recrafted ", "") 32 | local newVersion = first:match("Recrafted v?(%d+.%d+.%d+)") 33 | 34 | if newVersion and (oldVersion ~= newVersion) or (...) == "-f" then 35 | textutils.coloredPrint(colors.green, "Found", colors.white, ": ", 36 | colors.red, oldVersion, colors.yellow, " -> ", colors.lime, 37 | newVersion or oldVersion) 38 | 39 | io.write("Apply update? [y/N]: ") 40 | if io.read() ~= "y" then 41 | textutils.coloredPrint(colors.red, "Not applying update.") 42 | return 43 | end 44 | 45 | textutils.coloredPrint(colors.green, "Applying update.") 46 | local handle, err = http.get(base.."updater.lua", nil, true) 47 | if not handle then 48 | error("Failed downloading stage 2: " .. err, 0) 49 | end 50 | 51 | local data = handle.readAll() 52 | handle.close() 53 | 54 | local out = io.open("/.start_rc.lua", "w") 55 | out:write(data) 56 | out:close() 57 | 58 | textutils.coloredWrite(colors.yellow, "Restarting...") 59 | rc.sleep(3) 60 | rc.reboot() 61 | else 62 | textutils.coloredPrint(colors.red, "None found") 63 | end 64 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/programs/shell.lua: -------------------------------------------------------------------------------- 1 | -- rc.shell 2 | 3 | local rc = require("rc") 4 | local fs = require("fs") 5 | local term = require("term") 6 | local shell = require("shell") 7 | local colors = require("colors") 8 | local thread = require("rc.thread") 9 | local textutils = require("textutils") 10 | 11 | if os.version then 12 | textutils.coloredPrint(colors.yellow, os.version(), colors.white) 13 | else 14 | textutils.coloredPrint(colors.yellow, rc.version(), colors.white) 15 | end 16 | 17 | thread.vars().parentShell = thread.id() 18 | shell.init(_ENV) 19 | 20 | if not shell.__has_run_startup then 21 | shell.__has_run_startup = true 22 | if fs.exists("/startup.lua") then 23 | local ok, err = pcall(dofile, "/startup.lua") 24 | if not ok and err then 25 | io.stderr:write(err, "\n") 26 | end 27 | end 28 | 29 | if fs.exists("/startup") and fs.isDir("/startup") then 30 | local files = fs.list("/startup/") 31 | table.sort(files) 32 | 33 | for f=1, #files, 1 do 34 | local ok, err = pcall(dofile, "/startup/"..files[f]) 35 | if not ok and err then 36 | io.stderr:write(err, "\n") 37 | end 38 | end 39 | end 40 | end 41 | 42 | local aliases = { 43 | background = "bg", 44 | clr = "clear", 45 | cp = "copy", 46 | dir = "list", 47 | foreground = "fg", 48 | ls = "list", 49 | mv = "move", 50 | rm = "delete", 51 | rs = "redstone", 52 | sh = "shell", 53 | ps = "threads" 54 | } 55 | 56 | for k, v in pairs(aliases) do 57 | shell.setAlias(k, v) 58 | end 59 | 60 | local completions = "/rc/completions" 61 | for _, prog in ipairs(fs.list(completions)) do 62 | dofile(fs.combine(completions, prog)) 63 | end 64 | 65 | local history = {} 66 | while true do 67 | term.setTextColor(colors.yellow) 68 | term.setBackgroundColor(colors.black) 69 | rc.write(shell.dir().."> ") 70 | term.setTextColor(colors.white) 71 | 72 | local text = term.read(nil, history, shell.complete) 73 | if #text > 0 then 74 | history[#history+1] = text 75 | local ok, err = shell.run(text) 76 | if not ok and err then 77 | io.stderr:write(err, "\n") 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/apis/colors.lua: -------------------------------------------------------------------------------- 1 | -- rc.colors 2 | 3 | local term = require("term") 4 | local bit32 = require("bit32") 5 | local expect = require("cc.expect") 6 | 7 | local colors = { 8 | white = 0x1, 9 | orange = 0x2, 10 | magenta = 0x4, 11 | lightBlue = 0x8, 12 | yellow = 0x10, 13 | lime = 0x20, 14 | pink = 0x40, 15 | gray = 0x80, 16 | grey = 0x80, 17 | lightGray = 0x100, 18 | lightGrey = 0x100, 19 | cyan = 0x200, 20 | purple = 0x400, 21 | blue = 0x800, 22 | brown = 0x1000, 23 | green = 0x2000, 24 | red = 0x4000, 25 | black = 0x8000, 26 | } 27 | 28 | local defaults = { 29 | 0xf0f0f0, 0xf2b233, 0xe57fd8, 0x99b2f2, 30 | 0xdede6c, 0x7fcc19, 0xf2b2cc, 0x4c4c4c, 31 | 0x999999, 0x4c99b2, 0xb266e5, 0x3366cc, 32 | 0x7f664c, 0x57a64e, 0xcc4c4c, 0x111111 33 | } 34 | 35 | for i=1, #defaults, 1 do 36 | term.setPaletteColor(2^(i-1), defaults[i]) 37 | end 38 | 39 | function colors.combine(...) 40 | local ret = 0 41 | local cols = {...} 42 | for i=1, #cols, 1 do 43 | expect.expect(i, cols[i], "number") 44 | ret = bit32.bor(ret, cols[i]) 45 | end 46 | return ret 47 | end 48 | 49 | function colors.subtract(cols, ...) 50 | expect.expect(1, cols, "number") 51 | local subt = {...} 52 | for i=1, #subt, 1 do 53 | expect.expect(i+1, subt[i], "number") 54 | cols = bit32.band(cols, bit32.bnot(subt[i])) 55 | end 56 | return cols 57 | end 58 | 59 | colors.test = bit32.btest 60 | 61 | function colors.packRGB(r, g, b) 62 | expect.expect(1, r, "number") 63 | if r > 1 then return r end 64 | expect.range(r, 0, 1) 65 | expect.range(expect.expect(2, g, "number"), 0, 1) 66 | expect.range(expect.expect(3, b, "number"), 0, 1) 67 | return (r * 255 * 0x10000) + (g * 255 * 0x100) + (b * 255) 68 | end 69 | 70 | function colors.unpackRGB(rgb) 71 | expect.range(expect.expect(1, rgb, "number"), 0, 0xFFFFFF) 72 | return bit32.rshift(rgb, 16) / 255, 73 | bit32.rshift(bit32.band(rgb, 0xFF00), 8) / 255, 74 | bit32.band(rgb, 0xFF) / 255 75 | end 76 | 77 | function colors.toBlit(color) 78 | expect.expect(1, color, "number") 79 | return string.format("%x", math.floor(math.log(color, 2))) 80 | end 81 | 82 | return colors 83 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/programs/redstone.lua: -------------------------------------------------------------------------------- 1 | local rc = require("rc") 2 | local rs = require("redstone") 3 | local colors = require("colors") 4 | local textutils = require("textutils") 5 | 6 | local args = {...} 7 | 8 | local commands = {} 9 | 10 | local sides = {"top", "bottom", "left", "right", "front", "back"} 11 | 12 | function commands.probe() 13 | textutils.coloredPrint(colors.yellow, "redstone inputs", colors.white) 14 | local inputs = {} 15 | for i=1, #sides, 1 do 16 | if rs.getInput(sides[i]) then 17 | inputs[#inputs+1] = sides[i] 18 | end 19 | end 20 | if #inputs == 0 then inputs[1] = "None" end 21 | print(table.concat(inputs, ", ")) 22 | return true 23 | end 24 | 25 | local function coerce(value) 26 | if value == "true" then return true end 27 | if value == "false" then return false end 28 | return tonumber(value) or value 29 | end 30 | 31 | function commands.set(side, color, value) 32 | if not side then 33 | io.stderr:write("side expected\n") 34 | return true 35 | end 36 | 37 | if not value then 38 | value = color 39 | color = nil 40 | end 41 | 42 | value = coerce(value) 43 | if type(value) == "string" then 44 | io.stderr:write("value must be boolean or 0-15\n") 45 | end 46 | 47 | if color then 48 | color = coerce(color) 49 | if type(value) == "number" then 50 | io.stderr:write("value must be boolean\n") 51 | end 52 | 53 | if not colors[color] then 54 | io.stderr:write("color not defined\n") 55 | end 56 | 57 | rs.setBundledOutput(side, colors[color], value) 58 | 59 | elseif type(value) == "boolean" then 60 | rs.setOutput(side, value) 61 | 62 | else 63 | rs.setAnalogOutput(side, value) 64 | end 65 | 66 | return true 67 | end 68 | 69 | function commands.pulse(side, count, period) 70 | count = tonumber(count) or 1 71 | period = tonumber(period) or 0.5 72 | 73 | for _=1, count, 1 do 74 | rs.setOutput(side, true) 75 | rc.sleep(period / 2) 76 | rs.setOutput(side, false) 77 | rc.sleep(period / 2) 78 | end 79 | 80 | return true 81 | end 82 | 83 | if not (args[1] and commands[args[1]] and 84 | commands[args[1]](table.unpack(args, 2))) then 85 | io.stderr:write("Usages:\nredstone probe\n".. 86 | "redstone set \nredstone set \n".. 87 | "redstone pulse \n") 88 | end 89 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/programs/devbin.lua: -------------------------------------------------------------------------------- 1 | -- devbin (like pastebin) 2 | 3 | if not package.loaded.http then 4 | error("HTTP is not enabled in the ComputerCraft configuration", 0) 5 | end 6 | 7 | local http = require("http") 8 | local json = require("rc.json") 9 | local shell = require("shell") 10 | 11 | local args = {...} 12 | 13 | if #args < (args[1] == "get" and 3 or 2) then 14 | io.stderr:write([[ 15 | Usage: 16 | devbin put 17 | devbin get 18 | devbin run [argument ...] 19 | ]]) 20 | return 21 | end 22 | 23 | local paste = "https://devbin.dev/api/v3/paste" 24 | local key = "aNVXl8vxYGWcZGvMnuJTzLXH53mGWOuQtBXU025g8YDAsZDu" 25 | 26 | local function get(code) 27 | local handle, err, rerr = http.get("https://devbin.dev/raw/"..code) 28 | if not handle then 29 | if rerr then rerr.close() end 30 | error(err, 0) 31 | end 32 | 33 | local data = handle.readAll() 34 | handle.close() 35 | 36 | return data 37 | end 38 | 39 | if args[1] == "put" then 40 | local handle, err = io.open(shell.resolve(args[2]), "r") 41 | if not handle then error(err, 0) end 42 | local data = handle:read("a") 43 | handle:close() 44 | 45 | if (not data) or #data == 0 then 46 | error("cannot 'put' empty file", 0) 47 | end 48 | 49 | local request = json.encode({ 50 | title = args[2], 51 | syntaxName = "lua", 52 | exposure = 0, 53 | content = data, 54 | asGuest = true 55 | }) 56 | 57 | local response, rerr, rerr2 = http.post(paste, request, 58 | {["Content-Type"]="application/json", Authorization = key}, true) 59 | if not response then 60 | local _rerr2 = rerr2.readAll() 61 | if rerr2 then rerr2.close() end 62 | print(_rerr2) 63 | error(rerr, 0) 64 | --("%q: %q"):format(rerr, (rerr2 and rerr2.readAll()) or ""), 0) 65 | end 66 | 67 | local rdata = response.readAll() 68 | 69 | local code, message = response.getResponseCode() 70 | response.close() 71 | if code ~= 201 then 72 | error(code .. " " .. (message or ""), 0) 73 | end 74 | 75 | local decoded = json.decode(rdata) 76 | 77 | print(decoded.code) 78 | 79 | elseif args[1] == "get" then 80 | local data = get(args[2]) 81 | local handle, err = io.open(shell.resolve(args[3]), "w") 82 | if not handle then error(err, 0) end 83 | handle:write(data) 84 | handle:close() 85 | 86 | elseif args[1] == "run" then 87 | local data = get(args[2]) 88 | assert(load(data, "=", "t", _G))() 89 | end 90 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/apis/help.lua: -------------------------------------------------------------------------------- 1 | -- rc.help 2 | 3 | local fs = require("fs") 4 | local thread = require("rc.thread") 5 | local expect = require("cc.expect").expect 6 | local completion = require("cc.completion") 7 | 8 | local help = {} 9 | help._DEFAULT_PATH = "/rc/help" 10 | 11 | function help.init() 12 | local vars = thread.vars() 13 | vars.help = vars.help or help._DEFAULT_PATH 14 | end 15 | 16 | function help.path() 17 | return thread.vars().help or help._DEFAULT_PATH 18 | end 19 | 20 | function help.setPath(new) 21 | expect(1, new, "string") 22 | thread.vars().help = new 23 | end 24 | 25 | function help.lookup(topic) 26 | expect(1, topic, "string") 27 | 28 | topic = topic 29 | 30 | for directory in help.path():gmatch("[^:]+") do 31 | local try = fs.combine(directory, topic .. ".hlp") 32 | 33 | if fs.exists(try) then 34 | return try 35 | end 36 | end 37 | end 38 | 39 | function help.topics() 40 | local topics = {} 41 | 42 | for directory in help.path():gmatch("[^:]+") do 43 | local _topics = fs.list(directory) 44 | for i=1, #_topics, 1 do 45 | topics[#topics+1] = _topics[i]:gsub("%.hlp$", "") 46 | end 47 | end 48 | 49 | return topics 50 | end 51 | 52 | function help.completeTopic(prefix) 53 | local topics = help.topics() 54 | table.sort(topics, function(a, b) return #a < #b end) 55 | 56 | return completion.choice(prefix, topics) 57 | end 58 | 59 | local directives = { 60 | color = function(c) 61 | return require("colors")[c or "white"] 62 | end, 63 | ["break"] = function() 64 | return "\n" 65 | end 66 | } 67 | 68 | function help.loadTopic(name) 69 | local path = help.lookup(name) 70 | if not path then return end 71 | 72 | local handle = io.open(path, "r") 73 | local data = {} 74 | 75 | local lastWasText = false 76 | for line in handle:lines() do 77 | if line:sub(1,2) == ">>" then 78 | lastWasText = false 79 | local words = {} 80 | for word in line:sub(3):gmatch("[^ ]+") do 81 | words[#words+1] = word 82 | end 83 | 84 | if directives[words[1]] then 85 | data[#data+1] = directives[words[1]](table.unpack(words, 2)) 86 | end 87 | 88 | else 89 | if lastWasText then 90 | data[#data+1] = "\n" 91 | end 92 | lastWasText = true 93 | data[#data+1] = line 94 | end 95 | end 96 | 97 | handle:close() 98 | 99 | return data 100 | end 101 | 102 | return help 103 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/keymaps/lwjgl3.lua: -------------------------------------------------------------------------------- 1 | -- keymap for 1.16.5+ 2 | 3 | return { 4 | [32] = "space", 5 | [39] = "apostrophe", 6 | [44] = "comma", 7 | [45] = "minus", 8 | [46] = "period", 9 | [47] = "slash", 10 | [48] = "zero", 11 | [49] = "one", 12 | [50] = "two", 13 | [51] = "three", 14 | [52] = "four", 15 | [53] = "five", 16 | [54] = "six", 17 | [55] = "seven", 18 | [56] = "eight", 19 | [57] = "nine", 20 | [59] = "semicolon", 21 | [61] = "equals", 22 | [65] = "a", 23 | [66] = "b", 24 | [67] = "c", 25 | [68] = "d", 26 | [69] = "e", 27 | [70] = "f", 28 | [71] = "g", 29 | [72] = "h", 30 | [73] = "i", 31 | [74] = "j", 32 | [75] = "k", 33 | [76] = "l", 34 | [77] = "m", 35 | [78] = "n", 36 | [79] = "o", 37 | [80] = "p", 38 | [81] = "q", 39 | [82] = "r", 40 | [83] = "s", 41 | [84] = "t", 42 | [85] = "u", 43 | [86] = "v", 44 | [87] = "w", 45 | [88] = "x", 46 | [89] = "y", 47 | [90] = "z", 48 | [91] = "leftBracket", 49 | [92] = "backslash", 50 | [93] = "rightBracket", 51 | [96] = "grave", 52 | [257] = "enter", 53 | [258] = "tab", 54 | [259] = "backspace", 55 | [260] = "insert", 56 | [261] = "delete", 57 | [262] = "right", 58 | [263] = "left", 59 | [264] = "down", 60 | [265] = "up", 61 | [266] = "pageUp", 62 | [267] = "pageDown", 63 | [268] = "home", 64 | [269] = "end", 65 | [280] = "capsLock", 66 | [281] = "scrollLock", 67 | [282] = "numLock", 68 | [283] = "printScreen", 69 | [284] = "pause", 70 | [290] = "f1", 71 | [291] = "f2", 72 | [292] = "f3", 73 | [293] = "f4", 74 | [294] = "f5", 75 | [295] = "f6", 76 | [296] = "f7", 77 | [297] = "f8", 78 | [298] = "f9", 79 | [299] = "f10", 80 | [300] = "f11", 81 | [301] = "f12", 82 | [302] = "f13", 83 | [303] = "f14", 84 | [304] = "f15", 85 | [305] = "f16", 86 | [306] = "f17", 87 | [307] = "f18", 88 | [308] = "f19", 89 | [309] = "f20", 90 | [310] = "f21", 91 | [311] = "f22", 92 | [312] = "f23", 93 | [313] = "f24", 94 | [314] = "f25", 95 | [320] = "numpad0", 96 | [321] = "numpad1", 97 | [322] = "numpad2", 98 | [323] = "numpad3", 99 | [324] = "numpad4", 100 | [325] = "numpad5", 101 | [326] = "numpad6", 102 | [327] = "numpad7", 103 | [328] = "numpad8", 104 | [329] = "numpad9", 105 | [330] = "numpadDot", 106 | [331] = "numpadDivide", 107 | [332] = "numpadMultiply", 108 | [333] = "numpadSubtract", 109 | [334] = "numpadAdd", 110 | [335] = "numpadEnter", 111 | [336] = "numpadEqual", 112 | [340] = "leftShift", 113 | [341] = "leftCtrl", 114 | [342] = "leftAlt", 115 | [343] = "leftSuper", 116 | [344] = "rightShift", 117 | [345] = "rightCtrl", 118 | [346] = "rightAlt", 119 | [348] = "menu", 120 | } 121 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/apis/settings.lua: -------------------------------------------------------------------------------- 1 | -- rc.settings 2 | 3 | local expect = require("cc.expect") 4 | local textutils = require("textutils") 5 | 6 | local settings = {} 7 | local defs = {} 8 | local set = {} 9 | 10 | function settings.define(name, opts) 11 | expect.expect(1, name, "string") 12 | expect.expect(2, opts, "table", "nil") 13 | 14 | opts = opts or {} 15 | opts.description = expect.field(opts, "description", "string", "nil") 16 | opts.type = expect.field(opts, "type", "string", "nil") 17 | defs[name] = opts 18 | end 19 | 20 | function settings.undefine(name) 21 | expect.expect(1, name, "string") 22 | defs[name] = nil 23 | end 24 | 25 | function settings.set(name, value) 26 | expect.expect(1, name, "string") 27 | 28 | if defs[name] and defs[name].type then 29 | expect.expect(2, value, defs[name].type) 30 | else 31 | expect.expect(2, value, "number", "string", "boolean") 32 | end 33 | 34 | set[name] = value 35 | end 36 | 37 | function settings.get(name, default) 38 | expect.expect(1, name, "string") 39 | if set[name] ~= nil then 40 | return set[name] 41 | elseif default ~= nil then 42 | return default 43 | else 44 | return defs[name] and defs[name].default 45 | end 46 | end 47 | 48 | function settings.getDetails(name) 49 | expect.expect(1, name, "string") 50 | local def = defs[name] 51 | if not def then return end 52 | return { 53 | description = def.description, 54 | default = def.default, 55 | value = set[name], 56 | type = def.type, 57 | } 58 | end 59 | 60 | function settings.unset(name) 61 | expect.expect(1, name, "string") 62 | set[name] = nil 63 | end 64 | 65 | function settings.clear() 66 | set = {} 67 | end 68 | 69 | function settings.getNames() 70 | local names = {} 71 | for k in pairs(defs) do 72 | names[#names+1] = k 73 | end 74 | table.sort(names) 75 | return names 76 | end 77 | 78 | function settings.load(path) 79 | expect.expect(1, path, "string", "nil") 80 | 81 | path = path or ".settings" 82 | local handle = io.open(path, "r") 83 | if not handle then 84 | return false 85 | end 86 | 87 | local data = handle:read("a") 88 | handle:close() 89 | 90 | local new = textutils.unserialize(data) 91 | if not new then return false end 92 | for k, v in pairs(new) do 93 | set[k] = v 94 | end 95 | 96 | return true 97 | end 98 | 99 | function settings.save(path) 100 | expect.expect(1, path, "string", "nil") 101 | 102 | path = path or ".settings" 103 | local data = textutils.serialize(set) 104 | 105 | local handle = io.open(path, "w") 106 | if not handle then return false end 107 | 108 | handle:write(data) 109 | handle:close() 110 | 111 | return true 112 | end 113 | 114 | return settings 115 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/startup/90_settings.lua: -------------------------------------------------------------------------------- 1 | -- setting definitions 2 | 3 | local settings = require("settings") 4 | 5 | settings.define("list.show_hidden", { 6 | description = "Show hidden files in list's output", 7 | type = "boolean", 8 | default = false 9 | }) 10 | 11 | settings.define("bios.compat_mode", { 12 | description = "Attempt some CraftOS compatibility by injecting APIs into _G.", 13 | type = "boolean", 14 | default = false 15 | }) 16 | 17 | settings.define("shell.tracebacks", { 18 | description = "Show error tracebacks in the shell.", 19 | type = "boolean", 20 | default = false 21 | }) 22 | 23 | settings.define("edit.scroll_offset", { 24 | description = "How many lines to keep between the cursor and the screen edge.", 25 | type = "number", 26 | default = 3 27 | }) 28 | 29 | settings.define("edit.force_highlight", { 30 | description = "Whether to use the highlighting editor, even on basic computers.", 31 | type = "boolean", 32 | default = false 33 | }) 34 | 35 | settings.define("edit.scroll_factor", { 36 | description = "Related to how many lines the editor should jump at a time when scrolling. Determined by term_height/scroll_factor. Adjust this for performance.", 37 | type = "number", 38 | default = 8 39 | }) 40 | 41 | settings.define("edit.color_separator", { 42 | description = "What color separating characters (e.g. ()[];{}) should be.", 43 | type = "string", 44 | default = "lightBlue" 45 | }) 46 | 47 | settings.define("edit.color_operator", { 48 | description = "What color operators (e.g. +-/*) should be.", 49 | type = "string", 50 | default = "lightGray" 51 | }) 52 | 53 | settings.define("edit.color_keyword", { 54 | description = "What color keywords (e.g. local, for, if) should be.", 55 | type = "string", 56 | default = "orange" 57 | }) 58 | 59 | settings.define("edit.color_boolean", { 60 | description = "What color booleans (true/false) should be.", 61 | type = "string", 62 | default = "purple" 63 | }) 64 | 65 | settings.define("edit.color_comment", { 66 | description = "What color comments should be.", 67 | type = "string", 68 | default = "gray" 69 | }) 70 | 71 | settings.define("edit.color_global", { 72 | description = "What color globals (e.g. print, require) should be.", 73 | type = "string", 74 | default = "lime" 75 | }) 76 | 77 | settings.define("edit.color_string", { 78 | description = "What color strings should be.", 79 | type = "string", 80 | default = "red" 81 | }) 82 | 83 | settings.define("edit.color_number", { 84 | description = "What color numbers (e.g. 2, 0xF3, 0.42) should be.", 85 | type = "string", 86 | default = "magenta" 87 | }) 88 | 89 | settings.define("bios.restrict_globals", { 90 | description = "Disallow global variables", 91 | type = "boolean", 92 | default = false 93 | }) 94 | 95 | settings.define("bios.parallel_startup", { 96 | description = "Run startup scripts from /startup in parallel", 97 | type = "boolean", 98 | default = false 99 | }) 100 | 101 | settings.load() 102 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/modules/main/cc/shell/completion.lua: -------------------------------------------------------------------------------- 1 | -- cc.shell.completion 2 | -- XXX incompatible: functions do not accept a 'shell' argument 3 | 4 | local completion = require("cc.completion") 5 | local expect = require("cc.expect").expect 6 | local shell = require("shell") 7 | local fs = require("fs") 8 | 9 | local c = {} 10 | 11 | function c.file(text) 12 | expect(1, text, "string") 13 | return fs.complete(text, shell.dir(), true, false) 14 | end 15 | 16 | function c.dir(text) 17 | expect(1, text, "string") 18 | return fs.complete(text, shell.dir(), false) 19 | end 20 | 21 | function c.dirOrFile(text, previous, add_space) 22 | expect(1, text, "string") 23 | expect(2, previous, "table") 24 | expect(3, add_space, "boolean", "nil") 25 | local completed = fs.complete(text, shell.dir(), true, true) 26 | 27 | if add_space then 28 | for i=1, #completed, 1 do 29 | completed[i] = completed[i] .. " " 30 | end 31 | end 32 | 33 | return completed 34 | end 35 | 36 | function c.program(text, add_space) 37 | expect(1, text, "string") 38 | expect(2, add_space, "boolean", "table", "nil") 39 | local progs = shell.programs() 40 | local files = c.file(text) 41 | 42 | local seen = {} 43 | for i=1, #files, 1 do 44 | local full = text..files[i] 45 | if fs.isDir(full) and full:sub(-1) ~= "/" then 46 | full = full .. "/" 47 | end 48 | if not seen[full] then 49 | progs[#progs+1] = full 50 | end 51 | seen[full] = true 52 | end 53 | 54 | table.sort(progs, function(a,b) return #a < #b end) 55 | 56 | return completion.choice(text, progs, add_space) 57 | end 58 | 59 | function c.programWithArgs(text, previous, starting) 60 | expect(1, text, "string") 61 | expect(2, previous, "table") 62 | expect(3, starting, "number") 63 | 64 | if not previous[starting] then 65 | return shell.completeProgram(text) 66 | end 67 | 68 | local command = previous[starting] 69 | command = shell.aliases()[command] or command 70 | local complete = shell.getCompletionInfo()[command] 71 | 72 | if complete then 73 | return complete(#previous - starting + 1, text, 74 | {table.unpack(previous, starting)}) 75 | end 76 | end 77 | 78 | for k,v in pairs(completion) do 79 | c[k] = function(text, _, ...) return v(text, ...) end 80 | end 81 | 82 | function c.build(...) 83 | local args = table.pack(...) 84 | 85 | for i=1, args.n, 1 do 86 | expect(i, args[i], "function", "table", "nil") 87 | end 88 | 89 | return function(index, current, previous) 90 | local complete 91 | if args.n < index then 92 | if type(args[args.n]) == "table" and args[args.n].many then 93 | complete = args[args.n] 94 | end 95 | 96 | else 97 | complete = args[index] 98 | end 99 | 100 | if not complete then 101 | return {} 102 | end 103 | 104 | if type(complete) == "function" then 105 | return complete(current, previous) 106 | 107 | elseif type(complete) == "table" then 108 | return complete[1](current, previous, table.unpack(complete, 2)) 109 | end 110 | end 111 | end 112 | 113 | return c 114 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/startup/10_package.lua: -------------------------------------------------------------------------------- 1 | -- package library -- 2 | 3 | local rc = ... 4 | 5 | _G.package = {} 6 | 7 | package.config = "/\n;\n?\n!\n-" 8 | package.cpath = "" 9 | package.path = "/rc/apis/?.lua;/rc/modules/main/?.lua;./lib/?.lua;./lib/?/init.lua;./?.lua;./?/init.lua" 10 | 11 | local function rm(api) 12 | local tab = _G[api] 13 | _G[api] = nil 14 | return tab 15 | end 16 | 17 | package.loaded = { 18 | _G = _G, 19 | os = os, 20 | rc = rc, 21 | math = math, 22 | utf8 = utf8, 23 | table = table, 24 | debug = debug, 25 | bit32 = rawget(_G, "bit32"), 26 | string = string, 27 | package = package, 28 | coroutine = coroutine, 29 | 30 | -- CC-specific ones 31 | peripheral = rm("peripheral"), 32 | redstone = rm("redstone"), 33 | commands = rm("commands"), 34 | pocket = rm("pocket"), 35 | turtle = rm("turtle"), 36 | http = rm("http"), 37 | term = rm("term"), 38 | fs = rm("fs"), 39 | rs = rm("rs"), 40 | 41 | -- CraftOS-PC APIs 42 | periphemu = rm("periphemu"), 43 | mounter = rm("mounter"), 44 | config = rm("config"), 45 | 46 | -- CCEmuX API 47 | ccemux = rm("ccemux") 48 | } 49 | 50 | package.preload = {} 51 | 52 | package.searchers = { 53 | -- check package.preload 54 | function(mod) 55 | if package.preload[mod] then 56 | return package.preload[mod] 57 | else 58 | return nil, "no field package.preload['" .. mod .. "']" 59 | end 60 | end, 61 | 62 | -- check for lua library 63 | function(mod) 64 | local ok, err = package.searchpath(mod, package.path, ".", "/") 65 | if not ok then 66 | return nil, err 67 | end 68 | 69 | local func, loaderr = loadfile(ok) 70 | if not func then 71 | return nil, loaderr 72 | end 73 | return func() 74 | end, 75 | } 76 | 77 | local fs = package.loaded.fs 78 | -- require isn't here yet 79 | local expect = loadfile("/rc/modules/main/cc/expect.lua")() 80 | package.loaded["cc.expect"] = expect 81 | 82 | function package.searchpath(name, path, sep, rep) 83 | expect(1, name, "string") 84 | expect(2, path, "string") 85 | expect(3, sep, "string", "nil") 86 | expect(4, rep, "string", "nil") 87 | 88 | sep = "%" .. (sep or ".") 89 | rep = rep or "/" 90 | 91 | name = name:gsub(sep, rep) 92 | local serr = "" 93 | 94 | for search in path:gmatch("[^;]+") do 95 | search = search:gsub("%?", name) 96 | 97 | if fs.exists(search) then 98 | return search 99 | 100 | else 101 | if #serr > 0 then 102 | serr = serr .. "\n " 103 | end 104 | 105 | serr = serr .. "no file '" .. search .. "'" 106 | end 107 | end 108 | 109 | return nil, serr 110 | end 111 | 112 | function _G.require(mod) 113 | expect(1, mod, "string") 114 | 115 | if package.loaded[mod] then 116 | return package.loaded[mod] 117 | end 118 | 119 | local serr = "module '" .. mod .. "' not found:" 120 | for _, searcher in ipairs(package.searchers) do 121 | local result, err = searcher(mod) 122 | if result then 123 | package.loaded[mod] = result 124 | return result 125 | else 126 | serr = serr .. "\n " .. err 127 | end 128 | end 129 | 130 | error(serr, 2) 131 | end 132 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/apis/rednet.lua: -------------------------------------------------------------------------------- 1 | -- rc.rednet 2 | 3 | local expect = require("cc.expect").expect 4 | local peripheral = require("peripheral") 5 | 6 | local rednet = { 7 | CHANNEL_BROADCAST = 65535, 8 | CHANNEL_REPEAT = 65533, 9 | MAX_ID_CHANNELS = 65500, 10 | } 11 | 12 | local opened = {} 13 | function rednet.open(modem) 14 | expect(1, modem, "string") 15 | peripheral.call(modem, "open", os.computerID()) 16 | peripheral.call(modem, "open", rednet.CHANNEL_BROADCAST) 17 | opened[modem] = true 18 | end 19 | 20 | local function call(method, modem, erase, passids, ...) 21 | local ret = false 22 | if modem then 23 | if erase then opened[modem] = false end 24 | if passids then 25 | ret = ret or peripheral.call(modem, method, os.computerID(), ...) 26 | ret = ret or peripheral.call(modem, method, rednet.CHANNEL_BROADCAST, ...) 27 | else 28 | ret = peripheral.call(modem, method, ...) 29 | end 30 | 31 | else 32 | for k in pairs(opened) do 33 | ret = ret or call(k, method, erase, passids, ...) 34 | end 35 | end 36 | return ret 37 | end 38 | 39 | function rednet.close(modem) 40 | expect(1, modem, "string", "nil") 41 | return call("close", modem, true, true) 42 | end 43 | 44 | function rednet.isOpen(modem) 45 | expect(1, modem, "string", "nil") 46 | return call("isOpen", modem, false, true) 47 | end 48 | 49 | function rednet.send(to, message, protocol) 50 | expect(1, to, "number") 51 | expect(2, message, "string", "table", "number", "boolean") 52 | expect(3, protocol, "string", "nil") 53 | 54 | if type(message) == "table" then 55 | if protocol then table.insert(message, 1, protocol) end 56 | table.insert(message, 1, "rednet_message") 57 | else 58 | message = {"rednet_message", to, message, protocol} 59 | end 60 | call("transmit", nil, false, false, rednet.CHANNEL_BROADCAST, 61 | os.computerID(), message) 62 | return rednet.isOpen() 63 | end 64 | 65 | function rednet.broadcast(message, protocol) 66 | expect(1, message, "string", "table", "number", "boolean") 67 | expect(2, protocol, "string", "nil") 68 | call("transmit", nil, false, false, rednet.CHANNEL_BROADCAST, 69 | rednet.CHANNEL_BROADCAST, message) 70 | end 71 | 72 | function rednet.receive(protocol, timeout) 73 | expect(1, protocol, "string", "nil") 74 | timeout = expect(2, timeout, "number", "nil") or math.huge 75 | 76 | local timer 77 | if timeout then 78 | timer = os.startTimer(timer) 79 | end 80 | 81 | while true do 82 | local event = table.pack(os.pullEvent()) 83 | if event[1] == "timer" and event[2] == timer then return end 84 | if event[1] == "rednet_message" and (event[4] == protocol or 85 | not protocol) then 86 | return table.unpack(event, 2) 87 | end 88 | end 89 | end 90 | 91 | local running = false 92 | function rednet.run() 93 | if running then 94 | error("rednet is already running") 95 | end 96 | 97 | running = true 98 | 99 | while true do 100 | local event = table.pack(os.pullEvent()) 101 | if event[1] == "modem_message" then 102 | local message = event[5] 103 | if type(message) == "table" then 104 | if message[1] == "rednet_message" and (message[2] == os.computerID() or 105 | message[2] == rednet.CHANNEL_BROADCAST) then 106 | os.queueEvent("rednet_message", event[3], message[2], message[3]) 107 | end 108 | end 109 | end 110 | end 111 | end 112 | 113 | return rednet 114 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/modules/main/cc/strings.lua: -------------------------------------------------------------------------------- 1 | -- cc.strings 2 | 3 | local term = require("term") 4 | local expectlib = require("cc.expect") 5 | local expect = expectlib.expect 6 | local field = expectlib.field 7 | local strings = {} 8 | 9 | local function count(...) 10 | local tab = table.pack(...) 11 | local n = 0 12 | 13 | for i=1, tab.n, 1 do 14 | if tab[i] then n = n + 1 end 15 | end 16 | 17 | return n 18 | end 19 | 20 | function strings.splitElements(text, limit) 21 | expect(1, text, "string") 22 | expect(2, limit, "number", "nil") 23 | 24 | local tokens = {} 25 | 26 | while #text > 0 do 27 | local ws = text:match("^[ \t]+") 28 | local nl = text:match("^\n+") 29 | local sep = text:match("^[%-%+%*]") 30 | local word = text:match("^[^ \t\n%-%+%*]+") 31 | 32 | if count(ws, nl, sep, word) > 1 then 33 | error(("Edge case: %q, %q, %q, %q"):format(ws, nl, sep, word), 0) 34 | end 35 | 36 | local token = ws or nl or sep or word 37 | text = text:sub(#token + 1) 38 | 39 | while #token > 0 do 40 | local ttext = token:sub(1, limit or 65535) 41 | token = token:sub(#ttext + 1) 42 | 43 | tokens[#tokens+1] = { text = ttext, type = ws and "ws" or nl and "nl" 44 | or sep and "word" or word and "word" } 45 | end 46 | end 47 | 48 | return tokens 49 | end 50 | 51 | function strings.wrappedWriteElements(elements, width, doHalves, handler) 52 | expect(1, elements, "table") 53 | expect(2, width, "number") 54 | expect(3, doHalves, "boolean", "nil") 55 | expect(4, handler, "table") 56 | 57 | field(handler, "newline", "function") 58 | field(handler, "append", "function") 59 | field(handler, "getX", "function") 60 | 61 | for i=1, #elements, 1 do 62 | local e = elements[i] 63 | 64 | if e.type == "nl" then 65 | for _=1, #e.text do handler.newline() end 66 | 67 | elseif e.type == "ws" then 68 | local x = handler.getX() 69 | 70 | if x + #e.text > width + 1 then 71 | handler.newline() 72 | 73 | else 74 | handler.append(e.text) 75 | end 76 | 77 | elseif e.type == "word" then 78 | local x = handler.getX() 79 | local half = math.ceil(#e.text / 2) 80 | 81 | if x + #e.text > width + 1 then 82 | if doHalves and x + half < width and #e.text > width/2 then 83 | local halfText = e.text:sub(1, math.floor(#e.text / 2)) .. "-" 84 | e.text = e.text:sub(#halfText) 85 | handler.append(halfText) 86 | handler.newline() 87 | 88 | elseif x > 1 then 89 | handler.newline() 90 | end 91 | end 92 | 93 | handler.append(e.text) 94 | end 95 | end 96 | end 97 | 98 | function strings.wrap(text, width, doHalves) 99 | expect(1, text, "string") 100 | expect(2, width, "number", "nil") 101 | expect(3, doHalves, "boolean", "nil") 102 | 103 | width = width or term.getSize() 104 | 105 | local lines = { "" } 106 | local elements = strings.splitElements(text, width) 107 | 108 | strings.wrappedWriteElements(elements, width, doHalves, { 109 | newline = function() 110 | lines[#lines+1] = "" 111 | end, 112 | 113 | append = function(newText) 114 | lines[#lines] = lines[#lines] .. newText 115 | end, 116 | 117 | getX = function() 118 | return #lines[#lines] 119 | end 120 | }) 121 | 122 | return lines 123 | end 124 | 125 | function strings.ensure_width(line, width) 126 | expect(1, line, "string") 127 | expect(2, width, "number", "nil") 128 | width = width or term.getSize() 129 | 130 | return (line .. (" "):rep(width - #line)):sub(1, width) 131 | end 132 | 133 | return strings 134 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/modules/main/edit/syntax/lua.lua: -------------------------------------------------------------------------------- 1 | local syn = { 2 | whitespace = { 3 | { 4 | function(c) 5 | return c:match("[ \n\r\t]") 6 | end, 7 | function() 8 | return false 9 | end, 10 | function(c) 11 | return c:match("^[ \n\r\t]+") 12 | end 13 | }, 14 | }, 15 | word = { 16 | { 17 | function(c) 18 | return not not c:match("[a-zA-Z_]") 19 | end, 20 | function(_, c) 21 | return not not c:match("[a-zA-Z_0-9]") 22 | end 23 | } 24 | }, 25 | keyword = { 26 | "const", "close", "local", "while", "for", "repeat", "until", "do", "if", 27 | "in", "else", "elseif", "and", "or", "not", "then", "end", "return", 28 | "goto", "break", 29 | }, 30 | builtin = { 31 | "function", 32 | }, 33 | separator = { 34 | ",", "(", ")", "{", "}", "[", "]", 35 | }, 36 | operator = { 37 | "+", "-", "/", "*", "//", "==", ">>", "<<", ">", "<", "=", "&", 38 | "|", "^", "%", "~", "...", "..", "~=", "#", ".", ":" 39 | }, 40 | boolean = { 41 | "true", "false", "nil" 42 | }, 43 | comment = { 44 | { 45 | function(c) 46 | return c == "-" 47 | end, 48 | function(t,c) 49 | if t == "-" and c ~= "-" then return false end 50 | return c ~= "\n" 51 | end, 52 | function(t) 53 | return #t > 1 54 | end 55 | }, 56 | { 57 | function(c) 58 | return c == "-" 59 | end, 60 | function(t,c) 61 | if t == "-" and c == "-" then return true 62 | elseif t == "--" and c == "[" then return true 63 | elseif t == "--[" and c == "=" and c == "[" then return true 64 | elseif t:match("^%-%-%[(=*)$") and c == "=" and c == "[" then 65 | return true 66 | end 67 | local eqs = t:match("^%-%-%[(=*)") 68 | if not eqs then 69 | return false 70 | else 71 | if #t == #eqs + 3 and c == "[" then return true end 72 | if t:sub(-(#eqs+2)) == "]"..eqs.."]" then 73 | return false 74 | else 75 | return true 76 | end 77 | end 78 | end, 79 | function(t) 80 | return #t > 3 81 | end 82 | } 83 | }, 84 | string = { 85 | { 86 | function(c) 87 | return c == "'" or c == '"' 88 | end, 89 | function(t, c) 90 | local first = t:sub(1,1) 91 | local last = t:sub(#t) 92 | local penultimate = t:sub(-2, -2) 93 | if #t == 1 then return true end 94 | if first == last and penultimate ~= "\\" then return false end 95 | return true 96 | end 97 | }, 98 | { 99 | function(c) 100 | return c == "[" 101 | end, 102 | function(t,c) 103 | if t == "[" then 104 | return c == "=" or c == "[" 105 | elseif t:match("^%[(=*)$") and (c == "=" or c == "[") then 106 | return true 107 | end 108 | local eqs = t:match("^%[(=*)") 109 | if not eqs then 110 | return false 111 | else 112 | if #t == #eqs + 3 and c == "[" then return true end 113 | if t:sub(-(#eqs+2)) == "]"..eqs.."]" then 114 | return false 115 | else 116 | return true 117 | end 118 | end 119 | end, 120 | function(t) 121 | return #t > 2 122 | end 123 | } 124 | }, 125 | number = { 126 | { 127 | function(c) 128 | return not not tonumber(c) 129 | end, 130 | function(t, c) 131 | return not not tonumber(t .. c .. "0") 132 | end 133 | } 134 | } 135 | } 136 | 137 | local seen = {} 138 | local function add(k, v) 139 | if not v then return end 140 | if seen[v] then return end 141 | seen[v] = true 142 | for _k, _v in pairs(v) do 143 | syn.builtin[#syn.builtin+1] = char((k and k.."." or "").._k) 144 | if type(_v) == "table" then 145 | add((k and k.."." or "").._k, _v) 146 | end 147 | end 148 | end 149 | 150 | add(nil, globalenv) 151 | 152 | return syn 153 | -------------------------------------------------------------------------------- /NEW_FEATURES.txt: -------------------------------------------------------------------------------- 1 | NEW IN RECRAFTED 2 | 3 | The following is a non-exhaustive list of new 4 | and/or changed features in Recrafted compared to 5 | CraftOS. 6 | 7 | !!!! THIS FILE IS OUTDATED !!!! 8 | 9 | See https://ocaweso.me/recrafted for more 10 | up-to-date documentation. 11 | 12 | Major Changes 13 | ============= 14 | 15 | - `os.loadAPI` has been completely omitted. 16 | - * All APIs except the standard Lua ones must be 17 | loaded using `require` before they can be used. 18 | - * Lua 5.1 builtins have been removed from `_G`, 19 | but can be accessed through `require("lua51")`. 20 | - Startup scripts from /startup are run in 21 | parallel as separate threads. 22 | - Recrafted has full native support for 23 | multithreading -- custom schedulers are no 24 | longer necessary! 25 | - Multishell can be used even on standard (non- 26 | advanced) computers, using Alt+Left and 27 | Alt+Right to switch tabs. There is no 28 | dedicated Multishell program - only the API. A 29 | Multishell instance may be started at any time 30 | using multishell.launch. 31 | 32 | * These do not apply when compatibility mode is 33 | enabled - see "Compatibility Mode" below. 34 | 35 | 36 | New API Methods 37 | =============== 38 | 39 | Recrafted features a few extensions to the CraftOS 40 | APIs: 41 | 42 | - textutils.coloredWrite(...): 43 | Similar to textutils.tabulate(), but takes 44 | strings instead of tables and doesn't tabulate 45 | its arguments. Useful for easy printing of 46 | colorized text. Returns the number of lines 47 | written. 48 | 49 | - textutils.coloredPrint(...): 50 | Like textutils.coloredWrite(), but prints an 51 | extra newline at the end of the text, similar 52 | to print(). 53 | 54 | - Tables given to textutils.tabulate() may 55 | contain tables, along with strings and numbers; 56 | if a table is present, it must contain a set of 57 | arguments suitable for passing to 58 | textutils.coloredWrite(). 59 | 60 | - Recrafted's paintutils API uses the BIMG (Blit 61 | Image) format for its images. This format 62 | supports animations and lots of other useful 63 | metadata, plus combining text and images. 64 | See https://github.com/SkyTheCodeMaster/bimg 65 | for details on the format. 66 | 67 | 68 | The Multishell foreground thread management 69 | functions should only be used when absolutely 70 | necessary. If they ARE necessary, these should be 71 | used instead of the corresponding thread API 72 | functions regardless of whether multishell is 73 | actually enabled, to ensure proper behavior when it 74 | is enabled. 75 | 76 | The foreground thread is the only thread that will 77 | respond to terminate events. Ensuring that it is 78 | set correctly is therefore quite important. Under 79 | most circumstances you should not need to use these 80 | functions, since shell.run() uses them behind the 81 | scenes. 82 | 83 | These should not be confused with Multishell's 84 | getFocus() and setFocus() functions, which manage 85 | the focused tab. 86 | 87 | - multishell.getForeground(): 88 | Returns the foreground thread ID of the current 89 | tab. 90 | 91 | - multishell.pushForeground(pid): 92 | Adds a thread to the current tab's foreground 93 | stack; the given thread will be removed when 94 | it exits. 95 | 96 | - multishell.switchForeground(pid): 97 | Changes the top entry of the current tab's 98 | foreground stack; removes the old entry. 99 | 100 | - multishell.launch()'s first argument, the 101 | environment, is optional and may be completely 102 | omitted. 103 | 104 | 105 | Compatibility Mode 106 | ================== 107 | 108 | When the bios.compat_mode setting is set, Recrafted 109 | will enter CraftOS compatibility mode. This 110 | disables strict global checking and places all 111 | relevant functions and APIs into _G. In 112 | compatibility mode, os.version() returns 113 | "CraftOS 1.8" rather than the current Recrafted 114 | version. 115 | 116 | This mode should only be used when necessary. New 117 | programs should use proper Lua coding conventions 118 | and therefore work without it. 119 | -------------------------------------------------------------------------------- /installer.lua: -------------------------------------------------------------------------------- 1 | -- Recrafted installer 2 | 3 | local DEFAULT_ROM_DIR = "/rc" 4 | print("Downloading required libraries...") 5 | 6 | local function dl(f) 7 | local hand, err = http.get(f, nil, true) 8 | if not hand then 9 | error(err, 0) 10 | end 11 | 12 | local data = hand.readAll() 13 | hand.close() 14 | 15 | return data 16 | end 17 | 18 | -- set up package.loaded for Recrafted libs 19 | package.loaded.rc = { 20 | expect = require("cc.expect").expect, 21 | write = write, sleep = sleep 22 | } 23 | 24 | package.loaded.term = term 25 | package.loaded.colors = colors 26 | _G.require = require 27 | 28 | function term.at(x, y) 29 | term.setCursorPos(x, y) 30 | return term 31 | end 32 | 33 | local function ghload(f, c) 34 | return assert(load(dl("https://raw.githubusercontent.com/"..f), 35 | "="..(c or f), "t", _G))() 36 | end 37 | 38 | local json = ghload("rxi/json.lua/master/json.lua", "ghload(json)") 39 | package.loaded["rc.json"] = json 40 | 41 | local function rcload(f) 42 | return ghload( 43 | "ocawesome101/recrafted/primary/data/computercraft/lua/rom/"..f, f) 44 | end 45 | 46 | -- get recrafted's textutils with its extra utilities 47 | local tu = rcload("apis/textutils.lua") 48 | 49 | local function progress(y, a, b) 50 | local progress = a/b 51 | 52 | local w = term.getSize() 53 | local bar = (" "):rep(math.ceil((w-2) * progress)) 54 | term.at(1, y) 55 | tu.coloredPrint(colors.yellow, "[", {fg=colors.white, bg=colors.white}, bar, 56 | {fg=colors.white, bg=colors.black}, (" "):rep((w-2)-#bar), 57 | colors.yellow, "]") 58 | end 59 | 60 | term.at(1,1).clear() 61 | tu.coloredPrint(colors.yellow, 62 | "Recrafted Installer 1.0\n=======================") 63 | 64 | local ROM_DIR 65 | tu.coloredPrint("Enter installation directory ", colors.yellow, "[", 66 | colors.lightBlue, DEFAULT_ROM_DIR, colors.yellow, "]") 67 | tu.coloredWrite(colors.yellow, "> ") 68 | 69 | ROM_DIR = read() 70 | if #ROM_DIR == 0 then ROM_DIR = DEFAULT_ROM_DIR end 71 | 72 | ROM_DIR = "/"..shell.resolve(ROM_DIR) 73 | 74 | settings.set("recrafted.rom_dir", ROM_DIR) 75 | settings.save() 76 | 77 | tu.coloredPrint(colors.white, "Installing Recrafted to ", colors.lightBlue, 78 | ROM_DIR, colors.white) 79 | 80 | local function bullet(t) 81 | tu.coloredWrite(colors.red, "- ", colors.white, t) 82 | end 83 | 84 | local function ok() 85 | tu.coloredPrint(colors.green, "OK", colors.white) 86 | end 87 | 88 | bullet("Getting repository tree...") 89 | 90 | local repodata = dl("https://api.github.com/repos/ocawesome101/recrafted/git/trees/primary?recursive=1") 91 | 92 | repodata = json.decode(repodata) 93 | 94 | ok() 95 | 96 | bullet("Filtering files...") 97 | local look = "data/computercraft/lua/" 98 | local to_dl = {} 99 | for _, v in pairs(repodata.tree) do 100 | if v.path and v.path:sub(1,#look) == look then 101 | v.path = v.path:sub(#look+1) 102 | v.real_path = v.path:gsub("^/?rom", ROM_DIR) 103 | to_dl[#to_dl+1] = v 104 | end 105 | end 106 | ok() 107 | 108 | bullet("Creating directories...") 109 | for i=#to_dl, 1, -1 do 110 | local v = to_dl[i] 111 | if v.type == "tree" then 112 | fs.makeDir(fs.combine(v.real_path)) 113 | table.remove(to_dl, i) 114 | end 115 | end 116 | ok() 117 | 118 | bullet("Downloading files...") 119 | local okx, oky = term.getCursorPos() 120 | io.write("\n") 121 | local _, pby = term.getCursorPos() 122 | 123 | local parallels = {} 124 | local done = 0 125 | 126 | for i=1, #to_dl, 1 do 127 | local v = to_dl[i] 128 | if v.type == "blob" then 129 | parallels[#parallels+1] = function() 130 | local data = dl("https://raw.githubusercontent.com/ocawesome101/recrafted/primary/data/computercraft/lua/"..v.path) 131 | assert(io.open(v.real_path, "w")):write(data):close() 132 | done = done + 1 133 | progress(pby, done, #to_dl) 134 | end 135 | end 136 | end 137 | 138 | parallel.waitForAll(table.unpack(parallels)) 139 | 140 | term.at(1, pby).write((" "):rep((term.getSize()))) 141 | term.at(okx, oky) 142 | ok() 143 | 144 | assert(io.open( 145 | fs.exists("/startup.lua") and "/unbios-rc.lua" or "/startup.lua", "w")) 146 | :write(dl( 147 | "https://raw.githubusercontent.com/ocawesome101/recrafted/primary/unbios.lua" 148 | )):close() 149 | 150 | tu.coloredPrint(colors.yellow, "Your computer will restart in 5 seconds.") 151 | local _, y = term.getCursorPos() 152 | 153 | for i=1, 5, 1 do 154 | progress(y, i, 5) 155 | os.sleep(1) 156 | end 157 | 158 | os.reboot() 159 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/apis/paintutils.lua: -------------------------------------------------------------------------------- 1 | -- rc.paintutils 2 | 3 | local term = require("term") 4 | local expect = require("cc.expect").expect 5 | local textutils = require("textutils") 6 | 7 | local p = {} 8 | 9 | function p.parseImage(str) 10 | expect(1, str, "string") 11 | return textutils.unserialize(str) 12 | end 13 | 14 | function p.loadImage(path) 15 | expect(1, path, "string") 16 | local handle = io.open(path) 17 | if handle then 18 | local data = handle:read("a") 19 | handle:close() 20 | return p.parseImage(data) 21 | end 22 | end 23 | 24 | function p.drawPixel(x, y, color) 25 | expect(1, x, "number") 26 | expect(2, y, "number") 27 | expect(2, color, "number", "nil") 28 | if color then term.setBackgroundColor(color) end 29 | term.at(x, y).write(" ") 30 | end 31 | 32 | local function drawSteep(x0, y0, x1, y1, color) 33 | local distX = x1 - x0 34 | local distY = y1 - y0 35 | 36 | local diff = 2*distX - distY 37 | 38 | local x = x0 39 | for y=y0, y1, 1 do 40 | p.drawPixel(x, y, color) 41 | if diff > 0 then 42 | x = x + 1 43 | diff = diff + 2 * (distX - distY) 44 | else 45 | diff = diff + 2*distX 46 | end 47 | end 48 | end 49 | 50 | local function drawShallow(x0, y0, x1, y1, color) 51 | local distX, distY = x1 - x0, y1 - y0 52 | local diff = 2*distY - distX 53 | local y = y0 54 | 55 | for x=x0, x1, 1 do 56 | p.drawPixel(x, y, color) 57 | if diff > 0 then 58 | y = y + 1 59 | diff = diff - 2*distX 60 | end 61 | diff = diff + 2*distY 62 | end 63 | end 64 | 65 | 66 | function p.drawLine(_startX, _startY, _endX, _endY, color) 67 | expect(1, _startX, "number") 68 | expect(2, _startY, "number") 69 | expect(3, _endX, "number") 70 | expect(4, _endY, "number") 71 | expect(5, color, "number") 72 | local startX, startY, endX, endY = 73 | math.min(_startX, _endX), math.min(_startY, _endY), 74 | math.max(_startX, _endX), math.max(_startY, _endY) 75 | 76 | if startX == endX and startY == endY then 77 | return p.drawPixel(startX, startY, color) 78 | elseif startX == endX then 79 | if color then term.setBackgroundColor(color) end 80 | for y=startY, endY, 1 do 81 | term.at(startX, y).write(" ") 82 | end 83 | elseif startY == endY then 84 | if color then term.setBackgroundColor(color) end 85 | term.at(startX, startY).write((" "):rep(endX - startX)) 86 | end 87 | 88 | if (endY - startY) < (endX - startX) then 89 | drawShallow(startX, startY, endX, endY) 90 | else 91 | drawSteep(startX, startY, endX, endY) 92 | end 93 | end 94 | 95 | function p.drawBox(startX, startY, endX, endY, color) 96 | expect(1, startX, "number") 97 | expect(2, startY, "number") 98 | expect(3, endX, "number") 99 | expect(4, endY, "number") 100 | expect(5, color, "number") 101 | 102 | local col = string.format("%x", math.floor(math.log(color, 2))) 103 | local ht, hc = (" "):rep(endX-startX+1), col:rep(endX-startX+1) 104 | 105 | term.at(startX, startY).blit(ht, hc, hc) 106 | for i=startY, endY do 107 | term.at(startX, i).blit(" ", col, col) 108 | term.at(endX, i).blit(" ", col, col) 109 | end 110 | term.at(startX, endY).blit(ht, hc, hc) 111 | end 112 | 113 | function p.drawFilledBox(startX, startY, endX, endY, color) 114 | expect(1, startX, "number") 115 | expect(2, startY, "number") 116 | expect(3, endX, "number") 117 | expect(4, endY, "number") 118 | expect(5, color, "number") 119 | 120 | local col = string.format("%x", math.floor(math.log(color, 2))) 121 | local ht, hc = (" "):rep(endX-startX+1), col:rep(endX-startX+1) 122 | 123 | for y=startY, endY, 1 do 124 | term.at(startX, y).blit(ht, hc, hc) 125 | end 126 | end 127 | 128 | function p.drawImage(img, x, y, frame) 129 | expect(1, img, "table") 130 | expect(2, x, "number") 131 | expect(3, y, "number") 132 | expect(4, frame, "number", "nil") 133 | 134 | frame = frame or 1 135 | if not img[frame] then 136 | return nil, "invalid frame index " .. frame 137 | end 138 | 139 | if img.palette then 140 | for k, v in pairs(img.palette) do 141 | term.setPaletteColor(k, table.unpack(v)) 142 | end 143 | end 144 | 145 | if img[frame].palette then 146 | for k, v in pairs(img[frame].palette) do 147 | term.setPaletteColor(k, table.unpack(v)) 148 | end 149 | end 150 | 151 | for i, line in ipairs(img[frame]) do 152 | term.at(x+i-1, y).blit(table.unpack(line)) 153 | end 154 | 155 | return true 156 | end 157 | 158 | return p 159 | -------------------------------------------------------------------------------- /updater.lua: -------------------------------------------------------------------------------- 1 | -- Recrafted updater: stage 2 2 | 3 | local fs = rawget(_G, "fs") 4 | local term = rawget(_G, "term") 5 | local http = rawget(_G, "http") 6 | 7 | _G._RC_ROM_DIR = _RC_ROM_DIR or "/rc" 8 | if _RC_ROM_DIR == "/rom" then _RC_ROM_DIR = "/rc" end 9 | 10 | -- fail-safe 11 | local start_rc = [[ 12 | 13 | local fs = rawget(_G, "fs") 14 | local term = rawget(_G, "term") 15 | 16 | local w, h = term.getSize() 17 | local function at(x,y) 18 | term.setCursorPos(x,y) 19 | return term 20 | end 21 | 22 | term.setBackgroundColor(0x1) 23 | at(1,1).clearLine() 24 | at(1,h).clearLine() 25 | 26 | local title = "Recrafted Updater (Failure Notice)" 27 | term.setTextColor(0x4000) 28 | at(math.floor(w/2-#title/2), 1).write(title) 29 | 30 | for i=2, h-1, 1 do 31 | term.setBackgroundColor(0x4000) 32 | at(1,i).clearLine() 33 | end 34 | 35 | term.setTextColor(0x1) 36 | local message = { 37 | "A Recrafted update has failed or", 38 | "been interrupted. Your files are", 39 | "intact.", 40 | "", 41 | "Press any key to revert to the ROM.", 42 | "", 43 | "", 44 | } 45 | 46 | for i=1, #message, 1 do 47 | at(3, i+2).write(message[i]) 48 | end 49 | 50 | term.setCursorBlink(true) 51 | 52 | repeat local x = coroutine.yield() until x == "char" 53 | 54 | pcall(fs.delete, _RC_ROM_DIR) 55 | pcall(fs.delete, "/.start_rc.lua") 56 | 57 | os.reboot() 58 | while true do coroutine.yield() end 59 | 60 | ]] 61 | 62 | local function at(x,y) 63 | term.setCursorPos(x,y) 64 | return term 65 | end 66 | 67 | local handle = fs.open("/.start_rc.lua", "w") 68 | handle.write(start_rc) 69 | handle.close() 70 | 71 | assert(pcall(function() 72 | local w, h = term.getSize() 73 | 74 | local function dl(f) 75 | local hand, err = http.request(f, nil, nil, true) 76 | local evt 77 | repeat 78 | evt = table.pack(coroutine.yield()) 79 | until evt[1] == "http_success" or evt[1] == "http_failure" 80 | 81 | if evt[1] == "http_failure" then 82 | term.at(1, h).write(evt[3]) 83 | 84 | local id = os.startTimer(5) 85 | repeat local _,i = coroutine.yield() until i == id 86 | 87 | os.reboot() 88 | while true do coroutine.yield() end 89 | 90 | else 91 | hand = evt[3] 92 | 93 | local data = hand.readAll() 94 | hand.close() 95 | 96 | return data 97 | end 98 | end 99 | 100 | local function ghload(f, c) 101 | return assert(loadstring(dl("https://raw.githubusercontent.com/"..f), 102 | "=ghload("..(c or f)..")"))() 103 | end 104 | 105 | local json = ghload("rxi/json.lua/master/json.lua", "json") 106 | 107 | local function header() 108 | term.setTextColor(0x10) 109 | at(1, 1).clearLine() 110 | at(1, 1).write("Recrafted Updater (Stage 2)") 111 | at(1, 2).clearLine() 112 | at(1, 2).write("===========================") 113 | term.setTextColor(0x1) 114 | end 115 | 116 | local y = 1 117 | local function write(text) 118 | if y > h-3 then 119 | term.scroll(1) 120 | header() 121 | else 122 | y = y + 1 123 | end 124 | at(1, y+2).write(text) 125 | end 126 | 127 | header() 128 | 129 | write("Getting repository tree...") 130 | 131 | local repodata = json.decode(dl("https://api.github.com/repos/ocawesome101/recrafted/git/trees/primary?recursive=1")) 132 | 133 | write("Filtering files...") 134 | 135 | local look = "data/computercraft/lua/" 136 | local to_dl = {} 137 | for _, v in pairs(repodata.tree) do 138 | if v.path and v.path:sub(1,#look) == look then 139 | v.path = v.path:sub(#look+1) 140 | v.real_path = v.path:gsub("^/?rom", _RC_ROM_DIR) 141 | to_dl[#to_dl+1] = v 142 | end 143 | end 144 | 145 | write("Creating directories...") 146 | 147 | for i=#to_dl, 1, -1 do 148 | local v = to_dl[i] 149 | if v.type == "tree" then 150 | fs.makeDir(fs.combine(v.real_path)) 151 | table.remove(to_dl, i) 152 | end 153 | end 154 | 155 | write("Downloading files...") 156 | 157 | local function progress(a, b) 158 | at(1, 3).clearLine() 159 | term.setBackgroundColor(0x1) 160 | at(1, 3).write((" "):rep(math.ceil((w-2) * (a/b)))) 161 | term.setBackgroundColor(0x8000) 162 | end 163 | 164 | for i=1, #to_dl do 165 | local v = to_dl[i] 166 | if v.type == "blob" and v.real_path ~= "unbios.lua" then 167 | local data = dl("https://raw.githubusercontent.com/ocawesome101/recrafted/primary/data/computercraft/lua/"..v.path) 168 | write(v.real_path) 169 | progress(i, #to_dl) 170 | if v.real_path == "bios.lua" then 171 | v.real_path = "/.start_rc.lua" 172 | end 173 | local handle = fs.open(v.real_path, "w") 174 | handle.write(data) 175 | handle.close() 176 | end 177 | end 178 | 179 | os.reboot() 180 | while true do coroutine.yield() end 181 | 182 | end)) 183 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/startup/30_peripheral.lua: -------------------------------------------------------------------------------- 1 | -- rc.peripheral 2 | 3 | local old = require("peripheral") 4 | local expect = require("cc.expect").expect 5 | 6 | local p = {} 7 | package.loaded.peripheral = p 8 | 9 | local sides = {"bottom", "top", "left", "right", "front", "back"} 10 | 11 | function p.getNames() 12 | local names = {} 13 | 14 | for i=1, #sides, 1 do 15 | local side = sides[i] 16 | if old.isPresent(side) then 17 | names[#names+1] = side 18 | 19 | if old.hasType(side, "modem") and not old.call(side, "isWireless") then 20 | local remote_names = old.call(side, "getNamesRemote") 21 | for j=1, #remote_names, 1 do 22 | names[#names+1] = remote_names[j] 23 | end 24 | end 25 | end 26 | end 27 | 28 | return names 29 | end 30 | 31 | -- figure out where a peripheral is 32 | -- returns 0 if the peripheral is directly connected, 33 | -- and a side if it's connected through a modem 34 | local function findByName(name) 35 | if old.isPresent(name) then 36 | return 0 37 | 38 | else 39 | for i=1, #sides, 1 do 40 | local side = sides[i] 41 | 42 | if old.hasType(side, "modem") and not old.call(side, "isWireless") then 43 | if old.call(side, "isPresentRemote", name) then 44 | return side 45 | end 46 | end 47 | end 48 | end 49 | end 50 | 51 | function p.isPresent(name) 52 | expect(1, name, "string") 53 | 54 | return not not findByName(name) 55 | end 56 | 57 | function p.getType(per) 58 | expect(1, per, "string", "table") 59 | 60 | if type(per) == "string" then 61 | local place = findByName(per) 62 | 63 | if place == 0 then 64 | return old.getType(per) 65 | 66 | elseif place then 67 | return old.call(place, "getTypeRemote", per) 68 | end 69 | 70 | else 71 | return table.unpack(per.__types) 72 | end 73 | end 74 | 75 | function p.hasType(per, ptype) 76 | expect(1, per, "string", "table") 77 | expect(2, ptype, "string") 78 | 79 | if type(per) == "string" then 80 | local place = findByName(per) 81 | 82 | if place == 0 then 83 | return old.hasType(per, ptype) 84 | 85 | elseif place then 86 | return old.call(place, "hasTypeRemote", per, ptype) 87 | end 88 | 89 | else 90 | return per.__types[ptype] 91 | end 92 | end 93 | 94 | function p.getMethods(name) 95 | expect(1, name, "string") 96 | 97 | local place = findByName(name) 98 | if place == 0 then 99 | return old.getMethods(name) 100 | 101 | elseif place then 102 | return old.call(place, "getMethodsRemote", name) 103 | end 104 | end 105 | 106 | function p.getName(per) 107 | expect(1, per, "table") 108 | return per.__info.name 109 | end 110 | 111 | function p.call(name, method, ...) 112 | expect(1, name, "string") 113 | expect(2, method, "string") 114 | 115 | local place = findByName(name) 116 | if place == 0 then 117 | return old.call(name, method, ...) 118 | 119 | elseif place then 120 | return old.call(place, "callRemote", name, method, ...) 121 | end 122 | end 123 | 124 | function p.wrap(name) 125 | expect(1, name, "string") 126 | 127 | local place = findByName(name) 128 | if not place then return end 129 | 130 | local methods, types 131 | if place == 0 then 132 | methods = old.getMethods(name) 133 | types = table.pack(old.getType(name)) 134 | else 135 | methods = old.call(place, "getMethodsRemote", name) 136 | types = table.pack(old.call(place, "getTypesRemote", name)) 137 | end 138 | 139 | for i=1, #types, 1 do 140 | types[types[i]] = true 141 | end 142 | 143 | local wrapper = { 144 | __info = { 145 | name = name, 146 | types = types, 147 | } 148 | } 149 | 150 | if place == 0 then 151 | for i=1, #methods, 1 do 152 | wrapper[methods[i]] = function(...) 153 | return old.call(name, methods[i], ...) 154 | end 155 | end 156 | 157 | else 158 | for i=1, #methods, 1 do 159 | wrapper[methods[i]] = function(...) 160 | return old.call(place, "callRemote", name, methods[i], ...) 161 | end 162 | end 163 | end 164 | 165 | return wrapper 166 | end 167 | 168 | function p.find(ptype, filter) 169 | expect(1, ptype, "string") 170 | expect(2, filter, "function", "nil") 171 | 172 | local wrapped = {} 173 | 174 | for _, name in ipairs(p.getNames()) do 175 | if p.hasType(name, ptype) then 176 | local wrapper = p.wrap(name) 177 | if (p.filter and p.filter(name, wrapper)) or not p.filter then 178 | wrapped[#wrapped+1] = wrapper 179 | end 180 | end 181 | end 182 | 183 | return table.unpack(wrapped) 184 | end 185 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/startup/35_http.lua: -------------------------------------------------------------------------------- 1 | -- HTTP library adapted from .OS -- 2 | 3 | -- native http.request: function( 4 | -- url:string[, post:string[, headers:table[, binarymode:boolean]]]) 5 | -- post is the data to POST. otherwise a GET is sent. 6 | -- OR: function(parameters:table) 7 | -- where parameters = { 8 | -- url = string, -- the URL 9 | -- body = string, -- the data to POST/PATCH/PUT 10 | -- headers = table, -- request headers 11 | -- binary = boolean, -- self explanatory 12 | -- method = string} -- the HTTP method to use - one of: 13 | -- - GET 14 | -- - POST 15 | -- - HEAD 16 | -- - OPTIONS 17 | -- - PUT 18 | -- - DELETE 19 | -- - PATCH 20 | -- - TRACE 21 | -- 22 | -- native http.checkURL: function(url:string) 23 | -- url is a URL to try to reach. queues a http_check event with the result. 24 | -- native http.websocket(url:string[, headers:table]) 25 | -- url is the url to which to open a websocket. queues a websocket_success 26 | -- event on success, and websocket_failure on failure. 27 | -- native http.addListener(port:number) (CraftOS-PC only) 28 | -- add a listener on the specified port. when that port receives data, 29 | -- the listener queues a http_request(port:number, request, response). 30 | -- !!the response is not send until response.close() is called!! 31 | -- native http.removeListener(port:number) (CraftOS-PC only) 32 | -- remove the listener from that port 33 | 34 | local rc = ... 35 | 36 | if not package.loaded.http then 37 | return 38 | end 39 | 40 | local old = package.loaded.http 41 | 42 | local http = {} 43 | 44 | package.loaded.http = http 45 | 46 | local field = require("cc.expect").field 47 | local expect = require("cc.expect").expect 48 | 49 | local function listenForResponse(url) 50 | while true do 51 | local sig, a, b, c = rc.pullEvent() 52 | if sig == "http_success" and a == url then 53 | return b 54 | elseif sig == "http_failure" and a == url then 55 | return nil, b, c 56 | end 57 | end 58 | end 59 | 60 | function http.request(url, post, headers, binary, method, sync) 61 | if type(url) ~= "table" then 62 | url = { 63 | url = url, 64 | body = post, 65 | headers = headers, 66 | binary = binary, 67 | method = method or (post and "POST") or "GET", 68 | sync = not not sync 69 | } 70 | end 71 | 72 | field(url, "url", "string") 73 | field(url, "body", "string", "nil") 74 | field(url, "headers", "table", "nil") 75 | field(url, "binary", "boolean", "nil") 76 | field(url, "method", "string") 77 | field(url, "sync", "boolean", "nil") 78 | 79 | local ok, err = old.request(url) 80 | if not ok then 81 | return nil, err 82 | end 83 | 84 | if sync then return listenForResponse(url.url) end 85 | end 86 | 87 | function http.get(url, headers, binary) 88 | if type(url) == "table" then 89 | url.sync = true 90 | return http.request(url) 91 | else 92 | return http.request(url, nil, headers, binary, "GET", true) 93 | end 94 | end 95 | 96 | function http.post(url, body, headers, binary) 97 | if type(url) == "table" then 98 | url.sync = true 99 | url.method = "POST" 100 | return http.request(url) 101 | else 102 | return http.request(url, body, headers, binary, "POST", true) 103 | end 104 | end 105 | 106 | http.checkURLAsync = old.checkURL 107 | 108 | function http.checkURL(url) 109 | expect(1, url, "string") 110 | 111 | local ok, err = old.checkURL(url) 112 | if not ok then 113 | return nil, err 114 | end 115 | 116 | local sig, _url, a, b 117 | repeat 118 | sig, _url, a, b = coroutine.yield() 119 | until sig == "http_check" and url == _url 120 | 121 | return a, b 122 | end 123 | 124 | http.websocketAsync = old.websocket 125 | 126 | function http.websocket(url, headers) 127 | expect(1, url, "string") 128 | expect(2, headers, "string") 129 | 130 | local ok, err = old.websocket(url, headers) 131 | if not ok then 132 | return nil, err 133 | end 134 | 135 | while true do 136 | local sig, a, b, c = coroutine.yield() 137 | if sig == "websocket_success" and a == url then 138 | return b, c 139 | elseif sig == "websocket_failure" and a == url then 140 | return nil, b 141 | end 142 | end 143 | end 144 | 145 | if old.addListener then 146 | function http.listen(port, callback) 147 | expect(1, port, "number") 148 | expect(2, callback, "function") 149 | old.addListener(port) 150 | 151 | while true do 152 | local sig, a, b, c = coroutine.yield() 153 | if sig == "stop_listener" and a == port then 154 | old.removeListener(port) 155 | break 156 | elseif sig == "http_request" and a == port then 157 | if not callback(b, c) then 158 | old.removeListener(port) 159 | break 160 | end 161 | end 162 | end 163 | end 164 | else 165 | function http.listen() 166 | error("This functionality requires CraftOS-PC", 0) 167 | end 168 | end 169 | -------------------------------------------------------------------------------- /data/computercraft/lua/bios.lua: -------------------------------------------------------------------------------- 1 | _G._HOST = _G._HOST .. " (Recrafted 1.6.0)" 2 | 3 | local fs = rawget(_G, "fs") 4 | 5 | _G._RC_ROM_DIR = _RC_ROM_DIR or (...) and fs.exists("/rc") and "/rc" or "/rom" 6 | 7 | if fs.exists("/.start_rc.lua") and not (...) then 8 | _G._RC_USED_START = true 9 | local handle = assert(fs.open("/.start_rc.lua", "r")) 10 | local data = handle.readAll() 11 | handle.close() 12 | 13 | local _sd = rawget(os, "shutdown") 14 | local ld = rawget(_G, "loadstring") or load 15 | assert(ld(data, "=start_rc"))(true) 16 | _sd() 17 | while true do coroutine.yield() end 18 | end 19 | 20 | local function pull(tab, key) 21 | local func = tab[key] 22 | tab[key] = nil 23 | return func 24 | end 25 | 26 | -- this is overwritten further down but `load` needs it 27 | local expect = function(_, _, _, _) end 28 | 29 | local shutdown = pull(os, "shutdown") 30 | local reboot = pull(os, "reboot") 31 | 32 | -- `os` extras go in here now. 33 | local rc = { 34 | _NAME = "Recrafted", 35 | _VERSION = { 36 | major = 1, 37 | minor = 6, 38 | patch = 0 39 | }, 40 | queueEvent = pull(os, "queueEvent"), 41 | startTimer = pull(os, "startTimer"), 42 | cancelTimer = pull(os, "cancelTimer"), 43 | setAlarm = pull(os, "setAlarm"), 44 | cancelAlarm = pull(os, "cancelAlarm"), 45 | getComputerID = pull(os, "getComputerID"), 46 | computerID = pull(os, "computerID"), 47 | getComputerLabel = pull(os, "getComputerLabel"), 48 | computerLabel = pull(os, "computerLabel"), 49 | setComputerLabel = pull(os, "setComputerLabel"), 50 | day = pull(os, "day"), 51 | epoch = pull(os, "epoch"), 52 | } 53 | 54 | -- and a few more 55 | rc.pushEvent = rc.queueEvent 56 | 57 | function rc.shutdown() 58 | shutdown() 59 | while true do coroutine.yield() end 60 | end 61 | 62 | function rc.reboot() 63 | reboot() 64 | while true do coroutine.yield() end 65 | end 66 | 67 | local timer_filter = {} 68 | function rc.pullEventRaw(filter) 69 | expect(1, filter, "string", "nil") 70 | 71 | local sig 72 | repeat 73 | sig = table.pack(coroutine.yield()) 74 | until ((sig[1] == "timer" and 75 | timer_filter[sig[2]] == require("rc.thread").id()) or sig[1] ~= "timer") 76 | and (not filter) or (sig[1] == filter) 77 | 78 | return table.unpack(sig, 1, sig.n) 79 | end 80 | 81 | function rc.pullEvent(filter) 82 | expect(1, filter, "string", "nil") 83 | 84 | local sig 85 | repeat 86 | sig = table.pack(coroutine.yield()) 87 | if sig[1] == "terminate" then 88 | error("terminated", 0) 89 | end 90 | until ((sig[1] == "timer" and 91 | timer_filter[sig[2]] == require("rc.thread").id()) or sig[1] ~= "timer") 92 | and (not filter) or (sig[1] == filter) 93 | 94 | return table.unpack(sig, 1, sig.n) 95 | end 96 | 97 | function rc.sleep(time, no_term) 98 | local id = rc.startTimer(time) 99 | local thread = require("rc.thread").id() 100 | timer_filter[id] = thread 101 | 102 | repeat 103 | local _, tid = (no_term and rc.pullEventRaw or rc.pullEvent)("timer") 104 | until tid == id 105 | end 106 | 107 | function rc.version() 108 | return string.format("Recrafted %d.%d.%d", 109 | rc._VERSION.major, rc._VERSION.minor, rc._VERSION.patch) 110 | end 111 | 112 | -- Lua 5.1? meh 113 | if _VERSION == "Lua 5.1" then 114 | local old_load = load 115 | 116 | rc.lua51 = { 117 | loadstring = pull(_G, "loadstring"), 118 | setfenv = pull(_G, "setfenv"), 119 | getfenv = pull(_G, "getfenv"), 120 | unpack = pull(_G, "unpack"), 121 | log10 = pull(math, "log10"), 122 | maxn = pull(table, "maxn") 123 | } 124 | 125 | table.unpack = rc.lua51.unpack 126 | 127 | function _G.load(x, name, mode, env) 128 | expect(1, x, "string", "function") 129 | expect(2, name, "string", "nil") 130 | expect(3, mode, "string", "nil") 131 | expect(4, env, "table", "nil") 132 | 133 | env = env or _G 134 | 135 | local result, err 136 | if type(x) == "string" then 137 | result, err = rc.lua51.loadstring(x, name) 138 | else 139 | result, err = old_load(x, name) 140 | end 141 | 142 | if result then 143 | env._ENV = env 144 | rc.lua51.setfenv(result, env) 145 | end 146 | 147 | return result, err 148 | end 149 | 150 | -- Lua 5.1's xpcall sucks 151 | local old_xpcall = xpcall 152 | function _G.xpcall(call, func, ...) 153 | local args = table.pack(...) 154 | return old_xpcall(function() 155 | return call(table.unpack(args, 1, args.n)) 156 | end, func) 157 | end 158 | end 159 | 160 | local startup = _RC_ROM_DIR .. "/startup" 161 | local files = fs.list(startup) 162 | table.sort(files) 163 | 164 | function _G.loadfile(file) 165 | local handle, err = fs.open(file, "r") 166 | if not handle then 167 | return nil, err 168 | end 169 | 170 | local data = handle.readAll() 171 | handle.close() 172 | 173 | return load(data, "="..file, "t", _G) 174 | end 175 | 176 | function _G.dofile(file) 177 | return assert(loadfile(file))() 178 | end 179 | 180 | for i=1, #files, 1 do 181 | local file = startup .. "/" .. files[i] 182 | assert(loadfile(file))(rc) 183 | end 184 | 185 | expect = require("cc.expect").expect 186 | 187 | local thread = require("rc.thread") 188 | 189 | thread.start() 190 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/startup/00_fs.lua: -------------------------------------------------------------------------------- 1 | -- override the fs library to use this resolution function where necessary 2 | -- almost identical to the override used in .OS 3 | 4 | local fs = rawget(_G, "fs") 5 | 6 | -- split a file path into segments 7 | function fs.split(path) 8 | local s = {} 9 | for S in path:gmatch("[^/\\]+") do 10 | if S == ".." then 11 | s[#s] = nil 12 | elseif S ~= "." then 13 | s[#s+1] = S 14 | end 15 | end 16 | return s 17 | end 18 | 19 | -- package isn't loaded yet, so unfortunately this is necessary 20 | local function expect(...) 21 | return require and require("cc.expect").expect(...) 22 | end 23 | 24 | -- path resolution: 25 | -- if the path begins with /rc, then redirect to wherever that actually 26 | -- is; otherwise, resolve the path based on the current program's working 27 | -- directory 28 | -- this is to allow .OS to run from anywhere 29 | local function resolve(path) 30 | local thread = package and package.loaded.thread 31 | 32 | local root = (thread and thread.getroot()) or "/" 33 | local pwd = (thread and thread.dir()) or "/" 34 | 35 | if path:sub(1,1) ~= "/" then 36 | path = fs.combine(pwd, path) 37 | end 38 | path = fs.combine(root, path) 39 | 40 | local segments = fs.split(path) 41 | if segments[1] == "rc" then 42 | return fs.combine(_RC_ROM_DIR, table.concat(segments, "/", 2, #segments)) 43 | else 44 | return path 45 | end 46 | end 47 | 48 | -- override: fs.combine 49 | local combine = fs.combine 50 | function fs.combine(...) 51 | return "/" .. combine(...) 52 | end 53 | 54 | -- override: fs.getDir 55 | local getDir = fs.getDir 56 | function fs.getDir(p) 57 | return "/" .. getDir(p) 58 | end 59 | 60 | -- override: fs.exists 61 | local exists = fs.exists 62 | function fs.exists(path) 63 | expect(1, path, "string") 64 | return exists(resolve(path)) 65 | end 66 | 67 | -- override: fs.list 68 | local list = fs.list 69 | function fs.list(path) 70 | expect(1, path, "string") 71 | path = resolve(path) 72 | local _, files = pcall(list, path) 73 | if not _ then return nil, files end 74 | if path == "/" then 75 | -- inject /rc into the root listing 76 | if not exists("/rc") then 77 | files[#files+1] = "rc" 78 | end 79 | end 80 | return files 81 | end 82 | 83 | -- override: fs.getSize 84 | local getSize = fs.getSize 85 | function fs.getSize(path) 86 | expect(1, path, "string") 87 | return getSize((resolve(path))) 88 | end 89 | 90 | -- override: fs.isDir 91 | local isDir = fs.isDir 92 | function fs.isDir(path) 93 | expect(1, path, "string") 94 | return isDir(resolve(path)) 95 | end 96 | 97 | -- override: fs.makeDir 98 | local makeDir = fs.makeDir 99 | function fs.makeDir(path) 100 | expect(1, path, "string") 101 | return makeDir(resolve(path)) 102 | end 103 | 104 | -- override: fs.move 105 | local move = fs.move 106 | function fs.move(a, b) 107 | expect(1, a, "string") 108 | expect(2, b, "string") 109 | return move(resolve(a), resolve(b)) 110 | end 111 | 112 | -- override: fs.copy 113 | local copy = fs.copy 114 | function fs.copy(a, b) 115 | expect(1, a, "string") 116 | expect(2, b, "string") 117 | return copy(resolve(a), resolve(b)) 118 | end 119 | 120 | -- override: fs.delete 121 | local delete = fs.delete 122 | function fs.delete(path) 123 | expect(1, path, "string") 124 | return delete(resolve(path)) 125 | end 126 | 127 | -- override: fs.open 128 | local open = fs.open 129 | function fs.open(file, mode) 130 | expect(1, file, "string") 131 | expect(2, mode, "string") 132 | return open(resolve(file), mode or "r") 133 | end 134 | 135 | -- override: fs.find 136 | local find = fs.find 137 | function fs.find(path) 138 | expect(1, path, "string") 139 | return find(resolve(path)) 140 | end 141 | 142 | -- override: fs.attributes 143 | local attributes = fs.attributes 144 | function fs.attributes(path) 145 | expect(1, path, "string") 146 | return attributes(resolve(path)) 147 | end 148 | 149 | -- new: fs.complete 150 | function fs.complete(path, location, include_files, include_dirs) 151 | expect(1, path, "string") 152 | expect(2, location, "string") 153 | expect(3, include_files, "boolean", "nil") 154 | expect(4, include_dirs, "boolean", "nil") 155 | 156 | if include_files == nil then include_files = true end 157 | if include_dirs == nil then include_dirs = true end 158 | 159 | if path:sub(1,1) == "/" and path:sub(-1) ~= "/" then 160 | location = fs.getDir(path) 161 | elseif path:sub(-1) == "/" then 162 | location = path 163 | else 164 | location = fs.combine(location, fs.getDir(path)) 165 | end 166 | 167 | local completions = {} 168 | 169 | if not fs.exists(location) or not fs.isDir(location) then 170 | return completions 171 | end 172 | 173 | local name = fs.getName(path) 174 | if path:sub(-1) == "/" then name = "" end 175 | local files = fs.list(location) 176 | 177 | for i=1, #files, 1 do 178 | local file = files[i] 179 | local full = fs.combine(location, file) 180 | if file:sub(1, #name) == name then 181 | local dir = fs.isDir(full) 182 | if (dir and include_dirs) or include_files then 183 | completions[#completions+1] = file:sub(#name+1) 184 | if #completions[#completions] == 0 then 185 | completions[#completions] = nil 186 | end 187 | end 188 | if dir then 189 | completions[#completions+1] = file:sub(#name+1) .. "/" 190 | end 191 | end 192 | end 193 | 194 | return completions 195 | end 196 | 197 | function fs.isDriveRoot(path) 198 | expect(1, path, "string") 199 | if #path == 0 then path = "/" end 200 | return path == "/" or fs.getDrive(path) == fs.getDrive(fs.getDir(path)) 201 | end 202 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/modules/main/rc/io.lua: -------------------------------------------------------------------------------- 1 | -- rc.io 2 | 3 | local expect = require("cc.expect").expect 4 | local thread = require("rc.thread") 5 | local colors = require("colors") 6 | local term = require("term") 7 | local fs = require("fs") 8 | local rc = require("rc") 9 | 10 | local io = {} 11 | 12 | local _file = {} 13 | function _file:read(...) 14 | local args = table.pack(...) 15 | local ret = {} 16 | 17 | if args.n == 0 then 18 | args[1] = "l" 19 | args.n = 1 20 | end 21 | 22 | if not (self.handle.read and pcall(self.handle.read, 0)) then 23 | return nil, "bad file descriptor" 24 | end 25 | 26 | if self.handle.flush then self.handle.flush() end 27 | 28 | for i=1, args.n, 1 do 29 | local format = args[i] 30 | if format:sub(1,1) == "*" then 31 | format = format:sub(2) 32 | end 33 | 34 | if format == "a" then 35 | ret[#ret+1] = self.handle.readAll() 36 | 37 | elseif format == "l" or format == "L" then 38 | ret[#ret+1] = self.handle.readLine(format == "L") 39 | 40 | elseif type(format) == "number" then 41 | ret[#ret+1] = self.handle.read(format) 42 | 43 | else 44 | error("invalid format '"..format.."'", 2) 45 | end 46 | end 47 | 48 | return table.unpack(ret, 1, args.n) 49 | end 50 | 51 | function _file:lines(...) 52 | local formats = {...} 53 | if #formats == 0 then 54 | formats[1] = "l" 55 | end 56 | return function() 57 | return self:read(table.unpack(formats)) 58 | end 59 | end 60 | 61 | function _file:write(...) 62 | local args = table.pack(...) 63 | 64 | if not (self.handle.write and pcall(self.handle.write, "")) then 65 | return nil, "bad file descriptor" 66 | end 67 | 68 | for i=1, args.n, 1 do 69 | self.handle.write(args[i]) 70 | end 71 | 72 | return self 73 | end 74 | 75 | function _file:seek(whence, offset) 76 | expect(1, whence, "string", "nil") 77 | expect(2, offset, "number", "nil") 78 | if self.handle.seek then 79 | return self.handle.seek(whence, offset) 80 | else 81 | return nil, "bad file descriptor" 82 | end 83 | end 84 | 85 | function _file:flush() 86 | if self.handle.flush then self.handle.flush() end 87 | return self 88 | end 89 | 90 | function _file:close() 91 | self.closed = true 92 | pcall(self.handle.close) 93 | end 94 | 95 | local function iofile(handle) 96 | return setmetatable({handle = handle, closed = false}, {__index = _file}) 97 | end 98 | 99 | local stdin_rbuf = "" 100 | io.stdin = iofile { 101 | read = function(n) 102 | while #stdin_rbuf < n do 103 | stdin_rbuf = stdin_rbuf .. term.read() .. "\n" 104 | end 105 | local ret = stdin_rbuf:sub(1, n) 106 | stdin_rbuf = stdin_rbuf:sub(#ret+1) 107 | return ret 108 | end, 109 | 110 | readLine = function(trail) 111 | local nl = stdin_rbuf:find("\n") 112 | 113 | if nl then 114 | local ret = stdin_rbuf:sub(1, nl+1) 115 | if not trail then ret = ret:sub(1, -2) end 116 | stdin_rbuf = stdin_rbuf:sub(#ret+1) 117 | return ret 118 | 119 | else 120 | return stdin_rbuf .. term.read() .. (trail and "\n" or "") 121 | end 122 | end 123 | } 124 | 125 | io.stdout = iofile { 126 | write = rc.write 127 | } 128 | 129 | io.stderr = iofile { 130 | write = function(text) 131 | local old = term.getTextColor() 132 | term.setTextColor(colors.red) 133 | rc.write(text) 134 | term.setTextColor(old) 135 | end 136 | } 137 | 138 | function io.open(file, mode) 139 | expect(1, file, "string") 140 | expect(2, mode, "string", "nil") 141 | 142 | mode = (mode or "r"):match("[rwa]") .. "b" 143 | 144 | local handle, err = fs.open(file, mode) 145 | if not handle then 146 | return nil, err 147 | end 148 | 149 | return iofile(handle) 150 | end 151 | 152 | function io.input(file) 153 | expect(1, file, "string", "table", "nil") 154 | local vars = thread.vars() 155 | if type(file) == "string" then file = assert(io.open(file, "r")) end 156 | if file then vars.input = file end 157 | return vars.input or io.stdin 158 | end 159 | 160 | function io.output(file) 161 | expect(1, file, "string", "table", "nil") 162 | local vars = thread.vars() 163 | if type(file) == "string" then file = assert(io.open(file, "w")) end 164 | if file then vars.output = file end 165 | return vars.output or io.stdout 166 | end 167 | 168 | function io.read(...) 169 | return io.input():read(...) 170 | end 171 | 172 | function io.write(...) 173 | return io.output():write(...) 174 | end 175 | 176 | function io.flush(file) 177 | expect(1, file, "table", "nil") 178 | return (file or io.output):flush() 179 | end 180 | 181 | function io.close(file) 182 | expect(1, file, "table", "nil") 183 | return (file or io.output):close() 184 | end 185 | 186 | function io.lines(file, ...) 187 | expect(1, file, "string", "nil") 188 | if file then file = assert(io.open(file, "r")) end 189 | local formats = table.pack(...) 190 | return (file or io.stdin):lines(table.unpack(formats, 1, formats.n)) 191 | end 192 | 193 | function io.type(obj) 194 | if type(obj) == "table" then 195 | local is_file = true 196 | for k, v in pairs(_file) do 197 | if (not obj[k]) or v ~= obj[k] then 198 | is_file = false 199 | end 200 | end 201 | 202 | if is_file then 203 | return obj.closed and "closed file" or "file" 204 | end 205 | end 206 | end 207 | 208 | -- loadfile and dofile here as well 209 | function _G.loadfile(file, mode, env) 210 | expect(1, file, "string") 211 | expect(2, mode, "string", "nil") 212 | expect(3, env, "table", "nil") 213 | local handle, err = io.open(file, "r") 214 | if not handle then 215 | return nil, file .. ": " .. err 216 | end 217 | local data = handle:read("a") 218 | handle:close() 219 | return load(data, "="..file, mode or "bt", env) 220 | end 221 | 222 | function _G.dofile(file, ...) 223 | expect(1, file, "string") 224 | local func, err = loadfile(file) 225 | if not func then 226 | error(err) 227 | end 228 | return func(...) 229 | end 230 | 231 | return io 232 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/editors/basic.lua: -------------------------------------------------------------------------------- 1 | -- editor 2 | 3 | local args = {...} 4 | 5 | local rc = require("rc") 6 | local keys = require("keys") 7 | local term = require("term") 8 | local shell = require("shell") 9 | local colors = require("colors") 10 | local settings = require("settings") 11 | local textutils = require("textutils") 12 | 13 | local scroll_offset = settings.get("edit.scroll_offset") 14 | 15 | local state = { 16 | file = args[1] or ".new", 17 | unsaved = false, 18 | scroll = 0, 19 | hscroll = 0, 20 | lines = {""}, 21 | status = "Press Control for menu", 22 | cx = 1, 23 | cy = 1, 24 | } 25 | 26 | if args[1] then 27 | local path = shell.resolve(args[1]) 28 | local handle = io.open(path) 29 | state.file = path 30 | 31 | if handle then 32 | state.lines = {} 33 | 34 | for line in handle:lines() do 35 | state.lines[#state.lines+1] = line 36 | end 37 | 38 | handle:close() 39 | 40 | if not state.lines[1] then state.lines[1] = "" end 41 | end 42 | end 43 | 44 | local function redraw() 45 | local w, h = term.getSize() 46 | 47 | for i=1, h - 1, 1 do 48 | local to_write = state.lines[state.scroll + i] or "" 49 | if state.cx > w then 50 | to_write = to_write:sub(state.cx - (w-1)) 51 | end 52 | term.at(1, i).clearLine() 53 | term.write(to_write) 54 | end 55 | 56 | term.at(1, h).clearLine() 57 | textutils.coloredWrite(colors.yellow, state.status, colors.white) 58 | 59 | term.setCursorPos(math.min(w, state.cx), state.cy - state.scroll) 60 | end 61 | 62 | local run, menu = true, false 63 | 64 | local function save() 65 | if state.file == ".new" then 66 | local _, h = term.getSize() 67 | term.setCursorPos(1, h) 68 | textutils.coloredWrite(colors.yellow, "filename: ", colors.white) 69 | state.file = term.read() 70 | end 71 | 72 | local handle, err = io.open(state.file, "w") 73 | if not handle then 74 | state.status = err 75 | else 76 | for i=1, #state.lines, 1 do 77 | handle:write(state.lines[i] .. "\n") 78 | end 79 | handle:close() 80 | state.status = "Saved to " .. state.file 81 | state.unsaved = false 82 | end 83 | end 84 | 85 | local function processMenuInput() 86 | local event, id = rc.pullEvent() 87 | 88 | if event == "char" then 89 | if id:lower() == "e" then 90 | if state.unsaved and menu ~= 2 then 91 | state.status = "Lose unsaved work? E:yes C:no" 92 | menu = 2 93 | else 94 | term.at(1, 1).clear() 95 | run = false 96 | end 97 | 98 | elseif id:lower() == "c" and menu == 2 then 99 | menu = false 100 | 101 | elseif id:lower() == "s" then 102 | save() 103 | menu = false 104 | end 105 | 106 | elseif event == "key" then 107 | id = keys.getName(id) 108 | 109 | if id == "leftCtrl" or id == "rightCtrl" then 110 | state.status = "Press Control for menu" 111 | menu = false 112 | end 113 | end 114 | end 115 | 116 | local function processInput() 117 | local event, id = rc.pullEvent() 118 | 119 | local _, h = term.getSize() 120 | 121 | if event == "char" then 122 | local line = state.lines[state.cy] 123 | state.unsaved = true 124 | if state.cx > #line then 125 | line = line .. id 126 | 127 | elseif state.cx == 1 then 128 | line = id .. line 129 | 130 | else 131 | line = line:sub(0, state.cx-1)..id..line:sub(state.cx) 132 | end 133 | state.cx = state.cx + 1 134 | 135 | state.lines[state.cy] = line 136 | 137 | elseif event == "key" then 138 | id = keys.getName(id) 139 | 140 | if id == "backspace" then 141 | local line = state.lines[state.cy] 142 | state.unsaved = true 143 | if state.cx == 1 and state.cy > 1 then 144 | local previous = table.remove(state.lines, state.cy - 1) 145 | state.cy = state.cy - 1 146 | state.cx = #previous + 1 147 | line = previous .. line 148 | else 149 | if #line > 0 then 150 | if state.cx > #line then 151 | state.cx = state.cx - 1 152 | line = line:sub(1, -2) 153 | 154 | elseif state.cx > 1 then 155 | line = line:sub(0, state.cx - 2) .. line:sub(state.cx) 156 | state.cx = state.cx - 1 157 | 158 | end 159 | 160 | end 161 | end 162 | state.lines[state.cy] = line 163 | 164 | elseif id == "enter" then 165 | if state.cx == 1 then 166 | table.insert(state.lines, state.cy, "") 167 | elseif state.cx > #state.lines[state.cy] then 168 | table.insert(state.lines, state.cy + 1, "") 169 | else 170 | local line = state.lines[state.cy] 171 | local before, after = line:sub(0, state.cx - 1), line:sub(state.cx) 172 | state.lines[state.cy] = before 173 | table.insert(state.lines, state.cy + 1, after) 174 | end 175 | 176 | state.cy = state.cy + 1 177 | state.cx = 1 178 | 179 | elseif id == "up" then 180 | if state.cy > 1 then 181 | state.cy = state.cy - 1 182 | if state.cy - state.scroll < scroll_offset then 183 | state.scroll = math.max(0, state.cy - scroll_offset) 184 | end 185 | end 186 | 187 | state.cx = math.min(state.cx, #state.lines[state.cy] + 1) 188 | 189 | elseif id == "down" then 190 | if state.cy < #state.lines then 191 | state.cy = state.cy + 1 192 | 193 | if state.cy - state.scroll > h - scroll_offset then 194 | state.scroll = math.max(0, math.min(#state.lines - h + 1, 195 | state.cy - h + scroll_offset)) 196 | end 197 | end 198 | 199 | state.cx = math.min(state.cx, #state.lines[state.cy] + 1) 200 | 201 | elseif id == "left" then 202 | if state.cx > 1 then 203 | state.cx = state.cx - 1 204 | end 205 | 206 | elseif id == "right" then 207 | if state.cx < #state.lines[state.cy] + 1 then 208 | state.cx = state.cx + 1 209 | end 210 | 211 | elseif id == "leftCtrl" or id == "rightCtrl" then 212 | state.status = "S:save E:exit" 213 | menu = true 214 | 215 | end 216 | end 217 | end 218 | 219 | term.clear() 220 | while run do 221 | term.setCursorBlink(false) 222 | redraw() 223 | term.setCursorBlink(true) 224 | if menu then 225 | processMenuInput() 226 | else 227 | processInput() 228 | end 229 | end 230 | -------------------------------------------------------------------------------- /unbios.lua: -------------------------------------------------------------------------------- 1 | -- UnBIOS by JackMacWindows 2 | -- Modified by Ocawesome101 to work with Recrafted 3 | -- This will undo most of the changes/additions made in the BIOS, but some things may remain wrapped if `debug` is unavailable 4 | -- To use, just place a `bios.lua` in the root of the drive, and run this program 5 | -- Here's a list of things that are irreversibly changed: 6 | -- * both `bit` and `bit32` are kept for compatibility 7 | -- * string metatable blocking (on old versions of CC) 8 | -- In addition, if `debug` is not available these things are also irreversibly changed: 9 | -- * old Lua 5.1 `load` function (for loading from a function) 10 | -- * `loadstring` prefixing (before CC:T 1.96.0) 11 | -- * `http.request` 12 | -- * `os.shutdown` and `os.reboot` 13 | -- * `peripheral` 14 | -- * `turtle.equip[Left|Right]` 15 | -- Licensed under the MIT license 16 | if _HOST:find("UnBIOS") then return end 17 | local keptAPIs = {bit32 = true, bit = true, ccemux = true, config = true, coroutine = true, debug = true, fs = true, http = true, io = true, mounter = true, os = true, periphemu = true, peripheral = true, redstone = true, rs = true, term = true, utf8 = true, _HOST = true, _CC_DEFAULT_SETTINGS = true, _CC_DISABLE_LUA51_FEATURES = true, _VERSION = true, assert = true, collectgarbage = true, error = true, gcinfo = true, getfenv = true, getmetatable = true, ipairs = true, __inext = true, load = true, loadstring = true, math = true, newproxy = true, next = true, pairs = true, pcall = true, rawequal = true, rawget = true, rawlen = true, rawset = true, select = true, setfenv = true, setmetatable = true, string = true, table = true, tonumber = true, tostring = true, type = true, unpack = true, xpcall = true, turtle = true, pocket = true, commands = true, _G = true, _RC_ROM_DIR = true} 18 | _G._RC_ROM_DIR = settings.get("recrafted.rom_dir") or error("recrafted.rom_dir is not set!", 0) 19 | local t = {} 20 | for k in pairs(_G) do if not keptAPIs[k] then table.insert(t, k) end end 21 | for _,k in ipairs(t) do _G[k] = nil end 22 | _G.term = _G.term.native() 23 | _G.http.checkURL = _G.http.checkURLAsync 24 | _G.http.websocket = _G.http.websocketAsync 25 | if _G.commands then _G.commands = _G.commands.native end 26 | if _G.turtle then _G.turtle.native, _G.turtle.craft = nil end 27 | local delete = {os = {"version", "pullEventRaw", "pullEvent", "run", "loadAPI", "unloadAPI", "sleep"}, http = {"get", "post", "put", "delete", "patch", "options", "head", "trace", "listen", "checkURLAsync", "websocketAsync"}, fs = {"complete", "isDriveRoot"}} 28 | for k,v in pairs(delete) do for _,a in ipairs(v) do _G[k][a] = nil end end 29 | _G._HOST = _G._HOST .. " (UnBIOS)" 30 | -- Set up TLCO 31 | -- This functions by crashing `rednet.run` by removing `os.pullEventRaw`. Normally 32 | -- this would cause `parallel` to throw an error, but we replace `error` with an 33 | -- empty placeholder to let it continue and return without throwing. This results 34 | -- in the `pcall` returning successfully, preventing the error-displaying code 35 | -- from running - essentially making it so that `os.shutdown` is called immediately 36 | -- after the new BIOS exits. 37 | -- 38 | -- From there, the setup code is placed in `term.native` since it's the first 39 | -- thing called after `parallel` exits. This loads the new BIOS and prepares it 40 | -- for execution. Finally, it overwrites `os.shutdown` with the new function to 41 | -- allow it to be the last function called in the original BIOS, and returns. 42 | -- From there execution continues, calling the `term.redirect` dummy, skipping 43 | -- over the error-handling code (since `pcall` returned ok), and calling 44 | -- `os.shutdown()`. The real `os.shutdown` is re-added, and the new BIOS is tail 45 | -- called, which effectively makes it run as the main chunk. 46 | local olderror = error 47 | _G.error = function() end 48 | _G.term.redirect = function() end 49 | function _G.term.native() 50 | _G.term.native = nil 51 | _G.term.redirect = nil 52 | _G.error = olderror 53 | term.setBackgroundColor(32768) 54 | term.setTextColor(1) 55 | term.setCursorPos(1, 1) 56 | term.setCursorBlink(true) 57 | term.clear() 58 | local file = fs.open("/bios.lua", "r") 59 | if file == nil then 60 | term.setCursorBlink(false) 61 | term.setTextColor(16384) 62 | term.write("Could not find /bios.lua. UnBIOS cannot continue.") 63 | term.setCursorPos(1, 2) 64 | term.write("Press any key to continue") 65 | coroutine.yield("key") 66 | os.shutdown() 67 | end 68 | local fn, err = loadstring(file.readAll(), "@bios.lua") 69 | file.close() 70 | if fn == nil then 71 | term.setCursorBlink(false) 72 | term.setTextColor(16384) 73 | term.write("Could not load /bios.lua. UnBIOS cannot continue.") 74 | term.setCursorPos(1, 2) 75 | term.write(err) 76 | term.setCursorPos(1, 3) 77 | term.write("Press any key to continue") 78 | coroutine.yield("key") 79 | os.shutdown() 80 | end 81 | setfenv(fn, _G) 82 | local oldshutdown = os.shutdown 83 | os.shutdown = function() 84 | os.shutdown = oldshutdown 85 | return fn() 86 | end 87 | end 88 | if debug then 89 | -- Restore functions that were overwritten in the BIOS 90 | -- Apparently this has to be done *after* redefining term.native 91 | local function restoreValue(tab, idx, name, hint) 92 | local i, key, value = 1, debug.getupvalue(tab[idx], hint) 93 | while key ~= name and key ~= nil do 94 | key, value = debug.getupvalue(tab[idx], i) 95 | i=i+1 96 | end 97 | tab[idx] = value or tab[idx] 98 | end 99 | restoreValue(_G, "loadstring", "nativeloadstring", 1) 100 | restoreValue(_G, "load", "nativeload", 5) 101 | restoreValue(http, "request", "nativeHTTPRequest", 3) 102 | restoreValue(os, "shutdown", "nativeShutdown", 1) 103 | restoreValue(os, "reboot", "nativeReboot", 1) 104 | if turtle then 105 | restoreValue(turtle, "equipLeft", "v", 1) 106 | restoreValue(turtle, "equipRight", "v", 1) 107 | end 108 | do 109 | local i, key, value = 1, debug.getupvalue(peripheral.isPresent, 2) 110 | while key ~= "native" and key ~= nil do 111 | key, value = debug.getupvalue(peripheral.isPresent, i) 112 | i=i+1 113 | end 114 | _G.peripheral = value or peripheral 115 | end 116 | end 117 | coroutine.yield() 118 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/modules/main/edit/syntax.lua: -------------------------------------------------------------------------------- 1 | -- A simple, fairly clever method of tokenizing code. 2 | -- Each token is defined by a set of rules. These rules are 3 | -- accordingly defined in the relevant syntax definition file. 4 | -- A rule takes the form of a triplet of functions: 5 | -- - The first function takes a single character, and returns 6 | -- whether that character is valid as part of the corresponding 7 | -- token. 8 | -- - The second function takes a single character and the current 9 | -- token, and returns whether that character is valid as part 10 | -- of the token. This allows flexible implementations of highly 11 | -- language-specific features such as strings. 12 | -- - The third function takes only a token, and returns whether 13 | -- that token is valid. 14 | -- 15 | -- Multiple tokens may be evaluated in parallel and the longest is returned. 16 | 17 | local lib = {} 18 | 19 | local syntenv = { 20 | char = function(str) 21 | return { 22 | function(c) 23 | return c == str:sub(1,1) 24 | end, 25 | function(tk, c) 26 | return tk .. c == str:sub(1, #tk + 1) 27 | end, 28 | function(tk) 29 | return tk == str 30 | end 31 | } 32 | end, 33 | print = print, 34 | string = string, table = table, 35 | pairs = pairs, ipairs = ipairs, 36 | tonumber = tonumber, math = math, 37 | globalenv = _G, type = type, 38 | } 39 | 40 | -- basic ""reader"" 41 | local function reader(text) 42 | local chars = {} 43 | for c in text:gmatch(".") do 44 | chars[#chars+1] = c 45 | end 46 | 47 | local i = 0 48 | return { 49 | advance = function() 50 | i = i + 1 51 | return chars[i] 52 | end, 53 | backpedal = function() 54 | i = math.max(0, i - 1) 55 | end 56 | } 57 | end 58 | 59 | -- Takes a file and returns a builder. 60 | function lib.new(file) 61 | local definitions = assert(loadfile(file, "t", syntenv))() 62 | 63 | for _, _defs in pairs(definitions) do 64 | for i, ent in pairs(_defs) do 65 | if type(ent) == "string" then 66 | _defs[i] = syntenv.char(ent) 67 | end 68 | end 69 | end 70 | 71 | return function(text) 72 | local read = reader(text) 73 | local possibilities = {} 74 | local aux = "" 75 | 76 | -- find and return the most likely (aka, longest) token and its class 77 | local function most_likely() 78 | -- if there are no possibilities, then ... 79 | if #possibilities == 0 then 80 | -- ... if the aux value has some characters, return that ... 81 | if #aux > 0 then 82 | local result = aux 83 | aux = "" 84 | return result 85 | else 86 | -- ... otherwise return nil. 87 | return nil 88 | end 89 | end 90 | 91 | local former_longest, new_longest = 0, 0 92 | 93 | -- remove all invalid possibilites 94 | for i=#possibilities, 1, -1 do 95 | if not possibilities[i].valid(possibilities[i].token) then 96 | former_longest = math.max(#possibilities[i].token, former_longest) 97 | table.remove(possibilities, i) 98 | else 99 | new_longest = math.max(#possibilities[i].token, new_longest) 100 | end 101 | end 102 | 103 | if former_longest > new_longest then 104 | for _=new_longest, former_longest - 1 do 105 | read.backpedal() 106 | end 107 | end 108 | 109 | -- sort possibilities by length - and deprioritize whitespace/word 110 | table.sort(possibilities, function(a, b) 111 | return #a.token > #b.token 112 | or (#a.token == #b.token and b.class == "word") 113 | or b.class == "whitespace" 114 | end) 115 | 116 | if #possibilities == 0 then 117 | --read.backpedal() 118 | return most_likely() 119 | end 120 | 121 | -- grab the first (longest) one 122 | local token, class = possibilities[1].token, possibilities[1].class 123 | -- reset possibilities 124 | possibilities = {} 125 | 126 | aux = "" 127 | 128 | -- return it 129 | return token, class 130 | end 131 | 132 | -- return an iterator! 133 | return function() 134 | while true do 135 | local c = read.advance() 136 | 137 | -- if no character, return the most likely token 138 | if not c then return most_likely() end 139 | 140 | if #possibilities == 0 then 141 | -- if no current possibilities, then go through and check for them 142 | for class, defs in pairs(definitions) do 143 | for _, funcs in pairs(defs) do 144 | if funcs[1](c) then 145 | -- if the token is valid, add it here 146 | possibilities[#possibilities+1] = { 147 | check = funcs[2], class = class, token = c, 148 | valid = funcs[3] or function()return true end, active = true 149 | } 150 | end 151 | end 152 | end 153 | 154 | -- if there are now some possibilities, return whatever the "aux" 155 | -- value was 156 | if #possibilities > 0 then 157 | if #aux > 0 then 158 | local temp = aux--:sub(1,-2) 159 | aux = "" 160 | return temp 161 | end 162 | aux = c 163 | else 164 | -- otherwise, add c to the aux value 165 | aux = aux .. c 166 | end 167 | else 168 | aux = aux .. c 169 | -- whether any possibilities matched 170 | local valid_for_any = false 171 | 172 | for _, p in ipairs(possibilities) do 173 | -- 'active' is roughly equal to whether the last character matched 174 | if p.active then 175 | -- if valid, set valid_for_any to true and add c to its valid 176 | if p.check(p.token, c) then 177 | valid_for_any = true 178 | p.token = p.token .. c 179 | else 180 | -- otherwise, disable it from future checks 181 | p.active = false 182 | end 183 | end 184 | end 185 | 186 | -- if nothing was valid, retract the current character 187 | -- and return the most likely token 188 | if not valid_for_any then 189 | read.backpedal() 190 | return most_likely() 191 | end 192 | end 193 | end 194 | end 195 | end 196 | end 197 | 198 | return lib 199 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/editors/advanced.lua: -------------------------------------------------------------------------------- 1 | -- A much better editor. 2 | 3 | local rc = require("rc") 4 | local keys = require("keys") 5 | local term = require("term") 6 | local colors = require("colors") 7 | local settings = require("settings") 8 | local textutils = require("textutils") 9 | 10 | local args = {...} 11 | 12 | local type_colors = { 13 | separator = colors[settings.get("edit.color_separator") or "lightBlue"], 14 | operator = colors[settings.get("edit.color_operator") or "lightGray"], 15 | keyword = colors[settings.get("edit.color_keyword") or "orange"], 16 | boolean = colors[settings.get("edit.color_boolean") or "purple"], 17 | comment = colors[settings.get("edit.color_comment") or "gray"], 18 | builtin = colors[settings.get("edit.color_global") or "lime"], 19 | string = colors[settings.get("edit.color_string") or "red"], 20 | number = colors[settings.get("edit.color_number") or "magenta"] 21 | } 22 | 23 | local lines = {} 24 | local linesDraw = {} 25 | local run, menu = true, false 26 | local cx, cy = 1, 1 27 | local scroll = 0 28 | local hscroll = 0 29 | local scroll_offset = settings.get("edit.scroll_offset") or 3 30 | local scroll_increment = 0 31 | local scroll_factor = settings.get("edit.scroll_factor") or 8 32 | local unsaved, changed = false, true 33 | local file = args[1] or ".new" 34 | local status = "Press Ctrl for menu" 35 | 36 | if args[1] then 37 | local handle = io.open(args[1], "r") 38 | 39 | if handle then 40 | for line in handle:lines() do 41 | lines[#lines+1] = line 42 | end 43 | handle:close() 44 | end 45 | end 46 | 47 | if not lines[1] then lines[1] = "" end 48 | 49 | local win = require("window").create(term.current(), 1, 1, term.getSize()) 50 | 51 | local function redraw() 52 | local w, h = term.getSize() 53 | 54 | -- this seems to provide a good responsiveness curve on my machine 55 | scroll_increment = math.floor(h/scroll_factor) 56 | 57 | win.reposition(1, 1, w, h) 58 | 59 | win.setVisible(false) 60 | 61 | for i=1, h - 1, 1 do 62 | local line = linesDraw[i] 63 | win.setCursorPos(1 - hscroll, i) 64 | win.clearLine() 65 | if line then 66 | for t=1, #line, 1 do 67 | local item = line[t] 68 | if type(item) == "number" then 69 | win.setTextColor(item) 70 | else 71 | win.write(item) 72 | end 73 | end 74 | end 75 | end 76 | 77 | win.setCursorPos(1, h) 78 | win.clearLine() 79 | win.setTextColor(type_colors.accent or colors.yellow) 80 | win.write(status) 81 | win.setTextColor(colors.white) 82 | 83 | win.setCursorPos(math.min(w, cx), cy - scroll) 84 | win.setCursorBlink(true) 85 | 86 | win.setVisible(true) 87 | end 88 | 89 | local syntax = require("edit.syntax") 90 | .new("/rc/modules/main/edit/syntax/lua.lua") 91 | 92 | local function rehighlight() 93 | local line = {} 94 | linesDraw = {} 95 | local _, h = term.getSize() 96 | local text = table.concat(lines, "\n", scroll+1, 97 | math.min(#lines, scroll+h+1)) or "" 98 | for token, ttype in syntax(text) do 99 | if token == "\n" then 100 | linesDraw[#linesDraw+1] = line 101 | line = {} 102 | 103 | else 104 | repeat 105 | local bit = token:find("\n") 106 | local nl = not not bit 107 | local chunk = token:sub(1, bit or #token) 108 | token = token:sub(#chunk+1) 109 | line[#line+1] = type_colors[ttype] or colors.white 110 | line[#line+1] = chunk 111 | if nl then 112 | linesDraw[#linesDraw+1] = line 113 | line = {} 114 | end 115 | until #token == 0 116 | end 117 | end 118 | 119 | if #line > 0 then 120 | linesDraw[#linesDraw+1] = line 121 | end 122 | end 123 | 124 | local function save() 125 | if file == ".new" then 126 | local _, h = term.getSize() 127 | term.setCursorPos(1, h) 128 | textutils.coloredWrite(colors.yellow, "filename: ") 129 | file = term.read() 130 | end 131 | 132 | local handle, err = io.open(file, "w") 133 | if not handle then 134 | status = err 135 | 136 | else 137 | for i=1, #lines, 1 do 138 | handle:write(lines[i] .. "\n") 139 | end 140 | handle:close() 141 | 142 | status = "Saved to " .. file 143 | unsaved = false 144 | end 145 | end 146 | 147 | local function processInput() 148 | local event, id = rc.pullEvent() 149 | 150 | local w, h = term.getSize() 151 | 152 | if event == "char" then 153 | local line = lines[cy] 154 | unsaved = true 155 | 156 | if cx > #line then 157 | line = line .. id 158 | 159 | elseif cx == 1 then 160 | line = id .. line 161 | 162 | else 163 | line = line:sub(0, cx-1) .. id .. line:sub(cx) 164 | end 165 | 166 | cx = cx + 1 167 | lines[cy] = line 168 | changed = true 169 | 170 | elseif event == "key" then 171 | id = keys.getName(id) 172 | 173 | if id == "backspace" then 174 | local line = lines[cy] 175 | unsaved = true 176 | 177 | if cx == 1 and cy > 1 then 178 | local previous = table.remove(lines, cy - 1) 179 | cy = cy - 1 180 | cx = #previous + 1 181 | line = previous .. line 182 | 183 | else 184 | if #line > 0 then 185 | if cx > #line then 186 | cx = cx - 1 187 | line = line:sub(1, -2) 188 | 189 | elseif cx > 1 then 190 | line = line:sub(0, cx - 2) .. line:sub(cx) 191 | cx = cx - 1 192 | end 193 | end 194 | end 195 | 196 | lines[cy] = line 197 | changed = true 198 | 199 | elseif id == "enter" then 200 | if cx == 1 then 201 | table.insert(lines, cy, "") 202 | 203 | elseif cx > #lines[cy] then 204 | table.insert(lines, cy+1, "") 205 | 206 | else 207 | local line = lines[cy] 208 | local before, after = line:sub(0, cx - 1), line:sub(cx) 209 | lines[cy] = before 210 | table.insert(lines, cy + 1, after) 211 | end 212 | 213 | cy = cy + 1 214 | cx = 1 215 | 216 | changed = true 217 | 218 | elseif id == "up" then 219 | if cy > 1 then 220 | cy = cy - 1 221 | if cy - scroll < scroll_offset then 222 | local old_scroll = scroll 223 | scroll = math.max(0, scroll - scroll_increment) 224 | if scroll < old_scroll then 225 | rehighlight() 226 | end 227 | end 228 | end 229 | 230 | cx = math.min(cx, #lines[cy] + 1) 231 | 232 | elseif id == "down" then 233 | if cy < #lines then 234 | cy = math.min(#lines, cy + 1) 235 | 236 | if cy - scroll > h - scroll_offset then 237 | local old_scroll = scroll 238 | scroll = math.max(0, math.min(#lines - h + 1, 239 | scroll + scroll_increment)) 240 | if scroll > old_scroll then 241 | rehighlight() 242 | end 243 | end 244 | end 245 | 246 | cx = math.min(cx, #lines[cy] + 1) 247 | 248 | elseif id == "left" then 249 | if cx > 1 then 250 | cx = cx - 1 251 | end 252 | 253 | hscroll = math.max(0, cx - w) 254 | 255 | elseif id == "right" then 256 | if cx < #lines[cy] + 1 then 257 | cx = cx + 1 258 | end 259 | 260 | hscroll = math.max(0, cx - w) 261 | 262 | elseif id == "leftCtrl" or id == "rightCtrl" then 263 | status = "S:save E:exit" 264 | menu = true 265 | end 266 | end 267 | end 268 | 269 | local function processMenuInput() 270 | local event, id = rc.pullEvent() 271 | 272 | if event == "char" then 273 | if id:lower() == "e" then 274 | if unsaved and menu ~= 2 then 275 | status = "Lose unsaved work? E:yes C:no" 276 | menu = 2 277 | else 278 | term.at(1, 1).clear() 279 | run = false 280 | end 281 | 282 | elseif id:lower() == "c" and menu == 2 then 283 | menu = false 284 | 285 | elseif id:lower() == "s" then 286 | save() 287 | menu = false 288 | end 289 | 290 | elseif event == "key" then 291 | id = keys.getName(id) 292 | 293 | if id == "leftCtrl" or id == "rightCtrl" then 294 | status = "Press Ctrl for menu" 295 | menu = false 296 | end 297 | end 298 | end 299 | 300 | while run do 301 | if changed then rehighlight() changed = false end 302 | redraw() 303 | if menu then 304 | processMenuInput() 305 | else 306 | processInput() 307 | end 308 | end 309 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/apis/shell.lua: -------------------------------------------------------------------------------- 1 | -- Recrafted shell api 2 | 3 | local shell = {} 4 | 5 | local rc = require("rc") 6 | local fs = require("fs") 7 | local colors = require("colors") 8 | local thread = require("rc.thread") 9 | local expect = require("cc.expect").expect 10 | local settings = require("settings") 11 | local textutils = require("textutils") 12 | 13 | local function copyIfPresent(f, t) 14 | if t[f] then 15 | local old = t[f] 16 | 17 | t[f] = {} 18 | for k, v in pairs(old) do 19 | t[f][k] = v 20 | end 21 | 22 | else 23 | t[f] = {} 24 | end 25 | end 26 | 27 | local completions = {[0]={}} 28 | 29 | function shell.init(env) 30 | local vars = thread.vars() 31 | 32 | copyIfPresent("aliases", vars) 33 | completions[vars.parentShell or 0] = completions[vars.parentShell or 0] or {} 34 | 35 | vars.path = vars.path or ".:/rc/programs" 36 | vars.env = env or _ENV or _G 37 | end 38 | 39 | local builtins = { 40 | cd = function(dir) 41 | if dir then 42 | shell.setDir(dir) 43 | else 44 | print(shell.dir()) 45 | end 46 | end, 47 | 48 | exit = function() 49 | shell.exit() 50 | end, 51 | 52 | alias = function(...) 53 | local args = {...} 54 | 55 | if #args == 0 then 56 | textutils.coloredPrint(colors.yellow, "shell aliases", colors.white) 57 | 58 | local aliases = shell.aliases() 59 | 60 | local _aliases = {} 61 | for k, v in pairs(aliases) do 62 | table.insert(_aliases, {colors.cyan, k, colors.white, ":", v}) 63 | end 64 | 65 | textutils.pagedTabulate(_aliases) 66 | 67 | elseif #args == 1 then 68 | shell.clearAlias(args[1]) 69 | 70 | elseif #args == 2 then 71 | shell.setAlias(args[1], args[2]) 72 | 73 | else 74 | error("this program takes a maximum of two arguments", 0) 75 | end 76 | end 77 | } 78 | 79 | local function callCommand(command, func, ...) 80 | thread.vars().program = command 81 | 82 | local success, prog_err 83 | if settings.get("shell.tracebacks") then 84 | success, prog_err = xpcall(func, debug.traceback, ...) 85 | else 86 | success, prog_err = pcall(func, ...) 87 | end 88 | 89 | thread.vars().program = "shell" 90 | 91 | if not success then 92 | return nil, prog_err 93 | end 94 | 95 | return true 96 | end 97 | 98 | local function execProgram(fork, command, ...) 99 | local path, res_err = shell.resolveProgram(command) 100 | if not path then 101 | return nil, res_err 102 | end 103 | 104 | local ok, err = loadfile(path, "t", thread.vars().env) 105 | 106 | if not ok then 107 | return nil, err 108 | end 109 | 110 | if fork then 111 | local args = table.pack(...) 112 | local result 113 | local id = thread.spawn(function() 114 | shell.init() 115 | result = table.pack(callCommand(path, ok, 116 | table.unpack(args, 1, args.n))) 117 | end, command) 118 | 119 | repeat rc.sleep(0.05, true) until not thread.exists(id) 120 | 121 | if result then 122 | return table.unpack(result, 1, result.n) 123 | end 124 | 125 | return true 126 | 127 | else 128 | return callCommand(path, ok, ...) 129 | end 130 | end 131 | 132 | -- execute a command, but do NOT fork 133 | function shell.exec(command, ...) 134 | expect(1, command, "string") 135 | return execProgram(false, command, ...) 136 | end 137 | 138 | function shell.execute(command, ...) 139 | expect(1, command, "string") 140 | 141 | if builtins[command] then 142 | local func = builtins[command] 143 | return callCommand(command, func, ...) 144 | 145 | else 146 | return execProgram(true, command, ...) 147 | end 148 | 149 | return true 150 | end 151 | 152 | local function tokenize(str) 153 | local words = {} 154 | for word in str:gmatch("[^ ]+") do 155 | words[#words+1] = word 156 | end 157 | return words 158 | end 159 | 160 | function shell.run(...) 161 | return shell.execute(table.unpack(tokenize(table.concat({...}, " ")))) 162 | end 163 | 164 | -- difference: this exits the current thread immediately 165 | function shell.exit() 166 | thread.remove() 167 | end 168 | 169 | function shell.dir() 170 | return thread.dir() 171 | end 172 | 173 | function shell.setDir(dir) 174 | expect(1, dir, "string") 175 | return thread.setDir(shell.resolve(dir)) 176 | end 177 | 178 | function shell.path() 179 | return thread.vars().path 180 | end 181 | 182 | function shell.setPath(path) 183 | expect(1, path, "string") 184 | thread.vars().path = path 185 | end 186 | 187 | function shell.resolve(path) 188 | expect(1, path, "string") 189 | 190 | if path:sub(1,1) == "/" then 191 | return path 192 | end 193 | 194 | return fs.combine(thread.dir(), path) 195 | end 196 | 197 | function shell.resolveProgram(path) 198 | expect(1, path, "string") 199 | 200 | local aliases = thread.vars().aliases 201 | if aliases[path] then 202 | path = aliases[path] 203 | end 204 | 205 | if fs.exists(path) and not fs.isDir(path) then 206 | return path 207 | end 208 | 209 | for search in thread.vars().path:gmatch("[^:]+") do 210 | if search == "." then search = shell.dir() end 211 | local try1 = fs.combine(search, path) 212 | local try2 = fs.combine(search, path .. ".lua") 213 | if fs.exists(try1) and not fs.isDir(try1) then 214 | return try1 215 | end 216 | if fs.exists(try2) and not fs.isDir(try2) then 217 | return try2 218 | end 219 | end 220 | 221 | return nil, "command not found" 222 | end 223 | 224 | function shell.programs(hidden) 225 | expect(1, hidden, "boolean", "nil") 226 | 227 | local programs = {} 228 | 229 | local seen = {} 230 | for search in thread.vars().path:gmatch("[^:]+") do 231 | local files = fs.list(shell.resolve(search)) 232 | for i=1, #files, 1 do 233 | programs[#programs+1] = files[i]:match("^(.+)%.lua$") 234 | if programs[#programs] then 235 | seen[programs[#programs]] = true 236 | end 237 | end 238 | end 239 | 240 | for alias in pairs(shell.aliases()) do 241 | if not seen[alias] then programs[#programs+1] = alias end 242 | end 243 | 244 | for builtin in pairs(builtins) do 245 | if not seen[builtin] then programs[#programs+1] = builtin end 246 | end 247 | 248 | return programs 249 | end 250 | 251 | function shell.complete(line) 252 | expect(1, line, "string") 253 | 254 | local words = tokenize(line) 255 | local aliases = thread.vars().aliases or {} 256 | 257 | if #words > (line:sub(-1) == " " and 0 or 1) then 258 | words[1] = aliases[words[1]] or words[1] 259 | end 260 | 261 | if line:sub(-1) == " " and #words > 0 then 262 | local complete = completions[thread.vars().parentShell or 0][words[1]] 263 | if complete then 264 | table.remove(words, 1) 265 | return complete(#words + 1, "", words) 266 | end 267 | else 268 | if #words == 1 then 269 | local opt = shell.completeProgram(words[1]) 270 | 271 | for i=1, #opt, 1 do 272 | if shell.resolveProgram(words[1] .. opt[i]) then 273 | opt[i] = opt[i] .. " " 274 | end 275 | end 276 | 277 | return opt 278 | 279 | else 280 | local complete = completions[thread.vars().parentShell or 0][words[1]] 281 | if complete then 282 | local arg = table.remove(words, #words) 283 | table.remove(words, 1) 284 | return complete(#words + 1, arg, words) 285 | end 286 | end 287 | end 288 | end 289 | 290 | function shell.completeProgram(line) 291 | expect(1, line, "string") 292 | return require("cc.shell.completion").program(line) 293 | end 294 | 295 | function shell.setCompletionFunction(program, complete) 296 | expect(1, program, "string") 297 | expect(2, complete, "function") 298 | completions[thread.vars().parentShell or 0][program] = complete 299 | end 300 | 301 | function shell.getCompletionInfo() 302 | return completions[thread.vars().parentShell or 0] 303 | end 304 | 305 | function shell.getRunningProgram() 306 | return thread.vars().program 307 | end 308 | 309 | function shell.setAlias(command, program) 310 | expect(1, command, "string") 311 | expect(2, program, "string") 312 | 313 | thread.vars().aliases[command] = program 314 | end 315 | 316 | function shell.clearAlias(command) 317 | expect(1, command, "string") 318 | 319 | thread.vars().aliases[command] = nil 320 | end 321 | 322 | function shell.aliases() 323 | return thread.vars().aliases 324 | end 325 | 326 | function shell.openTab(...) 327 | return require("multishell").launch(...) 328 | end 329 | 330 | function shell.switchTab(id) 331 | return require("multishell").setFocus(id) 332 | end 333 | 334 | return shell 335 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/modules/main/cc/audio/dfpwm.lua: -------------------------------------------------------------------------------- 1 | --[[- 2 | Provides utilities for converting between streams of DFPWM audio data and a list of amplitudes. 3 | 4 | DFPWM (Dynamic Filter Pulse Width Modulation) is an audio codec designed by GreaseMonkey. It's a relatively compact 5 | format compared to raw PCM data, only using 1 bit per sample, but is simple enough to simple enough to encode and decode 6 | in real time. 7 | 8 | Typically DFPWM audio is read from @{fs.BinaryReadHandle|the filesystem} or a @{http.Response|a web request} as a 9 | string, and converted a format suitable for @{speaker.playAudio}. 10 | 11 | ## Encoding and decoding files 12 | This modules exposes two key functions, @{make_decoder} and @{make_encoder}, which construct a new decoder or encoder. 13 | The returned encoder/decoder is itself a function, which converts between the two kinds of data. 14 | 15 | These encoders and decoders have lots of hidden state, so you should be careful to use the same encoder or decoder for 16 | a specific audio stream. Typically you will want to create a decoder for each stream of audio you read, and an encoder 17 | for each one you write. 18 | 19 | ## Converting audio to DFPWM 20 | DFPWM is not a popular file format and so standard audio processing tools will not have an option to export to it. 21 | Instead, you can convert audio files online using [music.madefor.cc] or with the [LionRay Wav Converter][LionRay] Java 22 | application. 23 | 24 | [music.madefor.cc]: https://music.madefor.cc/ "DFPWM audio converter for Computronics and CC: Tweaked" 25 | [LionRay]: https://github.com/gamax92/LionRay/ "LionRay Wav Converter " 26 | 27 | @see guide!speaker_audio Gives a more general introduction to audio processing and the speaker. 28 | @see speaker.playAudio To play the decoded audio data. 29 | @usage Reads "data/example.dfpwm" in chunks, decodes them and then doubles the speed of the audio. The resulting audio 30 | is then re-encoded and saved to "speedy.dfpwm". This processed audio can then be played with the `speaker` program. 31 | 32 | ```lua 33 | local dfpwm = require("cc.audio.dfpwm") 34 | 35 | local encoder = dfpwm.make_encoder() 36 | local decoder = dfpwm.make_decoder() 37 | 38 | local out = fs.open("speedy.dfpwm", "wb") 39 | for input in io.lines("data/example.dfpwm", 16 * 1024 * 2) do 40 | local decoded = decoder(input) 41 | local output = {} 42 | 43 | -- Read two samples at once and take the average. 44 | for i = 1, #decoded, 2 do 45 | local value_1, value_2 = decoded[i], decoded[i + 1] 46 | output[(i + 1) / 2] = (value_1 + value_2) / 2 47 | end 48 | 49 | out.write(encoder(output)) 50 | 51 | sleep(0) -- This program takes a while to run, so we need to make sure we yield. 52 | end 53 | out.close() 54 | ``` 55 | ]] 56 | 57 | local expect = require "cc.expect".expect 58 | 59 | local char, byte, floor, band, rshift = string.char, string.byte, math.floor, bit32.band, bit32.arshift 60 | 61 | local PREC = 10 62 | local PREC_POW = 2 ^ PREC 63 | local PREC_POW_HALF = 2 ^ (PREC - 1) 64 | local STRENGTH_MIN = 2 ^ (PREC - 8 + 1) 65 | 66 | local function make_predictor() 67 | local charge, strength, previous_bit = 0, 0, false 68 | 69 | return function(current_bit) 70 | local target = current_bit and 127 or -128 71 | 72 | local next_charge = charge + floor((strength * (target - charge) + PREC_POW_HALF) / PREC_POW) 73 | if next_charge == charge and next_charge ~= target then 74 | next_charge = next_charge + (current_bit and 1 or -1) 75 | end 76 | 77 | local z = current_bit == previous_bit and PREC_POW - 1 or 0 78 | local next_strength = strength 79 | if next_strength ~= z then next_strength = next_strength + (current_bit == previous_bit and 1 or -1) end 80 | if next_strength < STRENGTH_MIN then next_strength = STRENGTH_MIN end 81 | 82 | charge, strength, previous_bit = next_charge, next_strength, current_bit 83 | return charge 84 | end 85 | end 86 | 87 | --[[- Create a new encoder for converting PCM audio data into DFPWM. 88 | 89 | The returned encoder is itself a function. This function accepts a table of amplitude data between -128 and 127 and 90 | returns the encoded DFPWM data. 91 | 92 | :::caution Reusing encoders 93 | Encoders have lots of internal state which tracks the state of the current stream. If you reuse an encoder for multiple 94 | streams, or use different encoders for the same stream, the resulting audio may not sound correct. 95 | ::: 96 | 97 | @treturn function(pcm: { number... }):string The encoder function 98 | @see encode A helper function for encoding an entire file of audio at once. 99 | ]] 100 | local function make_encoder() 101 | local predictor = make_predictor() 102 | local previous_charge = 0 103 | 104 | return function(input) 105 | expect(1, input, "table") 106 | 107 | local output, output_n = {}, 0 108 | for i = 1, #input, 8 do 109 | local this_byte = 0 110 | for j = 0, 7 do 111 | local inp_charge = floor(input[i + j] or 0) 112 | if inp_charge > 127 or inp_charge < -128 then 113 | error(("Amplitude at position %d was %d, but should be between -128 and 127"):format(i + j, inp_charge), 2) 114 | end 115 | 116 | local current_bit = inp_charge > previous_charge or (inp_charge == previous_charge and inp_charge == 127) 117 | this_byte = floor(this_byte / 2) + (current_bit and 128 or 0) 118 | 119 | previous_charge = predictor(current_bit) 120 | end 121 | 122 | output_n = output_n + 1 123 | output[output_n] = char(this_byte) 124 | end 125 | 126 | return table.concat(output, "", 1, output_n) 127 | end 128 | end 129 | 130 | --[[- Create a new decoder for converting DFPWM into PCM audio data. 131 | 132 | The returned decoder is itself a function. This function accepts a string and returns a table of amplitudes, each value 133 | between -128 and 127. 134 | 135 | :::caution Reusing decoders 136 | Decoders have lots of internal state which tracks the state of the current stream. If you reuse an decoder for multiple 137 | streams, or use different decoders for the same stream, the resulting audio may not sound correct. 138 | ::: 139 | 140 | @treturn function(dfpwm: string):{ number... } The encoder function 141 | @see decode A helper function for decoding an entire file of audio at once. 142 | 143 | @usage Reads "data/example.dfpwm" in blocks of 16KiB (the speaker can accept a maximum of 128×1024 samples), decodes 144 | them and then plays them through the speaker. 145 | 146 | ```lua {data-peripheral=speaker} 147 | local dfpwm = require "cc.audio.dfpwm" 148 | local speaker = peripheral.find("speaker") 149 | 150 | local decoder = dfpwm.make_decoder() 151 | for input in io.lines("data/example.dfpwm", 16 * 1024) do 152 | local decoded = decoder(input) 153 | while not speaker.playAudio(decoded) do 154 | os.pullEvent("speaker_audio_empty") 155 | end 156 | end 157 | ``` 158 | ]] 159 | local function make_decoder() 160 | local predictor = make_predictor() 161 | local low_pass_charge = 0 162 | local previous_charge, previous_bit = 0, false 163 | 164 | return function (input, output) 165 | expect(1, input, "string") 166 | 167 | local output, output_n = {}, 0 168 | for i = 1, #input do 169 | local input_byte = byte(input, i) 170 | for _ = 1, 8 do 171 | local current_bit = band(input_byte, 1) ~= 0 172 | local charge = predictor(current_bit) 173 | 174 | local antijerk = charge 175 | if current_bit ~= previous_bit then 176 | antijerk = floor((charge + previous_charge + 1) / 2) 177 | end 178 | 179 | previous_charge, previous_bit = charge, current_bit 180 | 181 | low_pass_charge = low_pass_charge + floor(((antijerk - low_pass_charge) * 140 + 0x80) / 256) 182 | 183 | output_n = output_n + 1 184 | output[output_n] = low_pass_charge 185 | 186 | input_byte = rshift(input_byte, 1) 187 | end 188 | end 189 | 190 | return output 191 | end 192 | end 193 | 194 | --[[- A convenience function for decoding a complete file of audio at once. 195 | 196 | This should only be used for short files. For larger files, one should read the file in chunks and process it using 197 | @{make_decoder}. 198 | 199 | @tparam string input The DFPWM data to convert. 200 | @treturn { number... } The produced amplitude data. 201 | @see make_decoder 202 | ]] 203 | local function decode(input) 204 | expect(1, input, "string") 205 | return make_decoder()(input) 206 | end 207 | 208 | --[[- A convenience function for encoding a complete file of audio at once. 209 | 210 | This should only be used for complete pieces of audio. If you are writing writing multiple chunks to the same place, 211 | you should use an encoder returned by @{make_encoder} instead. 212 | 213 | @tparam { number... } input The table of amplitude data. 214 | @treturn string The encoded DFPWM data. 215 | @see make_encoder 216 | ]] 217 | local function encode(input) 218 | expect(1, input, "table") 219 | return make_encoder()(input) 220 | end 221 | 222 | return { 223 | make_encoder = make_encoder, 224 | encode = encode, 225 | 226 | make_decoder = make_decoder, 227 | decode = decode, 228 | } 229 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/startup/15_term.lua: -------------------------------------------------------------------------------- 1 | -- some term things 2 | -- e.g. redirects, read() 3 | 4 | local rc = ... 5 | 6 | -- we need a couple of these 7 | local colors = require("colors") 8 | local native = require("term") 9 | local strings = require("cc.strings") 10 | local expect = require("cc.expect").expect 11 | 12 | local term = {} 13 | package.loaded.term = term 14 | local thread = require("rc.thread") 15 | 16 | local valid = { 17 | write = true, 18 | scroll = true, 19 | getCursorPos = true, 20 | setCursorPos = true, 21 | getCursorBlink = true, 22 | setCursorBlink = true, 23 | getSize = true, 24 | clear = true, 25 | clearLine = true, 26 | getTextColor = true, 27 | getTextColour = true, 28 | setTextColor = true, 29 | setTextColour = true, 30 | getBackgroundColor = true, 31 | getBackgroundColour = true, 32 | setBackgroundColor = true, 33 | setBackgroundColour = true, 34 | isColor = true, 35 | isColour = true, 36 | blit = true, 37 | setPaletteColor = true, 38 | setPaletteColour = true, 39 | getPaletteColor = true, 40 | getPaletteColour = true, 41 | 42 | -- CraftOS-PC graphics mode settings 43 | setGraphicsMode = not not native.setGraphicsMode, 44 | getGraphicsMode = not not native.getGraphicsMode, 45 | drawPixels = not not native.drawPixels, 46 | getPixels = not not native.getPixels, 47 | setPixel = not not native.setPixel, 48 | getPixel = not not native.getPixel 49 | } 50 | 51 | for k in pairs(valid) do 52 | term[k] = function(...) 53 | local redirect = thread.getTerm() 54 | if not redirect[k] then 55 | error("redirect object does not implement term."..k, 2) 56 | end 57 | 58 | return redirect[k](...) 59 | end 60 | end 61 | 62 | function term.current() 63 | return thread.getTerm() 64 | end 65 | 66 | function term.native() 67 | return native 68 | end 69 | 70 | function term.redirect(obj) 71 | expect(1, obj, "table") 72 | return thread.setTerm(obj) 73 | end 74 | 75 | function term.at(x, y) 76 | term.setCursorPos(x, y) 77 | return term 78 | end 79 | 80 | local keys = require("keys") 81 | 82 | -- rc.write 83 | -- [[ 84 | function rc.write(text) 85 | expect(1, text, "string") 86 | 87 | local lines = 0 88 | local w, h = term.getSize() 89 | local x, y = term.getCursorPos() 90 | 91 | local elements = strings.splitElements(text, w) 92 | 93 | strings.wrappedWriteElements(elements, w, false, { 94 | newline = function() 95 | lines = lines + 1 96 | 97 | x = 1 98 | if y >= h then 99 | term.scroll(1) 100 | else 101 | y = y + 1 102 | end 103 | 104 | term.at(x, y) 105 | end, 106 | 107 | append = function(newText) 108 | term.at(x, y).write(newText) 109 | x = x + #newText 110 | end, 111 | 112 | getX = function() 113 | return x 114 | end 115 | }) 116 | 117 | return lines 118 | end 119 | 120 | --]] 121 | 122 | -- old write() used for redrawing in read() 123 | local function write(text) 124 | expect(1, text, "string") 125 | 126 | local lines = 0 127 | local w, h = term.getSize() 128 | 129 | local function inc_cy(cy) 130 | lines = lines + 1 131 | 132 | if cy > h - 1 then 133 | term.scroll(1) 134 | return cy 135 | else 136 | return cy + 1 137 | end 138 | end 139 | 140 | while #text > 0 do 141 | local nl = text:find("\n") or #text 142 | local chunk = text:sub(1, nl) 143 | text = text:sub(#chunk + 1) 144 | 145 | local has_nl = chunk:sub(-1) == "\n" 146 | if has_nl then chunk = chunk:sub(1, -2) end 147 | 148 | local cx, cy = term.getCursorPos() 149 | while #chunk > 0 do 150 | if cx > w then 151 | term.setCursorPos(1, inc_cy(cy)) 152 | cx, cy = term.getCursorPos() 153 | end 154 | 155 | local to_write = chunk:sub(1, w - cx + 1) 156 | term.write(to_write) 157 | 158 | chunk = chunk:sub(#to_write + 1) 159 | cx, cy = term.getCursorPos() 160 | end 161 | 162 | if has_nl then 163 | term.setCursorPos(1, inc_cy(cy)) 164 | end 165 | end 166 | 167 | return lines 168 | end--]] 169 | 170 | rc.write = write 171 | 172 | -- read 173 | local empty = {} 174 | function term.read(replace, history, complete, default) 175 | expect(1, replace, "string", "nil") 176 | expect(2, history, "table", "nil") 177 | expect(3, complete, "function", "nil") 178 | expect(4, default, "string", "nil") 179 | 180 | if replace then replace = replace:sub(1, 1) end 181 | local hist = history or {} 182 | history = {} 183 | for i=1, #hist, 1 do 184 | history[i] = hist[i] 185 | end 186 | 187 | local buffer = default or "" 188 | local prev_buf = buffer 189 | history[#history+1] = buffer 190 | 191 | local hist_pos = #history 192 | local cursor_pos = 0 193 | 194 | local stx, sty = term.getCursorPos() 195 | local w, h = term.getSize() 196 | 197 | local dirty = false 198 | local completions = {} 199 | local comp_id = 0 200 | 201 | local function clearCompletion() 202 | if completions[comp_id] then 203 | write((" "):rep(#completions[comp_id])) 204 | end 205 | end 206 | 207 | local function full_redraw(force) 208 | if force or dirty then 209 | if complete and buffer ~= prev_buf then 210 | completions = complete(buffer) or empty 211 | comp_id = math.min(1, #completions) 212 | end 213 | prev_buf = buffer 214 | 215 | term.setCursorPos(stx, sty) 216 | local text = buffer 217 | if replace then text = replace:rep(#text) end 218 | local ln = write(text) 219 | 220 | if completions[comp_id] then 221 | local oldfg = term.getTextColor() 222 | local oldbg = term.getBackgroundColor() 223 | term.setTextColor(colors.white) 224 | term.setBackgroundColor(colors.gray) 225 | ln = ln + write(completions[comp_id]) 226 | term.setTextColor(oldfg) 227 | term.setBackgroundColor(oldbg) 228 | else 229 | ln = ln + write(" ") 230 | end 231 | 232 | if sty + ln > h then 233 | sty = sty - (sty + ln - h) 234 | end 235 | end 236 | 237 | -- set cursor to the appropriate spot 238 | local cx, cy = stx, sty 239 | cx = cx + #buffer - cursor_pos-- + #(completions[comp_id] or "") 240 | while cx > w do 241 | cx = cx - w 242 | cy = cy + 1 243 | end 244 | term.setCursorPos(cx, cy) 245 | end 246 | 247 | term.setCursorBlink(true) 248 | 249 | while true do 250 | full_redraw() 251 | -- get input 252 | local evt, id = rc.pullEvent() 253 | 254 | if evt == "char" then 255 | dirty = true 256 | clearCompletion() 257 | if cursor_pos == 0 then 258 | buffer = buffer .. id 259 | elseif cursor_pos == #buffer then 260 | buffer = id .. buffer 261 | else 262 | buffer = buffer:sub(0, -cursor_pos - 1)..id..buffer:sub(-cursor_pos) 263 | end 264 | 265 | elseif evt == "paste" then 266 | dirty = true 267 | clearCompletion() 268 | if cursor_pos == 0 then 269 | buffer = buffer .. id 270 | elseif cursor_pos == #buffer then 271 | buffer = id .. buffer 272 | else 273 | buffer = buffer:sub(0, -cursor_pos - 1)..id.. 274 | buffer:sub(-cursor_pos+(#id-1)) 275 | end 276 | 277 | elseif evt == "key" then 278 | id = keys.getName(id) 279 | 280 | if id == "backspace" and #buffer > 0 then 281 | dirty = true 282 | if cursor_pos == 0 then 283 | buffer = buffer:sub(1, -2) 284 | clearCompletion() 285 | elseif cursor_pos < #buffer then 286 | buffer = buffer:sub(0, -cursor_pos - 2)..buffer:sub(-cursor_pos) 287 | end 288 | 289 | elseif id == "delete" and cursor_pos > 0 then 290 | dirty = true 291 | 292 | if cursor_pos == #buffer then 293 | buffer = buffer:sub(2) 294 | elseif cursor_pos == 1 then 295 | buffer = buffer:sub(1, -2) 296 | else 297 | buffer = buffer:sub(0, -cursor_pos - 1) .. buffer:sub(-cursor_pos + 1) 298 | end 299 | cursor_pos = cursor_pos - 1 300 | 301 | elseif id == "up" then 302 | if #completions > 1 then 303 | dirty = true 304 | clearCompletion() 305 | if comp_id > 1 then 306 | comp_id = comp_id - 1 307 | else 308 | comp_id = #completions 309 | end 310 | 311 | elseif hist_pos > 1 then 312 | cursor_pos = 0 313 | 314 | history[hist_pos] = buffer 315 | hist_pos = hist_pos - 1 316 | 317 | buffer = (" "):rep(#buffer) 318 | full_redraw(true) 319 | 320 | buffer = history[hist_pos] 321 | dirty = true 322 | end 323 | 324 | elseif id == "down" then 325 | if #completions > 1 then 326 | dirty = true 327 | clearCompletion() 328 | if comp_id < #completions then 329 | comp_id = comp_id + 1 330 | else 331 | comp_id = 1 332 | end 333 | 334 | elseif hist_pos < #history then 335 | cursor_pos = 0 336 | 337 | history[hist_pos] = buffer 338 | hist_pos = hist_pos + 1 339 | 340 | buffer = (" "):rep(#buffer) 341 | full_redraw(true) 342 | 343 | buffer = history[hist_pos] 344 | dirty = true 345 | end 346 | 347 | elseif id == "left" then 348 | if cursor_pos < #buffer then 349 | clearCompletion() 350 | cursor_pos = cursor_pos + 1 351 | end 352 | 353 | elseif id == "right" then 354 | if cursor_pos > 0 then 355 | cursor_pos = cursor_pos - 1 356 | 357 | elseif comp_id > 0 then 358 | dirty = true 359 | buffer = buffer .. completions[comp_id] 360 | end 361 | 362 | elseif id == "tab" then 363 | if comp_id > 0 then 364 | dirty = true 365 | buffer = buffer .. completions[comp_id] 366 | end 367 | 368 | elseif id == "home" then 369 | cursor_pos = #buffer 370 | 371 | elseif id == "end" then 372 | cursor_pos = 0 373 | 374 | elseif id == "enter" then 375 | clearCompletion() 376 | write("\n") 377 | break 378 | end 379 | end 380 | end 381 | 382 | term.setCursorBlink(false) 383 | 384 | return buffer 385 | end 386 | 387 | rc.read = term.read 388 | setmetatable(term, {__index = native}) 389 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/apis/textutils.lua: -------------------------------------------------------------------------------- 1 | -- rc.textutils 2 | 3 | local rc = require("rc") 4 | local term = require("term") 5 | local json = require("rc.json") 6 | local colors = require("colors") 7 | local expect = require("cc.expect").expect 8 | local strings = require("cc.strings") 9 | 10 | local tu = {} 11 | 12 | function tu.slowWrite(text, rate) 13 | expect(1, text, "string") 14 | expect(2, rate, "number", "nil") 15 | 16 | local delay = 1/(rate or 20) 17 | for c in text:gmatch(".") do 18 | rc.write(c) 19 | rc.sleep(delay) 20 | end 21 | end 22 | 23 | function tu.slowPrint(text, rate) 24 | expect(1, text, "string") 25 | expect(2, rate, "number", "nil") 26 | tu.slowWrite(text.."\n", rate) 27 | end 28 | 29 | function tu.formatTime(time, _24h) 30 | expect(1, time, "number") 31 | expect(2, _24h, "boolean", "nil") 32 | 33 | local fmt = _24h and "!%H:%M" or "!%I:%M %p" 34 | 35 | return (os.date(fmt, time * 3600):gsub("^ ", "")) 36 | end 37 | 38 | local function pagedWrite(text, begin) 39 | local w, h = term.getSize() 40 | local x, y = term.getCursorPos() 41 | 42 | local realTotal = 0 43 | local lines = begin or 0 44 | 45 | local elements = strings.splitElements(text, w) 46 | 47 | strings.wrappedWriteElements(elements, w, false, { 48 | newline = function() 49 | rc.write("\n") 50 | realTotal = realTotal + 1 51 | lines = lines + 1 52 | x, y = term.getCursorPos() 53 | 54 | if lines >= h - 2 then 55 | local old = term.getTextColor() 56 | term.setTextColor(colors.white) 57 | rc.write("Press any key to continue") 58 | term.setTextColor(old) 59 | rc.pullEvent("char") 60 | local _, _y = term.getCursorPos() 61 | term.at(1, _y).clearLine() 62 | lines = 0 63 | end 64 | end, 65 | 66 | append = function(newText) 67 | term.at(x, y).write(newText) 68 | x = x + #newText 69 | end, 70 | 71 | getX = function() return x end 72 | }) 73 | 74 | return realTotal, lines 75 | end 76 | 77 | function tu.pagedPrint(text) 78 | expect(1, text, "string") 79 | return pagedWrite(text .. "\n") 80 | end 81 | 82 | local function coloredWrite(paged, ...) 83 | local args = table.pack(...) 84 | local lines = 0 85 | local pageLines = 0 86 | 87 | local write = paged and pagedWrite or rc.write 88 | local old_fg, old_bg = term.getTextColor(), term.getBackgroundColor() 89 | local _, h = term.getSize() 90 | 91 | for i=1, args.n, 1 do 92 | if type(args[i]) == "number" then 93 | term.setTextColor(args[i]) 94 | elseif type(args[i]) == "table" then 95 | if args[i].fg or args[i][1] then 96 | term.setTextColor(args[i].fg or args[i][1]) 97 | end 98 | 99 | if args[i].bg or args[i][2] then 100 | term.setBackgroundColor(args[i].bg or args[i][2]) 101 | end 102 | else 103 | local _lines, _tot = write(args[i], pageLines) 104 | lines = lines + _lines 105 | pageLines = _tot or 0 106 | while pageLines > h do pageLines = pageLines - h end 107 | end 108 | end 109 | 110 | term.setTextColor(old_fg) 111 | term.setBackgroundColor(old_bg) 112 | 113 | return lines 114 | end 115 | 116 | local function tabulate(paged, ...) 117 | local args = table.pack(...) 118 | 119 | local w = term.getSize() 120 | local max_len = 0 121 | 122 | local linear = {} 123 | 124 | for i=1, args.n, 1 do 125 | local argi = args[i] 126 | expect(i, argi, "table", "number") 127 | 128 | if type(argi) == "table" then 129 | for n=1, #argi, 1 do 130 | if type(argi[n]) == "table" then 131 | local total_len = 2 132 | local argin = argi[n] 133 | 134 | for j=1, #argin, 1 do 135 | expect(j, argin[j], "string", "number") 136 | if type(argin[j]) == "string" then 137 | total_len = total_len + #argin[j] 138 | end 139 | end 140 | 141 | argin.total_len = total_len 142 | max_len = math.max(max_len, total_len + 2) 143 | 144 | linear[#linear+1] = argi[n] 145 | 146 | else 147 | linear[#linear+1] = expect(n, argi[n], "string") 148 | max_len = math.max(max_len, #argi[n] + 2) 149 | end 150 | end 151 | 152 | else 153 | linear[#linear+1] = args[i] 154 | end 155 | end 156 | 157 | local written = 0 158 | 159 | local prt = paged and function(_args) 160 | if type(_args) == "string" then _args = {_args} end 161 | return coloredWrite(true, table.unpack(_args)) 162 | end or function(_args) 163 | if type(_args) == "string" then _args = {_args} end 164 | return coloredWrite(false, table.unpack(_args)) 165 | end 166 | 167 | for i=1, #linear, 1 do 168 | local lini = linear[i] 169 | 170 | if type(lini) == "number" then 171 | if written > 0 then 172 | prt("\n") 173 | written = 0 174 | end 175 | 176 | term.setTextColor(lini) 177 | 178 | else 179 | local len = type(lini) == "table" and lini.total_len or #lini 180 | if written + max_len > w then 181 | if written + len > w then 182 | prt("\n") 183 | prt(lini) 184 | rc.write((" "):rep(max_len - len)) 185 | written = max_len 186 | 187 | else 188 | prt(lini) 189 | prt("\n") 190 | written = 0 191 | end 192 | 193 | else 194 | prt(lini) 195 | rc.write((" "):rep(max_len - len)) 196 | written = written + max_len 197 | end 198 | end 199 | end 200 | 201 | if written > 0 then 202 | prt("\n") 203 | end 204 | end 205 | 206 | function tu.tabulate(...) 207 | tabulate(false, ...) 208 | end 209 | 210 | function tu.pagedTabulate(...) 211 | tabulate(true, ...) 212 | end 213 | 214 | local function mk_immut(str, field) 215 | return setmetatable({}, { 216 | __newindex = function() 217 | error(string.format("attempt to modify textutils.%s", field), 2) 218 | end, 219 | __tostring = function() 220 | return str 221 | end}) 222 | end 223 | 224 | tu.empty_json_array = mk_immut("[]", "empty_json_array") 225 | tu.json_null = mk_immut("null", "json_null") 226 | 227 | local function serialize(t, _seen) 228 | local ret = "" 229 | 230 | if type(t) == "table" then 231 | local seen = setmetatable({}, {__index = _seen}) 232 | 233 | ret = "{" 234 | for k, v in pairs(t) do 235 | if seen[k] then 236 | k = "" 237 | end 238 | if seen[v] then 239 | v = "" 240 | end 241 | if type(k) == "table" then 242 | seen[k] = true 243 | end 244 | if type(v) == "table" then 245 | seen[v] = true 246 | end 247 | ret = ret .. string.format("[%s] = %s,", serialize(k, seen), 248 | serialize(v, seen)) 249 | end 250 | ret = ret .. "}" 251 | elseif type(t) == "function" or type(t) == "thread" or 252 | type(t) == "userdata" then 253 | error("cannot serialize type " .. type(t), 2) 254 | else 255 | return string.format("%q", t) 256 | end 257 | 258 | return ret 259 | end 260 | 261 | function tu.serialize(t, opts) 262 | expect(1, t, "table") 263 | expect(2, opts, "table", "nil") 264 | 265 | return serialize(t, {}) 266 | end 267 | 268 | function tu.unserialize(s) 269 | expect(1, s, "string") 270 | local call = load("return " .. s, "=", "t", {}) 271 | if call then return call() end 272 | end 273 | 274 | tu.serialise = tu.serialize 275 | tu.unserialise = tu.unserialize 276 | 277 | function tu.serializeJSON(t, nbt) 278 | expect(1, t, "table") 279 | if nbt then 280 | error("NBT mode is not yet supported") 281 | end 282 | return json.encode(t) 283 | end 284 | 285 | function tu.unserializeJSON(s)--s, options) 286 | expect(1, s, "string") 287 | return json.decode(s) 288 | end 289 | 290 | tu.serialiseJSON = tu.serializeJSON 291 | tu.unserialiseJSON = tu.unserializeJSON 292 | 293 | function tu.urlEncode(str) 294 | expect(1, str, "string") 295 | 296 | -- TODO: possibly UTF-8 support? 297 | str = str:gsub("[^%w %-%_%.]", function(c) 298 | return string.format("%%%02x", c:byte()) 299 | end):gsub(" ", "+"):gsub("\n", "\r\n") 300 | 301 | return str 302 | end 303 | 304 | local function split(text) 305 | local dots = {""} 306 | 307 | for c in text:gmatch(".") do 308 | if c == "." or c == ":" then 309 | --dots[#dots+1] = c 310 | dots[#dots+1] = "" 311 | else 312 | dots[#dots] = dots[#dots] .. c 313 | end 314 | end 315 | 316 | return dots 317 | end 318 | 319 | local function getSuffix(thing, default) 320 | if type(thing) == "table" then 321 | return "." 322 | elseif type(thing) == "function" then 323 | return "(" 324 | end 325 | return default 326 | end 327 | 328 | function tu.complete(text, env) 329 | expect(1, text, "string") 330 | env = expect(2, env, "table", "nil") or _G 331 | 332 | local last_exp = text:match("[^%(%)%%%+%-%*/%[%]%{%}; =]*$") 333 | 334 | local results = {} 335 | 336 | if last_exp and #last_exp > 0 then 337 | local search = {env} 338 | local mt = getmetatable(env) 339 | if mt and type(mt.__index) == "table" then 340 | search[#search+1] = mt.__index 341 | end 342 | 343 | for s=1, #search, 1 do 344 | local dots = split(last_exp) 345 | local current = search[s] 346 | 347 | local final = 0 348 | for i=1, #dots, 1 do 349 | if current[dots[i]] then 350 | current = current[dots[i]] 351 | final = i 352 | else 353 | break 354 | end 355 | end 356 | 357 | for _=1, final, 1 do table.remove(dots, 1) end 358 | 359 | if #dots == 0 then 360 | results[#results+1] = getSuffix(current) 361 | end 362 | 363 | if #dots ~= 1 or type(current) ~= "table" then return results end 364 | 365 | local find = dots[1] 366 | for key, val in pairs(current) do 367 | key = key .. getSuffix(val, "") 368 | 369 | if key:sub(1, #find) == find then 370 | results[#results+1] = key:sub(#find + 1) 371 | end 372 | end 373 | end 374 | end 375 | 376 | return results 377 | end 378 | 379 | function tu.coloredWrite(...) 380 | return coloredWrite(false, ...) 381 | end 382 | 383 | function tu.coloredPrint(...) 384 | return coloredWrite(false, ...) + rc.write("\n") 385 | end 386 | 387 | function tu.coloredPagedPrint(...) 388 | return coloredWrite(true, ...) + rc.write("\n") 389 | end 390 | 391 | return tu 392 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/modules/main/rc/thread.lua: -------------------------------------------------------------------------------- 1 | -- New scheduler. 2 | -- Tabs are integral to the design of this scheduler; Multishell cannot 3 | -- be disabled. 4 | 5 | local rc = require("rc") 6 | local fs = require("fs") 7 | local keys = require("keys") 8 | local term = require("term") 9 | local colors = require("colors") 10 | local window = require("window") 11 | local expect = require("cc.expect") 12 | local copy = require("rc.copy").copy 13 | 14 | local getfenv 15 | if rc.lua51 then 16 | getfenv = rc.lua51.getfenv 17 | else 18 | getfenv = function() return _ENV or _G end 19 | end 20 | 21 | local tabs = { {} } 22 | local threads = {} 23 | local current, wrappedNative 24 | 25 | local focused = 1 26 | 27 | local api = {} 28 | 29 | function api.launchTab(x, name) 30 | expect(1, x, "string", "function") 31 | name = expect(2, name, "string", "nil") or tostring(x) 32 | 33 | local newTab = { 34 | term = window.create(wrappedNative, 1, 1, wrappedNative.getSize()), 35 | id = #tabs + 1 36 | } 37 | 38 | tabs[newTab.id] = newTab 39 | 40 | local _f = focused 41 | focused = newTab.id 42 | local id = (type(x) == "string" and api.load or api.spawn)(x, name) 43 | focused = _f 44 | 45 | return newTab.id, id 46 | end 47 | 48 | function api.setFocusedTab(f) 49 | expect(1, f, "number") 50 | if tabs[focused] then focused = f end 51 | return not not tabs[f] 52 | end 53 | 54 | function api.getFocusedTab() 55 | return focused 56 | end 57 | 58 | function api.getCurrentTab() 59 | return current.tab.id 60 | end 61 | 62 | function api.load(file, name) 63 | expect(1, file, "string") 64 | name = expect(2, name, "string", "nil") or file 65 | 66 | local env = copy(current and current.env or _ENV or _G, package.loaded) 67 | 68 | local func, err = loadfile(file, "t", env) 69 | if not func then 70 | return nil, err 71 | end 72 | 73 | return api.spawn(func, name, tabs[focused]) 74 | end 75 | 76 | function api.spawn(func, name, _) 77 | expect(1, func, "function") 78 | expect(2, name, "string") 79 | 80 | local new = { 81 | name = name, 82 | coro = coroutine.create(function() 83 | assert(xpcall(func, debug.traceback)) 84 | end), 85 | vars = setmetatable({}, {__index = current and current.vars}), 86 | env = getfenv(func) or _ENV or _G, 87 | tab = _ or tabs[focused], 88 | id = #threads + 1, 89 | dir = current and current.dir or "/" 90 | } 91 | 92 | new.tab[new.id] = true 93 | threads[new.id] = new 94 | 95 | new.tab.name = name 96 | 97 | return new.id 98 | end 99 | 100 | function api.exists(id) 101 | expect(1, id, "number") 102 | return not not threads[id] 103 | end 104 | 105 | function api.id() 106 | return current.id 107 | end 108 | 109 | function api.dir() 110 | return current.dir or "/" 111 | end 112 | 113 | function api.setDir(dir) 114 | expect(1, dir, "string") 115 | 116 | if not fs.exists(dir) then 117 | return nil, "that directory does not exist" 118 | 119 | elseif not fs.isDir(dir) then 120 | return nil, "not a directory" 121 | end 122 | 123 | current.dir = dir 124 | 125 | return true 126 | end 127 | 128 | function api.vars() 129 | return current.vars 130 | end 131 | 132 | function api.getTerm() 133 | return current and current.tab and current.tab.term or term.native() 134 | end 135 | 136 | function api.setTerm(new) 137 | if tabs[focused] then 138 | local old = tabs[focused].term 139 | tabs[focused].term = new 140 | return old 141 | end 142 | end 143 | 144 | local w, h 145 | local function getName(tab) 146 | local highest = 0 147 | 148 | for k in pairs(tab) do 149 | if type(k) == "number" then highest = math.max(highest, k) end 150 | end 151 | 152 | return threads[highest] and threads[highest].name or "???" 153 | end 154 | 155 | function api.info() 156 | local running = {} 157 | for i, thread in pairs(threads) do 158 | running[#running+1] = { id = i, name = thread.name, tab = thread.tab.id } 159 | end 160 | 161 | table.sort(running, function(a,b) return a.id < b.id end) 162 | 163 | return running 164 | end 165 | 166 | function api.remove(id) 167 | expect(1, id, "number", "nil") 168 | threads[id or current.id] = nil 169 | end 170 | 171 | local scroll = 0 172 | local totalNameLength = 0 173 | local function redraw() 174 | w, h = wrappedNative.getSize() 175 | 176 | wrappedNative.setVisible(false) 177 | 178 | local names = {} 179 | totalNameLength = 0 180 | for i=1, #tabs, 1 do 181 | names[i] = " " .. getName(tabs[i]) .. " " 182 | totalNameLength = totalNameLength + #names[i] 183 | end 184 | 185 | if #tabs > 1 then 186 | local len = -scroll + 1 187 | wrappedNative.setCursorPos(1, 1) 188 | wrappedNative.setTextColor(colors.black) 189 | wrappedNative.setBackgroundColor(colors.gray) 190 | wrappedNative.clearLine() 191 | 192 | for i=1, #tabs, 1 do 193 | local tab = tabs[i] 194 | local name = names[i] 195 | 196 | wrappedNative.setCursorPos(len, 1) 197 | len = len + #name 198 | 199 | if i == focused then 200 | wrappedNative.setTextColor(colors.yellow) 201 | wrappedNative.setBackgroundColor(colors.black) 202 | wrappedNative.write(name) 203 | 204 | else 205 | wrappedNative.setTextColor(colors.black) 206 | wrappedNative.setBackgroundColor(colors.gray) 207 | wrappedNative.write(name) 208 | end 209 | 210 | tab.term.setVisible(false) 211 | tab.term.reposition(1, 2, w, h - 1) 212 | end 213 | 214 | if totalNameLength > w-2 then 215 | wrappedNative.setTextColor(colors.black) 216 | wrappedNative.setBackgroundColor(colors.gray) 217 | if scroll > 0 then 218 | wrappedNative.setCursorPos(1, 1) 219 | wrappedNative.write("<") 220 | end 221 | if totalNameLength - scroll > w-1 then 222 | wrappedNative.setCursorPos(w, 1) 223 | wrappedNative.write(">") 224 | end 225 | end 226 | 227 | tabs[focused].term.setVisible(true) 228 | 229 | elseif #tabs > 0 then 230 | local tab = tabs[1] 231 | tab.term.reposition(1, 1, w, h) 232 | tab.term.setVisible(true) 233 | end 234 | 235 | wrappedNative.setVisible(true) 236 | end 237 | 238 | local inputEvents = { 239 | key = true, 240 | char = true, 241 | key_up = true, 242 | mouse_up = true, 243 | mouse_drag = true, 244 | mouse_click = true, 245 | mouse_scroll = true, 246 | terminate = true, 247 | } 248 | 249 | local altIsDown 250 | 251 | local function processEvent(event) 252 | if inputEvents[event[1]] then 253 | if #event > 3 then -- mouse event 254 | 255 | if #tabs > 1 then 256 | if event[4] == 1 then 257 | local curX = -scroll 258 | 259 | if event[1] == "mouse_scroll" then 260 | scroll = math.max(0, math.min(totalNameLength-w+1, 261 | scroll - event[2])) 262 | return false 263 | end 264 | 265 | for i=1, #tabs, 1 do 266 | local tab = tabs[i] 267 | curX = curX + #getName(tab) + 2 268 | 269 | if event[3] <= curX then 270 | focused = i 271 | redraw() 272 | break 273 | end 274 | end 275 | 276 | return false 277 | 278 | else 279 | event[4] = event[4] - 1 280 | end 281 | end 282 | 283 | elseif event[1] == "key" then 284 | if event[2] == keys.rightAlt then 285 | altIsDown = event[2] 286 | return false 287 | 288 | elseif altIsDown then 289 | local num = tonumber(keys.getName(event[2])) 290 | if num then 291 | if tabs[num] then 292 | focused = num 293 | redraw() 294 | return false 295 | end 296 | 297 | elseif event[2] == keys.left then 298 | focused = math.max(1, focused - 1) 299 | redraw() 300 | return false 301 | 302 | elseif event[2] == keys.right then 303 | focused = math.min(#tabs, focused + 1) 304 | redraw() 305 | return false 306 | 307 | elseif event[2] == keys.up then 308 | scroll = math.max(0, math.min(totalNameLength-w+1, 309 | scroll + 1)) 310 | return false 311 | 312 | elseif event[2] == keys.down then 313 | scroll = math.max(0, math.min(totalNameLength-w+1, 314 | scroll - 1)) 315 | return false 316 | end 317 | end 318 | 319 | elseif event[1] == "key_up" then 320 | if event[2] == keys.rightAlt then 321 | altIsDown = false 322 | return false 323 | end 324 | 325 | end 326 | end 327 | 328 | return true 329 | end 330 | 331 | local function cleanTabs() 332 | for t=#tabs, 1, -1 do 333 | local tab = tabs[t] 334 | 335 | local count, removed = 0, 0 336 | for i in pairs(tab) do 337 | if type(i) == "number" then 338 | count = count + 1 339 | if not threads[i] then 340 | removed = removed + 1 341 | tab[i] = nil 342 | end 343 | end 344 | end 345 | 346 | if count == removed then 347 | table.remove(tabs, t) 348 | end 349 | end 350 | 351 | for i=1, #tabs, 1 do 352 | tabs[i].id = i 353 | end 354 | 355 | focused = math.max(1, math.min(#tabs, focused)) 356 | end 357 | 358 | function api.start() 359 | api.start = nil 360 | 361 | local _native = term.native() 362 | wrappedNative = window.create(_native, 1, 1, _native.getSize()) 363 | api.launchTab("/rc/programs/shell.lua", "shell") 364 | 365 | rc.pushEvent("init") 366 | 367 | while #tabs > 0 and next(threads) do 368 | cleanTabs() 369 | redraw() 370 | local event = table.pack(coroutine.yield()) 371 | 372 | if event[1] == "term_resize" then 373 | wrappedNative.reposition(1, 1, _native.getSize()) 374 | end 375 | 376 | if processEvent(event) then 377 | for tid, thread in pairs(threads) do 378 | if thread.tab == tabs[focused] or not inputEvents[event[1]] then 379 | current = thread 380 | local result = table.pack(coroutine.resume(thread.coro, 381 | table.unpack(event, 1, event.n))) 382 | 383 | if not result[1] then 384 | io.stderr:write(result[2].."\n") 385 | threads[tid] = nil 386 | 387 | elseif coroutine.status(thread.coro) == "dead" then 388 | threads[tid] = nil 389 | end 390 | end 391 | end 392 | end 393 | end 394 | 395 | rc.shutdown() 396 | end 397 | 398 | return api 399 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/apis/window.lua: -------------------------------------------------------------------------------- 1 | -- window api 2 | 3 | local term = require("term") 4 | local colors = require("colors") 5 | local expect = require("cc.expect").expect 6 | local range = require("cc.expect").range 7 | local window = {} 8 | 9 | local rep = string.rep 10 | local sub = string.sub 11 | local max = math.max 12 | local min = math.min 13 | 14 | local function into_buffer(buf, x, y, text) 15 | if not text then return end 16 | if not buf[y] then return end 17 | text = sub(text, 1, #buf[y] - x + 1) 18 | if x < 1 then 19 | text = sub(text, -x + 2) 20 | x = 1 21 | end 22 | local olen = #buf[y] 23 | if x + #text > olen then 24 | buf[y] = sub(buf[y], 0, max(0, x-1)) .. text 25 | else 26 | buf[y] = sub(buf[y], 0, max(0, x-1)) .. text .. buf[y]:sub(x + #text) 27 | end 28 | buf[y] = sub(buf[y], 1, olen) 29 | end 30 | 31 | function window.create(parent, x, y, width, height, visible) 32 | if type(parent) ~= "table" then expect(1, parent, "table") end 33 | if parent == term then 34 | error("do not pass 'term' as a window parent", 0) 35 | end 36 | if type(x) ~= "number" then expect(2, x, "number") end 37 | if type(y) ~= "number" then expect(3, y, "number") end 38 | if type(width) ~= "number" then expect(4, width, "number") end 39 | if type(height) ~= "number" then expect(5, height, "number") end 40 | if type(visible) ~= "boolean" then expect(6, visible, "boolean", "nil") end 41 | if visible == nil then visible = true end 42 | 43 | local cursorX, cursorY, cursorBlink = 1, 1, false 44 | local foreground, background = colors.toBlit(colors.white), 45 | colors.toBlit(colors.black) 46 | local textbuf, fgbuf, bgbuf = {}, {}, {} 47 | 48 | local win = {} 49 | 50 | local palette = {} 51 | for i=0, 15, 1 do 52 | palette[i] = colors.packRGB(parent.getPaletteColor(2^i)) 53 | end 54 | 55 | local function drawLine(i) 56 | parent.setCursorPos(x, y + i - 1) 57 | if not textbuf[i] then return end 58 | parent.blit(textbuf[i], fgbuf[i], bgbuf[i]) 59 | end 60 | 61 | local function draw() 62 | local blink = parent.getCursorBlink() 63 | parent.setCursorBlink(false) 64 | 65 | local parentW, parentH = parent.getSize() 66 | local firstVisible = math.max(1, -y+2) 67 | 68 | for i=1, math.min(height, parentH), 1 do 69 | drawLine(firstVisible+i-1) 70 | end 71 | 72 | parent.setCursorBlink(blink) 73 | end 74 | 75 | local function restorePalette() 76 | for i=0, 15, 1 do 77 | parent.setPaletteColor(2^i, palette[i]) 78 | end 79 | end 80 | 81 | local function restoreCursorBlink() 82 | parent.setCursorBlink(cursorBlink) 83 | end 84 | 85 | local function restoreCursorPos() 86 | if cursorX > 0 and cursorY > 0 and 87 | cursorX <= width and cursorY <= height then 88 | parent.setCursorPos(x + cursorX - 1, y + cursorY - 1) 89 | else 90 | parent.setCursorPos(0, 0) 91 | end 92 | end 93 | 94 | local function restoreCursorColor() 95 | parent.setTextColor(2^tonumber(foreground, 16)) 96 | end 97 | 98 | function win.write(text) 99 | if type(text) ~= "string" then expect(1, text, "string") end 100 | local fg, bg = rep(foreground, #text), background:rep(#text) 101 | into_buffer(textbuf, cursorX, cursorY, text) 102 | into_buffer(fgbuf, cursorX, cursorY, fg) 103 | into_buffer(bgbuf, cursorX, cursorY, bg) 104 | cursorX = max(-100, min(cursorX + #text, width + 1)) 105 | 106 | local firstVisible, _, maxHeight = math.max(1, -y+2), parent.getSize() 107 | if visible and cursorY >= firstVisible and cursorY <= firstVisible+maxHeight then win.redraw() end 108 | end 109 | 110 | function win.blit(text, tcol, bcol) 111 | if type(text) ~= "string" then expect(1, text, "string") end 112 | if type(tcol) ~= "string" then expect(2, tcol, "string") end 113 | if type(bcol) ~= "string" then expect(3, bcol, "string") end 114 | assert(#text == #tcol and #text == #bcol, "mismatched argument lengths") 115 | 116 | into_buffer(textbuf, cursorX, cursorY, text) 117 | into_buffer(fgbuf, cursorX, cursorY, tcol) 118 | into_buffer(bgbuf, cursorX, cursorY, bcol) 119 | cursorX = max(0, min(cursorX + #text, width + 1)) 120 | 121 | local firstVisible, _, maxHeight = math.max(1, -y+2), parent.getSize() 122 | if visible and cursorY >= firstVisible and cursorY <= firstVisible+maxHeight then 123 | drawLine(cursorY) 124 | restoreCursorColor() 125 | restoreCursorPos() 126 | end 127 | end 128 | 129 | function win.clear() 130 | local fore = rep(foreground, width) 131 | local back = rep(background, width) 132 | local blank = rep(" ", width) 133 | 134 | for i=1, height, 1 do 135 | textbuf[i] = blank 136 | fgbuf[i] = fore 137 | bgbuf[i] = back 138 | end 139 | 140 | if visible then 141 | win.redraw() 142 | end 143 | end 144 | 145 | 146 | function win.clearLine() 147 | local emptyText, emptyFg, emptyBg = 148 | rep(" ", width), 149 | rep(foreground, width), 150 | rep(background, width) 151 | 152 | textbuf[cursorY] = emptyText 153 | fgbuf[cursorY] = emptyFg 154 | bgbuf[cursorY] = emptyBg 155 | 156 | local firstVisible, _, maxHeight = math.max(1, -y+2), parent.getSize() 157 | if visible and cursorY >= firstVisible and cursorY <= firstVisible+maxHeight then 158 | win.redraw() 159 | end 160 | end 161 | 162 | function win.getCursorPos() 163 | return cursorX, cursorY 164 | end 165 | 166 | function win.setCursorPos(_x, _y) 167 | if type(_x) ~= "number" then expect(1, _x, "number") end 168 | if type(_y) ~= "number" then expect(2, _y, "number") end 169 | 170 | cursorX, cursorY = _x, _y 171 | if visible then 172 | restoreCursorPos() 173 | end 174 | end 175 | 176 | function win.setCursorBlink(blink) 177 | cursorBlink = not not blink 178 | if visible then 179 | restoreCursorBlink() 180 | end 181 | end 182 | 183 | function win.getCursorBlink() 184 | return cursorBlink 185 | end 186 | 187 | function win.isColor() 188 | return parent.isColor() 189 | end 190 | 191 | win.isColour = win.isColor 192 | 193 | function win.setTextColor(color) 194 | if type(color) ~= "number" then expect(1, color, "number") end 195 | foreground = colors.toBlit(color) or foreground 196 | if visible then 197 | restoreCursorColor() 198 | end 199 | end 200 | 201 | win.setTextColour = win.setTextColor 202 | 203 | function win.setPaletteColor(color, r, g, b) 204 | if type(color) ~= "number" then expect(1, color, "number") end 205 | if type(r) ~= "number" then expect(2, r, "number") end 206 | 207 | if r < 1 then 208 | if type(g) ~= "number" then expect(3, g, "number") end 209 | if type(b) ~= "number" then expect(4, b, "number") end 210 | palette[math.floor(math.log(color, 2))] = colors.packRGB(r, g, b) 211 | else 212 | palette[math.floor(math.log(color, 2))] = r 213 | end 214 | 215 | if visible then 216 | restorePalette() 217 | end 218 | end 219 | 220 | win.setPaletteColour = win.setPaletteColor 221 | 222 | function win.getPaletteColor(color) 223 | if type(color) ~= "number" then expect(1, color, "number") end 224 | return palette[math.floor(math.log(color, 2))] 225 | end 226 | 227 | win.getPaletteColour = win.getPaletteColor 228 | 229 | function win.setBackgroundColor(color) 230 | if type(color) ~= "number" then expect(1, color, "number") end 231 | background = colors.toBlit(color) 232 | end 233 | 234 | win.setBackgroundColour = win.setBackgroundColor 235 | 236 | function win.getSize() 237 | return width, height 238 | end 239 | 240 | function win.scroll(n) 241 | if type(n) ~= "number" then expect(1, n, "number") end 242 | 243 | if n == 0 then return end 244 | local fg = rep(foreground, width) 245 | local bg = rep(background, width) 246 | local blank = rep(" ", width) 247 | 248 | if n > 0 then 249 | for _=1, n, 1 do 250 | table.remove(textbuf, 1) 251 | textbuf[#textbuf+1] = blank 252 | table.remove(fgbuf, 1) 253 | fgbuf[#fgbuf+1] = fg 254 | table.remove(bgbuf, 1) 255 | bgbuf[#bgbuf+1] = bg 256 | end 257 | else 258 | for _=1, -n, 1 do 259 | table.insert(textbuf, 1, blank) 260 | textbuf[#textbuf] = nil 261 | table.insert(fgbuf, 1, fg) 262 | fgbuf[#fgbuf] = nil 263 | table.insert(bgbuf, 1, bg) 264 | bgbuf[#bgbuf] = nil 265 | end 266 | end 267 | 268 | if visible then 269 | win.redraw() 270 | end 271 | end 272 | 273 | function win.getTextColor() 274 | return 2^tonumber(foreground, 16) 275 | end 276 | 277 | win.getTextColour = win.getTextColor 278 | 279 | function win.getBackgroundColor() 280 | return 2^tonumber(background, 16) 281 | end 282 | 283 | win.getBackgroundColour = win.getBackgroundColor 284 | 285 | function win.getLine(ly) 286 | if type(ly) ~= "number" then expect(1, ly, "number") end 287 | if ly < 1 or ly > height then range(ly, 1, height) end 288 | return textbuf[ly], fgbuf[ly], bgbuf[ly] 289 | end 290 | 291 | function win.setVisible(vis) 292 | if vis and not visible then 293 | draw() 294 | restorePalette() 295 | restoreCursorBlink() 296 | restoreCursorPos() 297 | restoreCursorColor() 298 | end 299 | visible = not not vis 300 | end 301 | 302 | function win.redraw() 303 | if visible then 304 | draw() 305 | restorePalette() 306 | restoreCursorPos() 307 | restoreCursorBlink() 308 | restoreCursorColor() 309 | end 310 | end 311 | 312 | function win.restoreCursor() 313 | if visible then 314 | restoreCursorBlink() 315 | restoreCursorPos() 316 | restoreCursorColor() 317 | end 318 | end 319 | 320 | function win.getPosition() 321 | return x, y 322 | end 323 | 324 | local function resize_buffer(buf, nw, nh, c) 325 | if nw > width then 326 | for i=1, #buf, 1 do 327 | buf[i] = buf[i] .. sub(rep(buf[i], -1), nw - width) 328 | end 329 | end 330 | 331 | if nh > #buf then 332 | for _=1, nh - #buf, 1 do 333 | buf[#buf+1] = rep(c, nw) 334 | end 335 | end 336 | end 337 | 338 | function win.reposition(nx, ny, nw, nh, npar) 339 | if type(nx) ~= "number" then expect(1, nx, "number") end 340 | if type(ny) ~= "number" then expect(2, ny, "number") end 341 | if type(nw) ~= "number" then expect(3, nw, "number", "nil") end 342 | if type(nh) ~= "number" then expect(4, nh, "number", "nil") end 343 | if type(npar) ~= "table" then expect(5, npar, "table", "nil") end 344 | 345 | x, y, width, height, parent = 346 | nx or x, ny or y, 347 | nw or width, nh or height, 348 | npar or parent 349 | 350 | resize_buffer(textbuf, width, height, " ") 351 | resize_buffer(fgbuf, width, height, "0") 352 | resize_buffer(bgbuf, width, height, "f") 353 | 354 | if visible then 355 | win.redraw() 356 | end 357 | end 358 | 359 | function win.at(_x, _y) 360 | win.setCursorPos(_x, _y) 361 | return win 362 | end 363 | 364 | win.clear() 365 | return win 366 | end 367 | 368 | return window 369 | -------------------------------------------------------------------------------- /data/computercraft/lua/rom/modules/main/rc/json.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- json.lua 3 | -- 4 | -- Copyright (c) 2020 rxi 5 | -- 6 | -- Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | -- this software and associated documentation files (the "Software"), to deal in 8 | -- the Software without restriction, including without limitation the rights to 9 | -- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 | -- of the Software, and to permit persons to whom the Software is furnished to do 11 | -- so, subject to the following conditions: 12 | -- 13 | -- The above copyright notice and this permission notice shall be included in all 14 | -- copies or substantial portions of the Software. 15 | -- 16 | -- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | -- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | -- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | -- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | -- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | -- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | -- SOFTWARE. 23 | -- 24 | 25 | local json = { _version = "0.1.2" } 26 | 27 | ------------------------------------------------------------------------------- 28 | -- Encode 29 | ------------------------------------------------------------------------------- 30 | 31 | local encode 32 | 33 | local escape_char_map = { 34 | [ "\\" ] = "\\", 35 | [ "\"" ] = "\"", 36 | [ "\b" ] = "b", 37 | [ "\f" ] = "f", 38 | [ "\n" ] = "n", 39 | [ "\r" ] = "r", 40 | [ "\t" ] = "t", 41 | } 42 | 43 | local escape_char_map_inv = { [ "/" ] = "/" } 44 | for k, v in pairs(escape_char_map) do 45 | escape_char_map_inv[v] = k 46 | end 47 | 48 | 49 | local function escape_char(c) 50 | return "\\" .. (escape_char_map[c] or string.format("u%04x", c:byte())) 51 | end 52 | 53 | 54 | local function encode_nil(val) 55 | return "null" 56 | end 57 | 58 | 59 | local function encode_table(val, stack) 60 | local res = {} 61 | stack = stack or {} 62 | 63 | -- Circular reference? 64 | if stack[val] then error("circular reference") end 65 | 66 | stack[val] = true 67 | 68 | if rawget(val, 1) ~= nil or next(val) == nil then 69 | -- Treat as array -- check keys are valid and it is not sparse 70 | local n = 0 71 | for k in pairs(val) do 72 | if type(k) ~= "number" then 73 | error("invalid table: mixed or invalid key types") 74 | end 75 | n = n + 1 76 | end 77 | if n ~= #val then 78 | error("invalid table: sparse array") 79 | end 80 | -- Encode 81 | for i, v in ipairs(val) do 82 | table.insert(res, encode(v, stack)) 83 | end 84 | stack[val] = nil 85 | return "[" .. table.concat(res, ",") .. "]" 86 | 87 | else 88 | -- Treat as an object 89 | for k, v in pairs(val) do 90 | if type(k) ~= "string" then 91 | error("invalid table: mixed or invalid key types") 92 | end 93 | table.insert(res, encode(k, stack) .. ":" .. encode(v, stack)) 94 | end 95 | stack[val] = nil 96 | return "{" .. table.concat(res, ",") .. "}" 97 | end 98 | end 99 | 100 | 101 | local function encode_string(val) 102 | return '"' .. val:gsub('[%z\1-\31\\"]', escape_char) .. '"' 103 | end 104 | 105 | 106 | local function encode_number(val) 107 | -- Check for NaN, -inf and inf 108 | if val ~= val or val <= -math.huge or val >= math.huge then 109 | error("unexpected number value '" .. tostring(val) .. "'") 110 | end 111 | return string.format("%.14g", val) 112 | end 113 | 114 | 115 | local type_func_map = { 116 | [ "nil" ] = encode_nil, 117 | [ "table" ] = encode_table, 118 | [ "string" ] = encode_string, 119 | [ "number" ] = encode_number, 120 | [ "boolean" ] = tostring, 121 | } 122 | 123 | 124 | encode = function(val, stack) 125 | local t = type(val) 126 | local f = type_func_map[t] 127 | if f then 128 | return f(val, stack) 129 | end 130 | error("unexpected type '" .. t .. "'") 131 | end 132 | 133 | 134 | function json.encode(val) 135 | return ( encode(val) ) 136 | end 137 | 138 | 139 | ------------------------------------------------------------------------------- 140 | -- Decode 141 | ------------------------------------------------------------------------------- 142 | 143 | local parse 144 | 145 | local function create_set(...) 146 | local res = {} 147 | for i = 1, select("#", ...) do 148 | res[ select(i, ...) ] = true 149 | end 150 | return res 151 | end 152 | 153 | local space_chars = create_set(" ", "\t", "\r", "\n") 154 | local delim_chars = create_set(" ", "\t", "\r", "\n", "]", "}", ",") 155 | local escape_chars = create_set("\\", "/", '"', "b", "f", "n", "r", "t", "u") 156 | local literals = create_set("true", "false", "null") 157 | 158 | local literal_map = { 159 | [ "true" ] = true, 160 | [ "false" ] = false, 161 | [ "null" ] = nil, 162 | } 163 | 164 | 165 | local function next_char(str, idx, set, negate) 166 | for i = idx, #str do 167 | if set[str:sub(i, i)] ~= negate then 168 | return i 169 | end 170 | end 171 | return #str + 1 172 | end 173 | 174 | 175 | local function decode_error(str, idx, msg) 176 | local line_count = 1 177 | local col_count = 1 178 | for i = 1, idx - 1 do 179 | col_count = col_count + 1 180 | if str:sub(i, i) == "\n" then 181 | line_count = line_count + 1 182 | col_count = 1 183 | end 184 | end 185 | error( string.format("%s at line %d col %d", msg, line_count, col_count) ) 186 | end 187 | 188 | 189 | local function codepoint_to_utf8(n) 190 | -- http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=iws-appendixa 191 | local f = math.floor 192 | if n <= 0x7f then 193 | return string.char(n) 194 | elseif n <= 0x7ff then 195 | return string.char(f(n / 64) + 192, n % 64 + 128) 196 | elseif n <= 0xffff then 197 | return string.char(f(n / 4096) + 224, f(n % 4096 / 64) + 128, n % 64 + 128) 198 | elseif n <= 0x10ffff then 199 | return string.char(f(n / 262144) + 240, f(n % 262144 / 4096) + 128, 200 | f(n % 4096 / 64) + 128, n % 64 + 128) 201 | end 202 | error( string.format("invalid unicode codepoint '%x'", n) ) 203 | end 204 | 205 | 206 | local function parse_unicode_escape(s) 207 | local n1 = tonumber( s:sub(1, 4), 16 ) 208 | local n2 = tonumber( s:sub(7, 10), 16 ) 209 | -- Surrogate pair? 210 | if n2 then 211 | return codepoint_to_utf8((n1 - 0xd800) * 0x400 + (n2 - 0xdc00) + 0x10000) 212 | else 213 | return codepoint_to_utf8(n1) 214 | end 215 | end 216 | 217 | 218 | local function parse_string(str, i) 219 | local res = "" 220 | local j = i + 1 221 | local k = j 222 | 223 | while j <= #str do 224 | local x = str:byte(j) 225 | 226 | if x < 32 then 227 | decode_error(str, j, "control character in string") 228 | 229 | elseif x == 92 then -- `\`: Escape 230 | res = res .. str:sub(k, j - 1) 231 | j = j + 1 232 | local c = str:sub(j, j) 233 | if c == "u" then 234 | local hex = str:match("^[dD][89aAbB]%x%x\\u%x%x%x%x", j + 1) 235 | or str:match("^%x%x%x%x", j + 1) 236 | or decode_error(str, j - 1, "invalid unicode escape in string") 237 | res = res .. parse_unicode_escape(hex) 238 | j = j + #hex 239 | else 240 | if not escape_chars[c] then 241 | decode_error(str, j - 1, "invalid escape char '" .. c .. "' in string") 242 | end 243 | res = res .. escape_char_map_inv[c] 244 | end 245 | k = j + 1 246 | 247 | elseif x == 34 then -- `"`: End of string 248 | res = res .. str:sub(k, j - 1) 249 | return res, j + 1 250 | end 251 | 252 | j = j + 1 253 | end 254 | 255 | decode_error(str, i, "expected closing quote for string") 256 | end 257 | 258 | 259 | local function parse_number(str, i) 260 | local x = next_char(str, i, delim_chars) 261 | local s = str:sub(i, x - 1) 262 | local n = tonumber(s) 263 | if not n then 264 | decode_error(str, i, "invalid number '" .. s .. "'") 265 | end 266 | return n, x 267 | end 268 | 269 | 270 | local function parse_literal(str, i) 271 | local x = next_char(str, i, delim_chars) 272 | local word = str:sub(i, x - 1) 273 | if not literals[word] then 274 | decode_error(str, i, "invalid literal '" .. word .. "'") 275 | end 276 | return literal_map[word], x 277 | end 278 | 279 | 280 | local function parse_array(str, i) 281 | local res = {} 282 | local n = 1 283 | i = i + 1 284 | while 1 do 285 | local x 286 | i = next_char(str, i, space_chars, true) 287 | -- Empty / end of array? 288 | if str:sub(i, i) == "]" then 289 | i = i + 1 290 | break 291 | end 292 | -- Read token 293 | x, i = parse(str, i) 294 | res[n] = x 295 | n = n + 1 296 | -- Next token 297 | i = next_char(str, i, space_chars, true) 298 | local chr = str:sub(i, i) 299 | i = i + 1 300 | if chr == "]" then break end 301 | if chr ~= "," then decode_error(str, i, "expected ']' or ','") end 302 | end 303 | return res, i 304 | end 305 | 306 | 307 | local function parse_object(str, i) 308 | local res = {} 309 | i = i + 1 310 | while 1 do 311 | local key, val 312 | i = next_char(str, i, space_chars, true) 313 | -- Empty / end of object? 314 | if str:sub(i, i) == "}" then 315 | i = i + 1 316 | break 317 | end 318 | -- Read key 319 | if str:sub(i, i) ~= '"' then 320 | decode_error(str, i, "expected string for key") 321 | end 322 | key, i = parse(str, i) 323 | -- Read ':' delimiter 324 | i = next_char(str, i, space_chars, true) 325 | if str:sub(i, i) ~= ":" then 326 | decode_error(str, i, "expected ':' after key") 327 | end 328 | i = next_char(str, i + 1, space_chars, true) 329 | -- Read value 330 | val, i = parse(str, i) 331 | -- Set 332 | res[key] = val 333 | -- Next token 334 | i = next_char(str, i, space_chars, true) 335 | local chr = str:sub(i, i) 336 | i = i + 1 337 | if chr == "}" then break end 338 | if chr ~= "," then decode_error(str, i, "expected '}' or ','") end 339 | end 340 | return res, i 341 | end 342 | 343 | 344 | local char_func_map = { 345 | [ '"' ] = parse_string, 346 | [ "0" ] = parse_number, 347 | [ "1" ] = parse_number, 348 | [ "2" ] = parse_number, 349 | [ "3" ] = parse_number, 350 | [ "4" ] = parse_number, 351 | [ "5" ] = parse_number, 352 | [ "6" ] = parse_number, 353 | [ "7" ] = parse_number, 354 | [ "8" ] = parse_number, 355 | [ "9" ] = parse_number, 356 | [ "-" ] = parse_number, 357 | [ "t" ] = parse_literal, 358 | [ "f" ] = parse_literal, 359 | [ "n" ] = parse_literal, 360 | [ "[" ] = parse_array, 361 | [ "{" ] = parse_object, 362 | } 363 | 364 | 365 | parse = function(str, idx) 366 | local chr = str:sub(idx, idx) 367 | local f = char_func_map[chr] 368 | if f then 369 | return f(str, idx) 370 | end 371 | decode_error(str, idx, "unexpected character '" .. chr .. "'") 372 | end 373 | 374 | 375 | function json.decode(str) 376 | if type(str) ~= "string" then 377 | error("expected argument of type string, got " .. type(str)) 378 | end 379 | local res, idx = parse(str, next_char(str, 1, space_chars, true)) 380 | idx = next_char(str, idx, space_chars, true) 381 | if idx <= #str then 382 | decode_error(str, idx, "trailing garbage") 383 | end 384 | return res 385 | end 386 | 387 | 388 | return json 389 | --------------------------------------------------------------------------------