Repository: fujiapple852/trippy
Pull request #1185 (2024-06-16 03:34:53)

Refactor misc


Patch:
diff --git a/crates/trippy/src/app.rs b/crates/trippy/src/app.rs
new file mode 100644
index 0000000..b1f094a
--- /dev/null
+++ b/crates/trippy/src/app.rs
@@ -0,0 +1,382 @@
+use crate::backend::trace::Trace;
+use crate::backend::Backend;
+use crate::config::{LogFormat, LogSpanEvents, Mode, TrippyConfig};
+use crate::frontend::TuiConfig;
+use crate::geoip::GeoIpLookup;
+use crate::{frontend, report};
+use anyhow::{anyhow, Error};
+use parking_lot::RwLock;
+use std::net::IpAddr;
+use std::sync::Arc;
+use std::thread;
+use std::time::Duration;
+use tracing_chrome::{ChromeLayerBuilder, FlushGuard};
+use tracing_subscriber::fmt::format::FmtSpan;
+use tracing_subscriber::layer::SubscriberExt;
+use tracing_subscriber::util::SubscriberInitExt;
+use trippy_core::{
+    ChannelConfig, Config, IcmpExtensionParseMode, MultipathStrategy, PlatformImpl, PortDirection,
+    PrivilegeMode, Protocol, SocketImpl, SourceAddr,
+};
+use trippy_dns::{DnsResolver, Resolver};
+use trippy_privilege::Privilege;
+
+pub fn run_trippy(cfg: &TrippyConfig, pid: u16) -> anyhow::Result<()> {
+    let _guard = configure_logging(cfg);
+    let resolver = start_dns_resolver(cfg)?;
+    let geoip_lookup = create_geoip_lookup(cfg)?;
+    let addrs = resolve_targets(cfg, &resolver)?;
+    if addrs.is_empty() {
+        return Err(anyhow!(
+            "failed to find any valid IP addresses for {} for address family {}",
+            cfg.targets.join(", "),
+            cfg.addr_family,
+        ));
+    }
+    let traces = start_tracers(cfg, &addrs, pid)?;
+    Privilege::drop_privileges()?;
+    run_frontend(cfg, resolver, geoip_lookup, traces)
+}
+
+/// Start all tracers.
+fn start_tracers(
+    cfg: &TrippyConfig,
+    addrs: &[TargetInfo],
+    pid: u16,
+) -> anyhow::Result<Vec<TraceInfo>> {
+    addrs
+        .iter()
+        .enumerate()
+        .map(|(i, TargetInfo { hostname, addr })| {
+            start_tracer(cfg, hostname, *addr, pid + i as u16)
+        })
+        .collect::<anyhow::Result<Vec<_>>>()
+}
+
+/// Start a tracer to a given target.
+fn start_tracer(
+    cfg: &TrippyConfig,
+    target_host: &str,
+    target_addr: IpAddr,
+    trace_identifier: u16,
+) -> Result<TraceInfo, Error> {
+    let source_addr = match cfg.source_addr {
+        None => SourceAddr::discover::<SocketImpl, PlatformImpl>(
+            target_addr,
+            cfg.port_direction,
+            cfg.interface.as_deref(),
+        )?,
+        Some(addr) => SourceAddr::validate::<SocketImpl>(addr)?,
+    };
+    let channel_config = make_channel_config(cfg, source_addr, target_addr);
+    let tracer_config = make_tracer_config(cfg, target_addr, trace_identifier)?;
+    let backend = Backend::new(
+        tracer_config,
+        channel_config,
+        cfg.tui_max_samples,
+        cfg.max_flows(),
+    );
+    let trace_data = backend.trace();
+    thread::Builder::new()
+        .name(format!("tracer-{}", tracer_config.trace_identifier.0))
+        .spawn(move || backend.start().expect("failed to run tracer backend"))?;
+    Ok(make_trace_info(
+        cfg,
+        trace_data,
+        source_addr,
+        target_host.to_string(),
+        target_addr,
+    ))
+}
+
+/// Run the TUI, stream or report.
+fn run_frontend(
+    args: &TrippyConfig,
+    resolver: DnsResolver,
+    geoip_lookup: GeoIpLookup,
+    traces: Vec<TraceInfo>,
+) -> anyhow::Result<()> {
+    match args.mode {
+        Mode::Tui => frontend::run_frontend(traces, make_tui_config(args), resolver, geoip_lookup)?,
+        Mode::Stream => report::stream::report(&traces[0], &resolver)?,
+        Mode::Csv => report::csv::report(&traces[0], args.report_cycles, &resolver)?,
+        Mode::Json => report::json::report(&traces[0], args.report_cycles, &resolver)?,
+        Mode::Pretty => report::table::report_pretty(&traces[0], args.report_cycles, &resolver)?,
+        Mode::Markdown => report::table::report_md(&traces[0], args.report_cycles, &resolver)?,
+        Mode::Dot => report::dot::report(&traces[0], args.report_cycles)?,
+        Mode::Flows => report::flows::report(&traces[0], args.report_cycles)?,
+        Mode::Silent => report::silent::report(&traces[0], args.report_cycles)?,
+    }
+    Ok(())
+}
+
+/// Resolve targets.
+fn resolve_targets(cfg: &TrippyConfig, resolver: &DnsResolver) -> anyhow::Result<Vec<TargetInfo>> {
+    cfg.targets
+        .iter()
+        .flat_map(|target| match resolver.lookup(target) {
+            Ok(addrs) => addrs
+                .into_iter()
+                .enumerate()
+                .take_while(|(i, _)| if cfg.dns_resolve_all { true } else { *i == 0 })
+                .map(|(i, addr)| {
+                    let hostname = if cfg.dns_resolve_all {
+                        format!("{} [{}]", target, i + 1)
+                    } else {
+                        target.to_string()
+                    };
+                    Ok(TargetInfo { hostname, addr })
+                })
+                .collect::<Vec<_>>()
+                .into_iter(),
+            Err(e) => {
+                vec![Err(anyhow!("failed to resolve target: {} ({})", target, e))].into_iter()
+            }
+        })
+        .collect::<anyhow::Result<Vec<_>>>()
+}
+
+/// Start the DNS resolver.
+fn start_dns_resolver(cfg: &TrippyConfig) -> anyhow::Result<DnsResolver> {
+    Ok(DnsResolver::start(trippy_dns::Config::new(
+        cfg.dns_resolve_method,
+        cfg.addr_family,
+        cfg.dns_timeout,
+    ))?)
+}
+
+fn create_geoip_lookup(cfg: &TrippyConfig) -> anyhow::Result<GeoIpLookup> {
+    if let Some(path) = cfg.geoip_mmdb_file.as_ref() {
+        GeoIpLookup::from_file(path)
+    } else {
+        Ok(GeoIpLookup::empty())
+    }
+}
+
+fn configure_logging(cfg: &TrippyConfig) -> Option<FlushGuard> {
+    if cfg.verbose {
+        let fmt_span = match cfg.log_span_events {
+            LogSpanEvents::Off => FmtSpan::NONE,
+            LogSpanEvents::Active => FmtSpan::ACTIVE,
+            LogSpanEvents::Full => FmtSpan::FULL,
+        };
+        match cfg.log_format {
+            LogFormat::Compact => {
+                tracing_subscriber::fmt()
+                    .with_span_events(fmt_span)
+                    .with_env_filter(&cfg.log_filter)
+                    .compact()
+                    .init();
+            }
+            LogFormat::Pretty => {
+                tracing_subscriber::fmt()
+                    .with_span_events(fmt_span)
+                    .with_env_filter(&cfg.log_filter)
+                    .pretty()
+                    .init();
+            }
+            LogFormat::Json => {
+                tracing_subscriber::fmt()
+                    .with_span_events(fmt_span)
+                    .with_env_filter(&cfg.log_filter)
+                    .json()
+                    .init();
+            }
+            LogFormat::Chrome => {
+                let (chrome_layer, guard) = ChromeLayerBuilder::new()
+                    .writer(std::io::stdout())
+                    .include_args(true)
+                    .build();
+                tracing_subscriber::registry().with(chrome_layer).init();
+                return Some(guard);
+            }
+        }
+    }
+    None
+}
+
+/// Make the tracer configuration.
+fn make_tracer_config(
+    args: &TrippyConfig,
+    target_addr: IpAddr,
+    trace_identifier: u16,
+) -> anyhow::Result<Config> {
+    Ok(Config::new(
+        target_addr,
+        args.protocol,
+        args.max_rounds,
+        trace_identifier,
+        args.first_ttl,
+        args.max_ttl,
+        args.grace_duration,
+        args.max_inflight,
+        args.initial_sequence,
+        args.multipath_strategy,
+        args.port_direction,
+        args.min_round_duration,
+        args.max_round_duration,
+    )?)
+}
+
+/// Make the tracer configuration.
+fn make_channel_config(
+    args: &TrippyConfig,
+    source_addr: IpAddr,
+    target_addr: IpAddr,
+) -> ChannelConfig {
+    ChannelConfig::new(
+        args.privilege_mode,
+        args.protocol,
+        source_addr,
+        target_addr,
+        args.packet_size,
+        args.payload_pattern,
+        args.initial_sequence,
+        args.tos,
+        args.icmp_extension_parse_mode,
+        args.read_timeout,
+        args.min_round_duration,
+    )
+}
+
+/// Make the per-trace information.
+fn make_trace_info(
+    args: &TrippyConfig,
+    trace_data: Arc<RwLock<Trace>>,
+    source_addr: IpAddr,
+    target: String,
+    target_addr: IpAddr,
+) -> TraceInfo {
+    TraceInfo::new(
+        trace_data,
+        source_addr,
+        target,
+        target_addr,
+        args.privilege_mode,
+        args.multipath_strategy,
+        args.port_direction,
+        args.protocol,
+        args.first_ttl,
+        args.max_ttl,
+        args.grace_duration,
+        args.min_round_duration,
+        args.max_round_duration,
+        args.max_inflight,
+        args.initial_sequence,
+        args.icmp_extension_parse_mode,
+        args.read_timeout,
+        args.packet_size,
+        args.payload_pattern,
+        args.interface.clone(),
+        args.geoip_mmdb_file.clone(),
+        args.dns_resolve_all,
+    )
+}
+
+/// Make the TUI configuration.
+fn make_tui_config(args: &TrippyConfig) -> TuiConfig {
+    TuiConfig::new(
+        args.tui_refresh_rate,
+        args.tui_privacy_max_ttl,
+        args.tui_preserve_screen,
+        args.tui_address_mode,
+        args.dns_lookup_as_info,
+        args.tui_as_mode,
+        args.tui_icmp_extension_mode,
+        args.tui_geoip_mode,
+        args.tui_max_addrs,
+        args.tui_max_samples,
+        args.max_flows(),
+        args.tui_theme,
+        &args.tui_bindings,
+        &args.tui_custom_columns,
+    )
+}
+
+/// Information about a `Trace` needed for the Tui, stream and reports.
+#[derive(Debug, Clone)]
+pub struct TraceInfo {
+    pub data: Arc<RwLock<Trace>>,
+    pub source_addr: IpAddr,
+    pub target_hostname: String,
+    pub target_addr: IpAddr,
+    pub privilege_mode: PrivilegeMode,
+    pub multipath_strategy: MultipathStrategy,
+    pub port_direction: PortDirection,
+    pub protocol: Protocol,
+    pub first_ttl: u8,
+    pub max_ttl: u8,
+    pub grace_duration: Duration,
+    pub min_round_duration: Duration,
+    pub max_round_duration: Duration,
+    pub max_inflight: u8,
+    pub initial_sequence: u16,
+    pub icmp_extensions: IcmpExtensionParseMode,
+    pub read_timeout: Duration,
+    pub packet_size: u16,
+    pub payload_pattern: u8,
+    pub interface: Option<String>,
+    pub geoip_mmdb_file: Option<String>,
+    pub dns_resolve_all: bool,
+}
+
+impl TraceInfo {
+    #[allow(clippy::too_many_arguments)]
+    #[must_use]
+    pub fn new(
+        data: Arc<RwLock<Trace>>,
+        source_addr: IpAddr,
+        target_hostname: String,
+        target_addr: IpAddr,
+        privilege_mode: PrivilegeMode,
+        multipath_strategy: MultipathStrategy,
+        port_direction: PortDirection,
+        protocol: Protocol,
+        first_ttl: u8,
+        max_ttl: u8,
+        grace_duration: Duration,
+        min_round_duration: Duration,
+        max_round_duration: Duration,
+        max_inflight: u8,
+        initial_sequence: u16,
+        icmp_extensions: IcmpExtensionParseMode,
+        read_timeout: Duration,
+        packet_size: u16,
+        payload_pattern: u8,
+        interface: Option<String>,
+        geoip_mmdb_file: Option<String>,
+        dns_resolve_all: bool,
+    ) -> Self {
+        Self {
+            data,
+            source_addr,
+            target_hostname,
+            target_addr,
+            privilege_mode,
+            multipath_strategy,
+            port_direction,
+            protocol,
+            first_ttl,
+            max_ttl,
+            grace_duration,
+            min_round_duration,
+            max_round_duration,
+            max_inflight,
+            initial_sequence,
+            icmp_extensions,
+            read_timeout,
+            packet_size,
+            payload_pattern,
+            interface,
+            geoip_mmdb_file,
+            dns_resolve_all,
+        }
+    }
+}
+
+/// Information about a tracing target.
+#[derive(Debug, Clone)]
+struct TargetInfo {
+    pub hostname: String,
+    pub addr: IpAddr,
+}
diff --git a/crates/trippy/src/config.rs b/crates/trippy/src/config.rs
index ed44aa4..2343f4f 100644
--- a/crates/trippy/src/config.rs
+++ b/crates/trippy/src/config.rs
@@ -1094,7 +1094,7 @@ fn validate_bindings(bindings: &TuiBindings) -> anyhow::Result<()> {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::tests::{insta, remove_whitespace};
+    use crate::util::{insta, remove_whitespace};
     use crossterm::event::KeyCode;
     use std::net::{Ipv4Addr, Ipv6Addr};
     use std::str::FromStr;
diff --git a/crates/trippy/src/frontend.rs b/crates/trippy/src/frontend.rs
index 64c58be..2d425b9 100644
--- a/crates/trippy/src/frontend.rs
+++ b/crates/trippy/src/frontend.rs
@@ -1,7 +1,7 @@
+use crate::app::TraceInfo;
 use crate::config::AddressMode;
 use crate::frontend::binding::CTRL_C;
 use crate::geoip::GeoIpLookup;
-use crate::TraceInfo;
 pub use config::TuiConfig;
 use crossterm::event::KeyEventKind;
 use crossterm::{
diff --git a/crates/trippy/src/frontend/tui_app.rs b/crates/trippy/src/frontend/tui_app.rs
index 5e53bc1..6d8b599 100644
--- a/crates/trippy/src/frontend/tui_app.rs
+++ b/crates/trippy/src/frontend/tui_app.rs
@@ -1,10 +1,10 @@
+use crate::app::TraceInfo;
 use crate::backend::flows::FlowId;
 use crate::backend::trace::Hop;
 use crate::backend::trace::Trace;
 use crate::frontend::config::TuiConfig;
 use crate::frontend::render::settings::{SETTINGS_TABS, SETTINGS_TAB_COLUMNS};
 use crate::geoip::GeoIpLookup;
-use crate::TraceInfo;
 use itertools::Itertools;
 use ratatui::widgets::TableState;
 use std::time::SystemTime;
diff --git a/crates/trippy/src/lib.rs b/crates/trippy/src/lib.rs
index e21f8d1..b454fd0 100644
--- a/crates/trippy/src/lib.rs
+++ b/crates/trippy/src/lib.rs
@@ -1,2 +1,5 @@
 #![allow(rustdoc::broken_intra_doc_links, rustdoc::bare_urls)]
 #![doc = include_str!("../../../README.md")]
+
+// Re-export the core library, so it may be used from trippy crate.
+pub use trippy_core::*;
diff --git a/crates/trippy/src/main.rs b/crates/trippy/src/main.rs
index 38a53be..92212e2 100644
--- a/crates/trippy/src/main.rs
+++ b/crates/trippy/src/main.rs
@@ -15,40 +15,20 @@
 )]
 #![forbid(unsafe_code)]
 
