Pull in CC:T 1.108.1, some other fixes

- Use Cobalt's new Java patcher, allowing us to use Java 17 syntax. As
   such, update a couple classes to make use of that.
 - Pull in latest ROM. This is very noisy (due to the link syntax
   changes), but mostly trivial changes.
 - Fix wget and pastebin programs using http methods which don't exist.
 - Make fs.open create the parent directory when opening for write, much
   like newer CC versions.
This commit is contained in:
Jonathan Coates 2023-10-04 18:56:11 +01:00
parent 35e227ed02
commit 136fbd2589
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
59 changed files with 774 additions and 1006 deletions

View File

@ -1,27 +0,0 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
plugins {
application
alias(libs.plugins.kotlin)
}
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}
repositories {
mavenCentral()
}
dependencies {
implementation(libs.bundles.asm)
implementation(libs.bundles.kotlin)
}
tasks.jar {
manifest.attributes("Main-Class" to "cc.tweaked.build.MainKt")
}

View File

@ -1,61 +0,0 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package cc.tweaked.build
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.util.CheckClassAdapter
import java.nio.file.Files
import java.nio.file.Path
/** Generate additional classes which don't exist in the original source set. */
interface ClassEmitter {
/** Emit a class if it does not already exist. */
fun generate(name: String, classReader: ClassReader? = null, flags: Int = 0, write: (ClassVisitor) -> Unit)
}
/** An implementation of [ClassEmitter] which writes files to a directory. */
class FileClassEmitter(private val outputDir: Path) : ClassEmitter {
private val emitted = mutableSetOf<String>()
override fun generate(name: String, classReader: ClassReader?, flags: Int, write: (ClassVisitor) -> Unit) {
if (!emitted.add(name)) return
val cw = NonLoadingClassWriter(classReader, flags)
write(CheckClassAdapter(cw))
val outputFile = outputDir.resolve("$name.class")
Files.createDirectories(outputFile.parent)
Files.write(outputFile, cw.toByteArray())
}
}
/** A unordered pair, such that (x, y) = (y, x) */
private class UnorderedPair<T>(private val x: T, private val y: T) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is cc.tweaked.build.UnorderedPair<*>) return false
return (x == other.x && y == other.y) || (x == other.y && y == other.x)
}
override fun hashCode(): Int = x.hashCode() xor y.hashCode()
override fun toString(): String = "UnorderedPair($x, $y)"
}
private val subclassRelations = mapOf<UnorderedPair<String>, String>(
)
/** A [ClassWriter] extension which avoids loading classes when computing frames. */
private class NonLoadingClassWriter(reader: ClassReader?, flags: Int) : ClassWriter(reader, flags) {
override fun getCommonSuperClass(type1: String, type2: String): String {
if (type1 == "java/lang/Object" || type2 == "java/lang/Object") return "java/lang/Object"
val subclass = subclassRelations[UnorderedPair(type1, type2)]
if (subclass != null) return subclass
println("[WARN] Guessing the super-class of $type1 and $type2.")
return "java/lang/Object"
}
}

View File

@ -1,29 +0,0 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package cc.tweaked.build
import org.objectweb.asm.ClassReader
import java.nio.file.Files
import java.nio.file.Paths
import kotlin.io.path.extension
import kotlin.system.exitProcess
fun main(args: Array<String>) {
if (args.size != 2) {
System.err.println("Expected: INPUT OUTPUT")
exitProcess(1)
}
val inputDir = Paths.get(args[0])
val outputDir = Paths.get(args[1])
val emitter = FileClassEmitter(outputDir)
Files.find(inputDir, Int.MAX_VALUE, { path, _ -> path.extension == "class" }).use { files ->
files.forEach { inputFile ->
val reader = Files.newInputStream(inputFile).use { ClassReader(it) }
emitter.generate(reader.className, flags = 0) { cw -> reader.accept(Unlambda(emitter, cw), 0) }
}
}
}

View File

@ -1,210 +0,0 @@
// SPDX-FileCopyrightText: 2023 The CC: Tweaked Developers
//
// SPDX-License-Identifier: MPL-2.0
package cc.tweaked.build
import org.objectweb.asm.*
import org.objectweb.asm.Opcodes.*
class Unlambda(private val emitter: ClassEmitter, visitor: ClassVisitor) :
ClassVisitor(ASM9, visitor) {
internal lateinit var className: String
private var isInterface: Boolean = false
private var lambda = 0
override fun visit(version: Int, access: Int, name: String, signature: String?, superName: String, interfaces: Array<out String>?) {
super.visit(V1_6, access, name, signature, superName, interfaces)
if (version != V1_8) throw IllegalStateException("Expected Java version 8")
className = name
isInterface = (access and ACC_INTERFACE) != 0
}
override fun visitMethod(access: Int, name: String, descriptor: String, signature: String?, exceptions: Array<out String>?): MethodVisitor? {
val access = if (access.and(ACC_STATIC) != 0) access.and(ACC_PRIVATE.inv()) else access
val mw = super.visitMethod(access, name, descriptor, signature, exceptions) ?: return null
if (isInterface && name != "<clinit>") {
if ((access and ACC_STATIC) != 0) println("[WARN] $className.$name is a static method")
else if ((access and ACC_ABSTRACT) == 0) println("[WARN] $className.$name is a default method")
}
return UnlambdaMethodVisitor(this, emitter, mw)
}
internal fun nextLambdaName(): String {
val name = "lambda$lambda"
lambda++
return name
}
}
internal class UnlambdaMethodVisitor(
private val parent: Unlambda,
private val emitter: ClassEmitter,
methodVisitor: MethodVisitor,
) : MethodVisitor(ASM9, methodVisitor) {
private class Bridge(val lambda: Handle, val bridgeName: String)
private val bridgeMethods = mutableListOf<Bridge>()
override fun visitMethodInsn(opcode: Int, owner: String, name: String, descriptor: String, isInterface: Boolean) {
if (opcode == INVOKESTATIC && isInterface) println("[WARN] Invoke interface $owner.$name in ${parent.className}")
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
}
override fun visitInvokeDynamicInsn(name: String, descriptor: String, handle: Handle, vararg arguments: Any) {
if (handle.owner == "java/lang/invoke/LambdaMetafactory" && handle.name == "metafactory" && handle.desc == "(Ljava/lang/invoke/MethodHandles\$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;") {
visitLambda(name, descriptor, arguments[0] as Type, arguments[1] as Handle)
} else {
super.visitInvokeDynamicInsn(name, descriptor, handle, *arguments)
}
}
private fun visitLambda(name: String, descriptor: String, signature: Type, lambda: Handle) {
val interfaceTy = Type.getReturnType(descriptor)
val fields = Type.getArgumentTypes(descriptor)
val lambdaName = parent.nextLambdaName()
val className = "${parent.className}\$$lambdaName"
val bridgeName = "${lambdaName}Bridge"
emitter.generate(className, flags = ClassWriter.COMPUTE_MAXS) { cw ->
cw.visit(V1_6, ACC_FINAL, className, null, "java/lang/Object", arrayOf(interfaceTy.internalName))
for ((i, ty) in fields.withIndex()) {
cw.visitField(ACC_PRIVATE or ACC_FINAL, "field$i", ty.descriptor, null, null)
.visitEnd()
}
cw.visitMethod(ACC_STATIC, "create", Type.getMethodDescriptor(interfaceTy, *fields), null, null).let { mw ->
mw.visitCode()
mw.visitTypeInsn(NEW, className)
mw.visitInsn(DUP)
for ((i, ty) in fields.withIndex()) mw.visitVarInsn(ty.getOpcode(ILOAD), i)
mw.visitMethodInsn(INVOKESPECIAL, className, "<init>", Type.getMethodDescriptor(Type.VOID_TYPE, *fields), false)
mw.visitInsn(ARETURN)
mw.visitMaxs(0, 0)
mw.visitEnd()
}
cw.visitMethod(0, "<init>", Type.getMethodDescriptor(Type.VOID_TYPE, *fields), null, null).let { mw ->
mw.visitCode()
mw.visitVarInsn(ALOAD, 0)
mw.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false)
for ((i, ty) in fields.withIndex()) {
mw.visitVarInsn(ALOAD, 0)
mw.visitVarInsn(ty.getOpcode(ILOAD), i + 1)
mw.visitFieldInsn(PUTFIELD, className, "field$i", ty.descriptor)
}
mw.visitInsn(RETURN)
mw.visitMaxs(0, 0)
mw.visitEnd()
}
cw.visitMethod(ACC_PUBLIC, name, signature.descriptor, null, null).let { mw ->
mw.visitCode()
val targetArgs = when (lambda.tag) {
H_INVOKEVIRTUAL, H_INVOKESPECIAL -> arrayOf(
Type.getObjectType(lambda.owner),
*Type.getArgumentTypes(lambda.desc),
)
H_INVOKESTATIC, H_NEWINVOKESPECIAL -> Type.getArgumentTypes(lambda.desc)
else -> throw IllegalStateException("Unhandled opcode")
}
var targetArgOffset = 0
// If we're a ::new method handle, create the object.
if (lambda.tag == H_NEWINVOKESPECIAL) {
mw.visitTypeInsn(NEW, lambda.owner)
mw.visitInsn(DUP)
}
// Load our fields
for ((i, ty) in fields.withIndex()) {
mw.visitVarInsn(ALOAD, 0)
mw.visitFieldInsn(GETFIELD, className, "field$i", ty.descriptor)
val expectedTy = targetArgs[targetArgOffset]
if (ty != expectedTy) println("$ty != $expectedTy")
targetArgOffset++
}
// Load the additional arguments
val arguments = signature.argumentTypes
for ((i, ty) in arguments.withIndex()) {
mw.visitVarInsn(ty.getOpcode(ILOAD), i + 1)
val expectedTy = targetArgs[targetArgOffset]
if (ty != expectedTy) {
println("[WARN] $ty != $expectedTy, adding a cast")
mw.visitTypeInsn(CHECKCAST, expectedTy.internalName)
}
targetArgOffset++
}
// Invoke our init call
mw.visitMethodInsn(
when (lambda.tag) {
H_INVOKEVIRTUAL, H_INVOKESPECIAL -> INVOKEVIRTUAL
H_INVOKESTATIC -> INVOKESTATIC
H_NEWINVOKESPECIAL -> INVOKESPECIAL
else -> throw IllegalStateException("Unhandled opcode")
},
lambda.owner, if (lambda.tag == H_INVOKESPECIAL) bridgeName else lambda.name, lambda.desc, false,
)
if (lambda.tag != H_NEWINVOKESPECIAL) {
val expectedRetTy = signature.returnType
val retTy = Type.getReturnType(lambda.desc)
if (expectedRetTy != retTy) {
// println("[WARN] $retTy != $expectedRetTy, adding a cast")
if (retTy == Type.INT_TYPE && expectedRetTy.descriptor == "Ljava/lang/Object;") {
mw.visitMethodInsn(INVOKESTATIC, "jav/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false)
} else {
// println("[ERROR] Unhandled")
}
}
}
// A little ugly special handling for ::new
mw.visitInsn(
if (lambda.tag == H_NEWINVOKESPECIAL) ARETURN else signature.returnType.getOpcode(IRETURN),
)
mw.visitMaxs(0, 0)
mw.visitEnd()
}
cw.visitEnd()
}
// If we're a ::new method handle, create the object.
if (lambda.tag == H_INVOKESPECIAL) {
bridgeMethods.add(Bridge(lambda, bridgeName))
}
visitMethodInsn(INVOKESTATIC, className, "create", Type.getMethodDescriptor(interfaceTy, *fields), false)
}
override fun visitEnd() {
super.visitEnd()
for (bridge in bridgeMethods) {
println("[INFO] Using bridge method ${bridge.bridgeName} for ${bridge.lambda}")
val mw = parent.visitMethod(ACC_PUBLIC, bridge.bridgeName, bridge.lambda.desc, null, null) ?: continue
mw.visitCode()
mw.visitVarInsn(ALOAD, 0)
for ((i, ty) in Type.getArgumentTypes(bridge.lambda.desc)
.withIndex()) mw.visitVarInsn(ty.getOpcode(ILOAD), i + 1)
mw.visitMethodInsn(INVOKESPECIAL, bridge.lambda.owner, bridge.lambda.name, bridge.lambda.desc, false)
mw.visitInsn(Type.getReturnType(bridge.lambda.desc).getOpcode(IRETURN))
val size = 1 + Type.getArgumentTypes(bridge.lambda.desc).size
mw.visitMaxs(size, size)
mw.visitEnd()
}
}
}

View File

