extern crate tom; use std::iter; use tom::{ TomlDoc, CstNode, Symbol, TextRange, Position::*, ast::{self, EntryOwner}, symbol::*, }; fn main() { let text = "\ title = \"TOML Example\" [owner] name = \"Tom Preston-Werner\" dob = 1979-05-27T07:32:00-08:00 # First class dates "; // `TomlDoc` is the API entry point. let doc = TomlDoc::new(text); // Internally, `TomlDoc` is represented as a homogeneous // Concrete Syntax Tree (CST) which includes whitespace and // comments explicitely. `.debug` method can be used to see // this representation: assert_eq!(doc.debug().trim(), r##" DOC@[0; 112) ENTRY@[0; 22) KEY@[0; 5) BARE_KEY@[0; 5) "title" WHITESPACE@[5; 6) EQ@[6; 7) "=" WHITESPACE@[7; 8) VALUE@[8; 22) BASIC_STRING@[8; 22) "\"TOML Example\"" WHITESPACE@[22; 24) TABLE@[24; 111) TABLE_HEADER@[24; 31) L_BRACK@[24; 25) "[" KEY@[25; 30) BARE_KEY@[25; 30) "owner" R_BRACK@[30; 31) "]" WHITESPACE@[31; 32) ENTRY@[32; 59) KEY@[32; 36) BARE_KEY@[32; 36) "name" WHITESPACE@[36; 37) EQ@[37; 38) "=" WHITESPACE@[38; 39) VALUE@[39; 59) BASIC_STRING@[39; 59) "\"Tom Preston-Werner\"" WHITESPACE@[59; 60) ENTRY@[60; 111) KEY@[60; 63) BARE_KEY@[60; 63) "dob" WHITESPACE@[63; 64) EQ@[64; 65) "=" WHITESPACE@[65; 66) VALUE@[66; 91) DATE_TIME@[66; 91) "1979-05-27T07:32:00-08:00" WHITESPACE@[91; 92) COMMENT@[92; 111) "# First class dates" WHITESPACE@[111; 112) "##.trim()); // Note that `new` method does not return a `Result`. // This is because the library can parse even partially // invalid toml documents. Use `.errors` method to check // if file has any syntax errors; let invalid_toml = TomlDoc::new(":-)\nfoo=1"); assert_eq!(invalid_toml.debug().trim(), r#" DOC@[0; 9) ERROR@[0; 1) ERROR@[0; 1) ":" ENTRY@[1; 9) KEY@[1; 2) BARE_KEY@[1; 2) "-" ERROR@[2; 3) ERROR@[2; 3) ")" WHITESPACE@[3; 4) KEY@[4; 7) BARE_KEY@[4; 7) "foo" EQ@[7; 8) "=" VALUE@[8; 9) NUMBER@[8; 9) "1" error@[0; 1) ":": expected a key error@[2; 3) ")": expected `.` error@[1; 8) "-)\nfoo=": newlines are forbidden in entries"#.trim()); assert_eq!(invalid_toml.errors().len(), 3); // To access CST, use `.cst` method. let root: CstNode = doc.cst(); // A CST node has a `Symbol`, which says what kind // of syntactic construct this node represents, a // range in the text and links to children, parent, // and siblings. // // Note that every method on `CstNode` needs a `&TomlDoc` argument. // This is because internally a `CstNode` is just an index, and it // needs the document to extract information. assert_eq!(root.symbol(&doc), DOC); assert_eq!(root.children(&doc).iter().count(), 4); let trailing_newline = root.children(&doc).last().unwrap(); let ws_symbol: Symbol = trailing_newline.symbol(&doc); assert_eq!(ws_symbol, WHITESPACE); assert_eq!(trailing_newline.get_text(&doc), "\n"); let title = root.children(&doc).first().unwrap(); assert_eq!(title.symbol(&doc), ENTRY); assert_eq!(title.range(&doc), TextRange::from_to(0.into(), 22.into())); assert_eq!(title.parent(&doc), Some(root)); assert_eq!(title.get_text(&doc), "title = \"TOML Example\""); // Working with CST directly is powerful, because you get access to // all comments, punctuation and whitespace, but it is not convenient // if you need the meaning of the toml document. For that purpose, // a higher-level `ast` API exists. let root: ast::Doc = doc.ast(); assert_eq!(root.entries(&doc).count(), 1); assert_eq!(root.tables(&doc).count(), 1); let table: ast::Table = root.tables(&doc).next().unwrap(); assert_eq!(table.entries(&doc).count(), 2); let entry: ast::Entry = table.entries(&doc).next().unwrap(); match entry.value(&doc).kind(&doc) { ast::ValueKind::StringLit(lit) => { assert_eq!(lit.value(&doc), "Tom Preston-Werner"); }, _ => panic!("unexpected entry") } // Internally, each AST node is a wrapper of the corresponding CST node, // and you can use `.cst()` and `::cast()` methods to convert between the two: let entry_raw = entry.cst(); assert_eq!(entry_raw.get_text(&doc), "name = \"Tom Preston-Werner\""); match ast::Entry::cast(entry_raw, &doc) { Some(entry) => assert_eq!(entry.keys(&doc).next().unwrap().name(&doc), "name"), None => panic!("can't cast a node to entry"), } // Let's see how the API for modifying the document works. // First, we need to call `.start_edit` method. This is required // for two reasons: // * A small edit can change the ranges of a large number of CST nodes // which makes maintaining correct ranges costly. Thus, in edit mode // ranges are assumed to be invalid and `CstNode::range` panics. // * By design, arbitrary CST structure can be created during edits. // That means that methods that assume that, for example, `ast::Value`'s // parent is always an `ast::Entry` may panic as well. let mut doc = doc; doc.start_edit(); // The primary way to create new elements is the family of // `TomlDoc::new_foo_from_text` methods. Using raw text, you // have full control of formatting, whitespace and comments: let new_entry: ast::Entry = doc.new_entry_from_text("foo= 92 #comments are preserved"); assert_eq!(new_entry.cst().get_text(&doc), "foo= 92 #comments are preserved"); // If the text can not be parsed as a requested syntactic construct, // the call will panic: // // doc.new_entry_from_text(":("); // panics! // You can also create new nodes in a typed way from components, // using `TomlDoc::new_foo` family of methods. let new_entry: ast::Entry = { let key: ast::Key = doc.new_key("foo"); let value: ast::Value = doc.new_value(92); doc.new_entry(iter::once(key), value) }; assert_eq!(new_entry.cst().get_text(&doc), "foo = 92"); // `TomlDoc` has several methods which manipulate trees: // `replace`, `detach`, `insert`. // Because each node is an index, and not a reference, we // are able mutate document without invalidating existing // nodes. doc.replace(entry, new_entry); assert_eq!(doc.cst().get_text(&doc), "\ title = \"TOML Example\" [owner] foo = 92 dob = 1979-05-27T07:32:00-08:00 # First class dates "); doc.detach(new_entry); assert_eq!(doc.cst().get_text(&doc), "\ title = \"TOML Example\" [owner] dob = 1979-05-27T07:32:00-08:00 # First class dates "); doc.insert(new_entry, PrependTo(root.cst())); assert_eq!(doc.cst().get_text(&doc), "\ foo = 92 title = \"TOML Example\" [owner] dob = 1979-05-27T07:32:00-08:00 # First class dates "); // AST nodes have type-safe mutating methods as well, // although currently only few are actually implemented :) let e1 = doc.new_entry_from_text("foo = 1"); let e2 = doc.new_entry_from_text("bar = 2"); let d = doc.new_dict_from_text("{}"); assert_eq!(d.cst().get_text(&doc), "{}"); d.append_entry(&mut doc, e1); assert_eq!(d.cst().get_text(&doc), "{ foo = 1 }"); d.append_entry(&mut doc, e2); assert_eq!(d.cst().get_text(&doc), "{ foo = 1, bar = 2 }"); // Remember that we've begun the editing with `.start_edit` call? // We have two ways to finish edit, with different tradeoffs. // // `doc.finish_edit_no_reparse();` will recalculate correct // ranges for elements, but it **will not** check that CST // structure is valid. However, all existing CST and AST // nodes (which, as a reminder, are indices), remain valid. // // `doc.finish_edit_full_reparse();` will reparse the document, // creating a fresh, valid CST. Because the CST structure might // change after the reparse, old indices become invalid. doc.finish_edit_no_reparse(); }