-use crate::backend::Backend;
-use crate::config::{
-    LogFormat, LogSpanEvents, Mode, TrippyAction, TrippyConfig, TuiCommandItem, TuiThemeItem,
-};
-use crate::geoip::GeoIpLookup;
-use anyhow::{anyhow, Error};
-use backend::trace::Trace;
-use clap::{CommandFactory, Parser};
-use clap_complete::Shell;
+use crate::config::TrippyAction;
+use clap::Parser;
 use config::Args;
-use frontend::TuiConfig;
-use parking_lot::RwLock;
-use std::net::IpAddr;
-use std::sync::Arc;
-use std::time::Duration;
-use std::{process, thread};
-use strum::VariantNames;
-use tracing_chrome::{ChromeLayerBuilder, FlushGuard};
-use tracing_subscriber::fmt::format::FmtSpan;
-use tracing_subscriber::layer::SubscriberExt;
-use tracing_subscriber::util::SubscriberInitExt;
-use trippy_core::{
-    ChannelConfig, Config, IcmpExtensionParseMode, MultipathStrategy, PlatformImpl, PortDirection,
-    Protocol, SocketImpl,
-};
-use trippy_core::{PrivilegeMode, SourceAddr};
-use trippy_dns::{DnsResolver, Resolver};
+use std::process;
 use trippy_privilege::Privilege;
 
+mod app;
 mod backend;
 mod config;
 mod frontend;
 mod geoip;
+mod print;
 mod report;
