This commit is contained in:
Matthieu Jolimaitre 2025-06-23 21:21:36 +02:00
commit 87266e7d54
8 changed files with 296 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

7
Cargo.lock generated Normal file
View file

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "boxon"
version = "0.1.0"

6
Cargo.toml Normal file
View file

@ -0,0 +1,6 @@
[package]
name = "boxon"
version = "0.1.0"
edition = "2024"
[dependencies]

3
README.md Normal file
View file

@ -0,0 +1,3 @@
# boxon
Utility to convert ascii boxes into ansi boxes inside text files.

2
rustfmt.toml Normal file
View file

@ -0,0 +1,2 @@
hard_tabs = true
max_width = 120

235
src/main.rs Normal file
View file

@ -0,0 +1,235 @@
use std::{env::args, fs};
fn main() {
let file = args().nth(1).expect("Usage : boxon <file>");
let content = fs::read_to_string(&file).unwrap_or_else(|_| panic!("Could not read file '{file}'."));
let mut grid = Grid::from(content);
grid.fix();
let content: String = grid.into();
fs::write(&file, content).unwrap_or_else(|_| panic!("Could not write result to file '{file}'."));
}
#[derive(Debug, Clone)]
struct Grid(Vec<Vec<char>>);
impl<T: AsRef<str>> From<T> for Grid {
fn from(value: T) -> Self {
Self(value.as_ref().lines().map(str::chars).map(Iterator::collect).collect())
}
}
impl From<Grid> for String {
fn from(val: Grid) -> Self {
val.0
.iter()
.map(|l| l.iter().collect::<String>())
.collect::<Vec<_>>()
.join("\n")
}
}
impl Grid {
fn fix(&mut self) -> usize {
let mut result = 0;
let copy = self.clone();
for (y, line) in self.0.iter_mut().enumerate() {
for (x, character) in line.iter_mut().enumerate() {
if let Some(cross) = copy.cross_at(x as i32, y as i32) {
let new_char = match cross {
Cross(
((), MM, ()), //
(MM, MM, MM), //
((), MM, ()), //
) => '┼',
Cross(
((), MM, ()), //
(F_, MM, MM), //
((), MM, ()), //
) => '├',
Cross(
((), MM, ()), //
(MM, MM, F_), //
((), MM, ()), //
) => '┤',
Cross(
((), MM, ()), //
(MM, MM, MM), //
((), F_, ()), //
) => '┴',
Cross(
((), F_, ()), //
(MM, MM, MM), //
((), MM, ()), //
) => '┬',
Cross(
((), F_, ()), //
(MM, MM, MM), //
((), F_, ()), //
) => '─',
Cross(
((), MM, ()), //
(F_, MM, F_), //
((), MM, ()), //
) => '│',
Cross(
((), MM, ()), //
(MM, MM, F_), //
((), F_, ()), //
) => '┘',
Cross(
((), F_, ()), //
(MM, MM, F_), //
((), MM, ()), //
) => '┐',
Cross(
((), F_, ()), //
(F_, MM, MM), //
((), MM, ()), //
) => '┌',
Cross(
((), MM, ()), //
(F_, MM, MM), //
((), F_, ()), //
) => '└',
_ => continue,
};
*character = new_char;
result += 1;
}
}
}
result
}
#[rustfmt::skip]
fn cross_at(&self, x: i32, y: i32) -> Option<Cross> {
fn at(g: &Grid, x: i32, y: i32) -> Option<Cross> {
if x < 0 || y < 0 {
None
} else {
Some(connects(*g.0.get(y as usize)?.get(x as usize)?))
}
}
let get = |x: i32, y: i32| -> Cross {
at(self, x, y).unwrap_or(connects(' '))
};
let c = get(x, y);
let its_y = y;
Some(Cross(
( (), get(x, y - 1).d() && c.u(), ()),
(get(x - 1, y).r() && c.l(), get(x, its_y).c(), get(x + 1, y).l() && c.r()),
( (), get(x, y + 1).u() && c.d(), ()),
))
}
}
fn connects(c: char) -> Cross {
match c {
'-' => Cross(
((), F_, ()), //
(MM, MM, MM), //
((), F_, ()), //
),
'|' => Cross(
((), MM, ()), //
(F_, MM, F_), //
((), MM, ()), //
),
'+' => Cross(
((), MM, ()), //
(MM, MM, MM), //
((), MM, ()), //
),
'┼' => Cross(
((), MM, ()), //
(MM, MM, MM), //
((), MM, ()), //
),
'├' => Cross(
((), MM, ()), //
(F_, MM, MM), //
((), MM, ()), //
),
'┤' => Cross(
((), MM, ()), //
(MM, MM, F_), //
((), MM, ()), //
),
'┴' => Cross(
((), MM, ()), //
(MM, MM, MM), //
((), F_, ()), //
),
'┬' => Cross(
((), F_, ()), //
(MM, MM, MM), //
((), MM, ()), //
),
'─' => Cross(
((), F_, ()), //
(MM, MM, MM), //
((), F_, ()), //
),
'│' => Cross(
((), MM, ()), //
(F_, MM, F_), //
((), MM, ()), //
),
'┘' => Cross(
((), MM, ()), //
(MM, MM, F_), //
((), F_, ()), //
),
'┐' => Cross(
((), F_, ()), //
(MM, MM, F_), //
((), MM, ()), //
),
'┌' => Cross(
((), F_, ()), //
(F_, MM, MM), //
((), MM, ()), //
),
'└' => Cross(
((), MM, ()), //
(F_, MM, MM), //
((), F_, ()), //
),
_ => Cross(
((), F_, ()), //
(F_, F_, F_), //
((), F_, ()), //
),
}
}
const F_: bool = false;
const MM: bool = true;
type Empt = ();
struct Cross(
(Empt, bool, Empt), //
(bool, bool, bool), //
(Empt, bool, Empt), //
);
impl Cross {
fn u(&self) -> bool {
self.0.1
}
fn d(&self) -> bool {
self.2.1
}
fn l(&self) -> bool {
self.1.0
}
fn r(&self) -> bool {
self.1.2
}
fn c(&self) -> bool {
self.1.1
}
}

21
test/out.txt Normal file
View file

@ -0,0 +1,21 @@
this is a normalized data table diagram.
+----------------+
|USERS BY EMAIL |
+--+-----+-------+
|id|email|user_id|
+--+-----+-------+
| 1|b@o.b| 1|
| 2|c@r.l| 2|
+--+-----+-------+
+---------------+
|USERS |
+--+--------+---+
|id| name|age|
+--+--------+---+
| 1| bob| 19|
| 2| charlie| 23|
+--+--------+---+

21
test/source.txt Normal file
View file

@ -0,0 +1,21 @@
this is a normalized data table diagram.
+----------------+
|USERS BY EMAIL |
+--+-----+-------+
|id|email|user_id|
+--+-----+-------+
| 1|b@o.b| 1|
| 2|c@r.l| 2|
+--+-----+-------+
+---------------+
|USERS |
+--+--------+---+
|id| name|age|
+--+--------+---+
| 1| bob| 19|
| 2| charlie| 23|
+--+--------+---+