pub struct Minifier;
impl Minifier {
pub fn minify(scon: &str) -> String {
let mut result = String::with_capacity(scon.len());
let mut prev_depth: isize = 0;
let mut is_first = true;
let indent = Self::detect_indent(scon);
for line in scon.lines() {
let trimmed = line.trim();
if trimmed.is_empty() || trimmed.starts_with('#') {
if trimmed.starts_with("#!scon/") {
result.push_str(trimmed);
result.push(';');
}
continue;
}
let depth = Self::calculate_depth(line, indent) as isize;
if !is_first {
let diff = prev_depth - depth;
if diff >= 2 {
for _ in 0..=(diff as usize) {
result.push(';');
}
} else if diff == 1 {
result.push_str(";;");
} else {
result.push(';');
}
}
result.push_str(trimmed);
prev_depth = depth;
if trimmed.ends_with(':') {
prev_depth = depth + 1;
}
if trimmed.starts_with("- ") {
prev_depth = depth + 1;
}
is_first = false;
}
result
}
pub fn expand(minified: &str, indent: usize) -> String {
let mut result = String::with_capacity(minified.len() * 2);
let mut depth: usize = 0;
let mut seg_start = 0;
let mut in_quotes = false;
let bytes = minified.as_bytes();
let len = bytes.len();
let mut is_first = true;
const SPACES: &str = " ";
let mut i = 0;
while i < len {
let c = bytes[i];
if c == b'\\' && in_quotes && i + 1 < len {
i += 2;
continue;
}
if c == b'"' {
in_quotes = !in_quotes;
i += 1;
continue;
}
if c == b';' && !in_quotes {
let mut semi_count = 1usize;
while i + 1 < len && bytes[i + 1] == b';' {
semi_count += 1;
i += 1;
}
let segment = minified[seg_start..i - semi_count + 1].trim();
if !segment.is_empty() {
if !is_first {
result.push('\n');
}
is_first = false;
let spaces = indent * depth;
if spaces > 0 {
if spaces <= SPACES.len() {
result.push_str(&SPACES[..spaces]);
} else {
for _ in 0..spaces { result.push(' '); }
}
}
result.push_str(segment);
if segment.ends_with(':') && !Self::has_value_after_colon(segment) {
depth += 1;
}
if segment.starts_with("- ") {
depth += 1;
}
}
if semi_count >= 2 {
depth = depth.saturating_sub(semi_count - 1);
}
seg_start = i + 1;
i += 1;
continue;
}
i += 1;
}
let segment = minified[seg_start..].trim();
if !segment.is_empty() {
if !is_first {
result.push('\n');
}
let spaces = indent * depth;
if spaces > 0 {
if spaces <= SPACES.len() {
result.push_str(&SPACES[..spaces]);
} else {
for _ in 0..spaces { result.push(' '); }
}
}
result.push_str(segment);
}
result
}
fn has_value_after_colon(s: &str) -> bool {
if let Some(colon_pos) = s.rfind(':') {
let after = s[colon_pos + 1..].trim();
!after.is_empty()
} else {
false
}
}
fn detect_indent(scon: &str) -> usize {
for line in scon.lines() {
let spaces = line.len() - line.trim_start_matches(' ').len();
if spaces > 0 && !line.trim().is_empty() && !line.trim().starts_with('#') {
return spaces;
}
}
1
}
fn calculate_depth(line: &str, indent: usize) -> usize {
let spaces = line.len() - line.trim_start_matches(' ').len();
if indent > 0 { spaces / indent } else { 0 }
}
}