mirror of
https://github.com/MightyPirates/OpenComputers.git
synced 2025-09-24 04:50:30 -04:00
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:
commit
1b1456951e
@ -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
|
||||||
|
@ -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";
|
||||||
|
@ -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)` активирует первый контакт.
|
||||||
|
@ -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)
|
|
@ -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)
|
|
@ -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)
|
|
@ -1 +0,0 @@
|
|||||||
return {name = "OpenOS"}
|
|
@ -0,0 +1 @@
|
|||||||
|
{label = "OpenOS", reboot=true, setlabel=true, setboot=true}
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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")
|
||||||
|
300
src/main/resources/assets/opencomputers/loot/openos/bin/less.lua
Normal file
300
src/main/resources/assets/opencomputers/loot/openos/bin/less.lua
Normal 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()
|
@ -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')
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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 = "",
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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,
|
||||||
|
}
|
@ -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
|
@ -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}
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
|
@ -0,0 +1 @@
|
|||||||
|
os.execute(install.from.."/oppm.lua")
|
@ -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(...)
|
||||||
|
@ -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}.")
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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))
|
||||||
|
@ -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
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user