use std::fmt;
pub trait IntoKeyTree {
fn to_keytree(&self) -> KeyTreeString;
}
pub enum Token {
KeyToken(KeyToken),
KeyValToken(KeyValToken),
Comment(Comment),
}
impl Token {
pub fn to_string(&self, max_key_len: usize) -> String {
match self {
Token::KeyToken(kt) => kt.to_string(max_key_len),
Token::KeyValToken(kvt) => kvt.to_string(max_key_len),
Token::Comment(c) => c.to_string(max_key_len),
}
}
}
pub struct KeyToken {
indent: usize,
key: String,
}
impl KeyToken {
pub fn to_string(&self, _max_key_len: usize) -> String {
let mut s = indent_to(self.indent, &self.key);
s.push(':');
s
}
}
pub struct KeyValToken {
indent: usize,
key: String,
val: String,
}
impl KeyValToken {
pub fn to_string(&self, max_key_len: usize) -> String {
let mut s = indent_to(self.indent, &self.key);
s.push_str(": ");
append_at_indent(chars_to_indent(max_key_len), &s, &self.val)
}
}
// Takes number of chars and returns minimum indent that is greater than the number of chars.
pub fn chars_to_indent(chars: usize) -> usize {
// 0 -> 0
// 1 -> 1
// 2 -> 1
// 3 -> 1
// 4 -> 1
// 5 -> 2
(chars + 3) / 4
}
#[test]
fn test_chars_to_indent() {
assert_eq!(chars_to_indent(0), 0);
assert_eq!(chars_to_indent(1), 1);
assert_eq!(chars_to_indent(4), 1);
assert_eq!(chars_to_indent(5), 2);
}
pub struct Comment {
indent: usize,
comment: String,
}
impl Comment {
pub fn to_string(&self, _max_key_len: usize) -> String {
let mut s = indent_to(self.indent, "// ");
s.push_str(&self.comment);
s
}
}
pub struct KeyTreeString {
tokens: Vec<Token>,
max_key_len: usize,
}
impl KeyTreeString {
pub fn new() -> Self {
KeyTreeString {
tokens: Vec::new(),
max_key_len: 0,
}
}
/// Push a key token onto the String. The indent value is relative to the root indent.
pub fn push_key(&mut self, indent: usize, key: &str) {
let key = Token::KeyToken(
KeyToken {
indent: indent,
key: String::from(key),
}
);
self.tokens.push(key)
}
/// Push a key-value token onto the String. The indent value is relative to the root indent.
pub fn push_keyvalue(&mut self, indent: usize, key: &str, value: &str) {
let kv = Token::KeyValToken(
KeyValToken {
indent: indent,
key: String::from(key),
val: String::from(value),
}
);
self.tokens.push(kv);
// Colon and space
let key_len = key.chars().count() + (indent * 4) + 2;
if key_len > self.max_key_len {
self.max_key_len = key_len;
};
}
/// Push a comment onto the String. The indent value is relative to the root indent.
pub fn push_comment(&mut self, indent: usize, comment: &str) {
let comment = Token::Comment(
Comment {
indent: indent * 4,
comment: String::from(comment),
}
);
self.tokens.push(comment)
}
/// Push a KeyTreeString on to the String. The indent value is relative to the root indent.
pub fn push_keytree(&mut self, indent: usize, keytree: KeyTreeString) {
for token in keytree.tokens {
match token {
Token::KeyToken(k) => {
self.push_key(k.indent + indent, &k.key);
},
Token::KeyValToken(kv) => {
self.push_keyvalue(kv.indent + indent, &kv.key, &kv.val);
},
Token::Comment(c) => {
self.push_comment(c.indent + indent, &c.comment);
}
}
}
}
}
#[test]
fn test_keytree_string1() {
let mut kts = KeyTreeString::new();
kts.push_key(0, "key");
kts.push_key(1, "a_key");
kts.push_keyvalue(1, "b_key", "b_val");
assert_eq!(
kts.to_string(),
"key:\n a_key:\n b_key: b_val",
)
}
impl fmt::Display for KeyTreeString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut s = String::new();
for tok in &self.tokens {
s.push_str(&tok.to_string(self.max_key_len));
s.push('\n');
}
s.pop();
write!(f, "{}", s)
}
}
/// Append whitespace of length `n` to start of a string.
fn indent_to(indent: usize, key: &str) -> String {
let mut s = String::new();
for _ in 0..(indent * 4) { s.push(' ') };
s.push_str(&key);
s
}
#[test]
fn test_indent_to() {
assert_eq!(
indent_to(1, "abc"),
" abc",
)
}
/// Append string `t` to end of `s` at indent `indent`. Panic if
/// strings overlap.
fn append_at_indent(indent: usize, key: &str, val: &str) -> String {
let mut s = String::from(key);
let padding = (indent * 4) - s.chars().count() - 1;
for _ in 0..=padding { s.push(' ') };
s.push_str(&val);
s
}
#[test]
fn test_append_at_indent() {
assert_eq!(
append_at_indent(3, " abc", "def"),
" abc def",
)
}