use crate::Byml;
use std::convert::From;
use std::error::Error;
use std::fmt::{self, Display};
#[derive(Copy, Clone, Debug)]
pub enum EmitError {
FmtError(fmt::Error),
}
impl Byml {
pub fn to_text(&self) -> Result<String, Box<dyn std::error::Error>> {
let mut text = String::new();
BymlEmitter::new(&mut text).dump(&self)?;
Ok(text)
}
}
impl Error for EmitError {
fn cause(&self) -> Option<&dyn Error> {
None
}
}
impl Display for EmitError {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
match *self {
EmitError::FmtError(ref err) => Display::fmt(err, formatter),
}
}
}
impl From<fmt::Error> for EmitError {
fn from(f: fmt::Error) -> Self {
EmitError::FmtError(f)
}
}
struct BymlEmitter<'a> {
writer: &'a mut dyn fmt::Write,
best_indent: usize,
level: isize,
}
pub type EmitResult = Result<(), EmitError>;
fn write_binary(wr: &mut dyn fmt::Write, v: &str) -> Result<(), fmt::Error> {
let mut start = 0;
for (i, byte) in v.bytes().enumerate() {
let escaped = match byte {
b'"' => "\\\"",
b'\\' => "\\\\",
b'\x00' => "\\u0000",
b'\x01' => "\\u0001",
b'\x02' => "\\u0002",
b'\x03' => "\\u0003",
b'\x04' => "\\u0004",
b'\x05' => "\\u0005",
b'\x06' => "\\u0006",
b'\x07' => "\\u0007",
b'\x08' => "\\b",
b'\t' => "\\t",
b'\n' => "\\n",
b'\x0b' => "\\u000b",
b'\x0c' => "\\f",
b'\r' => "\\r",
b'\x0e' => "\\u000e",
b'\x0f' => "\\u000f",
b'\x10' => "\\u0010",
b'\x11' => "\\u0011",
b'\x12' => "\\u0012",
b'\x13' => "\\u0013",
b'\x14' => "\\u0014",
b'\x15' => "\\u0015",
b'\x16' => "\\u0016",
b'\x17' => "\\u0017",
b'\x18' => "\\u0018",
b'\x19' => "\\u0019",
b'\x1a' => "\\u001a",
b'\x1b' => "\\u001b",
b'\x1c' => "\\u001c",
b'\x1d' => "\\u001d",
b'\x1e' => "\\u001e",
b'\x1f' => "\\u001f",
b'\x7f' => "\\u007f",
_ => continue,
};
if start < i {
wr.write_str(&v[start..i])?;
}
wr.write_str(escaped)?;
start = i + 1;
}
if start != v.len() {
wr.write_str(&v[start..])?;
}
Ok(())
}
fn escape_str(wr: &mut dyn fmt::Write, v: &str) -> Result<(), fmt::Error> {
wr.write_str("\"")?;
let mut start = 0;
for (i, byte) in v.bytes().enumerate() {
let escaped = match byte {
b'"' => "\\\"",
b'\\' => "\\\\",
b'\x00' => "\\u0000",
b'\x01' => "\\u0001",
b'\x02' => "\\u0002",
b'\x03' => "\\u0003",
b'\x04' => "\\u0004",
b'\x05' => "\\u0005",
b'\x06' => "\\u0006",
b'\x07' => "\\u0007",
b'\x08' => "\\b",
b'\t' => "\\t",
b'\n' => "\\n",
b'\x0b' => "\\u000b",
b'\x0c' => "\\f",
b'\r' => "\\r",
b'\x0e' => "\\u000e",
b'\x0f' => "\\u000f",
b'\x10' => "\\u0010",
b'\x11' => "\\u0011",
b'\x12' => "\\u0012",
b'\x13' => "\\u0013",
b'\x14' => "\\u0014",
b'\x15' => "\\u0015",
b'\x16' => "\\u0016",
b'\x17' => "\\u0017",
b'\x18' => "\\u0018",
b'\x19' => "\\u0019",
b'\x1a' => "\\u001a",
b'\x1b' => "\\u001b",
b'\x1c' => "\\u001c",
b'\x1d' => "\\u001d",
b'\x1e' => "\\u001e",
b'\x1f' => "\\u001f",
b'\x7f' => "\\u007f",
_ => continue,
};
if start < i {
wr.write_str(&v[start..i])?;
}
wr.write_str(escaped)?;
start = i + 1;
}
if start != v.len() {
wr.write_str(&v[start..])?;
}
wr.write_str("\"")?;
Ok(())
}
impl<'a> BymlEmitter<'a> {
pub fn new(writer: &'a mut dyn fmt::Write) -> BymlEmitter {
BymlEmitter {
writer,
best_indent: 2,
level: -1,
}
}
pub fn dump(&mut self, doc: &Byml) -> EmitResult {
self.level = -1;
self.emit_node(doc)
}
fn write_indent(&mut self) -> EmitResult {
if self.level <= 0 {
return Ok(());
}
for _ in 0..self.level {
for _ in 0..self.best_indent {
write!(self.writer, " ")?;
}
}
Ok(())
}
fn emit_node(&mut self, node: &Byml) -> EmitResult {
match node {
Byml::Array(ref v) => self.emit_array(v),
Byml::Hash(ref h) => self.emit_hash(h),
Byml::String(ref v) => {
if need_quotes(v) {
escape_str(self.writer, v)?;
} else {
write!(self.writer, "{}", v)?;
}
Ok(())
}
Byml::Bool(v) => {
if *v {
self.writer.write_str("true")?;
} else {
self.writer.write_str("false")?;
}
Ok(())
}
Byml::Int(v) => {
write!(self.writer, "{}", v)?;
Ok(())
}
Byml::Int64(v) => {
write!(self.writer, "!l {}", v)?;
Ok(())
}
Byml::UInt(v) => {
write!(self.writer, "!u {}", v)?;
Ok(())
}
Byml::UInt64(v) => {
write!(self.writer, "!ul {}", v)?;
Ok(())
}
Byml::Float(_) => {
write!(self.writer, "{:?}", node.as_float().unwrap())?;
Ok(())
}
Byml::Double(_) => {
write!(self.writer, "!f64 {:?}", node.as_double().unwrap())?;
Ok(())
}
Byml::Binary(v) => {
let data: String = format!("!!binary {}", base64::encode(&v));
write_binary(self.writer, &data)?;
Ok(())
}
Byml::Null => {
write!(self.writer, "~")?;
Ok(())
}
}
}
fn emit_array(&mut self, v: &[Byml]) -> EmitResult {
if v.is_empty() {
write!(self.writer, "[]")?;
} else {
self.level += 1;
for (cnt, x) in v.iter().enumerate() {
if cnt > 0 {
writeln!(self.writer)?;
self.write_indent()?;
}
write!(self.writer, "-")?;
self.emit_val(true, x)?;
}
self.level -= 1;
}
Ok(())
}
fn emit_hash(&mut self, h: &std::collections::BTreeMap<String, Byml>) -> EmitResult {
if h.is_empty() {
self.writer.write_str("{}")?;
} else {
self.level += 1;
for (cnt, (k, v)) in h.iter().enumerate() {
if cnt > 0 {
writeln!(self.writer)?;
self.write_indent()?;
}
self.emit_node(&Byml::String(k.to_owned()))?;
write!(self.writer, ":")?;
self.emit_val(false, v)?;
}
self.level -= 1;
}
Ok(())
}
fn emit_val(&mut self, inline: bool, val: &Byml) -> EmitResult {
match *val {
Byml::Array(ref v) => {
if inline || v.is_empty() {
write!(self.writer, " ")?;
} else {
writeln!(self.writer)?;
self.level += 1;
self.write_indent()?;
self.level -= 1;
}
self.emit_array(v)
}
Byml::Hash(ref h) => {
if inline || h.is_empty() {
write!(self.writer, " ")?;
} else {
writeln!(self.writer)?;
self.level += 1;
self.write_indent()?;
self.level -= 1;
}
self.emit_hash(h)
}
_ => {
write!(self.writer, " ")?;
self.emit_node(val)
}
}
}
}
fn need_quotes(string: &str) -> bool {
fn need_quotes_spaces(string: &str) -> bool {
string.starts_with(' ') || string.ends_with(' ')
}
string == ""
|| need_quotes_spaces(string)
|| string.starts_with(|character: char| match character {
'&' | '*' | '?' | '|' | '-' | '<' | '>' | '=' | '!' | '%' | '@' => true,
_ => false,
})
|| string.contains(|character: char| match character {
':'
| '{'
| '}'
| '['
| ']'
| ','
| '#'
| '`'
| '\"'
| '\''
| '\\'
| '\0'..='\x06'
| '\t'
| '\n'
| '\r'
| '\x0e'..='\x1a'
| '\x1c'..='\x1f' => true,
_ => false,
})
|| [
"yes", "Yes", "YES", "no", "No", "NO", "True", "TRUE", "true", "False", "FALSE",
"false", "on", "On", "ON", "off", "Off", "OFF", "null", "Null", "NULL", "~",
]
.contains(&string)
|| string.starts_with('.')
|| string.starts_with("0x")
|| string.parse::<i64>().is_ok()
|| string.parse::<f64>().is_ok()
}