• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

Blightmud / Blightmud / 14316676040

07 Apr 2025 06:27PM UTC coverage: 74.417% (-0.9%) from 75.283%
14316676040

push

github

web-flow
Merge pull request #1202 from Blightmud/dependabot/cargo/rustls-0.23.25

build(deps): bump rustls from 0.23.23 to 0.23.25

7089 of 9526 relevant lines covered (74.42%)

431.56 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.45
/src/lua/lua_script.rs
1
use super::fs_event::FSEvent;
2
use super::{
3
    audio::Audio, backend::Backend, blight::*, line::Line as LuaLine, plugin, script::Script,
4
    socket::SocketLib, tts::Tts,
5
};
6
use super::{constants::*, core::Core, ui_event::UiEvent};
7
use super::{
8
    log::Log, mud::Mud, regex::RegexLib, settings::Settings, store::Store, timer::Timer, util::*,
9
};
10
use crate::lua::fs::Fs;
11
use crate::lua::prompt::Prompt;
12
use crate::lua::prompt_mask::PromptMask;
13
#[cfg(feature = "spellcheck")]
14
use crate::lua::spellcheck::{self, Spellchecker};
15
use crate::model::Completions;
16
use crate::tools::util::expand_tilde;
17
use crate::{event::Event, lua::servers::Servers, model, model::Line};
18
use anyhow::Result;
19
use log::{debug, info};
20
use mlua::{AnyUserData, FromLua, Lua, Result as LuaResult, Value};
21
use std::io::prelude::*;
22
use std::path::Path;
23
use std::{fs::File, sync::mpsc::Sender};
24

25
pub struct LuaScriptBuilder {
26
    writer: Sender<Event>,
27
    dimensions: (u16, u16),
28
    reader_mode: bool,
29
    tts_enabled: bool,
30
}
31

32
impl LuaScriptBuilder {
33
    pub fn new(writer: Sender<Event>) -> Self {
149✔
34
        Self {
149✔
35
            writer,
149✔
36
            dimensions: (0, 0),
149✔
37
            reader_mode: false,
149✔
38
            tts_enabled: false,
149✔
39
        }
149✔
40
    }
149✔
41

42
    pub fn reader_mode(mut self, reader_mode: bool) -> Self {
95✔
43
        self.reader_mode = reader_mode;
95✔
44
        self
95✔
45
    }
95✔
46

47
    pub fn tts_enabled(mut self, tts_enabled: bool) -> Self {
95✔
48
        self.tts_enabled = tts_enabled;
95✔
49
        self
95✔
50
    }
95✔
51

52
    pub fn dimensions(mut self, dimensions: (u16, u16)) -> Self {
149✔
53
        self.dimensions = dimensions;
149✔
54
        self
149✔
55
    }
149✔
56

57
    pub fn build(self) -> LuaScript {
149✔
58
        let main_writer = self.writer.clone();
149✔
59
        let reader_mode = self.reader_mode;
149✔
60
        let tts_enabled = self.tts_enabled;
149✔
61
        LuaScript {
149✔
62
            state: create_default_lua_state(self, None),
149✔
63
            writer: main_writer,
149✔
64
            tts_enabled,
149✔
65
            reader_mode,
149✔
66
        }
149✔
67
    }
149✔
68
}
69

70
pub struct LuaScript {
71
    state: Lua,
72
    writer: Sender<Event>,
73
    tts_enabled: bool,
74
    reader_mode: bool,
75
}
76

77
/// load the provided filenames in the lua resource directory as named chunks that get called,
78
/// with the resulting value stored in the globals under the file name with the .lua suffix
79
/// removed.
80
macro_rules! lua_global_resources {
81
    ($state: ident, $globals: ident, $($path: expr),+ $(,)?) => {{
82
        $(
83
            let name = Path::new($path).file_name().unwrap().to_string_lossy();
84
            let name = name.strip_suffix(".lua");
85
            let value = $state
86
                .load(include_str!(concat!("../../resources/lua/", $path)))
87
                .set_name(concat!("../../resources/lua/", $path))
88
                .call::<_, mlua::Value>(())?;
89
            $globals.set(name, value)?;
90
        )+
91
    }};
92
}
93

94
/// load the provided filenames in the lua resource directory as named chunks that get executed.
95
macro_rules! lua_resources {
96
    ($state: ident, $($path: expr),+ $(,)?) => {{
97
        $(
98
            $state.load(include_str!(concat!("../../resources/lua/", $path)))
99
                .set_name($path)
100
                .exec()?;
101
        )+
102
    }};
103
}
104

105
fn create_default_lua_state(builder: LuaScriptBuilder, store: Option<Store>) -> Lua {
165✔
106
    let state = unsafe { Lua::unsafe_new() };
165✔
107
    let writer = builder.writer;
165✔
108

165✔
109
    let backend = Backend::new(writer.clone());
165✔
110
    let mut blight = Blight::new(writer.clone());
165✔
111
    let store = match store {
165✔
112
        Some(store) => store,
16✔
113
        None => Store::new(),
149✔
114
    };
115
    let tts = Tts::new(builder.tts_enabled);
165✔
116

165✔
117
    blight.screen_dimensions = builder.dimensions;
165✔
118
    blight.core_mode(true);
165✔
119
    let result: LuaResult<()> = (|| {
165✔
120
        let globals = state.globals();
165✔
121

165✔
122
        state.set_named_registry_value(BACKEND, backend)?;
165✔
123
        state.set_named_registry_value(MUD_OUTPUT_LISTENER_TABLE, state.create_table()?)?;
165✔
124
        state.set_named_registry_value(MUD_INPUT_LISTENER_TABLE, state.create_table()?)?;
165✔
125
        state.set_named_registry_value(BLIGHT_ON_QUIT_LISTENER_TABLE, state.create_table()?)?;
165✔
126
        state.set_named_registry_value(
165✔
127
            BLIGHT_ON_DIMENSIONS_CHANGE_LISTENER_TABLE,
165✔
128
            state.create_table()?,
165✔
129
        )?;
×
130
        state.set_named_registry_value(TIMED_CALLBACK_TABLE, state.create_table()?)?;
165✔
131
        state.set_named_registry_value(TIMED_CALLBACK_TABLE_CORE, state.create_table()?)?;
165✔
132
        state.set_named_registry_value(TIMED_NEXT_ID, 1)?;
165✔
133
        state.set_named_registry_value(TIMER_TICK_CALLBACK_TABLE, state.create_table()?)?;
165✔
134
        state.set_named_registry_value(TIMER_TICK_CALLBACK_TABLE_CORE, state.create_table()?)?;
165✔
135
        state.set_named_registry_value(COMMAND_BINDING_TABLE, state.create_table()?)?;
165✔
136
        state.set_named_registry_value(PROTO_ENABLED_LISTENERS_TABLE, state.create_table()?)?;
165✔
137
        state.set_named_registry_value(PROTO_DISABLED_LISTENERS_TABLE, state.create_table()?)?;
165✔
138
        state.set_named_registry_value(PROTO_SUBNEG_LISTENERS_TABLE, state.create_table()?)?;
165✔
139
        state.set_named_registry_value(ON_CONNECTION_CALLBACK_TABLE, state.create_table()?)?;
165✔
140
        state.set_named_registry_value(ON_DISCONNECT_CALLBACK_TABLE, state.create_table()?)?;
165✔
141
        state.set_named_registry_value(COMPLETION_CALLBACK_TABLE, state.create_table()?)?;
165✔
142
        state.set_named_registry_value(FS_LISTENERS, state.create_table()?)?;
165✔
143
        state.set_named_registry_value(SCRIPT_RESET_LISTENERS, state.create_table()?)?;
165✔
144
        state.set_named_registry_value(PROMPT_CONTENT, String::new())?;
165✔
145
        state.set_named_registry_value(PROMPT_CURSOR_INDEX, 0)?;
165✔
146
        state.set_named_registry_value(PROMPT_INPUT_LISTENER_TABLE, state.create_table()?)?;
165✔
147
        state.set_named_registry_value(STATUS_AREA_HEIGHT, 1)?;
165✔
148

149
        globals.set("blight", blight)?;
165✔
150
        globals.set("core", Core::new(writer.clone()))?;
165✔
151
        globals.set("tts", tts)?;
165✔
152
        globals.set("regex", RegexLib {})?;
165✔
153
        globals.set("mud", Mud::new())?;
165✔
154
        globals.set("fs", Fs {})?;
165✔
155
        globals.set("log", Log::new())?;
165✔
156
        globals.set("timer", Timer::new())?;
165✔
157
        globals.set("script", Script {})?;
165✔
158
        globals.set(Settings::LUA_GLOBAL_NAME, Settings::new())?;
165✔
159
        globals.set(Store::LUA_GLOBAL_NAME, store)?;
165✔
160
        globals.set("plugin", plugin::Handler::new())?;
165✔
161
        globals.set("audio", Audio {})?;
165✔
162
        globals.set("socket", SocketLib {})?;
165✔
163
        globals.set("servers", Servers {})?;
165✔
164
        globals.set("prompt", Prompt {})?;
165✔
165
        globals.set("prompt_mask", PromptMask {})?;
165✔
166
        #[cfg(feature = "spellcheck")]
167
        globals.set(spellcheck::LUA_GLOBAL_NAME, Spellchecker::new())?;
165✔
168

169
        lua_global_resources!(
165✔
170
            state,
165✔
171
            globals,
165✔
172
            "json.lua",
165✔
173
            "trigger.lua",
165✔
174
            "alias.lua",
165✔
175
            "search.lua",
165✔
176
            "history.lua",
165✔
177
            "gmcp.lua",
165✔
178
            "msdp.lua",
165✔
179
            "tasks.lua",
165✔
180
            "ttype.lua",
165✔
181
            "mssp.lua"
165✔
182
        );
183

184
        lua_resources!(
165✔
185
            state,
186
            "defaults.lua",
187
            "functions.lua",
188
            "bindings.lua",
189
            "lua_command.lua",
190
            "macros.lua",
191
            "plugins.lua",
192
            "telnet_charset.lua",
193
            "naws.lua",
194
        );
195

196
        {
197
            let blight_aud: AnyUserData = globals.get("blight")?;
165✔
198
            let mut blight = blight_aud.borrow_mut::<Blight>()?;
165✔
199
            blight.core_mode(false);
165✔
200
        }
165✔
201

165✔
202
        lua_resources!(state, "../../resources/lua/on_state_created.lua");
165✔
203

204
        Ok(())
165✔
205
    })();
206

207
    if let Err(err) = result {
165✔
208
        output_stack_trace(&writer, &err.to_string());
×
209
    }
165✔
210

211
    state
165✔
212
}
165✔
213

