use crate::command::Command;
use std::collections::HashMap;
use std::io::Write;
pub use self::config::{FloatFormat, FormatterOptions, NumberFormat, ParamFormatSelector, WriterConfig};
mod config;
mod formatters;
mod generators;
pub struct Writer<T: Write> {
writer: T,
config: WriterConfig,
current_indent: usize,
last_was_newline: bool,
}
impl<T: Write> Writer<T> {
pub fn new(writer: T, config: WriterConfig) -> Self {
Self {
writer,
config,
current_indent: 0,
last_was_newline: false,
}
}
pub fn write_command(&mut self, command: &Command) -> std::io::Result<()> {
self.write_command_with_options(command, None, None)
}
pub fn write_command_with_options(
&mut self,
command: &Command,
options: Option<&FormatterOptions>,
param_options: Option<&HashMap<ParamFormatSelector, &FormatterOptions>>,
) -> std::io::Result<()> {
let effective_options =
generators::Generators::get_effective_options(&command.name, options, &self.config);
if effective_options.newline_before && !self.last_was_newline {
self.newline()?;
}
generators::Generators::write_indent(
&mut self.writer,
self.current_indent,
&effective_options,
)?;
generators::Generators::write_command_with_param_options(
&mut self.writer,
command,
&self.config,
&effective_options,
param_options,
self.current_indent,
)?;
writeln!(self.writer)?;
if effective_options.newline_after {
self.newline()?;
} else {
self.last_was_newline = false;
}
Ok(())
}
pub fn inc_indent(&mut self) {
self.current_indent += 1;
}
pub fn dec_indent(&mut self) {
if self.current_indent > 0 {
self.current_indent -= 1;
}
}
pub fn get_indent(&self) -> usize {
self.current_indent
}
pub fn newline(&mut self) -> std::io::Result<()> {
writeln!(self.writer)?;
self.last_was_newline = true;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::command::{Command, Parameter};
#[test]
fn test_write_basic_command() {
let cmd = Command::new(
"character",
vec![Parameter::from("Alice"), Parameter::from("Hello, world!")],
);
let config = WriterConfig::default();
let mut buffer = Vec::new();
let mut writer = Writer::new(&mut buffer, config);
writer.write_command(&cmd).unwrap();
let result = String::from_utf8(buffer).unwrap();
assert_eq!(result, "#character Alice \"Hello, world!\"\n");
}
#[test]
fn test_write_text_command() {
let cmd = Command::new_text("Hello, world!");
let config = WriterConfig::default();
let mut buffer = Vec::new();
let mut writer = Writer::new(&mut buffer, config);
writer.write_command(&cmd).unwrap();
let result = String::from_utf8(buffer).unwrap();
assert_eq!(result, "Hello, world!\n");
}
#[test]
fn test_write_annotation_command() {
let cmd = Command::new_annotation("This is an annotation");
let config = WriterConfig::default();
let mut buffer = Vec::new();
let mut writer = Writer::new(&mut buffer, config);
writer.write_command(&cmd).unwrap();
let result = String::from_utf8(buffer).unwrap();
assert_eq!(result, "## This is an annotation\n");
}
#[test]
fn test_write_number_command() {
let cmd = Command::new_number(123, vec![Parameter::from("extra")]);
let config = WriterConfig::default();
let mut buffer = Vec::new();
let mut writer = Writer::new(&mut buffer, config);
writer.write_command(&cmd).unwrap();
let result = String::from_utf8(buffer).unwrap();
assert_eq!(result, "#123 extra\n");
}
#[test]
fn test_write_with_custom_options() {
let cmd = Command::new("character", vec![Parameter::from("Alice")]);
let config = WriterConfig::default();
let mut buffer = Vec::new();
let mut writer = Writer::new(&mut buffer, config);
let custom_options = FormatterOptions {
newline_before: true,
newline_after: true,
..Default::default()
};
writer
.write_command_with_options(&cmd, Some(&custom_options), None)
.unwrap();
let result = String::from_utf8(buffer).unwrap();
assert_eq!(result, "\n#character Alice\n\n");
}
#[test]
fn test_write_with_force_quotes() {
let cmd = Command::new(
"character",
vec![Parameter::from("Alice"), Parameter::from("Bob")],
);
let config = WriterConfig {
global_options: FormatterOptions {
force_quotes_for_vars: true,
..Default::default()
},
..Default::default()
};
let mut buffer = Vec::new();
let mut writer = Writer::new(&mut buffer, config);
writer.write_command(&cmd).unwrap();
let result = String::from_utf8(buffer).unwrap();
assert_eq!(result, "#character \"Alice\" \"Bob\"\n");
}
#[test]
fn test_write_with_invalid_var_names() {
let cmd = Command::new(
"character",
vec![
Parameter::from("123invalid"),
Parameter::from("with spaces"),
],
);
let config = WriterConfig::default();
let mut buffer = Vec::new();
let mut writer = Writer::new(&mut buffer, config);
writer.write_command(&cmd).unwrap();
let result = String::from_utf8(buffer).unwrap();
assert_eq!(result, "#character \"123invalid\" \"with spaces\"\n");
}
#[test]
fn test_write_with_number_formats() {
let cmd = Command::new(
"test",
vec![
Parameter::from(42),
Parameter::from(255),
Parameter::from(7),
],
);
let mut param_options = HashMap::new();
let hex_opt = FormatterOptions {
number_format: NumberFormat::Hex,
..Default::default()
};
param_options.insert(ParamFormatSelector::Position(0), &hex_opt);
let octal_opt = FormatterOptions {
number_format: NumberFormat::Octal,
..Default::default()
};
param_options.insert(ParamFormatSelector::Position(1), &octal_opt);
let binary_opt = FormatterOptions {
number_format: NumberFormat::Binary,
..Default::default()
};
param_options.insert(ParamFormatSelector::Position(2), &binary_opt);
let config = WriterConfig::default();
let mut buffer = Vec::new();
let mut writer = Writer::new(&mut buffer, config);
writer
.write_command_with_options(&cmd, None, Some(¶m_options))
.unwrap();
let result = String::from_utf8(buffer).unwrap();
assert_eq!(result, "#test 0x2a 0o377 0b111\n");
}
#[test]
fn test_write_with_param_newlines() {
let cmd = Command::new(
"test",
vec![
Parameter::from("param1"),
Parameter::from("param2"),
Parameter::from("param3"),
],
);
let mut param_options = HashMap::new();
let nl_after = FormatterOptions {
newline_after_param: true,
..Default::default()
};
param_options.insert(ParamFormatSelector::Position(0), &nl_after);
let nl_before = FormatterOptions {
newline_before_param: true,
..Default::default()
};
param_options.insert(ParamFormatSelector::Position(2), &nl_before);
let config = WriterConfig::default();
let mut buffer = Vec::new();
let mut writer = Writer::new(&mut buffer, config);
writer
.write_command_with_options(&cmd, None, Some(¶m_options))
.unwrap();
let result = String::from_utf8(buffer).unwrap();
assert_eq!(result, "#test param1\n param2\n param3\n");
}
#[test]
fn test_write_without_repeat_newlines() {
let cmd = Command::new(
"test",
vec![Parameter::from("param1"), Parameter::from("param2")],
);
let mut param_options = HashMap::new();
let nl_after = FormatterOptions {
newline_after_param: true,
..Default::default()
};
param_options.insert(ParamFormatSelector::Position(0), &nl_after);
let nl_before = FormatterOptions {
newline_before_param: true,
..Default::default()
};
param_options.insert(ParamFormatSelector::Position(1), &nl_before);
let config = WriterConfig::default();
let mut buffer = Vec::new();
let mut writer = Writer::new(&mut buffer, config);
writer
.write_command_with_options(&cmd, None, Some(¶m_options))
.unwrap();
let result = String::from_utf8(buffer).unwrap();
assert_eq!(result, "#test param1\n param2\n");
}
#[test]
fn test_write_with_composite_param_formatting() {
let cmd = Command::new(
"test",
vec![
Parameter::from("regular"),
Parameter::from(("composite", 42)),
Parameter::from("another"),
],
);
let mut param_options = HashMap::new();
let hex_opt = FormatterOptions {
number_format: NumberFormat::Hex,
..Default::default()
};
param_options.insert(ParamFormatSelector::Name("composite".to_string()), &hex_opt);
let config = WriterConfig::default();
let mut buffer = Vec::new();
let mut writer = Writer::new(&mut buffer, config);
writer
.write_command_with_options(&cmd, None, Some(¶m_options))
.unwrap();
let result = String::from_utf8(buffer).unwrap();
assert_eq!(result, "#test regular composite(0x2a) another\n");
}
#[test]
fn test_mutliline_command() {
let cmd = Command::new(
"test",
vec![
Parameter::from("regular"),
Parameter::from(("composite", 42)),
Parameter::from("another"),
],
);
let config = WriterConfig::default();
let mut buffer = Vec::new();
let mut writer = Writer::new(&mut buffer, config);
writer.write_command(&cmd).unwrap();
writer.write_command(&cmd).unwrap();
let result = String::from_utf8(buffer).unwrap();
assert_eq!(
result,
"#test regular composite(42) another\n#test regular composite(42) another\n"
);
}
}