use crate::error::FormatError;
use crate::options::FormatOptions;
use crate::script;
use crate::template;
use vize_atelier_sfc::{parse_sfc, SfcParseOptions};
use vize_carton::Allocator;
#[derive(Debug, Clone)]
pub struct FormatResult {
pub code: String,
pub changed: bool,
}
pub struct GlyphFormatter<'a> {
options: &'a FormatOptions,
allocator: &'a Allocator,
}
impl<'a> GlyphFormatter<'a> {
#[inline]
pub fn new(options: &'a FormatOptions, allocator: &'a Allocator) -> Self {
Self { options, allocator }
}
pub fn format(&self, source: &str) -> Result<FormatResult, FormatError> {
let descriptor = parse_sfc(source, SfcParseOptions::default())?;
let newline = self.options.newline_bytes();
let estimated_size = self.estimate_output_size(source, &descriptor);
let mut output = Vec::with_capacity(estimated_size);
if let Some(script_setup) = &descriptor.script_setup {
self.format_script_block_fast(
&mut output,
&script_setup.content,
true,
&script_setup.lang,
)?;
output.extend_from_slice(newline);
output.extend_from_slice(newline);
}
if let Some(script) = &descriptor.script {
self.format_script_block_fast(&mut output, &script.content, false, &script.lang)?;
output.extend_from_slice(newline);
output.extend_from_slice(newline);
}
if let Some(template) = &descriptor.template {
self.format_template_block_fast(&mut output, &template.content, &template.lang)?;
output.extend_from_slice(newline);
output.extend_from_slice(newline);
}
for style in &descriptor.styles {
self.format_style_block_fast(&mut output, &style.content, style.scoped, &style.lang)?;
output.extend_from_slice(newline);
output.extend_from_slice(newline);
}
for block in &descriptor.custom_blocks {
self.format_custom_block_fast(&mut output, &block.block_type, &block.content)?;
output.extend_from_slice(newline);
output.extend_from_slice(newline);
}
while output
.last()
.is_some_and(|&b| b == b'\n' || b == b'\r' || b == b' ' || b == b'\t')
{
output.pop();
}
output.extend_from_slice(newline);
let code = unsafe { String::from_utf8_unchecked(output) };
let changed = code != source;
Ok(FormatResult { code, changed })
}
#[inline]
fn estimate_output_size(
&self,
source: &str,
descriptor: &vize_atelier_sfc::SfcDescriptor<'_>,
) -> usize {
let mut size = source.len();
if descriptor.script_setup.is_some() || descriptor.script.is_some() {
size += 256; }
if descriptor.template.is_some() {
size += 128; }
size
}
#[inline]
fn format_script_block_fast(
&self,
output: &mut Vec<u8>,
content: &str,
is_setup: bool,
lang: &Option<std::borrow::Cow<'_, str>>,
) -> Result<(), FormatError> {
let formatted_content =
script::format_script_content(content.trim(), self.options, self.allocator)?;
output.extend_from_slice(b"<script");
if is_setup {
output.extend_from_slice(b" setup");
}
if let Some(lang) = lang {
output.extend_from_slice(b" lang=\"");
output.extend_from_slice(lang.as_bytes());
output.push(b'"');
}
output.push(b'>');
output.extend_from_slice(self.options.newline_bytes());
if self.options.vue_indent_script_and_style {
let indent = self.options.indent_bytes();
for line in formatted_content.as_bytes().split(|&b| b == b'\n') {
if !line.is_empty() && line != b"\r" {
output.extend_from_slice(indent);
}
output.extend_from_slice(line);
output.extend_from_slice(self.options.newline_bytes());
}
} else {
output.extend_from_slice(formatted_content.as_bytes());
if !formatted_content.ends_with('\n') {
output.extend_from_slice(self.options.newline_bytes());
}
}
output.extend_from_slice(b"</script>");
Ok(())
}
#[inline]
fn format_template_block_fast(
&self,
output: &mut Vec<u8>,
content: &str,
lang: &Option<std::borrow::Cow<'_, str>>,
) -> Result<(), FormatError> {
let formatted_content = template::format_template_content(content, self.options)?;
output.extend_from_slice(b"<template");
if let Some(lang) = lang {
output.extend_from_slice(b" lang=\"");
output.extend_from_slice(lang.as_bytes());
output.push(b'"');
}
output.push(b'>');
output.extend_from_slice(self.options.newline_bytes());
let indent = self.options.indent_bytes();
for line in formatted_content.as_bytes().split(|&b| b == b'\n') {
if !line.is_empty() && line != b"\r" {
output.extend_from_slice(indent);
}
output.extend_from_slice(line);
output.extend_from_slice(self.options.newline_bytes());
}
output.extend_from_slice(b"</template>");
Ok(())
}
#[inline]
fn format_style_block_fast(
&self,
output: &mut Vec<u8>,
content: &str,
scoped: bool,
lang: &Option<std::borrow::Cow<'_, str>>,
) -> Result<(), FormatError> {
let formatted_content = content.trim();
output.extend_from_slice(b"<style");
if scoped {
output.extend_from_slice(b" scoped");
}
if let Some(lang) = lang {
output.extend_from_slice(b" lang=\"");
output.extend_from_slice(lang.as_bytes());
output.push(b'"');
}
output.push(b'>');
output.extend_from_slice(self.options.newline_bytes());
if self.options.vue_indent_script_and_style {
let indent = self.options.indent_bytes();
for line in formatted_content.as_bytes().split(|&b| b == b'\n') {
if !line.is_empty() && line != b"\r" {
output.extend_from_slice(indent);
}
output.extend_from_slice(line);
output.extend_from_slice(self.options.newline_bytes());
}
} else {
output.extend_from_slice(formatted_content.as_bytes());
output.extend_from_slice(self.options.newline_bytes());
}
output.extend_from_slice(b"</style>");
Ok(())
}
#[inline]
fn format_custom_block_fast(
&self,
output: &mut Vec<u8>,
block_type: &str,
content: &str,
) -> Result<(), FormatError> {
output.push(b'<');
output.extend_from_slice(block_type.as_bytes());
output.push(b'>');
output.extend_from_slice(self.options.newline_bytes());
output.extend_from_slice(content.trim().as_bytes());
output.extend_from_slice(self.options.newline_bytes());
output.extend_from_slice(b"</");
output.extend_from_slice(block_type.as_bytes());
output.push(b'>');
Ok(())
}
}