clippier 0.3.0

MoosicBox clippier package
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
//! Lua engine for executing transform scripts.

use std::path::Path;

use mlua::{Function, Lua, LuaSerdeExt, Table, Value as LuaValue};
use serde_json::Value;

use super::context::{DependencyInfo, PackageInfo, TransformContext};

type BoxError = Box<dyn std::error::Error + Send + Sync>;

/// Lua engine for running transform scripts
pub struct TransformEngine {
    lua: Lua,
    trace_mode: bool,
}

impl TransformEngine {
    /// Create a new transform engine
    ///
    /// # Errors
    ///
    /// * Failed to create Lua engine
    /// * Failed to load workspace context
    /// * Failed to register API functions
    pub fn new(workspace_root: &Path) -> Result<Self, BoxError> {
        Self::with_trace(workspace_root, false)
    }

    /// Create a new transform engine with optional trace mode
    ///
    /// # Errors
    ///
    /// * Failed to create Lua engine
    /// * Failed to load workspace context
    /// * Failed to register API functions
    pub fn with_trace(workspace_root: &Path, trace_mode: bool) -> Result<Self, BoxError> {
        let lua = Lua::new();
        let context = TransformContext::new(workspace_root)?;

        // Register helper functions
        register_helpers(&lua)?;

        // Register context API
        register_context_api(&lua, &context, trace_mode)?;

        Ok(Self { lua, trace_mode })
    }

    /// Apply a transform script to the matrix
    ///
    /// # Errors
    ///
    /// * Script fails to compile
    /// * Script encounters runtime error
    /// * Script doesn't return a valid matrix
    pub fn apply_transform(
        &self,
        matrix: &mut Vec<serde_json::Map<String, Value>>,
        script: &str,
    ) -> Result<(), BoxError> {
        let original_len = matrix.len();
        let original_matrix = if self.trace_mode {
            Some(matrix.clone())
        } else {
            None
        };

        if self.trace_mode {
            log::info!("[Transform] Input matrix: {original_len} entries");
        }

        // Convert Rust matrix to Lua value
        let lua_matrix = self.lua.to_value(matrix).map_err(|e| {
            format!(
                "Failed to convert matrix to Lua value: {e}\nMatrix: {}",
                serde_json::to_string_pretty(matrix).unwrap_or_default()
            )
        })?;

        // Load and execute the script
        self.lua
            .load(script)
            .exec()
            .map_err(|e| format!("Failed to load transform script: {e}"))?;

        // Get the transform function
        let transform_fn: Function = self
            .lua
            .globals()
            .get("transform")
            .map_err(|e| format!("Transform script must define a 'transform' function: {e}"))?;

        // Get context table
        let context_table: Table = self
            .lua
            .globals()
            .get("context")
            .map_err(|e| format!("Failed to get context table: {e}"))?;

        // Call transform(context, matrix) and get result
        let result: LuaValue = transform_fn
            .call((context_table, lua_matrix))
            .map_err(|e| {
                let mut err_msg = format!("Transform function failed: {e}");
                if let Some(orig) = &original_matrix {
                    use std::fmt::Write;
                    write!(
                        &mut err_msg,
                        "\n\nMatrix before transform:\n{}",
                        serde_json::to_string_pretty(orig).unwrap_or_default()
                    )
                    .unwrap();
                }
                err_msg
            })?;

        // Convert result back to Rust
        *matrix = self.lua.from_value(result).map_err(|e| {
            format!(
                "Transform function must return a valid matrix array: {e}\n\
                 Make sure your transform function returns the matrix (or modified copy)"
            )
        })?;

        if self.trace_mode {
            let new_len = matrix.len();
            let delta_str = if new_len >= original_len {
                format!("+{}", new_len - original_len)
            } else {
                format!("-{}", original_len - new_len)
            };
            log::info!("[Transform] Output matrix: {new_len} entries ({delta_str} change)");

            if new_len != original_len
                && let Some(orig) = original_matrix
            {
                log::debug!("[Transform] Matrix diff:");
                log::debug!("  Removed: {}", original_len.saturating_sub(new_len));
                log::debug!(
                    "  Before: {}",
                    serde_json::to_string(&orig).unwrap_or_default()
                );
                log::debug!(
                    "  After: {}",
                    serde_json::to_string(matrix).unwrap_or_default()
                );
            }
        }

        Ok(())
    }
}

