417 lines
11 KiB
Plaintext
417 lines
11 KiB
Plaintext
Heading 2 Recipes
|
|
class mt-12
|
|
|
|
Heading 3 Basic Document Parsing
|
|
class mb-2
|
|
Markdown
|
|
Parse a simple Terrace document and print all nodes with their levels.
|
|
CodeBlock rust
|
|
use terrace::{TerraceDocument, StringReader};
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
let content = r#"
|
|
config
|
|
database
|
|
host localhost
|
|
port 5432
|
|
server
|
|
port 3000
|
|
host 0.0.0.0
|
|
"#;
|
|
|
|
let reader = StringReader::new(content);
|
|
let mut doc = TerraceDocument::with_reader(reader);
|
|
|
|
while let Some(node) = doc.next().await {
|
|
if !node.is_empty() {
|
|
println!("{:indent$}{}: '{}'",
|
|
"",
|
|
node.head(),
|
|
node.tail(),
|
|
indent = node.level() * 2);
|
|
}
|
|
}
|
|
}
|
|
|
|
Markdown
|
|
This will output:
|
|
```
|
|
config: ''
|
|
database: ''
|
|
host: 'localhost'
|
|
port: '5432'
|
|
server: ''
|
|
port: '3000'
|
|
host: '0.0.0.0'
|
|
```
|
|
|
|
Heading 3 Read Configuration into Struct
|
|
class mb-2
|
|
Markdown
|
|
Parse a Terrace configuration file and map it to a Rust struct.
|
|
CodeBlock rust
|
|
use terrace::{TerraceDocument, StringReader};
|
|
use std::collections::HashMap;
|
|
|
|
#[derive(Debug)]
|
|
struct Config {
|
|
database: DatabaseConfig,
|
|
server: ServerConfig,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct DatabaseConfig {
|
|
host: String,
|
|
port: u16,
|
|
name: String,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct ServerConfig {
|
|
host: String,
|
|
port: u16,
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
let content = r#"
|
|
config
|
|
database
|
|
host localhost
|
|
port 5432
|
|
name mydb
|
|
server
|
|
host 0.0.0.0
|
|
port 3000
|
|
"#;
|
|
|
|
let reader = StringReader::new(content);
|
|
let doc = TerraceDocument::with_reader(reader);
|
|
|
|
let mut config = Config {
|
|
database: DatabaseConfig {
|
|
host: String::new(),
|
|
port: 0,
|
|
name: String::new(),
|
|
},
|
|
server: ServerConfig {
|
|
host: String::new(),
|
|
port: 0,
|
|
},
|
|
};
|
|
|
|
let nodes = doc.collect().await;
|
|
|
|
for node in nodes {
|
|
match (node.level(), node.head()) {
|
|
(1, "database") => {
|
|
// Parse database section
|
|
// In a real implementation, you'd iterate through children
|
|
}
|
|
(1, "server") => {
|
|
// Parse server section
|
|
}
|
|
(2, "host") if node.tail().starts_with("localhost") => {
|
|
config.database.host = node.tail().to_string();
|
|
}
|
|
(2, "port") => {
|
|
if let Ok(port) = node.tail().parse::<u16>() {
|
|
if node.tail() == "5432" {
|
|
config.database.port = port;
|
|
} else if node.tail() == "3000" {
|
|
config.server.port = port;
|
|
}
|
|
}
|
|
}
|
|
(2, "name") => {
|
|
config.database.name = node.tail().to_string();
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
println!("{:?}", config);
|
|
}
|
|
|
|
Heading 3 Filter and Process Specific Nodes
|
|
class mb-2
|
|
Markdown
|
|
Find all nodes with a specific head value and process them.
|
|
CodeBlock rust
|
|
use terrace::{TerraceDocument, StringReader};
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
let content = r#"
|
|
users
|
|
user alice active
|
|
user bob inactive
|
|
user charlie active
|
|
user david inactive
|
|
groups
|
|
group admins
|
|
member alice
|
|
member charlie
|
|
group users
|
|
member bob
|
|
member david
|
|
"#;
|
|
|
|
let reader = StringReader::new(content);
|
|
let doc = TerraceDocument::with_reader(reader);
|
|
|
|
// Find all active users
|
|
let active_users = doc.filter(|node| {
|
|
node.head() == "user" && node.tail().contains("active")
|
|
}).await;
|
|
|
|
println!("Active users:");
|
|
for user in active_users {
|
|
let parts: Vec<&str> = user.tail().split_whitespace().collect();
|
|
if parts.len() >= 2 {
|
|
println!(" {} ({})", parts[0], parts[1]);
|
|
}
|
|
}
|
|
|
|
// Alternative: Process all users
|
|
let reader2 = StringReader::new(content);
|
|
let doc2 = TerraceDocument::with_reader(reader2);
|
|
|
|
let all_users: Vec<(String, String)> = doc2
|
|
.filter(|node| node.head() == "user")
|
|
.await
|
|
.into_iter()
|
|
.filter_map(|node| {
|
|
let parts: Vec<&str> = node.tail().split_whitespace().collect();
|
|
if parts.len() >= 2 {
|
|
Some((parts[0].to_string(), parts[1].to_string()))
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
println!("\nAll users:");
|
|
for (name, status) in all_users {
|
|
println!(" {}: {}", name, status);
|
|
}
|
|
}
|
|
|
|
Heading 3 Build Hierarchical Data Structure
|
|
class mb-2
|
|
Markdown
|
|
Parse a Terrace document into a hierarchical data structure.
|
|
CodeBlock rust
|
|
use terrace::{TerraceDocument, StringReader};
|
|
use std::collections::HashMap;
|
|
|
|
#[derive(Debug)]
|
|
enum Value {
|
|
String(String),
|
|
Number(f64),
|
|
Boolean(bool),
|
|
Object(HashMap<String, Value>),
|
|
}
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
let content = r#"
|
|
app
|
|
name My Application
|
|
version 1.0.0
|
|
debug true
|
|
database
|
|
host localhost
|
|
port 5432
|
|
credentials
|
|
username admin
|
|
password secret
|
|
features
|
|
auth true
|
|
logging false
|
|
"#;
|
|
|
|
let reader = StringReader::new(content);
|
|
let mut doc = TerraceDocument::with_reader(reader);
|
|
|
|
let mut root = HashMap::new();
|
|
let mut stack: Vec<(String, HashMap<String, Value>)> = Vec::new();
|
|
let mut current = &mut root;
|
|
|
|
while let Some(node) = doc.next().await {
|
|
if node.is_empty() {
|
|
continue;
|
|
}
|
|
|
|
match node.level() {
|
|
0 => {
|
|
// Root level - should be the main object name
|
|
if node.head() == "app" {
|
|
// Already at root
|
|
}
|
|
}
|
|
level => {
|
|
// Adjust stack to match current level
|
|
while stack.len() >= level {
|
|
stack.pop();
|
|
}
|
|
|
|
// Update current reference
|
|
if let Some((_, ref mut obj)) = stack.last_mut() {
|
|
current = obj;
|
|
} else {
|
|
current = &mut root;
|
|
}
|
|
|
|
// Parse the value
|
|
let value = if let Ok(num) = node.tail().parse::<f64>() {
|
|
Value::Number(num)
|
|
} else if node.tail() == "true" {
|
|
Value::Boolean(true)
|
|
} else if node.tail() == "false" {
|
|
Value::Boolean(false)
|
|
} else if node.tail().is_empty() {
|
|
// This is a nested object
|
|
let mut nested = HashMap::new();
|
|
current.insert(node.head().to_string(), Value::Object(nested.clone()));
|
|
stack.push((node.head().to_string(), nested));
|
|
continue;
|
|
} else {
|
|
Value::String(node.tail().to_string())
|
|
};
|
|
|
|
current.insert(node.head().to_string(), value);
|
|
}
|
|
}
|
|
}
|
|
|
|
println!("Parsed configuration:");
|
|
println!("{:?}", root);
|
|
}
|
|
|
|
Heading 3 Async File Reading
|
|
class mb-2
|
|
Markdown
|
|
Read a Terrace document from a file asynchronously.
|
|
CodeBlock rust
|
|
use terrace::{TerraceDocument, readers::AsyncReader};
|
|
use tokio::fs::File;
|
|
|
|
#[tokio::main]
|
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
// Open the file asynchronously
|
|
let file = File::open("config.tce").await?;
|
|
let reader = AsyncReader::new(file);
|
|
|
|
let mut doc = TerraceDocument::with_reader(reader);
|
|
|
|
println!("Configuration from file:");
|
|
while let Some(node) = doc.next().await {
|
|
if !node.is_empty() {
|
|
println!("{:indent$}{}: '{}'",
|
|
"",
|
|
node.head(),
|
|
node.tail(),
|
|
indent = node.level() * 2);
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
Heading 3 Error Handling
|
|
class mb-2
|
|
Markdown
|
|
Handle parsing errors and edge cases gracefully.
|
|
CodeBlock rust
|
|
use terrace::{TerraceDocument, StringReader};
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
let content = r#"
|
|
config
|
|
database
|
|
host localhost
|
|
port not_a_number
|
|
timeout 30
|
|
server
|
|
port 3000
|
|
host
|
|
"#;
|
|
|
|
let reader = StringReader::new(content);
|
|
let mut doc = TerraceDocument::with_reader(reader);
|
|
|
|
while let Some(node) = doc.next().await {
|
|
if node.is_empty() {
|
|
continue;
|
|
}
|
|
|
|
match node.head() {
|
|
"port" => {
|
|
match node.tail().parse::<u16>() {
|
|
Ok(port) => println!("Port: {}", port),
|
|
Err(_) => eprintln!("Warning: Invalid port '{}'", node.tail()),
|
|
}
|
|
}
|
|
"host" => {
|
|
if node.tail().is_empty() {
|
|
eprintln!("Warning: Empty host value");
|
|
} else {
|
|
println!("Host: {}", node.tail());
|
|
}
|
|
}
|
|
"timeout" => {
|
|
match node.tail().parse::<u64>() {
|
|
Ok(timeout) => println!("Timeout: {}ms", timeout),
|
|
Err(_) => eprintln!("Warning: Invalid timeout '{}'", node.tail()),
|
|
}
|
|
}
|
|
_ => {
|
|
println!("{}: {}", node.head(), node.tail());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Heading 3 Streaming Large Documents
|
|
class mb-2
|
|
Markdown
|
|
Process very large documents without loading everything into memory.
|
|
CodeBlock rust
|
|
use terrace::{TerraceDocument, StringReader};
|
|
|
|
#[tokio::main]
|
|
async fn main() {
|
|
// Simulate a large document
|
|
let mut large_content = String::new();
|
|
for i in 0..10000 {
|
|
large_content.push_str(&format!("item{}\n value{}\n count {}\n", i, i * 2, i * 3));
|
|
}
|
|
|
|
let reader = StringReader::new(large_content);
|
|
let mut doc = TerraceDocument::with_reader(reader);
|
|
|
|
let mut item_count = 0;
|
|
let mut total_values = 0i64;
|
|
|
|
while let Some(node) = doc.next().await {
|
|
match node.head() {
|
|
"item" => {
|
|
item_count += 1;
|
|
}
|
|
"value" => {
|
|
if let Ok(value) = node.tail().parse::<i64>() {
|
|
total_values += value;
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
println!("Processed {} items", item_count);
|
|
println!("Total values: {}", total_values);
|
|
println!("Average value: {}", total_values as f64 / item_count as f64);
|
|
}
|