@ -20,13 +20,16 @@ version = modVersion
base.archivesName.convention("cc-tweaked-$mcVersion")
java {
// Last version able to set a --release as low as 6
toolchain.languageVersion.set(JavaLanguageVersion.of(11))
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
withSourcesJar()
}
tasks.compileJava { options.release.set(8) }
val runtimeToolchain = javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(8))
}
repositories {
mavenCentral()
@ -38,10 +41,6 @@ repositories {
}
volde {
forgeCapabilities {
setSrgsAsFallback(true)
}
runs {
named("client") {
programArg("SquidDev")
@ -50,6 +49,10 @@ volde {
}
}
tasks.withType(net.fabricmc.loom.task.RunTask::class.java).configureEach {
javaLauncher.set(runtimeToolchain)
}
configurations {
val shade by registering
compileOnly { extendsFrom(shade.get()) }
@ -71,10 +74,12 @@ dependencies {
},
)
compileOnly("com.google.code.findbugs:jsr305:3.0.2")
compileOnly("org.jetbrains:annotations:24.0.1")
modImplementation("maven.modrinth:computercraft:1.50")
"shade"("org.squiddev:Cobalt")
"buildTools"(project(":build-tools"))
"buildTools"("cc.tweaked.cobalt:build-tools")
}
// Point compileJava to emit to classes/uninstrumentedJava/main, and then add a task to instrument these classes,
@ -88,12 +93,9 @@ val instrumentJava = tasks.register(mainSource.getTaskName("Instrument", "Java")
inputs.dir(untransformedClasses).withPropertyName("inputDir")
outputs.dir(javaClassesDir).withPropertyName("outputDir")
javaLauncher.set(
javaToolchains.launcherFor {
languageVersion.set(JavaLanguageVersion.of(17))
},
)
mainClass.set("cc.tweaked.build.MainKt")
// Run under Java 8, so we can check compatibility of methods.
javaLauncher.set(runtimeToolchain)
mainClass.set("cc.tweaked.cobalt.build.MainKt")
classpath = buildTools
args = listOf(
@ -129,6 +131,8 @@ tasks.jar {
}
tasks.processResources {
inputs.property("version", project.version)
inputs.property("mcVersion", mcVersion)
filesMatching("mcmod.info") {
expand("version" to project.version, "mcVersion" to mcVersion)
}

View File

@ -4,6 +4,6 @@
org.gradle.jvmargs=-Xmx3G
modVersion=1.105.1
modVersion=1.108.1
mcVersion=1.4.7

View File

@ -38,5 +38,3 @@ val mcVersion: String by settings
rootProject.name = "cc-tweaked-$mcVersion"
includeBuild("vendor/Cobalt")
include("build-tools")

View File

@ -4,8 +4,6 @@
package dan200.computercraft.api.lua;
import java.util.Objects;
/**
* A wrapper type for "coerced" values.
* <p>
@ -20,27 +18,9 @@
* }
* }</pre>
*
* @param <T> The type of the underlying value.
* @param value The argument value.
* @param <T> The type of the underlying value.
* @see IArguments#getStringCoerced(int)
*/
public final class Coerced<T> {
private final T value;
public Coerced(T value) {
this.value = value;
}
public T value() {
return value;
}
@Override
public boolean equals(Object obj) {
return obj == this || obj instanceof Coerced && Objects.equals(value, ((Coerced<?>) obj).value);
}
@Override
public int hashCode() {
return Objects.hashCode(value);
}
public record Coerced<T>(T value) {
}

View File

@ -4,6 +4,9 @@
package dan200.computercraft.api.lua;
import org.jetbrains.annotations.Contract;
import javax.annotation.Nullable;
import java.nio.ByteBuffer;
import java.util.Map;
import java.util.Optional;
@ -39,6 +42,7 @@ public abstract class IArguments {
* @throws IllegalStateException If accessing these arguments outside the scope of the original function. See
* {@link #escapes()}.
*/
@Nullable
public abstract Object get(int index) throws LuaException;
/**
@ -70,8 +74,8 @@ public abstract class IArguments {
* @see #get(int) To get a single argument.
*/
public Object[] getAll() throws LuaException {
Object[] result = new Object[count()];
for (int i = 0; i < result.length; i++) result[i] = get(i);
var result = new Object[count()];
for (var i = 0; i < result.length; i++) result[i] = get(i);
return result;
}
@ -84,9 +88,9 @@ public Object[] getAll() throws LuaException {
* @see #getFiniteDouble(int) if you require this to be finite (i.e. not infinite or NaN).
*/
public double getDouble(int index) throws LuaException {
Object value = get(index);
if (!(value instanceof Number)) throw LuaValues.badArgumentOf(this, index, "number");
return ((Number) value).doubleValue();
var value = get(index);
if (!(value instanceof Number number)) throw LuaValues.badArgumentOf(this, index, "number");
return number.doubleValue();
}
/**
@ -108,9 +112,9 @@ public int getInt(int index) throws LuaException {
* @throws LuaException If the value is not a long.
*/
public long getLong(int index) throws LuaException {
Object value = get(index);
if (!(value instanceof Number)) throw LuaValues.badArgumentOf(this, index, "number");
return LuaValues.checkFiniteNum(index, (Number) value).longValue();
var value = get(index);
if (!(value instanceof Number number)) throw LuaValues.badArgumentOf(this, index, "number");
return LuaValues.checkFiniteNum(index, number).longValue();
}
/**
@ -132,9 +136,9 @@ public double getFiniteDouble(int index) throws LuaException {
* @throws LuaException If the value is not a boolean.
*/
public boolean getBoolean(int index) throws LuaException {
Object value = get(index);
if (!(value instanceof Boolean)) throw LuaValues.badArgumentOf(this, index, "boolean");
return (Boolean) value;
var value = get(index);
if (!(value instanceof Boolean bool)) throw LuaValues.badArgumentOf(this, index, "boolean");
return bool;
}
/**
@ -145,9 +149,9 @@ public boolean getBoolean(int index) throws LuaException {
* @throws LuaException If the value is not a string.
*/
public String getString(int index) throws LuaException {
Object value = get(index);
if (!(value instanceof String)) throw LuaValues.badArgumentOf(this, index, "string");
return (String) value;
var value = get(index);
if (!(value instanceof String string)) throw LuaValues.badArgumentOf(this, index, "string");
return string;
}
/**
@ -167,12 +171,12 @@ public String getString(int index) throws LuaException {
* @see Coerced
*/
public String getStringCoerced(int index) throws LuaException {
Object value = get(index);
var value = get(index);
if (value == null) return "nil";
if (value instanceof Boolean || value instanceof String) return value.toString();
if (value instanceof Number) {
double asDouble = ((Number) value).doubleValue();
int asInt = (int) asDouble;
if (value instanceof Number number) {
var asDouble = number.doubleValue();
var asInt = (int) asDouble;
return asInt == asDouble ? Integer.toString(asInt) : Double.toString(asDouble);
}
@ -212,7 +216,7 @@ public <T extends Enum<T>> T getEnum(int index, Class<T> klass) throws LuaExcept
* @throws LuaException If the value is not a table.
*/
public Map<?, ?> getTable(int index) throws LuaException {
Object value = get(index);
var value = get(index);
if (!(value instanceof Map)) throw LuaValues.badArgumentOf(this, index, "table");
return (Map<?, ?>) value;
}
@ -225,10 +229,10 @@ public <T extends Enum<T>> T getEnum(int index, Class<T> klass) throws LuaExcept
* @throws LuaException If the value is not a number.
*/
public Optional<Double> optDouble(int index) throws LuaException {
Object value = get(index);
var value = get(index);
if (value == null) return Optional.empty();
if (!(value instanceof Number)) throw LuaValues.badArgumentOf(this, index, "number");
return Optional.of(((Number) value).doubleValue());
if (!(value instanceof Number number)) throw LuaValues.badArgumentOf(this, index, "number");
return Optional.of(number.doubleValue());
}
/**
@ -250,10 +254,10 @@ public Optional<Integer> optInt(int index) throws LuaException {
* @throws LuaException If the value is not a number.
*/
public Optional<Long> optLong(int index) throws LuaException {
Object value = get(index);
var value = get(index);
if (value == null) return Optional.empty();
if (!(value instanceof Number)) throw LuaValues.badArgumentOf(this, index, "number");
return Optional.of(LuaValues.checkFiniteNum(index, (Number) value).longValue());
if (!(value instanceof Number number)) throw LuaValues.badArgumentOf(this, index, "number");
return Optional.of(LuaValues.checkFiniteNum(index, number).longValue());
}
/**
@ -264,7 +268,7 @@ public Optional<Long> optLong(int index) throws LuaException {
* @throws LuaException If the value is not finite.
*/
public Optional<Double> optFiniteDouble(int index) throws LuaException {
Optional<Double> value = optDouble(index);
var value = optDouble(index);
if (value.isPresent()) LuaValues.checkFiniteNum(index, value.get());
return value;
}
@ -277,10 +281,10 @@ public Optional<Double> optFiniteDouble(int index) throws LuaException {
* @throws LuaException If the value is not a boolean.
*/
public Optional<Boolean> optBoolean(int index) throws LuaException {
Object value = get(index);
var value = get(index);
if (value == null) return Optional.empty();
if (!(value instanceof Boolean)) throw LuaValues.badArgumentOf(this, index, "boolean");
return Optional.of((Boolean) value);
if (!(value instanceof Boolean bool)) throw LuaValues.badArgumentOf(this, index, "boolean");
return Optional.of(bool);
}
/**
@ -291,10 +295,10 @@ public Optional<Boolean> optBoolean(int index) throws LuaException {
* @throws LuaException If the value is not a string.
*/
public Optional<String> optString(int index) throws LuaException {
Object value = get(index);
var value = get(index);
if (value == null) return Optional.empty();
if (!(value instanceof String)) throw LuaValues.badArgumentOf(this, index, "string");
return Optional.of((String) value);
if (!(value instanceof String string)) throw LuaValues.badArgumentOf(this, index, "string");
return Optional.of(string);
}
/**
@ -318,7 +322,7 @@ public Optional<ByteBuffer> optBytes(int index) throws LuaException {
* @throws LuaException If the value is not a string or not a valid option for this enum.
*/
public <T extends Enum<T>> Optional<T> optEnum(int index, Class<T> klass) throws LuaException {
Optional<String> str = optString(index);
var str = optString(index);
return str.isPresent() ? Optional.of(LuaValues.checkEnum(index, klass, str.get())) : Optional.empty();
}
@ -330,7 +334,7 @@ public <T extends Enum<T>> Optional<T> optEnum(int index, Class<T> klass) throws
* @throws LuaException If the value is not a table.
*/
public Optional<Map<?, ?>> optTable(int index) throws LuaException {
Object value = get(index);
var value = get(index);
if (value == null) return Optional.empty();
if (!(value instanceof Map)) throw LuaValues.badArgumentOf(this, index, "map");
return Optional.of((Map<?, ?>) value);
@ -340,7 +344,7 @@ public <T extends Enum<T>> Optional<T> optEnum(int index, Class<T> klass) throws
* Get an argument as a double.
*
* @param index The argument number.
* @param def The public value, if this argument is not given.
* @param def The default value, if this argument is not given.
* @return The argument's value, or {@code def} if none was provided.
* @throws LuaException If the value is not a number.
*/
@ -352,7 +356,7 @@ public double optDouble(int index, double def) throws LuaException {
* Get an argument as an int.
*
* @param index The argument number.
* @param def The public value, if this argument is not given.
* @param def The default value, if this argument is not given.
* @return The argument's value, or {@code def} if none was provided.
* @throws LuaException If the value is not a number.
*/
@ -364,7 +368,7 @@ public int optInt(int index, int def) throws LuaException {
* Get an argument as a long.
*
* @param index The argument number.
* @param def The public value, if this argument is not given.
* @param def The default value, if this argument is not given.
* @return The argument's value, or {@code def} if none was provided.
* @throws LuaException If the value is not a number.
*/
@ -376,7 +380,7 @@ public long optLong(int index, long def) throws LuaException {
* Get an argument as a finite number (not infinite or NaN).
*
* @param index The argument number.
* @param def The public value, if this argument is not given.
* @param def The default value, if this argument is not given.
* @return The argument's value, or {@code def} if none was provided.
* @throws LuaException If the value is not finite.
*/
@ -388,7 +392,7 @@ public double optFiniteDouble(int index, double def) throws LuaException {
* Get an argument as a boolean.
*
* @param index The argument number.
* @param def The public value, if this argument is not given.
* @param def The default value, if this argument is not given.
* @return The argument's value, or {@code def} if none was provided.
* @throws LuaException If the value is not a boolean.
*/
@ -400,11 +404,13 @@ public boolean optBoolean(int index, boolean def) throws LuaException {
* Get an argument as a string.
*
* @param index The argument number.
* @param def The public value, if this argument is not given.
* @param def The default value, if this argument is not given.
* @return The argument's value, or {@code def} if none was provided.
* @throws LuaException If the value is not a string.
*/
public String optString(int index, String def) throws LuaException {
@Nullable
@Contract("_, !null -> !null")
public String optString(int index, @Nullable String def) throws LuaException {
return optString(index).orElse(def);
}
@ -412,11 +418,13 @@ public String optString(int index, String def) throws LuaException {
* Get an argument as a table.
*
* @param index The argument number.
* @param def The public value, if this argument is not given.
* @param def The default value, if this argument is not given.
* @return The argument's value, or {@code def} if none was provided.
* @throws LuaException If the value is not a table.
*/
public Map<?, ?> optTable(int index, Map<Object, Object> def) throws LuaException {
@Nullable
@Contract("_, !null -> !null")
public Map<?, ?> optTable(int index, @Nullable Map<Object, Object> def) throws LuaException {
return optTable(index).orElse(def);
}
@ -436,9 +444,11 @@ public String optString(int index, String def) throws LuaException {
* yourself.
*
* @return An {@link IArguments} instance which can escape the current scope. May be {@code this}.
* @throws LuaException For the same reasons as {@link #get(int)}.
* @throws LuaException For the same reasons as {@link #get(int)}.
* @throws IllegalStateException If marking these arguments as escaping outside the scope of the original function.
*/
public IArguments escapes() throws LuaException {
// TODO(1.21.0): Make this return void, require that it mutates this.
return this;
}
}

View File

@ -4,21 +4,25 @@
package dan200.computercraft.api.lua;
import javax.annotation.Nullable;
import java.io.Serial;
/**
* An exception representing an error in Lua, like that raised by the {@code error()} function.
*/
public class LuaException extends Exception {
@Serial
private static final long serialVersionUID = -6136063076818512651L;
private final boolean hasLevel;
private final int level;
public LuaException(String message) {
public LuaException(@Nullable String message) {
super(message);
hasLevel = false;
level = 1;
}
public LuaException(String message, int level) {
public LuaException(@Nullable String message, int level) {
super(message);
hasLevel = true;
this.level = level;

View File

@ -4,6 +4,7 @@
package dan200.computercraft.api.lua;
import javax.annotation.Nullable;
import java.nio.ByteBuffer;
import java.util.Map;
@ -23,9 +24,9 @@ private LuaValues() {
* @return The encoded string.
*/
public static ByteBuffer encode(String string) {
byte[] chars = new byte[string.length()];
for (int i = 0; i < chars.length; i++) {
char c = string.charAt(i);
var chars = new byte[string.length()];
for (var i = 0; i < chars.length; i++) {
var c = string.charAt(i);
chars[i] = c < 256 ? (byte) c : 63;
}
@ -53,7 +54,7 @@ public static String getNumericType(double value) {
* @return A string representation of the given value's type, in a similar format to that provided by Lua's
* {@code type} function.
*/
public static String getType(Object value) {
public static String getType(@Nullable Object value) {
if (value == null) return "nil";
if (value instanceof String) return "string";
if (value instanceof Boolean) return "boolean";
@ -147,7 +148,7 @@ public static double checkFinite(int index, double value) throws LuaException {
* @throws LuaException If this is not a known enum value.
*/
public static <T extends Enum<T>> T checkEnum(int index, Class<T> klass, String value) throws LuaException {
for (T possibility : klass.getEnumConstants()) {
for (var possibility : klass.getEnumConstants()) {
if (possibility.name().equalsIgnoreCase(value)) return possibility;
}

View File

@ -4,6 +4,7 @@
package dan200.computercraft.api.lua;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@ -44,6 +45,7 @@ public IArguments drop(int count) {
return new ObjectArguments(args.subList(count, args.size()));
}
@Nullable
@Override
public Object get(int index) {
return index >= args.size() ? null : args.get(index);

View File

@ -152,6 +152,6 @@ private static int getColour(char c, Colour def) {
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
return 15 - def.ordinal();
return def.ordinal();
}
}

View File

@ -371,27 +371,36 @@ public final void delete(String path) throws LuaException {
public final Object[] open(String path, String mode) throws LuaException {
try {
switch (mode) {
// Open the file for reading, then create a wrapper around the reader
case "r":
case "r" -> {
// Open the file for reading, then create a wrapper around the reader
return new Object[]{ new EncodedReadableHandle(getFileSystem().openForRead(path)) };
// Open the file for writing, then create a wrapper around the writer
case "w":
}
case "w" -> {
// Open the file for writing, then create a wrapper around the writer
FileSystemExtensions.makeParentDir(fileSystem, path);
return new Object[]{ new EncodedWritableHandle(getFileSystem().openForWrite(path, false)) };
// Open the file for appending, then create a wrapper around the writer
case "a":
}
case "a" -> {
// Open the file for appending, then create a wrapper around the writer
FileSystemExtensions.makeParentDir(fileSystem, path);
return new Object[]{ new EncodedWritableHandle(getFileSystem().openForWrite(path, true)) };
// Open the file for binary reading, then create a wrapper around the reader
case "rb":
}
case "rb" -> {
// Open the file for binary reading, then create a wrapper around the reader
IMountedFileBinary reader = getFileSystem().openForBinaryRead(path);
return new Object[]{ new BinaryReadableHandle(reader) };
// Open the file for binary writing, then create a wrapper around the writer
case "wb":
}
case "wb" -> {
// Open the file for binary writing, then create a wrapper around the writer
FileSystemExtensions.makeParentDir(fileSystem, path);
return new Object[]{ new BinaryWritableHandle(getFileSystem().openForBinaryWrite(path, false)) };
// Open the file for binary appending, then create a wrapper around the reader
case "ab":
}
case "ab" -> {
// Open the file for binary appending, then create a wrapper around the reader
FileSystemExtensions.makeParentDir(fileSystem, path);
return new Object[]{ new BinaryWritableHandle(getFileSystem().openForBinaryWrite(path, true)) };
default:
throw new LuaException("Unsupported mode");
}
default -> throw new LuaException("Unsupported mode");
}
} catch (FileSystemException e) {
return new Object[]{ null, e.getMessage() };
@ -442,29 +451,6 @@ public final Object getFreeSpace(String path) throws LuaException {
}
}
/**
* Searches for files matching a string with wildcards.
* <p>
* This string is formatted like a normal path string, but can include any
* number of wildcards ({@code *}) to look for files matching anything.
* For example, <code>rom/&#42;/command*</code> will look for any path starting with
* {@code command} inside any subdirectory of {@code /rom}.
*
* @param path The wildcard-qualified path to search for.
* @return A list of paths that match the search string.
* @throws LuaException If the path doesn't exist.
* @cc.since 1.6
*/
@LuaFunction
public final String[] find(String path) throws LuaException {
try {
return FileSystemExtensions.find(getFileSystem(), path);
} catch (FileSystemException e) {
throw new LuaException(e.getMessage());
}
}
/**
* Returns the capacity of the drive the path is located on.
*

View File

@ -7,54 +7,15 @@
import dan200.computer.core.FileSystem;
import dan200.computer.core.FileSystemException;
import java.util.*;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
import java.util.regex.Pattern;
/**
* Backports additional methods from {@link FileSystem}.
*/
final class FileSystemExtensions {
private static void findIn(FileSystem fs, String dir, List<String> matches, Pattern wildPattern) throws FileSystemException {
String[] list = fs.list(dir);
for (String entry : list) {
String entryPath = dir.isEmpty() ? entry : dir + "/" + entry;
if (wildPattern.matcher(entryPath).matches()) {
matches.add(entryPath);
}
if (fs.isDir(entryPath)) {
findIn(fs, entryPath, matches, wildPattern);
}
}
}
public static synchronized String[] find(FileSystem fs, String wildPath) throws FileSystemException {
// Match all the files on the system
wildPath = sanitizePath(wildPath, true);
// If we don't have a wildcard at all just check the file exists
int starIndex = wildPath.indexOf('*');
if (starIndex == -1) {
return fs.exists(wildPath) ? new String[]{ wildPath } : new String[0];
}
// Find the all non-wildcarded directories. For instance foo/bar/baz* -> foo/bar
int prevDir = wildPath.substring(0, starIndex).lastIndexOf('/');
String startDir = prevDir == -1 ? "" : wildPath.substring(0, prevDir);
// If this isn't a directory then just abort
if (!fs.isDir(startDir)) return new String[0];
// Scan as normal, starting from this directory
Pattern wildPattern = Pattern.compile("^\\Q" + wildPath.replaceAll("\\*", "\\\\E[^\\\\/]*\\\\Q") + "\\E$");
List<String> matches = new ArrayList<>();
findIn(fs, startDir, matches, wildPattern);
// Return matches
String[] array = matches.toArray(new String[0]);
Arrays.sort(array);
return array;
}
public static String getDirectory(String path) {
path = sanitizePath(path, true);
if (path.isEmpty()) {
@ -69,6 +30,11 @@ public static String getDirectory(String path) {
}
}
public static void makeParentDir(FileSystem fileSystem, String path) throws FileSystemException {
var parent = getDirectory(path);
if (!parent.isEmpty()) fileSystem.makeDir(parent);
}
private static final Pattern threeDotsPattern = Pattern.compile("^\\.{3,}$");
public static String sanitizePath(String path, boolean allowWildcards) {

View File

@ -9,10 +9,10 @@ private StringUtil() {
}
public static String normaliseLabel(String label) {
int length = Math.min(32, label.length());
StringBuilder builder = new StringBuilder(length);
for (int i = 0; i < length; i++) {
char c = label.charAt(i);
var length = Math.min(32, label.length());
var builder = new StringBuilder(length);
for (var i = 0; i < length; i++) {
var c = label.charAt(i);
if ((c >= ' ' && c <= '~') || (c >= 161 && c <= 172) || (c >= 174 && c <= 255)) {
builder.append(c);
} else {

View File

@ -687,7 +687,7 @@ settings.define("paint.default_extension", {
settings.define("list.show_hidden", {
default = false,
description = [[Show hidden files (those starting with "." in the Lua REPL).]],
description = [[Whether the list program show hidden files (those starting with ".").]],
type = "boolean",
})

View File

@ -3,20 +3,20 @@
-- SPDX-License-Identifier: LicenseRef-CCPL
--[[- Constants and functions for colour values, suitable for working with
@{term} and @{redstone}.
[`term`] and [`redstone`].
This is useful in conjunction with @{redstone.setBundledOutput|Bundled Cables}
from mods like Project Red, and @{term.setTextColour|colors on Advanced
Computers and Advanced Monitors}.
This is useful in conjunction with [Bundled Cables][`redstone.setBundledOutput`]
from mods like Project Red, and [colors on Advanced Computers and Advanced
Monitors][`term.setTextColour`].
For the non-American English version just replace @{colors} with @{colours}.
For the non-American English version just replace [`colors`] with [`colours`].
This alternative API is exactly the same, except the colours use British English
(e.g. @{colors.gray} is spelt @{colours.grey}).
(e.g. [`colors.gray`] is spelt [`colours.grey`]).
On basic terminals (such as the Computer and Monitor), all the colors are
converted to grayscale. This means you can still use all 16 colors on the
screen, but they will appear as the nearest tint of gray. You can check if a
terminal supports color by using the function @{term.isColor}.
terminal supports color by using the function [`term.isColor`].
Grayscale colors are calculated by taking the average of the three components,
i.e. `(red + green + blue) / 3`.
@ -140,67 +140,67 @@ i.e. `(red + green + blue) / 3`.
local expect = dofile("rom/modules/main/cc/expect.lua").expect
--- White: Written as `0` in paint files and @{term.blit}, has a default
--- White: Written as `0` in paint files and [`term.blit`], has a default
-- terminal colour of #F0F0F0.
white = 0x1
--- Orange: Written as `1` in paint files and @{term.blit}, has a
--- Orange: Written as `1` in paint files and [`term.blit`], has a
-- default terminal colour of #F2B233.
orange = 0x2
--- Magenta: Written as `2` in paint files and @{term.blit}, has a
--- Magenta: Written as `2` in paint files and [`term.blit`], has a
-- default terminal colour of #E57FD8.
magenta = 0x4
--- Light blue: Written as `3` in paint files and @{term.blit}, has a
--- Light blue: Written as `3` in paint files and [`term.blit`], has a
-- default terminal colour of #99B2F2.
lightBlue = 0x8
--- Yellow: Written as `4` in paint files and @{term.blit}, has a
--- Yellow: Written as `4` in paint files and [`term.blit`], has a
-- default terminal colour of #DEDE6C.
yellow = 0x10
--- Lime: Written as `5` in paint files and @{term.blit}, has a default
--- Lime: Written as `5` in paint files and [`term.blit`], has a default
-- terminal colour of #7FCC19.
lime = 0x20
--- Pink: Written as `6` in paint files and @{term.blit}, has a default
--- Pink: Written as `6` in paint files and [`term.blit`], has a default
-- terminal colour of #F2B2CC.
pink = 0x40
--- Gray: Written as `7` in paint files and @{term.blit}, has a default
--- Gray: Written as `7` in paint files and [`term.blit`], has a default
-- terminal colour of #4C4C4C.
gray = 0x80
--- Light gray: Written as `8` in paint files and @{term.blit}, has a
--- Light gray: Written as `8` in paint files and [`term.blit`], has a
-- default terminal colour of #999999.
lightGray = 0x100
--- Cyan: Written as `9` in paint files and @{term.blit}, has a default
--- Cyan: Written as `9` in paint files and [`term.blit`], has a default
-- terminal colour of #4C99B2.
cyan = 0x200
--- Purple: Written as `a` in paint files and @{term.blit}, has a
--- Purple: Written as `a` in paint files and [`term.blit`], has a
-- default terminal colour of #B266E5.
purple = 0x400
--- Blue: Written as `b` in paint files and @{term.blit}, has a default
--- Blue: Written as `b` in paint files and [`term.blit`], has a default
-- terminal colour of #3366CC.
blue = 0x800
--- Brown: Written as `c` in paint files and @{term.blit}, has a default
--- Brown: Written as `c` in paint files and [`term.blit`], has a default
-- terminal colour of #7F664C.
brown = 0x1000
--- Green: Written as `d` in paint files and @{term.blit}, has a default
--- Green: Written as `d` in paint files and [`term.blit`], has a default
-- terminal colour of #57A64E.
green = 0x2000
--- Red: Written as `e` in paint files and @{term.blit}, has a default
--- Red: Written as `e` in paint files and [`term.blit`], has a default
-- terminal colour of #CC4C4C.
red = 0x4000
--- Black: Written as `f` in paint files and @{term.blit}, has a default
--- Black: Written as `f` in paint files and [`term.blit`], has a default
-- terminal colour of #111111.
black = 0x8000
@ -313,18 +313,18 @@ function unpackRGB(rgb)
bit32.band(rgb, 0xFF) / 255
end
--- Either calls @{colors.packRGB} or @{colors.unpackRGB}, depending on how many
--- Either calls [`colors.packRGB`] or [`colors.unpackRGB`], depending on how many
-- arguments it receives.
--
-- @tparam[1] number r The red channel, as an argument to @{colors.packRGB}.
-- @tparam[1] number g The green channel, as an argument to @{colors.packRGB}.
-- @tparam[1] number b The blue channel, as an argument to @{colors.packRGB}.
-- @tparam[2] number rgb The combined hexadecimal color, as an argument to @{colors.unpackRGB}.
-- @treturn[1] number The combined hexadecimal colour, as returned by @{colors.packRGB}.
-- @treturn[2] number The red channel, as returned by @{colors.unpackRGB}
-- @treturn[2] number The green channel, as returned by @{colors.unpackRGB}
-- @treturn[2] number The blue channel, as returned by @{colors.unpackRGB}
-- @deprecated Use @{packRGB} or @{unpackRGB} directly.
-- @tparam[1] number r The red channel, as an argument to [`colors.packRGB`].
-- @tparam[1] number g The green channel, as an argument to [`colors.packRGB`].
-- @tparam[1] number b The blue channel, as an argument to [`colors.packRGB`].
-- @tparam[2] number rgb The combined hexadecimal color, as an argument to [`colors.unpackRGB`].
-- @treturn[1] number The combined hexadecimal colour, as returned by [`colors.packRGB`].
-- @treturn[2] number The red channel, as returned by [`colors.unpackRGB`]
-- @treturn[2] number The green channel, as returned by [`colors.unpackRGB`]
-- @treturn[2] number The blue channel, as returned by [`colors.unpackRGB`]
-- @deprecated Use [`packRGB`] or [`unpackRGB`] directly.
-- @usage
-- ```lua
-- colors.rgb8(0xb23399)

View File

@ -2,7 +2,7 @@
--
-- SPDX-License-Identifier: LicenseRef-CCPL
--- An alternative version of @{colors} for lovers of British spelling.
--- An alternative version of [`colors`] for lovers of British spelling.
--
-- @see colors
-- @module colours
@ -13,14 +13,14 @@ for k, v in pairs(colors) do
colours[k] = v
end
--- Grey. Written as `7` in paint files and @{term.blit}, has a default
--- Grey. Written as `7` in paint files and [`term.blit`], has a default
-- terminal colour of #4C4C4C.
--
-- @see colors.gray
colours.grey = colors.gray
colours.gray = nil --- @local
--- Light grey. Written as `8` in paint files and @{term.blit}, has a
--- Light grey. Written as `8` in paint files and [`term.blit`], has a
-- default terminal colour of #999999.
--
-- @see colors.lightGray

View File

@ -5,20 +5,19 @@
--[[- Execute [Minecraft commands][mc] and gather data from the results from
a command computer.
:::note
This API is only available on Command computers. It is not accessible to normal
players.
:::
> [!NOTE]
> This API is only available on Command computers. It is not accessible to normal
> players.
While one may use @{commands.exec} directly to execute a command, the
While one may use [`commands.exec`] directly to execute a command, the
commands API also provides helper methods to execute every command. For
instance, `commands.say("Hi!")` is equivalent to `commands.exec("say Hi!")`.
@{commands.async} provides a similar interface to execute asynchronous
[`commands.async`] provides a similar interface to execute asynchronous
commands. `commands.async.say("Hi!")` is equivalent to
`commands.execAsync("say Hi!")`.
[mc]: https://minecraft.gamepedia.com/Commands
[mc]: https://minecraft.wiki/w/Commands
@module commands
@usage Set the block above this computer to stone:
@ -31,7 +30,7 @@ end
--- The builtin commands API, without any generated command helper functions
--
-- This may be useful if a built-in function (such as @{commands.list}) has been
-- This may be useful if a built-in function (such as [`commands.list`]) has been
-- overwritten by a command.
local native = commands.native or commands
@ -112,7 +111,7 @@ end
--- A table containing asynchronous wrappers for all commands.
--
-- As with @{commands.execAsync}, this returns the "task id" of the enqueued
-- As with [`commands.execAsync`], this returns the "task id" of the enqueued
-- command.
-- @see execAsync
-- @usage Asynchronously sets the block above the computer to stone.

View File

@ -9,10 +9,9 @@ locally attached drive, specify “side” as one of the six sides (e.g. `left`)
use a remote disk drive, specify its name as printed when enabling its modem
(e.g. `drive_0`).
:::tip
All computers (except command computers), turtles and pocket computers can be
placed within a disk drive to access it's internal storage like a disk.
:::
> [!TIP]
> All computers (except command computers), turtles and pocket computers can be
> placed within a disk drive to access it's internal storage like a disk.
@module disk
@since 1.2
@ -95,9 +94,9 @@ end
--- Whether the current disk is a [music disk][disk] as opposed to a floppy disk
-- or other item.
--
-- If this returns true, you will can @{disk.playAudio|play} the record.
-- If this returns true, you will can [play][`disk.playAudio`] the record.
--
-- [disk]: https://minecraft.gamepedia.com/Music_Disc
-- [disk]: https://minecraft.wiki/w/Music_Disc
--
-- @tparam string name The name of the disk drive.
-- @treturn boolean If the disk is present and has audio saved on it.
@ -110,10 +109,10 @@ end
--- Get the title of the audio track from the music record in the drive.
--
-- This generally returns the same as @{disk.getLabel} for records.
-- This generally returns the same as [`disk.getLabel`] for records.
--
-- @tparam string name The name of the disk drive.
-- @treturn string|false|nil The track title, @{false} if there is not a music
-- @treturn string|false|nil The track title, [`false`] if there is not a music
-- record in the drive or `nil` if no drive is present.
function getAudioTitle(name)
if isDrive(name) then
@ -126,7 +125,7 @@ end
--
-- If any record is already playing on any disk drive, it stops before the
-- target drive starts playing. The record stops when it reaches the end of the
-- track, when it is removed from the drive, when @{disk.stopAudio} is called, or
-- track, when it is removed from the drive, when [`disk.stopAudio`] is called, or
-- when another record is started.
--
-- @tparam string name The name of the disk drive.
@ -138,7 +137,7 @@ function playAudio(name)
end
--- Stops the music record in the drive from playing, if it was started with
-- @{disk.playAudio}.
-- [`disk.playAudio`].
--
-- @tparam string name The name o the disk drive.
function stopAudio(name)
@ -165,7 +164,7 @@ end
--- Returns a number which uniquely identifies the disk in the drive.
--
-- Note, unlike @{disk.getLabel}, this does not return anything for other media,
-- Note, unlike [`disk.getLabel`], this does not return anything for other media,
-- such as computers or turtles.
--
-- @tparam string name The name of the disk drive.

View File

@ -13,19 +13,19 @@ local fs = _ENV
for k, v in pairs(native) do fs[k] = v end
--[[- Provides completion for a file or directory name, suitable for use with
@{_G.read}.
[`_G.read`].
When a directory is a possible candidate for completion, two entries are
included - one with a trailing slash (indicating that entries within this
directory exist) and one without it (meaning this entry is an immediate
completion candidate). `include_dirs` can be set to @{false} to only include
completion candidate). `include_dirs` can be set to [`false`] to only include
those with a trailing slash.
@tparam[1] string path The path to complete.
@tparam[1] string location The location where paths are resolved from.
@tparam[1,opt=true] boolean include_files When @{false}, only directories will
@tparam[1,opt=true] boolean include_files When [`false`], only directories will
be included in the returned list.
@tparam[1,opt=true] boolean include_dirs When @{false}, "raw" directories will
@tparam[1,opt=true] boolean include_dirs When [`false`], "raw" directories will
not be included in the returned list.
@tparam[2] string path The path to complete.
@ -133,6 +133,93 @@ function fs.complete(sPath, sLocation, bIncludeFiles, bIncludeDirs)
return {}
end
local function find_aux(path, parts, i, out)
local part = parts[i]
if not part then
-- If we're at the end of the pattern, ensure our path exists and append it.
if fs.exists(path) then out[#out + 1] = path end
elseif part.exact then
-- If we're an exact match, just recurse into this directory.
return find_aux(fs.combine(path, part.contents), parts, i + 1, out)
else
-- Otherwise we're a pattern. Check we're a directory, then recurse into each
-- matching file.
if not fs.isDir(path) then return end
local files = fs.list(path)
for j = 1, #files do
local file = files[j]
if file:find(part.contents) then find_aux(fs.combine(path, file), parts, i + 1, out) end
end
end
end
local find_escape = {
-- Escape standard Lua pattern characters
["^"] = "%^", ["$"] = "%$", ["("] = "%(", [")"] = "%)", ["%"] = "%%",
["."] = "%.", ["["] = "%[", ["]"] = "%]", ["+"] = "%+", ["-"] = "%-",
-- Aside from our wildcards.
["*"] = ".*",
["?"] = ".",
}
--[[- Searches for files matching a string with wildcards.
This string looks like a normal path string, but can include wildcards, which
can match multiple paths:
- "?" matches any single character in a file name.
- "*" matches any number of characters.
For example, `rom/*/command*` will look for any path starting with `command`
inside any subdirectory of `/rom`.
Note that these wildcards match a single segment of the path. For instance
`rom/*.lua` will include `rom/startup.lua` but _not_ include `rom/programs/list.lua`.
@tparam string path The wildcard-qualified path to search for.
@treturn { string... } A list of paths that match the search string.
@throws If the supplied path was invalid.
@since 1.6
@changed 1.106.0 Added support for the `?` wildcard.
@usage List all Markdown files in the help folder
fs.find("rom/help/*.md")
]]
function fs.find(pattern)
expect(1, pattern, "string")
pattern = fs.combine(pattern) -- Normalise the path, removing ".."s.
-- If the pattern is trying to search outside the computer root, just abort.
-- This will fail later on anyway.
if pattern == ".." or pattern:sub(1, 3) == "../" then
error("/" .. pattern .. ": Invalid Path", 2)
end
-- If we've no wildcards, just check the file exists.
if not pattern:find("[*?]") then
if fs.exists(pattern) then return { pattern } else return {} end
end
local parts = {}
for part in pattern:gmatch("[^/]+") do
if part:find("[*?]") then
parts[#parts + 1] = {
exact = false,
contents = "^" .. part:gsub(".", find_escape) .. "$",
}
else
parts[#parts + 1] = { exact = true, contents = part }
end
end
local out = {}
find_aux("", parts, 1, out)
return out
end
--- Returns true if a path is mounted to the parent filesystem.
--
-- The root filesystem "/" is considered a mount, along with disk folders and

View File

@ -2,21 +2,20 @@
--
-- SPDX-License-Identifier: LicenseRef-CCPL
--[[- Use @{modem|modems} to locate the position of the current turtle or
--[[- Use [modems][`modem`] to locate the position of the current turtle or
computers.
It broadcasts a PING message over @{rednet} and wait for responses. In order for
It broadcasts a PING message over [`rednet`] and wait for responses. In order for
this system to work, there must be at least 4 computers used as gps hosts which
will respond and allow trilateration. Three of these hosts should be in a plane,
and the fourth should be either above or below the other three. The three in a
plane should not be in a line with each other. You can set up hosts using the
gps program.
:::note
When entering in the coordinates for the host you need to put in the `x`, `y`,
and `z` coordinates of the block that the modem is connected to, not the modem.
All modem distances are measured from the block that the modem is placed on.
:::
> [!NOTE]
> When entering in the coordinates for the host you need to put in the `x`, `y`,
> and `z` coordinates of the block that the modem is connected to, not the modem.
> All modem distances are measured from the block that the modem is placed on.
Also note that you may choose which axes x, y, or z refers to - so long as your
systems have the same definition as any GPS servers that're in range, it works

View File

@ -74,10 +74,10 @@ handleMetatable = {
This can be used in a for loop to iterate over all lines of a file
Once the end of the file has been reached, @{nil} will be returned. The file is
Once the end of the file has been reached, [`nil`] will be returned. The file is
*not* automatically closed.
@param ... The argument to pass to @{Handle:read} for each line.
@param ... The argument to pass to [`Handle:read`] for each line.
@treturn function():string|nil The line iterator.
@throws If the file cannot be opened for reading
@since 1.3
@ -324,14 +324,14 @@ each time it is called, returns a new line from the file.
This can be used in a for loop to iterate over all lines of a file
Once the end of the file has been reached, @{nil} will be returned. The file is
Once the end of the file has been reached, [`nil`] will be returned. The file is
automatically closed.
If no file name is given, the @{io.input|current input} will be used instead.
If no file name is given, the [current input][`io.input`] will be used instead.
In this case, the handle is not used.
@tparam[opt] string filename The name of the file to extract lines from
@param ... The argument to pass to @{Handle:read} for each line.
@param ... The argument to pass to [`Handle:read`] for each line.
@treturn function():string|nil The line iterator.
@throws If the file cannot be opened for reading
@ -362,7 +362,7 @@ function lines(filename, ...)
end
--- Open a file with the given mode, either returning a new file handle
-- or @{nil}, plus an error message.
-- or [`nil`], plus an error message.
--
-- The `mode` string can be any of the following:
-- - **"r"**: Read mode
@ -410,11 +410,11 @@ end
--- Read from the currently opened input file.
--
-- This is equivalent to `io.input():read(...)`. See @{Handle:read|the
-- documentation} there for full details.
-- This is equivalent to `io.input():read(...)`. See [the documentation][`Handle:read`]
-- there for full details.
--
-- @tparam string ... The formats to read, defaulting to a whole line.
-- @treturn (string|nil)... The data read, or @{nil} if nothing can be read.
-- @treturn (string|nil)... The data read, or [`nil`] if nothing can be read.
function read(...)
return currentInput:read(...)
end
@ -438,8 +438,8 @@ end
--- Write to the currently opened output file.
--
-- This is equivalent to `io.output():write(...)`. See @{Handle:write|the
-- documentation} there for full details.
-- This is equivalent to `io.output():write(...)`. See [the documentation][`Handle:write`]
-- there for full details.
--
-- @tparam string ... The strings to write
-- @changed 1.81.0 Multiple arguments are now allowed.

View File

@ -51,7 +51,7 @@ end
--
-- @tparam string image The string containing the raw-image data.
-- @treturn table The parsed image data, suitable for use with
-- @{paintutils.drawImage}.
-- [`paintutils.drawImage`].
-- @since 1.80pr1
function parseImage(image)
expect(1, image, "string")
@ -69,7 +69,7 @@ end
-- @tparam string path The file to load.
--
-- @treturn table|nil The parsed image data, suitable for use with
-- @{paintutils.drawImage}, or `nil` if the file does not exist.
-- [`paintutils.drawImage`], or `nil` if the file does not exist.
-- @usage Load an image and draw it.
--
-- local image = paintutils.loadImage("data/example.nfp")
@ -93,7 +93,7 @@ end
--
-- @tparam number xPos The x position to draw at, where 1 is the far left.
-- @tparam number yPos The y position to draw at, where 1 is the very top.
-- @tparam[opt] number colour The @{colors|color} of this pixel. This will be
-- @tparam[opt] number colour The [color][`colors`] of this pixel. This will be
-- the current background colour if not specified.
function drawPixel(xPos, yPos, colour)
expect(1, xPos, "number")
@ -115,7 +115,7 @@ end
-- @tparam number startY The starting y position of the line.
-- @tparam number endX The end x position of the line.
-- @tparam number endY The end y position of the line.
-- @tparam[opt] number colour The @{colors|color} of this pixel. This will be
-- @tparam[opt] number colour The [color][`colors`] of this pixel. This will be
-- the current background colour if not specified.
-- @usage paintutils.drawLine(2, 3, 30, 7, colors.red)
function drawLine(startX, startY, endX, endY, colour)
@ -189,7 +189,7 @@ end
-- @tparam number startY The starting y position of the line.
-- @tparam number endX The end x position of the line.
-- @tparam number endY The end y position of the line.
-- @tparam[opt] number colour The @{colors|color} of this pixel. This will be
-- @tparam[opt] number colour The [color][`colors`] of this pixel. This will be
-- the current background colour if not specified.
-- @usage paintutils.drawBox(2, 3, 30, 7, colors.red)
function drawBox(startX, startY, endX, endY, nColour)
@ -242,7 +242,7 @@ end
-- @tparam number startY The starting y position of the line.
-- @tparam number endX The end x position of the line.
-- @tparam number endY The end y position of the line.
-- @tparam[opt] number colour The @{colors|color} of this pixel. This will be
-- @tparam[opt] number colour The [color][`colors`] of this pixel. This will be
-- the current background colour if not specified.
-- @usage paintutils.drawFilledBox(2, 3, 30, 7, colors.red)
function drawFilledBox(startX, startY, endX, endY, nColour)
@ -278,7 +278,7 @@ function drawFilledBox(startX, startY, endX, endY, nColour)
end
end
--- Draw an image loaded by @{paintutils.parseImage} or @{paintutils.loadImage}.
--- Draw an image loaded by [`paintutils.parseImage`] or [`paintutils.loadImage`].
--
-- @tparam table image The parsed image data.
-- @tparam number xPos The x position to start drawing at.

View File

@ -6,36 +6,34 @@
Functions are not actually executed simultaneously, but rather this API will
automatically switch between them whenever they yield (e.g. whenever they call
@{coroutine.yield}, or functions that call that - such as @{os.pullEvent} - or
[`coroutine.yield`], or functions that call that - such as [`os.pullEvent`] - or
functions that call that, etc - basically, anything that causes the function
to "pause").
Each function executed in "parallel" gets its own copy of the event queue,
and so "event consuming" functions (again, mostly anything that causes the
script to pause - eg @{os.sleep}, @{rednet.receive}, most of the @{turtle} API,
script to pause - eg [`os.sleep`], [`rednet.receive`], most of the [`turtle`] API,
etc) can safely be used in one without affecting the event queue accessed by
the other.
:::caution
When using this API, be careful to pass the functions you want to run in
parallel, and _not_ the result of calling those functions.
For instance, the following is correct:
```lua
local function do_sleep() sleep(1) end
parallel.waitForAny(do_sleep, rednet.receive)
```
but the following is **NOT**:
```lua
local function do_sleep() sleep(1) end
parallel.waitForAny(do_sleep(), rednet.receive)
```
:::
> [!WARNING]
> When using this API, be careful to pass the functions you want to run in
> parallel, and _not_ the result of calling those functions.
>
> For instance, the following is correct:
>
> ```lua
> local function do_sleep() sleep(1) end
> parallel.waitForAny(do_sleep, rednet.receive)
> ```
>
> but the following is **NOT**:
>
> ```lua
> local function do_sleep() sleep(1) end
> parallel.waitForAny(do_sleep(), rednet.receive)
> ```
@module parallel
@since 1.2
@ -100,7 +98,7 @@ end
--[[- Switches between execution of the functions, until any of them
finishes. If any of the functions errors, the message is propagated upwards
from the @{parallel.waitForAny} call.
from the [`parallel.waitForAny`] call.
@tparam function ... The functions this task will run
@usage Print a message every second until the `q` key is pressed.
@ -128,7 +126,7 @@ end
--[[- Switches between execution of the functions, until all of them are
finished. If any of the functions errors, the message is propagated upwards
from the @{parallel.waitForAll} call.
from the [`parallel.waitForAll`] call.
@tparam function ... The functions this task will run
@usage Start off two timers and wait for them both to run.

View File

@ -5,8 +5,8 @@
--[[- Find and control peripherals attached to this computer.
Peripherals are blocks (or turtle and pocket computer upgrades) which can
be controlled by a computer. For instance, the @{speaker} peripheral allows a
computer to play music and the @{monitor} peripheral allows you to display text
be controlled by a computer. For instance, the [`speaker`] peripheral allows a
computer to play music and the [`monitor`] peripheral allows you to display text
in the world.
## Referencing peripherals
@ -18,10 +18,10 @@ computer will be called `"bottom"` in your Lua code, one to the left called
`"right"`, `"front"`, `"back"`).
You can list the names of all peripherals with the `peripherals` program, or the
@{peripheral.getNames} function.
[`peripheral.getNames`] function.
It's also possible to use peripherals which are further away from your computer
through the use of @{modem|Wired Modems}. Place one modem against your computer
through the use of [Wired Modems][`modem`]. Place one modem against your computer
(you may need to sneak and right click), run Networking Cable to your
peripheral, and then place another modem against that block. You can then right
click the modem to use (or *attach*) the peripheral. This will print a
@ -32,24 +32,23 @@ clipboard.
## Using peripherals
Once you have the name of a peripheral, you can call functions on it using the
@{peripheral.call} function. This takes the name of our peripheral, the name of
[`peripheral.call`] function. This takes the name of our peripheral, the name of
the function we want to call, and then its arguments.
:::info
Some bits of the peripheral API call peripheral functions *methods* instead
(for example, the @{peripheral.getMethods} function). Don't worry, they're the
same thing!
:::
> [!INFO]
> Some bits of the peripheral API call peripheral functions *methods* instead
> (for example, the [`peripheral.getMethods`] function). Don't worry, they're the
> same thing!
Let's say we have a monitor above our computer (and so "top") and want to
@{monitor.write|write some text to it}. We'd write the following:
[write some text to it][`monitor.write`]. We'd write the following:
```lua
peripheral.call("top", "write", "This is displayed on a monitor!")
```
Once you start calling making a couple of peripheral calls this can get very
repetitive, and so we can @{peripheral.wrap|wrap} a peripheral. This builds a
repetitive, and so we can [wrap][`peripheral.wrap`] a peripheral. This builds a
table of all the peripheral's functions so you can use it like an API or module.
For instance, we could have written the above example as follows:
@ -66,7 +65,7 @@ called, you just need to know it's there. For instance, if you're writing a
music player, you just need a speaker - it doesn't matter if it's above or below
the computer.
Thankfully there's a quick way to do this: @{peripheral.find}. This takes a
Thankfully there's a quick way to do this: [`peripheral.find`]. This takes a
*peripheral type* and returns all the attached peripherals which are of this
type.
@ -76,10 +75,10 @@ are just called `"speaker"`, and monitors `"monitor"`. Some peripherals might
have more than one type - a Minecraft chest is both a `"minecraft:chest"` and
`"inventory"`.
You can get all the types a peripheral has with @{peripheral.getType}, and check
a peripheral is a specific type with @{peripheral.hasType}.
You can get all the types a peripheral has with [`peripheral.getType`], and check
a peripheral is a specific type with [`peripheral.hasType`].
To return to our original example, let's use @{peripheral.find} to find an
To return to our original example, let's use [`peripheral.find`] to find an
attached speaker:
```lua
@ -233,7 +232,7 @@ function getMethods(name)
return nil
end
--- Get the name of a peripheral wrapped with @{peripheral.wrap}.
--- Get the name of a peripheral wrapped with [`peripheral.wrap`].
--
-- @tparam table peripheral The peripheral to get the name of.
-- @treturn string The name of the given peripheral.
@ -274,7 +273,7 @@ function call(name, method, ...)
end
--- Get a table containing all functions available on a peripheral. These can
-- then be called instead of using @{peripheral.call} every time.
-- then be called instead of using [`peripheral.call`] every time.
--
-- @tparam string name The name of the peripheral to wrap.
-- @treturn table|nil The table containing the peripheral's methods, or `nil` if
@ -309,7 +308,7 @@ function wrap(name)
end
--[[- Find all peripherals of a specific type, and return the
@{peripheral.wrap|wrapped} peripherals.
[wrapped][`peripheral.wrap`] peripherals.
@tparam string ty The type of peripheral to look for.
@tparam[opt] function(name:string, wrapped:table):boolean filter A
@ -329,7 +328,7 @@ and returns if it should be included in the result.
return modem.isWireless() -- Check this modem is wireless.
end) }
@usage This abuses the `filter` argument to call @{rednet.open} on every modem.
@usage This abuses the `filter` argument to call [`rednet.open`] on every modem.
peripheral.find("modem", rednet.open)
@since 1.6

View File

@ -2,42 +2,41 @@
--
-- SPDX-License-Identifier: LicenseRef-CCPL
--[[- Communicate with other computers by using @{modem|modems}. @{rednet}
provides a layer of abstraction on top of the main @{modem} peripheral, making
--[[- Communicate with other computers by using [modems][`modem`]. [`rednet`]
provides a layer of abstraction on top of the main [`modem`] peripheral, making
it slightly easier to use.
## Basic usage
In order to send a message between two computers, each computer must have a
modem on one of its sides (or in the case of pocket computers and turtles, the
modem must be equipped as an upgrade). The two computers should then call
@{rednet.open}, which sets up the modems ready to send and receive messages.
[`rednet.open`], which sets up the modems ready to send and receive messages.
Once rednet is opened, you can send messages using @{rednet.send} and receive
them using @{rednet.receive}. It's also possible to send a message to _every_
rednet-using computer using @{rednet.broadcast}.
Once rednet is opened, you can send messages using [`rednet.send`] and receive
them using [`rednet.receive`]. It's also possible to send a message to _every_
rednet-using computer using [`rednet.broadcast`].
:::caution Network security
While rednet provides a friendly way to send messages to specific computers, it
doesn't provide any guarantees about security. Other computers could be
listening in to your messages, or even pretending to send messages from other computers!
If you're playing on a multi-player server (or at least one where you don't
trust other players), it's worth encrypting or signing your rednet messages.
:::
> [Network security][!WARNING]
>
> While rednet provides a friendly way to send messages to specific computers, it
> doesn't provide any guarantees about security. Other computers could be
> listening in to your messages, or even pretending to send messages from other computers!
>
> If you're playing on a multi-player server (or at least one where you don't
> trust other players), it's worth encrypting or signing your rednet messages.
## Protocols and hostnames
Several rednet messages accept "protocol"s - simple string names describing what
a message is about. When sending messages using @{rednet.send} and
@{rednet.broadcast}, you can optionally specify a protocol for the message. This
same protocol can then be given to @{rednet.receive}, to ignore all messages not
a message is about. When sending messages using [`rednet.send`] and
[`rednet.broadcast`], you can optionally specify a protocol for the message. This
same protocol can then be given to [`rednet.receive`], to ignore all messages not
using this protocol.
It's also possible to look-up computers based on protocols, providing a basic
system for service discovery and [DNS]. A computer can advertise that it
supports a particular protocol with @{rednet.host}, also providing a friendly
supports a particular protocol with [`rednet.host`], also providing a friendly
"hostname". Other computers may then find all computers which support this
protocol using @{rednet.lookup}.
protocol using [`rednet.lookup`].
[DNS]: https://en.wikipedia.org/wiki/Domain_Name_System "Domain Name System"
@ -50,7 +49,7 @@ bare-bones but flexible interface.
local expect = dofile("rom/modules/main/cc/expect.lua").expect
--- The channel used by the Rednet API to @{broadcast} messages.
--- The channel used by the Rednet API to [`broadcast`] messages.
CHANNEL_BROADCAST = 65535
--- The channel used by the Rednet API to repeat messages.
@ -68,12 +67,12 @@ local function id_as_channel(id)
return (id or os.getComputerID()) % MAX_ID_CHANNELS
end
--[[- Opens a modem with the given @{peripheral} name, allowing it to send and
--[[- Opens a modem with the given [`peripheral`] name, allowing it to send and
receive messages over rednet.
This will open the modem on two channels: one which has the same
@{os.getComputerID|ID} as the computer, and another on
@{CHANNEL_BROADCAST|the broadcast channel}.
[ID][`os.getComputerID`] as the computer, and another on
[the broadcast channel][`CHANNEL_BROADCAST`].
@tparam string modem The name of the modem to open.
@throws If there is no such modem with the given name
@ -83,7 +82,7 @@ rednet messages using it.
rednet.open("back")
@usage Open rednet on all attached modems. This abuses the "filter" argument to
@{peripheral.find}.
[`peripheral.find`].
peripheral.find("modem", rednet.open)
@see rednet.close
@ -98,7 +97,7 @@ function open(modem)
peripheral.call(modem, "open", CHANNEL_BROADCAST)
end
--- Close a modem with the given @{peripheral} name, meaning it can no longer
--- Close a modem with the given [`peripheral`] name, meaning it can no longer
-- send and receive rednet messages.
--
-- @tparam[opt] string modem The side the modem exists on. If not given, all
@ -151,21 +150,21 @@ end
--[[- Allows a computer or turtle with an attached modem to send a message
intended for a sycomputer with a specific ID. At least one such modem must first
be @{rednet.open|opened} before sending is possible.
be [opened][`rednet.open`] before sending is possible.
Assuming the target was in range and also had a correctly opened modem, the
target computer may then use @{rednet.receive} to collect the message.
target computer may then use [`rednet.receive`] to collect the message.
@tparam number recipient The ID of the receiving computer.
@param message The message to send. Like with @{modem.transmit}, this can
@param message The message to send. Like with [`modem.transmit`], this can
contain any primitive type (numbers, booleans and strings) as well as
tables. Other types (like functions), as well as metatables, will not be
transmitted.
@tparam[opt] string protocol The "protocol" to send this message under. When
using @{rednet.receive} one can filter to only receive messages sent under a
using [`rednet.receive`] one can filter to only receive messages sent under a
particular protocol.
@treturn boolean If this message was successfully sent (i.e. if rednet is
currently @{rednet.open|open}). Note, this does not guarantee the message was
currently [open][`rednet.open`]). Note, this does not guarantee the message was
actually _received_.
@changed 1.6 Added protocol parameter.
@changed 1.82.0 Now returns whether the message was successfully sent.
@ -217,13 +216,13 @@ function send(recipient, message, protocol)
return sent
end
--[[- Broadcasts a string message over the predefined @{CHANNEL_BROADCAST}
--[[- Broadcasts a string message over the predefined [`CHANNEL_BROADCAST`]
channel. The message will be received by every device listening to rednet.
@param message The message to send. This should not contain coroutines or
functions, as they will be converted to @{nil}.
functions, as they will be converted to [`nil`].
@tparam[opt] string protocol The "protocol" to send this message under. When
using @{rednet.receive} one can filter to only receive messages sent under a
using [`rednet.receive`] one can filter to only receive messages sent under a
particular protocol.
@see rednet.receive
@changed 1.6 Added protocol parameter.
@ -311,7 +310,7 @@ function receive(protocol_filter, timeout)
end
--[[- Register the system as "hosting" the desired protocol under the specified
name. If a rednet @{rednet.lookup|lookup} is performed for that protocol (and
name. If a rednet [lookup][`rednet.lookup`] is performed for that protocol (and
maybe name) on the same network, the registered system will automatically
respond via a background process, hence providing the system performing the
lookup with its ID number.
@ -343,8 +342,8 @@ function host(protocol, hostname)
end
end
--- Stop @{rednet.host|hosting} a specific protocol, meaning it will no longer
-- respond to @{rednet.lookup} requests.
--- Stop [hosting][`rednet.host`] a specific protocol, meaning it will no longer
-- respond to [`rednet.lookup`] requests.
--
-- @tparam string protocol The protocol to unregister your self from.
-- @since 1.6
@ -353,7 +352,7 @@ function unhost(protocol)
hostnames[protocol] = nil
end
--[[- Search the local rednet network for systems @{rednet.host|hosting} the
--[[- Search the local rednet network for systems [hosting][`rednet.host`] the
desired protocol and returns any computer IDs that respond as "registered"
against it.
@ -365,7 +364,7 @@ match is found).
@treturn[1] number... A list of computer IDs hosting the given protocol.
@treturn[2] number|nil The computer ID with the provided hostname and protocol,
or @{nil} if none exists.
or [`nil`] if none exists.
@since 1.6
@usage Find all computers which are hosting the `"chat"` protocol.
@ -450,7 +449,7 @@ end
local started = false
--- Listen for modem messages and converts them into rednet messages, which may
-- then be @{receive|received}.
-- then be [received][`receive`].
--
-- This is automatically started in the background on computer startup, and
-- should not be called manually.

View File

@ -2,13 +2,31 @@
--
-- SPDX-License-Identifier: LicenseRef-CCPL
--- Read and write configuration options for CraftOS and your programs.
--
-- By default, the settings API will load its configuration from the
-- `/.settings` file. One can then use @{settings.save} to update the file.
--
-- @module settings
-- @since 1.78
--[[- Read and write configuration options for CraftOS and your programs.
When a computer starts, it reads the current value of settings from the
`/.settings` file. These values then may be [read][`settings.get`] or
[modified][`settings.set`].
> [!WARNING]
> Calling [`settings.set`] does _not_ update the settings file by default. You
> _must_ call [`settings.save`] to persist values.
@module settings
@since 1.78
@usage Define an basic setting `123` and read its value.
settings.define("my.setting", {
description = "An example setting",
default = 123,
type = number,
})
print("my.setting = " .. settings.get("my.setting")) -- 123
You can then use the `set` program to change its value (e.g. `set my.setting 456`),
and then re-run the `example` program to check it has changed.
]]
local expect = dofile("rom/modules/main/cc/expect.lua")
local type, expect, field = type, expect.expect, expect.field
@ -40,9 +58,9 @@ for _, v in ipairs(valid_types) do valid_types[v] = true end
-- Options for this setting. This table accepts the following fields:
--
-- - `description`: A description which may be printed when running the `set` program.
-- - `default`: A default value, which is returned by @{settings.get} if the
-- - `default`: A default value, which is returned by [`settings.get`] if the
-- setting has not been changed.
-- - `type`: Require values to be of this type. @{set|Setting} the value to another type
-- - `type`: Require values to be of this type. [Setting][`set`] the value to another type
-- will error.
-- @since 1.87.0
function define(name, options)
@ -66,9 +84,9 @@ function define(name, options)
details[name] = options
end
--- Remove a @{define|definition} of a setting.
--- Remove a [definition][`define`] of a setting.
--
-- If a setting has been changed, this does not remove its value. Use @{settings.unset}
-- If a setting has been changed, this does not remove its value. Use [`settings.unset`]
-- for that.
--
-- @tparam string name The name of this option
@ -92,13 +110,18 @@ local function set_value(name, new)
end
end
--- Set the value of a setting.
--
-- @tparam string name The name of the setting to set
-- @param value The setting's value. This cannot be `nil`, and must be
-- serialisable by @{textutils.serialize}.
-- @throws If this value cannot be serialised
-- @see settings.unset
--[[- Set the value of a setting.
> [!WARNING]
> Calling [`settings.set`] does _not_ update the settings file by default. You
> _must_ call [`settings.save`] to persist values.
@tparam string name The name of the setting to set
@param value The setting's value. This cannot be `nil`, and must be
serialisable by [`textutils.serialize`].
@throws If this value cannot be serialised
@see settings.unset
]]
function set(name, value)
expect(1, name, "string")
expect(2, value, "number", "string", "boolean", "table")
@ -134,7 +157,7 @@ end
--
-- @tparam string name The name of the setting to get.
-- @treturn { description? = string, default? = any, type? = string, value? = any }
-- Information about this setting. This includes all information from @{settings.define},
-- Information about this setting. This includes all information from [`settings.define`],
-- as well as this setting's value.
-- @since 1.87.0
function getDetails(name)
@ -148,8 +171,8 @@ end
--- Remove the value of a setting, setting it to the default.
--
-- @{settings.get} will return the default value until the setting's value is
-- @{settings.set|set}, or the computer is rebooted.
-- [`settings.get`] will return the default value until the setting's value is
-- [set][`settings.set`], or the computer is rebooted.
--
-- @tparam string name The name of the setting to unset.
-- @see settings.set
@ -159,7 +182,7 @@ function unset(name)
set_value(name, nil)
end
--- Resets the value of all settings. Equivalent to calling @{settings.unset}
--- Resets the value of all settings. Equivalent to calling [`settings.unset`]
--- on every setting.
--
-- @see settings.unset

View File

@ -17,9 +17,9 @@ end
local term = _ENV
--- Redirects terminal output to a monitor, a @{window}, or any other custom
--- Redirects terminal output to a monitor, a [`window`], or any other custom
-- terminal object. Once the redirect is performed, any calls to a "term"
-- function - or to a function that makes use of a term function, as @{print} -
-- function - or to a function that makes use of a term function, as [`print`] -
-- will instead operate with the new terminal object.
--
-- A "terminal object" is simply a table that contains functions with the same
@ -29,9 +29,9 @@ local term = _ENV
-- The redirect can be undone by pointing back to the previous terminal object
-- (which this function returns whenever you switch).
--
-- @tparam Redirect target The terminal redirect the @{term} API will draw to.
-- @tparam Redirect target The terminal redirect the [`term`] API will draw to.
-- @treturn Redirect The previous redirect object, as returned by
-- @{term.current}.
-- [`term.current`].
-- @since 1.31
-- @usage
-- Redirect to a monitor on the right of the computer.
@ -60,7 +60,7 @@ end
-- @treturn Redirect The current terminal redirect
-- @since 1.6
-- @usage
-- Create a new @{window} which draws to the current redirect target.
-- Create a new [`window`] which draws to the current redirect target.
--
-- window.create(term.current(), 1, 1, 10, 10)
term.current = function()
@ -70,7 +70,7 @@ end
--- Get the native terminal object of the current computer.
--
-- It is recommended you do not use this function unless you absolutely have
-- to. In a multitasked environment, @{term.native} will _not_ be the current
-- to. In a multitasked environment, [`term.native`] will _not_ be the current
-- terminal object, and so drawing may interfere with other programs.
--
-- @treturn Redirect The native terminal redirect.

View File

@ -14,7 +14,7 @@ local wrap = dofile("rom/modules/main/cc/strings.lua").wrap
--- Slowly writes string text at current cursor position,
-- character-by-character.
--
-- Like @{_G.write}, this does not insert a newline at the end.
-- Like [`_G.write`], this does not insert a newline at the end.
--
-- @tparam string text The the text to write to the screen
-- @tparam[opt] number rate The number of characters to write each second,
@ -42,7 +42,7 @@ end
--- Slowly prints string text at current cursor position,
-- character-by-character.
--
-- Like @{print}, this inserts a newline after printing.
-- Like [`print`], this inserts a newline after printing.
--
-- @tparam string sText The the text to write to the screen
-- @tparam[opt] number nRate The number of characters to write each second,
@ -56,7 +56,7 @@ end
--- Takes input time and formats it in a more readable format such as `6:30 PM`.
--
-- @tparam number nTime The time to format, as provided by @{os.time}.
-- @tparam number nTime The time to format, as provided by [`os.time`].
-- @tparam[opt] boolean bTwentyFourHour Whether to format this as a 24-hour
-- clock (`18:30`) rather than a 12-hour one (`6:30 AM`)
-- @treturn string The formatted time
@ -114,7 +114,7 @@ end
--[[- Prints a given string to the display.
If the action can be completed without scrolling, it acts much the same as
@{print}; otherwise, it will throw up a "Press any key to continue" prompt at
[`print`]; otherwise, it will throw up a "Press any key to continue" prompt at
the bottom of the display. Each press will cause it to scroll down and write a
single line more before prompting again, if need be.
@ -253,7 +253,7 @@ end
--[[- Prints tables in a structured form, stopping and prompting for input should
the result not fit on the terminal.
This functions identically to @{textutils.tabulate}, but will prompt for user
This functions identically to [`textutils.tabulate`], but will prompt for user
input should the whole output not fit on the display.
@tparam {string...}|number ... The rows and text colors to display.
@ -424,12 +424,31 @@ do
if map[c] == nil then map[c] = hexify(c) end
end
serializeJSONString = function(s)
return ('"%s"'):format(s:gsub("[\0-\x1f\"\\]", map):gsub("[\x7f-\xff]", hexify))
serializeJSONString = function(s, options)
if options and options.unicode_strings and s:find("[\x80-\xff]") then
local retval = '"'
for _, code in utf8.codes(s) do
if code > 0xFFFF then
-- Encode the codepoint as a UTF-16 surrogate pair
code = code - 0x10000
local high, low = bit32.extract(code, 10, 10) + 0xD800, bit32.extract(code, 0, 10) + 0xDC00
retval = retval .. ("\\u%04X\\u%04X"):format(high, low)
elseif code <= 0x5C and map[string.char(code)] then -- 0x5C = `\`, don't run `string.char` if we don't need to
retval = retval .. map[string.char(code)]
elseif code < 0x20 or code >= 0x7F then
retval = retval .. ("\\u%04X"):format(code)
else
retval = retval .. string.char(code)
end
end
return retval .. '"'
else
return ('"%s"'):format(s:gsub("[\0-\x1f\"\\]", map):gsub("[\x7f-\xff]", hexify))
end
end
end
local function serializeJSONImpl(t, tTracking, bNBTStyle)
local function serializeJSONImpl(t, tTracking, options)
local sType = type(t)
if t == empty_json_array then return "[]"
elseif t == json_null then return "null"
@ -450,13 +469,14 @@ local function serializeJSONImpl(t, tTracking, bNBTStyle)
local nObjectSize = 0
local nArraySize = 0
local largestArrayIndex = 0
local bNBTStyle = options and options.nbt_style
for k, v in pairs(t) do
if type(k) == "string" then
local sEntry
if bNBTStyle then
sEntry = tostring(k) .. ":" .. serializeJSONImpl(v, tTracking, bNBTStyle)
sEntry = tostring(k) .. ":" .. serializeJSONImpl(v, tTracking, options)
else
sEntry = serializeJSONString(k) .. ":" .. serializeJSONImpl(v, tTracking, bNBTStyle)
sEntry = serializeJSONString(k, options) .. ":" .. serializeJSONImpl(v, tTracking, options)
end
if nObjectSize == 0 then
sObjectResult = sObjectResult .. sEntry
@ -473,7 +493,7 @@ local function serializeJSONImpl(t, tTracking, bNBTStyle)
if t[k] == nil then --if the array is nil at index k the value is "null" as to keep the unused indexes in between used ones.
sEntry = "null"
else -- if the array index does not point to a nil we serialise it's content.
sEntry = serializeJSONImpl(t[k], tTracking, bNBTStyle)
sEntry = serializeJSONImpl(t[k], tTracking, options)
end
if nArraySize == 0 then
sArrayResult = sArrayResult .. sEntry
@ -492,7 +512,7 @@ local function serializeJSONImpl(t, tTracking, bNBTStyle)
end
elseif sType == "string" then
return serializeJSONString(t)
return serializeJSONString(t, options)
elseif sType == "number" or sType == "boolean" then
return tostring(t)
@ -682,13 +702,13 @@ do
--[[- Converts a serialised JSON string back into a reassembled Lua object.
This may be used with @{textutils.serializeJSON}, or when communicating
This may be used with [`textutils.serializeJSON`], or when communicating
with command blocks or web APIs.
If a `null` value is encountered, it is converted into `nil`. It can be converted
into @{textutils.json_null} with the `parse_null` option.
into [`textutils.json_null`] with the `parse_null` option.
If an empty array is encountered, it is converted into @{textutils.empty_json_array}.
If an empty array is encountered, it is converted into [`textutils.empty_json_array`].
It can be converted into a new empty table with the `parse_empty_array` option.
@tparam string s The serialised string to deserialise.
@ -697,12 +717,12 @@ do
- `nbt_style`: When true, this will accept [stringified NBT][nbt] strings,
as produced by many commands.
- `parse_null`: When true, `null` will be parsed as @{json_null}, rather than
- `parse_null`: When true, `null` will be parsed as [`json_null`], rather than
`nil`.
- `parse_empty_array`: When false, empty arrays will be parsed as a new table.
By default (or when this value is true), they are parsed as @{empty_json_array}.
By default (or when this value is true), they are parsed as [`empty_json_array`].
[nbt]: https://minecraft.gamepedia.com/NBT_format
[nbt]: https://minecraft.wiki/w/NBT_format
@return[1] The deserialised object
@treturn[2] nil If the object could not be deserialised.
@treturn string A message describing why the JSON string is invalid.
@ -714,7 +734,7 @@ do
textutils.unserialiseJSON('{"name": "Steve", "age": null}')
@usage Unserialise a basic JSON object, returning null values as @{json_null}.
@usage Unserialise a basic JSON object, returning null values as [`json_null`].
textutils.unserialiseJSON('{"name": "Steve", "age": null}', { parse_null = true })
]]
@ -793,7 +813,7 @@ serialise = serialize -- GB version
--- Converts a serialised string back into a reassembled Lua object.
--
-- This is mainly used together with @{textutils.serialise}.
-- This is mainly used together with [`textutils.serialise`].
--
-- @tparam string s The serialised string to deserialise.
-- @return[1] The deserialised object
@ -813,32 +833,57 @@ end
unserialise = unserialize -- GB version
--- Returns a JSON representation of the given data.
--
-- This function attempts to guess whether a table is a JSON array or
-- object. However, empty tables are assumed to be empty objects - use
-- @{textutils.empty_json_array} to mark an empty array.
--
-- This is largely intended for interacting with various functions from the
-- @{commands} API, though may also be used in making @{http} requests.
--
-- @param t The value to serialise. Like @{textutils.serialise}, this should not
-- contain recursive tables or functions.
-- @tparam[opt] boolean bNBTStyle Whether to produce NBT-style JSON (non-quoted keys)
-- instead of standard JSON.
-- @treturn string The JSON representation of the input.
-- @throws If the object contains a value which cannot be
-- serialised. This includes functions and tables which appear multiple
-- times.
-- @usage textutils.serialiseJSON({ values = { 1, "2", true } })
-- @since 1.7
-- @see textutils.json_null Use to serialise a JSON `null` value.
-- @see textutils.empty_json_array Use to serialise a JSON empty array.
function serializeJSON(t, bNBTStyle)
--[[- Returns a JSON representation of the given data.
This function attempts to guess whether a table is a JSON array or
object. However, empty tables are assumed to be empty objects - use
[`textutils.empty_json_array`] to mark an empty array.
This is largely intended for interacting with various functions from the
[`commands`] API, though may also be used in making [`http`] requests.
@param[1] t The value to serialise. Like [`textutils.serialise`], this should not
contain recursive tables or functions.
@tparam[1,opt] { nbt_style? = boolean, unicode_strings? = boolean } options Options for serialisation.
- `nbt_style`: Whether to produce NBT-style JSON (non-quoted keys) instead of standard JSON.
- `unicode_strings`: Whether to treat strings as containing UTF-8 characters instead of
using the default 8-bit character set.
@param[2] t The value to serialise. Like [`textutils.serialise`], this should not
contain recursive tables or functions.
@tparam[2] boolean bNBTStyle Whether to produce NBT-style JSON (non-quoted keys)
instead of standard JSON.
@treturn string The JSON representation of the input.
@throws If the object contains a value which cannot be serialised. This includes
functions and tables which appear multiple times.
@usage Serialise a simple object
textutils.serialiseJSON({ values = { 1, "2", true } })
@usage Serialise an object to a NBT-style string
textutils.serialiseJSON({ values = { 1, "2", true } }, { nbt_style = true })
@since 1.7
@changed 1.106.0 Added `options` overload and `unicode_strings` option.
@see textutils.json_null Use to serialise a JSON `null` value.
@see textutils.empty_json_array Use to serialise a JSON empty array.
]]
function serializeJSON(t, options)
expect(1, t, "table", "string", "number", "boolean")
expect(2, bNBTStyle, "boolean", "nil")
expect(2, options, "table", "boolean", "nil")
if type(options) == "boolean" then
options = { nbt_style = options }
elseif type(options) == "table" then
field(options, "nbt_style", "boolean", "nil")
field(options, "unicode_strings", "boolean", "nil")
end
local tTracking = {}
return serializeJSONImpl(t, tTracking, bNBTStyle or false)
return serializeJSONImpl(t, tTracking, options)
end
serialiseJSON = serializeJSON -- GB version
@ -884,7 +929,7 @@ local tEmpty = {}
-- variable name or table index.
--
-- @tparam[opt] table tSearchTable The table to find variables in, defaulting to
-- the global environment (@{_G}). The function also searches the "parent"
-- the global environment ([`_G`]). The function also searches the "parent"
-- environment via the `__index` metatable field.
--
-- @treturn { string... } The (possibly empty) list of completions.

View File

@ -4,7 +4,7 @@
--- A basic 3D vector type and some common vector operations. This may be useful
-- when working with coordinates in Minecraft's world (such as those from the
-- @{gps} API).
-- [`gps`] API).
--
-- An introduction to vectors can be found on [Wikipedia][wiki].
--
@ -180,7 +180,7 @@ local vmetatable = {
__eq = vector.equals,
}
--- Construct a new @{Vector} with the given coordinates.
--- Construct a new [`Vector`] with the given coordinates.
--
-- @tparam number x The X coordinate or direction of the vector.
-- @tparam number y The Y coordinate or direction of the vector.

View File

@ -2,10 +2,10 @@
--
-- SPDX-License-Identifier: LicenseRef-CCPL
--[[- A @{term.Redirect|terminal redirect} occupying a smaller area of an
--[[- A [terminal redirect][`term.Redirect`] occupying a smaller area of an
existing terminal. This allows for easy definition of spaces within the display
that can be written/drawn to, then later redrawn/repositioned/etc as need
be. The API itself contains only one function, @{window.create}, which returns
be. The API itself contains only one function, [`window.create`], which returns
the windows themselves.
Windows are considered terminal objects - as such, they have access to nearly
@ -60,11 +60,11 @@ local string_sub = string.sub
--[[- Returns a terminal object that is a space within the specified parent
terminal object. This can then be used (or even redirected to) in the same
manner as eg a wrapped monitor. Refer to @{term|the term API} for a list of
manner as eg a wrapped monitor. Refer to [the term API][`term`] for a list of
functions available to it.
@{term} itself may not be passed as the parent, though @{term.native} is
acceptable. Generally, @{term.current} or a wrapped monitor will be most
[`term`] itself may not be passed as the parent, though [`term.native`] is
acceptable. Generally, [`term.current`] or a wrapped monitor will be most
suitable, though windows may even have other windows assigned as their
parents.
@ -131,11 +131,7 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
local sEmptyTextColor = tEmptyColorLines[nTextColor]
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
for y = 1, nHeight do
tLines[y] = {
text = sEmptyText,
textColor = sEmptyTextColor,
backgroundColor = sEmptyBackgroundColor,
}
tLines[y] = { sEmptyText, sEmptyTextColor, sEmptyBackgroundColor }
end
for i = 0, 15 do
@ -165,7 +161,7 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
local function redrawLine(n)
local tLine = tLines[n]
parent.setCursorPos(nX, nY + n - 1)
parent.blit(tLine.text, tLine.textColor, tLine.backgroundColor)
parent.blit(tLine[1], tLine[2], tLine[3])
end
local function redraw()
@ -188,9 +184,9 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
-- Modify line
local tLine = tLines[nCursorY]
if nStart == 1 and nEnd == nWidth then
tLine.text = sText
tLine.textColor = sTextColor
tLine.backgroundColor = sBackgroundColor
tLine[1] = sText
tLine[2] = sTextColor
tLine[3] = sBackgroundColor
else
local sClippedText, sClippedTextColor, sClippedBackgroundColor
if nStart < 1 then
@ -210,9 +206,9 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
sClippedBackgroundColor = sBackgroundColor
end
local sOldText = tLine.text
local sOldTextColor = tLine.textColor
local sOldBackgroundColor = tLine.backgroundColor
local sOldText = tLine[1]
local sOldTextColor = tLine[2]
local sOldBackgroundColor = tLine[3]
local sNewText, sNewTextColor, sNewBackgroundColor
if nStart > 1 then
local nOldEnd = nStart - 1
@ -231,9 +227,9 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
sNewBackgroundColor = sNewBackgroundColor .. string_sub(sOldBackgroundColor, nOldStart, nWidth)
end
tLine.text = sNewText
tLine.textColor = sNewTextColor
tLine.backgroundColor = sNewBackgroundColor
tLine[1] = sNewText
tLine[2] = sNewTextColor
tLine[3] = sNewBackgroundColor
end
-- Redraw line
@ -251,7 +247,7 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
end
end
--- The window object. Refer to the @{window|module's documentation} for
--- The window object. Refer to the [module's documentation][`window`] for
-- a full description.
--
-- @type Window
@ -280,11 +276,10 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
local sEmptyTextColor = tEmptyColorLines[nTextColor]
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
for y = 1, nHeight do
tLines[y] = {
text = sEmptyText,
textColor = sEmptyTextColor,
backgroundColor = sEmptyBackgroundColor,
}
local line = tLines[y]
line[1] = sEmptyText
line[2] = sEmptyTextColor
line[3] = sEmptyBackgroundColor
end
if bVisible then
redraw()
@ -295,14 +290,10 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
function window.clearLine()
if nCursorY >= 1 and nCursorY <= nHeight then
local sEmptyText = sEmptySpaceLine
local sEmptyTextColor = tEmptyColorLines[nTextColor]
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
tLines[nCursorY] = {
text = sEmptyText,
textColor = sEmptyTextColor,
backgroundColor = sEmptyBackgroundColor,
}
local line = tLines[nCursorY]
line[1] = sEmptySpaceLine
line[2] = tEmptyColorLines[nTextColor]
line[3] = tEmptyColorLines[nBackgroundColor]
if bVisible then
redrawLine(nCursorY)
updateCursorColor()
@ -431,11 +422,7 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
if y >= 1 and y <= nHeight then
tNewLines[newY] = tLines[y]
else
tNewLines[newY] = {
text = sEmptyText,
textColor = sEmptyTextColor,
backgroundColor = sEmptyBackgroundColor,
}
tNewLines[newY] = { sEmptyText, sEmptyTextColor, sEmptyBackgroundColor }
end
end
tLines = tNewLines
@ -467,8 +454,8 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
--
-- @tparam number y The y position of the line to get.
-- @treturn string The textual content of this line.
-- @treturn string The text colours of this line, suitable for use with @{term.blit}.
-- @treturn string The background colours of this line, suitable for use with @{term.blit}.
-- @treturn string The text colours of this line, suitable for use with [`term.blit`].
-- @treturn string The background colours of this line, suitable for use with [`term.blit`].
-- @throws If `y` is not between 1 and this window's height.
-- @since 1.84.0
function window.getLine(y)
@ -478,7 +465,8 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
error("Line is out of range.", 2)
end
return tLines[y].text, tLines[y].textColor, tLines[y].backgroundColor
local line = tLines[y]
return line[1], line[2], line[3]
end
-- Other functions
@ -574,26 +562,22 @@ function create(parent, nX, nY, nWidth, nHeight, bStartVisible)
local sEmptyBackgroundColor = tEmptyColorLines[nBackgroundColor]
for y = 1, new_height do
if y > nHeight then
tNewLines[y] = {
text = sEmptyText,
textColor = sEmptyTextColor,
backgroundColor = sEmptyBackgroundColor,
}
tNewLines[y] = { sEmptyText, sEmptyTextColor, sEmptyBackgroundColor }
else
local tOldLine = tLines[y]
if new_width == nWidth then
tNewLines[y] = tOldLine
elseif new_width < nWidth then
tNewLines[y] = {
text = string_sub(tOldLine.text, 1, new_width),
textColor = string_sub(tOldLine.textColor, 1, new_width),
backgroundColor = string_sub(tOldLine.backgroundColor, 1, new_width),
string_sub(tOldLine[1], 1, new_width),
string_sub(tOldLine[2], 1, new_width),
string_sub(tOldLine[3], 1, new_width),
}
else
tNewLines[y] = {
text = tOldLine.text .. string_sub(sEmptyText, nWidth + 1, new_width),
textColor = tOldLine.textColor .. string_sub(sEmptyTextColor, nWidth + 1, new_width),
backgroundColor = tOldLine.backgroundColor .. string_sub(sEmptyBackgroundColor, nWidth + 1, new_width),
tOldLine[1] .. string_sub(sEmptyText, nWidth + 1, new_width),
tOldLine[2] .. string_sub(sEmptyTextColor, nWidth + 1, new_width),
tOldLine[3] .. string_sub(sEmptyBackgroundColor, nWidth + 1, new_width),
}
end
end

View File

@ -1,3 +1,62 @@
# New features in CC: Tweaked 1.108.1
Several bug fixes:
* Prevent no-opped players breaking or placing command computers.
* Allow using `@LuaFunction`-annotated methods on classes defined in child classloaders.
# New features in CC: Tweaked 1.108.0
* Remove compression from terminal/monitor packets. Vanilla applies its own compression, so this ends up being less helpful than expected.
* `/computercraft` command now supports permission mods.
* Split some GUI textures into sprite sheets.
* Support the `%g` character class in string pattern matching.
Several bug fixes:
* Fix crash when playing some modded records via a disk drive.
* Fix race condition when computers attach or detach from a monitor.
* Fix the "max websocket message" config option not being read.
* `tostring` now correctly obeys `__name`.
* Fix several inconsistencies with pattern matching character classes.
# New features in CC: Tweaked 1.107.0
* Add `disabled_generic_methods` config option to disable generic methods.
* Add basic integration with EMI.
* Enchanted turtle tools now render with a glint.
* Update several translations (PatriikPlays, 1Turtle, Ale32bit).
Several bug fixes:
* Fix client config file being generated on a dedicated server.
* Fix numbers ending in "f" or "d" being treated as avalid.
* Fix `string.pack`'s "z" specifier causing out-of-bounds errors.
* Fix several issues with `turtle.dig`'s custom actions (tilling, making paths).
# New features in CC: Tweaked 1.106.1
Several bug fixes:
* Block the CGNAT range (100.64.0.0/10) by default.
* Fix conflicts with other mods replacing reach distance.
# New features in CC: Tweaked 1.106.0
* Numerous documentation improvements (MCJack123, znepb, penguinencounter).
* Port `fs.find` to Lua. This also allows using `?` as a wildcard.
* Computers cursors now glow in the dark.
* Allow changing turtle upgrades from the GUI.
* Add option to serialize Unicode strings to JSON (MCJack123).
* Small optimisations to the `window` API.
* Turtle upgrades can now preserve NBT from upgrade item stack and when broken.
* Add support for tool enchantments and durability via datapacks. This is disabled for the built-in tools.
Several bug fixes:
* Fix turtles rendering incorrectly when upside down.
* Fix misplaced calls to IArguments.escapes.
* Lua REPL no longer accepts `)(` as a valid expression.
* Fix several inconsistencies with `require`/`package.path` in the Lua REPL (Wojbie).
* Fix turtle being able to place water buckets outside its reach distance.
* Fix private several IP address ranges not being blocked by the `$private` rule.
* Improve permission checks in the `/computercraft` command.
# New features in CC: Tweaked 1.105.0
* Optimise JSON string parsing.

View File

@ -12,4 +12,4 @@ commands.give( "dan200", "minecraft:diamond", 64 )
This works with any command. Use "commands.async" instead of "commands" to execute asynchronously.
The commands API is only available on Command Computers.
Visit http://minecraft.gamepedia.com/Commands for documentation on all commands.
Visit https://minecraft.wiki/w/Commands for documentation on all commands.

View File

@ -6,4 +6,4 @@ if sEvent == "key" and nKey == keys.enter then
-- Do something
end
See http://www.minecraftwiki.net/wiki/Key_codes, or the source code, for a complete reference.
See https://www.minecraft.wiki/w/Key_codes, or the source code, for a complete reference.

View File

@ -1,25 +1,7 @@
New features in CC: Tweaked 1.105.0
* Optimise JSON string parsing.
* Add `colors.fromBlit` (Erb3).
* Upload file size limit is now configurable (khankul).
* Wired cables no longer have a distance limit.
* Java methods now coerce values to strings consistently with Lua.
* Add custom timeout support to the HTTP API.
* Support custom proxies for HTTP requests (Lemmmy).
* The `speaker` program now errors when playing HTML files.
* `edit` now shows an error message when editing read-only files.
* Update Ukranian translation (SirEdvin).
New features in CC: Tweaked 1.108.1
Several bug fixes:
* Allow GPS hosts to only be 1 block apart.
* Fix "Turn On"/"Turn Off" buttons being inverted in the computer GUI (Erb3).
* Fix arrow keys not working in the printout UI.
* Several documentation fixes (zyxkad, Lupus590, Commandcracker).
* Fix monitor renderer debug text always being visible on Forge.
* Fix crash when another mod changes the LoggerContext.
* Fix the `monitor_renderer` option not being present in Fabric config files.
* Pasting on MacOS/OSX now uses Cmd+V rather than Ctrl+V.
* Fix turtles placing blocks upside down when at y<0.
* Prevent no-opped players breaking or placing command computers.
* Allow using `@LuaFunction`-annotated methods on classes defined in child classloaders.
Type "help changelog" to see the full version history.

View File

@ -9,11 +9,11 @@ DFPWM (Dynamic Filter Pulse Width Modulation) is an audio codec designed by Grea
format compared to raw PCM data, only using 1 bit per sample, but is simple enough to simple enough to encode and decode
in real time.
Typically DFPWM audio is read from @{fs.BinaryReadHandle|the filesystem} or a @{http.Response|a web request} as a
string, and converted a format suitable for @{speaker.playAudio}.
Typically DFPWM audio is read from [the filesystem][`fs.BinaryReadHandle`] or a [a web request][`http.Response`] as a
string, and converted a format suitable for [`speaker.playAudio`].
## Encoding and decoding files
This modules exposes two key functions, @{make_decoder} and @{make_encoder}, which construct a new decoder or encoder.
This modules exposes two key functions, [`make_decoder`] and [`make_encoder`], which construct a new decoder or encoder.
The returned encoder/decoder is itself a function, which converts between the two kinds of data.
These encoders and decoders have lots of hidden state, so you should be careful to use the same encoder or decoder for
@ -95,10 +95,9 @@ end
The returned encoder is itself a function. This function accepts a table of amplitude data between -128 and 127 and
returns the encoded DFPWM data.
:::caution Reusing encoders
Encoders have lots of internal state which tracks the state of the current stream. If you reuse an encoder for multiple
streams, or use different encoders for the same stream, the resulting audio may not sound correct.
:::
> [Reusing encoders][!WARNING]
> Encoders have lots of internal state which tracks the state of the current stream. If you reuse an encoder for multiple
> streams, or use different encoders for the same stream, the resulting audio may not sound correct.
@treturn function(pcm: { number... }):string The encoder function
@see encode A helper function for encoding an entire file of audio at once.
@ -138,10 +137,9 @@ end
The returned decoder is itself a function. This function accepts a string and returns a table of amplitudes, each value
between -128 and 127.
:::caution Reusing decoders
Decoders have lots of internal state which tracks the state of the current stream. If you reuse an decoder for multiple
streams, or use different decoders for the same stream, the resulting audio may not sound correct.
:::
> [Reusing decoders][!WARNING]
> Decoders have lots of internal state which tracks the state of the current stream. If you reuse an decoder for
> multiple streams, or use different decoders for the same stream, the resulting audio may not sound correct.
@treturn function(dfpwm: string):{ number... } The encoder function
@see decode A helper function for decoding an entire file of audio at once.
@ -167,7 +165,7 @@ local function make_decoder()
local low_pass_charge = 0
local previous_charge, previous_bit = 0, false
return function (input, output)
return function (input)
expect(1, input, "string")
local output, output_n = {}, 0
@ -200,7 +198,7 @@ end
--[[- A convenience function for decoding a complete file of audio at once.
This should only be used for short files. For larger files, one should read the file in chunks and process it using
@{make_decoder}.
[`make_decoder`].
@tparam string input The DFPWM data to convert.
@treturn { number... } The produced amplitude data.
@ -214,7 +212,7 @@ end
--[[- A convenience function for encoding a complete file of audio at once.
This should only be used for complete pieces of audio. If you are writing writing multiple chunks to the same place,
you should use an encoder returned by @{make_encoder} instead.
you should use an encoder returned by [`make_encoder`] instead.
@tparam { number... } input The table of amplitude data.
@treturn string The encoded DFPWM data.

View File

@ -3,11 +3,11 @@
-- SPDX-License-Identifier: LicenseRef-CCPL
--- A collection of helper methods for working with input completion, such
-- as that require by @{_G.read}.
-- as that require by [`_G.read`].
--
-- @module cc.completion
-- @see cc.shell.completion For additional helpers to use with
-- @{shell.setCompletionFunction}.
-- [`shell.setCompletionFunction`].
-- @since 1.85.0
local expect = require "cc.expect".expect
@ -34,7 +34,7 @@ end
-- @tparam { string... } choices The list of choices to complete from.
-- @tparam[opt] boolean add_space Whether to add a space after the completed item.
-- @treturn { string... } A list of suffixes of matching strings.
-- @usage Call @{_G.read}, completing the names of various animals.
-- @usage Call [`_G.read`], completing the names of various animals.
--
-- local completion = require "cc.completion"
-- local animals = { "dog", "cat", "lion", "unicorn" }
@ -76,7 +76,7 @@ local function side(text, add_space)
return choice_impl(text, sides, add_space)
end
--- Complete a @{settings|setting}.
--- Complete a [setting][`settings`].
--
-- @tparam string text The input string to complete.
-- @tparam[opt] boolean add_space Whether to add a space after the completed settings.
@ -92,7 +92,7 @@ end
local command_list
--- Complete the name of a Minecraft @{commands|command}.
--- Complete the name of a Minecraft [command][`commands`].
--
-- @tparam string text The input string to complete.
-- @tparam[opt] boolean add_space Whether to add a space after the completed command.

View File

@ -2,7 +2,7 @@
--
-- SPDX-License-Identifier: MPL-2.0
--[[- The @{cc.expect} library provides helper functions for verifying that
--[[- The [`cc.expect`] library provides helper functions for verifying that
function arguments are well-formed and of the correct type.
@module cc.expect

View File

@ -2,10 +2,10 @@
--
-- SPDX-License-Identifier: MPL-2.0
--- Read and draw nbt ("Nitrogen Fingers Text") images.
--- Read and draw nft ("Nitrogen Fingers Text") images.
--
-- nft ("Nitrogen Fingers Text") is a file format for drawing basic images.
-- Unlike the images that @{paintutils.parseImage} uses, nft supports coloured
-- Unlike the images that [`paintutils.parseImage`] uses, nft supports coloured
-- text as well as simple coloured pixels.
--
-- @module cc.image.nft
@ -87,7 +87,7 @@ end
--- Draw an nft image to the screen.
--
-- @tparam table image An image, as returned from @{load} or @{draw}.
-- @tparam table image An image, as returned from [`load`] or [`parse`].
-- @tparam number xPos The x position to start drawing at.
-- @tparam number xPos The y position to start drawing at.
-- @tparam[opt] term.Redirect target The terminal redirect to draw to. Defaults to the

View File

@ -4,10 +4,9 @@
--[[- A pretty-printer for Lua errors.
:::warning
This is an internal module and SHOULD NOT be used in your own code. It may
be removed or changed at any time.
:::
> [!DANGER]
> This is an internal module and SHOULD NOT be used in your own code. It may
> be removed or changed at any time.
This consumes a list of messages and "annotations" and displays the error to the
terminal.
@ -100,7 +99,7 @@ local code_accent = pretty.text("\x95", colours.cyan)
over the underlying source, exposing the following functions:
- `get_pos`: Get the line and column of an opaque position.
- `get_line`: Get the source code for an opaque position.
@tparam table message The message to display, as produced by @{cc.internal.syntax.errors}.
@tparam table message The message to display, as produced by [`cc.internal.syntax.errors`].
]]
return function(context, message)
expect(1, context, "table")

View File

@ -4,10 +4,9 @@
--[[- Internal tools for working with errors.
:::warning
This is an internal module and SHOULD NOT be used in your own code. It may
be removed or changed at any time.
:::
> [!DANGER]
> This is an internal module and SHOULD NOT be used in your own code. It may
> be removed or changed at any time.
@local
]]

View File

@ -2,12 +2,11 @@
--
-- SPDX-License-Identifier: MPL-2.0
--[[- Upload a list of files, as received by the @{event!file_transfer} event.
--[[- Upload a list of files, as received by the [`event!file_transfer`] event.
:::warning
This is an internal module and SHOULD NOT be used in your own code. It may
be removed or changed at any time.
:::
> [!DANGER]
> This is an internal module and SHOULD NOT be used in your own code. It may
> be removed or changed at any time.
@local
]]

View File

@ -4,14 +4,13 @@
--[[- The error messages reported by our lexer and parser.
:::warning
This is an internal module and SHOULD NOT be used in your own code. It may
be removed or changed at any time.
:::
> [!DANGER]
> This is an internal module and SHOULD NOT be used in your own code. It may
> be removed or changed at any time.
This provides a list of factory methods which take source positions and produce
appropriate error messages targeting that location. These error messages can
then be displayed to the user via @{cc.internal.error_printer}.
then be displayed to the user via [`cc.internal.error_printer`].
@local
]]
@ -121,7 +120,7 @@ function errors.unfinished_string(start_pos, end_pos, quote)
end
--[[- A string which ends with an escape sequence (so a literal `"foo\`). This
is slightly different from @{unfinished_string}, as we don't want to suggest
is slightly different from [`unfinished_string`], as we don't want to suggest
adding a quote.
@tparam number start_pos The start position of the string.
@ -467,7 +466,7 @@ function errors.standalone_name(pos)
}
end
--[[- A statement of the form `x.y`. This is similar to @{standalone_name}, but
--[[- A statement of the form `x.y`. This is similar to [`standalone_name`], but
when the next token is on another line.
@tparam number pos The position right after this name.

View File

@ -4,10 +4,9 @@
--[[- The main entrypoint to our Lua parser
:::warning
This is an internal module and SHOULD NOT be used in your own code. It may
be removed or changed at any time.
:::
> [!DANGER]
> This is an internal module and SHOULD NOT be used in your own code. It may
> be removed or changed at any time.
@local
]]
@ -21,6 +20,8 @@ local error_printer = require "cc.internal.error_printer"
local error_sentinel = {}
local function make_context(input)
expect(1, input, "string")
local context = {}
local lines = { 1 }
@ -73,8 +74,9 @@ local function parse(input, start_symbol)
expect(2, start_symbol, "number")
local context = make_context(input)
function context.report(msg)
expect(1, msg, "table")
function context.report(msg, ...)
expect(1, msg, "table", "function")
if type(msg) == "function" then msg = msg(...) end
error_printer(context, msg)
error(error_sentinel)
end
@ -110,8 +112,9 @@ local function parse_repl(input)
local context = make_context(input)
local last_error = nil
function context.report(msg)
expect(1, msg, "table")
function context.report(msg, ...)
expect(1, msg, "table", "function")
if type(msg) == "function" then msg = msg(...) end
last_error = msg
error(error_sentinel)
end

View File

@ -4,17 +4,16 @@
--[[- A lexer for Lua source code.
:::warning
This is an internal module and SHOULD NOT be used in your own code. It may
be removed or changed at any time.
:::
> [!DANGER]
> This is an internal module and SHOULD NOT be used in your own code. It may
> be removed or changed at any time.
This module provides utilities for lexing Lua code, returning tokens compatible
with @{cc.internal.syntax.parser}. While all lexers are roughly the same, there
with [`cc.internal.syntax.parser`]. While all lexers are roughly the same, there
are some design choices worth drawing attention to:
- The lexer uses Lua patterns (i.e. @{string.find}) as much as possible,
trying to avoid @{string.sub} loops except when needed. This allows us to
- The lexer uses Lua patterns (i.e. [`string.find`]) as much as possible,
trying to avoid [`string.sub`] loops except when needed. This allows us to
move string processing to native code, which ends up being much faster.
- We try to avoid allocating where possible. There are some cases we need to
@ -96,7 +95,7 @@ local function lex_number(context, str, start)
local contents = sub(str, start, pos - 1)
if not tonumber(contents) then
-- TODO: Separate error for "2..3"?
context.report(errors.malformed_number(start, pos - 1))
context.report(errors.malformed_number, start, pos - 1)
end
return tokens.NUMBER, pos - 1
@ -118,14 +117,14 @@ local function lex_string(context, str, start_pos, quote)
return tokens.STRING, pos
elseif c == "\n" or c == "\r" or c == "" then
-- We don't call newline here, as that's done for the next token.
context.report(errors.unfinished_string(start_pos, pos, quote))
context.report(errors.unfinished_string, start_pos, pos, quote)
return tokens.STRING, pos - 1
elseif c == "\\" then
c = sub(str, pos + 1, pos + 1)
if c == "\n" or c == "\r" then
pos = newline(context, str, pos + 1, c)
elseif c == "" then
context.report(errors.unfinished_string_escape(start_pos, pos, quote))
context.report(errors.unfinished_string_escape, start_pos, pos, quote)
return tokens.STRING, pos
elseif c == "z" then
pos = pos + 2
@ -133,7 +132,7 @@ local function lex_string(context, str, start_pos, quote)
local next_pos, _, c = find(str, "([%S\r\n])", pos)
if not next_pos then
context.report(errors.unfinished_string(start_pos, #str, quote))
context.report(errors.unfinished_string, start_pos, #str, quote)
return tokens.STRING, #str
end
@ -178,7 +177,7 @@ end
-- @tparam number start The start position, after the input boundary.
-- @tparam number len The expected length of the boundary. Equal to 1 + the
-- number of `=`.
-- @treturn number|nil The end position, or @{nil} if this is not terminated.
-- @treturn number|nil The end position, or [`nil`] if this is not terminated.
local function lex_long_str(context, str, start, len)
local pos = start
while true do
@ -196,7 +195,7 @@ local function lex_long_str(context, str, start, len)
elseif c == "[" then
local ok, boundary_pos = lex_long_str_boundary(str, pos + 1, "[")
if ok and boundary_pos - pos == len and len == 1 then
context.report(errors.nested_long_str(pos, boundary_pos))
context.report(errors.nested_long_str, pos, boundary_pos)
end
pos = boundary_pos
@ -238,12 +237,12 @@ local function lex_token(context, str, pos)
local end_pos = lex_long_str(context, str, boundary_pos + 1, boundary_pos - pos)
if end_pos then return tokens.STRING, end_pos end
context.report(errors.unfinished_long_string(pos, boundary_pos, boundary_pos - pos))
context.report(errors.unfinished_long_string, pos, boundary_pos, boundary_pos - pos)
return tokens.ERROR, #str
elseif pos + 1 == boundary_pos then -- Just a "["
return tokens.OSQUARE, pos
else -- Malformed long string, for instance "[="
context.report(errors.malformed_long_string(pos, boundary_pos, boundary_pos - pos))
context.report(errors.malformed_long_string, pos, boundary_pos, boundary_pos - pos)
return tokens.ERROR, boundary_pos
end
@ -260,7 +259,7 @@ local function lex_token(context, str, pos)
local end_pos = lex_long_str(context, str, boundary_pos + 1, boundary_pos - comment_pos)
if end_pos then return tokens.COMMENT, end_pos end
context.report(errors.unfinished_long_comment(pos, boundary_pos, boundary_pos - comment_pos))
context.report(errors.unfinished_long_comment, pos, boundary_pos, boundary_pos - comment_pos)
return tokens.ERROR, #str
end
end
@ -317,18 +316,18 @@ local function lex_token(context, str, pos)
if end_pos - pos <= 3 then
local contents = sub(str, pos, end_pos)
if contents == "&&" then
context.report(errors.wrong_and(pos, end_pos))
context.report(errors.wrong_and, pos, end_pos)
return tokens.AND, end_pos
elseif contents == "||" then
context.report(errors.wrong_or(pos, end_pos))
context.report(errors.wrong_or, pos, end_pos)
return tokens.OR, end_pos
elseif contents == "!=" or contents == "<>" then
context.report(errors.wrong_ne(pos, end_pos))
context.report(errors.wrong_ne, pos, end_pos)
return tokens.NE, end_pos
end
end
context.report(errors.unexpected_character(pos))
context.report(errors.unexpected_character, pos)
return tokens.ERROR, end_pos
end
end

View File

@ -4,10 +4,9 @@
--[[- A parser for Lua programs and expressions.
:::warning
This is an internal module and SHOULD NOT be used in your own code. It may
be removed or changed at any time.
:::
> [!DANGER]
> This is an internal module and SHOULD NOT be used in your own code. It may
> be removed or changed at any time.
Most of the code in this module is automatically generated from the Lua grammar,
hence being mostly unreadable!
@ -244,7 +243,7 @@ local function handle_error(context, stack, stack_n, token, token_start, token_e
end
end
context.report(errors.unexpected_token(token, token_start, token_end))
context.report(errors.unexpected_token, token, token_start, token_end)
return false
end

View File

@ -5,12 +5,12 @@
--[[- A pretty printer for rendering data structures in an aesthetically
pleasing manner.
In order to display something using @{cc.pretty}, you build up a series of
@{Doc|documents}. These behave a little bit like strings; you can concatenate
In order to display something using [`cc.pretty`], you build up a series of
[documents][`Doc`]. These behave a little bit like strings; you can concatenate
them together and then print them to the screen.
However, documents also allow you to control how they should be printed. There
are several functions (such as @{nest} and @{group}) which allow you to control
are several functions (such as [`nest`] and [`group`]) which allow you to control
the "layout" of the document. When you come to display the document, the 'best'
(most compact) layout is used.
@ -37,7 +37,7 @@ local expect, field = expect.expect, expect.field
local type, getmetatable, setmetatable, colours, str_write, tostring = type, getmetatable, setmetatable, colours, write, tostring
local debug_info, debug_local = debug.getinfo, debug.getlocal
--- @{table.insert} alternative, but with the length stored inline.
--- [`table.insert`] alternative, but with the length stored inline.
local function append(out, value)
local n = out.n + 1
out[n], out.n = value, n
@ -59,10 +59,10 @@ local empty = mk_doc({ tag = "nil" })
--- A document with a single space in it.
local space = mk_doc({ tag = "text", text = " " })
--- A line break. When collapsed with @{group}, this will be replaced with @{empty}.
--- A line break. When collapsed with [`group`], this will be replaced with [`empty`].
local line = mk_doc({ tag = "line", flat = empty })
--- A line break. When collapsed with @{group}, this will be replaced with @{space}.
--- A line break. When collapsed with [`group`], this will be replaced with [`space`].
local space_line = mk_doc({ tag = "line", flat = space })
local text_cache = { [""] = empty, [" "] = space, ["\n"] = space_line }
@ -73,7 +73,7 @@ end
--- Create a new document from a string.
--
-- If your string contains multiple lines, @{group} will flatten the string
-- If your string contains multiple lines, [`group`] will flatten the string
-- into a single line, with spaces between each line.
--
-- @tparam string text The string to construct a new document with.
@ -453,7 +453,7 @@ end
--- Pretty-print an arbitrary object, converting it into a document.
--
-- This can then be rendered with @{write} or @{print}.
-- This can then be rendered with [`write`] or [`print`].
--
-- @param obj The object to pretty-print.
-- @tparam[opt] { function_args = boolean, function_source = boolean } options
@ -479,7 +479,7 @@ local function pretty(obj, options)
return pretty_impl(obj, actual_options, {})
end
--[[- A shortcut for calling @{pretty} and @{print} together.
--[[- A shortcut for calling [`pretty`] and [`print`] together.
@param obj The object to pretty-print.
@tparam[opt] { function_args = boolean, function_source = boolean } options

View File

@ -2,8 +2,8 @@
--
-- SPDX-License-Identifier: LicenseRef-CCPL
--[[- A pure Lua implementation of the builtin @{require} function and
@{package} library.
--[[- A pure Lua implementation of the builtin [`require`] function and
[`package`] library.
Generally you do not need to use this module - it is injected into the every
program's environment. However, it may be useful when building a custom shell or
@ -11,7 +11,7 @@ when running programs yourself.
@module cc.require
@since 1.88.0
@see using_require For an introduction on how to use @{require}.
@see using_require For an introduction on how to use [`require`].
@usage Construct the package and require function, and insert them into a
custom environment.
@ -110,13 +110,13 @@ local function make_require(package)
end
end
--- Build an implementation of Lua's @{package} library, and a @{require}
--- Build an implementation of Lua's [`package`] library, and a [`require`]
-- function to load modules within it.
--
-- @tparam table env The environment to load packages into.
-- @tparam string dir The directory that relative packages are loaded from.
-- @treturn function The new @{require} function.
-- @treturn table The new @{package} library.
-- @treturn function The new [`require`] function.
-- @treturn table The new [`package`] library.
local function make_package(env, dir)
expect(1, env, "table")
expect(2, dir, "string")

View File

@ -4,16 +4,16 @@
--[[- A collection of helper methods for working with shell completion.
Most programs may be completed using the @{build} helper method, rather than
Most programs may be completed using the [`build`] helper method, rather than
manually switching on the argument index.
Note, the helper functions within this module do not accept an argument index,
and so are not directly usable with the @{shell.setCompletionFunction}. Instead,
wrap them using @{build}, or your own custom function.
and so are not directly usable with the [`shell.setCompletionFunction`]. Instead,
wrap them using [`build`], or your own custom function.
@module cc.shell.completion
@since 1.85.0
@see cc.completion For more general helpers, suitable for use with @{_G.read}.
@see cc.completion For more general helpers, suitable for use with [`_G.read`].
@see shell.setCompletionFunction
@usage Register a completion handler for example.lua which prompts for a
@ -135,15 +135,15 @@ end
--[[- A helper function for building shell completion arguments.
This accepts a series of single-argument completion functions, and combines
them into a function suitable for use with @{shell.setCompletionFunction}.
them into a function suitable for use with [`shell.setCompletionFunction`].
@tparam nil|table|function ... Every argument to @{build} represents an argument
@tparam nil|table|function ... Every argument to [`build`] represents an argument
to the program you wish to complete. Each argument can be one of three types:
- `nil`: This argument will not be completed.
- A function: This argument will be completed with the given function. It is
called with the @{shell} object, the string to complete and the arguments
called with the [`shell`] object, the string to complete and the arguments
before this one.
- A table: This acts as a more powerful version of the function case. The table
@ -197,12 +197,12 @@ return {
programWithArgs = programWithArgs,
-- Re-export various other functions
help = wrap(help.completeTopic), --- Wraps @{help.completeTopic} as a @{build} compatible function.
choice = wrap(completion.choice), --- Wraps @{cc.completion.choice} as a @{build} compatible function.
peripheral = wrap(completion.peripheral), --- Wraps @{cc.completion.peripheral} as a @{build} compatible function.
side = wrap(completion.side), --- Wraps @{cc.completion.side} as a @{build} compatible function.
setting = wrap(completion.setting), --- Wraps @{cc.completion.setting} as a @{build} compatible function.
command = wrap(completion.command), --- Wraps @{cc.completion.command} as a @{build} compatible function.
help = wrap(help.completeTopic), --- Wraps [`help.completeTopic`] as a [`build`] compatible function.
choice = wrap(completion.choice), --- Wraps [`cc.completion.choice`] as a [`build`] compatible function.
peripheral = wrap(completion.peripheral), --- Wraps [`cc.completion.peripheral`] as a [`build`] compatible function.
side = wrap(completion.side), --- Wraps [`cc.completion.side`] as a [`build`] compatible function.
setting = wrap(completion.setting), --- Wraps [`cc.completion.setting`] as a [`build`] compatible function.
command = wrap(completion.command), --- Wraps [`cc.completion.command`] as a [`build`] compatible function.
build = build,
}

View File

@ -13,7 +13,7 @@ local expect = (require and require("cc.expect") or dofile("rom/modules/main/cc/
--[[- Wraps a block of text, so that each line fits within the given width.
This may be useful if you want to wrap text before displaying it to a
@{monitor} or @{printer} without using @{_G.print|print}.
[`monitor`] or [`printer`] without using [print][`_G.print`].
@tparam string text The string to wrap.
@tparam[opt] number width The width to constrain to, defaults to the width of

View File

@ -6,17 +6,17 @@
--
-- When multiple programs are running, it displays a tab bar at the top of the
-- screen, which allows you to switch between programs. New programs can be
-- launched using the `fg` or `bg` programs, or using the @{shell.openTab} and
-- @{multishell.launch} functions.
-- launched using the `fg` or `bg` programs, or using the [`shell.openTab`] and
-- [`multishell.launch`] functions.
--
-- Each process is identified by its ID, which corresponds to its position in
-- the tab list. As tabs may be opened and closed, this ID is _not_ constant
-- over a program's run. As such, be careful not to use stale IDs.
--
-- As with @{shell}, @{multishell} is not a "true" API. Instead, it is a
-- As with [`shell`], [`multishell`] is not a "true" API. Instead, it is a
-- standard program, which launches a shell and injects its API into the shell's
-- environment. This API is not available in the global environment, and so is
-- not available to @{os.loadAPI|APIs}.
-- not available to [APIs][`os.loadAPI`].
--
-- @module[module] multishell
-- @since 1.6
@ -222,7 +222,7 @@ local multishell = {} --- @export
--- Get the currently visible process. This will be the one selected on
-- the tab bar.
--
-- Note, this is different to @{getCurrent}, which returns the process which is
-- Note, this is different to [`getCurrent`], which returns the process which is
-- currently executing.
--
-- @treturn number The currently visible process's index.
@ -235,7 +235,7 @@ end
--
-- @tparam number n The process index to switch to.
-- @treturn boolean If the process was changed successfully. This will
-- return @{false} if there is no process with this id.
-- return [`false`] if there is no process with this id.
-- @see getFocus
function multishell.setFocus(n)
expect(1, n, "number")
@ -250,9 +250,9 @@ end
--- Get the title of the given tab.
--
-- This starts as the name of the program, but may be changed using
-- @{multishell.setTitle}.
-- [`multishell.setTitle`].
-- @tparam number n The process index.
-- @treturn string|nil The current process title, or @{nil} if the
-- @treturn string|nil The current process title, or [`nil`] if the
-- process doesn't exist.
function multishell.getTitle(n)
expect(1, n, "number")

View File

@ -275,6 +275,12 @@ local function drawCanvas()
end
end
local function termResize()
w, h = term.getSize()
drawCanvas()
drawInterface()
end
local menu_choices = {
Save = function()
if bReadOnly then
@ -376,6 +382,8 @@ local function accessMenu()
nMenuPosEnd = nMenuPosEnd + 1
nMenuPosStart = nMenuPosEnd
end
elseif id == "term_resize" then
termResize()
end
end
end
@ -434,9 +442,7 @@ local function handleEvents()
drawInterface()
end
elseif id == "term_resize" then
w, h = term.getSize()
drawCanvas()
drawInterface()
termResize()
end
end
end

View File

@ -56,14 +56,6 @@ local function get(url)
)
if response then
-- If spam protection is activated, we get redirected to /paste with Content-Type: text/html
local headers = response.getResponseHeaders()
if not headers["Content-Type"] or not headers["Content-Type"]:find("^text/plain") then
io.stderr:write("Failed.\n")
print("Pastebin blocked the download due to spam protection. Please complete the captcha in a web browser: https://pastebin.com/" .. textutils.urlEncode(paste))
return
end
print("Success.")
local sResponse = response.readAll()

View File

@ -36,13 +36,6 @@ local function getFilename(sUrl)
end
local function get(sUrl)
-- Check if the URL is valid
local ok, err = http.checkURL(url)
if not ok then
printError(err or "Invalid URL.")
return
end
write("Connecting to " .. sUrl .. "... ")
local response = http.get(sUrl , nil , true)

View File

@ -25,21 +25,13 @@ local tEnv = {
}
setmetatable(tEnv, { __index = _ENV })
-- Replace our package.path, so that it loads from the current directory, rather
-- than from /rom/programs. This makes it a little more friendly to use and
-- closer to what you'd expect.
-- Replace our require with new instance that loads from the current directory
-- rather than from /rom/programs. This makes it more friendly to use and closer
-- to what you'd expect.
do
local make_package = require "cc.require".make
local dir = shell.dir()
if dir:sub(1, 1) ~= "/" then dir = "/" .. dir end
if dir:sub(-1) ~= "/" then dir = dir .. "/" end
local strip_path = "?;?.lua;?/init.lua;"
local path = package.path
if path:sub(1, #strip_path) == strip_path then
path = path:sub(#strip_path + 1)
end
package.path = dir .. "?;" .. dir .. "?.lua;" .. dir .. "?/init.lua;" .. path
_ENV.require, _ENV.package = make_package(_ENV, dir)
end
if term.isColour() then
@ -82,18 +74,13 @@ while running do
local name, offset = "=lua[" .. chunk_idx .. "]", 0
local force_print = 0
local func, err = load(input, name, "t", tEnv)
local expr_func = load("return _echo(" .. input .. ");", name, "t", tEnv)
if not func then
if expr_func then
func = expr_func
offset = 13
force_print = 1
end
elseif expr_func then
func = expr_func
if load("return " .. input) then
-- We wrap the expression with a call to _echo(...), which prevents tail
-- calls (and thus confusing errors). Note we check this is a valid
-- expression separately, to avoid accepting inputs like `)--` (which are
-- parsed as `_echo()--)`.
func = load("return _echo(" .. input .. "\n)", name, "t", tEnv)
offset = 13
end
@ -103,9 +90,8 @@ while running do
local results = table.pack(exception.try(func))
if results[1] then
local n = 1
while n < results.n or n <= force_print do
local value = results[n + 1]
for i = 2, results.n do
local value = results[i]
local ok, serialised = pcall(pretty.pretty, value, {
function_args = settings.get("lua.function_args"),
function_source = settings.get("lua.function_source"),
@ -115,7 +101,6 @@ while running do
else
print(tostring(value))
end
n = n + 1
end
else
printError(results[2])

View File

@ -4,29 +4,29 @@
--[[- The shell API provides access to CraftOS's command line interface.
It allows you to @{run|start programs}, @{setCompletionFunction|add completion
for a program}, and much more.
It allows you to [start programs][`run`], [add completion for a
program][`setCompletionFunction`], and much more.
@{shell} is not a "true" API. Instead, it is a standard program, which injects
[`shell`] is not a "true" API. Instead, it is a standard program, which injects
its API into the programs that it launches. This allows for multiple shells to
run at the same time, but means that the API is not available in the global
environment, and so is unavailable to other @{os.loadAPI|APIs}.
environment, and so is unavailable to other [APIs][`os.loadAPI`].
## Programs and the program path
When you run a command with the shell, either from the prompt or
@{shell.run|from Lua code}, the shell API performs several steps to work out
[from Lua code][`shell.run`], the shell API performs several steps to work out
which program to run:
1. Firstly, the shell attempts to resolve @{shell.aliases|aliases}. This allows
1. Firstly, the shell attempts to resolve [aliases][`shell.aliases`]. This allows
us to use multiple names for a single command. For example, the `list`
program has two aliases: `ls` and `dir`. When you write `ls /rom`, that's
expanded to `list /rom`.
2. Next, the shell attempts to find where the program actually is. For this, it
uses the @{shell.path|program path}. This is a colon separated list of
uses the [program path][`shell.path`]. This is a colon separated list of
directories, each of which is checked to see if it contains the program.
`list` or `list.lua` doesn't exist in `.` (the current directory), so she
`list` or `list.lua` doesn't exist in `.` (the current directory), so the
shell now looks in `/rom/programs`, where `list.lua` can be found!
3. Finally, the shell reads the file and checks if the file starts with a
@ -192,7 +192,7 @@ end
--- Run a program with the supplied arguments.
--
-- Unlike @{shell.run}, each argument is passed to the program verbatim. While
-- Unlike [`shell.run`], each argument is passed to the program verbatim. While
-- `shell.run("echo", "b c")` runs `echo` with `b` and `c`,
-- `shell.execute("echo", "b c")` runs `echo` with a single argument `b c`.
--
@ -276,7 +276,7 @@ function shell.exit()
end
--- Return the current working directory. This is what is displayed before the
-- `> ` of the shell prompt, and is used by @{shell.resolve} to handle relative
-- `> ` of the shell prompt, and is used by [`shell.resolve`] to handle relative
-- paths.
--
-- @treturn string The current working directory.
@ -313,10 +313,10 @@ function shell.path()
return sPath
end
--- Set the @{path|current program path}.
--- Set the [current program path][`path`].
--
-- Be careful to prefix directories with a `/`. Otherwise they will be searched
-- for from the @{shell.dir|current directory}, rather than the computer's root.
-- for from the [current directory][`shell.dir`], rather than the computer's root.
--
-- @tparam string path The new program path.
-- @since 1.2
@ -327,8 +327,8 @@ end
--- Resolve a relative path to an absolute path.
--
-- The @{fs} and @{io} APIs work using absolute paths, and so we must convert
-- any paths relative to the @{dir|current directory} to absolute ones. This
-- The [`fs`] and [`io`] APIs work using absolute paths, and so we must convert
-- any paths relative to the [current directory][`dir`] to absolute ones. This
-- does nothing when the path starts with `/`.
--
-- @tparam string path The path to resolve.
@ -357,10 +357,10 @@ local function pathWithExtension(_sPath, _sExt)
return _sPath .. "." .. _sExt
end
--- Resolve a program, using the @{path|program path} and list of @{aliases|aliases}.
--- Resolve a program, using the [program path][`path`] and list of [aliases][`aliases`].
--
-- @tparam string command The name of the program
-- @treturn string|nil The absolute path to the program, or @{nil} if it could
-- @treturn string|nil The absolute path to the program, or [`nil`] if it could
-- not be found.
-- @since 1.2
-- @changed 1.80pr1 Now searches for files with and without the `.lua` extension.
@ -406,7 +406,7 @@ function shell.resolveProgram(command)
return nil
end
--- Return a list of all programs on the @{shell.path|path}.
--- Return a list of all programs on the [path][`shell.path`].
--
-- @tparam[opt] boolean include_hidden Include hidden files. Namely, any which
-- start with `.`.
@ -518,7 +518,7 @@ end
-- completed to `ls rom/`.
--
-- Completion handlers for your program may be registered with
-- @{shell.setCompletionFunction}.
-- [`shell.setCompletionFunction`].
--
-- @tparam string sLine The input to complete.
-- @treturn { string }|nil The list of possible completions.
@ -614,7 +614,7 @@ end
--- Get a table containing all completion functions.
--
-- This should only be needed when building custom shells. Use
-- @{setCompletionFunction} to add a completion function.
-- [`setCompletionFunction`] to add a completion function.
--
-- @treturn { [string] = { fnComplete = function } } A table mapping the
-- absolute path of programs, to their completion functions.
@ -676,12 +676,12 @@ function shell.aliases()
end
if multishell then
--- Open a new @{multishell} tab running a command.
--- Open a new [`multishell`] tab running a command.
--
-- This behaves similarly to @{shell.run}, but instead returns the process
-- This behaves similarly to [`shell.run`], but instead returns the process
-- index.
--
-- This function is only available if the @{multishell} API is.
-- This function is only available if the [`multishell`] API is.
--
-- @tparam string ... The command line to run.
-- @see shell.run
@ -706,7 +706,7 @@ if multishell then
end
end
--- Switch to the @{multishell} tab with the given index.
--- Switch to the [`multishell`] tab with the given index.
--
-- @tparam number id The tab to switch to.
-- @see multishell.setFocus

2
vendor/Cobalt vendored

@ -1 +1 @@
Subproject commit a630403859ff6321d10292fbf94dcfb30b507d9d
Subproject commit 6b0d2e234ebbfb1ffd20cde071ecd4c782be6da1