Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ All notable changes to this project will be documented in this file.
`rpkiviews`; invalid source/collector combinations now return errors (#134).
* Added `--use-cache` / `--cache-dir` MRT-file caching and `--fields` output
selection to the `rib` command (#137).
* Added Cisco `sh ip bgp` text dump parsing to the `parse` command. The parser
auto-detects the fixed-width plaintext format from the preamble, extracts
prefix/AS-path/next-hop/metric/local-pref/origin, and supports all standard
parse filters, output formats, and MRT export (`--mrt-type rib|updates`).
Multi-path continuation lines and wrapped-prefix layouts are handled.
Header column detection uses whitespace split rather than fixed positions.

### Performance Improvements

Expand Down
278 changes: 182 additions & 96 deletions src/bin/commands/parse.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use std::io::Write;
use std::path::PathBuf;

use bgpkit_parser::encoder::MrtUpdatesEncoder;
use bgpkit_parser::encoder::{MrtRibEncoder, MrtUpdatesEncoder};
use bgpkit_parser::parser::filter::Filterable;
use bgpkit_parser::BgpElem;
use clap::Args;

use monocle::lens::parse::filter_file::{load_prefix_file, merge_prefix_file, FilterFile};
use monocle::lens::parse::{ParseFilters, ParseLens};
use monocle::lens::parse::text_dump;
use monocle::lens::parse::{MrtType, ParseFilters, ParseLens};
use monocle::utils::{OrderByField, OrderDirection, OutputFormat, TimestampFormat};

use super::elem_format::{
Expand All @@ -16,7 +18,8 @@ use super::elem_format::{
/// Arguments for the Parse command
#[derive(Args)]
pub(crate) struct ParseArgs {
/// File path to an MRT file, local or remote.
/// File path to an MRT file or Cisco `sh ip bgp` text dump, local or remote.
/// Format is auto-detected: binary MRT vs plaintext BGP table dump.
#[clap(name = "FILE")]
pub file_path: PathBuf,

Expand All @@ -28,6 +31,11 @@ pub(crate) struct ParseArgs {
#[clap(long, short = 'M')]
pub mrt_path: Option<PathBuf>,

/// MRT output subtype: `rib` for TABLE_DUMP_V2 RIB entries, `updates` for BGP4MP messages.
/// Default: auto-inferred (`rib` for text dumps, `updates` for MRT files).
#[clap(long, value_enum, value_name = "TYPE")]
pub mrt_type: Option<MrtType>,

/// Comma-separated list of fields to output
#[clap(long, short = 'f', value_name = "FIELDS", help = available_fields_help())]
pub fields: Option<String>,
Expand Down Expand Up @@ -64,6 +72,7 @@ pub fn run(args: ParseArgs, output_format: OutputFormat) {
file_path,
pretty,
mrt_path,
mrt_type,
fields: fields_arg,
order_by,
order,
Expand Down Expand Up @@ -106,136 +115,213 @@ pub fn run(args: ParseArgs, output_format: OutputFormat) {
return;
}

let file_path = match file_path.to_str() {
let file_path_str = match file_path.to_str() {
Some(path) => path,
None => {
eprintln!("Invalid file path");
std::process::exit(1);
}
};
let parser = match lens.create_parser(&filters, file_path) {
Ok(p) => p,

// ── Format detection ──────────────────────────────────────────
let reader = match oneio::get_reader(file_path_str) {
Ok(r) => r,
Err(e) => {
eprintln!("Failed to create parser for {}: {}", file_path, e);
eprintln!("Failed to open {}: {}", file_path_str, e);
std::process::exit(1);
}
};

let mut stdout = std::io::stdout();
let (is_text, _peeked) = match text_dump::detect_text_dump(reader) {
Ok(result) => result,
Err(e) => {
eprintln!("Failed to read {}: {}", file_path_str, e);
std::process::exit(1);
}
};

// Determine MRT type: explicit flag takes precedence, otherwise infer.
let mrt_type = mrt_type.unwrap_or_else(|| MrtType::infer(is_text));

// Adjust output format for pretty flag
// Preserve streaming output for ordinary MRT parsing. Text dumps and MRT
// exports require materialization for their respective encoders/parsers.
let output_format = if pretty && output_format == OutputFormat::Json {
OutputFormat::JsonPretty
} else {
output_format
};

// Determine if we need to buffer (for Table format or when ordering is requested)
let needs_buffering = output_format == OutputFormat::Table || order_by.is_some();

match mrt_path {
None => {
// If buffering is needed (Table format or ordering requested), collect and sort
if needs_buffering {
let mut elems: Vec<(BgpElem, Option<String>)> =
parser.into_iter().map(|elem| (elem, None)).collect();

// Sort if ordering is requested
if let Some(order_field) = order_by {
sort_elems(&mut elems, order_field, order);
if !is_text && mrt_path.is_none() && !needs_buffering {
let parser = match lens.create_parser(&filters, file_path_str) {
Ok(parser) => parser,
Err(error) => {
eprintln!("Failed to create parser for {}: {}", file_path_str, error);
std::process::exit(1);
}
};
let mut stdout = std::io::stdout();
if let Some(header) = get_header(output_format, &fields) {
if let Err(error) = writeln!(stdout, "{}", header) {
if error.kind() != std::io::ErrorKind::BrokenPipe {
eprintln!("ERROR: {error}");
}

if elems.is_empty() {
return;
std::process::exit(1);
}
}
for elem in parser {
if let Some(output) = format_elem(&elem, output_format, &fields, None, time_format) {
if let Err(error) = writeln!(stdout, "{}", output) {
if error.kind() != std::io::ErrorKind::BrokenPipe {
eprintln!("ERROR: {error}");
}
std::process::exit(1);
}
}
}
return;
}

// Output based on format
if output_format == OutputFormat::Table {
println!("{}", format_elems_table(&elems, &fields, time_format));
} else {
// Print header for markdown format
if let Some(header) = get_header(output_format, &fields) {
if let Err(e) = writeln!(stdout, "{}", header) {
if e.kind() != std::io::ErrorKind::BrokenPipe {
eprintln!("ERROR: {e}");
}
std::process::exit(1);
}
}
// ── Parse ─────────────────────────────────────────────────────
let elems: Vec<(BgpElem, Option<String>)> = if is_text {
// Re-open the file for full parsing (detect_text_dump consumed the reader)
let reader = match oneio::get_reader(file_path_str) {
Ok(r) => r,
Err(e) => {
eprintln!("Failed to re-open {}: {}", file_path_str, e);
std::process::exit(1);
}
};
let buf_reader = std::io::BufReader::new(reader);
let text_filters = match filters.to_filters() {
Ok(filters) => filters,
Err(e) => {
eprintln!("Failed to build text dump filters: {e}");
std::process::exit(1);
}
};
let timestamp = text_dump::infer_timestamp_from_path(file_path_str).unwrap_or(0.0);
match text_dump::parse_text_dump_with_timestamp(buf_reader, timestamp) {
Ok(elems) => elems
.into_iter()
.filter(|elem| elem.match_filters(&text_filters))
.map(|elem| (elem, None))
.collect(),
Err(e) => {
eprintln!("Failed to parse text dump {}: {}", file_path_str, e);
std::process::exit(1);
}
}
} else {
// Standard MRT parse: re-open since detect_text_dump consumed the reader
let parser = match lens.create_parser(&filters, file_path_str) {
Ok(p) => p,
Err(e) => {
eprintln!("Failed to create parser for {}: {}", file_path_str, e);
std::process::exit(1);
}
};
parser.into_iter().map(|elem| (elem, None)).collect()
};

// Output sorted elements
for (elem, collector) in &elems {
if let Some(output_str) = format_elem(
elem,
output_format,
&fields,
collector.as_deref(),
time_format,
) {
if let Err(e) = writeln!(stdout, "{}", output_str) {
if e.kind() != std::io::ErrorKind::BrokenPipe {
eprintln!("ERROR: {e}");
}
std::process::exit(1);
}
}
}
}
return;
if elems.is_empty() {
return;
}

let mut stdout = std::io::stdout();

// ── MRT output ─────────────────────────────────────────────────
if let Some(ref mrt_out_path) = mrt_path {
let mrt_out_str = match mrt_out_path.to_str() {
Some(s) => s,
None => {
eprintln!("Invalid MRT output path");
std::process::exit(1);
}
};

// Streaming output (no buffering needed)
// Print header for markdown format before first element
if let Some(header) = get_header(output_format, &fields) {
if let Err(e) = writeln!(stdout, "{}", header) {
if e.kind() != std::io::ErrorKind::BrokenPipe {
eprintln!("ERROR: {e}");
eprintln!("writing MRT ({mrt_type:?}) to {mrt_out_str}...");

match mrt_type {
MrtType::Rib => {
let mut encoder = MrtRibEncoder::new();
for (elem, _) in &elems {
encoder.process_elem(elem);
}
let bytes = encoder.export_bytes();
match oneio::get_writer(mrt_out_str) {
Ok(mut w) => {
if let Err(e) = w.write_all(&bytes) {
eprintln!("Failed to write MRT data: {}", e);
}
}
Err(e) => {
eprintln!("Failed to create MRT writer: {}", e);
std::process::exit(1);
}
std::process::exit(1);
}
}

for elem in parser {
// output to stdout based on format
if let Some(output_str) =
format_elem(&elem, output_format, &fields, None, time_format)
{
if let Err(e) = writeln!(stdout, "{}", output_str) {
if e.kind() != std::io::ErrorKind::BrokenPipe {
eprintln!("ERROR: {e}");
MrtType::Updates => {
let mut encoder = MrtUpdatesEncoder::new();
for (elem, _) in &elems {
encoder.process_elem(elem);
}
let bytes = encoder.export_bytes();
match oneio::get_writer(mrt_out_str) {
Ok(mut w) => {
if let Err(e) = w.write_all(&bytes) {
eprintln!("Failed to write MRT data: {}", e);
}
}
Err(e) => {
eprintln!("Failed to create MRT writer: {}", e);
std::process::exit(1);
}
}
}
}
Some(p) => {
let path = match p.to_str() {
Some(path) => path.to_string(),
None => {
eprintln!("Invalid MRT path");
std::process::exit(1);
}
};
eprintln!("processing. filtered messages output to {}...", path);
let mut encoder = MrtUpdatesEncoder::new();
let mut writer = match oneio::get_writer(&path) {
Ok(w) => w,
Err(e) => {

eprintln!("done. total of {} messages written", elems.len());
return;
}

// ── Text output ────────────────────────────────────────────────

// Sort if ordering is requested
let mut elems = elems;
if let Some(order_field) = order_by {
sort_elems(&mut elems, order_field, order);
}

// Output based on format
if output_format == OutputFormat::Table {
println!("{}", format_elems_table(&elems, &fields, time_format));
} else {
// Print header for markdown format
if let Some(header) = get_header(output_format, &fields) {
if let Err(e) = writeln!(stdout, "{}", header) {
if e.kind() != std::io::ErrorKind::BrokenPipe {
eprintln!("ERROR: {e}");
std::process::exit(1);
}
};
let mut total_count = 0;
for elem in parser {
total_count += 1;
encoder.process_elem(&elem);
std::process::exit(1);
}
if let Err(e) = writer.write_all(&encoder.export_bytes()) {
eprintln!("Failed to write MRT data: {}", e);
}

// Output elements
for (elem, collector) in &elems {
if let Some(output_str) = format_elem(
elem,
output_format,
&fields,
collector.as_deref(),
time_format,
) {
if let Err(e) = writeln!(stdout, "{}", output_str) {
if e.kind() != std::io::ErrorKind::BrokenPipe {
eprintln!("ERROR: {e}");
}
std::process::exit(1);
}
}
drop(writer);
eprintln!("done. total of {} message wrote", total_count);
}
}
}
Loading
Loading