/// Register helper functions in Lua
fn register_helpers(lua: &Lua) -> Result<(), BoxError> {
    lua.load(
        r"
        -- table.filter: filter elements based on predicate
        function table.filter(t, predicate)
            local result = {}
            for _, v in ipairs(t) do
                if predicate(v) then
                    table.insert(result, v)
                end
            end
            return result
        end

        -- table.map: transform elements
        function table.map(t, fn)
            local result = {}
            for _, v in ipairs(t) do
                table.insert(result, fn(v))
            end
            return result
        end

        -- table.contains: check if value exists
        function table.contains(t, value)
            for _, v in ipairs(t) do
                if v == value then
                    return true
                end
            end
            return false
        end

        -- table.find: find first element matching predicate
        function table.find(t, predicate)
            for _, v in ipairs(t) do
                if predicate(v) then
                    return v
                end
            end
            return nil
        end
    ",
    )
    .exec()
    .map_err(|e| format!("Failed to register helpers: {e}"))?;

    Ok(())
}

/// Convert `mlua::Error` to `BoxError`
fn lua_err(e: &mlua::Error) -> BoxError {
    format!("{e}").into()
}

/// Register context API functions
#[allow(clippy::too_many_lines)]
fn register_context_api(
    lua: &Lua,
    context: &TransformContext,
    trace_mode: bool,
) -> Result<(), BoxError> {
    let globals = lua.globals();

    // Create context table
    let context_table = lua.create_table().map_err(|e| lua_err(&e))?;

    // Register get_package
    let ctx = context.clone();
    context_table
        .set(
            "get_package",
            lua.create_function(move |lua, (_self, name): (mlua::Value, String)| {
                let Some(pkg) = ctx.get_package(&name) else {
                    return Err(mlua::Error::RuntimeError(format!(
                        "Package not found: {name}"
                    )));
                };

                create_package_table(lua, pkg)
            })
            .map_err(|e| lua_err(&e))?,
        )
        .map_err(|e| lua_err(&e))?;

    // Register is_workspace_member
    let ctx = context.clone();
    context_table
        .set(
            "is_workspace_member",
            lua.create_function(move |_lua, (_self, name): (mlua::Value, String)| {
                Ok(ctx.is_workspace_member(&name))
            })
            .map_err(|e| lua_err(&e))?,
        )
        .map_err(|e| lua_err(&e))?;

    // Register get_all_packages
    let ctx = context.clone();
    context_table
        .set(
            "get_all_packages",
            lua.create_function(move |_lua, _self: mlua::Value| Ok(ctx.get_all_packages()))
                .map_err(|e| lua_err(&e))?,
        )
        .map_err(|e| lua_err(&e))?;

    // Register package_depends_on
    let ctx = context.clone();
    context_table
        .set(
            "package_depends_on",
            lua.create_function(
                move |_lua, (_self, pkg, dep): (mlua::Value, String, String)| {
                    Ok(ctx.package_depends_on(&pkg, &dep))
                },
            )
            .map_err(|e| lua_err(&e))?,
        )
        .map_err(|e| lua_err(&e))?;

    // Register feature_exists
    let ctx = context.clone();
    context_table
        .set(
            "feature_exists",
            lua.create_function(
                move |_lua, (_self, pkg, feat): (mlua::Value, String, String)| {
                    Ok(ctx.feature_exists(&pkg, &feat))
                },
            )
            .map_err(|e| lua_err(&e))?,
        )
        .map_err(|e| lua_err(&e))?;

    // Register log function
    context_table
        .set(
            "log",
            lua.create_function(|_lua, (_self, message): (mlua::Value, String)| {
                log::info!("[Transform] {message}");
                Ok(())
            })
            .map_err(|e| lua_err(&e))?,
        )
        .map_err(|e| lua_err(&e))?;

    // Register warn function
    context_table
        .set(
            "warn",
            lua.create_function(|_lua, (_self, message): (mlua::Value, String)| {
                log::warn!("[Transform] {message}");
                Ok(())
            })
            .map_err(|e| lua_err(&e))?,
        )
        .map_err(|e| lua_err(&e))?;

    // Register error function
    context_table
        .set(
            "error",
            lua.create_function(|_lua, (_self, message): (mlua::Value, String)| {
                log::error!("[Transform] {message}");
                Ok(())
            })
            .map_err(|e| lua_err(&e))?,
        )
        .map_err(|e| lua_err(&e))?;

    // Register debug function for structured logging
    context_table
        .set(
            "debug",
            lua.create_function(move |lua, (_self, data): (mlua::Value, mlua::Value)| {
                // Try to convert to JSON for pretty printing
                let debug_str = lua
                    .from_value::<serde_json::Value>(data.clone())
                    .map_or_else(
                        |_| format!("{data:?}"),
                        |json_val| {
                            serde_json::to_string_pretty(&json_val)
                                .unwrap_or_else(|_| format!("{data:?}"))
                        },
                    );

                if trace_mode {
                    log::debug!("[Transform Debug]\n{debug_str}");
                } else {
                    log::trace!("[Transform Debug]\n{debug_str}");
                }
                Ok(())
            })
            .map_err(|e| lua_err(&e))?,
        )
        .map_err(|e| lua_err(&e))?;

    // Register inspect function for dependency visualization
    let ctx = context.clone();
    context_table
        .set(
            "inspect",
            lua.create_function(
                move |_lua, (_self, pkg_name, feature): (mlua::Value, String, String)| {
                    let Some(pkg) = ctx.get_package(&pkg_name) else {
                        return Ok(format!("Package '{pkg_name}' not found"));
                    };

                    let mut output = format!("Inspecting {pkg_name}:{feature}\n");

                    if !pkg.has_feature(&feature) {
                        use std::fmt::Write;
                        writeln!(&mut output, "  ⚠ Feature '{feature}' does not exist").unwrap();
                        return Ok(output);
                    }

                    let deps = pkg.feature_activates_dependencies(&feature);
                    if deps.is_empty() {
                        output.push_str("  No dependencies activated\n");
                    } else {
                        output.push_str("  Activates dependencies:\n");
                        for dep in deps {
                            use std::fmt::Write;
                            writeln!(
                                &mut output,
                                "{}{}",
                                dep.name,
                                if dep.features.is_empty() {
                                    String::new()
                                } else {
                                    format!("/{}", dep.features.join(","))
                                }
                            )
                            .unwrap();
                        }
                    }

                    Ok(output)
                },
            )
            .map_err(|e| lua_err(&e))?,
        )
        .map_err(|e| lua_err(&e))?;

    globals
        .set("context", context_table)
        .map_err(|e| lua_err(&e))?;

    Ok(())
}

