Merge branch 'OC1.6-MC1.7.10' of github.com:MightyPirates/OpenComputers into OC1.6-MC1.8.9

# Conflicts:
#	src/main/java/li/cil/oc/api/IMC.java
#	src/main/scala/li/cil/oc/common/IMC.scala
This commit is contained in:
Florian Nücke 2016-06-12 21:16:40 +02:00
commit 1b1456951e
45 changed files with 1180 additions and 348 deletions

View File

@ -2,7 +2,7 @@ minecraft.version=1.8.9
forge.version=11.15.1.1764 forge.version=11.15.1.1764
oc.version=1.6.0 oc.version=1.6.0
oc.subversion=beta.2 oc.subversion=beta.3
ae2.version=rv2-beta-26 ae2.version=rv2-beta-26
bc.version=7.0.9 bc.version=7.0.9

View File

@ -3,6 +3,7 @@ package li.cil.oc.api;
import net.minecraft.item.ItemStack; import net.minecraft.item.ItemStack;
import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagCompound;
import net.minecraft.nbt.NBTTagList; import net.minecraft.nbt.NBTTagList;
import net.minecraft.nbt.NBTTagString;
import net.minecraftforge.fml.common.event.FMLInterModComms; import net.minecraftforge.fml.common.event.FMLInterModComms;
import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.lang3.tuple.Pair;
@ -20,6 +21,7 @@ import org.apache.commons.lang3.tuple.Pair;
* copy this class while keeping the package name, to avoid conflicts if this * copy this class while keeping the package name, to avoid conflicts if this
* class gets updated. * class gets updated.
*/ */
@SuppressWarnings("unused")
public final class IMC { public final class IMC {
/** /**
* Register a callback that is used as a filter for assembler templates. * Register a callback that is used as a filter for assembler templates.
@ -37,7 +39,7 @@ public final class IMC {
* *
* @param callback the callback to register as a filtering method. * @param callback the callback to register as a filtering method.
*/ */
public static void registerAssemblerFilter(String callback) { public static void registerAssemblerFilter(final String callback) {
FMLInterModComms.sendMessage(MOD_ID, "registerAssemblerFilter", callback); FMLInterModComms.sendMessage(MOD_ID, "registerAssemblerFilter", callback);
} }
@ -98,7 +100,7 @@ public final class IMC {
* with only two card slots will pass <tt>null</tt> * with only two card slots will pass <tt>null</tt>
* for the third component slot. Up to nine. * for the third component slot. Up to nine.
*/ */
public static void registerAssemblerTemplate(String name, String select, String validate, String assemble, Class host, int[] containerTiers, int[] upgradeTiers, Iterable<Pair<String, Integer>> componentSlots) { public static void registerAssemblerTemplate(final String name, final String select, final String validate, final String assemble, final Class host, final int[] containerTiers, final int[] upgradeTiers, final Iterable<Pair<String, Integer>> componentSlots) {
final NBTTagCompound nbt = new NBTTagCompound(); final NBTTagCompound nbt = new NBTTagCompound();
if (name != null) { if (name != null) {
nbt.setString("name", name); nbt.setString("name", name);
@ -187,7 +189,7 @@ public final class IMC {
* @param disassemble callback used to apply a template and extract * @param disassemble callback used to apply a template and extract
* ingredients from an item. * ingredients from an item.
*/ */
public static void registerDisassemblerTemplate(String name, String select, String disassemble) { public static void registerDisassemblerTemplate(final String name, final String select, final String disassemble) {
final NBTTagCompound nbt = new NBTTagCompound(); final NBTTagCompound nbt = new NBTTagCompound();
if (name != null) { if (name != null) {
nbt.setString("name", name); nbt.setString("name", name);
@ -218,7 +220,7 @@ public final class IMC {
* *
* @param callback the callback to register as a durability provider. * @param callback the callback to register as a durability provider.
*/ */
public static void registerToolDurabilityProvider(String callback) { public static void registerToolDurabilityProvider(final String callback) {
FMLInterModComms.sendMessage(MOD_ID, "registerToolDurabilityProvider", callback); FMLInterModComms.sendMessage(MOD_ID, "registerToolDurabilityProvider", callback);
} }
@ -242,7 +244,7 @@ public final class IMC {
* *
* @param callback the callback to register as a wrench tool handler. * @param callback the callback to register as a wrench tool handler.
*/ */
public static void registerWrenchTool(String callback) { public static void registerWrenchTool(final String callback) {
FMLInterModComms.sendMessage(MOD_ID, "registerWrenchTool", callback); FMLInterModComms.sendMessage(MOD_ID, "registerWrenchTool", callback);
} }
@ -265,7 +267,7 @@ public final class IMC {
* *
* @param callback the callback to register as a wrench tool tester. * @param callback the callback to register as a wrench tool tester.
*/ */
public static void registerWrenchToolCheck(String callback) { public static void registerWrenchToolCheck(final String callback) {
FMLInterModComms.sendMessage(MOD_ID, "registerWrenchToolCheck", callback); FMLInterModComms.sendMessage(MOD_ID, "registerWrenchToolCheck", callback);
} }
@ -291,7 +293,7 @@ public final class IMC {
* @param canCharge the callback to register for checking chargeability. * @param canCharge the callback to register for checking chargeability.
* @param charge the callback to register for charging items. * @param charge the callback to register for charging items.
*/ */
public static void registerItemCharge(String name, String canCharge, String charge) { public static void registerItemCharge(final String name, final String canCharge, final String charge) {
final NBTTagCompound nbt = new NBTTagCompound(); final NBTTagCompound nbt = new NBTTagCompound();
nbt.setString("name", name); nbt.setString("name", name);
nbt.setString("canCharge", canCharge); nbt.setString("canCharge", canCharge);
@ -319,7 +321,7 @@ public final class IMC {
* *
* @param callback the callback to register as an ink provider. * @param callback the callback to register as an ink provider.
*/ */
public static void registerInkProvider(String callback) { public static void registerInkProvider(final String callback) {
FMLInterModComms.sendMessage(MOD_ID, "registerInkProvider", callback); FMLInterModComms.sendMessage(MOD_ID, "registerInkProvider", callback);
} }
@ -332,7 +334,7 @@ public final class IMC {
* *
* @param peripheral the class of the peripheral to blacklist. * @param peripheral the class of the peripheral to blacklist.
*/ */
public static void blacklistPeripheral(Class peripheral) { public static void blacklistPeripheral(final Class peripheral) {
FMLInterModComms.sendMessage(MOD_ID, "blacklistPeripheral", peripheral.getName()); FMLInterModComms.sendMessage(MOD_ID, "blacklistPeripheral", peripheral.getName());
} }
@ -351,7 +353,7 @@ public final class IMC {
* @param host the class of the host to blacklist the component for. * @param host the class of the host to blacklist the component for.
* @param stack the item stack representing the blacklisted component. * @param stack the item stack representing the blacklisted component.
*/ */
public static void blacklistHost(String name, Class host, ItemStack stack) { public static void blacklistHost(final String name, final Class host, final ItemStack stack) {
final NBTTagCompound nbt = new NBTTagCompound(); final NBTTagCompound nbt = new NBTTagCompound();
nbt.setString("name", name); nbt.setString("name", name);
nbt.setString("host", host.getName()); nbt.setString("host", host.getName());
@ -372,6 +374,44 @@ public final class IMC {
FMLInterModComms.sendMessage(MOD_ID, "registerCustomPowerSystem", "true"); FMLInterModComms.sendMessage(MOD_ID, "registerCustomPowerSystem", "true");
} }
/**
* Register a mapping of program name to loot disk.
* <p/>
* The table of mappings is made available to machines to allow displaying
* a message to the user telling her on which floppy disk to find the program
* they were trying to run.
* <p/>
* For Lua programs, this should be the program <em>name</em>, i.e. the file
* name without the <code>.lua</code> extension.
* <p/>
* The list of architectures is optional, if it is not specified this mapping
* will be made available to all architectures. It allows filtering since
* typically programs will be written for one specific architecture type, e.g.
* Lua programs will not (directly) work on a MIPS architecture. The name
* specified is the in the {@link li.cil.oc.api.machine.Architecture.Name}
* annotation of the architecture (also shown in the CPU tooltip).
* <p/>
* The architecture names for Lua are <code>Lua 5.2</code>, <code>Lua 5.3</code>
* and <code>LuaJ</code> for example.
*
* @param programName the name of the program.
* @param diskLabel the label of the disk the program is on.
* @param architectures the names of the architectures this entry applies to.
*/
public static void registerProgramDiskLabel(final String programName, final String diskLabel, final String... architectures) {
final NBTTagCompound nbt = new NBTTagCompound();
nbt.setString("program", programName);
nbt.setString("label", diskLabel);
if (architectures != null && architectures.length > 0) {
final NBTTagList architecturesNbt = new NBTTagList();
for (final String architecture : architectures) {
architecturesNbt.appendTag(new NBTTagString(architecture));
}
nbt.setTag("architectures", architecturesNbt);
}
FMLInterModComms.sendMessage(MOD_ID, "registerProgramDiskLabel", nbt);
}
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
private static final String MOD_ID = "OpenComputers"; private static final String MOD_ID = "OpenComputers";

View File

@ -27,6 +27,7 @@
- `getInput(index:number)` - Запрос текущего состояния контакта с указанным индексом. - `getInput(index:number)` - Запрос текущего состояния контакта с указанным индексом.
- `setInput(index:number, value:boolean)` - Устанавливает указанный контакт в указанное состояние. - `setInput(index:number, value:boolean)` - Устанавливает указанный контакт в указанное состояние.
- `getActiveEffects()` - Запрос списка активных эффектов. Некоторые эффекты могут быть не показаны в этом списке. - `getActiveEffects()` - Запрос списка активных эффектов. Некоторые эффекты могут быть не показаны в этом списке.
- `saveConfiguration()` - Требует наличия в инвентаре нанороботов, сохраняет текущую конфигурацию нанороботов игрока в них.
Например, в OpenOS: Например, в OpenOS:
- `component.modem.broadcast(1, "nanomachines", "setInput", 1, true)` активирует первый контакт. - `component.modem.broadcast(1, "nanomachines", "setInput", 1, true)` активирует первый контакт.

View File

@ -1,31 +0,0 @@
local event = require("event")
local fs = require("filesystem")
local process = require("process")
local proxy = ...
-- Install symlinks if they don't already exist.
local links = {}
local fsroot = fs.path(process.running())
local function inject(path)
for file in fs.list(fs.concat(fsroot, path)) do
local source = fs.concat(fsroot, path, file)
local target = fs.concat(path, file)
if fs.link(source, target) then
table.insert(links, target)
end
end
end
inject("lib")
inject("bin")
inject("usr/man")
-- Delete symlinks on removal.
event.listen("component_removed", function(_, address)
if address == proxy.address then
for _, link in ipairs(links) do
fs.remove(link)
end
return false -- remove listener
end
end)

View File

@ -1,31 +0,0 @@
local event = require("event")
local fs = require("filesystem")
local process = require("process")
local proxy = ...
-- Install symlinks if they don't already exist.
local links = {}
local fsroot = fs.path(process.running())
local function inject(path)
for file in fs.list(fs.concat(fsroot, path)) do
local source = fs.concat(fsroot, path, file)
local target = fs.concat(path, file)
if fs.link(source, target) then
table.insert(links, target)
end
end
end
inject("lib")
inject("bin")
inject("usr/man")
-- Delete symlinks on removal.
event.listen("component_removed", function(_, address)
if address == proxy.address then
for _, link in ipairs(links) do
fs.remove(link)
end
return false -- remove listener
end
end)

View File

@ -1,31 +0,0 @@
local event = require("event")
local fs = require("filesystem")
local process = require("process")
local proxy = ...
-- Install symlinks if they don't already exist.
local links = {}
local fsroot = fs.path(process.running())
local function inject(path)
for file in fs.list(fs.concat(fsroot, path)) do
local source = fs.concat(fsroot, path, file)
local target = fs.concat(path, file)
if fs.link(source, target) then
table.insert(links, target)
end
end
end
inject("lib")
inject("bin")
inject("usr/man")
-- Delete symlinks on removal.
event.listen("component_removed", function(_, address)
if address == proxy.address then
for _, link in ipairs(links) do
fs.remove(link)
end
return false -- remove listener
end
end)

View File

@ -1 +0,0 @@
return {name = "OpenOS"}

View File

@ -0,0 +1 @@
{label = "OpenOS", reboot=true, setlabel=true, setboot=true}

View File

@ -4,32 +4,27 @@ local fs = require("filesystem")
local args = shell.parse(...) local args = shell.parse(...)
local ec = 0 local ec = 0
if #args == 0 then if #args == 0 then
repeat args = {"-"}
local read = io.read("*L") end
if read then
io.write(read) for i = 1, #args do
end local arg = args[i]
until not read if fs.isDirectory(arg) then
else io.stderr:write(string.format('cat %s: Is a directory\n', arg))
for i = 1, #args do ec = 1
local arg = args[i] else
if fs.isDirectory(arg) then local file, reason = args[i] == "-" and io.stdin or io.open(shell.resolve(args[i]))
io.stderr:write(string.format('cat %s: Is a directory\n', arg)) if not file then
io.stderr:write(string.format("cat: %s: %s\n",args[i],tostring(reason)))
ec = 1 ec = 1
else else
local file, reason = args[i] == "-" and io.stdin or io.open(shell.resolve(args[i])) repeat
if not file then local line = file:read("*L")
io.stderr:write(string.format("cat: %s: %s\n",args[i],tostring(reason))) if line then
ec = 1 io.write(line)
else end
repeat until not line
local line = file:read("*L") file:close()
if line then
io.write(line)
end
until not line
file:close()
end
end end
end end
end end

View File

@ -1,5 +1,6 @@
local fs = require("filesystem") local fs = require("filesystem")
local shell = require("shell") local shell = require("shell")
local computer = require("computer")
local args, options = shell.parse(...) local args, options = shell.parse(...)
if #args < 2 then if #args < 2 then
@ -15,19 +16,20 @@ if #args < 2 then
return 1 return 1
end end
local exit_code = nil
options.P = options.P or options.r options.P = options.P or options.r
local from = {} -- interrupting is important, but not EVERY copy
for i = 1, #args - 1 do local greedy = computer.uptime()
table.insert(from, shell.resolve(args[i]))
end
local to = shell.resolve(args[#args])
local function status(from, to) local function status(from, to)
if options.v then if options.v then
io.write(from .. " -> " .. to .. "\n") io.write(from .. " -> " .. to .. "\n")
end end
os.sleep(0) -- allow interrupting if computer.uptime() - greedy > 4 then
os.sleep(0) -- allow interrupting
greedy = computer.uptime()
end
end end
local result, reason local result, reason
@ -68,14 +70,25 @@ for dev,path in fs.mounts() do
end end
local function recurse(fromPath, toPath, origin) local function recurse(fromPath, toPath, origin)
status(fromPath, toPath)
local isLink, target = fs.isLink(fromPath) local isLink, target = fs.isLink(fromPath)
if isLink and options.P then local toIsLink, toLinkTarget = fs.isLink(toPath)
local same_path = fs.canonical(isLink and target or fromPath) == fs.canonical(toIsLink and toLinkTarget or toPath)
local toExists = fs.exists(toPath)
if isLink and options.P and (not toExists or not same_path) then
if toExists and options.n then
return true
end
fs.remove(toPath)
if toExists and options.v then
io.write(string.format("removed '%s'\n", toPath))
end
status(fromPath, toPath)
return fs.link(target, toPath) return fs.link(target, toPath)
end elseif fs.isDirectory(fromPath) then
if fs.isDirectory(fromPath) then
if not options.r then if not options.r then
io.write("omitting directory `" .. fromPath .. "'\n") io.write("omitting directory `" .. fromPath .. "'\n")
exit_code = 1
return true return true
end end
if fs.exists(toPath) and not fs.isDirectory(toPath) then if fs.exists(toPath) and not fs.isDirectory(toPath) then
@ -85,10 +98,13 @@ local function recurse(fromPath, toPath, origin)
if options.x and origin and mounts[fs.canonical(fromPath)] then if options.x and origin and mounts[fs.canonical(fromPath)] then
return true return true
end end
if fs.get(fromPath) == fs.get(toPath) and fs.canonical(fs.path(toPath)):find(fs.canonical(fromPath),1,true) then if fs.get(fromPath) == fs.get(toPath) and fs.canonical(toPath):find(fs.canonical(fromPath),1,true) then
return nil, "cannot copy a directory, `" .. fromPath .. "', into itself, `" .. toPath .. "'" return nil, "cannot copy a directory, `" .. fromPath .. "', into itself, `" .. toPath .. "'"
end end
fs.makeDirectory(toPath) if not fs.exists(toPath) then
status(fromPath, toPath)
fs.makeDirectory(toPath)
end
for file in fs.list(fromPath) do for file in fs.list(fromPath) do
local result, reason = recurse(fs.concat(fromPath, file), fs.concat(toPath, file), origin or fs.get(fromPath)) local result, reason = recurse(fs.concat(fromPath, file), fs.concat(toPath, file), origin or fs.get(fromPath))
if not result then if not result then
@ -96,44 +112,53 @@ local function recurse(fromPath, toPath, origin)
end end
end end
return true return true
else elseif fs.exists(fromPath) then
if fs.exists(toPath) then if toExists then
if fs.canonical(fromPath) == fs.canonical(toPath) then if same_path then
return nil, "`" .. fromPath .. "' and `" .. toPath .. "' are the same file" return nil, "`" .. fromPath .. "' and `" .. toPath .. "' are the same file"
end end
if fs.isDirectory(toPath) then
if options.i then if options.n then
if not prompt("overwrite `" .. toPath .. "'?") then return true
return true
end
elseif options.n then
return true
else -- yes, even for -f
return nil, "cannot overwrite directory `" .. toPath .. "' with non-directory"
end
else
if options.u then
if areEqual(fromPath, toPath) then
return true
end
end
if options.i then
if not prompt("overwrite `" .. toPath .. "'?") then
return true
end
elseif options.n then
return true
end
-- else: default to overwriting
end end
-- if target is link, we are updating the target
if toIsLink then
toPath = toLinkTarget
end
if options.u and not fs.isDirectory(toPath) and areEqual(fromPath, toPath) then
return true
end
if options.i then
if not prompt("overwrite `" .. toPath .. "'?") then
return true
end
end
if fs.isDirectory(toPath) then
return nil, "cannot overwrite directory `" .. toPath .. "' with non-directory"
end
fs.remove(toPath) fs.remove(toPath)
end end
status(fromPath, toPath)
return fs.copy(fromPath, toPath) return fs.copy(fromPath, toPath)
else
return nil, "`" .. fromPath .. "': No such file or directory"
end end
end end
for _, fromPath in ipairs(from) do
local to = shell.resolve(args[#args])
for i = 1, #args - 1 do
local fromPath, cuts = args[i]:gsub("(/%.%.?)$", "%1")
fromPath = shell.resolve(fromPath)
local toPath = to local toPath = to
if fs.isDirectory(toPath) then -- fromPath ending with /. indicates copying the contents of fromPath
-- in which case (cuts>0) we do not append fromPath name to toPath
if cuts == 0 and fs.isDirectory(toPath) then
toPath = fs.concat(toPath, fs.name(fromPath)) toPath = fs.concat(toPath, fs.name(fromPath))
end end
result, reason = recurse(fromPath, toPath) result, reason = recurse(fromPath, toPath)
@ -144,3 +169,5 @@ for _, fromPath in ipairs(from) do
return 1 return 1
end end
end end
return exit_code

View File

@ -27,7 +27,7 @@ if #args == 0 then
end end
else else
for i = 1, #args do for i = 1, #args do
local proxy, path = fs.get(args[i]) local proxy, path = fs.get(shell.resolve(args[i]))
if not proxy then if not proxy then
io.stderr:write(args[i], ": no such file or directory\n") io.stderr:write(args[i], ": no such file or directory\n")
else else

View File

@ -110,7 +110,7 @@ for i=1,#args do
local file local file
if arg == '-' then if arg == '-' then
arg = 'standard input' arg = 'standard input'
file = setmetatable({close=function()end},{__index=io.stdin}) file = io.stdin
else else
file, reason = io.open(arg, 'r') file, reason = io.open(arg, 'r')
if not file then if not file then
@ -119,7 +119,7 @@ for i=1,#args do
end end
if file then if file then
if verbose or #args > 1 then if verbose or #args > 1 then
io.write(string.format('==> %s <==', arg)) io.write(string.format('==> %s <==\n', arg))
end end
local stream = new_stream() local stream = new_stream()

View File

@ -1,80 +1,44 @@
local component = require("component")
local computer = require("computer") local computer = require("computer")
local event = require("event")
local filesystem = require("filesystem")
local unicode = require("unicode")
local shell = require("shell") local shell = require("shell")
local args, options = shell.parse(...) local options
local fromAddress = options.from and component.get(options.from) or filesystem.get(os.getenv("_")).address do
local candidates = {} local basic = loadfile("/lib/tools/install_basics.lua", "bt", _G)
for address in component.list("filesystem", true) do options = basic(...)
local dev = component.proxy(address)
if not dev.isReadOnly() and dev.address ~= computer.tmpAddress() and dev.address ~= fromAddress then
table.insert(candidates, dev)
end
end end
if #candidates == 0 then if computer.freeMemory() < 50000 then
io.stderr:write("No writable disks found, aborting.\n") print("Low memory, collecting garbage")
return 1 for i=1,20 do os.sleep(0) end
end end
for i = 1, #candidates do local cp, reason = loadfile(shell.resolve("cp", "lua"), "bt", _G)
local label = candidates[i].getLabel()
if label then local ec = cp(table.unpack(options.cp_args))
label = label .. " (" .. candidates[i].address:sub(1, 8) .. "...)" if ec ~= nil and ec ~= 0 then
else return ec
label = candidates[i].address
end
io.write(i .. ") " .. label .. "\n")
end end
io.write("To select the device to install to, please enter a number between 1 and " .. #candidates .. ".\n") local write = io.write
io.write("Press 'q' to cancel the installation.\n") local read = io.read
local choice write("Installation complete!\n")
while not choice do
result = io.read() if options.setlabel then
if result:sub(1, 1):lower() == "q" then pcall(options.target.dev.setLabel, options.label)
os.exit()
end
local number = tonumber(result)
if number and number > 0 and number <= #candidates then
choice = candidates[number]
else
io.write("Invalid input, please try again.\n")
end
end end
local function findMount(address) if options.setboot then
for fs, path in filesystem.mounts() do computer.setBootAddress(options.target.dev.address)
if fs.address == component.get(address) then
return path
end
end
end end
local name = options.name or "OpenOS" if options.reboot then
io.write("Installing " .. name .." to device " .. (choice.getLabel() or choice.address) .. "\n") write("Reboot now? [Y/n] ")
os.sleep(0.25) local result = read()
local cpPath = filesystem.concat(findMount(filesystem.get(os.getenv("_")).address), "bin/cp")
local cpOptions = "-vrx" .. (options.u and "ui " or "")
local cpSource = filesystem.concat(findMount(fromAddress), options.fromDir or "/")
local cpDest = findMount(choice.address) .. "/"
local result, reason = os.execute(cpPath .. " " .. cpOptions .. " " .. cpSource .. " " .. cpDest)
if not result then
error(reason, 0)
end
if not options.nolabelset then pcall(choice.setLabel, name) end
if not options.noreboot then
io.write("All done! " .. ((not options.noboot) and "Set as boot device and r" or "R") .. "eboot now? [Y/n]\n")
local result = io.read()
if not result or result == "" or result:sub(1, 1):lower() == "y" then if not result or result == "" or result:sub(1, 1):lower() == "y" then
if not options.noboot then computer.setBootAddress(choice.address)end write("\nRebooting now!\n")
io.write("\nRebooting now!\n")
computer.shutdown(true) computer.shutdown(true)
end end
end end
io.write("Returning to shell.\n")
write("Returning to shell.\n")

View File

@ -0,0 +1,300 @@
local keyboard = require("keyboard")
local shell = require("shell")
local term = require("term")
local text = require("text")
local unicode = require("unicode")
local computer = require("computer")
local tx = require("transforms")
local args = shell.parse(...)
if #args > 1 then
io.write("Usage: more <filename>\n")
io.write("- or no args reads stdin\n")
return 1
end
local arg = args[1] or "-"
local initial_offset
-- test validity of args
do
if arg == "-" then
if not io.stdin then
io.stderr:write("this process has no stdin\n")
return 1
end
-- stdin may not be core_stdin
initial_offset = io.stdin:seek("cur")
else
local file, reason = io.open(shell.resolve(arg))
if not file then
io.stderr:write(reason,'\n')
return 1
end
initial_offset = file:seek("cur")
file:close()
end
end
local width, height = term.getViewport()
local max_display = height - 1
-- mgr is the data manager, it keeps track of what has been loaded
-- keeps a reasonable buffer, and keeps track of file handles
local mgr
mgr =
{
lines = {}, -- current buffer
chunk, -- temp from last read line that hasn't finished wrapping
lines_released = 0,
can_seek = initial_offset,
capacity = math.max(1, math.min(max_display * 10, computer.freeMemory() / 2 / width)),
size = 0,
file = nil,
path = arg ~= "-" and shell.resolve(arg) or nil,
open = function()
mgr.file = mgr.path and io.open(mgr.path) or io.stdin
end,
top_of_file = max_display,
total_lines = nil, -- nil means unknown
latest_line = nil, -- used for status improvements
rollback = function()
if not mgr.can_seek then
return false
end
if not mgr.file then
mgr.open()
elseif not mgr.file:seek("set", 0) then
mgr.close()
return false
end
mgr.lines_released = 0
mgr.lines = {}
mgr.size = 0
return true
end,
at = function(line_number)
local index = line_number - mgr.lines_released
if index < 1 then
index = index + mgr.capacity
if #mgr.lines ~= mgr.capacity or index <= mgr.size then
return nil
end
elseif index > mgr.size then
return nil
end
return mgr.lines[index] -- cached
end,
load = function(line_number)
local index = line_number - mgr.lines_released
if mgr.total_lines and mgr.total_lines < line_number then
return nil
end
if mgr.at(line_number) then
return true
end
-- lines[index] is line (lines_released + index) in the file
-- thus index == line_number - lines_released
if index <= 0 then
-- we have previously freed some of the buffer, and now the user wants it back
if not mgr.rollback() then
-- TODO how to nicely fail if can_seek == false
-- or if no more buffers
error("cannot load prior data")
end
return mgr.load(line_number) -- retry
end
if mgr.read_next() then
return mgr.load(line_number) -- retry
end
-- ran out of file, could not reach line_number
end,
write = function(line_number)
local line = mgr.at(line_number)
if not line then return false end
term.write(line)
end,
close = function()
if mgr.file then
mgr.file:close()
mgr.file = nil
end
end,
last = function()
-- return the last line_number available right now in the cache
return mgr.size + mgr.lines_released
end,
check_capacity = function(release)
-- if we have reached capacity
if mgr.size >= mgr.capacity then
if release then
mgr.lines_released = mgr.lines_released + mgr.size
mgr.size = 0
end
return true
end
end,
insert = function(line)
if mgr.check_capacity() then return false end
mgr.size = mgr.size + 1
mgr.lines[mgr.size] = line
-- latest_line is not used for computation, just for status reports
mgr.latest_line = math.max(mgr.latest_line or 0, mgr.size + mgr.lines_released)
return true
end,
read_next = function()
-- total_lines indicates we've reached the end previously
-- but have we just prior to this reached the end?
if mgr.last() == mgr.total_lines then
-- then there is no more after that point
return nil
end
if not mgr.file then
mgr.open()
end
mgr.check_capacity(true)
if not mgr.chunk then
mgr.chunk = mgr.file:read("*l")
if not mgr.chunk then
mgr.total_lines = mgr.size + mgr.lines_released -- now file length is known
mgr.close()
end
end
while mgr.chunk do
local wrapped, next = text.wrap(text.detab(mgr.chunk), width, width)
-- insert fails if capacity is full
if not mgr.insert(wrapped) then
return mgr.last()
end
mgr.chunk = next
end
return mgr.last()
end,
scroll = function(num)
if num < 0 then
num = math.max(num, mgr.top_of_file)
if num >= 0 then
return true -- nothing to scroll
end
end
term.setCursor(1, height)
local y = height
term.clearLine()
if num < 0 then
term.scroll(num) -- push text down
mgr.top_of_file = mgr.top_of_file - num
y = 1
term.setCursor(1, y) -- ready to write lines above
num = -num -- now print forward
end
local range
while num > 0 do
-- trigger load of data if needed
local line_number = y - mgr.top_of_file
if not mgr.load(line_number) then -- nothing more to read from the file
return range ~= nil -- first time it is nil
end
-- print num range of what is available, scroll to show it (if bottom of screen)
range = math.min(num, mgr.last() - line_number + 1)
if y == height then
range = math.min(range, max_display)
term.scroll(range)
y = y - range
term.setCursor(1, y)
mgr.top_of_file = mgr.top_of_file - range
end
for i=1,range do
mgr.write(line_number + i - 1)
term.setCursor(1, y + i)
end
y = y + range
num = num - range
end
return true
end,
print_status = function()
local first = mgr.top_of_file >= 1 and 1 or 1 - mgr.top_of_file
local perc = not mgr.total_lines and "--" or tostring((max_display - mgr.top_of_file) / mgr.total_lines * 100):gsub("%..*","")
local last_plus = mgr.total_lines and "" or "+"
local status = string.format("%s lines %d-%d/%s %s%%", mgr.path or "-", first, max_display - mgr.top_of_file, tostring(mgr.total_lines or mgr.latest_line)..last_plus, perc)
local gpu = term.gpu()
local sf, sb = gpu.setForeground, gpu.setBackground
local b_color, b_is_palette = gpu.getBackground()
local f_color, f_is_palette = gpu.getForeground()
sf(b_color, b_is_palette)
sb(f_color, f_is_palette)
term.write(status)
sb(b_color, b_is_palette)
sf(f_color, f_is_palette)
end
}
local function update(num)
-- unexpected
if num == 0 then
return
end
-- if this a positive direction, and we didn't previously know this was the end of the stream, give the user a once chance
local end_is_known = mgr.total_lines
-- clear buttom line, for status
local ok = mgr.scroll(num or max_display)
-- print status
term.setCursor(1, height)
-- we have to clear again in case we scrolled up
term.clearLine()
mgr.print_status()
return not end_is_known or ok
end
if not update() then
return
end
while true do
local ename, address, char, code, dy = term.pull()
local num
if ename == "scroll" then
if dy < 0 then
num = 3
else
num = -3
end
elseif ename == "key_down" then
num = 0
if code == keyboard.keys.q or code == keyboard.keys.d and keyboard.isControlDown() then
break
elseif code == keyboard.keys.space or code == keyboard.keys.pageDown then
num = max_display
elseif code == keyboard.keys.pageUp then
num = -max_display
elseif code == keyboard.keys.enter or code == keyboard.keys.down then
num = 1
elseif code == keyboard.keys.up then
num = -1
elseif code == keyboard.keys.home then
num = -math.huge
elseif code == keyboard.keys["end"] then
num = math.huge
end
elseif ename == "interrupted" then
break
end
if num then
update(num)
end
end
term.clearLine()

View File

@ -16,6 +16,10 @@ else
linkpath = fs.concat(shell.getWorkingDirectory(), fs.name(target)) linkpath = fs.concat(shell.getWorkingDirectory(), fs.name(target))
end end
if fs.isDirectory(linkpath) then
linkpath = fs.concat(linkpath, fs.name(target))
end
local result, reason = fs.link(target, linkpath) local result, reason = fs.link(target, linkpath)
if not result then if not result then
io.stderr:write(reason..'\n') io.stderr:write(reason..'\n')

View File

@ -9,8 +9,33 @@ if #args < 2 then
return 1 return 1
end end
local function is_mount(path)
if not fs.isDirectory(path) then return false end
path = fs.canonical(path) .. '/'
for driver, mount_point in fs.mounts() do
if path == mount_point then
return true
end
end
end
local function is_readonly(path)
return fs.get(path).isReadOnly()
end
local from = shell.resolve(args[1]) local from = shell.resolve(args[1])
local to = shell.resolve(args[2]) local to = shell.resolve(args[2])
if is_readonly(to) then
io.stderr:write("cannot write to " .. to .. ", filesystem is readonly\n");
return 1
end
if is_mount(from) then
io.stderr:write("cannot move " .. from .. ", it is a mount point\n");
return 1
end
if fs.isDirectory(to) then if fs.isDirectory(to) then
to = to .. "/" .. fs.name(from) to = to .. "/" .. fs.name(from)
end end

View File

@ -32,7 +32,7 @@ local metas = {}
local function _path(m) return shell.resolve(m.rel) end local function _path(m) return shell.resolve(m.rel) end
local function _link(m) return fs.isLink(_path(m)) end local function _link(m) return fs.isLink(_path(m)) end
local function _exists(m) return _link(m) or fs.exists(_path(m)) end local function _exists(m) return _link(m) or fs.exists(_path(m)) end
local function _dir(m) return fs.isDirectory(_path(m)) end local function _dir(m) return not _link(m) and fs.isDirectory(_path(m)) end
local function _readonly(m) return not _exists(m) or fs.get(_path(m)).isReadOnly() end local function _readonly(m) return not _exists(m) or fs.get(_path(m)).isReadOnly() end
local function _empty(m) return _exists(m) and _dir(m) and (fs.list(_path(m))==nil) end local function _empty(m) return _exists(m) and _dir(m) and (fs.list(_path(m))==nil) end

View File

@ -6,7 +6,6 @@ alias copy=cp
alias del=rm alias del=rm
alias md=mkdir alias md=mkdir
alias cls=clear alias cls=clear
alias less=more
alias rs=redstone alias rs=redstone
alias view=edit\ -r alias view=edit\ -r
alias help=man alias help=man

View File

@ -378,16 +378,23 @@ end
function filesystem.remove(path) function filesystem.remove(path)
local function removeVirtual() local function removeVirtual()
local node, rest, vnode, vrest = findNode(filesystem.path(path)) local node, rest, vnode, vrest = findNode(filesystem.path(path))
local name = filesystem.name(path) -- vrest represents the remaining path beyond vnode
if vnode.children[name] then -- vrest is nil if vnode reaches the full path
vnode.children[name] = nil -- thus, if vrest is NOT NIL, then we SHOULD NOT remove children nor links
removeEmptyNodes(vnode) if not vrest then
return true local name = filesystem.name(path)
elseif vnode.links[name] then if vnode.children[name] then
vnode.links[name] = nil vnode.children[name] = nil
removeEmptyNodes(vnode) removeEmptyNodes(vnode)
return true return true
elseif vnode.links[name] then
vnode.links[name] = nil
removeEmptyNodes(vnode)
return true
end
end end
-- return false even if vrest is nil because this means it was a expected
-- to be a real file
return false return false
end end
local function removePhysical() local function removePhysical()

View File

@ -28,7 +28,7 @@ end
function guid.next() function guid.next()
-- e.g. 3c44c8a9-0613-46a2-ad33-97b6ba2e9d9a -- e.g. 3c44c8a9-0613-46a2-ad33-97b6ba2e9d9a
-- 8-4-4-4-12 -- 8-4-4-4-12
local sets = {8, 4, 4, 12} local sets = {8, 4, 4, 4, 12}
local result = "" local result = ""
local i local i

View File

@ -64,9 +64,8 @@ local delay_tools = setmetatable({},{__mode="v"})
package.delay_data = delay_data package.delay_data = delay_data
function delay_data.__index(tbl,key) function delay_data.__index(tbl,key)
local lookup = delay_tools.lookup or loadfile("/lib/tools/delayLookup.lua") delay_data.lookup = delay_data.lookup or loadfile("/lib/tools/delayLookup.lua")
delay_tools.lookup = lookup return delay_data.lookup(delay_data, tbl, key)
return lookup(delay_data, tbl, key)
end end
delay_data.__pairs = delay_data.__index -- nil key acts like pairs delay_data.__pairs = delay_data.__index -- nil key acts like pairs

View File

@ -1,5 +1,11 @@
local serialization = {} local serialization = {}
-- delay loaded tables fail to deserialize cross [C] boundaries (such as when having to read files that cause yields)
local local_pairs = function(tbl)
local mt = getmetatable(tbl)
return (mt and mt.__pairs or pairs)(tbl)
end
-- Important: pretty formatting will allow presenting non-serializable values -- Important: pretty formatting will allow presenting non-serializable values
-- but may generate output that cannot be unserialized back. -- but may generate output that cannot be unserialized back.
function serialization.serialize(value, pretty) function serialization.serialize(value, pretty)
@ -44,7 +50,7 @@ function serialization.serialize(value, pretty)
local f local f
if pretty then if pretty then
local ks, sks, oks = {}, {}, {} local ks, sks, oks = {}, {}, {}
for k in pairs(v) do for k in local_pairs(v) do
if type(k) == "number" then if type(k) == "number" then
table.insert(ks, k) table.insert(ks, k)
elseif type(k) == "string" then elseif type(k) == "string" then
@ -72,7 +78,7 @@ function serialization.serialize(value, pretty)
end end
end) end)
else else
f = table.pack(pairs(v)) f = table.pack(local_pairs(v))
end end
for k, v in table.unpack(f) do for k, v in table.unpack(f) do
if r then if r then

View File

@ -217,9 +217,14 @@ function sh.internal.parseCommand(words)
table.insert(evaluated_words, arg) table.insert(evaluated_words, arg)
end end
end end
local program, reason = shell.resolve(evaluated_words[1], "lua") local eword = evaluated_words[1]
local possible_dir_path = shell.resolve(eword)
if possible_dir_path and fs.isDirectory(possible_dir_path) then
return nil, string.format("%s: is a directory", eword)
end
local program, reason = shell.resolve(eword, "lua")
if not program then if not program then
return nil, evaluated_words[1] .. ": " .. reason return nil, eword .. ": " .. reason
end end
evaluated_words = tx.sub(evaluated_words, 2) evaluated_words = tx.sub(evaluated_words, 2)
return program, evaluated_words return program, evaluated_words
@ -420,6 +425,10 @@ function --[[@delayloaded-start@]] sh.internal.buildPipeChain(threads)
if i < #threads then if i < #threads then
pipe = require("buffer").new("rw", sh.internal.newMemoryStream()) pipe = require("buffer").new("rw", sh.internal.newMemoryStream())
pipe:setvbuf("no") pipe:setvbuf("no")
-- buffer close flushes the buffer, but we have no buffer
-- also, when the buffer is closed, read and writes don't pass through
-- simply put, we don't want buffer:close
pipe.close = function(self) self.stream:close() end
pipe.stream.redirect[1] = rawget(pio, 1) pipe.stream.redirect[1] = rawget(pio, 1)
pio[1] = pipe pio[1] = pipe
table.insert(data.handles, pipe) table.insert(data.handles, pipe)
@ -507,7 +516,12 @@ function --[[@delayloaded-start@]] sh.getMatchingPrograms(baseName)
return result return result
end --[[@delayloaded-end@]] end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] sh.getMatchingFiles(basePath, name) function --[[@delayloaded-start@]] sh.getMatchingFiles(partial_path)
-- name: text of the partial file name being expanded
local name = partial_path:gsub("^.*/", "")
-- here we remove the name text from the partialPrefix
local basePath = unicode.sub(partial_path, 1, -unicode.len(name) - 1)
local resolvedPath = shell.resolve(basePath) local resolvedPath = shell.resolve(basePath)
local result, baseName = {} local result, baseName = {}
@ -525,7 +539,7 @@ function --[[@delayloaded-start@]] sh.getMatchingFiles(basePath, name)
for file in fs.list(resolvedPath) do for file in fs.list(resolvedPath) do
local match = file:match(baseName) local match = file:match(baseName)
if match then if match then
table.insert(result, basePath .. match) table.insert(result, basePath .. match:gsub("(%s)", "\\%1"))
end end
end end
-- (cont.) but if there's only one match and it's a directory, *then* we -- (cont.) but if there's only one match and it's a directory, *then* we
@ -537,19 +551,57 @@ function --[[@delayloaded-start@]] sh.getMatchingFiles(basePath, name)
end --[[@delayloaded-end@]] end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] sh.internal.hintHandlerSplit(line) function --[[@delayloaded-start@]] sh.internal.hintHandlerSplit(line)
if line:sub(-1):find("%s") then -- I do not plan on having text tokenizer parse error on
return '', line -- trailiing \ in case of future support for multiple line
end -- input. But, there are also no hints for it
local splits = text.internal.tokenize(line) if line:match("\\$") then return nil end
local splits, simple = text.internal.tokenize(line,{show_escapes=true})
if not splits then -- parse error, e.g. unclosed quotes if not splits then -- parse error, e.g. unclosed quotes
return nil -- no split, no hints return nil -- no split, no hints
end end
local num_splits = #splits local num_splits = #splits
if num_splits == 1 or not isWordOf(splits[num_splits-1],{";","&&","||","|"}) then
return '', line -- search for last statement delimiters
local last_close = 0
for index = num_splits, 1, -1 do
local word = splits[index]
if isWordOf(word, {";","&&","||","|"}) then
last_close = index
break
end
end
-- if the very last word of the line is a delimiter
-- consider this a fresh new, empty line
-- this captures edge cases with empty input as well (i.e. no splits)
if last_close == num_splits then
return nil -- no hints on empty command
end
local last_word = splits[num_splits]
local normal = text.internal.normalize({last_word})[1]
-- if there is white space following the words
-- and we have at least one word following the last delimiter
-- then in all cases we are looking for ANY arg
if unicode.sub(line, -unicode.len(normal)) ~= normal then
return line, nil, ""
end
local prefix = unicode.sub(line, 1, -unicode.len(normal) - 1)
-- renormlizing the string will create 'printed' quality text
normal = text.internal.normalize(text.internal.tokenize(normal), true)[1]
-- one word: cmd
-- many: arg
if last_close == num_splits - 1 then
return prefix, normal, nil
else
return prefix, nil, normal
end end
local l = text.internal.normalize({splits[num_splits]})[1]
return line:sub(1,-unicode.len(l)-1), l
end --[[@delayloaded-end@]] end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] sh.internal.hintHandlerImpl(full_line, cursor) function --[[@delayloaded-start@]] sh.internal.hintHandlerImpl(full_line, cursor)
@ -557,52 +609,47 @@ function --[[@delayloaded-start@]] sh.internal.hintHandlerImpl(full_line, cursor
local line = unicode.sub(full_line, 1, cursor - 1) local line = unicode.sub(full_line, 1, cursor - 1)
-- suffix: text following the cursor (if any, else empty string) to append to the hints -- suffix: text following the cursor (if any, else empty string) to append to the hints
local suffix = unicode.sub(full_line, cursor) local suffix = unicode.sub(full_line, cursor)
-- if there is no text to hint, there are no hints
if not line or #line < 1 then
return {}
end
-- hintHandlerSplit helps make the hints work even after delimiters such as ; -- hintHandlerSplit helps make the hints work even after delimiters such as ;
-- it also catches parse errors such as unclosed quotes -- it also catches parse errors such as unclosed quotes
local prev,line = sh.internal.hintHandlerSplit(line) -- prev: not needed for this hint
if not prev then -- failed to parse, e.g. unclosed quote, no hints -- cmd: the command needing hint
-- arg: the argument needing hint
local prev, cmd, arg = sh.internal.hintHandlerSplit(line)
-- also, if there is no text to hint, there are no hints
if not prev then -- no hints e.g. unclosed quote, e.g. no text
return {} return {}
end end
local result local result
-- prefix: text (if any) that will not be expanded (such as a command word preceding a file name that we are expanding)
-- partial: text that we want to expand local searchInPath = cmd and not cmd:find("/")
-- this first match determines if partial comes after redirect symbols such as >
local prefix, partial = line:match("^(.*[=><]%s*)(.*)$")
-- if redirection was not found, partial could just be arguments following a command
if not prefix then prefix, partial = line:match("^(.+%s+)(.*)$") end
-- partialPrefix: text of the partial that will not be expanded (i.e. a diretory path ending with /)
-- first, partialPrefix holds the whole text being expanded (we truncate later)
local partialPrefix = (partial or line)
-- name: text of the partial file name being expanded
local name = partialPrefix:gsub("^.*/", "")
-- here we remove the name text from the partialPrefix
partialPrefix = partialPrefix:sub(1, -unicode.len(name) - 1)
-- if no prefix was found and partialPrefix did not specify a closed directory path then we are expanding the first argument
-- i.e. the command word (a program name)
local searchInPath = not prefix and not partialPrefix:find("/")
if searchInPath then if searchInPath then
result = sh.getMatchingPrograms(line) result = sh.getMatchingPrograms(cmd)
else else
result = sh.getMatchingFiles(partialPrefix, name) -- special arg issue, after equal sign
if arg then
local equal_index = arg:find("=[^=]*$")
if equal_index then
prev = prev .. unicode.sub(arg, 1, equal_index)
arg = unicode.sub(arg, equal_index + 1)
end
end
result = sh.getMatchingFiles(cmd or arg)
end end
-- in very special cases, the suffix should include a blank space to indicate to the user that the hint is discrete -- in very special cases, the suffix should include a blank space to indicate to the user that the hint is discrete
local resultSuffix = suffix local resultSuffix = suffix
if #result > 0 and unicode.sub(result[1], -1) ~= "/" and if #result > 0 and unicode.sub(result[1], -1) ~= "/" and
not suffix:sub(1,1):find('%s') and not suffix:sub(1,1):find('%s') and
(#result == 1 or searchInPath or not prefix) then #result == 1 or searchInPath then
resultSuffix = " " .. resultSuffix resultSuffix = " " .. resultSuffix
end end
-- prefix no longer needs to refer to just the expanding section of the text
-- here we reintroduce the previous section of the text that hintHandlerSplit cut for us
prefix = prev .. (prefix or "")
table.sort(result) table.sort(result)
for i = 1, #result do for i = 1, #result do
-- the hints define the whole line of text -- the hints define the whole line of text
result[i] = prefix .. result[i] .. resultSuffix result[i] = prev .. result[i] .. resultSuffix
end end
return result return result
end --[[@delayloaded-end@]] end --[[@delayloaded-end@]]
@ -796,7 +843,7 @@ function --[[@delayloaded-start@]] sh.internal.newMemoryStream()
table.remove(self.result, 1) table.remove(self.result, 1)
return self return self
end end
return nil, 'stream closed' os.exit(0) -- abort the current process: SIGPIPE
end end
local stream = {closed = false, buffer = "", local stream = {closed = false, buffer = "",

View File

@ -127,7 +127,7 @@ function shell.resolveAlias(command, args)
end end
function shell.getWorkingDirectory() function shell.getWorkingDirectory()
return os.getenv("PWD") return os.getenv("PWD") or "/"
end end
function shell.setWorkingDirectory(dir) function shell.setWorkingDirectory(dir)

View File

@ -89,7 +89,7 @@ function term.internal.pull(input, c, off, t, ...)
if gpu then if gpu then
gpu.set(x,y,c[3]) gpu.set(x,y,c[3])
end end
return select(2,unpack(a)) return unpack(a)
end end
local blinking = w.blink local blinking = w.blink
if input then blinking = input.blink end if input then blinking = input.blink end
@ -99,7 +99,12 @@ end
function term.pull(p,...) function term.pull(p,...)
local a,t = {p,...} local a,t = {p,...}
if type(p) == "number" then t = table.remove(a,1) end if type(p) == "number" then t = table.remove(a,1) end
return term.internal.pull(nil,nil,nil,t,table.unpack(a)) -- term.internal.pull captures hard interrupts to keep term.readKeyboard peaceful
-- but other scripts may be using term.pull as an event.pull replacement
-- in which case, term.pull need to abort on hard interrupt
local packed = {term.internal.pull(nil,nil,nil,t,table.unpack(a))}
assert(packed[1], "interrupted")
return select(2, table.unpack(packed))
end end
function term.read(history,dobreak,hintHandler,pwchar,filter) function term.read(history,dobreak,hintHandler,pwchar,filter)
@ -229,7 +234,7 @@ function term.readKeyboard(ops)
term.internal.build_vertical_reader(input) term.internal.build_vertical_reader(input)
end end
while true do while true do
local name, address, char, code = term.internal.pull(input) local killed, name, address, char, code = term.internal.pull(input)
local c = nil local c = nil
local backup_cache = hints.cache local backup_cache = hints.cache
if name =="interrupted" then draw("^C\n",true) return "" if name =="interrupted" then draw("^C\n",true) return ""
@ -251,6 +256,8 @@ function term.readKeyboard(ops)
input:move(ctrl and term.internal.ctrl_movement(input, -1) or -1) input:move(ctrl and term.internal.ctrl_movement(input, -1) or -1)
elseif code==keys.right then elseif code==keys.right then
input:move(ctrl and term.internal.ctrl_movement(input, 1) or 1) input:move(ctrl and term.internal.ctrl_movement(input, 1) or 1)
elseif ctrl and char=="w" then
-- cut word
elseif code==keys.up then elseif code==keys.up then
term.internal.read_history(history,input,1) term.internal.read_history(history,input,1)
elseif code==keys.down then elseif code==keys.down then
@ -383,6 +390,38 @@ function term.bind(gpu, screen, kb, window)
end end
end end
function --[[@delayloaded-start@]] term.scroll(number, window)
-- if zero scroll length is requested, do nothing
if number == 0 then return end
-- window is optional, default to current active terminal
window = window or W()
-- gpu works with global coordinates
local gpu,width,height,dx,dy,x,y = window.gpu,term.getViewport(w)
-- scroll request can be too large
local abs_number = math.abs(number)
if (abs_number >= height) then
term.clear()
return
end
-- box positions to shift
local box_height = height - abs_number
local top = 0
if number > 0 then
top = number -- (e.g. 1 scroll moves box at 2)
end
gpu.copy(dx + 1, dy + top + 1, width, box_height, 0, -number)
local fill_top = 0
if number > 0 then
fill_top = box_height
end
gpu.fill(dx + 1, dy + fill_top + 1, width, abs_number, " ")
end --[[@delayloaded-end@]]
function --[[@delayloaded-start@]] term.internal.ctrl_movement(input, dir) function --[[@delayloaded-start@]] term.internal.ctrl_movement(input, dir)
local index, data = input.index, input.data local index, data = input.index, input.data

View File

@ -80,24 +80,21 @@ end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
-- tokenize allows nil for delimiters, quotes, and doNotNormalize -- separate string value into an array of words delimited by whitespace
-- always separates by whitespace -- groups by quotes
-- default quote rules: '' and "" -- options is a table used for internal undocumented purposes
-- default delimiters: all function text.tokenize(value, options)
-- default is to normalize, that is, no metadata is returned, just a list of tokens
function text.tokenize(value, doNotNormalize, quotes, delimiters)
checkArg(1, value, "string") checkArg(1, value, "string")
checkArg(2, doNotNormalize, "boolean", "nil") checkArg(2, options, "table", "nil")
checkArg(3, quotes, "table", "nil") options = options or {}
checkArg(4, delimiters, "table", "nil")
local tokens, reason = text.internal.tokenize(value, quotes, delimiters) local tokens, reason = text.internal.tokenize(value, options)
if type(tokens) ~= "table" then if type(tokens) ~= "table" then
return nil, reason return nil, reason
end end
if doNotNormalize then if options.doNotNormalize then
return tokens return tokens
end end
@ -113,6 +110,9 @@ function text.removeEscapes(txt)
end end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
-- like tokenize, but does not drop any text such as whitespace
-- splits input into an array for sub strings delimited by delimiters
-- delimiters are included in the result if not dropDelims
function --[[@delayloaded-start@]] text.split(input, delimiters, dropDelims, di) function --[[@delayloaded-start@]] text.split(input, delimiters, dropDelims, di)
checkArg(1, input, "string") checkArg(1, input, "string")
checkArg(2, delimiters, "table") checkArg(2, delimiters, "table")
@ -153,14 +153,15 @@ end --[[@delayloaded-end@]]
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
function text.internal.tokenize(value, quotes, delimiters) function text.internal.tokenize(value, options)
checkArg(1, value, "string") checkArg(1, value, "string")
checkArg(2, quotes, "table", "nil") checkArg(2, options, "table", "nil")
checkArg(3, delimiters, "table", "nil") options = options or {}
local custom = not not delimiters local delimiters = options.delimiters
local custom = not not options.delimiters
delimiters = delimiters or text.syntax delimiters = delimiters or text.syntax
local words, reason = text.internal.words(value, quotes) local words, reason = text.internal.words(value, options)
local splitter = text.escapeMagic(custom and table.concat(delimiters) or "<>|;&") local splitter = text.escapeMagic(custom and table.concat(delimiters) or "<>|;&")
if type(words) ~= "table" or if type(words) ~= "table" or
@ -173,9 +174,12 @@ function text.internal.tokenize(value, quotes, delimiters)
end end
-- tokenize input by quotes and whitespace -- tokenize input by quotes and whitespace
function text.internal.words(input, quotes) function text.internal.words(input, options)
checkArg(1, input, "string") checkArg(1, input, "string")
checkArg(2, quotes, "table", "nil") checkArg(2, options, "table", "nil")
options = options or {}
local quotes = options.quotes
local show_escapes = options.show_escapes
local qr = nil local qr = nil
quotes = quotes or {{"'","'",true},{'"','"'},{'`','`'}} quotes = quotes or {{"'","'",true},{'"','"'},{'`','`'}}
local function append(dst, txt, qr) local function append(dst, txt, qr)
@ -193,11 +197,12 @@ function text.internal.words(input, quotes)
local char = unicode.sub(input, i, i) local char = unicode.sub(input, i, i)
if escaped then -- escaped character if escaped then -- escaped character
escaped = false escaped = false
-- include escape char if -- include escape char if show_escapes
-- or the followwing are all true
-- 1. qr active -- 1. qr active
-- 2. the char escaped is NOT the qr closure -- 2. the char escaped is NOT the qr closure
-- 3. qr is not literal -- 3. qr is not literal
if qr and not qr[3] and qr[2] ~= char then if show_escapes or (qr and not qr[3] and qr[2] ~= char) then
append(token, '\\', qr) append(token, '\\', qr)
end end
append(token, char, qr) append(token, char, qr)

View File

@ -0,0 +1,207 @@
local computer = require("computer")
local shell = require("shell")
local component = require("component")
local event = require("event")
local fs = require("filesystem")
local unicode = require("unicode")
local text = require("text")
local write = io.write
local read = io.read
local args, options = shell.parse(...)
options.sources = {}
options.targets = {}
options.source_label = args[1]
local root_exception
if options.help then
print([[Usage: install [OPTION]...
--from=ADDR install filesystem at ADDR
default: builds list of
candidates and prompts user
--to=ADDR same as --from but for target
--fromDir=PATH install PATH from source
--root=PATH same as --fromDir but target
--toDir=PATH same as --root
-u, --update update files interactively
--label override label from .prop
--nosetlabel do not label target
--nosetboot do not use target for boot
--noreboot do not reboot after install]])
return nil -- exit success
end
local rootfs = fs.get("/")
if not rootfs then
io.stderr:write("no root filesystem, aborting\n");
os.exit(1)
end
local function up_deprecate(old_key, new_key)
if options[new_key] == nil then
options[new_key] = options[old_key]
end
options[old_key] = nil
end
local function cleanPath(path)
if path then
local rpath = shell.resolve(path)
if fs.isDirectory(rpath) then
return fs.canonical(rpath):gsub("/+$", "") .. '/'
end
end
end
local rootAddress = rootfs.address
-- if the rootfs is read only, it is probably the loot disk!
root_exception = rootAddress
if rootfs.isReadOnly() then
root_exception = nil
end
-- this may be OpenOS specific, default to "" in case no /dev mount point
local devfsAddress = (fs.get("/dev/") or {}).address or ""
-- tmp is only valid if specified as an option
local tmpAddress = computer.tmpAddress()
----- For now, I am allowing source==target -- cp can handle it if the user prepares conditions correctly
----- in other words, install doesn't need to filter this scenario:
--if #options.targets == 1 and #options.sources == 1 and options.targets[1] == options.sources[1] then
-- io.stderr:write("It is not the intent of install to use the same source and target filesystem.\n")
-- return 1
--end
------ load options
up_deprecate('noboot', 'nosetboot')
up_deprecate('nolabelset', 'nosetlabel')
up_deprecate('name', 'label')
options.source_root = cleanPath(options.from)
options.target_root = cleanPath(options.to)
options.source_dir = (options.fromDir or "") .. '.'
options.target_dir = (options.root or options.toDir or "")
options.update = options.u or options.update
local function path_to_dev(path)
return path and fs.isDirectory(path) and not fs.isLink(path) and fs.get(path)
end
local source_dev = path_to_dev(options.source_root)
local target_dev = path_to_dev(options.target_root)
-- use a single for loop of all filesystems to build the list of candidates of sources and targets
local function validDevice(candidate, exceptions, specified, existing)
local address = candidate.dev.address
for _,e in ipairs(existing) do
if e.dev.address == address then
return
end
end
if specified then
if address:find(specified, 1, true) == 1 then
return true
end
else
for _,e in ipairs(exceptions) do
if e == address then
return
end
end
return true
end
end
for dev, path in fs.mounts() do
local candidate = {dev=dev, path=path:gsub("/+$","")..'/'}
if validDevice(candidate, {devfsAddress, tmpAddress, root_exception}, source_dev and source_dev.address or options.from, options.sources) then
-- root path is either the command line path given for this dev or its natural mount point
local root_path = source_dev == dev and options.source_root or path
if (options.from or fs.list(root_path)()) then -- ignore empty sources unless specified
local prop = fs.open(root_path .. '/.prop')
if prop then
local prop_data = prop:read(math.huge)
prop:close()
prop = prop_data
end
candidate.prop = prop and load('return ' .. prop)() or {}
if not options.source_label or options.source_label:lower() == (candidate.prop.label or dev.getLabel()):lower() then
table.insert(options.sources, candidate)
end
end
end
-- in case candidate is valid for BOTH, we want a new table
candidate = {dev=candidate.dev, path=candidate.path} -- but not the prop
if validDevice(candidate, {devfsAddress, tmpAddress}, target_dev and target_dev.address or options.to, options.targets) then
if not dev.isReadOnly() then
table.insert(options.targets, candidate)
elseif options.to then
io.stderr:write("Cannot install to " .. options.to .. ", it is read only\n")
os.exit(1)
end
end
end
local source = options.sources[1]
local target = options.targets[1]
if #options.sources ~= 1 or #options.targets ~= 1 then
source, target = loadfile("/lib/tools/install_utils.lua", "bt", _G)('select', options)
end
if not source then return end
options.source_root = options.source_root or source.path
if not target then return end
options.target_root = options.target_root or target.path
-- now that source is selected, we can update options
options.label = options.label or source.prop.label
options.setlabel = source.prop.setlabel and not options.nosetlabel
options.setboot = source.prop.setboot and not options.nosetboot
options.reboot = source.prop.reboot and not options.noreboot
local installer_path = options.source_root .. "/.install"
if fs.exists(installer_path) then
return loadfile("/lib/tools/install_utils.lua", "bt", _G)('install', options)
end
local cp_args =
{
"-vrx" .. (options.update and "ui" or ""),
options.source_root .. options.source_dir,
options.target_root .. options.target_dir
}
local source_display = (source.prop or {}).label or source.dev.getLabel() or source.path
local special_target = ""
if #options.targets > 1 or options.to then
special_target = " to " .. cp_args[3]
end
io.write("Install " .. source_display .. special_target .. "? [Y/n] ")
local choice = read():lower()
if choice ~= "y" and choice ~= "" then
write("Installation cancelled\n")
os.exit()
end
return
{
setlabel = options.setlabel,
label = options.label,
setboot = options.setboot,
reboot = options.reboot,
target = target,
cp_args = cp_args,
}

View File

@ -0,0 +1,84 @@
local cmd, options = ...
local function select_prompt(devs, direction)
table.sort(devs, function(a, b) return a.path<b.path end)
local choice = devs[1]
if #devs > 1 then
io.write("Select the device to install " .. direction .. '\n')
for i = 1, #devs do
local src = devs[i]
local label = src.dev.getLabel()
if label then
label = label .. " (" .. src.dev.address:sub(1, 8) .. "...)"
else
label = src.dev.address
end
io.write(i .. ") " .. label .. " at " .. src.path .. '\n')
end
io.write("Please enter a number between 1 and " .. #devs .. '\n')
io.write("Enter 'q' to cancel the installation: ")
choice = nil
while not choice do
result = io.read()
if result:sub(1, 1):lower() == "q" then
os.exit()
end
local number = tonumber(result)
if number and number > 0 and number <= #devs then
choice = devs[number]
else
io.write("Invalid input, please try again: ")
end
end
end
return choice
end
if cmd == 'select' then
if #options.sources == 0 then
if options.source_label then
io.stderr:write("No install source matched given label: " .. options.source_label .. '\n')
elseif options.from then
io.stderr:write("No install source found: " .. options.from .. '\n')
else
io.stderr:write("Could not find any available installations\n")
end
os.exit(1)
end
if #options.targets == 0 then
if options.to then
io.stderr:write("No such filesystem to install to: " .. options.to .. '\n')
else
io.stderr:write("No writable disks found, aborting\n")
end
os.exit(1)
end
return select_prompt(options.sources, "from"), select_prompt(options.targets, "to")
elseif cmd == 'install' then
local installer_path = options.source_root .. "/.install"
local installer, reason = loadfile(installer_path, "bt", setmetatable({install=
{
from=options.source_root,
to=options.target_root,
fromDir=options.source_dir,
root=options.target_dir,
update=options.update,
label=options.label,
setlabel=options.setlabel,
setboot=options.setboot,
reboot=options.reboot,
}}, {__index=_G}))
if not installer then
io.stderr:write("installer failed to load: " .. tostring(reason) .. '\n')
os.exit(1)
else
return installer()
end
end

View File

@ -54,16 +54,16 @@ function lib.first(tbl,pred,f,l)
end end
-- if value was made by lib.sub then find can find from whence -- if value was made by lib.sub then find can find from whence
function --[[@delayloaded-start@]] lib.find(tbl,v,f,l) function --[[@delayloaded-start@]] lib.find(tbl, sub, first, last)
checkArg(1,tbl,'table') checkArg(1, tbl, 'table')
checkArg(2,v,'table') checkArg(2, sub, 'table')
local s=#v local sub_len = #sub
return lib.first(tbl,function(e,i,tbl) return lib.first(tbl, function(element, index, projected_table)
for n=0,s-1 do for n=0,sub_len-1 do
if tbl[n+i]~=v[n+1] then return nil end if projected_table[n + index] ~= sub[n + 1] then return nil end
end end
return 1,s return 1, sub_len
end,f,l) end, first, last)
end --[[@delayloaded-end@]] end --[[@delayloaded-end@]]
-- Returns a list of subsets of tbl where partitioner acts as a delimiter. -- Returns a list of subsets of tbl where partitioner acts as a delimiter.
@ -150,12 +150,14 @@ function lib.foreach(tbl,c,f,l)
return r return r
end end
lib.select=lib.foreach lib.select=lib.foreach
function --[[@delayloaded-start@]] lib.where(tbl,p,f,l) function --[[@delayloaded-start@]] lib.where(tbl,p,f,l)
return lib.foreach(tbl, return lib.foreach(tbl,
function(e,i,tbl) function(e,i,tbl)
return p(e,i,tbl)and e or nil return p(e,i,tbl)and e or nil
end,f,l) end,f,l)
end --[[@delayloaded-end@]] end --[[@delayloaded-end@]]
function lib.concat(...) function lib.concat(...)
local r,rn,k={},0 local r,rn,k={},0
for _,tbl in ipairs({...})do for _,tbl in ipairs({...})do
@ -172,4 +174,19 @@ function lib.concat(...)
return r return r
end end
-- works with pairs on tables
-- returns the kv pair, or nil and the number of pairs iterated
function --[[@delayloaded-start@]] lib.at(tbl, index)
checkArg(1, tbl, "table")
checkArg(2, index, "number", "nil")
local current_index = 1
for k,v in pairs(tbl) do
if current_index == index then
return k,v
end
current_index = current_index + 1
end
return nil, current_index - 1 -- went one too far
end --[[@delayloaded-end@]]
return lib,{adjust=adjust,view=view} return lib,{adjust=adjust,view=view}

View File

@ -1,13 +1,13 @@
NAME NAME
head - Print the first 10 lines of each FILE to stdout. head - Print the first 10 lines of each FILE to stdout.
SYNOPSIS SYNOPSIS
head [OPTION]... [FILE]... head [OPTION]... [FILE]...
DESCRIPTION DESCRIPTION
Print the first 10 lines of each FILE to stdout. Print the first 10 lines of each FILE to stdout.
With no FILE, or when FILE is -, read stdin. With no FILE, or when FILE is -, read stdin.
--bytes=[-]n print the first n bytes of each file' --bytes=[-]n print the first n bytes of each file'
with the leading '-', print all but the last with the leading '-', print all but the last
n bytes of each file n bytes of each file
@ -18,14 +18,14 @@ DESCRIPTION
-v, --verbose always print headers giving file names -v, --verbose always print headers giving file names
--help print help message --help print help message
EXAMPLES EXAMPLES
head head
head - head -
Read next 10 lines from standard in and print to standard out, then close. Read next 10 lines from standard in and print to standard out, then close.
head file.txt head file.txt
Print first 10 lines of file.txt and print to stdout Print first 10 lines of file.txt and print to stdout
head -n 32 file.txt head -n 32 file.txt
Print first 32 lines of file.txt and print to stdout Print first 32 lines of file.txt and print to stdout

View File

@ -0,0 +1,103 @@
NAME
install - installs files from a source filesystem to a target filesystem
SYNOPSIS
install [name] [OPTIONS]...
DESCRIPTION
install builds a list of candidate source and target mounted filesystems. If there are multiple candidates, the user is prompted for selections. By default, install copies all files from the source filesystem's root to the target filesystem's root path. The source filesystem can define label, boot, and reboot behavior via .prop and a fully custom install experience via .install which supercedes install running cp from source to target filesystems. Developers creating their own .install files for devices should respect environment variables set by install as per options it is given, such as the root path. This manual page details those environment variables.
OPTIONS
--from=ADDR
Specifies the source filesystem or its root path. ADDR can be the device guid or a directory path. If this is a directory path, it represents a root path to install from. This option can also be used to specify source paths that would otherwise be ignored, those being devfs, tmpfs, and the rootfs. e.g. --from=/tmp . Note that if both --from and a [name] is given, install expects the source path to have a .prop defining the same name. See .prop for more details.
--to=ADDR
Same as --from but specifies the target filesystem by guid or its root path. This option can also be used to specify filesystems that are otherwise ignored including tmpfs. i.e. --to=ADDR where ADDR matches the tmpfs device address or its mount point path. e.g. --to=/tmp
--fromDir=PATH
Install PATH from source. PATH is relative to the root of the source filesystem or path given by --from. The default is .
--root=PATH
Same as --fromDir but for the target filesystem.
--toDir=PATH
Same as --root. Either can be used. It is meaningless to specify both and is not documented which takes precedence in such a case.
-u, --update
Indicates that install should prompt the user before modifying files. This invokes -i and -u for /bin/cp.
The following can override settings defined in .prop in the source filesystem.
--label=NAME
use NAME for label instead of any value specified by .prop, --name is deprecated
--nosetlabel
do not set target label. --nolabelset is deprecated
--nosetboot
do not set target as default boot device when rebooting. --noboot is deprecated
--noreboot
do not reboot after install
.prop
.prop should have valid lua code that returns a table of keys and their values: e.g. "return {name='OpenOS'}"
name=string
Declares an identifying name of the installation. This is displayed by install during source selection and also can be used on the commandline: e.g. (where {name="tape"} is given) `install tape`. If setlabel is true, this value is used for the target filesystem label. --name overrides this value. Note that install uses a case insensitive search: e.g. install TAPE works the same as install tape.
setlabel=boolean
Determines whether the install should set the target filesystem's label. If .prop does not define a name key and the user does not define a command line --name=VALUE, setlabel has no action. --nosetlabel overrides this value
setboot=boolean
Determines if the target filesystem should be set as the machine's default boot device. Default is false, overriden by --nosetboot
reboot=boolean
Determines if the machine should reboot after the install completes. Overriden by --noreboot
EXAMPLE:
return {name='OpenOS', setlabel=true, setboot=true, reboot=true}
.install ENVIRONMENT
When .install is loaded and executed, a custom table is added to the environment: ENV_.install
These are the keys of the table as populated by install
ENV_.install.from:
This is the path of the selected source filesystem to install from. It should be the path to the executing .install
example: /mnt/ABC
ENV_.install.to:
This is the path of the selected target filesystem to install to.
example: /
_ENV.install.fromdir
This is the relative path to use in the source filesysterm as passed by command line to install. If unspecified to install it defaults to "."
example: .
_ENV.install.root
This is the relative path to use in the target filesystem as passed by command line to install. If unspecified to install it defaults to "."
example: .
_ENV.install.update
Assigned value of --update, see OPTIONS
_ENV.install.label
Assigned value of --name or .prop's label, see OPTIONS
_ENV.install.setlabel
Assigned value of .prop's setlabel unless --nolabelset, see OPTIONS
_ENV.install.setboot
Assigned value of .prop's boot unless --nosetboot, see OPTIONS
_ENV.install.reboot
Assigned value of .prop's reboot unless --noreboot, see OPTIONS
EXAMPLES
install
Searches all non rootfs filesystems to install from, and all non tmpfs filesystems to install to. Prompts the user for a selection, and copies. If .prop is defined in source, sets label and will prompt for reboot when completed.
install openos
Searches candidates source filesystems that have .prop's that define name="OpenOS" and prompts the user to confirm install to candidate target filesystems.

View File

@ -0,0 +1 @@
os.execute(install.from.."/oppm.lua")

View File

@ -1334,8 +1334,11 @@ local libcomputer = {
beep = function(...) beep = function(...)
return libcomponent.invoke(computer.address(), "beep", ...) return libcomponent.invoke(computer.address(), "beep", ...)
end, end,
getDeviceInfo = function(...) getDeviceInfo = function()
return libcomponent.invoke(computer.address(), "getDeviceInfo", ...) return libcomponent.invoke(computer.address(), "getDeviceInfo")
end,
getProgramLocations = function()
return libcomponent.invoke(computer.address(), "getProgramLocations")
end, end,
getArchitectures = function(...) getArchitectures = function(...)

View File

@ -11,8 +11,11 @@ import li.cil.oc.common.template.DisassemblerTemplates
import li.cil.oc.integration.util.ItemCharge import li.cil.oc.integration.util.ItemCharge
import li.cil.oc.integration.util.Wrench import li.cil.oc.integration.util.Wrench
import li.cil.oc.server.driver.Registry import li.cil.oc.server.driver.Registry
import li.cil.oc.server.machine.ProgramLocations
import li.cil.oc.util.ExtendedNBT._
import net.minecraft.entity.player.EntityPlayer import net.minecraft.entity.player.EntityPlayer
import net.minecraft.item.ItemStack import net.minecraft.item.ItemStack
import net.minecraft.nbt.NBTTagString
import net.minecraft.util.BlockPos import net.minecraft.util.BlockPos
import net.minecraftforge.common.util.Constants.NBT import net.minecraftforge.common.util.Constants.NBT
import net.minecraftforge.fml.common.event.FMLInterModComms.IMCEvent import net.minecraftforge.fml.common.event.FMLInterModComms.IMCEvent
@ -92,8 +95,13 @@ object IMC {
} }
} }
else if (message.key == "registerCustomPowerSystem" && message.isStringMessage) { else if (message.key == "registerCustomPowerSystem" && message.isStringMessage) {
OpenComputers.log.info(s"Was told there is an unknown power system present by mod ${message.getSender}.")
Settings.get.is3rdPartyPowerSystemPresent = message.getStringValue == "true" Settings.get.is3rdPartyPowerSystemPresent = message.getStringValue == "true"
} }
else if (message.key == "registerProgramDiskLabel" && message.isNBTMessage) {
OpenComputers.log.info(s"Registering new program location mapping for program '${message.getNBTValue.getString("program")}' being on disk '${message.getNBTValue.getString("label")}' from mod ${message.getSender}.")
ProgramLocations.addMapping(message.getNBTValue.getString("program"), message.getNBTValue.getString("label"), message.getNBTValue.getTagList("architectures", NBT.TAG_STRING).map((tag: NBTTagString) => tag.func_150285_a_()).toArray: _*)
}
else { else {
OpenComputers.log.warn(s"Got an unrecognized or invalid IMC message '${message.key}' from mod ${message.getSender}.") OpenComputers.log.warn(s"Got an unrecognized or invalid IMC message '${message.key}' from mod ${message.getSender}.")
} }

View File

@ -70,9 +70,30 @@ object ModOpenComputers extends ModProxy {
"OpenComputers", "OpenComputers",
"li.cil.oc.integration.opencomputers.ModOpenComputers.canCharge", "li.cil.oc.integration.opencomputers.ModOpenComputers.canCharge",
"li.cil.oc.integration.opencomputers.ModOpenComputers.charge") "li.cil.oc.integration.opencomputers.ModOpenComputers.charge")
api.IMC.registerInkProvider("li.cil.oc.integration.opencomputers.ModOpenComputers.inkCartridgeInkProvider") api.IMC.registerInkProvider("li.cil.oc.integration.opencomputers.ModOpenComputers.inkCartridgeInkProvider")
api.IMC.registerInkProvider("li.cil.oc.integration.opencomputers.ModOpenComputers.dyeInkProvider") api.IMC.registerInkProvider("li.cil.oc.integration.opencomputers.ModOpenComputers.dyeInkProvider")
api.IMC.registerProgramDiskLabel("build", "builder", "Lua 5.2", "Lua 5.3", "LuaJ")
api.IMC.registerProgramDiskLabel("dig", "dig", "Lua 5.2", "Lua 5.3", "LuaJ")
api.IMC.registerProgramDiskLabel("base64", "data", "Lua 5.2", "Lua 5.3", "LuaJ")
api.IMC.registerProgramDiskLabel("deflate", "data", "Lua 5.2", "Lua 5.3", "LuaJ")
api.IMC.registerProgramDiskLabel("gpg", "data", "Lua 5.2", "Lua 5.3", "LuaJ")
api.IMC.registerProgramDiskLabel("inflate", "data", "Lua 5.2", "Lua 5.3", "LuaJ")
api.IMC.registerProgramDiskLabel("md5sum", "data", "Lua 5.2", "Lua 5.3", "LuaJ")
api.IMC.registerProgramDiskLabel("sha256sum", "data", "Lua 5.2", "Lua 5.3", "LuaJ")
api.IMC.registerProgramDiskLabel("refuel", "generator", "Lua 5.2", "Lua 5.3", "LuaJ")
api.IMC.registerProgramDiskLabel("pastebin", "internet", "Lua 5.2", "Lua 5.3", "LuaJ")
api.IMC.registerProgramDiskLabel("wget", "internet", "Lua 5.2", "Lua 5.3", "LuaJ")
api.IMC.registerProgramDiskLabel("irc", "irc", "Lua 5.2", "Lua 5.3", "LuaJ")
api.IMC.registerProgramDiskLabel("maze", "maze", "Lua 5.2", "Lua 5.3", "LuaJ")
api.IMC.registerProgramDiskLabel("arp", "network", "Lua 5.2", "Lua 5.3", "LuaJ")
api.IMC.registerProgramDiskLabel("ifconfig", "network", "Lua 5.2", "Lua 5.3", "LuaJ")
api.IMC.registerProgramDiskLabel("ping", "network", "Lua 5.2", "Lua 5.3", "LuaJ")
api.IMC.registerProgramDiskLabel("route", "network", "Lua 5.2", "Lua 5.3", "LuaJ")
api.IMC.registerProgramDiskLabel("opl-flash", "openloader", "Lua 5.2", "Lua 5.3", "LuaJ")
api.IMC.registerProgramDiskLabel("oppm", "oppm", "Lua 5.2", "Lua 5.3", "LuaJ")
ForgeChunkManager.setForcedChunkLoadingCallback(OpenComputers, ChunkloaderUpgradeHandler) ForgeChunkManager.setForcedChunkLoadingCallback(OpenComputers, ChunkloaderUpgradeHandler)
MinecraftForge.EVENT_BUS.register(EventHandler) MinecraftForge.EVENT_BUS.register(EventHandler)

View File

@ -452,6 +452,10 @@ class Machine(val host: MachineHost) extends prefab.ManagedEnvironment with mach
}.collect { case Some(kvp) => kvp }.toMap) }.collect { case Some(kvp) => kvp }.toMap)
} }
@Callback(doc = """function():table -- Returns a map of program name to disk label for known programs.""")
def getProgramLocations(context: Context, args: Arguments): Array[AnyRef] =
result(ProgramLocations.getMappings(Machine.getArchitectureName(architecture.getClass)))
// ----------------------------------------------------------------------- // // ----------------------------------------------------------------------- //
def isExecuting = state.synchronized(state.contains(Machine.State.Running)) def isExecuting = state.synchronized(state.contains(Machine.State.Running))

View File

@ -0,0 +1,19 @@
package li.cil.oc.server.machine
import scala.collection.mutable
object ProgramLocations {
final val architectureLocations = mutable.Map.empty[String, mutable.Map[String, String]]
final val globalLocations = mutable.Map.empty[String, String]
def addMapping(program: String, label: String, architectures: String*): Unit = {
if (architectures == null || architectures.isEmpty) {
globalLocations += (program -> label)
}
else {
architectures.foreach(architectureLocations.getOrElseUpdate(_, mutable.Map.empty[String, String]) += (program -> label))
}
}
def getMappings(architecture: String) = architectureLocations.getOrElse(architecture, Iterable.empty) ++ globalLocations
}