214
impl LuaScript {
215
    pub fn on_reset(&mut self) {
14✔
216
        self.exec_lua(&mut || -> LuaResult<()> {
14✔
217
            let table: mlua::Table = self.state.named_registry_value(SCRIPT_RESET_LISTENERS)?;
14✔
218
            for pair in table.pairs::<mlua::Value, mlua::Function>() {
28✔
219
                let (_, cb) = pair?;
28✔
220
                cb.call::<_, ()>(())?;
28✔
221
            }
222
            Ok(())
14✔
223
        });
14✔
224
    }
14✔
225

226
    pub fn reset(&mut self, dimensions: (u16, u16)) -> Result<()> {
16✔
227
        let store = self.state.globals().get(Store::LUA_GLOBAL_NAME)?;
16✔
228
        let builder = LuaScriptBuilder {
16✔
229
            writer: self.writer.clone(),
16✔
230
            dimensions,
16✔
231
            tts_enabled: self.tts_enabled,
16✔
232
            reader_mode: self.reader_mode,
16✔
233
        };
16✔
234
        self.state = create_default_lua_state(builder, store);
16✔
235
        Ok(())
16✔
236
    }
16✔
237

238
    pub fn handle_fs_event(&self, event: crate::io::FSEvent) -> Result<()> {
×
239
        self.exec_lua(&mut || -> LuaResult<()> {
×
240
            let table: mlua::Table = self.state.named_registry_value(FS_LISTENERS)?;
×
241
            for pair in table.pairs::<mlua::Value, mlua::Function>() {
×
242
                let (_, cb) = pair?;
×
243
                let fs_event = FSEvent::from(event.clone());
×
244
                debug!("FSEVENT(lua): {:?}", fs_event);
×
245
                cb.call::<_, ()>(fs_event)?;
×
246
            }
247
            Ok(())
×
248
        });
×
249
        Ok(())
×
250
    }
×
251

252
    pub fn get_output_lines(&self) -> Vec<Line> {
730✔
253
        let blight_aud: AnyUserData = self
730✔
254
            .state
730✔
255
            .globals()
730✔
256
            .get("blight")
730✔
257
            .expect("blight global not found");
730✔
258
        let mut blight = blight_aud
730✔
259
            .borrow_mut::<Blight>()
730✔
260
            .expect("Could not borrow blight global as mut");
730✔
261
        blight.get_output_lines()
730✔
262
    }
730✔
263

264
    fn exec_lua<T>(&self, func: &mut dyn FnMut() -> LuaResult<T>) -> Option<T> {
793✔
265
        let result = func();
793✔
266
        if let Err(msg) = &result {
793✔
267
            output_stack_trace(&self.writer, &msg.to_string());
7✔
268
        }
786✔
269
        result.ok()
793✔
270
    }
793✔
271

272
    pub fn set_prompt_content(&mut self, content: String, pos: usize) {
16✔
273
        self.exec_lua(&mut || -> LuaResult<()> {
16✔
274
            self.state
16✔
275
                .set_named_registry_value(PROMPT_CONTENT, content.clone())?;
16✔
276
            self.state
16✔
277
                .set_named_registry_value(PROMPT_CURSOR_INDEX, pos)?;
16✔
278
            Ok(())
16✔
279
        });
16✔
280
    }
16✔
281

282
    pub fn set_prompt_mask_content(&mut self, mask: &model::PromptMask) {
1✔
283
        let updated_mask = mask.to_table(&self.state).unwrap();
1✔
284
        self.state
1✔
285
            .set_named_registry_value(PROMPT_MASK_CONTENT, updated_mask)
1✔
286
            .unwrap();
1✔
287
    }
1✔
288

289
    pub fn on_prompt_update(&self, content: &str) {
2✔
290
        self.exec_lua(&mut || -> LuaResult<()> {
2✔
291
            let table: mlua::Table = self
2✔
292
                .state
2✔
293
                .named_registry_value(PROMPT_INPUT_LISTENER_TABLE)?;
2✔
294
            for pair in table.pairs::<mlua::Value, mlua::Function>() {
2✔
295
                let (_, cb) = pair?;
1✔
296
                cb.call::<_, ()>(content)?;
1✔
297
            }
298
            Ok(())
2✔
299
        });
2✔
300
    }
2✔
301

302
    pub fn on_mud_output(&self, line: &mut Line) {
69✔
303
        if !line.flags.bypass_script {
69✔
304
            let mut lline = LuaLine::from(line.clone());
69✔
305
            self.exec_lua(&mut || -> LuaResult<()> {
69✔
306
                let table: mlua::Table =
69✔
307
                    self.state.named_registry_value(MUD_OUTPUT_LISTENER_TABLE)?;
69✔
308
                for pair in table.pairs::<mlua::Value, mlua::Function>() {
111✔
309
                    let (_, cb) = pair?;
111✔
310
                    lline = cb.call::<_, LuaLine>(lline.clone())?;
111✔
311
                }
312
                line.replace_with(&lline.inner);
69✔
313
                if let Some(replacement) = &lline.replacement {
69✔
314
                    line.set_content(replacement);
×
315
                }
69✔
316
                Ok(())
69✔
317
            });
69✔
318
        }
×
319
    }
69✔
320

321
    pub fn on_mud_input(&self, line: &mut Line) {
62✔
322
        if !line.flags.bypass_script {
62✔
323
            let mut lline = LuaLine::from(line.clone());
62✔
324
            let res = self.exec_lua(&mut || -> LuaResult<()> {
62✔
325
                let table: mlua::Table =
62✔
326
                    self.state.named_registry_value(MUD_INPUT_LISTENER_TABLE)?;
62✔
327
                for pair in table.pairs::<mlua::Value, mlua::Function>() {
145✔
328
                    let (_, cb) = pair?;
145✔
329
                    lline = cb.call::<_, LuaLine>(lline.clone())?;
145✔
330
                }
331
                line.replace_with(&lline.inner);
62✔
332
                if let Some(replacement) = &lline.replacement {
62✔
333
                    line.set_content(replacement);
×
334
                }
62✔
335
                Ok(())
62✔
336
            });
62✔
337
            if res.is_none() {
62✔
338
                line.flags.matched = true;
×
339
            }
62✔
340
        }
×
341
    }
62✔
342

343
    pub fn on_quit(&self) {
85✔
344
        self.exec_lua(&mut || -> LuaResult<()> {
85✔
345
            let table: mlua::Table = self
85✔
346
                .state
85✔
347
                .named_registry_value(BLIGHT_ON_QUIT_LISTENER_TABLE)?;
85✔
348
            for pair in table.pairs::<mlua::Value, mlua::Function>() {
86✔
349
                let (_, cb) = pair?;
86✔
350
                cb.call::<_, ()>(())?;
86✔
351
            }
352
            Ok(())
85✔
353
        });
85✔
354
    }
85✔
355

356
    pub fn run_timed_function(&mut self, id: u32) {
50✔
357
        self.exec_lua(&mut || -> LuaResult<()> {
50✔
358
            let core_table: mlua::Table =
50✔
359
                self.state.named_registry_value(TIMED_CALLBACK_TABLE_CORE)?;
50✔
360
            match core_table.get(id)? {
50✔
361
                mlua::Value::Function(func) => func.call::<_, ()>(()),
×
362
                _ => {
363
                    let table: mlua::Table =
50✔
364
                        self.state.named_registry_value(TIMED_CALLBACK_TABLE)?;
50✔
365
                    match table.get(id)? {
50✔
366
                        mlua::Value::Function(func) => func.call::<_, ()>(()),
50✔
367
                        _ => Ok(()),
×
368
                    }
369
                }
370
            }
371
        });
50✔
372
    }
50✔
373

374
    pub fn tick(&mut self, millis: u128) {
192✔
375
        self.exec_lua(&mut || -> LuaResult<()> {
192✔
376
            let core_tick_table: mlua::Table = self
192✔
377
                .state
192✔
378
                .named_registry_value(TIMER_TICK_CALLBACK_TABLE_CORE)?;
192✔
379
            let tick_table: mlua::Table =
192✔
380
                self.state.named_registry_value(TIMER_TICK_CALLBACK_TABLE)?;
192✔
381
            let pairs = core_tick_table
192✔
382
                .pairs::<mlua::Integer, mlua::Function>()
192✔
383
                .chain(tick_table.pairs::<mlua::Integer, mlua::Function>());
192✔
384
            for pair in pairs.flatten() {
195✔
385
                pair.1.call::<_, ()>(millis)?;
195✔
386
            }
387

388
            Ok(())
192✔
389
        });
192✔
390
    }
192✔
391

392
    pub fn remove_timed_function(&mut self, id: u32) {
1✔
393
        self.exec_lua(&mut || -> LuaResult<()> {
1✔
394
            let core_table: mlua::Table =
1✔
395
                self.state.named_registry_value(TIMED_CALLBACK_TABLE_CORE)?;
1✔
396
            let table: mlua::Table = self.state.named_registry_value(TIMED_CALLBACK_TABLE)?;
1✔
397
            core_table.set(id, mlua::Nil)?;
1✔
398
            table.set(id, mlua::Nil)
1✔
399
        });
1✔
400
    }
1✔
401

402
    pub fn load_script(&mut self, path: &str) -> Result<()> {
56✔
403
        info!("Loading: {}", path);
56✔
404
        let file_path = expand_tilde(path);
56✔
405
        let mut file = File::open(file_path.as_ref())?;
56✔
406
        let dir = file_path.rsplit_once('/').unwrap_or(("", "")).0;
56✔
407
        let mut content = String::new();
56✔
408
        file.read_to_string(&mut content)?;
56✔
409
        self.exec_lua(&mut || -> LuaResult<()> {
56✔
410
            let package: mlua::Table = self.state.globals().get("package")?;
56✔
411
            let ppath = package.get::<&str, String>("path")?;
56✔
412
            package.set("path", format!("{dir}/?.lua;{ppath}"))?;
56✔
413
            let result = self.state.load(&content).set_name(path).exec();
56✔
414
            package.set("path", ppath)?;
56✔
415
            result
56✔
416
        });
56✔
417
        Ok(())
56✔
418
    }
56✔
419

420
    pub fn eval(&mut self, script: &str) -> Result<()> {
35✔
421
        self.exec_lua(&mut || -> LuaResult<()> {
35✔
422
            self.state.load(script).exec()?;
35✔
423
            Ok(())
35✔
424
        });
35✔
425
        Ok(())
35✔
426
    }
35✔
427

428
    pub fn on_connect(&mut self, host: &str, port: u16, id: u16) {
72✔
429
        self.exec_lua(&mut || -> LuaResult<()> {
72✔
430
            self.state.set_named_registry_value(IS_CONNECTED, true)?;
72✔
431
            self.state.set_named_registry_value(CONNECTION_ID, id)?;
72✔
432
            let table: mlua::Table = self
72✔
433
                .state
72✔
434
                .named_registry_value(ON_CONNECTION_CALLBACK_TABLE)?;
72✔
435
            for pair in table.pairs::<mlua::Value, mlua::Function>() {
76✔
436
                let (_, cb) = pair.unwrap();
48✔
437
                cb.call::<_, ()>((host, port))?;
48✔
438
            }
439
            Ok(())
72✔
440
        });
72✔
441
    }
72✔
442

443
    pub fn on_disconnect(&mut self) {
72✔
444
        self.exec_lua(&mut || -> LuaResult<()> {
72✔
445
            self.state.set_named_registry_value(IS_CONNECTED, false)?;
72✔
446
            let table: mlua::Table = self
72✔
447
                .state
72✔
448
                .named_registry_value(ON_DISCONNECT_CALLBACK_TABLE)?;
72✔
449
            for pair in table.pairs::<mlua::Value, mlua::Function>() {
292✔
450
                let (_, cb) = pair.unwrap();
292✔
451
                cb.call::<_, ()>(())?;
292✔
452
            }
453
            Ok(())
72✔
454
        });
72✔
455
    }
72✔
456

457
    pub fn set_dimensions(&mut self, dim: (u16, u16)) {
1✔
458
        self.exec_lua(&mut || -> LuaResult<()> {
1✔
459
            let blight_aud: AnyUserData = self.state.globals().get("blight")?;
1✔
460
            {
461
                let mut blight = blight_aud.borrow_mut::<Blight>()?;
1✔
462
                blight.screen_dimensions = dim;
1✔
463
            }
464
            let table: mlua::Table = self
1✔
465
                .state
1✔
466
                .named_registry_value(BLIGHT_ON_DIMENSIONS_CHANGE_LISTENER_TABLE)?;
1✔
467
            for pair in table.pairs::<mlua::Value, mlua::Function>() {
2✔
468
                let (_, cb) = pair?;
2✔
469
                cb.call::<_, ()>(dim)?;
2✔
470
            }
471
            Ok(())
1✔
472
        });
1✔
473
    }
1✔
474

475
    pub fn set_reader_mode(&mut self, reader_mode: bool) {
×
476
        self.reader_mode = reader_mode;
×
477
        self.exec_lua(&mut || -> LuaResult<()> {
×
478
            let blight_aud: AnyUserData = self.state.globals().get("blight")?;
×
479
            let mut blight = blight_aud.borrow_mut::<Blight>()?;
×
480
            blight.reader_mode = reader_mode;
×
481
            Ok(())
×
482
        });
×
483
    }
×
484

485
    pub fn set_tts_enabled(&mut self, tts_enabled: bool) {
×
486
        {
×
487
            self.tts_enabled = tts_enabled;
×
488
            self.exec_lua(&mut || -> LuaResult<()> {
×
489
                let tts_aud: AnyUserData = self.state.globals().get("tts")?;
×
490
                let mut tts = tts_aud.borrow_mut::<Tts>()?;
×
491
                tts.enabled = tts_enabled;
×
492
                Ok(())
×
493
            });
×
494
        }
495
    }
×
496

497
    pub fn proto_disabled(&mut self, proto: u8) {
×
498
        self.exec_lua(&mut || -> LuaResult<()> {
×
499
            let table: mlua::Table = self
×
500
                .state
×
501
                .named_registry_value(PROTO_DISABLED_LISTENERS_TABLE)?;
×
502
            for pair in table.pairs::<mlua::Value, mlua::Function>() {
×
503
                let (_, cb) = pair.unwrap();
×
504
                cb.call::<_, ()>(proto)?;
×
505
            }
506
            Ok(())
×
507
        });
×
508
    }
×
509

510
    pub fn proto_enabled(&mut self, proto: u8) {
15✔
511
        self.exec_lua(&mut || -> LuaResult<()> {
15✔
512
            let table: mlua::Table = self
15✔
513
                .state
15✔
514
                .named_registry_value(PROTO_ENABLED_LISTENERS_TABLE)?;
15✔
515
            for pair in table.pairs::<mlua::Value, mlua::Function>() {
91✔
516
                let (_, cb) = pair.unwrap();
91✔
517
                cb.call::<_, ()>(proto)?;
91✔
518
            }
519
            Ok(())
15✔
520
        });
15✔
521
    }
15✔
522

523
    pub fn proto_subneg(&mut self, proto: u8, bytes: &[u8]) {
22✔
524
        self.exec_lua(&mut || -> LuaResult<()> {
22✔
525
            let table: mlua::Table = self
22✔
526
                .state
22✔
527
                .named_registry_value(PROTO_SUBNEG_LISTENERS_TABLE)?;
22✔
528
            for pair in table.pairs::<mlua::Value, mlua::Function>() {
111✔
529
                let (_, cb) = pair.unwrap();
111✔
530
                cb.call::<_, ()>((proto, bytes.to_vec()))?;
111✔
531
            }
532
            Ok(())
22✔
533
        });
22✔
534
    }
22✔
535

536
    pub fn tab_complete(&mut self, input: &str) -> Completions {
8✔
537
        self.exec_lua(&mut || -> LuaResult<Completions> {
8✔
538
            let mut completions = Completions::default();
8✔
539
            let cb_table: mlua::Table =
8✔
540
                self.state.named_registry_value(COMPLETION_CALLBACK_TABLE)?;
8✔
541
            for cb in cb_table.sequence_values::<mlua::Function>() {
8✔
542
                let cb = cb?;
7✔
543
                let result = cb.call::<_, mlua::MultiValue>(input.to_string())?;
7✔
544
                if !result.is_empty() {
7✔
545
                    let mut it = result.into_iter();
7✔
546
                    if let Some(Value::Table(table)) = it.next() {
7✔
547
                        if let Ok(mut comps) =
5✔
548
                            Vec::<String>::from_lua(Value::Table(table), &self.state)
5✔
549
                        {
5✔
550
                            comps.sort();
5✔
551
                            completions.add_all(&mut comps);
5✔
552
                        }
5✔
553
                    }
2✔
554
                    if let Some(Value::Boolean(lock)) = it.next() {
7✔
555
                        completions.lock(lock);
3✔
556
                    }
4✔
557
                }
×
558
            }
559
            Ok(completions)
8✔
560
        })
8✔
561
        .unwrap_or_default()
8✔
562
    }
8✔
563

564
    pub fn check_bindings(&mut self, cmd: &str) -> bool {
21✔
565
        let mut response = false;
21✔
566
        self.exec_lua(&mut || -> LuaResult<()> {
21✔
567
            let bind_table: mlua::Table = self.state.named_registry_value(COMMAND_BINDING_TABLE)?;
21✔
568
            if let Ok(callback) = bind_table.get::<_, mlua::Function>(cmd) {
21✔
569
                response = true;
20✔
570
                callback.call::<_, ()>(())
20✔
571
            } else {
572
                Ok(())
1✔
573
            }
574
        });
21✔
575
        response
21✔
576
    }
21✔
577

578
    pub fn get_ui_events(&mut self) -> Vec<UiEvent> {
16✔
579
        match (|| -> LuaResult<Vec<UiEvent>> {
16✔
580
            let blight_aud: AnyUserData = self.state.globals().get("blight")?;
16✔
581
            let mut blight = blight_aud.borrow_mut::<Blight>()?;
16✔
582
            let events = blight.get_ui_events();
16✔
583
            Ok(events)
16✔
584
        })() {
585
            Ok(data) => data,
16✔
586
            Err(msg) => {
×
587
                output_stack_trace(&self.writer, &msg.to_string());
×
588
                vec![]
×
589
            }
590
        }
591
    }
16✔
592
}
593

