#[cfg(not(feature = "std"))]
use alloc::{
format,
string::{String, ToString},
vec::Vec,
};
use crate::io::traits::{BencodeWrite, IDestination};
use crate::nodes::node::*;
use crate::stringify::common::escape_string;
fn write_indent(level: usize, destination: &mut (impl BencodeWrite + ?Sized)) {
for _ in 1..level {
destination.write_bytes(b" ");
}
}
fn write_node(node: &Node, level: usize, destination: &mut (impl BencodeWrite + ?Sized)) {
match node {
Node::Integer(n) => destination.write_bytes(n.to_string().as_bytes()),
Node::Str(s) => {
destination.write_byte(b'"');
escape_string(s, destination);
destination.write_byte(b'"');
}
Node::List(items) => {
if items.is_empty() {
destination.write_bytes(b"[]")
} else {
destination.write_bytes(b"\n");
for item in items {
write_indent(level + 1, destination);
destination.write_bytes(b"- ");
write_node(item, level + 1, destination);
destination.write_bytes(b"\n");
}
}
}
Node::Dictionary(dict) => {
if dict.is_empty() {
destination.write_bytes(b"{}")
} else {
destination.write_bytes(b"\n");
let mut sorted: Vec<_> = dict.iter().collect();
sorted.sort_by(|a, b| a.0.cmp(b.0));
for (key, value) in sorted {
write_indent(level + 1, destination);
destination.write_bytes(format!("{}: ", String::from_utf8_lossy(key.as_ref())).as_bytes());
write_node(value, level + 1, destination);
destination.write_bytes(b"\n");
}
}
}
_ => destination.write_bytes(b"unknown"),
}
}
pub fn stringify(node: &Node, destination: &mut dyn IDestination) -> Result<(), String> {
write_node(node, 0, destination);
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::io::destinations::buffer::Buffer;
#[test]
fn stringify_empty_list_works() {
let mut destination = Buffer::new();
stringify(&Node::List(vec![]), &mut destination).unwrap();
assert_eq!(destination.to_string(), "[]");
}
#[test]
fn stringify_list_works() {
let mut destination = Buffer::new();
stringify(
&Node::List(vec![Node::Integer(1), Node::Integer(2)]),
&mut destination,
)
.unwrap();
assert_eq!(destination.to_string(), "\n- 1\n- 2\n");
}
#[test]
fn stringify_empty_dictionary_works() {
let mut destination = Buffer::new();
let dict = std::collections::HashMap::new();
stringify(&Node::Dictionary(dict), &mut destination).unwrap();
assert_eq!(destination.to_string(), "{}");
}
#[test]
fn stringify_dictionary_works() {
let mut destination = Buffer::new();
let mut dict = std::collections::HashMap::new();
dict.insert("key".to_string(), Node::Integer(1));
stringify(&Node::Dictionary(dict), &mut destination).unwrap();
assert_eq!(destination.to_string(), "\nkey: 1\n");
}
#[test]
fn stringify_integer_works() {
let mut destination = Buffer::new();
stringify(&Node::Integer(42), &mut destination).unwrap();
assert_eq!(destination.to_string(), "42");
}
#[test]
fn stringify_string_works() {
let mut destination = Buffer::new();
stringify(&Node::Str(String::from("test")), &mut destination).unwrap();
assert_eq!(destination.to_string(), "\"test\"");
}
#[test]
fn stringify_unknown_works() {
let mut destination = Buffer::new();
stringify(&Node::None, &mut destination).unwrap();
assert_eq!(destination.to_string(), "unknown");
}
#[test]
fn stringify_integer_zero() {
let mut dest = Buffer::new();
stringify(&Node::Integer(0), &mut dest).unwrap();
assert_eq!(dest.to_string(), "0");
}
#[test]
fn stringify_integer_negative() {
let mut dest = Buffer::new();
stringify(&Node::Integer(-99), &mut dest).unwrap();
assert_eq!(dest.to_string(), "-99");
}
#[test]
fn stringify_integer_max_i64() {
let mut dest = Buffer::new();
stringify(&Node::Integer(i64::MAX), &mut dest).unwrap();
assert_eq!(dest.to_string(), i64::MAX.to_string());
}
#[test]
fn stringify_integer_min_i64() {
let mut dest = Buffer::new();
stringify(&Node::Integer(i64::MIN), &mut dest).unwrap();
assert_eq!(dest.to_string(), i64::MIN.to_string());
}
#[test]
fn stringify_empty_string() {
let mut dest = Buffer::new();
stringify(&Node::Str(String::new()), &mut dest).unwrap();
assert_eq!(dest.to_string(), "\"\"");
}
#[test]
fn stringify_string_single_char() {
let mut dest = Buffer::new();
stringify(&Node::Str("z".into()), &mut dest).unwrap();
assert_eq!(dest.to_string(), "\"z\"");
}
#[test]
fn stringify_string_with_double_quote_escaped() {
let mut dest = Buffer::new();
stringify(&Node::Str("say \"hi\"".into()), &mut dest).unwrap();
assert_eq!(dest.to_string(), "\"say \\\"hi\\\"\"");
}
#[test]
fn stringify_string_with_backslash_escaped() {
let mut dest = Buffer::new();
stringify(&Node::Str("a\\b".into()), &mut dest).unwrap();
assert_eq!(dest.to_string(), "\"a\\\\b\"");
}
#[test]
fn stringify_string_with_newline_escaped() {
let mut dest = Buffer::new();
stringify(&Node::Str("line1\nline2".into()), &mut dest).unwrap();
assert_eq!(dest.to_string(), "\"line1\\u000aline2\"");
}
#[test]
fn stringify_list_single_integer() {
let mut dest = Buffer::new();
stringify(&Node::List(vec![Node::Integer(7)]), &mut dest).unwrap();
assert_eq!(dest.to_string(), "\n- 7\n");
}
#[test]
fn stringify_list_of_strings() {
let mut dest = Buffer::new();
stringify(
&Node::List(vec![Node::Str("foo".into()), Node::Str("bar".into())]),
&mut dest,
)
.unwrap();
assert_eq!(dest.to_string(), "\n- \"foo\"\n- \"bar\"\n");
}
#[test]
fn stringify_list_three_integers() {
let mut dest = Buffer::new();
stringify(
&Node::List(vec![Node::Integer(1), Node::Integer(2), Node::Integer(3)]),
&mut dest,
)
.unwrap();
assert_eq!(dest.to_string(), "\n- 1\n- 2\n- 3\n");
}
#[test]
fn stringify_nested_list() {
let inner = Node::List(vec![Node::Integer(1), Node::Integer(2)]);
let mut dest = Buffer::new();
stringify(&Node::List(vec![inner]), &mut dest).unwrap();
let s = dest.to_string();
assert!(s.contains("- 1"));
assert!(s.contains("- 2"));
}
#[test]
fn stringify_list_item_count_matches_dash_count() {
let list = Node::List(vec![
Node::Integer(10),
Node::Integer(20),
Node::Integer(30),
]);
let mut dest = Buffer::new();
stringify(&list, &mut dest).unwrap();
let s = dest.to_string();
let dash_count = s.matches("- ").count();
assert_eq!(dash_count, 3);
}
#[test]
fn stringify_dict_string_value() {
let mut dict = std::collections::HashMap::new();
dict.insert("name".to_string(), Node::Str("Alice".into()));
let mut dest = Buffer::new();
stringify(&Node::Dictionary(dict), &mut dest).unwrap();
assert_eq!(dest.to_string(), "\nname: \"Alice\"\n");
}
#[test]
fn stringify_dict_sorts_keys() {
let mut dict = std::collections::HashMap::new();
dict.insert("z".to_string(), Node::Integer(1));
dict.insert("a".to_string(), Node::Integer(2));
dict.insert("m".to_string(), Node::Integer(3));
let mut dest = Buffer::new();
stringify(&Node::Dictionary(dict), &mut dest).unwrap();
let s = dest.to_string();
let pos_a = s.find("a: ").unwrap();
let pos_m = s.find("m: ").unwrap();
let pos_z = s.find("z: ").unwrap();
assert!(pos_a < pos_m && pos_m < pos_z);
}
#[test]
fn stringify_dict_multiple_entries() {
let mut dict = std::collections::HashMap::new();
dict.insert("age".to_string(), Node::Integer(30));
dict.insert("name".to_string(), Node::Str("Bob".into()));
let mut dest = Buffer::new();
stringify(&Node::Dictionary(dict), &mut dest).unwrap();
let s = dest.to_string();
assert!(s.contains("age: 30"));
assert!(s.contains("name: \"Bob\""));
}
#[test]
fn stringify_dict_list_value() {
let mut dict = std::collections::HashMap::new();
dict.insert(
"nums".to_string(),
Node::List(vec![Node::Integer(1), Node::Integer(2)]),
);
let mut dest = Buffer::new();
stringify(&Node::Dictionary(dict), &mut dest).unwrap();
let s = dest.to_string();
assert!(s.contains("nums:"));
assert!(s.contains("- 1"));
assert!(s.contains("- 2"));
}
#[test]
fn stringify_nested_dict() {
let mut inner = std::collections::HashMap::new();
inner.insert("x".to_string(), Node::Integer(9));
let mut outer = std::collections::HashMap::new();
outer.insert("inner".to_string(), Node::Dictionary(inner));
let mut dest = Buffer::new();
stringify(&Node::Dictionary(outer), &mut dest).unwrap();
let s = dest.to_string();
assert!(s.contains("inner:"));
assert!(s.contains("x: 9"));
}
#[test]
fn write_indent_level_zero_produces_no_spaces() {
let mut dest = Buffer::new();
write_indent(0, &mut dest);
assert_eq!(dest.to_string(), "");
}
#[test]
fn write_indent_level_one_produces_no_spaces() {
let mut dest = Buffer::new();
write_indent(1, &mut dest);
assert_eq!(dest.to_string(), "");
}
#[test]
fn write_indent_level_two_produces_two_spaces() {
let mut dest = Buffer::new();
write_indent(2, &mut dest);
assert_eq!(dest.to_string(), " ");
}
#[test]
fn write_indent_level_three_produces_four_spaces() {
let mut dest = Buffer::new();
write_indent(3, &mut dest);
assert_eq!(dest.to_string(), " ");
}
#[test]
fn stringify_twice_gives_same_result() {
let node = Node::List(vec![Node::Integer(1), Node::Str("a".into())]);
let mut dest1 = Buffer::new();
let mut dest2 = Buffer::new();
stringify(&node, &mut dest1).unwrap();
stringify(&node, &mut dest2).unwrap();
assert_eq!(dest1.to_string(), dest2.to_string());
}
#[test]
fn stringify_via_make_node_integer() {
let mut dest = Buffer::new();
stringify(&make_node(5i64), &mut dest).unwrap();
assert_eq!(dest.to_string(), "5");
}
#[test]
fn stringify_via_make_node_string() {
let mut dest = Buffer::new();
stringify(&make_node("hi"), &mut dest).unwrap();
assert_eq!(dest.to_string(), "\"hi\"");
}
}