/// Create a Lua table representing a package
fn create_package_table(lua: &Lua, pkg: &PackageInfo) -> mlua::Result<Table> {
    let pkg_table = lua.create_table()?;

    // Basic fields
    pkg_table.set("name", pkg.name.clone())?;
    pkg_table.set("path", pkg.path.to_string_lossy().to_string())?;

    // Add methods
    let pkg_clone = pkg.clone();
    pkg_table.set(
        "depends_on",
        lua.create_function(move |_lua, (_self, dep_name): (mlua::Value, String)| {
            Ok(pkg_clone.depends_on(&dep_name))
        })?,
    )?;

    let pkg_clone = pkg.clone();
    pkg_table.set(
        "has_feature",
        lua.create_function(move |_lua, (_self, feature): (mlua::Value, String)| {
            Ok(pkg_clone.has_feature(&feature))
        })?,
    )?;

    let pkg_clone = pkg.clone();
    pkg_table.set(
        "feature_definition",
        lua.create_function(move |_lua, (_self, feature): (mlua::Value, String)| {
            Ok(pkg_clone.feature_definition(&feature).cloned())
        })?,
    )?;

    let pkg_clone = pkg.clone();
    pkg_table.set(
        "feature_activates_dependencies",
        lua.create_function(move |lua, (_self, feature): (mlua::Value, String)| {
            let deps = pkg_clone.feature_activates_dependencies(&feature);
            create_dependencies_table(lua, &deps)
        })?,
    )?;

    let pkg_clone = pkg.clone();
    pkg_table.set(
        "skips_feature_on_os",
        lua.create_function(
            move |_lua, (_self, feature, os): (mlua::Value, String, String)| {
                Ok(pkg_clone.skips_feature_on_os(&feature, &os))
            },
        )?,
    )?;

    let pkg_clone = pkg.clone();
    pkg_table.set(
        "get_all_features",
        lua.create_function(move |_lua, _self: mlua::Value| Ok(pkg_clone.get_all_features()))?,
    )?;

    Ok(pkg_table)
}

/// Create a Lua table representing dependencies
fn create_dependencies_table(lua: &Lua, deps: &[DependencyInfo]) -> mlua::Result<Table> {
    let deps_table = lua.create_table()?;

    for (i, dep) in deps.iter().enumerate() {
        let dep_entry = lua.create_table()?;
        dep_entry.set("name", dep.name.clone())?;
        dep_entry.set("optional", dep.optional)?;
        dep_entry.set("workspace_member", dep.workspace_member)?;
        dep_entry.set("features", dep.features.clone())?;

        deps_table.set(i + 1, dep_entry)?;
    }

    Ok(deps_table)
}