rustbag 0.1.1

A high-performance ROS 2 bag player
// Copyright 2025 Ivo Ivanov.
// Copyright 2018 Open Source Robotics Foundation, Inc.
// Copyright 2018, Bosch Software Innovations GmbH.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use std::str::FromStr;

use clap::{ArgAction, Parser, ValueEnum};

#[derive(Debug, Clone)]
pub struct RemapPair(pub String, pub String);

impl FromStr for RemapPair {
    type Err = String;

    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
        s.split_once(":=")
            .map(|(from, to)| RemapPair(from.into(), to.into()))
            .ok_or_else(|| format!("Invalid remap format: {s} (expected /old_topic:=/new_topic)"))
    }
}

/// Play back ROS data from a bag
#[derive(Parser, Debug, Clone)]
#[command(author, version, about, long_about = None)]
pub struct Args {
    /// Filepath to the mcap file/rosbag.
    #[arg(required = true)]
    pub input: Vec<String>,

    /// Lookahead distance in seconds for filling the message queue.
    #[arg(long, default_value_t = 0.5)]
    pub lookahead: f64,

    /// Playback rate (> 0.0)
    #[arg(short, long)]
    pub rate: Option<f64>,

    /// List of topics to play.
    #[arg(long, num_args = 1.., value_name = "TOPIC")]
    pub topics: Vec<String>,

    /// Regex to include topics/services.
    #[arg(short = 'e', long)]
    pub regex: Option<String>,

    /// Regex to exclude topics/services.
    #[arg(short = 'x', long)]
    pub exclude_regex: Option<String>,

    /// List of topics not to play.
    #[arg(long, num_args = 1.., value_name = "EXCLUDE_TOPIC")]
    pub exclude_topics: Vec<String>,

    /// Loop playback indefinitely.
    #[arg(short, long, action = ArgAction::SetTrue)]
    pub r#loop: bool,

    /// Topic remapping: old:=new
    #[arg(short = 'm', long = "remap")]
    #[arg(long, num_args = 1.., value_name = "REMAP", value_parser = clap::value_parser!(RemapPair))]
    pub remap: Vec<RemapPair>,

    /// Publish /clock before every replayed message
    #[arg(long, action = ArgAction::SetTrue)]
    pub clock: bool,

    /// Max frequency for publishing /clock (Hz)
    #[arg(long, default_value_t = 100.0)]
    pub clock_rate_hz: f64,

    /// Start playback this many seconds into the bag
    #[arg(long)]
    pub start_offset: Option<f64>,

    /// Log level: error, warn, info, debug, trace
    #[arg(long, value_enum, default_value = "info")]
    pub log_level: LogLevel,
}

#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum LogLevel {
    Error,
    Warn,
    Info,
    Debug,
    Trace,
}