Improved header documentation.
[?]
Jul 17, 2021, 1:48 AM
KD6IJRPTTSGM3U6FQKUPVXUJ3SAF5BEU2UD4M6EASFO56WLHKSTACDependencies
- [2]
K5VHGRGGAdd serialization. - [3]
6ZJX2OQVFirst commit - [4]
W6BDDJBJChanged name of fn to_keytree() to keytree().
Change contents
- file deletion: README.md
# KeyTree`KeyTree` is an elegant markup language designed to convert human readable information into Rustdata-structures. It is easy to implement for one's own types. Its main use isfor configuration files. The format looks like```texthobbit:name: Frodo Bagginsage: 60friends:hobbit:name: Bilbo Bagginsage: 111hobbit:name: Samwise Gamgeeage: 38nick: Sam```so data can be recursive. Also, it is easy to refer to a set of data using a path such as`hobbit::friends::hobbit` refers to a collection of two hobbits.## Example`Into` from `KeyTree` into Rust types is automatically implemented for `Vec<T>`, `Option<T>`and basic Rust types. `KeyTree` text can be automatically converted to these data types, makinguse of type inference. The `at()` function returns an iterator over `KeyTree` types that can beused to implement `Into` for your own types. The following example should cover 90 percent ofuse cases,```rustuse core::convert::TryInto;use keytree::{error::Err,KeyTree,parser::KeyTreeBuilder,};#[derive(Debug)]struct Hobbit {name: String,age: u32,friends: Vec<Hobbit>,nick: Option<String>,}impl<'a> TryInto<Hobbit> for KeyTree<'a> {type Error = KeyErr;fn try_into(self) -> Result<Hobbit, Self::Error> {Ok(Hobbit {name: self.at("hobbit::name")?,age: self.at("hobbit::age")?,friends: self.vec("hobbit::friends::hobbit")?,nick: self.op("hobbit::nick")?,})}}fn main() {let core = KeyTreeBuilder::parse("hobbit.keytree").unwrap();let hobbit: Hobbit = KeyTree::from_core(&core).try_into().unwrap();dbg!(&hobbit);}```## DetailsWe'll have a look in detail at what is happening in the example on the line```textfriends: self.at("hobbit::friends::hobbit"),```In the `Into` trait impl, we want Bilbo Baggins and Samwise Gamgee to be Frodo'sfriends. We specify their location in the `KeyTree` string using a path`"hobbit::friends::hobbit"` which refers to two branches in the tree (twohobbits). The `at()` function, unlike the the `op()` function, requires that thebranches exist. Rust infers that they need to be converted into the type `Vec<Hobbit>` asspecified in the `Hobbit` struct. The `Vec<T>` impl of `Into` is supplied by `KeyTree`. In factthe `at()` function supplies an iterator over the Hobbits, which the `Vec<T>` impl uses.## Data Format Rules- Indentation has meaning and is 4 spaces, relative to the top key. Since indenting isrelative to the top key, then you can neatly align strings embedded in code.- Each line can be empty, have whitespace only, be a comment, be a key, or be a key/valuepair.- There are keys and values. Key/Value pairs look like```textname: Frodo```are used for `struct` fields and `enum` variants.Keys refer to child keys or child key/value pairs indented on lines under it, for example```texthobbit:name: Frodo```hobbit refers to the name of the struct or enum. In this way, the data maps simply to Rustdata-structures.- If a key has many children with the same key, it forms a collection, for example```testhobbit:name: Frodoname: Bilbo```is a collection of hobbits.- Keys must not include but must be followed by a colon `:`.- Values are all characters between the combination of ':' and whitespace and the end of theline. The value is trimmed of whitespace at both ends.- Comments require `//` at the start of the line. For example```text// commenthobbit:name: Frodo``` - edit in src/serialize.rs at line 2
use std::fmt::Display; - replacement in src/serialize.rs at line 15
pub fn to_string(&self, max_key_len: usize) -> String {pub fn to_str(&self, val_indent: usize) -> String { - replacement in src/serialize.rs at line 17
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),Token::KeyToken(kt) => kt.to_str(),Token::KeyValToken(kvt) => kvt.to_str(val_indent),Token::Comment(c) => c.to_str(), - replacement in src/serialize.rs at line 30
pub fn to_string(&self, _max_key_len: usize) -> String {let mut s = indent_to(self.indent, &self.key);pub fn to_str(&self) -> String {let mut s = String::new();s.push_str(&padding(self.indent * 4));s.push_str(&self.key); - replacement in src/serialize.rs at line 46
pub fn to_string(&self, max_key_len: usize) -> String {let mut s = indent_to(self.indent, &self.key);pub fn to_str(&self, val_indent: usize) -> String {let mut s = String::new();s.push_str(&padding(self.indent * 4));s.push_str(&self.key); - replacement in src/serialize.rs at line 51
append_at_indent(chars_to_indent(max_key_len), &s, &self.val)s.push_str(&padding(val_indent - (self.key.len() + 2 + self.indent * 4)));s.push_str(&self.val);s - edit in src/serialize.rs at line 57
// 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);} - replacement in src/serialize.rs at line 63
pub fn to_string(&self, _max_key_len: usize) -> String {let mut s = indent_to(self.indent, "// ");pub fn to_str(&self) -> String {let mut s = String::new();s.push_str(&padding(self.indent * 4));s.push_str("// "); - replacement in src/serialize.rs at line 74
max_key_len: usize,/// The indentation in chars to align values.val_indent: usize, - replacement in src/serialize.rs at line 83
max_key_len: 0,val_indent: 0, - edit in src/serialize.rs at line 98
pub fn push_opt_value<T: Display>(&mut self, indent: usize, key: &str, value: Option<T>) {match value {None => return,Some(v) => {self.push_value(indent, key, v.to_string().as_str())},}} - replacement in src/serialize.rs at line 108
pub fn push_keyvalue(&mut self, indent: usize, key: &str, value: &str) {pub fn push_value<T: Display>(&mut self, indent: usize, key: &str, value: T) { - replacement in src/serialize.rs at line 113
val: String::from(value),val: value.to_string(), - replacement in src/serialize.rs at line 117
// Colon and space// |// v// 012345// key: - replacement in src/serialize.rs at line 123
if key_len > self.max_key_len {self.max_key_len = key_len;// | |// v v// 0123456let val_indent = match key_len % 4 {0 => key_len,1 => key_len + 3,2 => key_len + 2,3 => key_len + 1,_ => unreachable!(), - edit in src/serialize.rs at line 134
// val_indent (5)// |// v// 012345// key://// (padding = val_indent - (key.len() + 2)if val_indent > self.val_indent {self.val_indent = val_indent;}; - replacement in src/serialize.rs at line 167
self.push_keyvalue(kv.indent + indent, &kv.key, &kv.val);self.push_value(kv.indent + indent, &kv.key, &kv.val); - edit in src/serialize.rs at line 175
}#[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",) - replacement in src/serialize.rs at line 180
for tok in &self.tokens {s.push_str(&tok.to_string(self.max_key_len));for token in &self.tokens {match token {Token::KeyValToken(kvt) => s.push_str(&kvt.to_str(self.val_indent)),Token::KeyToken(kt) => s.push_str(&kt.to_str()),Token::Comment(com) => s.push_str(&com.to_str()),} - replacement in src/serialize.rs at line 193
/// Append whitespace of length `n` to start of a string.fn indent_to(indent: usize, key: &str) -> String {fn padding(len: usize) -> String { - replacement in src/serialize.rs at line 195
for _ in 0..(indent * 4) { s.push(' ') };s.push_str(&key);for _ in 0..len {s.push(' ');} - replacement in src/serialize.rs at line 202
fn test_indent_to() {fn test_1() {let mut kt_string = KeyTreeString::new();kt_string.push_keyvalue(1, "key", "value"); - replacement in src/serialize.rs at line 206
indent_to(1, "abc")," abc",kt_string.to_string()," key: value", - replacement in src/serialize.rs at line 211
/// 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_2() {let mut kt_string = KeyTreeString::new();kt_string.push_keyvalue(0, "key", "value");assert_eq!(kt_string.to_string(),"key: value",) - replacement in src/serialize.rs at line 222
fn test_append_at_indent() {fn test_3() {let mut kt_string = KeyTreeString::new();kt_string.push_keyvalue(0, "ky", "value"); - replacement in src/serialize.rs at line 226
append_at_indent(3, " abc", "def")," abc def",kt_string.to_string(),"ky: value", - replacement in src/serialize.rs at line 230[2.5356]
- edit in src/lib.rs at line 1
//! # KeyTree//!//! `KeyTree` is a text format designed to convert human readable information into Rust//! data-structures. It is easy to implement for one's own types. Its main use is//! for configuration files. The format looks like//!//! ```text//! hobbit://! name: Frodo Baggins//! age: 60//! friends://! hobbit://! name: Bilbo Baggins//! age: 111//! hobbit://! name: Samwise Gamgee//! age: 38//! nick: Sam//! ```//!//! so data can be recursive. Also, it is easy to refer to a set of data using a path such as//! `hobbit::friends::hobbit` refers to a collection of two hobbits.//!//! ## Example//!//! `Into` from `KeyTree` into Rust types is automatically implemented for `Vec<T>`, `Option<T>`//! and basic Rust types. `KeyTree` text can be automatically converted to these data types, making//! use of type inference. The `at()` function returns an iterator over `KeyTree` types that can be//! used to implement `Into` for your own types. The following example should cover 90 percent of//! use cases,//!//! ```rust//! use std::convert::TryInto;//! use keytree::{KeyTree, KeyTreeRef};//! use keytree::Error;//!//! static HOBBITS: &'static str = r#"//! hobbit://! name: Frodo Baggins//! age: 60//! friends://! hobbit://! name: Bilbo Baggins//! age: 111//! hobbit://! name: Samwise Gamgee//! age: 38//! nick: Sam//! "#;//!//! #[derive(Debug)]//! struct Hobbit {//! name: String,//! age: u32,//! friends: Vec<Hobbit>,//! nick: Option<String>,//! }//!//! impl<'a> TryInto<Hobbit> for KeyTreeRef<'a> {//! type Error = Error;//!//! fn try_into(self) -> Result<Hobbit, Error> {//! Ok(//! Hobbit {//! name: self.value("hobbit::name")?,//! age: self.value("hobbit::age")?,//! friends: self.vec_at("hobbit::friends::hobbit")?,//! nick: self.opt_value("hobbit::nick")?,//! }//! )//! }//! }//!//! fn main() {//! let kt = KeyTree::parse(HOBBITS).unwrap();//! let hobbit: Hobbit = kt.to_ref().try_into().unwrap();//!//! &dbg!(&hobbit);//! }//! ```//!//! In the `TryInto` implementation that deserializes the keytree string into a data-structure, the//! line//! ```//! name: self.value("hobbit::name")?,//! ```//! takes the value on the line specified by `hobbit:name` (`Frodo Baggins`) and then uses the//! `FromStr` implement to convert it into the receiver type of the `name` field which is type//! `String`.//!//! The line//! ```//! friends: self.vec_at("hobbit::friends::hobbit")?,//! ```//! takes the collection of lines//! ```text//! hobbit://! name: Bilbo Baggins//! age: 111//! hobbit://! name: Samwise Gamgee//! age: 38//! nick: Sam//! ```//! and then uses the `TryInto` implementation of the items in the receiving type `Hobbit` to map//! itself into a `Vec` of `Hobbit`s.//!//! ## Data Format Rules//!//! - Indentation has meaning and is 4 spaces, relative to the top key. Since indenting is//! relative to the top key, then you can neatly align strings embedded in code.//!//! - Each line can be empty, have whitespace only, be a comment, be a key, or be a key/value//! pair.//!//! - There are keys and values. Key/Value pairs look like//!//! ```text//! name: Frodo//! ```//! are used for `struct` fields and `enum` variants.//!//! Keys refer to child keys or child key/value pairs indented on lines under it, for example//!//! ```text//! hobbit://! name: Frodo//! ```//! hobbit refers to the name of the struct or enum. In this way, the data maps simply to Rust//! data-structures.//!//! - If a key has many children with the same key, it forms a collection, for example//!//! ```test//! hobbit://! name: Frodo//! name: Bilbo//! ```//! is a collection of hobbits.//!//! - Keys must not include but must be followed by a colon `:`.//!//! - Values are all characters between the combination of ':' and whitespace and the end of the//! line. The value is trimmed of whitespace at both ends.//!//! - Comments require `//` at the start of the line. For example//!//! ```text//! // comment//! hobbit://! name: Frodo//! ``` - replacement in Cargo.toml at line 12
members = [ "examples/hobbit", "examples/embedded" ]members = [ "examples/hobbit" ]