594
#[cfg(test)]
595
mod lua_script_tests {
596
    use super::LuaScript;
597
    use super::LuaScriptBuilder;
598
    use super::CONNECTION_ID;
599
    use crate::event::QuitMethod;
600
    use crate::lua::constants::TIMED_CALLBACK_TABLE;
601
    use crate::model::Completions;
602
    use crate::model::{Connection, PromptMask, Regex};
603
    use crate::{event::Event, lua::regex::Regex as LReg, model::Line, PROJECT_NAME, VERSION};
604
    use libmudtelnet::bytes::Bytes;
605
    use mlua::Table;
606
    use std::{
607
        collections::BTreeMap,
608
        sync::mpsc::{channel, Receiver, Sender},
609
    };
610

611
    fn test_trigger(line: &str, lua: &LuaScript) -> bool {
11✔
612
        let mut line = Line::from(line);
11✔
613
        lua.on_mud_output(&mut line);
11✔
614
        line.flags.matched
11✔
615
    }
11✔
616

617
    fn test_prompt_trigger(line: &str, lua: &LuaScript) -> bool {
5✔
618
        let mut line = Line::from(line);
5✔
619
        line.flags.prompt = true;
5✔
620
        lua.on_mud_output(&mut line);
5✔
621
        line.flags.matched
5✔
622
    }
5✔
623

624
    fn get_lua() -> (LuaScript, Receiver<Event>) {
40✔
625
        let (writer, reader): (Sender<Event>, Receiver<Event>) = channel();
40✔
626
        let lua = LuaScriptBuilder::new(writer).dimensions((80, 80)).build();
40✔
627
        loop {
628
            if reader.try_recv().is_err() {
280✔
629
                break;
40✔
630
            }
240✔
631
        }
632
        (lua, reader)
40✔
633
    }
40✔
634

635
    #[test]
636
    fn test_lua_trigger() {
1✔
637
        let create_trigger_lua = r#"
1✔
638
        trigger.add("^test$", {gag=true}, function () end)
1✔
639
        "#;
1✔
640

1✔
641
        let lua = get_lua().0;
1✔
642
        lua.state.load(create_trigger_lua).exec().unwrap();
1✔
643

1✔
644
        assert!(test_trigger("test", &lua));
1✔
645
        assert!(!test_trigger("test test", &lua));
1✔
646
    }
1✔
647

648
    #[test]
649
    fn test_lua_counted_trigger() {
1✔
650
        let create_trigger_lua = r#"
1✔
651
        trigger.add("^test$", {count=3}, function () end)
1✔
652
        "#;
1✔
653

1✔
654
        let lua = get_lua().0;
1✔
655
        lua.state.load(create_trigger_lua).exec().unwrap();
1✔
656

1✔
657
        assert!(test_trigger("test", &lua));
1✔
658
        assert!(test_trigger("test", &lua));
1✔
659
        assert!(test_trigger("test", &lua));
1✔
660
        assert!(!test_trigger("test", &lua));
1✔
661
    }
1✔
662

663
    #[test]
664
    fn test_lua_prompt_trigger() {
1✔
665
        let create_prompt_trigger_lua = r#"
1✔
666
        trigger.add("^test$", {prompt=true, gag=true}, function () end)
1✔
667
        "#;
1✔
668

1✔
669
        let lua = get_lua().0;
1✔
670
        lua.state.load(create_prompt_trigger_lua).exec().unwrap();
1✔
671

1✔
672
        assert!(test_prompt_trigger("test", &lua));
1✔
673
        assert!(!test_prompt_trigger("test test", &lua));
1✔
674
    }
1✔
675

676
    #[test]
677
    fn test_lua_trigger_id_increment() {
1✔
678
        let lua = get_lua().0;
1✔
679
        lua.state
1✔
680
            .load(r#"trigger.add("^test regular$", {}, function () end)"#)
1✔
681
            .exec()
1✔
682
            .unwrap();
1✔
683
        lua.state
1✔
684
            .load(r#"trigger.add("^test regular$", {}, function () end)"#)
1✔
685
            .exec()
1✔
686
            .unwrap();
1✔
687
        let ttrig: u32 = lua
1✔
688
            .state
1✔
689
            .load(r#"return trigger.add("^test$", {}, function () end).id"#)
1✔
690
            .call(())
1✔
691
            .unwrap();
1✔
692
        let ptrig: u32 = lua
1✔
693
            .state
1✔
694
            .load(r#"return trigger.add("^test$", {prompt=true}, function () end).id"#)
1✔
695
            .call(())
1✔
696
            .unwrap();
1✔
697

1✔
698
        assert_ne!(ttrig, ptrig);
1✔
699
    }
1✔
700

701
    #[test]
702
    fn test_lua_raw_trigger() {
1✔
703
        let create_trigger_lua = r#"
1✔
704
        trigger.add("^\\x1b\\[31mtest\\x1b\\[0m$", {raw=true}, function () end)
1✔
705
        "#;
1✔
706

1✔
707
        let (lua, _reader) = get_lua();
1✔
708
        lua.state.load(create_trigger_lua).exec().unwrap();
1✔
709

1✔
710
        assert!(test_trigger("\x1b[31mtest\x1b[0m", &lua));
1✔
711
        assert!(!test_trigger("test", &lua));
1✔
712
    }
1✔
713

714
    #[test]
715
    fn test_remove_trigger() {
1✔
716
        let lua = get_lua().0;
1✔
717
        let ttrig: u32 = lua
1✔
718
            .state
1✔
719
            .load(r#"return trigger.add("^test$", {}, function () end).id"#)
1✔
720
            .call(())
1✔
721
            .unwrap();
1✔
722
        let ptrig: u32 = lua
1✔
723
            .state
1✔
724
            .load(r#"return trigger.add("^test$", {prompt=true}, function () end).id"#)
1✔
725
            .call(())
1✔
726
            .unwrap();
1✔
727

1✔
728
        assert!(test_trigger("test", &lua));
1✔
729
        assert!(test_prompt_trigger("test", &lua));
1✔
730

731
        lua.state
1✔
732
            .load(&format!("trigger.remove({})", ttrig))
1✔
733
            .exec()
1✔
734
            .unwrap();
1✔
735

1✔
736
        assert!(test_prompt_trigger("test", &lua));
1✔
737
        assert!(!test_trigger("test", &lua));
1✔
738

739
        lua.state
1✔
740
            .load(&format!("trigger.remove({})", ptrig))
1✔
741
            .exec()
1✔
742
            .unwrap();
1✔
743

1✔
744
        assert!(!test_trigger("test", &lua));
1✔
745
        assert!(!test_prompt_trigger("test", &lua));
1✔
746
    }
1✔
747

748
    fn check_alias_match(lua: &LuaScript, mut line: Line) -> bool {
4✔
749
        lua.on_mud_input(&mut line);
4✔
750
        line.flags.matched
4✔
751
    }
4✔
752

753
    #[test]
754
    fn test_lua_alias() {
1✔
755
        let create_alias_lua = r#"
1✔
756
        alias.add("^test$", function () end)
1✔
757
        "#;
1✔
758

1✔
759
        let lua = get_lua().0;
1✔
760
        lua.state.load(create_alias_lua).exec().unwrap();
1✔
761

1✔
762
        assert!(check_alias_match(&lua, Line::from("test")));
1✔
763
        assert!(!check_alias_match(&lua, Line::from(" test")));
1✔
764
    }
1✔
765

766
    #[test]
767
    fn test_lua_remove_alias() {
1✔
768
        let create_alias_lua = r#"
1✔
769
        return alias.add("^test$", function () end).id
1✔
770
        "#;
1✔
771

1✔
772
        let lua = get_lua().0;
1✔
773
        let index: i32 = lua.state.load(create_alias_lua).call(()).unwrap();
1✔
774

1✔
775
        assert!(check_alias_match(&lua, Line::from("test")));
1✔
776

777
        let delete_alias = format!("alias.remove({})", index);
1✔
778
        lua.state.load(&delete_alias).exec().unwrap();
1✔
779
        assert!(!check_alias_match(&lua, Line::from("test")));
1✔
780
    }
1✔
781

782
    #[test]
783
    fn test_dimensions() {
1✔
784
        let mut lua = get_lua().0;
1✔
785
        lua.state
1✔
786
            .load(
1✔
787
                r#"
1✔
788
        width = 0
1✔
789
        height = 0
1✔
790
        blight.on_dimensions_change(function (w, h)
1✔
791
            width = w
1✔
792
            height = h
1✔
793
        end)
1✔
794
        "#,
1✔
795
            )
1✔
796
            .exec()
1✔
797
            .unwrap();
1✔
798
        let dim: (u16, u16) = lua
1✔
799
            .state
1✔
800
            .load("return blight.terminal_dimensions()")
1✔
801
            .call(())
1✔
802
            .unwrap();
1✔
803
        assert_eq!(dim, (80, 80));
1✔
804
        lua.set_dimensions((70, 70));
1✔
805
        let dim: (u16, u16) = lua
1✔
806
            .state
1✔
807
            .load("return blight.terminal_dimensions()")
1✔
808
            .call(())
1✔
809
            .unwrap();
1✔
810
        assert_eq!(dim, (70, 70));
1✔
811
        assert_eq!(lua.state.globals().get::<_, i16>("width").unwrap(), 70);
1✔
812
        assert_eq!(lua.state.globals().get::<_, i16>("height").unwrap(), 70);
1✔
813
    }
1✔
814

815
    #[test]
816
    fn test_enable_proto() {
1✔
817
        let send_gmcp_lua = r#"
1✔
818
        core.enable_protocol(200)
1✔
819
        "#;
1✔
820

1✔
821
        let (lua, reader) = get_lua();
1✔
822
        lua.state.load(send_gmcp_lua).exec().unwrap();
1✔
823

1✔
824
        assert_eq!(reader.recv(), Ok(Event::EnableProto(200)));
1✔
825
    }
1✔
826

827
    #[test]
828
    fn test_proto_send() {
1✔
829
        let send_gmcp_lua = r#"
1✔
830
        core.subneg_send(201, { 255, 250, 86, 255, 240 })
1✔
831
        "#;
1✔
832

1✔
833
        let (lua, reader) = get_lua();
1✔
834
        lua.state.load(send_gmcp_lua).exec().unwrap();
1✔
835

1✔
836
        assert_eq!(
1✔
837
            reader.recv(),
1✔
838
            Ok(Event::ProtoSubnegSend(
1✔
839
                201,
1✔
840
                Bytes::copy_from_slice(&[255, 250, 86, 255, 240])
1✔
841
            ))
1✔
842
        );
843
    }
1✔
844

845
    #[test]
846
    fn test_version() {
1✔
847
        let lua = get_lua().0;
1✔
848
        let (name, version): (String, String) = lua
1✔
849
            .state
1✔
850
            .load("return blight.version()")
1✔
851
            .call::<(), (String, String)>(())
1✔
852
            .unwrap();
1✔
853
        assert_eq!(version, VERSION);
1✔
854
        assert_eq!(name, PROJECT_NAME);
1✔
855
    }
1✔
856

857
    fn assert_event(lua_code: &str, event: Event) {
2✔
858
        let (lua, reader) = get_lua();
2✔
859
        lua.state.load(lua_code).exec().unwrap();
2✔
860

2✔
861
        assert_eq!(reader.recv(), Ok(event));
2✔
862
    }
2✔
863

864
    fn assert_events(lua_code: &str, events: Vec<Event>) {
1✔
865
        let (lua, reader) = get_lua();
1✔
866
        lua.state.load(lua_code).exec().unwrap();
1✔
867

868
        for event in events.iter() {
1✔
869
            assert_eq!(reader.recv(), Ok(event.clone()));
1✔
870
        }
871
    }
1✔
872

873
    #[test]
874
    fn test_output() {
1✔
875
        let (lua, _) = get_lua();
1✔
876
        lua.state
1✔
877
            .load("blight.output(\"test\", \"test\")")
1✔
878
            .exec()
1✔
879
            .unwrap();
1✔
880
        assert_eq!(lua.get_output_lines(), vec![Line::from("test test")]);
1✔
881
    }
1✔
882

883
    #[test]
884
    fn test_load() {
1✔
885
        assert_event(
1✔
886
            "script.load(\"/some/fancy/path\")",
1✔
887
            Event::LoadScript("/some/fancy/path".to_string()),
1✔
888
        );
1✔
889
    }
1✔
890

891
    #[test]
892
    fn test_reset() {
1✔
893
        assert_event("script.reset()", Event::ResetScript);
1✔
894
    }
1✔
895

896
    #[test]
897
    fn test_sending() {
1✔
898
        assert_events(
1✔
899
            "mud.send(\"message\")",
1✔
900
            vec![Event::ServerInput(Line::from("message"))],
1✔
901
        );
1✔
902
    }
1✔
903

904
    #[test]
905
    fn test_conditional_gag() {
1✔
906
        let trigger = r#"
1✔
907
        trigger.add("^Health (\\d+)$", {}, function (matches, line)
1✔
908
            if matches[2] == "100" then
1✔
909
                line:gag(true)
1✔
910
            end
1✔
911
        end)
1✔
912
        "#;
1✔
913

1✔
914
        let (lua, _reader) = get_lua();
1✔
915
        lua.state.load(trigger).exec().unwrap();
1✔
916

1✔
917
        let mut line = Line::from("Health 100");
1✔
918
        lua.on_mud_output(&mut line);
1✔
919
        assert!(line.flags.gag);
1✔
920

921
        let mut line = Line::from("Health 10");
1✔
922
        lua.on_mud_output(&mut line);
1✔
923
        assert!(!line.flags.gag);
1✔
924
    }
1✔
925

926
    fn check_color(lua: &LuaScript, output: &str, result: &str) {
7✔
927
        lua.state
7✔
928
            .load(&format!("blight.output({})", output))
7✔
929
            .exec()
7✔
930
            .unwrap();
7✔
931
        assert_eq!(lua.get_output_lines()[0], Line::from(result));
7✔
932
    }
7✔
933

934
    #[test]
935
    fn test_color_output() {
1✔
936
        let (lua, _reader) = get_lua();
1✔
937
        check_color(
1✔
938
            &lua,
1✔
939
            "C_RED .. \"COLOR\" .. C_RESET",
1✔
940
            "\x1b[31mCOLOR\x1b[0m",
1✔
941
        );
1✔
942
        check_color(
1✔
943
            &lua,
1✔
944
            "C_GREEN .. \"COLOR\" .. C_RESET",
1✔
945
            "\x1b[32mCOLOR\x1b[0m",
1✔
946
        );
1✔
947
        check_color(
1✔
948
            &lua,
1✔
949
            "C_YELLOW .. \"COLOR\" .. C_RESET",
1✔
950
            "\x1b[33mCOLOR\x1b[0m",
1✔
951
        );
1✔
952
        check_color(
1✔
953
            &lua,
1✔
954
            "C_BLUE .. \"COLOR\" .. C_RESET",
1✔
955
            "\x1b[34mCOLOR\x1b[0m",
1✔
956
        );
1✔
957
        check_color(
1✔
958
            &lua,
1✔
959
            "C_MAGENTA .. \"COLOR\" .. C_RESET",
1✔
960
            "\x1b[35mCOLOR\x1b[0m",
1✔
961
        );
1✔
962
        check_color(
1✔
963
            &lua,
1✔
964
            "C_CYAN .. \"COLOR\" .. C_RESET",
1✔
965
            "\x1b[36mCOLOR\x1b[0m",
1✔
966
        );
1✔
967
        check_color(
1✔
968
            &lua,
1✔
969
            "C_WHITE .. \"COLOR\" .. C_RESET",
1✔
970
            "\x1b[37mCOLOR\x1b[0m",
1✔
971
        );
1✔
972
    }
1✔
973

974
    #[test]
975
    fn test_bindings() {
1✔
976
        let lua_code = r#"
1✔
977
        blight.bind("ctrl-a", function ()
1✔
978
            blight.output("ctrl-a")
1✔
979
        end)
1✔
980
        blight.bind("f1", function ()
1✔
981
            blight.output("f1")
1✔
982
        end)
1✔
983
        blight.bind("alt-1", function ()
1✔
984
            blight.output("alt-1")
1✔
985
        end)
1✔
986
        blight.bind("\x1b[1;5A", function ()
1✔
987
            blight.output("ctrl-up")
1✔
988
        end)
1✔
989
        "#;
1✔
990

1✔
991
        let (mut lua, _reader) = get_lua();
1✔
992
        lua.state.load(lua_code).exec().unwrap();
1✔
993

1✔
994
        lua.check_bindings("ctrl-a");
1✔
995
        assert_eq!(lua.get_output_lines(), [Line::from("ctrl-a")]);
1✔
996
        lua.check_bindings("alt-1");
1✔
997
        assert_eq!(lua.get_output_lines(), [Line::from("alt-1")]);
1✔
998
        lua.check_bindings("f1");
1✔
999
        assert_eq!(lua.get_output_lines(), [Line::from("f1")]);
1✔
1000
        lua.check_bindings("ctrl-0");
1✔
1001
        assert_eq!(lua.get_output_lines(), []);
1✔
1002
        lua.check_bindings("\x1b[1;5a");
1✔
1003
        assert_eq!(lua.get_output_lines(), [Line::from("ctrl-up")]);
1✔
1004
    }
1✔
1005

1006
    #[test]
1007
    fn test_on_connect_test() {
1✔
1008
        let lua_code = r#"
1✔
1009
        mud.on_connect(function (host, port)
1✔
1010
            blight.output(host .. ":" .. port .. "-1")
1✔
1011
        end)
1✔
1012
        mud.on_connect(function (host, port)
1✔
1013
            blight.output(host .. ":" .. port .. "-2")
1✔
1014
        end)
1✔
1015
        mud.on_connect(function (host, port)
1✔
1016
            blight.output(host .. ":" .. port .. "-3")
1✔
1017
        end)
1✔
1018
        "#;
1✔
1019

1✔
1020
        let (mut lua, _reader) = get_lua();
1✔
1021
        lua.state.load(lua_code).exec().unwrap();
1✔
1022

1✔
1023
        lua.on_connect("test", 21, 12);
1✔
1024
        assert_eq!(
1✔
1025
            lua.get_output_lines(),
1✔
1026
            [
1✔
1027
                Line::from("test:21-1"),
1✔
1028
                Line::from("test:21-2"),
1✔
1029
                Line::from("test:21-3"),
1✔
1030
            ]
1✔
1031
        );
1032
        assert_eq!(
1✔
1033
            lua.state
1✔
1034
                .named_registry_value::<u32>(CONNECTION_ID)
1✔
1035
                .unwrap(),
1✔
1036
            12
1037
        );
1038
        lua.reset((100, 100)).unwrap();
1✔
1039
        lua.state.load(lua_code).exec().unwrap();
1✔
1040
        lua.on_connect("server", 1000, 13);
1✔
1041
        assert_eq!(
1✔
1042
            lua.get_output_lines(),
1✔
1043
            [
1✔
1044
                Line::from("server:1000-1"),
1✔
1045
                Line::from("server:1000-2"),
1✔
1046
                Line::from("server:1000-3"),
1✔
1047
            ]
1✔
1048
        );
1049
        assert_eq!(
1✔
1050
            lua.state
1✔
1051
                .named_registry_value::<u32>(CONNECTION_ID)
1✔
1052
                .unwrap(),
1✔
1053
            13
1054
        );
1055
    }
1✔
1056

1057
    #[test]
1058
    fn test_on_disconnect_test() {
1✔
1059
        let lua_code = r#"
1✔
1060
        mud.on_disconnect(function ()
1✔
1061
            blight.output("disconnected1")
1✔
1062
        end)
1✔
1063
        mud.on_disconnect(function ()
1✔
1064
            blight.output("disconnected2")
1✔
1065
        end)
1✔
1066
        mud.on_disconnect(function ()
1✔
1067
            blight.output("disconnected3")
1✔
1068
        end)
1✔
1069
        "#;
1✔
1070

1✔
1071
        let (mut lua, _reader) = get_lua();
1✔
1072
        lua.state.load(lua_code).exec().unwrap();
1✔
1073

1✔
1074
        lua.on_disconnect();
1✔
1075
        assert_eq!(
1✔
1076
            lua.get_output_lines(),
1✔
1077
            [
1✔
1078
                Line::from("disconnected1"),
1✔
1079
                Line::from("disconnected2"),
1✔
1080
                Line::from("disconnected3"),
1✔
1081
            ]
1✔
1082
        );
1083
        lua.reset((100, 100)).unwrap();
1✔
1084
        lua.state.load(lua_code).exec().unwrap();
1✔
1085
        lua.on_disconnect();
1✔
1086
        assert_eq!(
1✔
1087
            lua.get_output_lines(),
1✔
1088
            [
1✔
1089
                Line::from("disconnected1"),
1✔
1090
                Line::from("disconnected2"),
1✔
1091
                Line::from("disconnected3"),
1✔
1092
            ]
1✔
1093
        );
1094
    }
1✔
1095

1096
    #[test]
1097
    fn test_alias_ids() {
1✔
1098
        let (lua, _reader) = get_lua();
1✔
1099
        let id = lua
1✔
1100
            .state
1✔
1101
            .load(r#"return alias.add("test", function () end).id"#)
1✔
1102
            .call(())
1✔
1103
            .unwrap();
1✔
1104

1✔
1105
        let aliases: BTreeMap<u32, mlua::Table> = lua
1✔
1106
            .state
1✔
1107
            .load(r#"return alias.get_group():get_aliases()"#)
1✔
1108
            .call(())
1✔
1109
            .unwrap();
1✔
1110

1✔
1111
        assert!(aliases.contains_key(&id));
1✔
1112

1113
        let alias: &mlua::Table = aliases.get(&id).unwrap();
1✔
1114
        assert_eq!(alias.get::<_, bool>("enabled").unwrap(), true);
1✔
1115
        assert_eq!(alias.get::<_, LReg>("regex").unwrap().to_string(), "test");
1✔
1116

1117
        lua.state.load(r#"alias.clear()"#).exec().unwrap();
1✔
1118
        let ids: BTreeMap<u32, mlua::Table> = lua
1✔
1119
            .state
1✔
1120
            .load(r#"return alias.get_group():get_aliases()"#)
1✔
1121
            .call(())
1✔
1122
            .unwrap();
1✔
1123

1✔
1124
        assert!(ids.is_empty());
1✔
1125
    }
1✔
1126

1127
    #[test]
1128
    fn test_trigger_ids() {
1✔
1129
        let (lua, _reader) = get_lua();
1✔
1130
        let id = lua
1✔
1131
            .state
1✔
1132
            .load(r#"return trigger.add("test", {}, function () end).id"#)
1✔
1133
            .call(())
1✔
1134
            .unwrap();
1✔
1135

1✔
1136
        let triggers: BTreeMap<u32, mlua::Table> = lua
1✔
1137
            .state
1✔
1138
            .load(r#"return trigger.get_group():get_triggers()"#)
1✔
1139
            .call(())
1✔
1140
            .unwrap();
1✔
1141

1✔
1142
        assert!(triggers.contains_key(&id));
1✔
1143

1144
        let trigger: &mlua::Table = triggers.get(&id).unwrap();
1✔
1145
        assert_eq!(trigger.get::<_, LReg>("regex").unwrap().to_string(), "test");
1✔
1146
        assert_eq!(trigger.get::<_, bool>("enabled").unwrap(), true);
1✔
1147
        assert_eq!(trigger.get::<_, bool>("gag").unwrap(), false);
1✔
1148
        assert_eq!(trigger.get::<_, bool>("raw").unwrap(), false);
1✔
1149
        assert_eq!(trigger.get::<_, bool>("prompt").unwrap(), false);
1✔
1150

1151
        lua.state.load(r#"trigger.clear()"#).exec().unwrap();
1✔
1152
        let ids: BTreeMap<u32, mlua::Table> = lua
1✔
1153
            .state
1✔
1154
            .load(r#"return trigger.get_group():get_triggers()"#)
1✔
1155
            .call(())
1✔
1156
            .unwrap();
1✔
1157

1✔
1158
        assert!(ids.is_empty());
1✔
1159
    }
1✔
1160

1161
    #[test]
1162
    fn confirm_connection_macros() {
1✔
1163
        let (lua, reader) = get_lua();
1✔
1164
        lua.on_mud_input(&mut Line::from("/connect example.com 4000"));
1✔
1165
        assert_eq!(
1✔
1166
            reader.recv().unwrap(),
1✔
1167
            Event::Connect(Connection::new("example.com", 4000, false, false))
1✔
1168
        );
1169
        lua.on_mud_input(&mut Line::from("/connect example.com 4000 true"));
1✔
1170
        assert_eq!(
1✔
1171
            reader.recv().unwrap(),
1✔
1172
            Event::Connect(Connection::new("example.com", 4000, true, true))
1✔
1173
        );
1174
        lua.on_mud_input(&mut Line::from("/connect example.com 4000 true true"));
1✔
1175
        assert_eq!(
1✔
1176
            reader.recv().unwrap(),
1✔
1177
            Event::Connect(Connection::new("example.com", 4000, true, true))
1✔
1178
        );
1179
        lua.on_mud_input(&mut Line::from("/connect example.com 4000 true false"));
1✔
1180
        assert_eq!(
1✔
1181
            reader.recv().unwrap(),
1✔
1182
            Event::Connect(Connection::new("example.com", 4000, true, false))
1✔
1183
        );
1184
        lua.on_mud_input(&mut Line::from("/disconnect"));
1✔
1185
        assert_eq!(reader.recv().unwrap(), Event::Disconnect);
1✔
1186

1187
        lua.on_mud_input(&mut Line::from("/reconnect"));
1✔
1188
        assert_eq!(reader.recv().unwrap(), Event::Reconnect);
1✔
1189
    }
1✔
1190

1191
    #[test]
1192
    fn confirm_logging_macros() {
1✔
1193
        let (lua, reader) = get_lua();
1✔
1194
        lua.on_mud_input(&mut Line::from("/start_log test"));
1✔
1195
        assert_eq!(
1✔
1196
            reader.recv().unwrap(),
1✔
1197
            Event::StartLogging("test".to_string(), true)
1✔
1198
        );
1199
        lua.on_mud_input(&mut Line::from("/stop_log"));
1✔
1200
        assert_eq!(reader.recv().unwrap(), Event::StopLogging);
1✔
1201
    }
1✔
1202

1203
    #[test]
1204
    fn confirm_load_macro() {
1✔
1205
        let (lua, reader) = get_lua();
1✔
1206
        lua.on_mud_input(&mut Line::from("/load test"));
1✔
1207
        assert_eq!(
1✔
1208
            reader.recv().unwrap(),
1✔
1209
            Event::LoadScript("test".to_string())
1✔
1210
        );
1211
    }
1✔
1212

1213
    #[test]
1214
    fn confirm_quit_macro() {
1✔
1215
        let (lua, reader) = get_lua();
1✔
1216
        lua.on_mud_input(&mut Line::from("/quit"));
1✔
1217
        assert_eq!(reader.recv().unwrap(), Event::Quit(QuitMethod::Script));
1✔
1218
        lua.on_mud_input(&mut Line::from("/q"));
1✔
1219
        assert_eq!(reader.recv().unwrap(), Event::Quit(QuitMethod::Script));
1✔
1220
    }
1✔
1221

1222
    #[test]
1223
    fn confirm_help_macro() {
1✔
1224
        let (lua, reader) = get_lua();
1✔
1225
        lua.on_mud_input(&mut Line::from("/help test1"));
1✔
1226
        assert_eq!(
1✔
1227
            reader.recv().unwrap(),
1✔
1228
            Event::ShowHelp("test1".to_string(), true)
1✔
1229
        );
1230
    }
1✔
1231

1232
    #[test]
1233
    fn confirm_search_macros() {
1✔
1234
        let (lua, reader) = get_lua();
1✔
1235
        lua.on_mud_input(&mut Line::from("/search test1"));
1✔
1236
        let re = Regex::new("test1", None).unwrap();
1✔
1237
        assert_eq!(reader.recv().unwrap(), Event::FindBackward(re.clone()));
1✔
1238
        lua.on_mud_input(&mut Line::from("/s test1"));
1✔
1239
        assert_eq!(reader.recv().unwrap(), Event::FindBackward(re));
1✔
1240
    }
1✔
1241

1242
    #[test]
1243
    fn confirm_tick_callback() {
1✔
1244
        let (mut lua, _reader) = get_lua();
1✔
1245
        lua.state
1✔
1246
            .load(
1✔
1247
                r#"
1✔
1248
        total_millis = 0
1✔
1249
        timer.on_tick(function (millis) total_millis = total_millis + millis end)
1✔
1250
        "#,
1✔
1251
            )
1✔
1252
            .exec()
1✔
1253
            .unwrap();
1✔
1254
        lua.tick(100);
1✔
1255
        assert_eq!(
1✔
1256
            lua.state.globals().get::<_, u128>("total_millis").unwrap(),
1✔
1257
            100
1258
        );
1259
        lua.tick(100);
1✔
1260
        assert_eq!(
1✔
1261
            lua.state.globals().get::<_, u128>("total_millis").unwrap(),
1✔
1262
            200
1263
        );
1264
        lua.tick(100);
1✔
1265
        assert_eq!(
1✔
1266
            lua.state.globals().get::<_, u128>("total_millis").unwrap(),
1✔
1267
            300
1268
        );
1269
    }
1✔
1270

1271
    #[test]
1272
    fn confirm_quit_callback() {
1✔
1273
        let (lua, _reader) = get_lua();
1✔
1274
        lua.state
1✔
1275
            .load(
1✔
1276
                r#"
1✔
1277
        quit = false
1✔
1278
        blight.on_quit(function () quit = true end)
1✔
1279
        "#,
1✔
1280
            )
1✔
1281
            .exec()
1✔
1282
            .unwrap();
1✔
1283
        assert!(!lua.state.globals().get::<_, bool>("quit").unwrap());
1✔
1284
        lua.on_quit();
1✔
1285
        assert!(lua.state.globals().get::<_, bool>("quit").unwrap());
1✔
1286
    }
1✔
1287

1288
    #[test]
1289
    fn confirm_timed_function() {
1✔
1290
        let (mut lua, _reader) = get_lua();
1✔
1291
        lua.state
1✔
1292
            .load(
1✔
1293
                r#"
1✔
1294
        run = false
1✔
1295
        id = timer.add(1, 1, function () run = true end)
1✔
1296
        "#,
1✔
1297
            )
1✔
1298
            .exec()
1✔
1299
            .unwrap();
1✔
1300
        assert!(!lua.state.globals().get::<_, bool>("run").unwrap());
1✔
1301
        let id = lua.state.globals().get::<_, u32>("id").unwrap();
1✔
1302
        lua.run_timed_function(id);
1✔
1303
        assert!(lua.state.globals().get::<_, bool>("run").unwrap());
1✔
1304
    }
1✔
1305

1306
    #[test]
1307
    fn confirm_remove_timed_function() {
1✔
1308
        let (mut lua, _reader) = get_lua();
1✔
1309
        lua.state
1✔
1310
            .load(
1✔
1311
                r#"
1✔
1312
        id = timer.add(1, 1, function () run = true end)
1✔
1313
        "#,
1✔
1314
            )
1✔
1315
            .exec()
1✔
1316
            .unwrap();
1✔
1317
        let id = lua.state.globals().get::<_, u32>("id").unwrap();
1✔
1318
        assert!(lua
1✔
1319
            .state
1✔
1320
            .named_registry_value::<mlua::Table>(TIMED_CALLBACK_TABLE)
1✔
1321
            .unwrap()
1✔
1322
            .contains_key(id)
1✔
1323
            .unwrap());
1✔
1324
        lua.remove_timed_function(id);
1✔
1325
        assert!(!lua
1✔
1326
            .state
1✔
1327
            .named_registry_value::<mlua::Table>(TIMED_CALLBACK_TABLE)
1✔
1328
            .unwrap()
1✔
1329
            .contains_key(id)
1✔
1330
            .unwrap());
1✔
1331
    }
1✔
1332

1333
    #[test]
1334
    fn confirm_proto_enabled() {
1✔
1335
        let (mut lua, _reader) = get_lua();
1✔
1336
        lua.state
1✔
1337
            .load(
1✔
1338
                r#"
1✔
1339
        subneg = 0
1✔
1340
        core.on_protocol_enabled(function (proto) subneg = proto end)
1✔
1341
        "#,
1✔
1342
            )
1✔
1343
            .exec()
1✔
1344
            .unwrap();
1✔
1345
        lua.proto_enabled(201);
1✔
1346
        assert_eq!(lua.state.globals().get::<_, u32>("subneg").unwrap(), 201);
1✔
1347
    }
1✔
1348

1349
    #[test]
1350
    fn confirm_proto_subneg() {
1✔
1351
        let (mut lua, _reader) = get_lua();
1✔
1352
        lua.state
1✔
1353
            .load(
1✔
1354
                r#"
1✔
1355
        subneg = 0
1✔
1356
        core.subneg_recv(function (proto, _) subneg = proto end)
1✔
1357
        "#,
1✔
1358
            )
1✔
1359
            .exec()
1✔
1360
            .unwrap();
1✔
1361
        lua.proto_subneg(201, &[]);
1✔
1362
        assert_eq!(lua.state.globals().get::<_, u32>("subneg").unwrap(), 201);
1✔
1363
    }
1✔
1364

1365
    #[test]
1366
    fn confirm_completion() {
1✔
1367
        let (mut lua, _reader) = get_lua();
1✔
1368
        lua.state
1✔
1369
            .load(
1✔
1370
                r#"
1✔
1371
                blight.on_complete(function (input)
1✔
1372
                    if input == "bat" then
1✔
1373
                        return {"batman"}
1✔
1374
                    elseif input == "batm" then
1✔
1375
                        return {"batman", "batmobile"}
1✔
1376
                    else
1✔
1377
                        return nil
1✔
1378
                    end
1✔
1379
                end)
1✔
1380
                "#,
1✔
1381
            )
1✔
1382
            .exec()
1✔
1383
            .unwrap();
1✔
1384

1✔
1385
        assert_eq!(
1✔
1386
            lua.tab_complete(&"bat".to_string()),
1✔
1387
            Completions::from(vec!["batman".to_string()])
1✔
1388
        );
1389
        assert_eq!(
1✔
1390
            lua.tab_complete(&"batm".to_string()),
1✔
1391
            Completions::from(vec!["batman".to_string(), "batmobile".to_string()])
1✔
1392
        );
1393
        assert_eq!(lua.tab_complete(&"rob".to_string()), Completions::default());
1✔
1394
    }
1✔
1395

1396
    #[test]
1397
    fn confirm_completion_lock() {
1✔
1398
        let (mut lua, _reader) = get_lua();
1✔
1399
        lua.state
1✔
1400
            .load(
1✔
1401
                r#"
1✔
1402
                blight.on_complete(function (input)
1✔
1403
                    if input == "bat" then
1✔
1404
                        return {"batman"}, true
1✔
1405
                    elseif input == "batm" then
1✔
1406
                        return {"batman", "batmobile"}, false
1✔
1407
                    elseif input == "fail" then
1✔
1408
                        return true
1✔
1409
                    else
1✔
1410
                        return {}, true
1✔
1411
                    end
1✔
1412
                end)
1✔
1413
                "#,
1✔
1414
            )
1✔
1415
            .exec()
1✔
1416
            .unwrap();
1✔
1417

1✔
1418
        let mut result = Completions::from(vec!["batman".to_string()]);
1✔
1419
        result.lock(true);
1✔
1420
        assert_eq!(lua.tab_complete(&"bat".to_string()), result);
1✔
1421
        let result = Completions::from(vec!["batman".to_string(), "batmobile".to_string()]);
1✔
1422
        assert_eq!(lua.tab_complete(&"batm".to_string()), result);
1✔
1423
        let mut result = Completions::default();
1✔
1424
        result.lock(true);
1✔
1425
        assert_eq!(lua.tab_complete(&"rob".to_string()), result);
1✔
1426
        let result = Completions::default();
1✔
1427
        assert_eq!(lua.tab_complete(&"fail".to_string()), result);
1✔
1428
    }
1✔
1429

1430
    #[test]
1431
    fn on_prompt_update() {
1✔
1432
        let (lua, _reader) = get_lua();
1✔
1433
        lua.state
1✔
1434
            .load(
1✔
1435
                r#"
1✔
1436
        buf = ""
1✔
1437
        prompt.add_prompt_listener(function (data) buf = data end)
1✔
1438
        "#,
1✔
1439
            )
1✔
1440
            .exec()
1✔
1441
            .unwrap();
1✔
1442

1✔
1443
        assert_eq!(lua.state.globals().get::<_, String>("buf").unwrap(), "");
1✔
1444
        lua.on_prompt_update(&"test".to_string());
1✔
1445
        assert_eq!(lua.state.globals().get::<_, String>("buf").unwrap(), "test");
1✔
1446
    }
1✔
1447

1448
    #[test]
1449
    fn set_prompt_mask_content() {
1✔
1450
        let (mut lua, _reader) = get_lua();
1✔
1451
        let mask = PromptMask::from(BTreeMap::from([
1✔
1452
            (10, "hi".to_string()),
1✔
1453
            (20, "bye".to_string()),
1✔
1454
        ]));
1✔
1455

1✔
1456
        lua.set_prompt_mask_content(&mask);
1✔
1457
        lua.state.load("mask = prompt_mask.get()").exec().unwrap();
1✔
1458
        let result = lua.state.globals().get::<_, Table>("mask").unwrap();
1✔
1459

1✔
1460
        assert_eq!(result.get::<i32, String>(11).unwrap(), "hi");
1✔
1461
        assert_eq!(result.get::<i32, String>(21).unwrap(), "bye");
1✔
1462
    }
1✔
1463

1464
    #[test]
1465
    fn test_gmcp_utf8() {
1✔
1466
        let (lua, _reader) = get_lua();
1✔
1467

1✔
1468
        // Load the GMCP resource script, to get a handle on the 'gmcp' Table.
1✔
1469
        let gmcp_script = include_str!("../../resources/lua/gmcp.lua");
1✔
1470
        let gmcp_module = lua.state.load(gmcp_script).call::<_, Table>(()).unwrap();
1✔
1471

1✔
1472
        // Within the GMCP table, get the subnegotiation received handler.
1✔
1473
        let handler: mlua::Function = gmcp_module.get("_subneg_recv").unwrap();
1✔
1474

1✔
1475
        // Put the 'gmcp' table in global scope for our script to use.
1✔
1476
        lua.state.globals().set("gmcp", gmcp_module).unwrap();
1✔
1477

1✔
1478
        // Set up a GCMP receive handler that captures the data received into a global.
1✔
1479
        lua.state
1✔
1480
            .load(
1✔
1481
                r#"
1✔
1482
        recv_data = ""
1✔
1483
        gmcp.receive('Test', function(data)
1✔
1484
            recv_data = data
1✔
1485
        end)
1✔
1486
        "#,
1✔
1487
            )
1✔
1488
            .exec()
1✔
1489
            .unwrap();
1✔
1490

1✔
1491
        // Invoke the gmcp module's subnegotiation received handler with a message
1✔
1492
        // matching the 'Test' GMCP package, containing a multi-byte character as
1✔
1493
        // the payload.
1✔
1494
        let gmcp_payload = "👋";
1✔
1495
        let gmcp_data = format!("Test {gmcp_payload}").as_bytes().to_vec();
1✔
1496
        handler.call::<_, mlua::Value>((201, gmcp_data)).unwrap();
1✔
1497

1✔
1498
        // We should find the expected data was received by our 'Test' GMCP package
1✔
1499
        // handler (after trimming leading whitespace from splitting off the GMCP package).
1✔
1500
        let recv_data: String = lua.state.globals().get("recv_data").unwrap();
1✔
1501
        assert_eq!(recv_data.trim_start(), gmcp_payload);
1✔
1502
    }
1✔
1503
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc