Parse cube() arguments: supports all LuaCAD forms. Returns (w, d, h, center).
( args: &mlua::MultiValue, )
| 42 | fn table_get_u32(t: &mlua::Table, key: &str) -> Option<u32> { |
| 43 | table_get_f32(t, key).map(|v| v as u32) |
| 44 | } |
| 45 | |
| 46 | /// Get segments from table: checks "segments" and "fn" keys, returns default otherwise. |
| 47 | fn table_segments(t: &mlua::Table, default: u32) -> u32 { |
| 48 | table_get_u32(t, "segments") |
| 49 | .or_else(|| table_get_u32(t, "fn")) |
| 50 | .unwrap_or(default) |
| 51 | } |
| 52 | |
| 53 | /// Levenshtein distance, used to suggest a parameter the user probably meant. |
| 54 | pub fn edit_distance(a: &str, b: &str) -> usize { |
| 55 | let b_chars: Vec<char> = b.chars().collect(); |
| 56 | let mut prev: Vec<usize> = (0..=b_chars.len()).collect(); |
| 57 | let mut curr = vec![0; b_chars.len() + 1]; |
| 58 | |
| 59 | for (i, ca) in a.chars().enumerate() { |
| 60 | curr[0] = i + 1; |
| 61 | for (j, cb) in b_chars.iter().enumerate() { |
| 62 | let cost = usize::from(ca != *cb); |
| 63 | curr[j + 1] = (prev[j] + cost).min(prev[j + 1] + 1).min(curr[j] + 1); |
| 64 | } |
| 65 | std::mem::swap(&mut prev, &mut curr); |
| 66 | } |
| 67 | prev[b_chars.len()] |
| 68 | } |
| 69 | |
| 70 | /// Reject named parameters a function does not understand. |
| 71 | /// |
| 72 | /// Silently dropping them turns a typo — or an OpenSCAD habit such as |
| 73 | /// `$fn` — into a model that is quietly the wrong shape, which is the |
| 74 | /// hardest kind of CAD bug to notice. Only string keys are checked, so the |
| 75 | /// positional forms (`cube { 1, 2, 3 }`) are unaffected. |
| 76 | fn check_table_keys( |
| 77 | t: &mlua::Table, |
| 78 | func: &str, |
| 79 | allowed: &[&str], |
| 80 | ) -> mlua::Result<()> { |
| 81 | let mut unknown: Vec<String> = Vec::new(); |
| 82 | for pair in t.pairs::<mlua::Value, mlua::Value>() { |
| 83 | let (key, _) = pair?; |
| 84 | if let LuaValue::String(s) = key { |
| 85 | let key = s.to_str()?.to_string(); |
| 86 | if !allowed.contains(&key.as_str()) { |
| 87 | unknown.push(key); |
| 88 | } |
| 89 | } |
| 90 | } |
| 91 | |
| 92 | if unknown.is_empty() { |
| 93 | return Ok(()); |
| 94 | } |
| 95 | unknown.sort(); |
| 96 | |
| 97 | let mut msg = format!( |
| 98 | "{func}() got unknown parameter{} {}", |
| 99 | if unknown.len() == 1 { "" } else { "s" }, |
| 100 | unknown |
| 101 | .iter() |
no test coverage detected