docs: ccCLI framework and ChatManager

This commit is contained in:
2025-11-03 22:32:18 +08:00
parent 2f57d9ab3d
commit a4e74dcfa0
3 changed files with 494 additions and 1 deletions

View File

@@ -30,7 +30,17 @@ A declarative, reactive TUI (Terminal User Interface) framework inspired by [Sol
- **Control Flow:** Includes `<For>` and `<Show>` components for conditional and list-based rendering. - **Control Flow:** Includes `<For>` and `<Show>` components for conditional and list-based rendering.
- **Component-Based:** Structure your UI into reusable components. See `src/tuiExample/main.ts` for a demo. - **Component-Based:** Structure your UI into reusable components. See `src/tuiExample/main.ts` for a demo.
### 4. Core Libraries ### 4. ccCLI Framework
A lightweight, functional-style framework for building command-line interfaces (CLIs) within CC:Tweaked. It supports nested commands, arguments, options, and automatic help generation. See the [ccCLI Documentation](docs/ccCLI.md) for more details.
- **Declarative API:** Define commands, arguments, and options using a simple, object-based structure.
- **Nested Commands:** Organize complex applications with subcommands (e.g., `mycli command subcommand`).
- **Automatic Help:** Generates detailed help messages for commands and subcommands.
- **Global Context:** Inject shared state or services into command actions.
- **Type-Safe:** Built with TypeScript for robust development.
### 5. Core Libraries
- **`ChatManager`:** A powerful manager for `chatBox` peripherals that handles message queuing, cooldowns, and asynchronous sending/receiving. See the [ChatManager Documentation](docs/ChatManager.md) for more details.
- **`ccLog`:** A robust logging library with automatic, time-based log file rotation. - **`ccLog`:** A robust logging library with automatic, time-based log file rotation.
- **`PeripheralManager`:** A utility for easily finding and requiring peripherals by name or type. - **`PeripheralManager`:** A utility for easily finding and requiring peripherals by name or type.
- **`CraftManager`:** A library for parsing and executing crafting recipes from Create mod packages. - **`CraftManager`:** A library for parsing and executing crafting recipes from Create mod packages.

235
docs/ChatManager.md Normal file
View File

@@ -0,0 +1,235 @@
# ChatManager Documentation
## Introduction
`ChatManager` is a powerful utility for managing interactions with one or more `chatBox` peripherals in ComputerCraft. It simplifies the process of sending and receiving chat messages by handling complexities like peripheral cooldowns, message queuing, and asynchronous operations.
It is designed for applications that need to reliably send a high volume of messages or toasts without getting bogged down by peripheral limitations, or for applications that need to listen for commands or messages from players.
## Features
* **Multi-Peripheral Management:** Seamlessly manages one or more `chatBox` peripherals.
* **Message Queuing:** Automatically queues messages and toasts, sending them as chatboxes become available.
* **Cooldown Handling:** Respects the 1-second cooldown of chatboxes to prevent message loss.
* **Asynchronous Operation:** Can run in the background (`runAsync`) without blocking your main program loop.
* **Message Buffering:** Receives and buffers incoming chat messages for your application to process.
* **Queued and Immediate Sending:** Supports both adding messages to a queue and sending them immediately (if a chatbox is available).
* **Rich Content Support:** Send simple strings or complex formatted messages using `MinecraftTextComponent`.
* **Robust Error Handling:** Uses a `Result`-based API to make error handling explicit and reliable.
* **Comprehensive API:** Provides methods for sending global messages, private messages, and toast notifications.
## Tutorial: Getting Started with ChatManager
Heres how to integrate `ChatManager` into your project.
### 1. Initialization
First, find your available `chatBox` peripherals and create a `ChatManager` instance.
```typescript
import { ChatManager } from '@/lib/ChatManager';
// Find all available chatbox peripherals
const peripheralNames = peripheral.getNames();
const chatboxPeripherals: ChatBoxPeripheral[] = [];
for (const name of peripheralNames) {
const peripheralType = peripheral.getType(name);
if (peripheralType[0] === "chatBox") {
const chatbox = peripheral.wrap(name) as ChatBoxPeripheral;
chatboxPeripherals.push(chatbox);
}
}
if (chatboxPeripherals.length === 0) {
print("Error: No chatbox peripherals found.");
return;
}
// Create the manager instance
const chatManager = new ChatManager(chatboxPeripherals);
```
### 2. Running the Manager
To start the sending and receiving loops, you must run the manager. For most use cases, running it asynchronously is best.
```typescript
// Start ChatManager in the background so it doesn't block the main program
const runResult = chatManager.runAsync();
if (runResult.isErr()) {
print(`Warning: Failed to start ChatManager: ${runResult.error.reason}`);
} else {
print("ChatManager started successfully!");
}
// Your main program logic can continue here...
```
**Important:** `ChatManager` relies on `gTimerManager` to handle cooldowns. Ensure you are also running the global timer manager in your application.
```typescript
import { gTimerManager } from "@/lib/TimerManager";
// In your main parallel loop
parallel.waitForAll(
() => yourMainLoop(),
() => chatManager.run(), // if you choose the blocking run
() => gTimerManager.run()
);
```
### 3. Sending a Message (Queued)
Use `sendMessage` to add a message to the queue. `ChatManager` will send it as soon as a chatbox is free.
```typescript
// Send a global message
chatManager.sendMessage({
message: "Hello, world!",
prefix: "MySystem",
});
// Send a private message
chatManager.sendMessage({
message: "This is a secret.",
targetPlayer: "Steve",
prefix: "Whisper",
});
```
### 4. Sending a Toast (Queued)
Similarly, use `sendToast` to queue a toast notification.
```typescript
chatManager.sendToast({
title: "Server Alert",
message: "Restart in 5 minutes!",
targetPlayer: "Steve",
prefix: "Admin",
});
```
### 5. Receiving Messages
Use `getReceivedMessage` to pull incoming chat events from the buffer. It's best to do this in a loop.
```typescript
function myCliLoop() {
while (true) {
const result = chatManager.getReceivedMessage();
if (result.isOk()) {
const event = result.value;
print(`[${event.username}]: ${event.message}`);
// Process the command or message...
} else {
// Buffer is empty, wait a bit before checking again
sleep(0.5);
}
}
}
```
## Advanced Topics
### Immediate Sending
If you need to send a message right away and bypass the queue, use the `Immediate` methods. These will fail if no chatbox is currently available.
```typescript
const result = chatManager.sendMessageImmediate({
message: "URGENT!",
targetPlayer: "Admin",
});
if (result.isErr()) {
if (result.error.kind === "NoIdleChatbox") {
print("Could not send immediately: all chatboxes are busy.");
} else {
print(`Failed to send message: ${result.error.reason}`);
}
}
```
### Rich Text Messages (`MinecraftTextComponent`)
You can send fully formatted messages by providing a `MinecraftTextComponent` object instead of a string.
```typescript
const richMessage: MinecraftTextComponent = {
text: "This is ",
color: "gold",
extra: [
{ text: "important!", color: "red", bold: true }
],
};
chatManager.sendMessage({
message: richMessage,
targetPlayer: "AllPlayers",
utf8Support: true, // Recommended for complex components
});
```
### Error Handling
Methods return a `Result` object (`Ok` or `Err`). Always check the result to handle potential failures gracefully.
```typescript
const result = chatManager.sendMessage(message);
if (result.isErr()) {
logger.error(`Failed to queue message: ${result.error.reason}`);
}
```
The possible error `kind`s are:
* `ChatManagerError`: General errors, e.g., failure to enqueue.
* `NoIdleChatboxError`: Returned by `Immediate` methods when no chatbox is free.
* `SendFailureError`: A hardware or permission error occurred during sending.
* `EmptyBufferError`: Returned by `getReceivedMessage` when the buffer is empty.
### Status and Management
You can inspect and control the `ChatManager` at runtime.
```typescript
// Get the number of items waiting to be sent
const pending = chatManager.getPendingMessageCount();
print(`Messages in queue: ${pending}`);
// Get the number of received messages waiting to be processed
const buffered = chatManager.getBufferedMessageCount();
print(`Received messages in buffer: ${buffered}`);
// Get the status of each chatbox (true = idle, false = busy)
const statuses = chatManager.getChatboxStatus();
// Clear the sending queues
chatManager.clearQueues();
// Clear the received message buffer
chatManager.clearBuffer();
// Stop the manager's background loops
chatManager.stop();
```
## API Reference
### Core Class
* `ChatManager(peripherals: ChatBoxPeripheral[])`
### Primary Methods
* `run(): Result<void, ChatManagerError>`: Starts the manager (blocking).
* `runAsync(): Result<LuaThread, ChatManagerError>`: Starts the manager in the background.
* `stop(): Result<void, ChatManagerError>`: Stops the background loops.
* `sendMessage(message: ChatMessage): Result<void, ChatManagerError>`: Queues a chat message.
* `sendToast(toast: ChatToast): Result<void, ChatManagerError>`: Queues a toast.
* `getReceivedMessage(): Result<ChatBoxEvent, EmptyBufferError>`: Retrieves a message from the receive buffer.
* `sendMessageImmediate(message: ChatMessage): Result<void, ChatError>`: Sends a message immediately.
* `sendToastImmediate(toast: ChatToast): Result<void, ChatError>`: Sends a toast immediately.
### Interfaces
* `ChatMessage`
* `ChatToast`
* `ChatError` (union of all possible error types)

248
docs/ccCLI.md Normal file
View File

@@ -0,0 +1,248 @@
# ccCLI Framework Documentation
## Introduction
`ccCLI` is a lightweight, functional-style framework for building command-line interfaces (CLIs) within the CC:Tweaked environment using TSTL (TypeScriptToLua). It provides a declarative and type-safe way to define commands, arguments, and options, with built-in support for nested commands, automatic help generation, and robust error handling.
Its design is inspired by modern CLI libraries and emphasizes simplicity and ease of use, allowing developers to quickly structure complex command-based applications.
## Features
* **Declarative API:** Define commands as simple objects.
* **Type-Safe:** Leverage TypeScript for defining commands, arguments, options, and context.
* **Nested Commands:** Easily create command groups and subcommands (e.g., `git remote add`).
* **Automatic Help Generation:** Generates `--help` messages for the root command and all subcommands.
* **Flexible Argument & Option Parsing:** Supports long names (`--verbose`), short names (`-v`), value assignment (`--file=path.txt`), and boolean flags.
* **Global Context Injection:** Share state, services, or configuration across all commands.
* **Result-Based Error Handling:** Command actions return a `Result` type, ensuring that errors are handled explicitly.
* **No Dependencies:** Written in pure TypeScript with no external runtime dependencies.
## Core Concepts
The framework is built around a few key interfaces:
* `Command<TContext>`: The central piece. It defines a command's name, description, arguments, options, subcommands, and the action to perform.
* `Argument`: Defines a positional argument for a command. It can be marked as required.
* `Option`: Defines a named option (flag). It can have a long name, a short name, a default value, and be marked as required.
* `ActionContext<TContext>`: The object passed to every command's `action` function. It contains the parsed `args`, `options`, and the shared `context` object.
## Tutorial: Creating a Simple Calculator CLI
Let's build a simple calculator to see how `ccCLI` works.
### 1. Define the Global Context (Optional)
The global context is a powerful feature for sharing data or services. Let's define a context for our app.
```typescript
// src/cliExample/main.ts
interface AppContext {
appName: string;
log: (message: string) => void;
debugMode: boolean;
}
```
### 2. Define Commands
Commands are just JavaScript objects. The logic goes into the `action` function.
```typescript
// src/cliExample/main.ts
import { Command, CliError } from "../lib/ccCLI/index";
import { Ok, Result } from "../lib/thirdparty/ts-result-es";
const addCommand: Command<AppContext> = {
name: "add",
description: "Adds two numbers together",
args: [
{ name: "a", description: "The first number", required: true },
{ name: "b", description: "The second number", required: true },
],
action: ({ args, context }): Result<void, CliError> => {
context.log(`Executing 'add' command in '${context.appName}'`);
const a = tonumber(args.a as string);
const b = tonumber(args.b as string);
if (a === undefined || b === undefined) {
print("Error: Arguments must be numbers.");
return Ok.EMPTY;
}
const result = a + b;
print(`${a} + ${b} = ${result}`);
return Ok.EMPTY;
},
};
```
### 3. Create Nested Commands
You can group commands under a parent command using the `subcommands` property.
```typescript
// src/cliExample/main.ts
// (addCommand is defined above, subtractCommand would be similar)
const mathCommand: Command<AppContext> = {
name: "math",
description: "Mathematical operations",
subcommands: new Map([
["add", addCommand],
["subtract", subtractCommand], // Assuming subtractCommand is defined
]),
};
```
If a command with subcommands is called without an action, it will automatically display its help page.
### 4. Define the Root Command
The root command is the entry point for your entire application. It contains all top-level commands and global options.
```typescript
// src/cliExample/main.ts
const rootCommand: Command<AppContext> = {
name: "calculator",
description: "A feature-rich calculator program",
options: new Map([
[
"debug",
{
name: "debug",
shortName: "d",
description: "Enable debug mode",
defaultValue: false,
},
],
]),
subcommands: new Map([
["math", mathCommand],
// other commands...
]),
action: ({ context }) => {
print(`Welcome to ${context.appName}!`);
print("Use --help to see available commands");
return Ok.EMPTY;
},
};
```
### 5. Create and Run the CLI
Finally, create the context instance and pass it along with the root command to `createCli`. This returns a handler function that you can call with the program's arguments.
```typescript
// src/cliExample/main.ts
import { createCli } from "../lib/ccCLI/index";
// Create global context instance
const appContext: AppContext = {
appName: "MyAwesome Calculator",
debugMode: false,
log: (message) => {
if (appContext.debugMode) {
print(`[LOG] ${message}`);
}
},
};
// Create the CLI handler
const cli = createCli(rootCommand, { globalContext: appContext });
// Get arguments and run
const args = [...$vararg];
cli(args);
```
### Usage
You can now run your CLI from the ComputerCraft terminal:
```sh
> lua program.lua math add 5 7
12
> lua program.lua --debug math add 5 7
[LOG] Executing 'add' command in 'MyAwesome Calculator'
12
> lua program.lua math --help
# Displays help for the 'math' command
```
## Advanced Topics
### Arguments
Arguments are positional values passed after a command. They are defined in an array.
```typescript
args: [
{ name: "a", description: "The first number", required: true },
{ name: "b", description: "The second number" }, // optional
],
```
### Options
Options are named values (flags) that can appear anywhere. They are defined in a `Map`.
```typescript
options: new Map([
[
"name", // The key in the map must match the option's name
{
name: "name",
shortName: "n",
description: "The name to greet",
defaultValue: "World",
},
],
[
"force",
{
name: "force",
description: "Force the operation",
defaultValue: false, // For boolean flags
},
],
]),
```
They can be used like this:
* `--name "John"` or `-n "John"`
* `--name="John"`
* `--force` (sets the value to `true`)
### Error Handling
The `action` function must return a `Result<void, CliError>`.
* Return `Ok.EMPTY` on success.
* The framework automatically handles parsing errors like missing arguments or unknown commands. You can return your own errors from within an action if needed, though this is less common. The primary mechanism is simply printing an error message and returning `Ok.EMPTY`.
## API Reference
The public API is exposed through `src/lib/ccCLI/index.ts`.
### Core Function
* `createCli<TContext>(rootCommand, options)`: Creates the main CLI handler function.
* `rootCommand`: The top-level command of your application.
* `options.globalContext`: The context object to be injected into all actions.
* `options.writer`: An optional function to handle output (defaults to `textutils.pagedPrint`).
### Core Types
* `Command<TContext>`
* `Argument`
* `Option`
* `ActionContext<TContext>`
* `CliError`
This documentation provides a comprehensive overview of the `ccCLI` framework. By following the tutorial and referencing the examples, you can build powerful and well-structured command-line tools for CC:Tweaked.