commit f4354cbb9f46e00ee4831657d32095d50758798f
parent 762308025171194a7d1afcc125f95d7d5e12810d
Author: Sylvia Ivory <git@sivory.net>
Date: Sun, 22 Jun 2025 00:36:22 -0700
Improve string escaping
Diffstat:
6 files changed, 103 insertions(+), 16 deletions(-)
diff --git a/Cargo.lock b/Cargo.lock
@@ -488,6 +488,7 @@ dependencies = [
name = "manen"
version = "0.1.0"
dependencies = [
+ "aho-corasick",
"clap",
"color-eyre",
"comfy-table",
diff --git a/Cargo.toml b/Cargo.toml
@@ -4,6 +4,7 @@ version = "0.1.0"
edition = "2024"
[dependencies]
+aho-corasick = "1.1.3"
clap = { version = "4.5.40", features = ["derive"] }
color-eyre = "0.6.5"
comfy-table = "7.1.4"
diff --git a/src/editor.rs b/src/editor.rs
@@ -117,7 +117,7 @@ impl Editor {
let value: LuaValue = self.lua.load(line).set_name("=stdin").eval()?;
let stringify = match value {
- LuaValue::Table(tbl) => self.table_format.format(&self.lua, &tbl)?,
+ LuaValue::Table(tbl) => self.table_format.format(&self.lua, &tbl, true)?,
value => lua_to_string(&value)?,
};
diff --git a/src/format.rs b/src/format.rs
@@ -2,6 +2,10 @@ use std::collections::HashMap;
use comfy_table::{Table, presets::UTF8_FULL_CONDENSED};
use mlua::prelude::*;
+use nu_ansi_term::Color;
+use reedline::Highlighter;
+
+use crate::{highlight::LuaHighlighter, inspect::cleanup_string};
const INSPECT_CODE: &str = include_str!("inspect.lua");
@@ -15,19 +19,9 @@ fn addr_tbl(tbl: &LuaTable) -> String {
format!("table@{:?}", tbl.to_pointer())
}
-fn convert_string(string: &LuaString) -> String {
- let bytes = string
- .as_bytes()
- .iter()
- .flat_map(|b| std::ascii::escape_default(*b))
- .collect::<Vec<_>>();
-
- String::from_utf8_lossy(&bytes).to_string()
-}
-
pub fn lua_to_string(value: &LuaValue) -> LuaResult<String> {
match value {
- LuaValue::String(string) => Ok(convert_string(string)),
+ LuaValue::String(string) => Ok(cleanup_string(string)),
LuaValue::Table(tbl) => Ok(addr_tbl(tbl)),
value => value.to_string(),
}
@@ -114,17 +108,34 @@ fn comfy_table(
}
impl TableFormat {
- pub fn format(&self, lua: &Lua, tbl: &LuaTable) -> LuaResult<String> {
+ pub fn format(&self, lua: &Lua, tbl: &LuaTable, colorize: bool) -> LuaResult<String> {
match self {
- TableFormat::Address => Ok(format!("table@{:?}", tbl.to_pointer())),
+ TableFormat::Address => {
+ if colorize {
+ Ok(format!(
+ "{}{}{}",
+ Color::LightBlue.paint("table"),
+ Color::Default.paint("@"),
+ Color::LightYellow.paint(format!("{:?}", tbl.to_pointer()))
+ ))
+ } else {
+ Ok(format!("table@{:?}", tbl.to_pointer()))
+ }
+ }
TableFormat::Inspect => {
if let Some(inspect) = lua.globals().get::<Option<LuaTable>>("_inspect")? {
- inspect.call::<String>(tbl)
+ let out = inspect.call::<String>(tbl)?;
+
+ if colorize {
+ Ok(LuaHighlighter::new().highlight(&out, 0).render_simple())
+ } else {
+ Ok(out)
+ }
} else {
let inspect: LuaTable = lua.load(INSPECT_CODE).eval()?;
lua.globals().set("_inspect", inspect)?;
- self.format(lua, tbl)
+ self.format(lua, tbl, colorize)
}
}
TableFormat::ComfyTable(recursive) => {
diff --git a/src/inspect.rs b/src/inspect.rs
@@ -0,0 +1,73 @@
+use aho_corasick::AhoCorasick;
+use mlua::prelude::*;
+
+fn escape_control(s: &str) -> String {
+ let mut escapes = vec![
+ String::from("\x07"),
+ String::from("\x08"),
+ String::from("\x0C"),
+ String::from("\n"),
+ String::from("\r"),
+ String::from("\t"),
+ String::from("\x0B"),
+ String::from("\x7F"),
+ ];
+
+ let mut replacements = vec![
+ String::from("\\a"),
+ String::from("\\b"),
+ String::from("\\f"),
+ String::from("\\n"),
+ String::from("\\r"),
+ String::from("\\t"),
+ String::from("\\v"),
+ String::from("\\127"),
+ ];
+
+ for i in 0..=31 {
+ escapes.push(String::from_utf8_lossy(&[i]).to_string());
+ replacements.push(format!("\\{i}"));
+ }
+
+ // TODO; reuse
+ let ac = AhoCorasick::new(escapes).unwrap();
+
+ ac.replace_all(s, &replacements).replace("\\\\x", "\\x")
+}
+
+fn remove_invalid(mut bytes: &[u8]) -> String {
+ let mut buffer = String::new();
+
+ loop {
+ match str::from_utf8(bytes) {
+ Ok(s) => {
+ buffer.push_str(s);
+ return buffer;
+ }
+ Err(e) => {
+ let (valid, invalid) = bytes.split_at(e.valid_up_to());
+
+ if !valid.is_empty() {
+ // SAFETY: We already know the bytes until this point are valid
+ buffer.push_str(unsafe { str::from_utf8_unchecked(valid) })
+ }
+
+ let error_len = if let Some(len) = e.error_len() {
+ len
+ } else {
+ return buffer;
+ };
+
+ for bad_byte in &invalid[..error_len] {
+ buffer.push_str(&format!("\\x{:X?}", bad_byte));
+ }
+
+ bytes = &invalid[error_len..];
+ }
+ }
+ }
+}
+
+pub fn cleanup_string(lua_str: &LuaString) -> String {
+ escape_control(&remove_invalid(&lua_str.as_bytes()))
+}
diff --git a/src/main.rs b/src/main.rs
@@ -12,6 +12,7 @@ use reedline::Highlighter;
mod editor;
mod format;
mod highlight;
+mod inspect;
mod validator;
#[derive(Parser)]