# textfsm-rust
A Rust implementation of Google's [TextFSM](https://github.com/google/textfsm) template-based state machine for parsing semi-formatted text.
## Overview
TextFSM is a template-based state machine originally developed by Google for parsing CLI output from network devices. This crate provides a native Rust implementation with the same template syntax and behavior.
## Features
- **Battle-tested compatibility** - 99.8% pass rate against the [ntc-templates](https://github.com/networktocode/ntc-templates) test suite (1633/1637 tests)
- **Compile-time template validation** - Catch template errors at compile time with proc macros, not at runtime
- **Serde integration** - Deserialize parsed results directly into typed Rust structs
- **Thread-safe design** - `Template` is `Send + Sync`; share compiled templates across threads with zero overhead
- **Detailed error messages** - Template errors include line numbers, context, and suggestions
- **Full TextFSM syntax** - All value options (`Required`, `Filldown`, `Fillup`, `List`, `Key`) and actions (`Next`, `Continue`, `Record`, `Clear`, `Error`)
- **Efficient regex handling** - Uses [fancy-regex](https://docs.rs/fancy-regex) which delegates to the fast [regex](https://docs.rs/regex) crate when advanced features aren't needed
- **Python regex compatibility** - Handles Python regex quirks like `\<` and `\>` automatically
## Installation
Add to your `Cargo.toml`:
```toml
[dependencies]
textfsm-rust = "0.1"
```
## Quick Start
```rust
use textfsm_rust::Template;
let template = Template::parse_str(r#"
Value Name (\S+)
Value Age (\d+)
Start
^Name: ${Name}, Age: ${Age} -> Record
"#)?;
let mut parser = template.parser();
let input = "Name: Alice, Age: 30\nName: Bob, Age: 25\n";
let results = parser.parse_text(input)?;
```
## Compile-Time Template Validation
Catch template errors before your program runs:
```rust
use textfsm_rust::{validate_template, validate_templates, Template};
// Validate a single template at compile time
validate_template!("templates/cisco_show_version.textfsm");
// Validate all .textfsm files in a directory
validate_templates!("templates/");
// Parse at runtime when you need it
let template = Template::parse_str(
include_str!("templates/cisco_show_version.textfsm")
).expect("validated at compile time");
```
If a template is invalid, you get a compile error with the template line number:
```
error: Template validation failed for 'templates/bad.textfsm':
invalid variable substitution at line 8: unknown variable 'Interfce'
--> src/main.rs:4:21
|
```
## Template Syntax
TextFSM templates consist of two sections:
### Value Definitions
```
Value [Options] Name (Regex)
```
Options:
- `Required` - Record only saved if this value is matched
- `Filldown` - Value retained across records
- `Fillup` - Value fills upward into previous records
- `List` - Value is a list of matches
- `Key` - Marks value as a key field
### State Rules
```
StateName
^Regex -> [Actions]
```
Actions:
- `Next` - Continue to next line (default)
- `Continue` - Continue processing current line
- `Record` - Save current record
- `Clear` - Clear non-filldown values
- `Error` - Stop processing with error
## Serde Integration
Enable the `serde` feature to deserialize parsed results directly into typed Rust structs:
```toml
[dependencies]
textfsm-rust = { version = "0.1", features = ["serde"] }
```
```rust
use textfsm_rust::{Deserialize, Template};
#[derive(Deserialize, Debug)]
struct Interface {
interface: String,
status: String,
ip_address: Option<String>,
}
let template = Template::parse_str(r#"
Value Interface (\S+)
Start
^Interface: ${Interface} is ${Status}
^ IP: ${IP_Address} -> Record
"#)?;
let mut parser = template.parser();
let input = "Interface: eth0 is up\n IP: 192.168.1.1\nInterface: eth1 is down\n IP: 10.0.0.1\n";
// Deserialize directly into typed structs
let interfaces: Vec<Interface> = parser.parse_text_into(input)?;
assert_eq!(interfaces[0].interface, "eth0");
assert_eq!(interfaces[0].status, "up");
assert_eq!(interfaces[0].ip_address, Some("192.168.1.1".into()));
```
Field names are matched case-insensitively against template value names (underscores are preserved).
## Thread Safety
`Template` is `Send + Sync` and can be safely shared across threads. The compiled template (including all regexes) is immutable after creation, so you can wrap it in an `Arc` and share it freely.
`Parser` is a lightweight, stateful wrapper that borrows a `Template`. Create one per thread - they're cheap since they don't copy the compiled regexes.
```rust
use std::sync::Arc;
use std::thread;
let template = Arc::new(Template::parse_str(template_str)?);