textfsm-core 0.2.0

Core parsing library for TextFSM template-based state machine
Documentation

textfsm-rust

A Rust implementation of Google's 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 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 which delegates to the fast regex crate when advanced features aren't needed
  • Python regex compatibility - Handles Python regex quirks like \< and \> automatically

Installation

Add to your Cargo.toml:

[dependencies]
textfsm-rust = "0.1"

Quick Start

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:

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
  |
4 | validate_template!("templates/bad.textfsm");
  |                    ^^^^^^^^^^^^^^^^^^^^^^^^

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:

[dependencies]
textfsm-rust = { version = "0.1", features = ["serde"] }
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+)
Value Status (up|down)
Value IP_Address (\d+\.\d+\.\d+\.\d+)

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.

use std::sync::Arc;
use std::thread;

let template = Arc::new(Template::parse_str(template_str)?);

let handles: Vec<_> = inputs.into_iter().map(|input| {
    let template = Arc::clone(&template);
    thread::spawn(move || {
        let mut parser = template.parser();
        parser.parse_text(&input)
    })
}).collect();

This design is efficient for concurrent workloads like parsing output from multiple network devices in parallel.

Error Messages

Template errors include line numbers to help you fix issues quickly:

invalid variable substitution at line 8: unknown variable 'Interfce'
invalid Value definition at line 3: regex must be wrapped in parentheses
invalid rule at line 12: unclosed variable substitution
missing required 'Start' state

Roadmap

  • Full TextFSM syntax compatibility
  • Tested against ntc-templates (99.8% pass rate)
  • Thread-safe template sharing
  • Compile-time template validation macros
  • Serde deserialization for parsed results
  • CliTable - index-based template selection by command/platform (like Python's CliTable)
  • Template linting and suggestions
  • Performance benchmarks vs Python implementation

License

Licensed under either of:

at your option.

Acknowledgments

This project is a Rust port of Google's TextFSM, originally developed by Google Inc. and licensed under Apache 2.0.