use crate::mappings::Mappings;
use std::io::Write;
pub fn format_machine(human_dx: &str) -> Result<Vec<u8>, String> {
let mappings = Mappings::get();
let mut output = Vec::new();
let lines: Vec<&str> = human_dx.lines().collect();
let mut i = 0;
while i < lines.len() {
let line = lines[i].trim();
i += 1;
if line.is_empty() || line.starts_with('#') {
continue;
}
if line.starts_with("# ") && line.contains("TABLE") {
while i < lines.len()
&& (lines[i].trim().starts_with('#') || lines[i].trim().starts_with('-'))
{
i += 1;
}
continue;
}
if let Some((key, value)) = line.split_once(':') {
let key = key.trim();
let value = value.trim();
if let Some(rest) = key.strip_prefix('^') {
output.extend_from_slice(b"^");
let rest = rest.trim();
let compressed = compress_full_key(rest, mappings);
output.extend_from_slice(compressed.as_bytes());
} else {
let compressed = compress_full_key(key, mappings);
output.extend_from_slice(compressed.as_bytes());
}
output.extend_from_slice(b":");
output.extend_from_slice(value.as_bytes());
if i < lines.len() && lines[i].trim().starts_with('^') {
continue;
}
output.extend_from_slice(b"\n");
}
else if line.contains('>') {
let Some((key, values)) = line.split_once('>') else {
continue; };
let key = key.trim();
let values = values.trim();
let compressed = compress_full_key(key, mappings);
output.extend_from_slice(compressed.as_bytes());
output.extend_from_slice(b">");
let compressed_values = values
.split('|')
.map(|s| s.trim())
.collect::<Vec<_>>()
.join("|");
output.extend_from_slice(compressed_values.as_bytes());
output.extend_from_slice(b"\n");
}
else if line.contains(char::is_whitespace) && !line.contains(':') {
let parts: Vec<&str> = line.split_whitespace().collect();
if parts
.iter()
.all(|p| p.chars().next().map(|c| c.is_uppercase()).unwrap_or(false))
{
let compressed_cols: Vec<String> = parts
.iter()
.map(|col| compress_table_column(col, mappings))
.collect();
output.extend_from_slice(compressed_cols.join(" ").as_bytes());
output.extend_from_slice(b"\n");
} else {
output.extend_from_slice(line.as_bytes());
output.extend_from_slice(b"\n");
}
}
}
Ok(output)
}
#[inline]
fn compress_full_key(full_key: &str, mappings: &Mappings) -> String {
if full_key.contains('.') {
full_key
.split('.')
.map(|part| mappings.compress_key(part))
.collect::<Vec<_>>()
.join(".")
} else {
mappings.compress_key(full_key)
}
}
fn compress_table_column(col: &str, mappings: &Mappings) -> String {
mappings.compress_key(col)
}
pub fn compress_to_writer<W: Write>(human_dx: &str, writer: &mut W) -> Result<(), String> {
let compressed = format_machine(human_dx)?;
writer
.write_all(&compressed)
.map_err(|e| format!("Failed to write: {}", e))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_compression() {
let human = "context.name : dx\n^version : 0.0.1";
let machine = format_machine(human).unwrap();
let result = String::from_utf8(machine).unwrap();
assert!(result.contains("c.n:dx"));
assert!(result.contains("v:0.0.1"));
}
#[test]
fn test_array_compression() {
let human = "name > frontend/www | frontend/mobile";
let machine = format_machine(human).unwrap();
let result = String::from_utf8(machine).unwrap();
assert!(result.contains("n>"));
assert!(result.contains("frontend/www"));
assert!(result.contains("frontend/mobile"));
}
#[test]
fn test_roundtrip() {
let _original = "c.n:dx^v:0.0.1\nws>frontend|backend";
let human = "context.name: dx\nworkspace > frontend | backend";
let compressed = format_machine(human).unwrap();
assert!(!compressed.is_empty());
}
}