finish basic access control system

This commit is contained in:
2025-10-09 14:09:47 +08:00
parent d3cbc15450
commit 11d138751a
32 changed files with 4687 additions and 345 deletions

2
.gitignore vendored
View File

@@ -1,5 +1,5 @@
node_modules
*.lua
.DS_Store
event/
build/
reference/

View File

@@ -1,9 +1,16 @@
set windows-shell := ["powershell.exe", "-NoLogo", "-Command"]
build: build-autocraft sync
build: build-autocraft build-accesscontrol build-test sync
build-autocraft:
pnpm build-autocraft
pnpm tstl -p ./tsconfig.autocraft.json
build-accesscontrol:
pnpm tstl -p ./tsconfig.accesscontrol.json
cp ./src/accesscontrol/access.config.json ./build/
build-test:
pnpm tstl -p ./tsconfig.test.json
sync:
cp -r "./build/*" "C:\\Users\\sikongjueluo\\AppData\\Roaming\\CraftOS-PC\\computer\\0\\user\\"

View File

@@ -5,16 +5,17 @@
"main": "main.ts",
"scripts": {
"build": "tstl",
"build-autocraft": "tstl -p ./tsconfig.autocraft.json",
"sync": "cp -r \"./build/*\" \"C:\\Users\\sikongjueluo\\AppData\\Roaming\\CraftOS-PC\\computer\\0\\user\""
},
"devDependencies": {
"@eslint/js": "^9.36.0",
"@sikongjueluo/advanced-peripherals-types": "file:types/advanced-peripherals",
"@jackmacwindows/cc-types": "file:types/cc",
"@jackmacwindows/craftos-types": "file:types/craftos",
"@jackmacwindows/lua-types": "^2.13.2",
"@jackmacwindows/typescript-to-lua": "^1.28.1",
"@sikongjueluo/advanced-peripherals-types": "file:types/advanced-peripherals",
"@sikongjueluo/toml2lua-types": "file:types/toml2lua",
"@sikongjueluo/dkjson-types": "file:types/dkjson",
"@typescript-to-lua/language-extensions": "^1.19.0",
"eslint": "^9.36.0",
"typescript": "^5.7.2",

16
pnpm-lock.yaml generated
View File

@@ -26,6 +26,12 @@ importers:
'@sikongjueluo/advanced-peripherals-types':
specifier: file:types/advanced-peripherals
version: file:types/advanced-peripherals
'@sikongjueluo/dkjson-types':
specifier: file:types/dkjson
version: file:types/dkjson
'@sikongjueluo/toml2lua-types':
specifier: file:types/toml2lua
version: file:types/toml2lua
'@typescript-to-lua/language-extensions':
specifier: ^1.19.0
version: 1.19.0
@@ -126,6 +132,12 @@ packages:
'@sikongjueluo/advanced-peripherals-types@file:types/advanced-peripherals':
resolution: {directory: types/advanced-peripherals, type: directory}
'@sikongjueluo/dkjson-types@file:types/dkjson':
resolution: {directory: types/dkjson, type: directory}
'@sikongjueluo/toml2lua-types@file:types/toml2lua':
resolution: {directory: types/toml2lua, type: directory}
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
@@ -677,6 +689,10 @@ snapshots:
'@sikongjueluo/advanced-peripherals-types@file:types/advanced-peripherals': {}
'@sikongjueluo/dkjson-types@file:types/dkjson': {}
'@sikongjueluo/toml2lua-types@file:types/toml2lua': {}
'@types/estree@1.0.8': {}
'@types/json-schema@7.0.15': {}

2
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,2 @@
onlyBuiltDependencies:
- core-js-pure

View File

@@ -0,0 +1,67 @@
{
"detectRange": 64,
"detectInterval": 1,
"warnInterval": 7,
"adminGroupConfig": {
"groupName": "Admin",
"groupUsers": ["Selcon"],
"isAllowed": true,
"isWarnTarget": true
},
"defaultToastConfig": {
"title": {
"text": "Welcome",
"color": "green"
},
"msg": {
"text": "Hello %groupName% %playerName%",
"color": "green"
},
"prefix": "桃花源",
"brackets": "[]",
"bracketColor": ""
},
"warnToastConfig": {
"title": {
"text": "Attention!!!",
"color": "red"
},
"msg": {
"text": "%playerName% you are not allowed to be here",
"color": "red"
},
"prefix": "Taohuayuan",
"brackets": "[]",
"bracketColor": ""
},
"usersGroups": [
{
"groupName": "user",
"groupUsers": [],
"isAllowed": true,
"isWarnTarget": true
},
{
"groupName": "VIP",
"groupUsers": [],
"isAllowed": true,
"isWarnTarget": false
},
{
"groupName": "enemy",
"groupUsers": [],
"isAllowed": false,
"isWarnTarget": false,
"toastConfig": {
"title": {
"text": "Warn",
"color": "red"
},
"msg": {
"text": "Warn %playerName%",
"color": "red"
}
}
}
]
}

0
src/accesscontrol/cli.ts Normal file
View File

158
src/accesscontrol/config.ts Normal file
View File

@@ -0,0 +1,158 @@
import { CCLog } from "@/lib/ccLog";
import * as dkjson from "@sikongjueluo/dkjson-types";
let log: CCLog | undefined;
interface ToastConfig {
title: MinecraftTextComponent;
msg: MinecraftTextComponent;
prefix?: string;
brackets?: string;
bracketColor?: string;
}
interface UserGroupConfig {
groupName: string;
isAllowed: boolean;
isWarnTarget: boolean;
groupUsers: string[];
toastConfig?: ToastConfig;
}
interface AccessConfig {
detectInterval: number;
warnInterval: number;
detectRange: number;
adminGroupConfig: UserGroupConfig;
defaultToastConfig: ToastConfig;
warnToastConfig: ToastConfig;
usersGroups: UserGroupConfig[];
}
const defaultConfig: AccessConfig = {
detectRange: 64,
detectInterval: 3,
warnInterval: 7,
adminGroupConfig: {
groupName: "Admin",
groupUsers: ["Selcon"],
isAllowed: true,
isWarnTarget: false,
},
usersGroups: [
{
groupName: "user",
groupUsers: [],
isAllowed: true,
isWarnTarget: true,
},
{
groupName: "VIP",
groupUsers: [],
isAllowed: true,
isWarnTarget: false,
},
{
groupName: "enemies",
groupUsers: [],
isAllowed: false,
isWarnTarget: false,
toastConfig: {
title: {
text: "Warn",
color: "red",
},
msg: {
text: "Warn %playerName%",
color: "red",
},
},
},
],
defaultToastConfig: {
title: {
text: "Welcome",
color: "green",
},
msg: {
text: "Hello User %playerName%",
color: "green",
},
prefix: "Taohuayuan",
brackets: "[]",
bracketColor: "",
},
warnToastConfig: {
title: {
text: "Attention!!!",
color: "red",
},
msg: {
text: "%playerName% you are not allowed to be here",
color: "red",
},
prefix: "Taohuayuan",
brackets: "[]",
bracketColor: "",
},
};
function setLog(newLog: CCLog) {
log = newLog;
}
function loadConfig(filepath: string): AccessConfig {
const [fp] = io.open(filepath, "r");
if (fp == undefined) {
print("Failed to open config file " + filepath);
return defaultConfig;
}
const configJson = fp.read("*a");
if (configJson == undefined) {
print("Failed to read config file");
return defaultConfig;
}
const [config, pos, err] = dkjson.decode(configJson);
if (config == undefined) {
log?.warn(
`Config decode failed at ${pos}, use default instead. Error :${err}`,
);
return defaultConfig;
}
// Not use external lib
// const config = textutils.unserialiseJSON(configJson, {
// parse_empty_array: true,
// });
return config as AccessConfig;
}
function saveConfig(config: AccessConfig, filepath: string) {
const configJson = dkjson.encode(config, { indent: true }) as string;
// Not use external lib
// const configJson = textutils.serializeJSON(config, { unicode_strings: true });
if (configJson == undefined) {
print("Failed to save config");
}
const [fp, _err] = io.open(filepath, "w+");
if (fp == undefined) {
print("Failed to open config file " + filepath);
return;
}
fp.write(configJson);
fp.close();
}
export {
ToastConfig,
UserGroupConfig,
AccessConfig,
loadConfig,
saveConfig,
setLog,
};

355
src/accesscontrol/main.ts Normal file
View File

@@ -0,0 +1,355 @@
import { CCLog, DAY } from "@/lib/ccLog";
import {
ToastConfig,
UserGroupConfig,
loadConfig,
saveConfig,
setLog,
} from "./config";
import * as peripheralManager from "../lib/PeripheralManager";
import { ChatBoxEvent, pullEventAs } from "@/lib/event";
import { quotestring } from "@sikongjueluo/dkjson-types";
const DEBUG = false;
const args = [...$vararg];
const log = new CCLog("accesscontrol.log", DAY);
setLog(log);
const configFilepath = `${shell.dir()}/access.config.json`;
const config = loadConfig(configFilepath);
log.info("Load config successfully!");
log.debug(textutils.serialise(config, { allow_repetitions: true }));
const groupNames = config.usersGroups.map((value) => value.groupName);
const warnTargetPlayers = config.adminGroupConfig.groupUsers.concat(
config.usersGroups
.filter((value) => value.isWarnTarget)
.map((value) => value.groupUsers ?? [])
.flat(),
);
const playerDetector = peripheralManager.findByNameRequired("playerDetector");
const chatBox = peripheralManager.findByNameRequired("chatBox");
let inRangePlayers: string[] = [];
let notAllowedPlayers: string[] = [];
function safeParseTextComponent(
component: MinecraftTextComponent,
playerName: string,
groupName?: string,
): string {
if (component.text == undefined) {
component.text = "Wrong text, please contanct with admin";
} else if (component.text.includes("%")) {
component.text = component.text.replace("%playerName%", playerName);
if (groupName != undefined)
component.text = component.text.replace("%groupName%", groupName);
}
return textutils.serialiseJSON(component);
}
function sendToast(
toastConfig: ToastConfig,
player: string,
groupConfig?: UserGroupConfig,
) {
return chatBox.sendFormattedToastToPlayer(
safeParseTextComponent(
toastConfig.msg ?? config.defaultToastConfig.msg,
player,
groupConfig?.groupName,
),
safeParseTextComponent(
toastConfig.title ?? config.defaultToastConfig.title,
player,
groupConfig?.groupName,
),
player,
quotestring(toastConfig.prefix ?? config.defaultToastConfig.prefix!),
toastConfig.brackets ?? config.defaultToastConfig.brackets,
toastConfig.bracketColor ?? config.defaultToastConfig.bracketColor,
undefined,
true,
);
}
function sendWarn(player: string) {
const playerPos = playerDetector.getPlayerPos(player);
const onlinePlayers = playerDetector.getOnlinePlayers();
const warnMsg = `Not Allowed Player ${player} Break in Home at Position ${playerPos?.x}, ${playerPos?.y}, ${playerPos?.z}`;
log.warn(warnMsg);
sendToast(config.warnToastConfig, player);
chatBox.sendFormattedMessageToPlayer(
safeParseTextComponent(config.warnToastConfig.msg, player),
player,
"AccessControl",
"[]",
undefined,
undefined,
true,
);
for (const targetPlayer of warnTargetPlayers) {
if (!onlinePlayers.includes(targetPlayer)) continue;
chatBox.sendFormattedMessageToPlayer(
textutils.serialise({
text: warnMsg,
color: "red",
} as MinecraftTextComponent),
targetPlayer,
"AccessControl",
"[]",
undefined,
undefined,
true,
);
}
}
function sendCommandHelp(targetPlayer: string) {
chatBox.sendMessageToPlayer(
`
Command Usage: @AC /<Command> [args]
Command:
- add <userGroup> <playerName>
add player to group
userGroup: ${groupNames.join(", ")}
- del <userGroup> <playerName>
delete player in the group, except Admin
userGroup: ${groupNames.join(", ")}
- list
list all of the player with its group
- set <options> [params]
config access control settins
options:
- warnInterval <number>
set the interval of warn, which is not allowed
- detectInterval <number>
set the interval of detecting players
- detectRange <number>
set the sphere range of detect
`,
targetPlayer,
"AccessControl",
"[]",
undefined,
undefined,
true,
);
}
function warnLoop() {
while (true) {
for (const player of notAllowedPlayers) {
if (inRangePlayers.includes(player)) {
sendWarn(player);
} else {
notAllowedPlayers = notAllowedPlayers.filter(
(value) => value != player,
);
}
}
os.sleep(config.warnInterval);
}
}
function mainLoop() {
while (true) {
const players = playerDetector.getPlayersInRange(config.detectRange);
if (DEBUG) {
const playersList = "[ " + players.join(",") + " ]";
log.debug(`Detected ${players.length} players: ${playersList}`);
}
for (const player of players) {
if (inRangePlayers.includes(player)) continue;
if (config.adminGroupConfig.groupUsers.includes(player)) {
log.info(`Admin ${player} enter`);
sendToast(
config.adminGroupConfig.toastConfig ?? config.defaultToastConfig,
player,
config.adminGroupConfig,
);
continue;
}
let inUserGroup = false;
for (const userGroupConfig of config.usersGroups) {
if (userGroupConfig.groupUsers == undefined) continue;
if (!userGroupConfig.groupUsers.includes(player)) continue;
if (!userGroupConfig.isAllowed) {
sendWarn(player);
notAllowedPlayers.push(player);
continue;
}
log.info(`${userGroupConfig.groupName} ${player} enter`);
sendToast(
userGroupConfig.toastConfig ?? config.defaultToastConfig,
player,
userGroupConfig,
);
inUserGroup = true;
}
if (inUserGroup) continue;
sendWarn(player);
notAllowedPlayers.push(player);
}
inRangePlayers = players;
os.sleep(config.detectInterval);
}
}
function configLoop() {
while (true) {
const ev = pullEventAs(ChatBoxEvent, "chat");
if (ev == undefined) continue;
if (!config.adminGroupConfig.groupUsers.includes(ev.username)) continue;
if (!ev.message.startsWith("@AC")) continue;
// log.info(`Received "${ev.message}" from admin ${ev.username}`);
const params = ev.message.split(" ");
if (params.length < 2) {
sendCommandHelp(ev.username);
continue;
}
if (params[1] == "/add" && params.length == 4) {
if (params[2] == "admin") {
config.adminGroupConfig.groupUsers.push(params[3]);
chatBox.sendMessageToPlayer(
`Add player ${params[3]} to admin`,
ev.username,
"AccessControl",
);
} else if (groupNames.includes(params[2])) {
const groupConfig = config.usersGroups.find(
(value) => value.groupName == params[2],
)!;
if (groupConfig.groupUsers == undefined)
groupConfig.groupUsers = [params[3]];
else groupConfig.groupUsers.push(params[3]);
chatBox.sendMessageToPlayer(
`Add player ${params[3]} to ${groupConfig.groupName}`,
ev.username,
"AccessControl",
);
} else {
sendCommandHelp(ev.username);
continue;
}
} else if (params[1] == "/del" && params.length == 4) {
if (params[2] == "admin") {
chatBox.sendMessageToPlayer(
`Could't delete admin, please edit config`,
ev.username,
"AccessControl",
);
} else if (groupNames.includes(params[2])) {
const groupConfig = config.usersGroups.find(
(value) => value.groupName == params[2],
)!;
if (groupConfig.groupUsers == undefined) groupConfig.groupUsers = [];
else
groupConfig.groupUsers = groupConfig.groupUsers.filter(
(user) => user != params[3],
);
chatBox.sendMessageToPlayer(
`Delete ${groupConfig.groupName} ${params[3]}`,
ev.username,
"AccessControl",
);
} else {
sendCommandHelp(ev.username);
continue;
}
} else if (params[1] == "/list") {
chatBox.sendMessageToPlayer(
`Admins : [ ${config.adminGroupConfig.groupUsers.join(", ")} ]`,
ev.username,
"AccessControl",
);
for (const groupConfig of config.usersGroups) {
chatBox.sendMessageToPlayer(
`${groupConfig.groupName} : [ ${config.adminGroupConfig.groupUsers.join(", ")} ]`,
ev.username,
"AccessControl",
);
}
} else if (params[1] == "/set" && params.length == 4) {
if (params[2] == "warnInterval") {
config.warnInterval = parseInt(params[3]);
chatBox.sendMessageToPlayer(
`Set warn interval to ${config.warnInterval}`,
ev.username,
"AccessControl",
);
} else if (params[2] == "detectInterval") {
config.detectInterval = parseInt(params[3]);
chatBox.sendMessageToPlayer(
`Set detect interval to ${config.detectInterval}`,
ev.username,
"AccessControl",
);
} else if (params[2] == "detectRange") {
config.detectRange = parseInt(params[3]);
chatBox.sendMessageToPlayer(
`Set detect range to ${config.detectRange}`,
ev.username,
"AccessControl",
);
} else {
sendCommandHelp(ev.username);
continue;
}
} else {
sendCommandHelp(ev.username);
continue;
}
saveConfig(config, configFilepath);
}
}
function main(args: string[]) {
log.debug("Starting access control system, get args: " + args.join(", "));
if (args.length == 1) {
if (args[0] == "start") {
parallel.waitForAll(
() => {
mainLoop();
},
() => {
configLoop();
},
() => {
warnLoop();
},
);
return;
}
}
print(`Usage: accesscontrol start`);
}
try {
main(args);
} catch (error: unknown) {
log.error(textutils.serialise(error as object));
} finally {
log.close();
}

View File

@@ -58,7 +58,7 @@ function main() {
// Get package NBT
packagesContainer.pushItems(turtleLocalName, slot);
const packageInfo = blockReader.getBlockData().Items[1];
const packageInfo = blockReader.getBlockData()!.Items[1];
// log.info(textutils.serialise(packageInfo));
// Get recipe
@@ -96,7 +96,7 @@ function main() {
restCraftCnt -= craftCnt;
// Get output item
craftOutputItem ??= blockReader.getBlockData().Items[1];
craftOutputItem ??= blockReader.getBlockData()!.Items[1];
} while (restCraftCnt > 0);
// Finally output

View File

@@ -1,43 +1,62 @@
type PeripheralType = "inventory" | "modem" | "wiredModem" | "blockReader";
type PeripheralType =
| "inventory"
| "modem"
| "wiredModem"
| "blockReader"
| "chatBox"
| "playerDetector";
type BlockSide = "top" | "bottom" | "left" | "right" | "front" | "back";
// Declare the function signature for findBySide
function findByName(
devType: "inventory",
devName: string,
devName?: string,
): InventoryPeripheral | undefined;
function findByName(
devType: "modem",
devName: string,
devName?: string,
): ModemPeripheral | undefined;
function findByName(
devType: "wiredModem",
devName: string,
devName?: string,
): WiredModemPeripheral | undefined;
function findByName(
devType: "blockReader",
devName: string,
devName?: string,
): BlockReaderPeripheral | undefined;
function findByName(
devType: "chatBox",
devName?: string,
): ChatBoxPeripheral | undefined;
function findByName(
devType: "playerDetector",
devName?: string,
): PlayerDetectorPeripheral | undefined;
function findByName(
devType: PeripheralType,
side: BlockSide,
): IPeripheral | undefined;
function findByName(
devType: PeripheralType,
devName: string,
devName?: string,
): IPeripheral | undefined;
// Implement the function signature for findBySide
// Implement the function signature for findByName
function findByName(
devType: PeripheralType,
devName: string,
devName?: string,
): IPeripheral | undefined {
const dev = peripheral.find(
devType == "wiredModem" ? "modem" : devType,
(name: string, _) => {
return name == devName;
},
)[0];
let dev;
if (devName == undefined) {
dev = peripheral.find(devType)[0];
} else {
dev = peripheral.find(
devType == "wiredModem" ? "modem" : devType,
(name: string, _) => {
return name == devName;
},
)[0];
}
// Seperate Modem and wiredModem
if (
@@ -54,41 +73,54 @@ function findByName(
)
return undefined;
if (dev != undefined && peripheral.getType(dev) != devType) return undefined;
return dev;
}
// Declare the function signature for findBySideRequired
function findByNameRequired(
devType: "inventory",
devName: string,
devName?: string,
): InventoryPeripheral;
function findByNameRequired(devType: "modem", devName: string): ModemPeripheral;
function findByNameRequired(
devType: "modem",
devName?: string,
): ModemPeripheral;
function findByNameRequired(
devType: "wiredModem",
devName: string,
devName?: string,
): WiredModemPeripheral;
function findByNameRequired(
devType: "blockReader",
devName: string,
devName?: string,
): BlockReaderPeripheral;
function findByNameRequired(
devType: "chatBox",
devName?: string,
): ChatBoxPeripheral;
function findByNameRequired(
devType: "playerDetector",
devName?: string,
): PlayerDetectorPeripheral;
function findByNameRequired<T extends IPeripheral>(
devType: PeripheralType,
side: BlockSide,
): T;
function findByNameRequired<T extends IPeripheral>(
devType: PeripheralType,
devName: string,
devName?: string,
): T;
// Implement the function signature for findBySideRequired
function findByNameRequired<T extends IPeripheral>(
devType: PeripheralType,
side: string,
devName?: string,
): T {
const dev = findByName(devType, side);
if (!dev) {
const dev = findByName(devType, devName);
if (dev == undefined) {
throw new Error(
`Required peripheral of type '${devType}' not found on side '${side}'`,
`Required peripheral of type '${devType}' not found with name '${devName}'`,
);
}
return dev as T;

View File

@@ -1,16 +1,35 @@
enum LogLevel {
Info = 0,
Warn = 1,
Error = 2,
Debug = 0,
Info = 1,
Warn = 2,
Error = 3,
}
// Define time interval constants in seconds
export const SECOND = 1;
export const MINUTE = 60 * SECOND;
export const HOUR = 60 * MINUTE;
export const DAY = 24 * HOUR;
export const WEEK = 7 * DAY;
export class CCLog {
private fp: LuaFile | undefined;
constructor(filename?: string) {
private filename?: string;
private interval: number;
private startTime: number;
private currentTimePeriod: string;
constructor(filename?: string, interval: number = DAY) {
term.clear();
term.setCursorPos(1, 1);
this.interval = interval;
this.startTime = os.time(os.date("*t"));
this.currentTimePeriod = this.getTimePeriodString(this.startTime);
if (filename != undefined && filename.length != 0) {
const filepath = shell.dir() + "/" + filename;
this.filename = filename;
const filepath = this.generateFilePath(filename, this.currentTimePeriod);
const [file, error] = io.open(filepath, fs.exists(filepath) ? "a" : "w+");
if (file != undefined) {
this.fp = file;
@@ -20,12 +39,84 @@ export class CCLog {
}
}
/**
* Generates a time period string based on the interval
* For DAY interval: YYYY-MM-DD
* For HOUR interval: YYYY-MM-DD-HH
* For MINUTE interval: YYYY-MM-DD-HH-MM
* For SECOND interval: YYYY-MM-DD-HH-MM-SS
*/
private getTimePeriodString(time: number): string {
// Calculate which time period this timestamp falls into
const periodStart = Math.floor(time / this.interval) * this.interval;
const periodDate = os.date("*t", periodStart);
if (this.interval >= DAY) {
return `${periodDate.year}-${String(periodDate.month).padStart(2, "0")}-${String(periodDate.day).padStart(2, "0")}`;
} else {
return `[${periodDate.year}-${String(periodDate.month).padStart(2, "0")}-${String(periodDate.day).padStart(2, "0")}] - [${String(periodDate.hour).padStart(2, "0")}-${String(periodDate.min).padStart(2, "0")}-${String(periodDate.sec).padStart(2, "0")}]`;
}
}
private generateFilePath(baseFilename: string, timePeriod: string): string {
// Extract file extension if present
const fileNameSubStrings = baseFilename.split(".");
let filenameWithoutExt: string;
let extension = "";
if (fileNameSubStrings.length > 1) {
filenameWithoutExt = fileNameSubStrings[0];
extension = fileNameSubStrings[1];
} else {
filenameWithoutExt = baseFilename;
}
return `${shell.dir()}/${filenameWithoutExt}[${timePeriod}].${extension}`;
}
private checkAndRotateLogFile() {
if (this.filename != undefined && this.filename.length != 0) {
const currentTime = os.time(os.date("*t"));
const currentTimePeriod = this.getTimePeriodString(currentTime);
// If we're in a new time period, rotate the log file
if (currentTimePeriod !== this.currentTimePeriod) {
// Close current file if open
if (this.fp) {
this.fp.close();
this.fp = undefined;
}
// Update the current time period
this.currentTimePeriod = currentTimePeriod;
// Open new log file for the new time period
const filepath = this.generateFilePath(
this.filename,
this.currentTimePeriod,
);
const [file, error] = io.open(
filepath,
fs.exists(filepath) ? "a" : "w+",
);
if (file != undefined) {
this.fp = file;
} else {
throw Error(error);
}
}
}
}
private getFormatMsg(msg: string, level: LogLevel): string {
const date = os.date("*t");
return `[ ${date.year}/${date.month}/${date.day} -- ${date.hour}:${date.min}:${date.sec} ${LogLevel[level]} ] : ${msg}\r\n`;
return `[ ${date.year}/${String(date.month).padStart(2, "0")}/${String(date.day).padStart(2, "0")} ${String(date.hour).padStart(2, "0")}:${String(date.min).padStart(2, "0")}:${String(date.sec).padStart(2, "0")} ${LogLevel[level]} ] : ${msg}`;
}
public writeLine(msg: string, color?: Color) {
// Check if we need to rotate the log file
this.checkAndRotateLogFile();
let originalColor: Color = 0;
if (color != undefined) {
originalColor = term.getTextColor();
@@ -33,17 +124,18 @@ export class CCLog {
}
// Log
term.write(msg);
print(msg);
if (this.fp != undefined) {
this.fp.write(msg);
this.fp.write(msg + "\r\n");
}
if (color != undefined) {
term.setTextColor(originalColor);
}
}
// Next line
term.setCursorPos(1, term.getCursorPos()[1] + 1);
public debug(msg: string) {
this.writeLine(this.getFormatMsg(msg, LogLevel.Debug), colors.gray);
}
public info(msg: string) {

25
src/lib/ccTime.ts Normal file
View File

@@ -0,0 +1,25 @@
class ccDate {
private _timestamp: number;
constructor() {
this._timestamp = os.time(os.date("*t"));
}
public static toDateTable(timestamp: number): LuaDate {
return os.date("*t", timestamp) as LuaDate;
}
public toDateTable(): LuaDate {
return os.date("*t", this._timestamp) as LuaDate;
}
public static toTimestamp(date: LuaDate): number {
return os.time(date);
}
public toTimestamp(): number {
return this._timestamp;
}
}
export { ccDate };

View File

@@ -2,312 +2,461 @@
// delete them from eventInitializers as well.
export interface IEvent {
get_name(): string;
get_args(): any[];
get_name(): string;
get_args(): unknown[];
}
export class CharEvent implements IEvent {
public character: string = "";
public get_name() {return "char";}
public get_args() {return [this.character];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || (args[0] as string) != "char") return null;
let ev = new CharEvent();
ev.character = (args[1] as string);
return ev;
}
public character = "";
public get_name() {
return "char";
}
public get_args() {
return [this.character];
}
public static init(args: unknown[]): IEvent | undefined {
if (!(typeof args[0] === "string") || args[0] != "char") return undefined;
const ev = new CharEvent();
ev.character = args[1] as string;
return ev;
}
}
export class KeyEvent implements IEvent {
public key: Key = 0;
public isHeld: boolean = false;
public isUp: boolean = false;
public get_name() {return this.isUp ? "key_up" : "key";}
public get_args() {return [this.key, (this.isUp ? null : this.isHeld)];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || ((args[0] as string) != "key" && (args[0] as string) != "key_up")) return null;
let ev = new KeyEvent();
ev.key = (args[1] as number);
ev.isUp = (args[0] as string) == "key_up";
ev.isHeld = ev.isUp ? false : (args[2] as boolean);
return ev;
}
public key: Key = 0;
public isHeld = false;
public isUp = false;
public get_name() {
return this.isUp ? "key_up" : "key";
}
public get_args() {
return [this.key, this.isUp ? undefined : this.isHeld];
}
public static init(args: unknown[]): IEvent | undefined {
if (
!(typeof args[0] === "string") ||
(args[0] != "key" && args[0] != "key_up")
)
return undefined;
const ev = new KeyEvent();
ev.key = args[1] as number;
ev.isUp = (args[0] as string) == "key_up";
ev.isHeld = ev.isUp ? false : (args[2] as boolean);
return ev;
}
}
export class PasteEvent implements IEvent {
public text: string = "";
public get_name() {return "paste";}
public get_args() {return [(this.text as any)];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || (args[0] as string) != "paste") return null;
let ev = new PasteEvent();
ev.text = (args[1] as string);
return ev;
}
public text = "";
public get_name() {
return "paste";
}
public get_args() {
return [this.text];
}
public static init(args: unknown[]): IEvent | undefined {
if (!(typeof args[0] === "string") || args[0] != "paste") return undefined;
const ev = new PasteEvent();
ev.text = args[1] as string;
return ev;
}
}
export class TimerEvent implements IEvent {
public id: number = 0;
public isAlarm: boolean = false;
public get_name() {return this.isAlarm ? "alarm" : "timer";}
public get_args() {return [this.id];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || ((args[0] as string) != "timer" && (args[0] as string) != "alarm")) return null;
let ev = new TimerEvent();
ev.id = (args[1] as number);
ev.isAlarm = (args[0] as string) == "alarm";
return ev;
}
public id = 0;
public isAlarm = false;
public get_name() {
return this.isAlarm ? "alarm" : "timer";
}
public get_args() {
return [this.id];
}
public static init(args: unknown[]): IEvent | undefined {
if (
!(typeof args[0] === "string") ||
(args[0] != "timer" && args[0] != "alarm")
)
return undefined;
const ev = new TimerEvent();
ev.id = args[1] as number;
ev.isAlarm = args[0] == "alarm";
return ev;
}
}
export class TaskCompleteEvent implements IEvent {
public id: number = 0;
public success: boolean = false;
public error: string | null = null;
public params: any[] = [];
public get_name() {return "task_complete";}
public get_args() {
if (this.success) return [this.id, this.success].concat(this.params);
else return [this.id, this.success, this.error];
}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || (args[0] as string) != "task_complete") return null;
let ev = new TaskCompleteEvent();
ev.id = (args[1] as number);
ev.success = (args[2] as boolean);
if (ev.success) {
ev.error = null;
ev.params = args.slice(3);
} else {
ev.error = (args[3] as string);
ev.params = [];
}
return ev;
public id = 0;
public success = false;
public error: string | undefined = undefined;
public params: any[] = [];
public get_name() {
return "task_complete";
}
public get_args() {
if (this.success) return [this.id, this.success].concat(this.params);
else return [this.id, this.success, this.error];
}
public static init(args: unknown[]): IEvent | undefined {
if (!(typeof args[0] === "string") || args[0] != "task_complete")
return undefined;
const ev = new TaskCompleteEvent();
ev.id = args[1] as number;
ev.success = args[2] as boolean;
if (ev.success) {
ev.error = undefined;
ev.params = args.slice(3);
} else {
ev.error = args[3] as string;
ev.params = [];
}
return ev;
}
}
export class RedstoneEvent implements IEvent {
public get_name() {return "redstone";}
public get_args() {return [];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || (args[0] as string) != "redstone") return null;
let ev = new RedstoneEvent();
return ev;
}
public get_name() {
return "redstone";
}
public get_args() {
return [];
}
public static init(args: any[]): IEvent | undefined {
if (!(typeof args[0] === "string") || (args[0] as string) != "redstone")
return undefined;
let ev = new RedstoneEvent();
return ev;
}
}
export class TerminateEvent implements IEvent {
public get_name() {return "terminate";}
public get_args() {return [];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || (args[0] as string) != "terminate") return null;
let ev = new TerminateEvent();
return ev;
}
public get_name() {
return "terminate";
}
public get_args() {
return [];
}
public static init(args: any[]): IEvent | undefined {
if (!(typeof args[0] === "string") || (args[0] as string) != "terminate")
return undefined;
let ev = new TerminateEvent();
return ev;
}
}
export class DiskEvent implements IEvent {
public side: string = "";
public eject: boolean = false;
public get_name() {return this.eject ? "disk_eject" : "disk";}
public get_args() {return [this.side];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || ((args[0] as string) != "disk" && (args[0] as string) != "disk_eject")) return null;
let ev = new DiskEvent();
ev.side = (args[1] as string);
ev.eject = (args[0] as string) == "disk_eject";
return ev;
}
public side: string = "";
public eject: boolean = false;
public get_name() {
return this.eject ? "disk_eject" : "disk";
}
public get_args() {
return [this.side];
}
public static init(args: any[]): IEvent | undefined {
if (
!(typeof args[0] === "string") ||
((args[0] as string) != "disk" && (args[0] as string) != "disk_eject")
)
return undefined;
let ev = new DiskEvent();
ev.side = args[1] as string;
ev.eject = (args[0] as string) == "disk_eject";
return ev;
}
}
export class PeripheralEvent implements IEvent {
public side: string = "";
public detach: boolean = false;
public get_name() {return this.detach ? "peripheral_detach" : "peripheral";}
public get_args() {return [this.side];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || ((args[0] as string) != "peripheral" && (args[0] as string) != "peripheral_detach")) return null;
let ev = new PeripheralEvent();
ev.side = (args[1] as string);
ev.detach = (args[0] as string) == "peripheral_detach";
return ev;
}
public side: string = "";
public detach: boolean = false;
public get_name() {
return this.detach ? "peripheral_detach" : "peripheral";
}
public get_args() {
return [this.side];
}
public static init(args: any[]): IEvent | undefined {
if (
!(typeof args[0] === "string") ||
((args[0] as string) != "peripheral" &&
(args[0] as string) != "peripheral_detach")
)
return undefined;
let ev = new PeripheralEvent();
ev.side = args[1] as string;
ev.detach = (args[0] as string) == "peripheral_detach";
return ev;
}
}
export class RednetMessageEvent implements IEvent {
public sender: number = 0;
public message: any;
public protocol: string | null = null;
public get_name() {return "rednet_message";}
public get_args() {return [this.sender, this.message, this.protocol];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || (args[0] as string) != "rednet_message") return null;
let ev = new RednetMessageEvent();
ev.sender = (args[1] as number);
ev.message = args[2];
ev.protocol = (args[3] as string);
return ev;
}
public sender: number = 0;
public message: any;
public protocol: string | undefined = undefined;
public get_name() {
return "rednet_message";
}
public get_args() {
return [this.sender, this.message, this.protocol];
}
public static init(args: any[]): IEvent | undefined {
if (
!(typeof args[0] === "string") ||
(args[0] as string) != "rednet_message"
)
return undefined;
let ev = new RednetMessageEvent();
ev.sender = args[1] as number;
ev.message = args[2];
ev.protocol = args[3] as string;
return ev;
}
}
export class ModemMessageEvent implements IEvent {
public side: string = "";
public channel: number = 0;
public replyChannel: number = 0;
public message: any;
public distance: number = 0;
public get_name() {return "modem_message";}
public get_args() {return [this.side, this.channel, this.replyChannel, this.message, this.distance];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || (args[0] as string) != "modem_message") return null;
let ev = new ModemMessageEvent();
ev.side = (args[1] as string);
ev.channel = (args[2] as number);
ev.replyChannel = (args[3] as number);
ev.message = args[4];
ev.distance = (args[5] as number);
return ev;
}
public side: string = "";
public channel: number = 0;
public replyChannel: number = 0;
public message: any;
public distance: number = 0;
public get_name() {
return "modem_message";
}
public get_args() {
return [
this.side,
this.channel,
this.replyChannel,
this.message,
this.distance,
];
}
public static init(args: any[]): IEvent | undefined {
if (
!(typeof args[0] === "string") ||
(args[0] as string) != "modem_message"
)
return undefined;
let ev = new ModemMessageEvent();
ev.side = args[1] as string;
ev.channel = args[2] as number;
ev.replyChannel = args[3] as number;
ev.message = args[4];
ev.distance = args[5] as number;
return ev;
}
}
export class HTTPEvent implements IEvent {
public url: string = "";
public handle: HTTPResponse | null = null;
public error: string | null = null;
public get_name() {return this.error == null ? "http_success" : "http_failure";}
public get_args() {return [this.url, (this.error == null ? this.handle : this.error), (this.error != null ? this.handle : null)];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || ((args[0] as string) != "http_success" && (args[0] as string) != "http_failure")) return null;
let ev = new HTTPEvent();
ev.url = (args[1] as string);
if ((args[0] as string) == "http_success") {
ev.error = null;
ev.handle = (args[2] as HTTPResponse);
} else {
ev.error = (args[2] as string);
if (ev.error == null) ev.error = "";
ev.handle = (args[3] as HTTPResponse);
}
return ev;
public url: string = "";
public handle: HTTPResponse | undefined = undefined;
public error: string | undefined = undefined;
public get_name() {
return this.error == undefined ? "http_success" : "http_failure";
}
public get_args() {
return [
this.url,
this.error == undefined ? this.handle : this.error,
this.error != undefined ? this.handle : undefined,
];
}
public static init(args: any[]): IEvent | undefined {
if (
!(typeof args[0] === "string") ||
((args[0] as string) != "http_success" &&
(args[0] as string) != "http_failure")
)
return undefined;
let ev = new HTTPEvent();
ev.url = args[1] as string;
if ((args[0] as string) == "http_success") {
ev.error = undefined;
ev.handle = args[2] as HTTPResponse;
} else {
ev.error = args[2] as string;
if (ev.error == undefined) ev.error = "";
ev.handle = args[3] as HTTPResponse;
}
return ev;
}
}
export class WebSocketEvent implements IEvent {
public handle: WebSocket | null = null;
public error: string | null = null;
public get_name() {return this.error == null ? "websocket_success" : "websocket_failure";}
public get_args() {return [this.handle == null ? this.error : this.handle];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || ((args[0] as string) != "websocket_success" && (args[0] as string) != "websocket_failure")) return null;
let ev = new WebSocketEvent();
if ((args[0] as string) == "websocket_success") {
ev.handle = (args[1] as WebSocket);
ev.error = null;
} else {
ev.error = (args[1] as string);
ev.handle = null;
}
return ev;
public handle: WebSocket | undefined = undefined;
public error: string | undefined = undefined;
public get_name() {
return this.error == undefined ? "websocket_success" : "websocket_failure";
}
public get_args() {
return [this.handle == undefined ? this.error : this.handle];
}
public static init(args: any[]): IEvent | undefined {
if (
!(typeof args[0] === "string") ||
((args[0] as string) != "websocket_success" &&
(args[0] as string) != "websocket_failure")
)
return undefined;
let ev = new WebSocketEvent();
if ((args[0] as string) == "websocket_success") {
ev.handle = args[1] as WebSocket;
ev.error = undefined;
} else {
ev.error = args[1] as string;
ev.handle = undefined;
}
return ev;
}
}
export enum MouseEventType {
Click,
Up,
Scroll,
Drag,
Touch,
Move,
Click,
Up,
Scroll,
Drag,
Touch,
Move,
}
export class MouseEvent implements IEvent {
public button: number = 0;
public x: number = 0;
public y: number = 0;
public side: string | null = null;
public type: MouseEventType = MouseEventType.Click;
public get_name() {
return {
[MouseEventType.Click]: "mouse_click",
[MouseEventType.Up]: "mouse_up",
[MouseEventType.Scroll]: "mouse_scroll",
[MouseEventType.Drag]: "mouse_drag",
[MouseEventType.Touch]: "monitor_touch",
[MouseEventType.Move]: "mouse_move"
}[this.type];
}
public get_args() {return [(this.type == MouseEventType.Touch ? this.side : this.button), this.x, this.y];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string")) return null;
let ev = new MouseEvent();
const type = args[0] as string;
if (type == "mouse_click") {ev.type = MouseEventType.Click; ev.button = (args[1] as number); ev.side = null;}
else if (type == "mouse_up") {ev.type = MouseEventType.Up; ev.button = (args[1] as number); ev.side = null;}
else if (type == "mouse_scroll") {ev.type = MouseEventType.Scroll; ev.button = (args[1] as number); ev.side = null;}
else if (type == "mouse_drag") {ev.type = MouseEventType.Drag; ev.button = (args[1] as number); ev.side = null;}
else if (type == "monitor_touch") {ev.type = MouseEventType.Touch; ev.button = 0; ev.side = (args[1] as string);}
else if (type == "mouse_move") {ev.type = MouseEventType.Move; ev.button = (args[1] as number); ev.side = null;}
else return null;
ev.x = (args[2] as number);
ev.y = (args[3] as number);
return ev;
}
public button: number = 0;
public x: number = 0;
public y: number = 0;
public side: string | undefined = undefined;
public type: MouseEventType = MouseEventType.Click;
public get_name() {
return {
[MouseEventType.Click]: "mouse_click",
[MouseEventType.Up]: "mouse_up",
[MouseEventType.Scroll]: "mouse_scroll",
[MouseEventType.Drag]: "mouse_drag",
[MouseEventType.Touch]: "monitor_touch",
[MouseEventType.Move]: "mouse_move",
}[this.type];
}
public get_args() {
return [
this.type == MouseEventType.Touch ? this.side : this.button,
this.x,
this.y,
];
}
public static init(args: any[]): IEvent | undefined {
if (!(typeof args[0] === "string")) return undefined;
let ev = new MouseEvent();
const type = args[0] as string;
if (type == "mouse_click") {
ev.type = MouseEventType.Click;
ev.button = args[1] as number;
ev.side = undefined;
} else if (type == "mouse_up") {
ev.type = MouseEventType.Up;
ev.button = args[1] as number;
ev.side = undefined;
} else if (type == "mouse_scroll") {
ev.type = MouseEventType.Scroll;
ev.button = args[1] as number;
ev.side = undefined;
} else if (type == "mouse_drag") {
ev.type = MouseEventType.Drag;
ev.button = args[1] as number;
ev.side = undefined;
} else if (type == "monitor_touch") {
ev.type = MouseEventType.Touch;
ev.button = 0;
ev.side = args[1] as string;
} else if (type == "mouse_move") {
ev.type = MouseEventType.Move;
ev.button = args[1] as number;
ev.side = undefined;
} else return undefined;
ev.x = args[2] as number;
ev.y = args[3] as number;
return ev;
}
}
export class ResizeEvent implements IEvent {
public side: string | null = null;
public get_name() {return this.side == null ? "term_resize" : "monitor_resize";}
public get_args() {return [this.side];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || ((args[0] as string) != "term_resize" && (args[0] as string) != "monitor_resize")) return null;
let ev = new ResizeEvent();
if ((args[0] as string) == "monitor_resize") ev.side = (args[1] as string);
else ev.side = null;
return ev;
}
public side: string | undefined = undefined;
public get_name() {
return this.side == undefined ? "term_resize" : "monitor_resize";
}
public get_args() {
return [this.side];
}
public static init(args: any[]): IEvent | undefined {
if (
!(typeof args[0] === "string") ||
((args[0] as string) != "term_resize" &&
(args[0] as string) != "monitor_resize")
)
return undefined;
let ev = new ResizeEvent();
if ((args[0] as string) == "monitor_resize") ev.side = args[1] as string;
else ev.side = undefined;
return ev;
}
}
export class TurtleInventoryEvent implements IEvent {
public get_name() {return "turtle_inventory";}
public get_args() {return [];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || (args[0] as string) != "turtle_inventory") return null;
let ev = new TurtleInventoryEvent();
return ev;
}
public get_name() {
return "turtle_inventory";
}
public get_args() {
return [];
}
public static init(args: any[]): IEvent | undefined {
if (
!(typeof args[0] === "string") ||
(args[0] as string) != "turtle_inventory"
)
return undefined;
let ev = new TurtleInventoryEvent();
return ev;
}
}
class SpeakerAudioEmptyEvent implements IEvent {
public side: string = "";
public get_name() {return "speaker_audio_empty";}
public get_args() {return [this.side];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || (args[0] as string) != "speaker_audio_empty") return null;
let ev: SpeakerAudioEmptyEvent;
ev.side = args[1] as string;
return ev;
}
public side = "";
public get_name() {
return "speaker_audio_empty";
}
public get_args() {
return [this.side];
}
public static init(args: unknown[]): IEvent | undefined {
if (!(typeof args[0] === "string") || args[0] != "speaker_audio_empty")
return undefined;
const ev = new SpeakerAudioEmptyEvent();
ev.side = args[1] as string;
return ev;
}
}
class ComputerCommandEvent implements IEvent {
public args: string[] = [];
public get_name() {return "computer_command";}
public get_args() {return this.args;}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || (args[0] as string) != "computer_command") return null;
let ev: ComputerCommandEvent;
ev.args = args.slice(1);
return ev;
}
public args: string[] = [];
public get_name() {
return "computer_command";
}
public get_args() {
return this.args;
}
public static init(args: unknown[]): IEvent | undefined {
if (!(typeof args[0] === "string") || args[0] != "computer_command")
return undefined;
const ev = new ComputerCommandEvent();
ev.args = args.slice(1) as string[];
return ev;
}
}
/*
class Event implements IEvent {
public get_name() {return "";}
public get_args() {return [(: any)];}
public static init(args: any[]): IEvent | null {
if (!(typeof args[0] === "string") || (args[0] as string) != "") return null;
public static init(args: any[]): IEvent | undefined {
if (!(typeof args[0] === "string") || (args[0] as string) != "") return undefined;
let ev: Event;
return ev;
@@ -315,60 +464,107 @@ class Event implements IEvent {
}
*/
export class GenericEvent implements IEvent {
public args: any[] = [];
public get_name() {return (this.args[0] as string);}
public get_args() {return this.args.slice(1);}
public static init(args: any[]): IEvent | null {
let ev = new GenericEvent();
ev.args = args;
return ev;
}
export class ChatBoxEvent implements IEvent {
public username: string = "";
public message: string = "";
public uuid: string = "";
public isHidden: boolean = false;
public messageUtf8: string = "";
public get_name() {
return "chat";
}
public get_args() {
return [
this.username,
this.message,
this.uuid,
this.isHidden,
this.messageUtf8,
];
}
public static init(args: any[]): IEvent | undefined {
if (!(typeof args[0] === "string") || (args[0] as string) != "chat")
return undefined;
let ev = new ChatBoxEvent();
ev.username = args[1] as string;
ev.message = args[2] as string;
ev.uuid = args[3] as string;
ev.isHidden = args[4] as boolean;
ev.messageUtf8 = args[5] as string;
return ev;
}
}
let eventInitializers: ((args: any[]) => IEvent | null)[] = [
CharEvent.init,
KeyEvent.init,
PasteEvent.init,
TimerEvent.init,
TaskCompleteEvent.init,
RedstoneEvent.init,
TerminateEvent.init,
DiskEvent.init,
PeripheralEvent.init,
RednetMessageEvent.init,
ModemMessageEvent.init,
HTTPEvent.init,
WebSocketEvent.init,
MouseEvent.init,
ResizeEvent.init,
TurtleInventoryEvent.init,
SpeakerAudioEmptyEvent.init,
ComputerCommandEvent.init,
GenericEvent.init
export class GenericEvent implements IEvent {
public args: any[] = [];
public get_name() {
return this.args[0] as string;
}
public get_args() {
return this.args.slice(1);
}
public static init(args: any[]): IEvent | undefined {
let ev = new GenericEvent();
ev.args = args;
return ev;
}
}
let eventInitializers: ((args: unknown[]) => IEvent | undefined)[] = [
(args) => CharEvent.init(args),
(args) => KeyEvent.init(args),
(args) => PasteEvent.init(args),
(args) => TimerEvent.init(args),
(args) => TaskCompleteEvent.init(args),
(args) => RedstoneEvent.init(args),
(args) => TerminateEvent.init(args),
(args) => DiskEvent.init(args),
(args) => PeripheralEvent.init(args),
(args) => RednetMessageEvent.init(args),
(args) => ModemMessageEvent.init(args),
(args) => HTTPEvent.init(args),
(args) => WebSocketEvent.init(args),
(args) => MouseEvent.init(args),
(args) => ResizeEvent.init(args),
(args) => TurtleInventoryEvent.init(args),
(args) => SpeakerAudioEmptyEvent.init(args),
(args) => ComputerCommandEvent.init(args),
(args) => ChatBoxEvent.init(args),
(args) => GenericEvent.init(args),
];
type Constructor<T extends {} = {}> = new (...args: any[]) => T;
export function pullEventRaw(filter: string | null = null): IEvent | null {
let args = table.pack(...coroutine.yield(filter));
for (let init of eventInitializers) {
let ev = init(args);
if (ev != null) return ev;
}
return GenericEvent.init(args);
export function pullEventRaw(
filter: string | undefined = undefined,
): IEvent | undefined {
let args = table.pack(...coroutine.yield(filter));
for (let init of eventInitializers) {
let ev = init(args);
if (ev != undefined) return ev;
}
return GenericEvent.init(args);
}
export function pullEvent(filter: string | null = null): IEvent | null {
let ev = pullEventRaw(filter);
if (ev instanceof TerminateEvent) throw "Terminated";
return ev;
export function pullEvent(
filter: string | undefined = undefined,
): IEvent | undefined {
let ev = pullEventRaw(filter);
if (ev instanceof TerminateEvent) throw "Terminated";
return ev;
}
export function pullEventRawAs<T extends IEvent>(type: Constructor<T>, filter: string | null = null): T | null {
let ev = pullEventRaw(filter);
if ((ev instanceof type)) return ev as T;
else return null;
export function pullEventRawAs<T extends IEvent>(
type: Constructor<T>,
filter: string | undefined = undefined,
): T | undefined {
let ev = pullEventRaw(filter);
if (ev instanceof type) return ev as T;
else return undefined;
}
export function pullEventAs<T extends IEvent>(type: Constructor<T>, filter: string | null = null): T | null {
let ev = pullEvent(filter);
if ((ev instanceof type)) return ev as T;
else return null;
export function pullEventAs<T extends IEvent>(
type: Constructor<T>,
filter: string | undefined = undefined,
): T | undefined {
let ev = pullEvent(filter);
if (ev instanceof type) return ev as T;
else return undefined;
}

3
src/test/main.ts Normal file
View File

@@ -0,0 +1,3 @@
import { testTimeBasedRotation } from "./testCcLog";
testTimeBasedRotation();

35
src/test/testCcLog.ts Normal file
View File

@@ -0,0 +1,35 @@
import { CCLog, MINUTE, HOUR, SECOND } from "@/lib/ccLog";
// Test the new time-based rotation functionality
function testTimeBasedRotation() {
print("Testing time-based log rotation functionality...");
// Test with default interval (1 day)
const logger1 = new CCLog("test_log_default.txt");
logger1.info("This is a test message with default interval (1 day)");
// Test with custom interval (1 hour)
const logger2 = new CCLog("test_log_hourly.txt", HOUR);
logger2.info("This is a test message with 1-hour interval");
// Test with custom interval (30 minutes)
const logger3 = new CCLog("test_log_30min.txt", 30 * MINUTE);
logger3.info("This is a test message with 30-minute interval");
// Test with custom interval (5 seconds)
const logger4 = new CCLog("test_log_5sec.txt", 5 * SECOND);
logger4.info("This is a test message with 5-second interval");
for (let i = 0; i < 10; i++) {
logger4.info(`This is a test message with 5-second interval ${i}`);
sleep(1);
}
logger1.close();
logger2.close();
logger3.close();
logger4.close();
print("Test completed successfully!");
}
export { testTimeBasedRotation };

View File

@@ -0,0 +1,9 @@
{
"$schema": "https://raw.githubusercontent.com/MCJack123/TypeScriptToLua/master/tsconfig-schema.json",
"extends": "./tsconfig.json",
"tstl": {
"luaBundle": "build/accesscontrol.lua",
"luaBundleEntry": "src/accesscontrol/main.ts"
},
"include": ["src/accesscontrol/*.ts"]
}

View File

@@ -8,6 +8,8 @@
"strict": true,
"types": [
"@sikongjueluo/advanced-peripherals-types",
"@sikongjueluo/toml2lua-types",
"@sikongjueluo/dkjson-types",
"@jackmacwindows/lua-types/cc",
"@jackmacwindows/craftos-types",
"@jackmacwindows/cc-types",
@@ -19,7 +21,9 @@
"@jackmacwindows/lua-types/cc": ["./types/cc"],
"@sikongjueluo/advanced-peripherals-types": [
"./types/advanced-peripherals"
]
],
"@sikongjueluo/toml2lua-types": ["./types/toml2lua"],
"@sikongjueluo/dkjson-types": ["./types/dkjson"]
}
},
"tstl": {

9
tsconfig.test.json Normal file
View File

@@ -0,0 +1,9 @@
{
"$schema": "https://raw.githubusercontent.com/MCJack123/TypeScriptToLua/master/tsconfig-schema.json",
"extends": "./tsconfig.json",
"tstl": {
"luaBundle": "build/test.lua",
"luaBundleEntry": "src/test/main.ts"
},
"include": ["src/test/*.ts"]
}

View File

@@ -0,0 +1,38 @@
/// <reference path="./shared.d.ts" />
/**
* Represents the Block Reader peripheral from Advanced Peripherals.
* Used to read data about blocks in front of it.
*
* @see https://docs.advanced-peripherals.de/0.7/peripherals/block_reader/
*/
/** @noSelf **/
declare interface BlockReaderPeripheral extends IPeripheral {
/**
* Returns the registry name of the block (ex. minecraft:dirt).
*
* @returns The registry name of the block
*/
getBlockName(): string;
/**
* Returns the block data of the block if block is a tile entity.
*
* @returns The block data table if the block is a tile entity, otherwise nil
*/
getBlockData(): BlockDetailData | undefined;
/**
* Returns the properties of a block and its state.
*
* @returns The block states table if available, otherwise nil
*/
getBlockStates(): Record<string, unknown> | undefined;
/**
* Returns true whether the block is a tile entity or not.
*
* @returns Boolean indicating if the block is a tile entity, or nil if unable to determine
*/
isTileEntity(): boolean | undefined;
}

150
types/advanced-peripherals/chat-box.d.ts vendored Normal file
View File

@@ -0,0 +1,150 @@
/// <reference path="./shared.d.ts" />
/**
* Represents the Chat Box peripheral from Advanced Peripherals.
* Used to interact with Minecraft's chat system.
*
* @see https://minecraft.wiki/w/Text_component_format
* @see https://docs.advanced-peripherals.de/latest/peripherals/chat_box/
*/
/** @noSelf **/
declare interface ChatBoxPeripheral extends IPeripheral {
/**
* Broadcasts a message to the global chat or if range is specified it is sent to all players in the range.
* The prefix will change the text that appears inside the brackets at the start of a message. Defaults to "AP".
* To change the brackets used around the prefix you must specify a string like so: "[]", "()", "<>", ...
* bracketColor specifies the color to use for the brackets, this must be in the MOTD code format.
* If utf8Support is true: message, prefix, brackets, and bracketColor are all expected to be UTF8 encoded, using the utf8 standard library, unicode escape sequences, or similar.
*
* @param message The message to send
* @param prefix The prefix to display in brackets at the start of the message (defaults to "AP")
* @param brackets The bracket style around the prefix (e.g., "[]", "()", "<>")
* @param bracketColor The color for the brackets in MOTD code format
* @param range The range in blocks to send the message to players (if not global)
* @param utf8Support Whether to use UTF8 encoding for the message
* @returns true if the message is successfully sent, or nil and an error message if it fails
*/
sendMessage(
message: string,
prefix?: string,
brackets?: string,
bracketColor?: string,
range?: number,
utf8Support?: boolean,
): LuaMultiReturn<[boolean, string | undefined]>;
/**
* Similar to sendMessage() this sends a message to one specific player. Specify the player to send the message to with the username parameter.
*
* @param message The message to send
* @param username The username of the player to send the message to
* @param prefix The prefix to display in brackets at the start of the message (defaults to "AP")
* @param brackets The bracket style around the prefix (e.g., "[]", "()", "<>")
* @param bracketColor The color for the brackets in MOTD code format
* @param range The range in blocks to send the message to players (if not global)
* @param utf8Support Whether to use UTF8 encoding for the message
* @returns true if the message is successfully sent, or nil and an error message if it fails
*/
sendMessageToPlayer(
message: string,
username: string,
prefix?: string,
brackets?: string,
bracketColor?: string,
range?: number,
utf8Support?: boolean,
): LuaMultiReturn<[boolean, string | undefined]>;
/**
* Sends a toast to the specified player. The design of the toast is the classic notification design.
* It's planned to add a custom rendered design in the future.
*
* @param message The message for the toast
* @param title The title of the toast
* @param username The username of the player to send the toast to
* @param prefix The prefix to display in brackets at the start of the message (defaults to "AP")
* @param brackets The bracket style around the prefix (e.g., "[]", "()", "<>")
* @param bracketColor The color for the brackets in MOTD code format
* @param range The range in blocks to send the message to players (if not global)
* @param utf8Support Whether to use UTF8 encoding for the message
* @returns true if the toast is successfully sent, or nil and an error message if it fails
*/
sendToastToPlayer(
message: string,
title: string,
username: string,
prefix?: string,
brackets?: string,
bracketColor?: string,
range?: number,
utf8Support?: boolean,
): LuaMultiReturn<[boolean, string | undefined]>;
/**
* This function is fundamentally the same as sendMessage() except it takes a Minecraft text component as the first parameter.
* Find out more information on how the text component format works on the minecraft wiki. You can generate the json at minecraft.tools.
*
* @param json The Minecraft text component to send (as JSON string)
* @param prefix The prefix to display in brackets at the start of the message (defaults to "AP")
* @param brackets The bracket style around the prefix (e.g., "[]", "()", "<>")
* @param bracketColor The color for the brackets in MOTD code format
* @param range The range in blocks to send the message to players (if not global)
* @param utf8Support Whether to use UTF8 encoding for the message
* @returns true if the message is successfully sent, or nil and an error message if it fails
*/
sendFormattedMessage(
json: string,
prefix?: string,
brackets?: string,
bracketColor?: string,
range?: number,
utf8Support?: boolean,
): LuaMultiReturn<[boolean, string | undefined]>;
/**
* Similar to sendFormattedMessage() this sends a formatted message to one specific player. Specify the player to send the message to with the username parameter.
*
* @param json The Minecraft text component to send (as JSON string)
* @param username The username of the player to send the message to
* @param prefix The prefix to display in brackets at the start of the message (defaults to "AP")
* @param brackets The bracket style around the prefix (e.g., "[]", "()", "<>")
* @param bracketColor The color for the brackets in MOTD code format
* @param range The range in blocks to send the message to players (if not global)
* @param utf8Support Whether to use UTF8 encoding for the message
* @returns true if the message is successfully sent, or nil and an error message if it fails
*/
sendFormattedMessageToPlayer(
json: string,
username: string,
prefix?: string,
brackets?: string,
bracketColor?: string,
range?: number,
utf8Support?: boolean,
): LuaMultiReturn<[boolean, string | undefined]>;
/**
* This function is fundamentally the same as sendToast() except it takes a Minecraft text component as the first and second parameter.
* Find out more information on how the text component format works on the minecraft wiki. You can generate the json at minecraft.tools.
*
* @param messageJson The Minecraft text component for the message (as JSON string)
* @param titleJson The Minecraft text component for the title (as JSON string)
* @param username The username of the player to send the toast to
* @param prefix The prefix to display in brackets at the start of the message (defaults to "AP")
* @param brackets The bracket style around the prefix (e.g., "[]", "()", "<>")
* @param bracketColor The color for the brackets in MOTD code format
* @param range The range in blocks to send the message to players (if not global)
* @param utf8Support Whether to use UTF8 encoding for the message
* @returns true if the toast is successfully sent, or nil and an error message if it fails
*/
sendFormattedToastToPlayer(
messageJson: string,
titleJson: string,
username: string,
prefix?: string,
brackets?: string,
bracketColor?: string,
range?: number,
utf8Support?: boolean,
): LuaMultiReturn<[boolean, string | undefined]>;
}

View File

@@ -1,14 +1,3 @@
declare interface BlockItemDetailData {
id: string;
tag: object;
Count: number;
Slot: number;
}
declare interface BlockDetailData {
Items: Record<number, BlockItemDetailData>;
}
declare class BlockReaderPeripheral {
getBlockData(): BlockDetailData;
}
/// <reference path="./block-reader.d.ts" />
/// <reference path="./chat-box.d.ts" />
/// <reference path="./player-detector.d.ts" />

View File

@@ -4,7 +4,7 @@
"description": "TypeScript type definitions for base Advanced Peripherals APIs.",
"types": "./index.d.ts",
"files": [
"./index.d.ts"
"./*.d.ts"
],
"author": "SikongJueluo",
"license": "MIT"

View File

@@ -0,0 +1,276 @@
/**
* Represents the Player Detector peripheral from Advanced Peripherals.
* Used to detect and track players in the world.
*
* @see https://docs.advanced-peripherals.de/0.7/peripherals/player_detector/
*/
/** @noSelf **/
declare interface PlayerDetectorPeripheral extends IPeripheral {
/**
* Returns information about the player with the specified username.
*
* @param username The player's username to look up
* @returns A table containing player information, or nil if the player is not found
*/
getPlayerPos(username: string): PlayerInfo | undefined;
/**
* Returns information about the player with the specified username.
* Alternative name for getPlayerPos.
*
* @param username The player's username to look up
* @returns A table containing player information, or nil if the player is not found
*/
getPlayer(username: string): PlayerInfo | undefined;
/**
* Returns a list of all online players.
*
* @returns Table containing all online players as an array of usernames
*/
getOnlinePlayers(): string[];
/**
* Returns a list of players within the given range of the peripheral.
*
* @param range The range to search for players
* @returns Array containing usernames of players within range
*/
getPlayersInRange(range: number): string[];
/**
* Returns a list of players within the 2 positions posOne and posTwo.
*
* @param posOne Position with x, y, z coordinates
* @param posTwo Position with x, y, z coordinates
* @returns Array containing usernames of players within the specified coordinates
*/
getPlayersInCoords(posOne: Coordinate, posTwo: Coordinate): string[];
/**
* Returns a list of players within a cuboid centered at the peripheral.
*
* @param w Width of the cuboid (x-axis)
* @param h Height of the cuboid (y-axis)
* @param d Depth of the cuboid (z-axis)
* @returns Array containing usernames of players within the specified cuboid
*/
getPlayersInCubic(w: number, h: number, d: number): string[];
/**
* Returns true if the player whose username matches the provided username is within the given range of the peripheral.
*
* @param range The range to check
* @param username The player's username to check
* @returns Boolean indicating if the player is in range
*/
isPlayerInRange(range: number, username: string): boolean;
/**
* Returns true if the player is within the 2 positions.
*
* @param posOne Position with x, y, z coordinates
* @param posTwo Position with x, y, z coordinates
* @param username The player's username to check
* @returns Boolean indicating if the player is in the specified coordinates
*/
isPlayerInCoords(
posOne: Coordinate,
posTwo: Coordinate,
username: string,
): boolean;
/**
* Returns true if the player is within the cuboid centered at the peripheral.
*
* @param w Width of the cuboid (x-axis)
* @param h Height of the cuboid (y-axis)
* @param d Depth of the cuboid (z-axis)
* @param username The player's username to check
* @returns Boolean indicating if the player is in the specified cuboid
*/
isPlayerInCubic(w: number, h: number, d: number, username: string): boolean;
/**
* Returns true if there is any player in the given range.
*
* @param range The range to check
* @returns Boolean indicating if any player is in range
*/
isPlayersInRange(range: number): boolean;
/**
* Returns true if any player is within the 2 positions.
*
* @param posOne Position with x, y, z coordinates
* @param posTwo Position with x, y, z coordinates
* @returns Boolean indicating if any player is in the specified coordinates
*/
isPlayersInCoords(posOne: Coordinate, posTwo: Coordinate): boolean;
/**
* Returns true if any player is within the cuboid centered at the peripheral.
*
* @param w Width of the cuboid (x-axis)
* @param h Height of the cuboid (y-axis)
* @param d Depth of the cuboid (z-axis)
* @returns Boolean indicating if any player is in the specified cuboid
*/
isPlayersInCubic(w: number, h: number, d: number): boolean;
}
/**
* Represents a coordinate in 3D space.
*/
declare interface Coordinate {
/**
* The x coordinate.
*/
x: number;
/**
* The y coordinate.
*/
y: number;
/**
* The z coordinate.
*/
z: number;
}
/**
* Contains detailed information about a player.
*/
declare interface PlayerInfo {
/**
* The dimension the player is in.
*/
dimension: string;
/**
* The height of the player's eyes.
*/
eyeHeight: number;
/**
* The pitch of the player's head.
*/
pitch: number;
/**
* The health of the player.
*/
health: number;
/**
* The max health of the player.
*/
maxHealth: number;
/**
* The air supply of the player.
*/
airSupply: number;
/**
* The respawn position of the player.
*/
respawnPosition: number;
/**
* The respawn dimension of the player.
*/
respawnDimension: number;
/**
* The respawn angle of the player in degrees.
*/
respawnAngle: number;
/**
* The yaw of the player's head.
*/
yaw: number;
/**
* The x coordinate.
*/
x: number;
/**
* The y coordinate.
*/
y: number;
/**
* The z coordinate.
*/
z: number;
}
/**
* Player click event type for the Player Detector peripheral.
* Fires when a player clicks on the block.
*/
declare interface PlayerClickEvent {
/**
* The name of the event.
*/
event: "playerClick";
/**
* The username of the player who clicked the block.
*/
username: string;
/**
* The name of the peripheral like playerDetector_4.
*/
devicename: string;
}
/**
* Player join event type for the Player Detector peripheral.
* Fires when a player joins the world/a server.
*/
declare interface PlayerJoinEvent {
/**
* The name of the event.
*/
event: "playerJoin";
/**
* The username of the player who joined.
*/
username: string;
/**
* The resource id of the dimension the player is in.
*/
dimension: string;
}
/**
* Player leave event type for the Player Detector peripheral.
* Fires when a player leaves the world/a server.
*/
declare interface PlayerLeaveEvent {
/**
* The name of the event.
*/
event: "playerLeave";
/**
* The username of the player who left.
*/
username: string;
/**
* The resource id of the dimension the player was in.
*/
dimension: string;
}
/**
* Player changed dimension event type for the Player Detector peripheral.
* Fires when a player changes dimensions.
*/
declare interface PlayerChangedDimensionEvent {
/**
* The name of the event.
*/
event: "playerChangedDimension";
/**
* The username of the player who changed dimensions.
*/
username: string;
/**
* The resource id of the dimension the player was in.
*/
fromDim: string;
/**
* The resource id of the dimension the player is in.
*/
toDim: string;
}

111
types/advanced-peripherals/shared.d.ts vendored Normal file
View File

@@ -0,0 +1,111 @@
declare interface BlockItemDetailData {
id: string;
tag: object;
Count: number;
Slot: number;
}
declare interface BlockDetailData {
Items: Record<number, BlockItemDetailData>;
}
/**
* Minecraft Text Component format
* @see https://minecraft.wiki/w/Text_component_format
*/
declare type MinecraftColor =
| "black"
| "dark_blue"
| "dark_green"
| "dark_aqua"
| "dark_red"
| "dark_purple"
| "gold"
| "gray"
| "dark_gray"
| "blue"
| "green"
| "aqua"
| "red"
| "light_purple"
| "yellow"
| "white"
| "reset"; // RGB color in #RRGGBB format
declare type MinecraftFont =
| "minecraft:default"
| "minecraft:uniform"
| "minecraft:alt";
declare type ClickEventAction =
| "open_url"
| "open_file"
| "run_command"
| "suggest_command"
| "change_page"
| "copy_to_clipboard";
declare type HoverEventAction = "show_text" | "show_item" | "show_entity";
declare interface ClickEvent {
action: ClickEventAction;
value: string | number;
}
declare interface HoverEvent {
action: HoverEventAction;
contents?: unknown;
value?: unknown;
}
declare interface BaseTextComponent {
type?: "text" | "translatable" | "score" | "selector" | "keybind" | "nbt";
text?: string;
translate?: string;
with?: (MinecraftTextComponent | string)[];
score?: {
name: string;
objective: string;
value?: string;
};
selector?: string;
keybind?: string;
nbt?: string;
interpret?: boolean;
separator?: MinecraftTextComponent;
block?: string;
entity?: string;
storage?: string;
// Formatting
color?: MinecraftColor;
font?: MinecraftFont;
bold?: boolean;
italic?: boolean;
underlined?: boolean;
strikethrough?: boolean;
obfuscated?: boolean;
insertion?: string;
clickEvent?: ClickEvent;
hoverEvent?: HoverEvent;
shadow_color?: number;
// Nested components
extra?: MinecraftTextComponent[];
}
declare interface TextTextComponent extends BaseTextComponent {
type?: "text";
text: string;
}
declare interface TranslatableTextComponent extends BaseTextComponent {
type: "translatable";
translate: string;
with?: (MinecraftTextComponent | string)[];
}
declare type MinecraftTextComponent =
| TextTextComponent
| TranslatableTextComponent
| BaseTextComponent;

View File

@@ -477,7 +477,7 @@ declare namespace parallel {
}
/** @noSelf */
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
interface IPeripheral {}
declare interface IPeripheral {}
/** @noSelf */
declare class CommandPeripheral implements IPeripheral {

130
types/dkjson/index.d.ts vendored Normal file
View File

@@ -0,0 +1,130 @@
/** @noResolution */
/**
* Represents a JSON null value that is distinct from Lua's nil.
*/
interface JsonNull {
readonly __json_null: unique symbol;
}
/**
* State object for JSON encoding options
*/
interface EncodeState {
/** When set, the output will contain newlines and indentations */
indent?: boolean;
/** Specifies ordering of keys in encoded output */
keyorder?: string[];
/** Initial indentation level when indent is set (2 spaces per level, default is 0) */
level?: number;
/** Array to store result strings for concatenation at once */
buffer?: string[];
/** Index of last element in buffer (when set) */
bufferlen?: number;
/** Used to detect reference cycles (temporary, created when absent) */
tables?: unknown[];
/** Called when encoder cannot encode a value */
exception?: (
reason: string,
value: unknown,
state: EncodeState,
defaultmessage: string,
) => unknown;
}
/**
* Options for JSON decoding with custom metatables
*/
interface DecodeOptions {
objectmeta?: unknown;
arraymeta?: unknown;
}
/**
* The dkjson module version string
*/
export const version: string;
/**
* Special value representing JSON null (distinct from Lua's nil)
*/
export const jsonNull: JsonNull;
/**
* Encode a Lua value to a JSON string.
* @param object The value to encode (can be a table, string, number, boolean, nil, json.null or any object with a __tojson function in its metatable)
* @param state Optional table with configuration options for encoding
* @returns A string containing the JSON representation, or true if state.buffer is set and encoding was successful
*/
export function encode(
object: object | string | number | boolean | undefined,
state?: EncodeState,
): string | boolean;
/**
* Decode a JSON string starting at a given position.
* @param str The JSON string to decode
* @param pos Starting position (default is 1)
* @param nullval Value to return for null values (default is nil, can be set to json.null)
* @param objectmeta Custom metatable for decoded objects (optional)
* @param arraymeta Custom metatable for decoded arrays (optional)
* @returns The decoded object (or the custom null value) and position of next character not part of the object, or undefined, position, and error message in case of errors
*/
export function decode(
str: string,
pos?: number,
nullval?: unknown,
objectmeta?: unknown,
arraymeta?: unknown,
): LuaMultiReturn<[object | undefined, number | undefined, string | undefined]>;
/**
* Quote a UTF-8 string and escape critical characters using JSON escape sequences.
* Only necessary when building custom __tojson functions.
* @param str The string to quote and escape
* @returns The quoted and escaped string
*/
export function quotestring(str: string): string;
/**
* When state.indent is set, adds a newline to state.buffer and spaces according to state.level.
* @param state The encoding state containing indent and level information
*/
export function addnewline(state: EncodeState): void;
/**
* Function that can be used as the exception option in encode. Instead of raising an error,
* this function encodes the error message as a string for debugging malformed input.
* @param reason The reason for the exception
* @param value The value that caused the exception
* @param state The encoding state
* @param defaultmessage The default error message
* @returns The encoded error message
*/
export function encodeexception(
reason: string,
value: unknown,
state: EncodeState,
defaultmessage: string,
): string;
/**
* Require the LPeg module and return a copy of the module table where the decode function
* is replaced by a version that uses LPeg for better performance.
* @returns A copy of the module with LPeg-optimized decode function
*/
export function use_lpeg(): {
version: string;
null: JsonNull;
encode: typeof encode;
decode: typeof decode;
quotestring: typeof quotestring;
addnewline: typeof addnewline;
encodeexception: typeof encodeexception;
use_lpeg: typeof use_lpeg;
using_lpeg: boolean;
};
/**
* Variable set to true in the module table copy that uses LPeg support.
*/
export const using_lpeg: boolean;

762
types/dkjson/index.lua Normal file
View File

@@ -0,0 +1,762 @@
-- Module options:
local always_use_lpeg = false
local register_global_module_table = false
local global_module_name = 'json'
--[==[
David Kolf's JSON module for Lua 5.1 - 5.4
Version 2.8
For the documentation see the corresponding readme.txt or visit
<http://dkolf.de/dkjson-lua/>.
You can contact the author by sending an e-mail to 'david' at the
domain 'dkolf.de'.
Copyright (C) 2010-2024 David Heiko Kolf
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
--]==]
-- global dependencies:
local pairs, type, tostring, tonumber, getmetatable, setmetatable =
pairs, type, tostring, tonumber, getmetatable, setmetatable
local error, require, pcall, select = error, require, pcall, select
local floor, huge = math.floor, math.huge
local strrep, gsub, strsub, strbyte, strchar, strfind, strlen, strformat =
string.rep, string.gsub, string.sub, string.byte, string.char,
string.find, string.len, string.format
local strmatch = string.match
local concat = table.concat
local json = { version = "dkjson 2.8" }
local jsonlpeg = {}
if register_global_module_table then
if always_use_lpeg then
_G[global_module_name] = jsonlpeg
else
_G[global_module_name] = json
end
end
local _ENV = nil -- blocking globals in Lua 5.2 and later
pcall(function()
-- Enable access to blocked metatables.
-- Don't worry, this module doesn't change anything in them.
local debmeta = require "debug".getmetatable
if debmeta then getmetatable = debmeta end
end)
json.null = setmetatable({}, {
__tojson = function() return "null" end
})
local function isarray(tbl)
local max, n, arraylen = 0, 0, 0
for k, v in pairs(tbl) do
if k == 'n' and type(v) == 'number' then
arraylen = v
if v > max then
max = v
end
else
if type(k) ~= 'number' or k < 1 or floor(k) ~= k then
return false
end
if k > max then
max = k
end
n = n + 1
end
end
if max > 10 and max > arraylen and max > n * 2 then
return false -- don't create an array with too many holes
end
return true, max
end
local escapecodes = {
["\""] = "\\\"",
["\\"] = "\\\\",
["\b"] = "\\b",
["\f"] = "\\f",
["\n"] = "\\n",
["\r"] = "\\r",
["\t"] = "\\t"
}
local function escapeutf8(uchar)
local value = escapecodes[uchar]
if value then
return value
end
local a, b, c, d = strbyte(uchar, 1, 4)
a, b, c, d = a or 0, b or 0, c or 0, d or 0
if a <= 0x7f then
value = a
elseif 0xc0 <= a and a <= 0xdf and b >= 0x80 then
value = (a - 0xc0) * 0x40 + b - 0x80
elseif 0xe0 <= a and a <= 0xef and b >= 0x80 and c >= 0x80 then
value = ((a - 0xe0) * 0x40 + b - 0x80) * 0x40 + c - 0x80
elseif 0xf0 <= a and a <= 0xf7 and b >= 0x80 and c >= 0x80 and d >= 0x80 then
value = (((a - 0xf0) * 0x40 + b - 0x80) * 0x40 + c - 0x80) * 0x40 + d - 0x80
else
return ""
end
if value <= 0xffff then
return strformat("\\u%.4x", value)
elseif value <= 0x10ffff then
-- encode as UTF-16 surrogate pair
value = value - 0x10000
local highsur, lowsur = 0xD800 + floor(value / 0x400), 0xDC00 + (value % 0x400)
return strformat("\\u%.4x\\u%.4x", highsur, lowsur)
else
return ""
end
end
local function fsub(str, pattern, repl)
-- gsub always builds a new string in a buffer, even when no match
-- exists. First using find should be more efficient when most strings
-- don't contain the pattern.
if strfind(str, pattern) then
return gsub(str, pattern, repl)
else
return str
end
end
local function quotestring(value)
-- based on the regexp "escapable" in https://github.com/douglascrockford/JSON-js
value = fsub(value, "[%z\1-\31\"\\\127]", escapeutf8)
if strfind(value, "[\194\216\220\225\226\239]") then
value = fsub(value, "\194[\128-\159\173]", escapeutf8)
value = fsub(value, "\216[\128-\132]", escapeutf8)
value = fsub(value, "\220\143", escapeutf8)
value = fsub(value, "\225\158[\180\181]", escapeutf8)
value = fsub(value, "\226\128[\140-\143\168-\175]", escapeutf8)
value = fsub(value, "\226\129[\160-\175]", escapeutf8)
value = fsub(value, "\239\187\191", escapeutf8)
value = fsub(value, "\239\191[\176-\191]", escapeutf8)
end
return "\"" .. value .. "\""
end
json.quotestring = quotestring
local function replace(str, o, n)
local i, j = strfind(str, o, 1, true)
if i then
return strsub(str, 1, i - 1) .. n .. strsub(str, j + 1, -1)
else
return str
end
end
-- locale independent num2str and str2num functions
local decpoint, numfilter
local function updatedecpoint()
decpoint = strmatch(tostring(0.5), "([^05+])")
-- build a filter that can be used to remove group separators
numfilter = "[^0-9%-%+eE" .. gsub(decpoint, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0") .. "]+"
end
updatedecpoint()
local function num2str(num)
return replace(fsub(tostring(num), numfilter, ""), decpoint, ".")
end
local function str2num(str)
local num = tonumber(replace(str, ".", decpoint))
if not num then
updatedecpoint()
num = tonumber(replace(str, ".", decpoint))
end
return num
end
local function addnewline2(level, buffer, buflen)
buffer[buflen + 1] = "\n"
buffer[buflen + 2] = strrep(" ", level)
buflen = buflen + 2
return buflen
end
function json.addnewline(state)
if state.indent then
state.bufferlen = addnewline2(state.level or 0,
state.buffer, state.bufferlen or #(state.buffer))
end
end
local encode2 -- forward declaration
local function addpair(key, value, prev, indent, level, buffer, buflen, tables, globalorder, state)
local kt = type(key)
if kt ~= 'string' and kt ~= 'number' then
return nil, "type '" .. kt .. "' is not supported as a key by JSON."
end
if prev then
buflen = buflen + 1
buffer[buflen] = ","
end
if indent then
buflen = addnewline2(level, buffer, buflen)
end
-- When Lua is compiled with LUA_NOCVTN2S this will fail when
-- numbers are mixed into the keys of the table. JSON keys are always
-- strings, so this would be an implicit conversion too and the failure
-- is intentional.
buffer[buflen + 1] = quotestring(key)
buffer[buflen + 2] = ":"
return encode2(value, indent, level, buffer, buflen + 2, tables, globalorder, state)
end
local function appendcustom(res, buffer, state)
local buflen = state.bufferlen
if type(res) == 'string' then
buflen = buflen + 1
buffer[buflen] = res
end
return buflen
end
local function exception(reason, value, state, buffer, buflen, defaultmessage)
defaultmessage = defaultmessage or reason
local handler = state.exception
if not handler then
return nil, defaultmessage
else
state.bufferlen = buflen
local ret, msg = handler(reason, value, state, defaultmessage)
if not ret then return nil, msg or defaultmessage end
return appendcustom(ret, buffer, state)
end
end
function json.encodeexception(reason, value, state, defaultmessage)
return quotestring("<" .. defaultmessage .. ">")
end
encode2 = function(value, indent, level, buffer, buflen, tables, globalorder, state)
local valtype = type(value)
local valmeta = getmetatable(value)
valmeta = type(valmeta) == 'table' and valmeta -- only tables
local valtojson = valmeta and valmeta.__tojson
if valtojson then
if tables[value] then
return exception('reference cycle', value, state, buffer, buflen)
end
tables[value] = true
state.bufferlen = buflen
local ret, msg = valtojson(value, state)
if not ret then return exception('custom encoder failed', value, state, buffer, buflen, msg) end
tables[value] = nil
buflen = appendcustom(ret, buffer, state)
elseif value == nil then
buflen = buflen + 1
buffer[buflen] = "null"
elseif valtype == 'number' then
local s
if value ~= value or value >= huge or -value >= huge then
-- This is the behaviour of the original JSON implementation.
s = "null"
else
s = num2str(value)
end
buflen = buflen + 1
buffer[buflen] = s
elseif valtype == 'boolean' then
buflen = buflen + 1
buffer[buflen] = value and "true" or "false"
elseif valtype == 'string' then
buflen = buflen + 1
buffer[buflen] = quotestring(value)
elseif valtype == 'table' then
if tables[value] then
return exception('reference cycle', value, state, buffer, buflen)
end
tables[value] = true
level = level + 1
local isa, n = isarray(value)
if n == 0 and valmeta and valmeta.__jsontype == 'object' then
isa = false
end
local msg
if isa then -- JSON array
buflen = buflen + 1
buffer[buflen] = "["
for i = 1, n do
buflen, msg = encode2(value[i], indent, level, buffer, buflen, tables, globalorder, state)
if not buflen then return nil, msg end
if i < n then
buflen = buflen + 1
buffer[buflen] = ","
end
end
buflen = buflen + 1
buffer[buflen] = "]"
else -- JSON object
local prev = false
buflen = buflen + 1
buffer[buflen] = "{"
local order = valmeta and valmeta.__jsonorder or globalorder
if order then
local used = {}
n = #order
for i = 1, n do
local k = order[i]
local v = value[k]
if v ~= nil then
used[k] = true
buflen, msg = addpair(k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
if not buflen then return nil, msg end
prev = true -- add a seperator before the next element
end
end
for k, v in pairs(value) do
if not used[k] then
buflen, msg = addpair(k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
if not buflen then return nil, msg end
prev = true -- add a seperator before the next element
end
end
else -- unordered
for k, v in pairs(value) do
buflen, msg = addpair(k, v, prev, indent, level, buffer, buflen, tables, globalorder, state)
if not buflen then return nil, msg end
prev = true -- add a seperator before the next element
end
end
if indent then
buflen = addnewline2(level - 1, buffer, buflen)
end
buflen = buflen + 1
buffer[buflen] = "}"
end
tables[value] = nil
else
return exception('unsupported type', value, state, buffer, buflen,
"type '" .. valtype .. "' is not supported by JSON.")
end
return buflen
end
function json.encode(value, state)
state = state or {}
local oldbuffer = state.buffer
local buffer = oldbuffer or {}
state.buffer = buffer
updatedecpoint()
local ret, msg = encode2(value, state.indent, state.level or 0,
buffer, state.bufferlen or 0, state.tables or {}, state.keyorder, state)
if not ret then
error(msg, 2)
elseif oldbuffer == buffer then
state.bufferlen = ret
return true
else
state.bufferlen = nil
state.buffer = nil
return concat(buffer)
end
end
local function loc(str, where)
local line, pos, linepos = 1, 1, 0
while true do
pos = strfind(str, "\n", pos, true)
if pos and pos < where then
line = line + 1
linepos = pos
pos = pos + 1
else
break
end
end
return strformat("line %d, column %d", line, where - linepos)
end
local function unterminated(str, what, where)
return nil, strlen(str) + 1, "unterminated " .. what .. " at " .. loc(str, where)
end
local function scanwhite(str, pos)
while true do
pos = strfind(str, "%S", pos)
if not pos then return nil end
local sub2 = strsub(str, pos, pos + 1)
if sub2 == "\239\187" and strsub(str, pos + 2, pos + 2) == "\191" then
-- UTF-8 Byte Order Mark
pos = pos + 3
elseif sub2 == "//" then
pos = strfind(str, "[\n\r]", pos + 2)
if not pos then return nil end
elseif sub2 == "/*" then
pos = strfind(str, "*/", pos + 2)
if not pos then return nil end
pos = pos + 2
else
return pos
end
end
end
local escapechars = {
["\""] = "\"",
["\\"] = "\\",
["/"] = "/",
["b"] = "\b",
["f"] = "\f",
["n"] = "\n",
["r"] = "\r",
["t"] = "\t"
}
local function unichar(value)
if value < 0 then
return nil
elseif value <= 0x007f then
return strchar(value)
elseif value <= 0x07ff then
return strchar(0xc0 + floor(value / 0x40),
0x80 + (floor(value) % 0x40))
elseif value <= 0xffff then
return strchar(0xe0 + floor(value / 0x1000),
0x80 + (floor(value / 0x40) % 0x40),
0x80 + (floor(value) % 0x40))
elseif value <= 0x10ffff then
return strchar(0xf0 + floor(value / 0x40000),
0x80 + (floor(value / 0x1000) % 0x40),
0x80 + (floor(value / 0x40) % 0x40),
0x80 + (floor(value) % 0x40))
else
return nil
end
end
local function scanstring(str, pos)
local lastpos = pos + 1
local buffer, n = {}, 0
while true do
local nextpos = strfind(str, "[\"\\]", lastpos)
if not nextpos then
return unterminated(str, "string", pos)
end
if nextpos > lastpos then
n = n + 1
buffer[n] = strsub(str, lastpos, nextpos - 1)
end
if strsub(str, nextpos, nextpos) == "\"" then
lastpos = nextpos + 1
break
else
local escchar = strsub(str, nextpos + 1, nextpos + 1)
local value
if escchar == "u" then
value = tonumber(strsub(str, nextpos + 2, nextpos + 5), 16)
if value then
local value2
if 0xD800 <= value and value <= 0xDBff then
-- we have the high surrogate of UTF-16. Check if there is a
-- low surrogate escaped nearby to combine them.
if strsub(str, nextpos + 6, nextpos + 7) == "\\u" then
value2 = tonumber(strsub(str, nextpos + 8, nextpos + 11), 16)
if value2 and 0xDC00 <= value2 and value2 <= 0xDFFF then
value = (value - 0xD800) * 0x400 + (value2 - 0xDC00) + 0x10000
else
value2 = nil -- in case it was out of range for a low surrogate
end
end
end
value = value and unichar(value)
if value then
if value2 then
lastpos = nextpos + 12
else
lastpos = nextpos + 6
end
end
end
end
if not value then
value = escapechars[escchar] or escchar
lastpos = nextpos + 2
end
n = n + 1
buffer[n] = value
end
end
if n == 1 then
return buffer[1], lastpos
elseif n > 1 then
return concat(buffer), lastpos
else
return "", lastpos
end
end
local scanvalue -- forward declaration
local function scantable(what, closechar, str, startpos, nullval, objectmeta, arraymeta)
local tbl, n = {}, 0
local pos = startpos + 1
if what == 'object' then
setmetatable(tbl, objectmeta)
else
setmetatable(tbl, arraymeta)
end
while true do
pos = scanwhite(str, pos)
if not pos then return unterminated(str, what, startpos) end
local char = strsub(str, pos, pos)
if char == closechar then
return tbl, pos + 1
end
local val1, err
val1, pos, err = scanvalue(str, pos, nullval, objectmeta, arraymeta)
if err then return nil, pos, err end
pos = scanwhite(str, pos)
if not pos then return unterminated(str, what, startpos) end
char = strsub(str, pos, pos)
if char == ":" then
if val1 == nil then
return nil, pos, "cannot use nil as table index (at " .. loc(str, pos) .. ")"
end
pos = scanwhite(str, pos + 1)
if not pos then return unterminated(str, what, startpos) end
local val2
val2, pos, err = scanvalue(str, pos, nullval, objectmeta, arraymeta)
if err then return nil, pos, err end
tbl[val1] = val2
pos = scanwhite(str, pos)
if not pos then return unterminated(str, what, startpos) end
char = strsub(str, pos, pos)
else
n = n + 1
tbl[n] = val1
end
if char == "," then
pos = pos + 1
end
end
end
scanvalue = function(str, pos, nullval, objectmeta, arraymeta)
pos = pos or 1
pos = scanwhite(str, pos)
if not pos then
return nil, strlen(str) + 1, "no valid JSON value (reached the end)"
end
local char = strsub(str, pos, pos)
if char == "{" then
return scantable('object', "}", str, pos, nullval, objectmeta, arraymeta)
elseif char == "[" then
return scantable('array', "]", str, pos, nullval, objectmeta, arraymeta)
elseif char == "\"" then
return scanstring(str, pos)
else
local pstart, pend = strfind(str, "^%-?[%d%.]+[eE]?[%+%-]?%d*", pos)
if pstart then
local number = str2num(strsub(str, pstart, pend))
if number then
return number, pend + 1
end
end
pstart, pend = strfind(str, "^%a%w*", pos)
if pstart then
local name = strsub(str, pstart, pend)
if name == "true" then
return true, pend + 1
elseif name == "false" then
return false, pend + 1
elseif name == "null" then
return nullval, pend + 1
end
end
return nil, pos, "no valid JSON value at " .. loc(str, pos)
end
end
local function optionalmetatables(...)
if select("#", ...) > 0 then
return ...
else
return { __jsontype = 'object' }, { __jsontype = 'array' }
end
end
function json.decode(str, pos, nullval, ...)
local objectmeta, arraymeta = optionalmetatables(...)
return scanvalue(str, pos, nullval, objectmeta, arraymeta)
end
-- function json.use_lpeg ()
-- local g = require ("lpeg")
-- if type(g.version) == 'function' and g.version() == "0.11" then
-- error "due to a bug in LPeg 0.11, it cannot be used for JSON matching"
-- end
-- local pegmatch = g.match
-- local P, S, R = g.P, g.S, g.R
-- local function ErrorCall (str, pos, msg, state)
-- if not state.msg then
-- state.msg = msg .. " at " .. loc (str, pos)
-- state.pos = pos
-- end
-- return false
-- end
-- local function Err (msg)
-- return g.Cmt (g.Cc (msg) * g.Carg (2), ErrorCall)
-- end
-- local function ErrorUnterminatedCall (str, pos, what, state)
-- return ErrorCall (str, pos - 1, "unterminated " .. what, state)
-- end
-- local SingleLineComment = P"//" * (1 - S"\n\r")^0
-- local MultiLineComment = P"/*" * (1 - P"*/")^0 * P"*/"
-- local Space = (S" \n\r\t" + P"\239\187\191" + SingleLineComment + MultiLineComment)^0
-- local function ErrUnterminated (what)
-- return g.Cmt (g.Cc (what) * g.Carg (2), ErrorUnterminatedCall)
-- end
-- local PlainChar = 1 - S"\"\\\n\r"
-- local EscapeSequence = (P"\\" * g.C (S"\"\\/bfnrt" + Err "unsupported escape sequence")) / escapechars
-- local HexDigit = R("09", "af", "AF")
-- local function UTF16Surrogate (match, pos, high, low)
-- high, low = tonumber (high, 16), tonumber (low, 16)
-- if 0xD800 <= high and high <= 0xDBff and 0xDC00 <= low and low <= 0xDFFF then
-- return true, unichar ((high - 0xD800) * 0x400 + (low - 0xDC00) + 0x10000)
-- else
-- return false
-- end
-- end
-- local function UTF16BMP (hex)
-- return unichar (tonumber (hex, 16))
-- end
-- local U16Sequence = (P"\\u" * g.C (HexDigit * HexDigit * HexDigit * HexDigit))
-- local UnicodeEscape = g.Cmt (U16Sequence * U16Sequence, UTF16Surrogate) + U16Sequence/UTF16BMP
-- local Char = UnicodeEscape + EscapeSequence + PlainChar
-- local String = P"\"" * (g.Cs (Char ^ 0) * P"\"" + ErrUnterminated "string")
-- local Integer = P"-"^(-1) * (P"0" + (R"19" * R"09"^0))
-- local Fractal = P"." * R"09"^0
-- local Exponent = (S"eE") * (S"+-")^(-1) * R"09"^1
-- local Number = (Integer * Fractal^(-1) * Exponent^(-1))/str2num
-- local Constant = P"true" * g.Cc (true) + P"false" * g.Cc (false) + P"null" * g.Carg (1)
-- local SimpleValue = Number + String + Constant
-- local ArrayContent, ObjectContent
-- -- The functions parsearray and parseobject parse only a single value/pair
-- -- at a time and store them directly to avoid hitting the LPeg limits.
-- local function parsearray (str, pos, nullval, state)
-- local obj, cont
-- local start = pos
-- local npos
-- local t, nt = {}, 0
-- repeat
-- obj, cont, npos = pegmatch (ArrayContent, str, pos, nullval, state)
-- if cont == 'end' then
-- return ErrorUnterminatedCall (str, start, "array", state)
-- end
-- pos = npos
-- if cont == 'cont' or cont == 'last' then
-- nt = nt + 1
-- t[nt] = obj
-- end
-- until cont ~= 'cont'
-- return pos, setmetatable (t, state.arraymeta)
-- end
-- local function parseobject (str, pos, nullval, state)
-- local obj, key, cont
-- local start = pos
-- local npos
-- local t = {}
-- repeat
-- key, obj, cont, npos = pegmatch (ObjectContent, str, pos, nullval, state)
-- if cont == 'end' then
-- return ErrorUnterminatedCall (str, start, "object", state)
-- end
-- pos = npos
-- if cont == 'cont' or cont == 'last' then
-- t[key] = obj
-- end
-- until cont ~= 'cont'
-- return pos, setmetatable (t, state.objectmeta)
-- end
-- local Array = P"[" * g.Cmt (g.Carg(1) * g.Carg(2), parsearray)
-- local Object = P"{" * g.Cmt (g.Carg(1) * g.Carg(2), parseobject)
-- local Value = Space * (Array + Object + SimpleValue)
-- local ExpectedValue = Value + Space * Err "value expected"
-- local ExpectedKey = String + Err "key expected"
-- local End = P(-1) * g.Cc'end'
-- local ErrInvalid = Err "invalid JSON"
-- ArrayContent = (Value * Space * (P"," * g.Cc'cont' + P"]" * g.Cc'last'+ End + ErrInvalid) + g.Cc(nil) * (P"]" * g.Cc'empty' + End + ErrInvalid)) * g.Cp()
-- local Pair = g.Cg (Space * ExpectedKey * Space * (P":" + Err "colon expected") * ExpectedValue)
-- ObjectContent = (g.Cc(nil) * g.Cc(nil) * P"}" * g.Cc'empty' + End + (Pair * Space * (P"," * g.Cc'cont' + P"}" * g.Cc'last' + End + ErrInvalid) + ErrInvalid)) * g.Cp()
-- local DecodeValue = ExpectedValue * g.Cp ()
-- jsonlpeg.version = json.version
-- jsonlpeg.encode = json.encode
-- jsonlpeg.null = json.null
-- jsonlpeg.quotestring = json.quotestring
-- jsonlpeg.addnewline = json.addnewline
-- jsonlpeg.encodeexception = json.encodeexception
-- jsonlpeg.using_lpeg = true
-- function jsonlpeg.decode (str, pos, nullval, ...)
-- local state = {}
-- state.objectmeta, state.arraymeta = optionalmetatables(...)
-- local obj, retpos = pegmatch (DecodeValue, str, pos, nullval, state)
-- if state.msg then
-- return nil, state.pos, state.msg
-- else
-- return obj, retpos
-- end
-- end
-- -- cache result of this function:
-- json.use_lpeg = function () return jsonlpeg end
-- jsonlpeg.use_lpeg = json.use_lpeg
-- return jsonlpeg
-- end
-- if always_use_lpeg then
-- return json.use_lpeg()
-- end
return json

12
types/dkjson/package.json Normal file
View File

@@ -0,0 +1,12 @@
{
"name": "@sikongjueluo/dkjson-types",
"version": "1.0.0",
"description": "TypeScript type definitions for library dkjson APIs.",
"types": "./index.d.ts",
"files": [
"./*.d.ts",
"./index.lua"
],
"author": "SikongJueluo",
"license": "MIT"
}

84
types/toml2lua/index.d.ts vendored Normal file
View File

@@ -0,0 +1,84 @@
/** @noResolution */
/**
* Represents a date-time value in TOML.
* The object can have various combinations of year, month, day, hour, min, sec, and zone properties.
*/
interface TomlDate {
year?: number;
month?: number;
day?: number;
hour?: number;
min?: number;
sec?: number;
zone?: number; // timezone offset
}
/**
* Options for TOML parsing
*/
interface ParseOptions {
/** Whether to follow the TOML spec strictly (default: true) */
strict?: boolean;
}
/** The current supported TOML version */
export const version: string;
/** Whether the parser should follow the TOML spec strictly */
export const strict: boolean;
/**
* Creates a date object with proper validation and string representation
* @param tab Date components to validate and create a date object from
* @returns Validated date object or nil with error message
*/
export function datefy(tab: TomlDate): TomlDate | [undefined, string];
/**
* Checks if a table is a date object
* @param tab The table to check
* @returns True if the table is a date object, false otherwise
*/
export function isdate(tab: unknown): boolean;
/**
* Creates a multi-step parser for streaming TOML data
* @param options Parsing options
* @returns A coroutine-based parser function that can be called with data chunks
* and then called without arguments to get the result
*/
export function multistep_parser(options?: ParseOptions): {
(data: string): void; // Provide data chunk
(): [any, string] | any; // Get final result (call without arguments)
};
/**
* Parses TOML data into a Lua table
* @param data The TOML string to parse
* @param options Parsing options
* @returns The parsed data as a Lua table, or [null, error_message] on failure
*/
export function parse(
data: string,
options?: ParseOptions,
): [undefined, string] | any;
/**
* Parse TOML and return values in toml-test intermediate format
* Useful for debugging or when you need explicit type information
* @param data The TOML string to parse
* @param options Parsing options
* @returns The parsed data in toml-test format, or [null, error_message] on failure
*/
export function parseToTestFormat(
data: string,
options?: ParseOptions,
): [undefined, string] | any;
/**
* Encodes a Lua table to TOML format
* @param tbl The Lua table to encode
* @returns The TOML string representation of the table
*/
export function encode(tbl: any): string;

1767
types/toml2lua/index.lua Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,12 @@
{
"name": "@sikongjueluo/toml2lua-types",
"version": "1.0.0",
"description": "TypeScript type definitions for library toml2lua APIs.",
"types": "./index.d.ts",
"files": [
"./*.d.ts",
"./index.lua"
],
"author": "SikongJueluo",
"license": "MIT"
}