+mod util;
 
 fn main() -> anyhow::Result<()> {
     let args = Args::parse();
@@ -56,459 +36,12 @@ fn main() -> anyhow::Result<()> {
     let privilege = Privilege::discover()?;
     let pid = u16::try_from(process::id() % u32::from(u16::MAX))?;
     match TrippyAction::from(args, &privilege, pid)? {
-        TrippyAction::Trippy(cfg) => run_trippy(&cfg, pid)?,
-        TrippyAction::PrintTuiThemeItems => print_tui_theme_items(),
-        TrippyAction::PrintTuiBindingCommands => print_tui_binding_commands(),
-        TrippyAction::PrintConfigTemplate => print_config_template(),
-        TrippyAction::PrintManPage => print_man_page()?,
-        TrippyAction::PrintShellCompletions(shell) => print_shell_completions(shell)?,
+        TrippyAction::Trippy(cfg) => app::run_trippy(&cfg, pid)?,
+        TrippyAction::PrintTuiThemeItems => print::print_tui_theme_items(),
+        TrippyAction::PrintTuiBindingCommands => print::print_tui_binding_commands(),
+        TrippyAction::PrintConfigTemplate => print::print_config_template(),
+        TrippyAction::PrintManPage => print::print_man_page()?,
+        TrippyAction::PrintShellCompletions(shell) => print::print_shell_completions(shell)?,
     }
     Ok(())
 }
-
-fn run_trippy(cfg: &TrippyConfig, pid: u16) -> anyhow::Result<()> {
-    let _guard = configure_logging(cfg);
-    let resolver = start_dns_resolver(cfg)?;
-    let geoip_lookup = create_geoip_lookup(cfg)?;
-    let addrs = resolve_targets(cfg, &resolver)?;
-    if addrs.is_empty() {
-        return Err(anyhow!(
-            "failed to find any valid IP addresses for {} for address family {}",
-            cfg.targets.join(", "),
-            cfg.addr_family,
-        ));
-    }
-    let traces = start_tracers(cfg, &addrs, pid)?;
-    Privilege::drop_privileges()?;
-    run_frontend(cfg, resolver, geoip_lookup, traces)
-}
-
-fn print_tui_theme_items() {
-    println!("{}", tui_theme_items());
-    process::exit(0);
-}
-
-fn print_tui_binding_commands() {
-    println!("{}", tui_binding_commands());
-    process::exit(0);
-}
-
-fn print_config_template() {
-    println!("{}", include_str!("../../../trippy-config-sample.toml"));
-    process::exit(0);
-}
-
-fn print_shell_completions(shell: Shell) -> anyhow::Result<()> {
-    println!("{}", shell_completions(shell)?);
-    process::exit(0);
-}
-
-fn print_man_page() -> anyhow::Result<()> {
-    println!("{}", man_page()?);
-    process::exit(0);
-}
-
-/// Start the DNS resolver.
-fn start_dns_resolver(cfg: &TrippyConfig) -> anyhow::Result<DnsResolver> {
-    Ok(DnsResolver::start(trippy_dns::Config::new(
-        cfg.dns_resolve_method,
-        cfg.addr_family,
-        cfg.dns_timeout,
-    ))?)
-}
-
-fn create_geoip_lookup(cfg: &TrippyConfig) -> anyhow::Result<GeoIpLookup> {
-    if let Some(path) = cfg.geoip_mmdb_file.as_ref() {
-        GeoIpLookup::from_file(path)
-    } else {
-        Ok(GeoIpLookup::empty())
-    }
-}
-
-fn configure_logging(cfg: &TrippyConfig) -> Option<FlushGuard> {
-    if cfg.verbose {
-        let fmt_span = match cfg.log_span_events {
-            LogSpanEvents::Off => FmtSpan::NONE,
-            LogSpanEvents::Active => FmtSpan::ACTIVE,
-            LogSpanEvents::Full => FmtSpan::FULL,
-        };
-        match cfg.log_format {
-            LogFormat::Compact => {
-                tracing_subscriber::fmt()
-                    .with_span_events(fmt_span)
-                    .with_env_filter(&cfg.log_filter)
-                    .compact()
-                    .init();
-            }
-            LogFormat::Pretty => {
-                tracing_subscriber::fmt()
-                    .with_span_events(fmt_span)
-                    .with_env_filter(&cfg.log_filter)
-                    .pretty()
-                    .init();
-            }
-            LogFormat::Json => {
-                tracing_subscriber::fmt()
-                    .with_span_events(fmt_span)
-                    .with_env_filter(&cfg.log_filter)
-                    .json()
-                    .init();
-            }
-            LogFormat::Chrome => {
-                let (chrome_layer, guard) = ChromeLayerBuilder::new()
-                    .writer(std::io::stdout())
-                    .include_args(true)
-                    .build();
-                tracing_subscriber::registry().with(chrome_layer).init();
-                return Some(guard);
-            }
-        }
-    }
-    None
-}
-
-/// Resolve targets.
-fn resolve_targets(cfg: &TrippyConfig, resolver: &DnsResolver) -> anyhow::Result<Vec<TargetInfo>> {
-    cfg.targets
-        .iter()
-        .flat_map(|target| match resolver.lookup(target) {
-            Ok(addrs) => addrs
-                .into_iter()
-                .enumerate()
-                .take_while(|(i, _)| if cfg.dns_resolve_all { true } else { *i == 0 })
-                .map(|(i, addr)| {
-                    let hostname = if cfg.dns_resolve_all {
-                        format!("{} [{}]", target, i + 1)
-                    } else {
-                        target.to_string()
-                    };
-                    Ok(TargetInfo { hostname, addr })
-                })
-                .collect::<Vec<_>>()
-                .into_iter(),
-            Err(e) => {
-                vec![Err(anyhow!("failed to resolve target: {} ({})", target, e))].into_iter()
-            }
-        })
-        .collect::<anyhow::Result<Vec<_>>>()
-}
-
-/// Start all tracers.
-fn start_tracers(
-    cfg: &TrippyConfig,
-    addrs: &[TargetInfo],
-    pid: u16,
-) -> anyhow::Result<Vec<TraceInfo>> {
-    addrs
-        .iter()
-        .enumerate()
-        .map(|(i, TargetInfo { hostname, addr })| {
-            start_tracer(cfg, hostname, *addr, pid + i as u16)
-        })
-        .collect::<anyhow::Result<Vec<_>>>()
-}
-
-/// Start a tracer to a given target.
-fn start_tracer(
-    cfg: &TrippyConfig,
-    target_host: &str,
-    target_addr: IpAddr,
-    trace_identifier: u16,
-) -> Result<TraceInfo, Error> {
-    let source_addr = match cfg.source_addr {
-        None => SourceAddr::discover::<SocketImpl, PlatformImpl>(
-            target_addr,
-            cfg.port_direction,
-            cfg.interface.as_deref(),
-        )?,
-        Some(addr) => SourceAddr::validate::<SocketImpl>(addr)?,
-    };
-    let channel_config = make_channel_config(cfg, source_addr, target_addr);
-    let tracer_config = make_tracer_config(cfg, target_addr, trace_identifier)?;
-    let backend = Backend::new(
-        tracer_config,
-        channel_config,
-        cfg.tui_max_samples,
-        cfg.max_flows(),
-    );
-    let trace_data = backend.trace();
-    thread::Builder::new()
-        .name(format!("tracer-{}", tracer_config.trace_identifier.0))
-        .spawn(move || backend.start().expect("failed to run tracer backend"))?;
-    Ok(make_trace_info(
-        cfg,
-        trace_data,
-        source_addr,
-        target_host.to_string(),
-        target_addr,
-    ))
-}
-
-/// Run the TUI, stream or report.
-fn run_frontend(
-    args: &TrippyConfig,
-    resolver: DnsResolver,
-    geoip_lookup: GeoIpLookup,
-    traces: Vec<TraceInfo>,
-) -> anyhow::Result<()> {
-    match args.mode {
-        Mode::Tui => frontend::run_frontend(traces, make_tui_config(args), resolver, geoip_lookup)?,
-        Mode::Stream => report::stream::report(&traces[0], &resolver)?,
-        Mode::Csv => report::csv::report(&traces[0], args.report_cycles, &resolver)?,
-        Mode::Json => report::json::report(&traces[0], args.report_cycles, &resolver)?,
-        Mode::Pretty => report::table::report_pretty(&traces[0], args.report_cycles, &resolver)?,
-        Mode::Markdown => report::table::report_md(&traces[0], args.report_cycles, &resolver)?,
-        Mode::Dot => report::dot::report(&traces[0], args.report_cycles)?,
-        Mode::Flows => report::flows::report(&traces[0], args.report_cycles)?,
-        Mode::Silent => report::silent::report(&traces[0], args.report_cycles)?,
-    }
-    Ok(())
-}
-
-/// Make the tracer configuration.
-fn make_tracer_config(
-    args: &TrippyConfig,
-    target_addr: IpAddr,
-    trace_identifier: u16,
-) -> anyhow::Result<Config> {
-    Ok(Config::new(
-        target_addr,
-        args.protocol,
-        args.max_rounds,
-        trace_identifier,
-        args.first_ttl,
-        args.max_ttl,
-        args.grace_duration,
-        args.max_inflight,
-        args.initial_sequence,
-        args.multipath_strategy,
-        args.port_direction,
-        args.min_round_duration,
-        args.max_round_duration,
-    )?)
-}
-
-/// Make the tracer configuration.
-fn make_channel_config(
-    args: &TrippyConfig,
-    source_addr: IpAddr,
-    target_addr: IpAddr,
-) -> ChannelConfig {
-    ChannelConfig::new(
-        args.privilege_mode,
-        args.protocol,
-        source_addr,
-        target_addr,
-        args.packet_size,
-        args.payload_pattern,
-        args.initial_sequence,
-        args.tos,
-        args.icmp_extension_parse_mode,
-        args.read_timeout,
-        args.min_round_duration,
-    )
-}
-
-/// Make the per-trace information.
-fn make_trace_info(
-    args: &TrippyConfig,
-    trace_data: Arc<RwLock<Trace>>,
-    source_addr: IpAddr,
-    target: String,
-    target_addr: IpAddr,
-) -> TraceInfo {
-    TraceInfo::new(
-        trace_data,
-        source_addr,
-        target,
-        target_addr,
-        args.privilege_mode,
-        args.multipath_strategy,
-        args.port_direction,
-        args.protocol,
-        args.first_ttl,
-        args.max_ttl,
-        args.grace_duration,
-        args.min_round_duration,
-        args.max_round_duration,
-        args.max_inflight,
-        args.initial_sequence,
-        args.icmp_extension_parse_mode,
-        args.read_timeout,
-        args.packet_size,
-        args.payload_pattern,
-        args.interface.clone(),
-        args.geoip_mmdb_file.clone(),
-        args.dns_resolve_all,
-    )
-}
-
-/// Make the TUI configuration.
-fn make_tui_config(args: &TrippyConfig) -> TuiConfig {
-    TuiConfig::new(
-        args.tui_refresh_rate,
-        args.tui_privacy_max_ttl,
-        args.tui_preserve_screen,
-        args.tui_address_mode,
-        args.dns_lookup_as_info,
-        args.tui_as_mode,
-        args.tui_icmp_extension_mode,
-        args.tui_geoip_mode,
-        args.tui_max_addrs,
-        args.tui_max_samples,
-        args.max_flows(),
-        args.tui_theme,
-        &args.tui_bindings,
-        &args.tui_custom_columns,
-    )
-}
-
-/// Information about a tracing target.
-#[derive(Debug, Clone)]
-pub struct TargetInfo {
-    pub hostname: String,
-    pub addr: IpAddr,
-}
-
-/// Information about a `Trace` needed for the Tui, stream and reports.
-#[derive(Debug, Clone)]
-pub struct TraceInfo {
-    pub data: Arc<RwLock<Trace>>,
-    pub source_addr: IpAddr,
-    pub target_hostname: String,
-    pub target_addr: IpAddr,
-    pub privilege_mode: PrivilegeMode,
-    pub multipath_strategy: MultipathStrategy,
-    pub port_direction: PortDirection,
-    pub protocol: Protocol,
-    pub first_ttl: u8,
-    pub max_ttl: u8,
-    pub grace_duration: Duration,
-    pub min_round_duration: Duration,
-    pub max_round_duration: Duration,
-    pub max_inflight: u8,
-    pub initial_sequence: u16,
-    pub icmp_extensions: IcmpExtensionParseMode,
-    pub read_timeout: Duration,
-    pub packet_size: u16,
-    pub payload_pattern: u8,
-    pub interface: Option<String>,
-    pub geoip_mmdb_file: Option<String>,
-    pub dns_resolve_all: bool,
-}
-
-impl TraceInfo {
-    #[allow(clippy::too_many_arguments)]
-    #[must_use]
-    pub fn new(
-        data: Arc<RwLock<Trace>>,
-        source_addr: IpAddr,
-        target_hostname: String,
-        target_addr: IpAddr,
-        privilege_mode: PrivilegeMode,
-        multipath_strategy: MultipathStrategy,
-        port_direction: PortDirection,
-        protocol: Protocol,
-        first_ttl: u8,
-        max_ttl: u8,
-        grace_duration: Duration,
-        min_round_duration: Duration,
-        max_round_duration: Duration,
-        max_inflight: u8,
-        initial_sequence: u16,
-        icmp_extensions: IcmpExtensionParseMode,
-        read_timeout: Duration,
-        packet_size: u16,
-        payload_pattern: u8,
-        interface: Option<String>,
-        geoip_mmdb_file: Option<String>,
-        dns_resolve_all: bool,
-    ) -> Self {
-        Self {
-            data,
-            source_addr,
-            target_hostname,
-            target_addr,
-            privilege_mode,
-            multipath_strategy,
-            port_direction,
-            protocol,
-            first_ttl,
-            max_ttl,
-            grace_duration,
-            min_round_duration,
-            max_round_duration,
-            max_inflight,
-            initial_sequence,
-            icmp_extensions,
-            read_timeout,
-            packet_size,
-            payload_pattern,
-            interface,
-            geoip_mmdb_file,
-            dns_resolve_all,
-        }
-    }
-}
-
-fn tui_theme_items() -> String {
-    format!(
-        "TUI theme color items: {}",
-        TuiThemeItem::VARIANTS.join(", ")
-    )
-}
-
-fn tui_binding_commands() -> String {
-    format!(
-        "TUI binding commands: {}",
-        TuiCommandItem::VARIANTS.join(", ")
-    )
-}
-
-fn shell_completions(shell: Shell) -> anyhow::Result<String> {
-    let mut cmd = Args::command();
-    let name = cmd.get_name().to_string();
-    let mut buffer: Vec<u8> = vec![];
-    clap_complete::generate(shell, &mut cmd, name, &mut buffer);
-    Ok(String::from_utf8(buffer)?)
-}
-
-fn man_page() -> anyhow::Result<String> {
-    let cmd = Args::command();
-    let mut buffer: Vec<u8> = vec![];
-    clap_mangen::Man::new(cmd).render(&mut buffer)?;
-    Ok(String::from_utf8(buffer)?)
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use test_case::test_case;
-
-    #[test_case(&tui_theme_items(), "tui theme items match"; "tui theme items match")]
-    #[test_case(&tui_binding_commands(), "tui binding commands match"; "tui binding commands match")]
-    #[test_case(&shell_completions(Shell::Bash).unwrap(), "generate bash shell completions"; "generate bash shell completions")]
-    #[test_case(&shell_completions(Shell::Elvish).unwrap(), "generate elvish shell completions"; "generate elvish shell completions")]
-    #[test_case(&shell_completions(Shell::Fish).unwrap(), "generate fish shell completions"; "generate fish shell completions")]
-    #[test_case(&shell_completions(Shell::PowerShell).unwrap(), "generate powershell shell completions"; "generate powershell shell completions")]
-    #[test_case(&shell_completions(Shell::Zsh).unwrap(), "generate zsh shell completions"; "generate zsh shell completions")]
-    #[test_case(&man_page().unwrap(), "generate man page"; "generate man page")]
-    fn test_output(actual: &str, name: &str) {
-        insta(name, || {
-            insta::assert_snapshot!(remove_whitespace(actual.to_string()));
-        });
-    }
-
-    pub fn insta<F: FnOnce()>(name: &str, f: F) {
-        let mut settings = insta::Settings::new();
-        settings.set_snapshot_suffix(name.replace(' ', "_"));
-        settings.set_snapshot_path("../tests/resources/snapshots");
-        settings.set_omit_expression(true);
-        settings.bind(f);
-    }
-
-    pub fn remove_whitespace(mut s: String) -> String {
-        s.retain(|c| !c.is_whitespace());
-        s
-    }
-}
diff --git a/crates/trippy/src/print.rs b/crates/trippy/src/print.rs
new file mode 100644
index 0000000..da90fc7
--- /dev/null
+++ b/crates/trippy/src/print.rs
@@ -0,0 +1,80 @@
+use crate::config::{Args, TuiCommandItem, TuiThemeItem};
+use clap::CommandFactory;
+use clap_complete::Shell;
+use std::process;
+use strum::VariantNames;
+
+pub fn print_tui_theme_items() {
+    println!("{}", tui_theme_items());
+    process::exit(0);
+}
+
+pub fn print_tui_binding_commands() {
+    println!("{}", tui_binding_commands());
+    process::exit(0);
+}
+
+pub fn print_config_template() {
+    println!("{}", include_str!("../../../trippy-config-sample.toml"));
+    process::exit(0);
+}
+
+pub fn print_shell_completions(shell: Shell) -> anyhow::Result<()> {
+    println!("{}", shell_completions(shell)?);
+    process::exit(0);
+}
+
+pub fn print_man_page() -> anyhow::Result<()> {
+    println!("{}", man_page()?);
+    process::exit(0);
+}
+
+fn tui_theme_items() -> String {
+    format!(
+        "TUI theme color items: {}",
+        TuiThemeItem::VARIANTS.join(", ")
+    )
+}
+
+fn tui_binding_commands() -> String {
+    format!(
+        "TUI binding commands: {}",
+        TuiCommandItem::VARIANTS.join(", ")
+    )
+}
+
+fn shell_completions(shell: Shell) -> anyhow::Result<String> {
+    let mut cmd = Args::command();
+    let name = cmd.get_name().to_string();
+    let mut buffer: Vec<u8> = vec![];
+    clap_complete::generate(shell, &mut cmd, name, &mut buffer);
+    Ok(String::from_utf8(buffer)?)
+}
+
+fn man_page() -> anyhow::Result<String> {
+    let cmd = Args::command();
+    let mut buffer: Vec<u8> = vec![];
+    clap_mangen::Man::new(cmd).render(&mut buffer)?;
+    Ok(String::from_utf8(buffer)?)
+}
+
+#[cfg(test)]
+pub mod tests {
+    use super::*;
+    use crate::util::{insta, remove_whitespace};
+    use test_case::test_case;
+
+    #[test_case(&tui_theme_items(), "tui theme items match"; "tui theme items match")]
+    #[test_case(&tui_binding_commands(), "tui binding commands match"; "tui binding commands match")]
+    #[test_case(&shell_completions(Shell::Bash).unwrap(), "generate bash shell completions"; "generate bash shell completions")]
+    #[test_case(&shell_completions(Shell::Elvish).unwrap(), "generate elvish shell completions"; "generate elvish shell completions")]
+    #[test_case(&shell_completions(Shell::Fish).unwrap(), "generate fish shell completions"; "generate fish shell completions")]
+    #[test_case(&shell_completions(Shell::PowerShell).unwrap(), "generate powershell shell completions"; "generate powershell shell completions")]
+    #[test_case(&shell_completions(Shell::Zsh).unwrap(), "generate zsh shell completions"; "generate zsh shell completions")]
+    #[test_case(&man_page().unwrap(), "generate man page"; "generate man page")]
+    fn test_output(actual: &str, name: &str) {
+        insta(name, || {
+            insta::assert_snapshot!(remove_whitespace(actual.to_string()));
+        });
+    }
+}
diff --git a/crates/trippy/src/report/csv.rs b/crates/trippy/src/report/csv.rs
index ebdf5fe..9b2a836 100644
--- a/crates/trippy/src/report/csv.rs
+++ b/crates/trippy/src/report/csv.rs
@@ -1,6 +1,7 @@
+use crate::app::TraceInfo;
+use crate::backend;
 use crate::backend::trace::Trace;
 use crate::report::types::fixed_width;
-use crate::{backend, TraceInfo};
 use itertools::Itertools;
 use serde::Serialize;
 use std::net::IpAddr;
diff --git a/crates/trippy/src/report/dot.rs b/crates/trippy/src/report/dot.rs
index 12e6eb7..a36cf5d 100644
--- a/crates/trippy/src/report/dot.rs
+++ b/crates/trippy/src/report/dot.rs
@@ -1,5 +1,5 @@
+use crate::app::TraceInfo;
 use crate::backend::flows::FlowEntry;
-use crate::TraceInfo;
 use petgraph::dot::{Config, Dot};
 use petgraph::graphmap::DiGraphMap;
 use std::fmt::{Debug, Formatter};
diff --git a/crates/trippy/src/report/flows.rs b/crates/trippy/src/report/flows.rs
index 8302b56..f1c2aa7 100644
--- a/crates/trippy/src/report/flows.rs
+++ b/crates/trippy/src/report/flows.rs
@@ -1,4 +1,4 @@
-use crate::TraceInfo;
+use crate::app::TraceInfo;
 
 /// Run a trace and report all flows observed.
 pub fn report(info: &TraceInfo, report_cycles: usize) -> anyhow::Result<()> {
diff --git a/crates/trippy/src/report/json.rs b/crates/trippy/src/report/json.rs
index f0bb368..ffd4f20 100644
--- a/crates/trippy/src/report/json.rs
+++ b/crates/trippy/src/report/json.rs
@@ -1,6 +1,6 @@
+use crate::app::TraceInfo;
 use crate::backend::trace::Trace;
 use crate::report::types::{Hop, Host, Info, Report};
-use crate::TraceInfo;
 use trippy_dns::Resolver;
 
 /// Generate a json report of trace data.
diff --git a/crates/trippy/src/report/silent.rs b/crates/trippy/src/report/silent.rs
index f713be4..95f71ac 100644
--- a/crates/trippy/src/report/silent.rs
+++ b/crates/trippy/src/report/silent.rs
@@ -1,4 +1,4 @@
-use crate::TraceInfo;
+use crate::app::TraceInfo;
 
 /// Run a trace without generating any output.
 pub fn report(info: &TraceInfo, report_cycles: usize) -> anyhow::Result<()> {
diff --git a/crates/trippy/src/report/stream.rs b/crates/trippy/src/report/stream.rs
index 6bb51fa..99c9cbb 100644
--- a/crates/trippy/src/report/stream.rs
+++ b/crates/trippy/src/report/stream.rs
@@ -1,6 +1,6 @@
+use crate::app::TraceInfo;
 use crate::backend::trace::Trace;
 use crate::report::types::Hop;
-use crate::TraceInfo;
 use anyhow::anyhow;
 use std::thread::sleep;
 use trippy_dns::Resolver;
diff --git a/crates/trippy/src/report/table.rs b/crates/trippy/src/report/table.rs
index 0876a47..e363961 100644
--- a/crates/trippy/src/report/table.rs
+++ b/crates/trippy/src/report/table.rs
@@ -1,5 +1,5 @@
+use crate::app::TraceInfo;
 use crate::backend::trace::Trace;
-use crate::TraceInfo;
 use comfy_table::presets::{ASCII_MARKDOWN, UTF8_FULL};
 use comfy_table::{ContentArrangement, Table};
 use itertools::Itertools;
diff --git a/crates/trippy/src/util.rs b/crates/trippy/src/util.rs
new file mode 100644
index 0000000..e1831dd
--- /dev/null
+++ b/crates/trippy/src/util.rs
@@ -0,0 +1,14 @@
+#[cfg(test)]
+pub fn insta<F: FnOnce()>(name: &str, f: F) {
+    let mut settings = insta::Settings::new();
+    settings.set_snapshot_suffix(name.replace(' ', "_"));
+    settings.set_snapshot_path("../tests/resources/snapshots");
+    settings.set_omit_expression(true);
+    settings.bind(f);
+}
+
+#[cfg(test)]
+pub fn remove_whitespace(mut s: String) -> String {
+    s.retain(|c| !c.is_whitespace());
+    s
+}


Test patch:
diff --git a/crates/trippy/tests/resources/snapshots/trip__tests__output@generate_bash_shell_completions.snap b/crates/trippy/tests/resources/snapshots/trip__print__tests__output@generate_bash_shell_completions.snap
similarity index 99%
rename from crates/trippy/tests/resources/snapshots/trip__tests__output@generate_bash_shell_completions.snap
rename to crates/trippy/tests/resources/snapshots/trip__print__tests__output@generate_bash_shell_completions.snap
index 37a2bf8..25b5e4b 100644
--- a/crates/trippy/tests/resources/snapshots/trip__tests__output@generate_bash_shell_completions.snap
+++ b/crates/trippy/tests/resources/snapshots/trip__print__tests__output@generate_bash_shell_completions.snap
@@ -1,4 +1,4 @@
 ---
-source: crates/trippy/src/main.rs
+source: crates/trippy/src/print.rs
 ---
 _trip(){localicurprevoptscmdCOMPREPLY=()cur="${COMP_WORDS[COMP_CWORD]}"prev="${COMP_WORDS[COMP_CWORD-1]}"cmd=""opts=""foriin${COMP_WORDS[@]}docase"${cmd},${i}"in",$1")cmd="trip";;*);;esacdonecase"${cmd}"intrip)opts="-c-m-u-p-F-4-6-P-S-A-I-i-T-g-R-U-f-t-Q-e-r-y-z-a-M-s-C-G-v-h-V--config-file--mode--unprivileged--protocol--udp--tcp--icmp--addr-family--ipv4--ipv6--target-port--source-port--source-address--interface--min-round-duration--max-round-duration--grace-duration--initial-sequence--multipath-strategy--max-inflight--first-ttl--max-ttl--packet-size--payload-pattern--tos--icmp-extensions--read-timeout--dns-resolve-method--dns-resolve-all--dns-timeout--dns-lookup-as-info--tui-address-mode--tui-as-mode--tui-custom-columns--tui-icmp-extension-mode--tui-geoip-mode--tui-max-addrs--tui-max-samples--tui-max-flows--tui-preserve-screen--tui-refresh-rate--tui-privacy-max-ttl--tui-theme-colors--print-tui-theme-items--tui-key-bindings--print-tui-binding-commands--report-cycles--geoip-mmdb-file--generate--generate-man--print-config-template--log-format--log-filter--log-span-events--verbose--help--version[TARGETS]..."if[[${cur}==-*||${COMP_CWORD}-eq1]];thenCOMPREPLY=($(compgen-W"${opts}"--"${cur}"))return0ficase"${prev}"in--config-file)localoldifsif[-n"${IFS+x}"];thenoldifs="$IFS"fiIFS=$'\n'COMPREPLY=($(compgen-f"${cur}"))if[-n"${oldifs+x}"];thenIFS="$oldifs"fiif[["${BASH_VERSINFO[0]}"-ge4]];thencompopt-ofilenamesfireturn0;;-c)localoldifsif[-n"${IFS+x}"];thenoldifs="$IFS"fiIFS=$'\n'COMPREPLY=($(compgen-f"${cur}"))if[-n"${oldifs+x}"];thenIFS="$oldifs"fiif[["${BASH_VERSINFO[0]}"-ge4]];thencompopt-ofilenamesfireturn0;;--mode)COMPREPLY=($(compgen-W"tuistreamprettymarkdowncsvjsondotflowssilent"--"${cur}"))return0;;-m)COMPREPLY=($(compgen-W"tuistreamprettymarkdowncsvjsondotflowssilent"--"${cur}"))return0;;--protocol)COMPREPLY=($(compgen-W"icmpudptcp"--"${cur}"))return0;;-p)COMPREPLY=($(compgen-W"icmpudptcp"--"${cur}"))return0;;--addr-family)COMPREPLY=($(compgen-W"ipv4ipv6ipv6-then-ipv4ipv4-then-ipv6"--"${cur}"))return0;;-F)COMPREPLY=($(compgen-W"ipv4ipv6ipv6-then-ipv4ipv4-then-ipv6"--"${cur}"))return0;;--target-port)COMPREPLY=($(compgen-f"${cur}"))return0;;-P)COMPREPLY=($(compgen-f"${cur}"))return0;;--source-port)COMPREPLY=($(compgen-f"${cur}"))return0;;-S)COMPREPLY=($(compgen-f"${cur}"))return0;;--source-address)COMPREPLY=($(compgen-f"${cur}"))return0;;-A)COMPREPLY=($(compgen-f"${cur}"))return0;;--interface)COMPREPLY=($(compgen-f"${cur}"))return0;;-I)COMPREPLY=($(compgen-f"${cur}"))return0;;--min-round-duration)COMPREPLY=($(compgen-f"${cur}"))return0;;-i)COMPREPLY=($(compgen-f"${cur}"))return0;;--max-round-duration)COMPREPLY=($(compgen-f"${cur}"))return0;;-T)COMPREPLY=($(compgen-f"${cur}"))return0;;--grace-duration)COMPREPLY=($(compgen-f"${cur}"))return0;;-g)COMPREPLY=($(compgen-f"${cur}"))return0;;--initial-sequence)COMPREPLY=($(compgen-f"${cur}"))return0;;--multipath-strategy)COMPREPLY=($(compgen-W"classicparisdublin"--"${cur}"))return0;;-R)COMPREPLY=($(compgen-W"classicparisdublin"--"${cur}"))return0;;--max-inflight)COMPREPLY=($(compgen-f"${cur}"))return0;;-U)COMPREPLY=($(compgen-f"${cur}"))return0;;--first-ttl)COMPREPLY=($(compgen-f"${cur}"))return0;;-f)COMPREPLY=($(compgen-f"${cur}"))return0;;--max-ttl)COMPREPLY=($(compgen-f"${cur}"))return0;;-t)COMPREPLY=($(compgen-f"${cur}"))return0;;--packet-size)COMPREPLY=($(compgen-f"${cur}"))return0;;--payload-pattern)COMPREPLY=($(compgen-f"${cur}"))return0;;--tos)COMPREPLY=($(compgen-f"${cur}"))return0;;-Q)COMPREPLY=($(compgen-f"${cur}"))return0;;--read-timeout)COMPREPLY=($(compgen-f"${cur}"))return0;;--dns-resolve-method)COMPREPLY=($(compgen-W"systemresolvgooglecloudflare"--"${cur}"))return0;;-r)COMPREPLY=($(compgen-W"systemresolvgooglecloudflare"--"${cur}"))return0;;--dns-timeout)COMPREPLY=($(compgen-f"${cur}"))return0;;--tui-address-mode)COMPREPLY=($(compgen-W"iphostboth"--"${cur}"))return0;;-a)COMPREPLY=($(compgen-W"iphostboth"--"${cur}"))return0;;--tui-as-mode)COMPREPLY=($(compgen-W"asnprefixcountry-coderegistryallocatedname"--"${cur}"))return0;;--tui-custom-columns)COMPREPLY=($(compgen-f"${cur}"))return0;;--tui-icmp-extension-mode)COMPREPLY=($(compgen-W"offmplsfullall"--"${cur}"))return0;;--tui-geoip-mode)COMPREPLY=($(compgen-W"offshortlonglocation"--"${cur}"))return0;;--tui-max-addrs)COMPREPLY=($(compgen-f"${cur}"))return0;;-M)COMPREPLY=($(compgen-f"${cur}"))return0;;--tui-max-samples)COMPREPLY=($(compgen-f"${cur}"))return0;;-s)COMPREPLY=($(compgen-f"${cur}"))return0;;--tui-max-flows)COMPREPLY=($(compgen-f"${cur}"))return0;;--tui-refresh-rate)COMPREPLY=($(compgen-f"${cur}"))return0;;--tui-privacy-max-ttl)COMPREPLY=($(compgen-f"${cur}"))return0;;--tui-theme-colors)COMPREPLY=($(compgen-f"${cur}"))return0;;--tui-key-bindings)COMPREPLY=($(compgen-f"${cur}"))return0;;--report-cycles)COMPREPLY=($(compgen-f"${cur}"))return0;;-C)COMPREPLY=($(compgen-f"${cur}"))return0;;--geoip-mmdb-file)localoldifsif[-n"${IFS+x}"];thenoldifs="$IFS"fiIFS=$'\n'COMPREPLY=($(compgen-f"${cur}"))if[-n"${oldifs+x}"];thenIFS="$oldifs"fiif[["${BASH_VERSINFO[0]}"-ge4]];thencompopt-ofilenamesfireturn0;;-G)localoldifsif[-n"${IFS+x}"];thenoldifs="$IFS"fiIFS=$'\n'COMPREPLY=($(compgen-f"${cur}"))if[-n"${oldifs+x}"];thenIFS="$oldifs"fiif[["${BASH_VERSINFO[0]}"-ge4]];thencompopt-ofilenamesfireturn0;;--generate)COMPREPLY=($(compgen-W"bashelvishfishpowershellzsh"--"${cur}"))return0;;--log-format)COMPREPLY=($(compgen-W"compactprettyjsonchrome"--"${cur}"))return0;;--log-filter)COMPREPLY=($(compgen-f"${cur}"))return0;;--log-span-events)COMPREPLY=($(compgen-W"offactivefull"--"${cur}"))return0;;*)COMPREPLY=();;esacCOMPREPLY=($(compgen-W"${opts}"--"${cur}"))return0;;esac}if[["${BASH_VERSINFO[0]}"-eq4&&"${BASH_VERSINFO[1]}"-ge4||"${BASH_VERSINFO[0]}"-gt4]];thencomplete-F_trip-onosort-obashdefault-odefaulttripelsecomplete-F_trip-obashdefault-odefaulttripfi
diff --git a/crates/trippy/tests/resources/snapshots/trip__tests__output@generate_elvish_shell_completions.snap b/crates/trippy/tests/resources/snapshots/trip__print__tests__output@generate_elvish_shell_completions.snap
similarity index 99%
rename from crates/trippy/tests/resources/snapshots/trip__tests__output@generate_elvish_shell_completions.snap
rename to crates/trippy/tests/resources/snapshots/trip__print__tests__output@generate_elvish_shell_completions.snap
index ee1c58a..c653703 100644
--- a/crates/trippy/tests/resources/snapshots/trip__tests__output@generate_elvish_shell_completions.snap
+++ b/crates/trippy/tests/resources/snapshots/trip__print__tests__output@generate_elvish_shell_completions.snap
@@ -1,4 +1,4 @@
 ---
-source: crates/trippy/src/main.rs
+source: crates/trippy/src/print.rs
 ---
 usebuiltin;usestr;setedit:completion:arg-completer[trip]={|@words|fnspaces{|n|builtin:repeat$n''|str:join''}fncand{|textdesc|edit:complex-candidate$text&display=$text''(spaces(-14(wcswidth$text)))$desc}varcommand='trip'forword$words[1..-1]{if(str:has-prefix$word'-'){break}setcommand=$command';'$word}varcompletions=[&'trip'={cand-c'Configfile'cand--config-file'Configfile'cand-m'Outputmode[default:tui]'cand--mode'Outputmode[default:tui]'cand-p'Tracingprotocol[default:icmp]'cand--protocol'Tracingprotocol[default:icmp]'cand-F'Theaddressfamily[default:Ipv4thenIpv6]'cand--addr-family'Theaddressfamily[default:Ipv4thenIpv6]'cand-P'Thetargetport(TCP&UDPonly)[default:80]'cand--target-port'Thetargetport(TCP&UDPonly)[default:80]'cand-S'Thesourceport(TCP&UDPonly)[default:auto]'cand--source-port'Thesourceport(TCP&UDPonly)[default:auto]'cand-A'ThesourceIPaddress[default:auto]'cand--source-address'ThesourceIPaddress[default:auto]'cand-I'Thenetworkinterface[default:auto]'cand--interface'Thenetworkinterface[default:auto]'cand-i'Theminimumdurationofeveryround[default:1s]'cand--min-round-duration'Theminimumdurationofeveryround[default:1s]'cand-T'Themaximumdurationofeveryround[default:1s]'cand--max-round-duration'Themaximumdurationofeveryround[default:1s]'cand-g'TheperiodoftimetowaitforadditionalICMPresponsesafterthetargethasresponded[default:100ms]'cand--grace-duration'TheperiodoftimetowaitforadditionalICMPresponsesafterthetargethasresponded[default:100ms]'cand--initial-sequence'Theinitialsequencenumber[default:33000]'cand-R'TheEqual-costMulti-Pathroutingstrategy(UDPonly)[default:classic]'cand--multipath-strategy'TheEqual-costMulti-Pathroutingstrategy(UDPonly)[default:classic]'cand-U'Themaximumnumberofin-flightICMPechorequests[default:24]'cand--max-inflight'Themaximumnumberofin-flightICMPechorequests[default:24]'cand-f'TheTTLtostartfrom[default:1]'cand--first-ttl'TheTTLtostartfrom[default:1]'cand-t'ThemaximumnumberofTTLhops[default:64]'cand--max-ttl'ThemaximumnumberofTTLhops[default:64]'cand--packet-size'ThesizeofIPpackettosend(IPheader+ICMPheader+payload)[default:84]'cand--payload-pattern'TherepeatingpatterninthepayloadoftheICMPpacket[default:0]'cand-Q'TheTOS(i.e.DSCP+ECN)IPheadervalue(TCPandUDPonly)[default:0]'cand--tos'TheTOS(i.e.DSCP+ECN)IPheadervalue(TCPandUDPonly)[default:0]'cand--read-timeout'Thesocketreadtimeout[default:10ms]'cand-r'HowtoperformDNSqueries[default:system]'cand--dns-resolve-method'HowtoperformDNSqueries[default:system]'cand--dns-timeout'ThemaximumtimetowaittoperformDNSqueries[default:5s]'cand-a'Howtorenderaddresses[default:host]'cand--tui-address-mode'Howtorenderaddresses[default:host]'cand--tui-as-mode'HowtorenderASinformation[default:asn]'cand--tui-custom-columns'CustomcolumnstobedisplayedintheTUIhopstable[default:holsravbwdt]'cand--tui-icmp-extension-mode'HowtorenderICMPextensions[default:off]'cand--tui-geoip-mode'HowtorenderGeoIpinformation[default:short]'cand-M'Themaximumnumberofaddressestoshowperhop[default:auto]'cand--tui-max-addrs'Themaximumnumberofaddressestoshowperhop[default:auto]'cand-s'Themaximumnumberofsamplestorecordperhop[default:256]'cand--tui-max-samples'Themaximumnumberofsamplestorecordperhop[default:256]'cand--tui-max-flows'Themaximumnumberofflowstoshow[default:64]'cand--tui-refresh-rate'TheTuirefreshrate[default:100ms]'cand--tui-privacy-max-ttl'Themaximumttlofhopswhichwillbemaskedforprivacy[default:0]'cand--tui-theme-colors'TheTUIthemecolors[item=color,item=color,..]'cand--tui-key-bindings'TheTUIkeybindings[command=key,command=key,..]'cand-C'Thenumberofreportcyclestorun[default:10]'cand--report-cycles'Thenumberofreportcyclestorun[default:10]'cand-G'ThesupportedMaxMindorIPinfoGeoIpmmdbfile'cand--geoip-mmdb-file'ThesupportedMaxMindorIPinfoGeoIpmmdbfile'cand--generate'Generateshellcompletion'cand--log-format'Thedebuglogformat[default:pretty]'cand--log-filter'Thedebuglogfilter[default:trippy=debug]'cand--log-span-events'Thedebuglogformat[default:off]'cand-u'Tracewithoutrequiringelevatedprivilegesonsupportedplatforms[default:false]'cand--unprivileged'Tracewithoutrequiringelevatedprivilegesonsupportedplatforms[default:false]'cand--udp'TraceusingtheUDPprotocol'cand--tcp'TraceusingtheTCPprotocol'cand--icmp'TraceusingtheICMPprotocol'cand-4'UseIPv4only'cand--ipv4'UseIPv4only'cand-6'UseIPv6only'cand--ipv6'UseIPv6only'cand-e'ParseICMPextensions'cand--icmp-extensions'ParseICMPextensions'cand-y'TracetoallIPsresolvedfromDNSlookup[default:false]'cand--dns-resolve-all'TracetoallIPsresolvedfromDNSlookup[default:false]'cand-z'Lookupautonomoussystem(AS)informationduringDNSqueries[default:false]'cand--dns-lookup-as-info'Lookupautonomoussystem(AS)informationduringDNSqueries[default:false]'cand--tui-preserve-screen'Preservethescreenonexit[default:false]'cand--print-tui-theme-items'PrintallTUIthemeitemsandexit'cand--print-tui-binding-commands'PrintallTUIcommandsthatcanbeboundandexit'cand--generate-man'GenerateROFFmanpage'cand--print-config-template'Printatemplatetomlconfigfileandexit'cand-v'Enableverbosedebuglogging'cand--verbose'Enableverbosedebuglogging'cand-h'Printhelp(seemorewith''--help'')'cand--help'Printhelp(seemorewith''--help'')'cand-V'Printversion'cand--version'Printversion'}]$completions[$command]}
diff --git a/crates/trippy/tests/resources/snapshots/trip__tests__output@generate_fish_shell_completions.snap b/crates/trippy/tests/resources/snapshots/trip__print__tests__output@generate_fish_shell_completions.snap
similarity index 99%
rename from crates/trippy/tests/resources/snapshots/trip__tests__output@generate_fish_shell_completions.snap
rename to crates/trippy/tests/resources/snapshots/trip__print__tests__output@generate_fish_shell_completions.snap
index a90be83..e70b7af 100644
--- a/crates/trippy/tests/resources/snapshots/trip__tests__output@generate_fish_shell_completions.snap
+++ b/crates/trippy/tests/resources/snapshots/trip__print__tests__output@generate_fish_shell_completions.snap
@@ -1,4 +1,4 @@
 ---
-source: crates/trippy/src/main.rs
+source: crates/trippy/src/print.rs
 ---
 complete-ctrip-sc-lconfig-file-d'Configfile'-r-Fcomplete-ctrip-sm-lmode-d'Outputmode[default:tui]'-r-f-a"{tui'DisplayinteractiveTUI',stream'Displayacontinuousstreamoftracingdata',pretty'GenerateaprettytexttablereportforNcycles',markdown'GenerateaMarkdowntexttablereportforNcycles',csv'GenerateaCSVreportforNcycles',json'GenerateaJSONreportforNcycles',dot'GenerateaGraphvizDOTfileforNcycles',flows'DisplayallflowsforNcycles',silent'DonotgenerateanytracingoutputforNcycles'}"complete-ctrip-sp-lprotocol-d'Tracingprotocol[default:icmp]'-r-f-a"{icmp'InternetControlMessageProtocol',udp'UserDatagramProtocol',tcp'TransmissionControlProtocol'}"complete-ctrip-sF-laddr-family-d'Theaddressfamily[default:Ipv4thenIpv6]'-r-f-a"{ipv4'Ipv4only',ipv6'Ipv6only',ipv6-then-ipv4'Ipv6withafallbacktoIpv4',ipv4-then-ipv6'Ipv4withafallbacktoIpv6'}"complete-ctrip-sP-ltarget-port-d'Thetargetport(TCP&UDPonly)[default:80]'-rcomplete-ctrip-sS-lsource-port-d'Thesourceport(TCP&UDPonly)[default:auto]'-rcomplete-ctrip-sA-lsource-address-d'ThesourceIPaddress[default:auto]'-rcomplete-ctrip-sI-linterface-d'Thenetworkinterface[default:auto]'-rcomplete-ctrip-si-lmin-round-duration-d'Theminimumdurationofeveryround[default:1s]'-rcomplete-ctrip-sT-lmax-round-duration-d'Themaximumdurationofeveryround[default:1s]'-rcomplete-ctrip-sg-lgrace-duration-d'TheperiodoftimetowaitforadditionalICMPresponsesafterthetargethasresponded[default:100ms]'-rcomplete-ctrip-linitial-sequence-d'Theinitialsequencenumber[default:33000]'-rcomplete-ctrip-sR-lmultipath-strategy-d'TheEqual-costMulti-Pathroutingstrategy(UDPonly)[default:classic]'-r-f-a"{classic'Thesrcordestportisusedtostorethesequencenumber',paris'TheUDP`checksum`fieldisusedtostorethesequencenumber',dublin'TheIP`identifier`fieldisusedtostorethesequencenumber'}"complete-ctrip-sU-lmax-inflight-d'Themaximumnumberofin-flightICMPechorequests[default:24]'-rcomplete-ctrip-sf-lfirst-ttl-d'TheTTLtostartfrom[default:1]'-rcomplete-ctrip-st-lmax-ttl-d'ThemaximumnumberofTTLhops[default:64]'-rcomplete-ctrip-lpacket-size-d'ThesizeofIPpackettosend(IPheader+ICMPheader+payload)[default:84]'-rcomplete-ctrip-lpayload-pattern-d'TherepeatingpatterninthepayloadoftheICMPpacket[default:0]'-rcomplete-ctrip-sQ-ltos-d'TheTOS(i.e.DSCP+ECN)IPheadervalue(TCPandUDPonly)[default:0]'-rcomplete-ctrip-lread-timeout-d'Thesocketreadtimeout[default:10ms]'-rcomplete-ctrip-sr-ldns-resolve-method-d'HowtoperformDNSqueries[default:system]'-r-f-a"{system'ResolveusingtheOSresolver',resolv'Resolveusingthe`/etc/resolv.conf`DNSconfiguration',google'ResolveusingtheGoogle`8.8.8.8`DNSservice',cloudflare'ResolveusingtheCloudflare`1.1.1.1`DNSservice'}"complete-ctrip-ldns-timeout-d'ThemaximumtimetowaittoperformDNSqueries[default:5s]'-rcomplete-ctrip-sa-ltui-address-mode-d'Howtorenderaddresses[default:host]'-r-f-a"{ip'ShowIPaddressonly',host'Showreverse-lookupDNShostnameonly',both'ShowbothIPaddressandreverse-lookupDNShostname'}"complete-ctrip-ltui-as-mode-d'HowtorenderASinformation[default:asn]'-r-f-a"{asn'ShowtheASN',prefix'DisplaytheASprefix',country-code'Displaythecountrycode',registry'Displaytheregistryname',allocated'Displaytheallocateddate',name'DisplaytheASname'}"complete-ctrip-ltui-custom-columns-d'CustomcolumnstobedisplayedintheTUIhopstable[default:holsravbwdt]'-rcomplete-ctrip-ltui-icmp-extension-mode-d'HowtorenderICMPextensions[default:off]'-r-f-a"{off'Donotshow`icmp`extensions',mpls'ShowMPLSlabel(s)only',full'Showfull`icmp`extensiondataforallknownextensions',all'Showfull`icmp`extensiondataforallclasses'}"complete-ctrip-ltui-geoip-mode-d'HowtorenderGeoIpinformation[default:short]'-r-f-a"{off'DonotdisplayGeoIpdata',short'Showshortformat',long'Showlongformat',location'ShowlatitudeandLongitudeformat'}"complete-ctrip-sM-ltui-max-addrs-d'Themaximumnumberofaddressestoshowperhop[default:auto]'-rcomplete-ctrip-ss-ltui-max-samples-d'Themaximumnumberofsamplestorecordperhop[default:256]'-rcomplete-ctrip-ltui-max-flows-d'Themaximumnumberofflowstoshow[default:64]'-rcomplete-ctrip-ltui-refresh-rate-d'TheTuirefreshrate[default:100ms]'-rcomplete-ctrip-ltui-privacy-max-ttl-d'Themaximumttlofhopswhichwillbemaskedforprivacy[default:0]'-rcomplete-ctrip-ltui-theme-colors-d'TheTUIthemecolors[item=color,item=color,..]'-rcomplete-ctrip-ltui-key-bindings-d'TheTUIkeybindings[command=key,command=key,..]'-rcomplete-ctrip-sC-lreport-cycles-d'Thenumberofreportcyclestorun[default:10]'-rcomplete-ctrip-sG-lgeoip-mmdb-file-d'ThesupportedMaxMindorIPinfoGeoIpmmdbfile'-r-Fcomplete-ctrip-lgenerate-d'Generateshellcompletion'-r-f-a"{bash'',elvish'',fish'',powershell'',zsh''}"complete-ctrip-llog-format-d'Thedebuglogformat[default:pretty]'-r-f-a"{compact'Displaylogdatainacompactformat',pretty'Displaylogdatainaprettyformat',json'Displaylogdatainajsonformat',chrome'DisplaylogdatainChrometraceformat'}"complete-ctrip-llog-filter-d'Thedebuglogfilter[default:trippy=debug]'-rcomplete-ctrip-llog-span-events-d'Thedebuglogformat[default:off]'-r-f-a"{off'Donotdisplayeventspans',active'Displayenterandexiteventspans',full'Displayalleventspans'}"complete-ctrip-su-lunprivileged-d'Tracewithoutrequiringelevatedprivilegesonsupportedplatforms[default:false]'complete-ctrip-ludp-d'TraceusingtheUDPprotocol'complete-ctrip-ltcp-d'TraceusingtheTCPprotocol'complete-ctrip-licmp-d'TraceusingtheICMPprotocol'complete-ctrip-s4-lipv4-d'UseIPv4only'complete-ctrip-s6-lipv6-d'UseIPv6only'complete-ctrip-se-licmp-extensions-d'ParseICMPextensions'complete-ctrip-sy-ldns-resolve-all-d'TracetoallIPsresolvedfromDNSlookup[default:false]'complete-ctrip-sz-ldns-lookup-as-info-d'Lookupautonomoussystem(AS)informationduringDNSqueries[default:false]'complete-ctrip-ltui-preserve-screen-d'Preservethescreenonexit[default:false]'complete-ctrip-lprint-tui-theme-items-d'PrintallTUIthemeitemsandexit'complete-ctrip-lprint-tui-binding-commands-d'PrintallTUIcommandsthatcanbeboundandexit'complete-ctrip-lgenerate-man-d'GenerateROFFmanpage'complete-ctrip-lprint-config-template-d'Printatemplatetomlconfigfileandexit'complete-ctrip-sv-lverbose-d'Enableverbosedebuglogging'complete-ctrip-sh-lhelp-d'Printhelp(seemorewith\'--help\')'complete-ctrip-sV-lversion-d'Printversion'
diff --git a/crates/trippy/tests/resources/snapshots/trip__tests__output@generate_man_page.snap b/crates/trippy/tests/resources/snapshots/trip__print__tests__output@generate_man_page.snap
similarity index 99%
rename from crates/trippy/tests/resources/snapshots/trip__tests__output@generate_man_page.snap
rename to crates/trippy/tests/resources/snapshots/trip__print__tests__output@generate_man_page.snap
index e991386..e414f90 100644
--- a/crates/trippy/tests/resources/snapshots/trip__tests__output@generate_man_page.snap
+++ b/crates/trippy/tests/resources/snapshots/trip__print__tests__output@generate_man_page.snap
@@ -1,4 +1,4 @@
 ---
-source: crates/trippy/src/main.rs
+source: crates/trippy/src/print.rs
 ---
 .ie\n(.g.dsAq\(aq.el.dsAq'.THtrip1"trip0.11.0-dev".SHNAMEtrip\-Anetworkdiagnostictool.SHSYNOPSIS\fBtrip\fR[\fB\-c\fR|\fB\-\-config\-file\fR][\fB\-m\fR|\fB\-\-mode\fR][\fB\-u\fR|\fB\-\-unprivileged\fR][\fB\-p\fR|\fB\-\-protocol\fR][\fB\-\-udp\fR][\fB\-\-tcp\fR][\fB\-\-icmp\fR][\fB\-F\fR|\fB\-\-addr\-family\fR][\fB\-4\fR|\fB\-\-ipv4\fR][\fB\-6\fR|\fB\-\-ipv6\fR][\fB\-P\fR|\fB\-\-target\-port\fR][\fB\-S\fR|\fB\-\-source\-port\fR][\fB\-A\fR|\fB\-\-source\-address\fR][\fB\-I\fR|\fB\-\-interface\fR][\fB\-i\fR|\fB\-\-min\-round\-duration\fR][\fB\-T\fR|\fB\-\-max\-round\-duration\fR][\fB\-g\fR|\fB\-\-grace\-duration\fR][\fB\-\-initial\-sequence\fR][\fB\-R\fR|\fB\-\-multipath\-strategy\fR][\fB\-U\fR|\fB\-\-max\-inflight\fR][\fB\-f\fR|\fB\-\-first\-ttl\fR][\fB\-t\fR|\fB\-\-max\-ttl\fR][\fB\-\-packet\-size\fR][\fB\-\-payload\-pattern\fR][\fB\-Q\fR|\fB\-\-tos\fR][\fB\-e\fR|\fB\-\-icmp\-extensions\fR][\fB\-\-read\-timeout\fR][\fB\-r\fR|\fB\-\-dns\-resolve\-method\fR][\fB\-y\fR|\fB\-\-dns\-resolve\-all\fR][\fB\-\-dns\-timeout\fR][\fB\-z\fR|\fB\-\-dns\-lookup\-as\-info\fR][\fB\-a\fR|\fB\-\-tui\-address\-mode\fR][\fB\-\-tui\-as\-mode\fR][\fB\-\-tui\-custom\-columns\fR][\fB\-\-tui\-icmp\-extension\-mode\fR][\fB\-\-tui\-geoip\-mode\fR][\fB\-M\fR|\fB\-\-tui\-max\-addrs\fR][\fB\-s\fR|\fB\-\-tui\-max\-samples\fR][\fB\-\-tui\-max\-flows\fR][\fB\-\-tui\-preserve\-screen\fR][\fB\-\-tui\-refresh\-rate\fR][\fB\-\-tui\-privacy\-max\-ttl\fR][\fB\-\-tui\-theme\-colors\fR][\fB\-\-print\-tui\-theme\-items\fR][\fB\-\-tui\-key\-bindings\fR][\fB\-\-print\-tui\-binding\-commands\fR][\fB\-C\fR|\fB\-\-report\-cycles\fR][\fB\-G\fR|\fB\-\-geoip\-mmdb\-file\fR][\fB\-\-generate\fR][\fB\-\-generate\-man\fR][\fB\-\-print\-config\-template\fR][\fB\-\-log\-format\fR][\fB\-\-log\-filter\fR][\fB\-\-log\-span\-events\fR][\fB\-v\fR|\fB\-\-verbose\fR][\fB\-h\fR|\fB\-\-help\fR][\fB\-V\fR|\fB\-\-version\fR][\fITARGETS\fR].SHDESCRIPTIONAnetworkdiagnostictool.SHOPTIONS.TP\fB\-c\fR,\fB\-\-config\-file\fR=\fICONFIG_FILE\fRConfigfile.TP\fB\-m\fR,\fB\-\-mode\fR=\fIMODE\fROutputmode[default:tui].br.br\fIPossiblevalues:\fR.RS14.IP\(bu2tui:DisplayinteractiveTUI.IP\(bu2stream:Displayacontinuousstreamoftracingdata.IP\(bu2pretty:GenerateaprettytexttablereportforNcycles.IP\(bu2markdown:GenerateaMarkdowntexttablereportforNcycles.IP\(bu2csv:GenerateaCSVreportforNcycles.IP\(bu2json:GenerateaJSONreportforNcycles.IP\(bu2dot:GenerateaGraphvizDOTfileforNcycles.IP\(bu2flows:DisplayallflowsforNcycles.IP\(bu2silent:DonotgenerateanytracingoutputforNcycles.RE.TP\fB\-u\fR,\fB\-\-unprivileged\fRTracewithoutrequiringelevatedprivilegesonsupportedplatforms[default:false].TP\fB\-p\fR,\fB\-\-protocol\fR=\fIPROTOCOL\fRTracingprotocol[default:icmp].br.br\fIPossiblevalues:\fR.RS14.IP\(bu2icmp:InternetControlMessageProtocol.IP\(bu2udp:UserDatagramProtocol.IP\(bu2tcp:TransmissionControlProtocol.RE.TP\fB\-\-udp\fRTraceusingtheUDPprotocol.TP\fB\-\-tcp\fRTraceusingtheTCPprotocol.TP\fB\-\-icmp\fRTraceusingtheICMPprotocol.TP\fB\-F\fR,\fB\-\-addr\-family\fR=\fIADDR_FAMILY\fRTheaddressfamily[default:Ipv4thenIpv6].br.br\fIPossiblevalues:\fR.RS14.IP\(bu2ipv4:Ipv4only.IP\(bu2ipv6:Ipv6only.IP\(bu2ipv6\-then\-ipv4:Ipv6withafallbacktoIpv4.IP\(bu2ipv4\-then\-ipv6:Ipv4withafallbacktoIpv6.RE.TP\fB\-4\fR,\fB\-\-ipv4\fRUseIPv4only.TP\fB\-6\fR,\fB\-\-ipv6\fRUseIPv6only.TP\fB\-P\fR,\fB\-\-target\-port\fR=\fITARGET_PORT\fRThetargetport(TCP&UDPonly)[default:80].TP\fB\-S\fR,\fB\-\-source\-port\fR=\fISOURCE_PORT\fRThesourceport(TCP&UDPonly)[default:auto].TP\fB\-A\fR,\fB\-\-source\-address\fR=\fISOURCE_ADDRESS\fRThesourceIPaddress[default:auto].TP\fB\-I\fR,\fB\-\-interface\fR=\fIINTERFACE\fRThenetworkinterface[default:auto].TP\fB\-i\fR,\fB\-\-min\-round\-duration\fR=\fIMIN_ROUND_DURATION\fRTheminimumdurationofeveryround[default:1s].TP\fB\-T\fR,\fB\-\-max\-round\-duration\fR=\fIMAX_ROUND_DURATION\fRThemaximumdurationofeveryround[default:1s].TP\fB\-g\fR,\fB\-\-grace\-duration\fR=\fIGRACE_DURATION\fRTheperiodoftimetowaitforadditionalICMPresponsesafterthetargethasresponded[default:100ms].TP\fB\-\-initial\-sequence\fR=\fIINITIAL_SEQUENCE\fRTheinitialsequencenumber[default:33000].TP\fB\-R\fR,\fB\-\-multipath\-strategy\fR=\fIMULTIPATH_STRATEGY\fRTheEqual\-costMulti\-Pathroutingstrategy(UDPonly)[default:classic].br.br\fIPossiblevalues:\fR.RS14.IP\(bu2classic:Thesrcordestportisusedtostorethesequencenumber.IP\(bu2paris:TheUDP`checksum`fieldisusedtostorethesequencenumber.IP\(bu2dublin:TheIP`identifier`fieldisusedtostorethesequencenumber.RE.TP\fB\-U\fR,\fB\-\-max\-inflight\fR=\fIMAX_INFLIGHT\fRThemaximumnumberofin\-flightICMPechorequests[default:24].TP\fB\-f\fR,\fB\-\-first\-ttl\fR=\fIFIRST_TTL\fRTheTTLtostartfrom[default:1].TP\fB\-t\fR,\fB\-\-max\-ttl\fR=\fIMAX_TTL\fRThemaximumnumberofTTLhops[default:64].TP\fB\-\-packet\-size\fR=\fIPACKET_SIZE\fRThesizeofIPpackettosend(IPheader+ICMPheader+payload)[default:84].TP\fB\-\-payload\-pattern\fR=\fIPAYLOAD_PATTERN\fRTherepeatingpatterninthepayloadoftheICMPpacket[default:0].TP\fB\-Q\fR,\fB\-\-tos\fR=\fITOS\fRTheTOS(i.e.DSCP+ECN)IPheadervalue(TCPandUDPonly)[default:0].TP\fB\-e\fR,\fB\-\-icmp\-extensions\fRParseICMPextensions.TP\fB\-\-read\-timeout\fR=\fIREAD_TIMEOUT\fRThesocketreadtimeout[default:10ms].TP\fB\-r\fR,\fB\-\-dns\-resolve\-method\fR=\fIDNS_RESOLVE_METHOD\fRHowtoperformDNSqueries[default:system].br.br\fIPossiblevalues:\fR.RS14.IP\(bu2system:ResolveusingtheOSresolver.IP\(bu2resolv:Resolveusingthe`/etc/resolv.conf`DNSconfiguration.IP\(bu2google:ResolveusingtheGoogle`8.8.8.8`DNSservice.IP\(bu2cloudflare:ResolveusingtheCloudflare`1.1.1.1`DNSservice.RE.TP\fB\-y\fR,\fB\-\-dns\-resolve\-all\fRTracetoallIPsresolvedfromDNSlookup[default:false].TP\fB\-\-dns\-timeout\fR=\fIDNS_TIMEOUT\fRThemaximumtimetowaittoperformDNSqueries[default:5s].TP\fB\-z\fR,\fB\-\-dns\-lookup\-as\-info\fRLookupautonomoussystem(AS)informationduringDNSqueries[default:false].TP\fB\-a\fR,\fB\-\-tui\-address\-mode\fR=\fITUI_ADDRESS_MODE\fRHowtorenderaddresses[default:host].br.br\fIPossiblevalues:\fR.RS14.IP\(bu2ip:ShowIPaddressonly.IP\(bu2host:Showreverse\-lookupDNShostnameonly.IP\(bu2both:ShowbothIPaddressandreverse\-lookupDNShostname.RE.TP\fB\-\-tui\-as\-mode\fR=\fITUI_AS_MODE\fRHowtorenderASinformation[default:asn].br.br\fIPossiblevalues:\fR.RS14.IP\(bu2asn:ShowtheASN.IP\(bu2prefix:DisplaytheASprefix.IP\(bu2country\-code:Displaythecountrycode.IP\(bu2registry:Displaytheregistryname.IP\(bu2allocated:Displaytheallocateddate.IP\(bu2name:DisplaytheASname.RE.TP\fB\-\-tui\-custom\-columns\fR=\fITUI_CUSTOM_COLUMNS\fRCustomcolumnstobedisplayedintheTUIhopstable[default:holsravbwdt].TP\fB\-\-tui\-icmp\-extension\-mode\fR=\fITUI_ICMP_EXTENSION_MODE\fRHowtorenderICMPextensions[default:off].br.br\fIPossiblevalues:\fR.RS14.IP\(bu2off:Donotshow`icmp`extensions.IP\(bu2mpls:ShowMPLSlabel(s)only.IP\(bu2full:Showfull`icmp`extensiondataforallknownextensions.IP\(bu2all:Showfull`icmp`extensiondataforallclasses.RE.TP\fB\-\-tui\-geoip\-mode\fR=\fITUI_GEOIP_MODE\fRHowtorenderGeoIpinformation[default:short].br.br\fIPossiblevalues:\fR.RS14.IP\(bu2off:DonotdisplayGeoIpdata.IP\(bu2short:Showshortformat.IP\(bu2long:Showlongformat.IP\(bu2location:ShowlatitudeandLongitudeformat.RE.TP\fB\-M\fR,\fB\-\-tui\-max\-addrs\fR=\fITUI_MAX_ADDRS\fRThemaximumnumberofaddressestoshowperhop[default:auto].TP\fB\-s\fR,\fB\-\-tui\-max\-samples\fR=\fITUI_MAX_SAMPLES\fRThemaximumnumberofsamplestorecordperhop[default:256].TP\fB\-\-tui\-max\-flows\fR=\fITUI_MAX_FLOWS\fRThemaximumnumberofflowstoshow[default:64].TP\fB\-\-tui\-preserve\-screen\fRPreservethescreenonexit[default:false].TP\fB\-\-tui\-refresh\-rate\fR=\fITUI_REFRESH_RATE\fRTheTuirefreshrate[default:100ms].TP\fB\-\-tui\-privacy\-max\-ttl\fR=\fITUI_PRIVACY_MAX_TTL\fRThemaximumttlofhopswhichwillbemaskedforprivacy[default:0].TP\fB\-\-tui\-theme\-colors\fR=\fITUI_THEME_COLORS\fRTheTUIthemecolors[item=color,item=color,..].TP\fB\-\-print\-tui\-theme\-items\fRPrintallTUIthemeitemsandexit.TP\fB\-\-tui\-key\-bindings\fR=\fITUI_KEY_BINDINGS\fRTheTUIkeybindings[command=key,command=key,..].TP\fB\-\-print\-tui\-binding\-commands\fRPrintallTUIcommandsthatcanbeboundandexit.TP\fB\-C\fR,\fB\-\-report\-cycles\fR=\fIREPORT_CYCLES\fRThenumberofreportcyclestorun[default:10].TP\fB\-G\fR,\fB\-\-geoip\-mmdb\-file\fR=\fIGEOIP_MMDB_FILE\fRThesupportedMaxMindorIPinfoGeoIpmmdbfile.TP\fB\-\-generate\fR=\fIGENERATE\fRGenerateshellcompletion.br.br[\fIpossiblevalues:\fRbash,elvish,fish,powershell,zsh].TP\fB\-\-generate\-man\fRGenerateROFFmanpage.TP\fB\-\-print\-config\-template\fRPrintatemplatetomlconfigfileandexit.TP\fB\-\-log\-format\fR=\fILOG_FORMAT\fRThedebuglogformat[default:pretty].br.br\fIPossiblevalues:\fR.RS14.IP\(bu2compact:Displaylogdatainacompactformat.IP\(bu2pretty:Displaylogdatainaprettyformat.IP\(bu2json:Displaylogdatainajsonformat.IP\(bu2chrome:DisplaylogdatainChrometraceformat.RE.TP\fB\-\-log\-filter\fR=\fILOG_FILTER\fRThedebuglogfilter[default:trippy=debug].TP\fB\-\-log\-span\-events\fR=\fILOG_SPAN_EVENTS\fRThedebuglogformat[default:off].br.br\fIPossiblevalues:\fR.RS14.IP\(bu2off:Donotdisplayeventspans.IP\(bu2active:Displayenterandexiteventspans.IP\(bu2full:Displayalleventspans.RE.TP\fB\-v\fR,\fB\-\-verbose\fREnableverbosedebuglogging.TP\fB\-h\fR,\fB\-\-help\fRPrinthelp(seeasummarywith\*(Aq\-h\*(Aq).TP\fB\-V\fR,\fB\-\-version\fRPrintversion.TP[\fITARGETS\fR]AspacedelimitedlistofhostnamesandIPstotrace.SHVERSIONv0.11.0\-dev.SHAUTHORSFujiApple<fujiapple852@gmail.com>
diff --git a/crates/trippy/tests/resources/snapshots/trip__tests__output@generate_powershell_shell_completions.snap b/crates/trippy/tests/resources/snapshots/trip__print__tests__output@generate_powershell_shell_completions.snap
similarity index 99%
rename from crates/trippy/tests/resources/snapshots/trip__tests__output@generate_powershell_shell_completions.snap
rename to crates/trippy/tests/resources/snapshots/trip__print__tests__output@generate_powershell_shell_completions.snap
index 612daa3..4e2d0ac 100644
--- a/crates/trippy/tests/resources/snapshots/trip__tests__output@generate_powershell_shell_completions.snap
+++ b/crates/trippy/tests/resources/snapshots/trip__print__tests__output@generate_powershell_shell_completions.snap
@@ -1,4 +1,4 @@
 ---
-source: crates/trippy/src/main.rs
+source: crates/trippy/src/print.rs
 ---
 usingnamespaceSystem.Management.AutomationusingnamespaceSystem.Management.Automation.LanguageRegister-ArgumentCompleter-Native-CommandName'trip'-ScriptBlock{param($wordToComplete,$commandAst,$cursorPosition)$commandElements=$commandAst.CommandElements$command=@('trip'for($i=1;$i-lt$commandElements.Count;$i++){$element=$commandElements[$i]if($element-isnot[StringConstantExpressionAst]-or$element.StringConstantType-ne[StringConstantType]::BareWord-or$element.Value.StartsWith('-')-or$element.Value-eq$wordToComplete){break}$element.Value})-join';'$completions=@(switch($command){'trip'{[CompletionResult]::new('-c','c',[CompletionResultType]::ParameterName,'Configfile')[CompletionResult]::new('--config-file','config-file',[CompletionResultType]::ParameterName,'Configfile')[CompletionResult]::new('-m','m',[CompletionResultType]::ParameterName,'Outputmode[default:tui]')[CompletionResult]::new('--mode','mode',[CompletionResultType]::ParameterName,'Outputmode[default:tui]')[CompletionResult]::new('-p','p',[CompletionResultType]::ParameterName,'Tracingprotocol[default:icmp]')[CompletionResult]::new('--protocol','protocol',[CompletionResultType]::ParameterName,'Tracingprotocol[default:icmp]')[CompletionResult]::new('-F','F',[CompletionResultType]::ParameterName,'Theaddressfamily[default:Ipv4thenIpv6]')[CompletionResult]::new('--addr-family','addr-family',[CompletionResultType]::ParameterName,'Theaddressfamily[default:Ipv4thenIpv6]')[CompletionResult]::new('-P','P',[CompletionResultType]::ParameterName,'Thetargetport(TCP&UDPonly)[default:80]')[CompletionResult]::new('--target-port','target-port',[CompletionResultType]::ParameterName,'Thetargetport(TCP&UDPonly)[default:80]')[CompletionResult]::new('-S','S',[CompletionResultType]::ParameterName,'Thesourceport(TCP&UDPonly)[default:auto]')[CompletionResult]::new('--source-port','source-port',[CompletionResultType]::ParameterName,'Thesourceport(TCP&UDPonly)[default:auto]')[CompletionResult]::new('-A','A',[CompletionResultType]::ParameterName,'ThesourceIPaddress[default:auto]')[CompletionResult]::new('--source-address','source-address',[CompletionResultType]::ParameterName,'ThesourceIPaddress[default:auto]')[CompletionResult]::new('-I','I',[CompletionResultType]::ParameterName,'Thenetworkinterface[default:auto]')[CompletionResult]::new('--interface','interface',[CompletionResultType]::ParameterName,'Thenetworkinterface[default:auto]')[CompletionResult]::new('-i','i',[CompletionResultType]::ParameterName,'Theminimumdurationofeveryround[default:1s]')[CompletionResult]::new('--min-round-duration','min-round-duration',[CompletionResultType]::ParameterName,'Theminimumdurationofeveryround[default:1s]')[CompletionResult]::new('-T','T',[CompletionResultType]::ParameterName,'Themaximumdurationofeveryround[default:1s]')[CompletionResult]::new('--max-round-duration','max-round-duration',[CompletionResultType]::ParameterName,'Themaximumdurationofeveryround[default:1s]')[CompletionResult]::new('-g','g',[CompletionResultType]::ParameterName,'TheperiodoftimetowaitforadditionalICMPresponsesafterthetargethasresponded[default:100ms]')[CompletionResult]::new('--grace-duration','grace-duration',[CompletionResultType]::ParameterName,'TheperiodoftimetowaitforadditionalICMPresponsesafterthetargethasresponded[default:100ms]')[CompletionResult]::new('--initial-sequence','initial-sequence',[CompletionResultType]::ParameterName,'Theinitialsequencenumber[default:33000]')[CompletionResult]::new('-R','R',[CompletionResultType]::ParameterName,'TheEqual-costMulti-Pathroutingstrategy(UDPonly)[default:classic]')[CompletionResult]::new('--multipath-strategy','multipath-strategy',[CompletionResultType]::ParameterName,'TheEqual-costMulti-Pathroutingstrategy(UDPonly)[default:classic]')[CompletionResult]::new('-U','U',[CompletionResultType]::ParameterName,'Themaximumnumberofin-flightICMPechorequests[default:24]')[CompletionResult]::new('--max-inflight','max-inflight',[CompletionResultType]::ParameterName,'Themaximumnumberofin-flightICMPechorequests[default:24]')[CompletionResult]::new('-f','f',[CompletionResultType]::ParameterName,'TheTTLtostartfrom[default:1]')[CompletionResult]::new('--first-ttl','first-ttl',[CompletionResultType]::ParameterName,'TheTTLtostartfrom[default:1]')[CompletionResult]::new('-t','t',[CompletionResultType]::ParameterName,'ThemaximumnumberofTTLhops[default:64]')[CompletionResult]::new('--max-ttl','max-ttl',[CompletionResultType]::ParameterName,'ThemaximumnumberofTTLhops[default:64]')[CompletionResult]::new('--packet-size','packet-size',[CompletionResultType]::ParameterName,'ThesizeofIPpackettosend(IPheader+ICMPheader+payload)[default:84]')[CompletionResult]::new('--payload-pattern','payload-pattern',[CompletionResultType]::ParameterName,'TherepeatingpatterninthepayloadoftheICMPpacket[default:0]')[CompletionResult]::new('-Q','Q',[CompletionResultType]::ParameterName,'TheTOS(i.e.DSCP+ECN)IPheadervalue(TCPandUDPonly)[default:0]')[CompletionResult]::new('--tos','tos',[CompletionResultType]::ParameterName,'TheTOS(i.e.DSCP+ECN)IPheadervalue(TCPandUDPonly)[default:0]')[CompletionResult]::new('--read-timeout','read-timeout',[CompletionResultType]::ParameterName,'Thesocketreadtimeout[default:10ms]')[CompletionResult]::new('-r','r',[CompletionResultType]::ParameterName,'HowtoperformDNSqueries[default:system]')[CompletionResult]::new('--dns-resolve-method','dns-resolve-method',[CompletionResultType]::ParameterName,'HowtoperformDNSqueries[default:system]')[CompletionResult]::new('--dns-timeout','dns-timeout',[CompletionResultType]::ParameterName,'ThemaximumtimetowaittoperformDNSqueries[default:5s]')[CompletionResult]::new('-a','a',[CompletionResultType]::ParameterName,'Howtorenderaddresses[default:host]')[CompletionResult]::new('--tui-address-mode','tui-address-mode',[CompletionResultType]::ParameterName,'Howtorenderaddresses[default:host]')[CompletionResult]::new('--tui-as-mode','tui-as-mode',[CompletionResultType]::ParameterName,'HowtorenderASinformation[default:asn]')[CompletionResult]::new('--tui-custom-columns','tui-custom-columns',[CompletionResultType]::ParameterName,'CustomcolumnstobedisplayedintheTUIhopstable[default:holsravbwdt]')[CompletionResult]::new('--tui-icmp-extension-mode','tui-icmp-extension-mode',[CompletionResultType]::ParameterName,'HowtorenderICMPextensions[default:off]')[CompletionResult]::new('--tui-geoip-mode','tui-geoip-mode',[CompletionResultType]::ParameterName,'HowtorenderGeoIpinformation[default:short]')[CompletionResult]::new('-M','M',[CompletionResultType]::ParameterName,'Themaximumnumberofaddressestoshowperhop[default:auto]')[CompletionResult]::new('--tui-max-addrs','tui-max-addrs',[CompletionResultType]::ParameterName,'Themaximumnumberofaddressestoshowperhop[default:auto]')[CompletionResult]::new('-s','s',[CompletionResultType]::ParameterName,'Themaximumnumberofsamplestorecordperhop[default:256]')[CompletionResult]::new('--tui-max-samples','tui-max-samples',[CompletionResultType]::ParameterName,'Themaximumnumberofsamplestorecordperhop[default:256]')[CompletionResult]::new('--tui-max-flows','tui-max-flows',[CompletionResultType]::ParameterName,'Themaximumnumberofflowstoshow[default:64]')[CompletionResult]::new('--tui-refresh-rate','tui-refresh-rate',[CompletionResultType]::ParameterName,'TheTuirefreshrate[default:100ms]')[CompletionResult]::new('--tui-privacy-max-ttl','tui-privacy-max-ttl',[CompletionResultType]::ParameterName,'Themaximumttlofhopswhichwillbemaskedforprivacy[default:0]')[CompletionResult]::new('--tui-theme-colors','tui-theme-colors',[CompletionResultType]::ParameterName,'TheTUIthemecolors[item=color,item=color,..]')[CompletionResult]::new('--tui-key-bindings','tui-key-bindings',[CompletionResultType]::ParameterName,'TheTUIkeybindings[command=key,command=key,..]')[CompletionResult]::new('-C','C',[CompletionResultType]::ParameterName,'Thenumberofreportcyclestorun[default:10]')[CompletionResult]::new('--report-cycles','report-cycles',[CompletionResultType]::ParameterName,'Thenumberofreportcyclestorun[default:10]')[CompletionResult]::new('-G','G',[CompletionResultType]::ParameterName,'ThesupportedMaxMindorIPinfoGeoIpmmdbfile')[CompletionResult]::new('--geoip-mmdb-file','geoip-mmdb-file',[CompletionResultType]::ParameterName,'ThesupportedMaxMindorIPinfoGeoIpmmdbfile')[CompletionResult]::new('--generate','generate',[CompletionResultType]::ParameterName,'Generateshellcompletion')[CompletionResult]::new('--log-format','log-format',[CompletionResultType]::ParameterName,'Thedebuglogformat[default:pretty]')[CompletionResult]::new('--log-filter','log-filter',[CompletionResultType]::ParameterName,'Thedebuglogfilter[default:trippy=debug]')[CompletionResult]::new('--log-span-events','log-span-events',[CompletionResultType]::ParameterName,'Thedebuglogformat[default:off]')[CompletionResult]::new('-u','u',[CompletionResultType]::ParameterName,'Tracewithoutrequiringelevatedprivilegesonsupportedplatforms[default:false]')[CompletionResult]::new('--unprivileged','unprivileged',[CompletionResultType]::ParameterName,'Tracewithoutrequiringelevatedprivilegesonsupportedplatforms[default:false]')[CompletionResult]::new('--udp','udp',[CompletionResultType]::ParameterName,'TraceusingtheUDPprotocol')[CompletionResult]::new('--tcp','tcp',[CompletionResultType]::ParameterName,'TraceusingtheTCPprotocol')[CompletionResult]::new('--icmp','icmp',[CompletionResultType]::ParameterName,'TraceusingtheICMPprotocol')[CompletionResult]::new('-4','4',[CompletionResultType]::ParameterName,'UseIPv4only')[CompletionResult]::new('--ipv4','ipv4',[CompletionResultType]::ParameterName,'UseIPv4only')[CompletionResult]::new('-6','6',[CompletionResultType]::ParameterName,'UseIPv6only')[CompletionResult]::new('--ipv6','ipv6',[CompletionResultType]::ParameterName,'UseIPv6only')[CompletionResult]::new('-e','e',[CompletionResultType]::ParameterName,'ParseICMPextensions')[CompletionResult]::new('--icmp-extensions','icmp-extensions',[CompletionResultType]::ParameterName,'ParseICMPextensions')[CompletionResult]::new('-y','y',[CompletionResultType]::ParameterName,'TracetoallIPsresolvedfromDNSlookup[default:false]')[CompletionResult]::new('--dns-resolve-all','dns-resolve-all',[CompletionResultType]::ParameterName,'TracetoallIPsresolvedfromDNSlookup[default:false]')[CompletionResult]::new('-z','z',[CompletionResultType]::ParameterName,'Lookupautonomoussystem(AS)informationduringDNSqueries[default:false]')[CompletionResult]::new('--dns-lookup-as-info','dns-lookup-as-info',[CompletionResultType]::ParameterName,'Lookupautonomoussystem(AS)informationduringDNSqueries[default:false]')[CompletionResult]::new('--tui-preserve-screen','tui-preserve-screen',[CompletionResultType]::ParameterName,'Preservethescreenonexit[default:false]')[CompletionResult]::new('--print-tui-theme-items','print-tui-theme-items',[CompletionResultType]::ParameterName,'PrintallTUIthemeitemsandexit')[CompletionResult]::new('--print-tui-binding-commands','print-tui-binding-commands',[CompletionResultType]::ParameterName,'PrintallTUIcommandsthatcanbeboundandexit')[CompletionResult]::new('--generate-man','generate-man',[CompletionResultType]::ParameterName,'GenerateROFFmanpage')[CompletionResult]::new('--print-config-template','print-config-template',[CompletionResultType]::ParameterName,'Printatemplatetomlconfigfileandexit')[CompletionResult]::new('-v','v',[CompletionResultType]::ParameterName,'Enableverbosedebuglogging')[CompletionResult]::new('--verbose','verbose',[CompletionResultType]::ParameterName,'Enableverbosedebuglogging')[CompletionResult]::new('-h','h',[CompletionResultType]::ParameterName,'Printhelp(seemorewith''--help'')')[CompletionResult]::new('--help','help',[CompletionResultType]::ParameterName,'Printhelp(seemorewith''--help'')')[CompletionResult]::new('-V','V',[CompletionResultType]::ParameterName,'Printversion')[CompletionResult]::new('--version','version',[CompletionResultType]::ParameterName,'Printversion')break}})$completions.Where{$_.CompletionText-like"$wordToComplete*"}|Sort-Object-PropertyListItemText}
diff --git a/crates/trippy/tests/resources/snapshots/trip__tests__output@generate_zsh_shell_completions.snap b/crates/trippy/tests/resources/snapshots/trip__print__tests__output@generate_zsh_shell_completions.snap
similarity index 99%
rename from crates/trippy/tests/resources/snapshots/trip__tests__output@generate_zsh_shell_completions.snap
rename to crates/trippy/tests/resources/snapshots/trip__print__tests__output@generate_zsh_shell_completions.snap
index a035ced..89c148f 100644
--- a/crates/trippy/tests/resources/snapshots/trip__tests__output@generate_zsh_shell_completions.snap
+++ b/crates/trippy/tests/resources/snapshots/trip__print__tests__output@generate_zsh_shell_completions.snap
@@ -1,4 +1,4 @@
 ---
-source: crates/trippy/src/main.rs
+source: crates/trippy/src/print.rs
 ---
 #compdeftripautoload-Uis-at-least_trip(){typeset-Aopt_argstypeset-a_arguments_optionslocalret=1ifis-at-least5.2;then_arguments_options=(-s-S-C)else_arguments_options=(-s-C)filocalcontextcurcontext="$curcontext"stateline_arguments"${_arguments_options[@]}":\'-c+[Configfile]:CONFIG_FILE:_files'\'--config-file=[Configfile]:CONFIG_FILE:_files'\'-m+[Outputmode\[default\:tui\]]:MODE:((tui\:"DisplayinteractiveTUI"stream\:"Displayacontinuousstreamoftracingdata"pretty\:"GenerateaprettytexttablereportforNcycles"markdown\:"GenerateaMarkdowntexttablereportforNcycles"csv\:"GenerateaCSVreportforNcycles"json\:"GenerateaJSONreportforNcycles"dot\:"GenerateaGraphvizDOTfileforNcycles"flows\:"DisplayallflowsforNcycles"silent\:"DonotgenerateanytracingoutputforNcycles"))'\'--mode=[Outputmode\[default\:tui\]]:MODE:((tui\:"DisplayinteractiveTUI"stream\:"Displayacontinuousstreamoftracingdata"pretty\:"GenerateaprettytexttablereportforNcycles"markdown\:"GenerateaMarkdowntexttablereportforNcycles"csv\:"GenerateaCSVreportforNcycles"json\:"GenerateaJSONreportforNcycles"dot\:"GenerateaGraphvizDOTfileforNcycles"flows\:"DisplayallflowsforNcycles"silent\:"DonotgenerateanytracingoutputforNcycles"))'\'-p+[Tracingprotocol\[default\:icmp\]]:PROTOCOL:((icmp\:"InternetControlMessageProtocol"udp\:"UserDatagramProtocol"tcp\:"TransmissionControlProtocol"))'\'--protocol=[Tracingprotocol\[default\:icmp\]]:PROTOCOL:((icmp\:"InternetControlMessageProtocol"udp\:"UserDatagramProtocol"tcp\:"TransmissionControlProtocol"))'\'-F+[Theaddressfamily\[default\:Ipv4thenIpv6\]]:ADDR_FAMILY:((ipv4\:"Ipv4only"ipv6\:"Ipv6only"ipv6-then-ipv4\:"Ipv6withafallbacktoIpv4"ipv4-then-ipv6\:"Ipv4withafallbacktoIpv6"))'\'--addr-family=[Theaddressfamily\[default\:Ipv4thenIpv6\]]:ADDR_FAMILY:((ipv4\:"Ipv4only"ipv6\:"Ipv6only"ipv6-then-ipv4\:"Ipv6withafallbacktoIpv4"ipv4-then-ipv6\:"Ipv4withafallbacktoIpv6"))'\'-P+[Thetargetport(TCP&UDPonly)\[default\:80\]]:TARGET_PORT:'\'--target-port=[Thetargetport(TCP&UDPonly)\[default\:80\]]:TARGET_PORT:'\'-S+[Thesourceport(TCP&UDPonly)\[default\:auto\]]:SOURCE_PORT:'\'--source-port=[Thesourceport(TCP&UDPonly)\[default\:auto\]]:SOURCE_PORT:'\'(-I--interface)-A+[ThesourceIPaddress\[default\:auto\]]:SOURCE_ADDRESS:'\'(-I--interface)--source-address=[ThesourceIPaddress\[default\:auto\]]:SOURCE_ADDRESS:'\'-I+[Thenetworkinterface\[default\:auto\]]:INTERFACE:'\'--interface=[Thenetworkinterface\[default\:auto\]]:INTERFACE:'\'-i+[Theminimumdurationofeveryround\[default\:1s\]]:MIN_ROUND_DURATION:'\'--min-round-duration=[Theminimumdurationofeveryround\[default\:1s\]]:MIN_ROUND_DURATION:'\'-T+[Themaximumdurationofeveryround\[default\:1s\]]:MAX_ROUND_DURATION:'\'--max-round-duration=[Themaximumdurationofeveryround\[default\:1s\]]:MAX_ROUND_DURATION:'\'-g+[TheperiodoftimetowaitforadditionalICMPresponsesafterthetargethasresponded\[default\:100ms\]]:GRACE_DURATION:'\'--grace-duration=[TheperiodoftimetowaitforadditionalICMPresponsesafterthetargethasresponded\[default\:100ms\]]:GRACE_DURATION:'\'--initial-sequence=[Theinitialsequencenumber\[default\:33000\]]:INITIAL_SEQUENCE:'\'-R+[TheEqual-costMulti-Pathroutingstrategy(UDPonly)\[default\:classic\]]:MULTIPATH_STRATEGY:((classic\:"Thesrcordestportisusedtostorethesequencenumber"paris\:"TheUDP\`checksum\`fieldisusedtostorethesequencenumber"dublin\:"TheIP\`identifier\`fieldisusedtostorethesequencenumber"))'\'--multipath-strategy=[TheEqual-costMulti-Pathroutingstrategy(UDPonly)\[default\:classic\]]:MULTIPATH_STRATEGY:((classic\:"Thesrcordestportisusedtostorethesequencenumber"paris\:"TheUDP\`checksum\`fieldisusedtostorethesequencenumber"dublin\:"TheIP\`identifier\`fieldisusedtostorethesequencenumber"))'\'-U+[Themaximumnumberofin-flightICMPechorequests\[default\:24\]]:MAX_INFLIGHT:'\'--max-inflight=[Themaximumnumberofin-flightICMPechorequests\[default\:24\]]:MAX_INFLIGHT:'\'-f+[TheTTLtostartfrom\[default\:1\]]:FIRST_TTL:'\'--first-ttl=[TheTTLtostartfrom\[default\:1\]]:FIRST_TTL:'\'-t+[ThemaximumnumberofTTLhops\[default\:64\]]:MAX_TTL:'\'--max-ttl=[ThemaximumnumberofTTLhops\[default\:64\]]:MAX_TTL:'\'--packet-size=[ThesizeofIPpackettosend(IPheader+ICMPheader+payload)\[default\:84\]]:PACKET_SIZE:'\'--payload-pattern=[TherepeatingpatterninthepayloadoftheICMPpacket\[default\:0\]]:PAYLOAD_PATTERN:'\'-Q+[TheTOS(i.e.DSCP+ECN)IPheadervalue(TCPandUDPonly)\[default\:0\]]:TOS:'\'--tos=[TheTOS(i.e.DSCP+ECN)IPheadervalue(TCPandUDPonly)\[default\:0\]]:TOS:'\'--read-timeout=[Thesocketreadtimeout\[default\:10ms\]]:READ_TIMEOUT:'\'-r+[HowtoperformDNSqueries\[default\:system\]]:DNS_RESOLVE_METHOD:((system\:"ResolveusingtheOSresolver"resolv\:"Resolveusingthe\`/etc/resolv.conf\`DNSconfiguration"google\:"ResolveusingtheGoogle\`8.8.8.8\`DNSservice"cloudflare\:"ResolveusingtheCloudflare\`1.1.1.1\`DNSservice"))'\'--dns-resolve-method=[HowtoperformDNSqueries\[default\:system\]]:DNS_RESOLVE_METHOD:((system\:"ResolveusingtheOSresolver"resolv\:"Resolveusingthe\`/etc/resolv.conf\`DNSconfiguration"google\:"ResolveusingtheGoogle\`8.8.8.8\`DNSservice"cloudflare\:"ResolveusingtheCloudflare\`1.1.1.1\`DNSservice"))'\'--dns-timeout=[ThemaximumtimetowaittoperformDNSqueries\[default\:5s\]]:DNS_TIMEOUT:'\'-a+[Howtorenderaddresses\[default\:host\]]:TUI_ADDRESS_MODE:((ip\:"ShowIPaddressonly"host\:"Showreverse-lookupDNShostnameonly"both\:"ShowbothIPaddressandreverse-lookupDNShostname"))'\'--tui-address-mode=[Howtorenderaddresses\[default\:host\]]:TUI_ADDRESS_MODE:((ip\:"ShowIPaddressonly"host\:"Showreverse-lookupDNShostnameonly"both\:"ShowbothIPaddressandreverse-lookupDNShostname"))'\'--tui-as-mode=[HowtorenderASinformation\[default\:asn\]]:TUI_AS_MODE:((asn\:"ShowtheASN"prefix\:"DisplaytheASprefix"country-code\:"Displaythecountrycode"registry\:"Displaytheregistryname"allocated\:"Displaytheallocateddate"name\:"DisplaytheASname"))'\'--tui-custom-columns=[CustomcolumnstobedisplayedintheTUIhopstable\[default\:holsravbwdt\]]:TUI_CUSTOM_COLUMNS:'\'--tui-icmp-extension-mode=[HowtorenderICMPextensions\[default\:off\]]:TUI_ICMP_EXTENSION_MODE:((off\:"Donotshow\`icmp\`extensions"mpls\:"ShowMPLSlabel(s)only"full\:"Showfull\`icmp\`extensiondataforallknownextensions"all\:"Showfull\`icmp\`extensiondataforallclasses"))'\'--tui-geoip-mode=[HowtorenderGeoIpinformation\[default\:short\]]:TUI_GEOIP_MODE:((off\:"DonotdisplayGeoIpdata"short\:"Showshortformat"long\:"Showlongformat"location\:"ShowlatitudeandLongitudeformat"))'\'-M+[Themaximumnumberofaddressestoshowperhop\[default\:auto\]]:TUI_MAX_ADDRS:'\'--tui-max-addrs=[Themaximumnumberofaddressestoshowperhop\[default\:auto\]]:TUI_MAX_ADDRS:'\'-s+[Themaximumnumberofsamplestorecordperhop\[default\:256\]]:TUI_MAX_SAMPLES:'\'--tui-max-samples=[Themaximumnumberofsamplestorecordperhop\[default\:256\]]:TUI_MAX_SAMPLES:'\'--tui-max-flows=[Themaximumnumberofflowstoshow\[default\:64\]]:TUI_MAX_FLOWS:'\'--tui-refresh-rate=[TheTuirefreshrate\[default\:100ms\]]:TUI_REFRESH_RATE:'\'--tui-privacy-max-ttl=[Themaximumttlofhopswhichwillbemaskedforprivacy\[default\:0\]]:TUI_PRIVACY_MAX_TTL:'\'*--tui-theme-colors=[TheTUIthemecolors\[item=color,item=color,..\]]:TUI_THEME_COLORS:'\'*--tui-key-bindings=[TheTUIkeybindings\[command=key,command=key,..\]]:TUI_KEY_BINDINGS:'\'-C+[Thenumberofreportcyclestorun\[default\:10\]]:REPORT_CYCLES:'\'--report-cycles=[Thenumberofreportcyclestorun\[default\:10\]]:REPORT_CYCLES:'\'-G+[ThesupportedMaxMindorIPinfoGeoIpmmdbfile]:GEOIP_MMDB_FILE:_files'\'--geoip-mmdb-file=[ThesupportedMaxMindorIPinfoGeoIpmmdbfile]:GEOIP_MMDB_FILE:_files'\'--generate=[Generateshellcompletion]:GENERATE:(bashelvishfishpowershellzsh)'\'--log-format=[Thedebuglogformat\[default\:pretty\]]:LOG_FORMAT:((compact\:"Displaylogdatainacompactformat"pretty\:"Displaylogdatainaprettyformat"json\:"Displaylogdatainajsonformat"chrome\:"DisplaylogdatainChrometraceformat"))'\'--log-filter=[Thedebuglogfilter\[default\:trippy=debug\]]:LOG_FILTER:'\'--log-span-events=[Thedebuglogformat\[default\:off\]]:LOG_SPAN_EVENTS:((off\:"Donotdisplayeventspans"active\:"Displayenterandexiteventspans"full\:"Displayalleventspans"))'\'-u[Tracewithoutrequiringelevatedprivilegesonsupportedplatforms\[default\:false\]]'\'--unprivileged[Tracewithoutrequiringelevatedprivilegesonsupportedplatforms\[default\:false\]]'\'(-p--protocol--tcp--icmp)--udp[TraceusingtheUDPprotocol]'\'(-p--protocol--udp--icmp)--tcp[TraceusingtheTCPprotocol]'\'(-p--protocol--udp--tcp)--icmp[TraceusingtheICMPprotocol]'\'(-6--ipv6-F--addr-family)-4[UseIPv4only]'\'(-6--ipv6-F--addr-family)--ipv4[UseIPv4only]'\'(-4--ipv4-F--addr-family)-6[UseIPv6only]'\'(-4--ipv4-F--addr-family)--ipv6[UseIPv6only]'\'-e[ParseICMPextensions]'\'--icmp-extensions[ParseICMPextensions]'\'-y[TracetoallIPsresolvedfromDNSlookup\[default\:false\]]'\'--dns-resolve-all[TracetoallIPsresolvedfromDNSlookup\[default\:false\]]'\'-z[Lookupautonomoussystem(AS)informationduringDNSqueries\[default\:false\]]'\'--dns-lookup-as-info[Lookupautonomoussystem(AS)informationduringDNSqueries\[default\:false\]]'\'--tui-preserve-screen[Preservethescreenonexit\[default\:false\]]'\'--print-tui-theme-items[PrintallTUIthemeitemsandexit]'\'--print-tui-binding-commands[PrintallTUIcommandsthatcanbeboundandexit]'\'--generate-man[GenerateROFFmanpage]'\'--print-config-template[Printatemplatetomlconfigfileandexit]'\'-v[Enableverbosedebuglogging]'\'--verbose[Enableverbosedebuglogging]'\'-h[Printhelp(seemorewith'\''--help'\'')]'\'--help[Printhelp(seemorewith'\''--help'\'')]'\'-V[Printversion]'\'--version[Printversion]'\'*::targets--AspacedelimitedlistofhostnamesandIPstotrace:'\&&ret=0}(($+functions[_trip_commands]))||_trip_commands(){localcommands;commands=()_describe-tcommands'tripcommands'commands"$@"}if["$funcstack[1]"="_trip"];then_trip"$@"elsecompdef_triptripfi
diff --git a/crates/trippy/tests/resources/snapshots/trip__tests__output@tui_binding_commands_match.snap b/crates/trippy/tests/resources/snapshots/trip__print__tests__output@tui_binding_commands_match.snap
similarity index 92%
rename from crates/trippy/tests/resources/snapshots/trip__tests__output@tui_binding_commands_match.snap
rename to crates/trippy/tests/resources/snapshots/trip__print__tests__output@tui_binding_commands_match.snap
index 32323c0..777b36b 100644
--- a/crates/trippy/tests/resources/snapshots/trip__tests__output@tui_binding_commands_match.snap
+++ b/crates/trippy/tests/resources/snapshots/trip__print__tests__output@tui_binding_commands_match.snap
@@ -1,4 +1,4 @@
 ---
-source: crates/trippy/src/main.rs
+source: crates/trippy/src/print.rs
 ---
 TUIbindingcommands:toggle-help,toggle-help-alt,toggle-settings,next-hop,previous-hop,next-trace,previous-trace,next-hop-address,previous-hop-address,address-mode-ip,address-mode-host,address-mode-both,toggle-freeze,toggle-chart,toggle-map,toggle-flows,toggle-privacy,expand-hosts,expand-hosts-max,contract-hosts,contract-hosts-min,chart-zoom-in,chart-zoom-out,clear-trace-data,clear-dns-cache,clear-selection,toggle-as-info,toggle-hop-details,quit
diff --git a/crates/trippy/tests/resources/snapshots/trip__tests__output@tui_theme_items_match.snap b/crates/trippy/tests/resources/snapshots/trip__print__tests__output@tui_theme_items_match.snap
similarity index 95%
rename from crates/trippy/tests/resources/snapshots/trip__tests__output@tui_theme_items_match.snap
rename to crates/trippy/tests/resources/snapshots/trip__print__tests__output@tui_theme_items_match.snap
index cffa2d4..0274e59 100644
--- a/crates/trippy/tests/resources/snapshots/trip__tests__output@tui_theme_items_match.snap
+++ b/crates/trippy/tests/resources/snapshots/trip__print__tests__output@tui_theme_items_match.snap
@@ -1,4 +1,4 @@
 ---
-source: crates/trippy/src/main.rs
+source: crates/trippy/src/print.rs
 ---
 TUIthemecoloritems:bg-color,border-color,text-color,tab-text-color,hops-table-header-bg-color,hops-table-header-text-color,hops-table-row-active-text-color,hops-table-row-inactive-text-color,hops-chart-selected-color,hops-chart-unselected-color,hops-chart-axis-color,frequency-chart-bar-color,frequency-chart-text-color,flows-chart-bar-selected-color,flows-chart-bar-unselected-color,flows-chart-text-current-color,flows-chart-text-non-current-color,samples-chart-color,help-dialog-bg-color,help-dialog-text-color,settings-tab-text-color,settings-dialog-bg-color,settings-table-header-text-color,settings-table-header-bg-color,settings-table-row-text-color,map-world-color,map-radius-color,map-selected-color,map-info-panel-border-color,map-info-panel-bg-color,map-info-panel-text-color