` and `` blocks as well as Markdown code blocks.
29 | include_verbatim = true
30 |
31 | # Proceed for server connections considered insecure (invalid TLS).
32 | insecure = true
33 |
34 | # Exclude these filesystem paths from getting checked.
35 | exclude_path = [
36 | # Unfortunately lychee doesn't yet read .gitignore https://github.com/lycheeverse/lychee/issues/1331
37 | # The following entries are there because of that:
38 | ".git",
39 | "__pycache__",
40 | "_deps/",
41 | ".pixi",
42 | "build",
43 | "target_ra",
44 | "target_wasm",
45 | "target",
46 | "venv",
47 | ]
48 |
49 | # Exclude URLs and mail addresses from checking (supports regex).
50 | exclude = [
51 | # Skip speculative links
52 | '.*?speculative-link',
53 |
54 | # Strings with replacements.
55 | '/__VIEWER_VERSION__/', # Replacement variable __VIEWER_VERSION__.
56 | '/\$', # Replacement variable $.
57 | '/GIT_HASH/', # Replacement variable GIT_HASH.
58 | '\{\}', # Ignore links with string interpolation.
59 | '\$relpath\^', # Relative paths as used by rerun_cpp's doc header.
60 | '%7B.+%7D', # Ignore strings that look like ready to use links but contain a replacement strings. The URL escaping is for '{.+}' (this seems to be needed for html embedded urls since lychee assumes they use this encoding).
61 | '%7B%7D', # Ignore links with string interpolation, escaped variant.
62 |
63 | # Local links that require further setup.
64 | 'http://127.0.0.1',
65 | 'http://localhost',
66 | 'recording:/', # rrd recording link.
67 | 'ws:/',
68 | 're_viewer.js', # Build artifact that html is linking to.
69 |
70 | # Api endpoints.
71 | 'https://fonts.googleapis.com/', # Font API entrypoint, not a link.
72 | 'https://fonts.gstatic.com/', # Font API entrypoint, not a link.
73 | 'https://tel.rerun.io/', # Analytics endpoint.
74 |
75 | # Avoid rate limiting.
76 | 'https://crates.io/crates/.*', # Avoid crates.io rate-limiting
77 | 'https://github.com/rerun-io/rerun/commit/\.*', # Ignore links to our own commits (typically in changelog).
78 | 'https://github.com/rerun-io/rerun/pull/\.*', # Ignore links to our own pull requests (typically in changelog).
79 |
80 | # Used in rerun_template repo until the user search-replaces `rerun-ros`
81 | 'https://github.com/rerun-io/rerun-ros',
82 | ]
83 |
--------------------------------------------------------------------------------
/.github/workflows/rust.yml:
--------------------------------------------------------------------------------
1 | # Copied from https://github.com/rerun-io/rerun_template
2 | on: [push, pull_request]
3 |
4 | name: Rust
5 |
6 | env:
7 | RUSTFLAGS: -D warnings
8 | RUSTDOCFLAGS: -D warnings
9 |
10 | jobs:
11 | rust-check:
12 | name: Rust
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v4
16 |
17 | - uses: actions-rs/toolchain@v1
18 | with:
19 | profile: default
20 | toolchain: 1.76.0
21 | override: true
22 |
23 | - name: Install packages (Linux)
24 | if: runner.os == 'Linux' && false # TODO: enable if eframe is part of the project, otherwise remove
25 | uses: awalsh128/cache-apt-pkgs-action@v1.3.0
26 | with:
27 | # some deps used by eframe, if that is part of the project
28 | packages: libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev # libgtk-3-dev is used by rfd
29 | version: 1.0
30 | execute_install_scripts: true
31 |
32 | - uses: prefix-dev/setup-pixi@v0.8.1
33 |
34 | - name: Set up cargo cache
35 | uses: Swatinem/rust-cache@v2
36 |
37 | - name: Rustfmt
38 | uses: actions-rs/cargo@v1
39 | with:
40 | command: fmt
41 | args: --all -- --check
42 |
43 | - name: check --all-features
44 | run: pixi run cargo check --all-features --all-targets
45 |
46 | - name: check default features
47 | run: pixi run cargo check --all-targets
48 |
49 | - name: check --no-default-features
50 | run: pixi run cargo check --no-default-features --lib --all-targets
51 |
52 | - name: Test doc-tests
53 | run: pixi run cargo test --doc --all-features
54 |
55 | - name: cargo doc --lib
56 | run: pixi run cargo doc --lib --no-deps --all-features
57 |
58 | - name: cargo doc --document-private-items
59 | run: pixi run cargo doc --document-private-items --no-deps --all-features
60 |
61 | - name: Build tests
62 | run: pixi run cargo test --all-features --no-run
63 |
64 | - name: Run test
65 | run: pixi run cargo test --all-features
66 |
67 | - name: Clippy
68 | run: pixi run cargo clippy --all-targets --all-features -- -D warnings
69 |
70 | # ---------------------------------------------------------------------------
71 |
72 | check_wasm:
73 | if: false
74 | name: Check wasm32
75 | runs-on: ubuntu-latest
76 | steps:
77 | - uses: actions/checkout@v4
78 | - uses: actions-rs/toolchain@v1
79 | with:
80 | profile: minimal
81 | toolchain: 1.76.0
82 | target: wasm32-unknown-unknown
83 | override: true
84 | components: clippy
85 |
86 | - name: Set up cargo cache
87 | uses: Swatinem/rust-cache@v2
88 |
89 | - name: Check wasm32
90 | uses: actions-rs/cargo@v1
91 | with:
92 | command: check
93 | args: --target wasm32-unknown-unknown --lib
94 |
95 | - name: Clippy wasm32
96 | env:
97 | CLIPPY_CONF_DIR: "scripts/clippy_wasm" # Use scripts/clippy_wasm/clippy.toml
98 | run: cargo clippy --target wasm32-unknown-unknown --lib -- -D warnings
99 |
100 | # ---------------------------------------------------------------------------
101 |
102 | cargo-deny:
103 | name: Check Rust dependencies (cargo-deny)
104 | runs-on: ubuntu-latest
105 | steps:
106 | - uses: actions/checkout@v3
107 | - uses: EmbarkStudios/cargo-deny-action@v1
108 | with:
109 | rust-version: "1.76.0"
110 | log-level: warn
111 | command: check
112 |
--------------------------------------------------------------------------------
/src/config.rs:
--------------------------------------------------------------------------------
1 | use anyhow::Result;
2 | use serde::Deserialize;
3 | use std::collections::HashMap;
4 | use std::fs;
5 | use std::path::Path;
6 |
7 | /// Represents a single conversion configuration.
8 | #[derive(Deserialize, Debug)]
9 | struct Conversion {
10 | topic: String,
11 | frame_id: String,
12 | ros_type: String,
13 | entity_path: String,
14 | }
15 |
16 | /// Parses and holds conversion configurations.
17 | pub struct ConfigParser {
18 | conversions: HashMap<(String, String), (String, String)>,
19 | }
20 |
21 | impl ConfigParser {
22 | /// Creates a new `ConfigParser` from the given configuration file.
23 | ///
24 | /// # Arguments
25 | ///
26 | /// * `config_file` - A string slice that holds the path to the configuration file.
27 | ///
28 | /// # Errors
29 | ///
30 | /// This function will return an error if:
31 | /// - The configuration file cannot be found or read.
32 | /// - The configuration file contains invalid TOML.
33 | /// - The configuration file does not contain the expected structure.
34 | pub fn new(config_file: &str) -> Result {
35 | let conversions = {
36 | let mut conversions = HashMap::new();
37 | let config_path = Path::new(config_file);
38 | let full_path = config_path.canonicalize()?;
39 |
40 | let config_str = fs::read_to_string(full_path)?;
41 | let config: HashMap> = toml::from_str(&config_str)?;
42 |
43 | for conversion in &config["conversion"] {
44 | conversions.insert(
45 | (conversion.topic.clone(), conversion.frame_id.clone()),
46 | (conversion.ros_type.clone(), conversion.entity_path.clone()),
47 | );
48 | }
49 |
50 | conversions
51 | };
52 |
53 | Ok(Self { conversions })
54 | }
55 |
56 | /// Returns a reference to the conversions hashmap.
57 | pub fn conversions(&self) -> &HashMap<(String, String), (String, String)> {
58 | &self.conversions
59 | }
60 | }
61 |
62 | #[cfg(test)]
63 | mod tests {
64 | use super::*;
65 | use std::fs::File;
66 | use std::io::Write;
67 | use tempfile::tempdir;
68 |
69 | #[test]
70 | fn test_config_parser_new_valid_file() {
71 | // Create a temporary directory
72 | let dir = tempdir().unwrap();
73 | let file_path = dir.path().join("config.toml");
74 |
75 | // Write a valid TOML configuration to the file
76 | let mut file = File::create(&file_path).unwrap();
77 | writeln!(
78 | file,
79 | r#"
80 | [[conversion]]
81 | topic = "topic1"
82 | frame_id = "frame1"
83 | ros_type = "type1"
84 | entity_path = "foo/bar1"
85 | [[conversion]]
86 | topic = "topic2"
87 | frame_id = "frame2"
88 | ros_type = "type2"
89 | entity_path = "foo/bar2"
90 | "#
91 | )
92 | .unwrap();
93 |
94 | // Create a ConfigParser instance
95 | let config_parser = ConfigParser::new(file_path.to_str().unwrap()).unwrap();
96 |
97 | // Check the conversions hashmap
98 | let conversions = config_parser.conversions();
99 | assert_eq!(conversions.len(), 2);
100 | assert_eq!(
101 | conversions.get(&("topic1".to_owned(), "frame1".to_owned())),
102 | Some(&("type1".to_owned(), "foo/bar1".to_owned()))
103 | );
104 | assert_eq!(
105 | conversions.get(&("topic2".to_owned(), "frame2".to_owned())),
106 | Some(&("type2".to_owned(), "foo/bar2".to_owned()))
107 | );
108 | }
109 |
110 | #[test]
111 | fn test_config_parser_new_invalid_file() {
112 | // Create a temporary directory
113 | let dir = tempdir().unwrap();
114 | let file_path = dir.path().join("config.toml");
115 |
116 | // Write an invalid TOML configuration to the file
117 | let mut file = File::create(&file_path).unwrap();
118 | writeln!(file, "invalid_toml").unwrap();
119 |
120 | // Attempt to create a ConfigParser instance and expect an error
121 | let result = ConfigParser::new(file_path.to_str().unwrap());
122 | assert!(result.is_err());
123 | }
124 |
125 | #[test]
126 | fn test_config_parser_new_missing_file() {
127 | // Attempt to create a ConfigParser instance with a non-existent file
128 | let result = ConfigParser::new("non_existent_file.toml");
129 | assert!(result.is_err());
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/src/ros_introspection/msgspec.rs:
--------------------------------------------------------------------------------
1 | use crate::ros_introspection::{self, BuiltinType, Message, Type};
2 | use anyhow::{anyhow, Error, Result};
3 | use std::fs;
4 | use std::sync::Arc;
5 |
6 | /// Represents a ROS message specification.
7 | pub struct MsgSpec {
8 | data: Arc,
9 | children: Vec>,
10 | }
11 |
12 | impl MsgSpec {
13 | /// Creates a new `MsgSpec` instance for the given topic type.
14 | ///
15 | /// # Arguments
16 | ///
17 | /// * `topic_type` - A string slice that holds the type of the topic.
18 | ///
19 | /// # Returns
20 | ///
21 | /// * `Result` - A result containing the new `MsgSpec` instance or an error.
22 | ///
23 | /// # Errors
24 | ///
25 | /// This function will return an error if the message definition cannot be retrieved.
26 | pub fn new(topic_type: &str) -> Result {
27 | Self::new_with_parent_package(topic_type, "")
28 | }
29 |
30 | /// Creates a new `MsgSpec` instance for the given topic type and parent package.
31 | ///
32 | /// # Arguments
33 | ///
34 | /// * `topic_type` - A string slice that holds the type of the topic.
35 | /// * `parent_package` - A string slice that holds the name of the parent package.
36 | ///
37 | /// # Returns
38 | ///
39 | /// * `Result` - A result containing the new `MsgSpec` instance or an error.
40 | ///
41 | /// # Errors
42 | ///
43 | /// This function will return an error if the message definition cannot be retrieved.
44 | fn new_with_parent_package(topic_type: &str, parent_package: &str) -> Result {
45 | let msg_def = Self::get_message_definition(topic_type, parent_package)?;
46 | let mut children = Vec::new();
47 |
48 | for field in msg_def.fields() {
49 | if field.type_().id() == &BuiltinType::Other {
50 | let child = Self::new_with_parent_package(
51 | field.type_().name(),
52 | msg_def.type_().pkg_name(),
53 | )?;
54 | children.push(Arc::new(child));
55 | }
56 | }
57 |
58 | Ok(Self {
59 | data: msg_def,
60 | children,
61 | })
62 | }
63 |
64 | /// Retrieves the message definition for the given topic type and parent package.
65 | ///
66 | /// # Arguments
67 | ///
68 | /// * `topic_type` - A string slice that holds the type of the topic.
69 | /// * `parent_package` - A string slice that holds the name of the parent package.
70 | ///
71 | /// # Returns
72 | ///
73 | /// * `Result, Error>` - A result containing the message definition or an error.
74 | ///
75 | /// # Errors
76 | ///
77 | /// This function will return an error if the message type is invalid, the package share directory cannot be found, or the message file cannot be read.
78 | fn get_message_definition(
79 | topic_type: &str,
80 | parent_package: &str,
81 | ) -> Result, Error> {
82 | let message_type = {
83 | let message_type = Type::new(topic_type)?;
84 | if message_type.pkg_name().is_empty() && message_type.id() == &BuiltinType::Other {
85 | Type::new_with_parent_package(topic_type, parent_package)?
86 | } else {
87 | message_type
88 | }
89 | };
90 |
91 | let ament_index = ament_rs::Ament::new()?;
92 | let mut msg_file_path = ament_index
93 | .get_package_share_directory(message_type.pkg_name())
94 | .ok_or(anyhow!(
95 | "Could not find package share directory for package: {}",
96 | message_type.pkg_name(),
97 | ))?;
98 |
99 | msg_file_path.push("msg");
100 | msg_file_path.push(format!("{}.msg", message_type.msg_name()));
101 |
102 | let contents = fs::read_to_string(msg_file_path)?;
103 |
104 | let msg_parsed = ros_introspection::parse_message_definitions(&contents, &message_type)?;
105 |
106 | let msg_def = Arc::clone(&msg_parsed[0]);
107 | Ok(msg_def)
108 | }
109 |
110 | /// Returns a reference to the message data.
111 | ///
112 | /// # Returns
113 | ///
114 | /// * `&Arc` - A reference to the message data.
115 | pub fn data(&self) -> &Arc {
116 | &self.data
117 | }
118 |
119 | /// Returns a reference to the children message specifications.
120 | ///
121 | /// # Returns
122 | ///
123 | /// * `&Vec>` - A reference to the vector of children message specifications.
124 | pub fn children(&self) -> &Vec> {
125 | &self.children
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, caste, color, religion, or sexual
10 | identity and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the overall
26 | community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or advances of
31 | any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email address,
35 | without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | opensource@rerun.io.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series of
86 | actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or permanent
93 | ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within the
113 | community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.1, available at
119 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
120 |
121 | Community Impact Guidelines were inspired by
122 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
123 |
124 | For answers to common questions about this code of conduct, see the FAQ at
125 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
126 | [https://www.contributor-covenant.org/translations][translations].
127 |
128 | [homepage]: https://www.contributor-covenant.org
129 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
130 | [Mozilla CoC]: https://github.com/mozilla/diversity
131 | [FAQ]: https://www.contributor-covenant.org/faq
132 | [translations]: https://www.contributor-covenant.org/translations
133 |
--------------------------------------------------------------------------------
/scripts/generate_changelog.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # Copied from https://github.com/rerun-io/rerun_template
3 |
4 | """
5 | Summarizes recent PRs based on their GitHub labels.
6 |
7 | The result can be copy-pasted into CHANGELOG.md,
8 | though it often needs some manual editing too.
9 | """
10 |
11 | from __future__ import annotations
12 |
13 | import argparse
14 | import multiprocessing
15 | import os
16 | import re
17 | import sys
18 | from dataclasses import dataclass
19 | from typing import Any, Optional
20 |
21 | import requests
22 | from git import Repo # pip install GitPython
23 | from tqdm import tqdm
24 |
25 | OWNER = "rerun-io"
26 | REPO = "rerun-ros"
27 | INCLUDE_LABELS = False # It adds quite a bit of visual noise
28 | OFFICIAL_RERUN_DEVS = [
29 | "abey79",
30 | "emilk",
31 | "jleibs",
32 | "jprochazk",
33 | "nikolausWest",
34 | "teh-cmc",
35 | "Wumpf",
36 | ]
37 |
38 |
39 | @dataclass
40 | class PrInfo:
41 | gh_user_name: str
42 | pr_title: str
43 | labels: list[str]
44 |
45 |
46 | @dataclass
47 | class CommitInfo:
48 | hexsha: str
49 | title: str
50 | pr_number: Optional[int]
51 |
52 |
53 | def get_github_token() -> str:
54 | token = os.environ.get("GH_ACCESS_TOKEN", "")
55 | if token != "":
56 | return token
57 |
58 | home_dir = os.path.expanduser("~")
59 | token_file = os.path.join(home_dir, ".githubtoken")
60 |
61 | try:
62 | with open(token_file, encoding="utf8") as f:
63 | token = f.read().strip()
64 | return token
65 | except Exception:
66 | pass
67 |
68 | print("ERROR: expected a GitHub token in the environment variable GH_ACCESS_TOKEN or in ~/.githubtoken")
69 | sys.exit(1)
70 |
71 |
72 | # Slow
73 | def fetch_pr_info_from_commit_info(commit_info: CommitInfo) -> Optional[PrInfo]:
74 | if commit_info.pr_number is None:
75 | return None
76 | else:
77 | return fetch_pr_info(commit_info.pr_number)
78 |
79 |
80 | # Slow
81 | def fetch_pr_info(pr_number: int) -> Optional[PrInfo]:
82 | url = f"https://api.github.com/repos/{OWNER}/{REPO}/pulls/{pr_number}"
83 | gh_access_token = get_github_token()
84 | headers = {"Authorization": f"Token {gh_access_token}"}
85 | response = requests.get(url, headers=headers)
86 | json = response.json()
87 |
88 | # Check if the request was successful (status code 200)
89 | if response.status_code == 200:
90 | labels = [label["name"] for label in json["labels"]]
91 | gh_user_name = json["user"]["login"]
92 | return PrInfo(gh_user_name=gh_user_name, pr_title=json["title"], labels=labels)
93 | else:
94 | print(f"ERROR {url}: {response.status_code} - {json['message']}")
95 | return None
96 |
97 |
98 | def get_commit_info(commit: Any) -> CommitInfo:
99 | match = re.match(r"(.*) \(#(\d+)\)", commit.summary)
100 | if match:
101 | title = str(match.group(1))
102 | pr_number = int(match.group(2))
103 | return CommitInfo(hexsha=commit.hexsha, title=title, pr_number=pr_number)
104 | else:
105 | return CommitInfo(hexsha=commit.hexsha, title=commit.summary, pr_number=None)
106 |
107 |
108 | def remove_prefix(text: str, prefix: str) -> str:
109 | if text.startswith(prefix):
110 | return text[len(prefix) :]
111 | return text # or whatever
112 |
113 |
114 | def print_section(crate: str, items: list[str]) -> None:
115 | if 0 < len(items):
116 | print(f"#### {crate}")
117 | for line in items:
118 | print(f"* {line}")
119 | print()
120 |
121 |
122 | def main() -> None:
123 | parser = argparse.ArgumentParser(description="Generate a changelog.")
124 | parser.add_argument("--commit-range", help="e.g. 0.1.0..HEAD", required=True)
125 | args = parser.parse_args()
126 |
127 | repo = Repo(".")
128 | commits = list(repo.iter_commits(args.commit_range))
129 | commits.reverse() # Most recent last
130 | commit_infos = list(map(get_commit_info, commits))
131 |
132 | pool = multiprocessing.Pool()
133 | pr_infos = list(
134 | tqdm(
135 | pool.imap(fetch_pr_info_from_commit_info, commit_infos),
136 | total=len(commit_infos),
137 | desc="Fetch PR info commits",
138 | )
139 | )
140 |
141 | prs = []
142 | unsorted_commits = []
143 |
144 | for commit_info, pr_info in zip(commit_infos, pr_infos):
145 | hexsha = commit_info.hexsha
146 | title = commit_info.title
147 | title = title.rstrip(".").strip() # Some PR end with an unnecessary period
148 | pr_number = commit_info.pr_number
149 |
150 | if pr_number is None:
151 | # Someone committed straight to main:
152 | summary = f"{title} [{hexsha[:7]}](https://github.com/{OWNER}/{REPO}/commit/{hexsha})"
153 | unsorted_commits.append(summary)
154 | else:
155 | # We prefer the PR title if available
156 | title = pr_info.pr_title if pr_info else title
157 | labels = pr_info.labels if pr_info else []
158 |
159 | if "exclude from changelog" in labels:
160 | continue
161 | if "typo" in labels:
162 | # We get so many typo PRs. Let's not flood the changelog with them.
163 | continue
164 |
165 | summary = f"{title} [#{pr_number}](https://github.com/{OWNER}/{REPO}/pull/{pr_number})"
166 |
167 | if INCLUDE_LABELS and 0 < len(labels):
168 | summary += f" ({', '.join(labels)})"
169 |
170 | if pr_info is not None:
171 | gh_user_name = pr_info.gh_user_name
172 | if gh_user_name not in OFFICIAL_RERUN_DEVS:
173 | summary += f" (thanks [@{gh_user_name}](https://github.com/{gh_user_name})!)"
174 |
175 | prs.append(summary)
176 |
177 | # Clean up:
178 | for i in range(len(prs)):
179 | line = prs[i]
180 | line = line[0].upper() + line[1:] # Upper-case first letter
181 | prs[i] = line
182 |
183 | print()
184 | print(f"Full diff at https://github.com/rerun-io/{REPO}/compare/{args.commit_range}")
185 | print()
186 | print_section("PRs", prs)
187 | print_section("Unsorted commits", unsorted_commits)
188 |
189 |
190 | if __name__ == "__main__":
191 | main()
192 |
--------------------------------------------------------------------------------
/scripts/template_update.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | # Copied from https://github.com/rerun-io/rerun_template
3 |
4 | """
5 | The script has two purposes.
6 |
7 | After using `rerun_template` as a template, run this to clean out things you don't need.
8 | Use `scripts/template_update.py init --languages cpp,rust,python` for this.
9 |
10 | Update an existing repository with the latest changes from the template.
11 | Use `scripts/template_update.py update --languages cpp,rust,python` for this.
12 |
13 | In either case, make sure the list of languages matches the languages you want to support.
14 | You can also use `--dry-run` to see what would happen without actually changing anything.
15 | """
16 |
17 | from __future__ import annotations
18 |
19 | import argparse
20 | import os
21 | import shutil
22 | import tempfile
23 |
24 | from git import Repo # pip install GitPython
25 |
26 | OWNER = "rerun-io"
27 |
28 | # Don't overwrite these when updating existing repository from the template
29 | DO_NOT_OVERWRITE = {
30 | "Cargo.lock",
31 | "CHANGELOG.md",
32 | "main.py",
33 | "pixi.lock",
34 | "README.md",
35 | "requirements.txt",
36 | }
37 |
38 | # Files required by C++, but not by _both_ Python and Rust
39 | CPP_FILES = {
40 | ".clang-format",
41 | ".github/workflows/cpp.yml",
42 | "CMakeLists.txt",
43 | "pixi.lock", # Pixi is only C++ & Python - For Rust we only use cargo
44 | "pixi.toml", # Pixi is only C++ & Python - For Rust we only use cargo
45 | "src/",
46 | "src/main.cpp",
47 | }
48 |
49 | # Files required by Python, but not by _both_ C++ and Rust
50 | PYTHON_FILES = {
51 | ".github/workflows/python.yml",
52 | ".mypy.ini",
53 | "main.py",
54 | "pixi.lock", # Pixi is only C++ & Python - For Rust we only use cargo
55 | "pixi.toml", # Pixi is only C++ & Python - For Rust we only use cargo
56 | "pyproject.toml",
57 | "requirements.txt",
58 | }
59 |
60 | # Files required by Rust, but not by _both_ C++ and Python
61 | RUST_FILES = {
62 | ".github/workflows/rust.yml",
63 | "bacon.toml",
64 | "Cargo.lock",
65 | "Cargo.toml",
66 | "CHANGELOG.md", # We only keep a changelog for Rust crates at the moment
67 | "clippy.toml",
68 | "Cranky.toml",
69 | "deny.toml",
70 | "rust-toolchain",
71 | "scripts/clippy_wasm/",
72 | "scripts/clippy_wasm/clippy.toml",
73 | "scripts/generate_changelog.py", # We only keep a changelog for Rust crates at the moment
74 | "src/",
75 | "src/lib.rs",
76 | "src/main.rs",
77 | }
78 |
79 | # Files we used to have, but have been removed in never version of rerun_template
80 | DEAD_FILES = ["bacon.toml", "Cranky.toml"]
81 |
82 |
83 | def parse_languages(lang_str: str) -> set[str]:
84 | languages = lang_str.split(",") if lang_str else []
85 | for lang in languages:
86 | assert lang in ["cpp", "python", "rust"], f"Unsupported language: {lang}"
87 | return set(languages)
88 |
89 |
90 | def calc_deny_set(languages: set[str]) -> set[str]:
91 | """The set of files to delete/ignore."""
92 | files_to_delete = CPP_FILES | PYTHON_FILES | RUST_FILES
93 | if "cpp" in languages:
94 | files_to_delete -= CPP_FILES
95 | if "python" in languages:
96 | files_to_delete -= PYTHON_FILES
97 | if "rust" in languages:
98 | files_to_delete -= RUST_FILES
99 | return files_to_delete
100 |
101 |
102 | def init(languages: set[str], dry_run: bool) -> None:
103 | print("Removing all language-specific files not needed for languages {languages}.")
104 | files_to_delete = calc_deny_set(languages)
105 | delete_files_and_folder(files_to_delete, dry_run)
106 |
107 |
108 | def remove_file(filepath: str) -> None:
109 | try:
110 | os.remove(filepath)
111 | except FileNotFoundError:
112 | pass
113 |
114 |
115 | def delete_files_and_folder(paths: set[str], dry_run: bool) -> None:
116 | repo_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
117 | for path in paths:
118 | full_path = os.path.join(repo_path, path)
119 | if os.path.exists(full_path):
120 | if os.path.isfile(full_path):
121 | print(f"Removing file {full_path}…")
122 | if not dry_run:
123 | remove_file(full_path)
124 | elif os.path.isdir(full_path):
125 | print(f"Removing folder {full_path}…")
126 | if not dry_run:
127 | shutil.rmtree(full_path)
128 |
129 |
130 | def update(languages: set[str], dry_run: bool) -> None:
131 | for file in DEAD_FILES:
132 | print(f"Removing dead file {file}…")
133 | if not dry_run:
134 | remove_file(file)
135 |
136 | files_to_ignore = calc_deny_set(languages) | DO_NOT_OVERWRITE
137 | repo_path = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
138 |
139 | with tempfile.TemporaryDirectory() as temp_dir:
140 | Repo.clone_from("https://github.com/rerun-io/rerun_template.git", temp_dir)
141 | for root, dirs, files in os.walk(temp_dir):
142 | for file in files:
143 | src_path = os.path.join(root, file)
144 | rel_path = os.path.relpath(src_path, temp_dir)
145 |
146 | if rel_path.startswith(".git/"):
147 | continue
148 | if rel_path.startswith("src/"):
149 | continue
150 | if rel_path in files_to_ignore:
151 | continue
152 |
153 | dest_path = os.path.join(repo_path, rel_path)
154 |
155 | print(f"Updating {rel_path}…")
156 | if not dry_run:
157 | os.makedirs(os.path.dirname(dest_path), exist_ok=True)
158 | shutil.copy2(src_path, dest_path)
159 |
160 |
161 | def main() -> None:
162 | parser = argparse.ArgumentParser(description="Handle the Rerun template.")
163 | subparsers = parser.add_subparsers(dest="command")
164 |
165 | init_parser = subparsers.add_parser("init", help="Initialize a new checkout of the template.")
166 | init_parser.add_argument(
167 | "--languages", default="", nargs="?", const="", help="The languages to support (e.g. `cpp,python,rust`)."
168 | )
169 | init_parser.add_argument("--dry-run", action="store_true", help="Don't actually delete any files.")
170 |
171 | update_parser = subparsers.add_parser(
172 | "update", help="Update all existing Rerun repositories with the latest changes from the template"
173 | )
174 | update_parser.add_argument(
175 | "--languages", default="", nargs="?", const="", help="The languages to support (e.g. `cpp,python,rust`)."
176 | )
177 | update_parser.add_argument("--dry-run", action="store_true", help="Don't actually delete any files.")
178 |
179 | args = parser.parse_args()
180 |
181 | if args.command == "init":
182 | init(parse_languages(args.languages), args.dry_run)
183 | elif args.command == "update":
184 | update(parse_languages(args.languages), args.dry_run)
185 | else:
186 | parser.print_help()
187 | exit(1)
188 |
189 |
190 | if __name__ == "__main__":
191 | main()
192 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | authors = ["rerun.io "]
3 | categories = [] # TODO: fill in if you plan on publishing the crate
4 | description = "" # TODO: fill in if you plan on publishing the crate
5 | edition = "2021"
6 | homepage = "https://github.com/rerun-io/rerun-ros"
7 | include = ["LICENSE-APACHE", "LICENSE-MIT", "**/*.rs", "Cargo.toml"]
8 | keywords = [] # TODO: fill in if you plan on publishing the crate
9 | license = "MIT OR Apache-2.0"
10 | name = "rerun_ros"
11 | publish = false # TODO: set to `true` if you plan on publishing the crate
12 | readme = "README.md"
13 | repository = "https://github.com/rerun-io/rerun-ros"
14 | rust-version = "1.76"
15 | version = "0.1.0"
16 |
17 | [package.metadata.docs.rs]
18 | all-features = true
19 | targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"]
20 |
21 |
22 | [features]
23 | default = []
24 |
25 |
26 | [dev-dependencies]
27 |
28 |
29 | [patch.crates-io]
30 |
31 |
32 | [lints]
33 | workspace = true
34 |
35 |
36 | [workspace.lints.rust]
37 | unsafe_code = "allow"
38 |
39 | elided_lifetimes_in_paths = "warn"
40 | future_incompatible = "warn"
41 | nonstandard_style = "warn"
42 | rust_2018_idioms = "warn"
43 | rust_2021_prelude_collisions = "warn"
44 | semicolon_in_expressions_from_macros = "warn"
45 | trivial_numeric_casts = "warn"
46 | unsafe_op_in_unsafe_fn = "warn" # `unsafe_op_in_unsafe_fn` may become the default in future Rust versions: https://github.com/rust-lang/rust/issues/71668
47 | unused_extern_crates = "warn"
48 | unused_import_braces = "warn"
49 | unused_lifetimes = "warn"
50 |
51 | trivial_casts = "allow"
52 | unused_qualifications = "allow"
53 |
54 | [workspace.lints.rustdoc]
55 | all = "warn"
56 | missing_crate_level_docs = "warn"
57 |
58 | # See also clippy.toml
59 | [workspace.lints.clippy]
60 | as_ptr_cast_mut = "warn"
61 | await_holding_lock = "warn"
62 | bool_to_int_with_if = "warn"
63 | char_lit_as_u8 = "warn"
64 | checked_conversions = "warn"
65 | clear_with_drain = "warn"
66 | cloned_instead_of_copied = "warn"
67 | dbg_macro = "warn"
68 | debug_assert_with_mut_call = "warn"
69 | derive_partial_eq_without_eq = "warn"
70 | disallowed_macros = "warn" # See clippy.toml
71 | disallowed_methods = "warn" # See clippy.toml
72 | disallowed_names = "warn" # See clippy.toml
73 | disallowed_script_idents = "warn" # See clippy.toml
74 | disallowed_types = "warn" # See clippy.toml
75 | doc_link_with_quotes = "warn"
76 | doc_markdown = "warn"
77 | empty_enum = "warn"
78 | enum_glob_use = "warn"
79 | equatable_if_let = "warn"
80 | exit = "warn"
81 | expl_impl_clone_on_copy = "warn"
82 | explicit_deref_methods = "warn"
83 | explicit_into_iter_loop = "warn"
84 | explicit_iter_loop = "warn"
85 | fallible_impl_from = "warn"
86 | filter_map_next = "warn"
87 | flat_map_option = "warn"
88 | float_cmp_const = "warn"
89 | fn_params_excessive_bools = "warn"
90 | fn_to_numeric_cast_any = "warn"
91 | from_iter_instead_of_collect = "warn"
92 | get_unwrap = "warn"
93 | if_let_mutex = "warn"
94 | implicit_clone = "warn"
95 | imprecise_flops = "warn"
96 | index_refutable_slice = "warn"
97 | inefficient_to_string = "warn"
98 | infinite_loop = "warn"
99 | into_iter_without_iter = "warn"
100 | invalid_upcast_comparisons = "warn"
101 | iter_not_returning_iterator = "warn"
102 | iter_on_empty_collections = "warn"
103 | iter_on_single_items = "warn"
104 | iter_over_hash_type = "warn"
105 | iter_without_into_iter = "warn"
106 | large_digit_groups = "warn"
107 | large_include_file = "warn"
108 | large_stack_arrays = "warn"
109 | large_stack_frames = "warn"
110 | large_types_passed_by_value = "warn"
111 | let_underscore_untyped = "warn"
112 | let_unit_value = "warn"
113 | linkedlist = "warn"
114 | lossy_float_literal = "warn"
115 | macro_use_imports = "warn"
116 | manual_assert = "warn"
117 | manual_clamp = "warn"
118 | manual_instant_elapsed = "warn"
119 | manual_let_else = "warn"
120 | manual_ok_or = "warn"
121 | manual_string_new = "warn"
122 | map_err_ignore = "warn"
123 | map_flatten = "warn"
124 | map_unwrap_or = "warn"
125 | match_on_vec_items = "warn"
126 | match_same_arms = "warn"
127 | match_wild_err_arm = "warn"
128 | match_wildcard_for_single_variants = "warn"
129 | mem_forget = "warn"
130 | mismatched_target_os = "warn"
131 | mismatching_type_param_order = "warn"
132 | missing_assert_message = "warn"
133 | missing_enforced_import_renames = "warn"
134 | missing_errors_doc = "warn"
135 | missing_safety_doc = "warn"
136 | mut_mut = "warn"
137 | mutex_integer = "warn"
138 | needless_borrow = "warn"
139 | needless_continue = "warn"
140 | needless_for_each = "warn"
141 | needless_pass_by_ref_mut = "warn"
142 | needless_pass_by_value = "warn"
143 | negative_feature_names = "warn"
144 | nonstandard_macro_braces = "warn"
145 | option_option = "warn"
146 | path_buf_push_overwrite = "warn"
147 | ptr_as_ptr = "warn"
148 | ptr_cast_constness = "warn"
149 | pub_without_shorthand = "warn"
150 | rc_mutex = "warn"
151 | readonly_write_lock = "warn"
152 | redundant_type_annotations = "warn"
153 | ref_option_ref = "warn"
154 | rest_pat_in_fully_bound_structs = "warn"
155 | same_functions_in_if_condition = "warn"
156 | semicolon_if_nothing_returned = "warn"
157 | should_panic_without_expect = "warn"
158 | significant_drop_tightening = "warn"
159 | single_match_else = "warn"
160 | str_to_string = "warn"
161 | string_add = "warn"
162 | string_add_assign = "warn"
163 | string_lit_as_bytes = "warn"
164 | string_lit_chars_any = "warn"
165 | string_to_string = "warn"
166 | suspicious_command_arg_space = "warn"
167 | suspicious_xor_used_as_pow = "warn"
168 | todo = "warn"
169 | too_many_lines = "warn"
170 | trailing_empty_array = "warn"
171 | trait_duplication_in_bounds = "warn"
172 | tuple_array_conversions = "warn"
173 | unchecked_duration_subtraction = "warn"
174 | undocumented_unsafe_blocks = "warn"
175 | unimplemented = "warn"
176 | uninhabited_references = "warn"
177 | uninlined_format_args = "warn"
178 | unnecessary_box_returns = "warn"
179 | unnecessary_safety_doc = "warn"
180 | unnecessary_struct_initialization = "warn"
181 | unnecessary_wraps = "warn"
182 | unnested_or_patterns = "warn"
183 | unused_peekable = "warn"
184 | unused_rounding = "warn"
185 | unused_self = "warn"
186 | unwrap_used = "warn"
187 | use_self = "warn"
188 | useless_transmute = "warn"
189 | verbose_file_reads = "warn"
190 | wildcard_dependencies = "warn"
191 | wildcard_imports = "warn"
192 | zero_sized_map_values = "warn"
193 |
194 | manual_range_contains = "allow" # this one is just worse imho
195 | ref_patterns = "allow" # It's nice to avoid ref pattern, but there are some situations that are hard (impossible?) to express without.
196 |
197 | [dependencies]
198 | ament_rs = "0.2.1"
199 | anyhow = "1.0.86"
200 | cdr = "0.2.4"
201 | clap = { version = "4.5.17", features = ["derive"] }
202 | log = "0.4.22"
203 | rclrs = { git = "https://github.com/esteve/ros2_rust.git", branch = "generic-subscriptions" }
204 | regex = "1.10.6"
205 | rosidl_runtime_rs = { git = "https://github.com/esteve/ros2_rust.git", branch = "generic-subscriptions" }
206 | serde = { version = "1.0.210", features = ["derive"] }
207 | serde_derive = "1.0.210"
208 | tempfile = "3.12.0"
209 | toml = "0.8.19"
210 | widestring = "1.1.0"
211 |
--------------------------------------------------------------------------------
/src/ros_introspection/type.rs:
--------------------------------------------------------------------------------
1 | use std::collections::hash_map::DefaultHasher;
2 | use std::hash::{Hash, Hasher};
3 |
4 | use anyhow::{anyhow, Error, Result};
5 |
6 | #[derive(Debug, Clone)]
7 | pub struct Type {
8 | base_name: String,
9 | pkg_name: String,
10 | msg_name: String,
11 | id: BuiltinType,
12 | hash: u64,
13 | }
14 |
15 | impl Type {
16 | /// Creates a new `Type` instance with the given name and parent package name.
17 | ///
18 | /// # Arguments
19 | ///
20 | /// * `name` - A string slice that holds the name of the type.
21 | /// * `parent_pkg_name` - A string slice that holds the name of the parent package.
22 | ///
23 | /// # Returns
24 | ///
25 | /// * `Result` - A result containing the new `Type` instance or an error.
26 | ///
27 | /// # Errors
28 | ///
29 | /// This function will return an error if:
30 | /// - The regular expression for parsing the message datatype fails to compile.
31 | /// - The message name cannot be extracted from the given name.
32 | pub fn new_with_parent_package(name: &str, parent_pkg_name: &str) -> Result {
33 | let msg_datatype_regex =
34 | regex::Regex::new(r"([a-zA-Z][a-zA-Z0-9_]+)/(msg/)?([a-zA-Z][a-zA-Z0-9_]+)")?;
35 |
36 | let (pkg_name, msg_name, id) = {
37 | let id = to_builtin_type(name);
38 |
39 | if let Some(what) = msg_datatype_regex.captures(name) {
40 | let pkg_name = what
41 | .get(1)
42 | .ok_or(anyhow!("Could not extract message name from {}", name))?
43 | .as_str()
44 | .to_owned();
45 |
46 | let msg_name = what
47 | .get(3)
48 | .ok_or(anyhow!("Could not extract message name from {}", name))?
49 | .as_str()
50 | .to_owned();
51 | (pkg_name, msg_name, id)
52 | } else if id == BuiltinType::Other {
53 | (parent_pkg_name.to_owned(), name.to_owned(), id)
54 | } else {
55 | (String::default(), name.to_owned(), id)
56 | }
57 | };
58 |
59 | let hash = calculate_hash(name);
60 |
61 | Ok(Self {
62 | base_name: name.to_owned(),
63 | pkg_name,
64 | msg_name,
65 | id,
66 | hash,
67 | })
68 | }
69 |
70 | /// Creates a new `Type` instance with the given name.
71 | ///
72 | /// # Arguments
73 | ///
74 | /// * `name` - A string slice that holds the name of the type.
75 | ///
76 | /// # Returns
77 | ///
78 | /// * `Result` - A result containing the new `Type` instance or an error.
79 | ///
80 | /// # Errors
81 | ///
82 | /// This function will return an error if the message name cannot be extracted from the given name.
83 | pub fn new(name: &str) -> Result {
84 | Self::new_with_parent_package(name, "")
85 | }
86 |
87 | /// Returns the package name of the type.
88 | ///
89 | /// # Returns
90 | ///
91 | /// * `&str` - A string slice that holds the package name of the type.
92 | pub fn pkg_name(&self) -> &str {
93 | &self.pkg_name
94 | }
95 |
96 | /// Returns the message name of the type.
97 | ///
98 | /// # Returns
99 | ///
100 | /// * `&str` - A string slice that holds the message name of the type.
101 | pub fn msg_name(&self) -> &str {
102 | &self.msg_name
103 | }
104 |
105 | /// Returns the ID of the type.
106 | ///
107 | /// # Returns
108 | ///
109 | /// * `&BuiltinType` - A reference to the `BuiltinType` of the type.
110 | pub fn id(&self) -> &BuiltinType {
111 | &self.id
112 | }
113 |
114 | /// Returns the base name of the type.
115 | ///
116 | /// # Returns
117 | ///
118 | /// * `&str` - A string slice that holds the base name of the type.
119 | pub fn name(&self) -> &str {
120 | &self.base_name
121 | }
122 | }
123 |
124 | impl PartialEq for Type {
125 | fn eq(&self, other: &Self) -> bool {
126 | self.hash == other.hash
127 | }
128 | }
129 |
130 | impl Eq for Type {}
131 |
132 | impl std::hash::Hash for Type {
133 | fn hash(&self, state: &mut H) {
134 | self.hash.hash(state);
135 | }
136 | }
137 |
138 | impl std::fmt::Display for Type {
139 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140 | write!(f, "{}", self.base_name)
141 | }
142 | }
143 |
144 | /// Calculates the hash value for a given string.
145 | ///
146 | /// # Arguments
147 | ///
148 | /// * `s` - A string slice that holds the input string.
149 | ///
150 | /// # Returns
151 | ///
152 | /// * `u64` - The hash value of the input string.
153 | fn calculate_hash(s: &str) -> u64 {
154 | let mut hasher = DefaultHasher::new();
155 | s.hash(&mut hasher);
156 | hasher.finish()
157 | }
158 |
159 | #[derive(Debug, Clone, PartialEq, Eq, Hash)]
160 | pub enum BuiltinType {
161 | Bool,
162 | Byte,
163 | Char,
164 | Float32,
165 | Float64,
166 | Int8,
167 | Uint8,
168 | Int16,
169 | Uint16,
170 | Int32,
171 | Uint32,
172 | Int64,
173 | Uint64,
174 | String,
175 | WString,
176 | Other,
177 | }
178 |
179 | /// Converts a string to a `BuiltinType`.
180 | ///
181 | /// # Arguments
182 | ///
183 | /// * `s` - A string slice that holds the input string.
184 | ///
185 | /// # Returns
186 | ///
187 | /// * `BuiltinType` - The corresponding `BuiltinType` for the input string.
188 | fn to_builtin_type(s: &str) -> BuiltinType {
189 | match s {
190 | "bool" => BuiltinType::Bool,
191 | "byte" => BuiltinType::Byte,
192 | "char" => BuiltinType::Char,
193 | "float32" => BuiltinType::Float32,
194 | "float64" => BuiltinType::Float64,
195 | "int8" => BuiltinType::Int8,
196 | "uint8" => BuiltinType::Uint8,
197 | "int16" => BuiltinType::Int16,
198 | "uint16" => BuiltinType::Uint16,
199 | "int32" => BuiltinType::Int32,
200 | "uint32" => BuiltinType::Uint32,
201 | "int64" => BuiltinType::Int64,
202 | "uint64" => BuiltinType::Uint64,
203 | "string" => BuiltinType::String,
204 | "wstring" => BuiltinType::WString,
205 | _ => BuiltinType::Other,
206 | }
207 | }
208 |
209 | #[cfg(test)]
210 | mod tests {
211 | use super::*;
212 |
213 | #[test]
214 | fn test_new() {
215 | let ros_type = Type::new("std_msgs/msg/String").unwrap();
216 | assert_eq!(ros_type.pkg_name(), "std_msgs");
217 | assert_eq!(ros_type.msg_name(), "String");
218 | assert_eq!(ros_type.id(), &BuiltinType::Other);
219 | assert_eq!(ros_type.name(), "std_msgs/msg/String");
220 |
221 | let ros_type = Type::new("std_msgs/String").unwrap();
222 | assert_eq!(ros_type.pkg_name(), "std_msgs");
223 | assert_eq!(ros_type.msg_name(), "String");
224 | assert_eq!(ros_type.id(), &BuiltinType::Other);
225 | assert_eq!(ros_type.name(), "std_msgs/String");
226 |
227 | let ros_type = Type::new("String").unwrap();
228 | assert_eq!(ros_type.pkg_name(), "");
229 | assert_eq!(ros_type.msg_name(), "String");
230 | assert_eq!(ros_type.id(), &BuiltinType::Other);
231 | assert_eq!(ros_type.name(), "String");
232 |
233 | let ros_type = Type::new("unknown_type").unwrap();
234 | assert_eq!(ros_type.pkg_name(), "");
235 | assert_eq!(ros_type.msg_name(), "unknown_type");
236 | assert_eq!(ros_type.id(), &BuiltinType::Other);
237 | assert_eq!(ros_type.name(), "unknown_type");
238 |
239 | let ros_type = Type::new("int32").unwrap();
240 | assert_eq!(ros_type.pkg_name(), "");
241 | assert_eq!(ros_type.msg_name(), "int32");
242 | assert_eq!(ros_type.id(), &BuiltinType::Int32);
243 | assert_eq!(ros_type.name(), "int32");
244 | }
245 |
246 | #[test]
247 | fn test_getters() {
248 | let ros_type = Type::new("std_msgs/msg/String").unwrap();
249 |
250 | assert_eq!(ros_type.pkg_name(), "std_msgs");
251 | assert_eq!(ros_type.msg_name(), "String");
252 | assert_eq!(ros_type.id(), &BuiltinType::Other);
253 | assert_eq!(ros_type.name(), "std_msgs/msg/String");
254 | }
255 |
256 | #[test]
257 | fn test_partial_eq() {
258 | let ros_type1 = Type::new("std_msgs/msg/String").unwrap();
259 | let ros_type2 = Type::new("std_msgs/msg/String").unwrap();
260 | let ros_type3 = Type::new("std_msgs/msg/Int32").unwrap();
261 |
262 | assert_eq!(ros_type1, ros_type2);
263 | assert_ne!(ros_type1, ros_type3);
264 | }
265 |
266 | #[test]
267 | fn test_hash() {
268 | let ros_type = Type::new("std_msgs/msg/String").unwrap();
269 | let expected_hash = calculate_hash("std_msgs/msg/String");
270 |
271 | assert_eq!(ros_type.hash, expected_hash);
272 | }
273 |
274 | #[test]
275 | fn test_display() {
276 | let ros_type = Type::new("std_msgs/msg/String").unwrap();
277 | assert_eq!(format!("{ros_type}"), "std_msgs/msg/String");
278 | }
279 | }
280 |
--------------------------------------------------------------------------------
/src/ros_introspection/field.rs:
--------------------------------------------------------------------------------
1 | use crate::ros_introspection::Type;
2 | use anyhow::Result;
3 | use regex::Regex;
4 | use std::str::FromStr;
5 |
6 | #[derive(Debug, Clone)]
7 | pub struct Field {
8 | fieldname: String,
9 | field_type: Type,
10 | is_array: bool,
11 | array_size: isize,
12 | is_constant: bool,
13 | value: String,
14 | }
15 |
16 | impl Field {
17 | /// Creates a new `Field` instance with the given type and name.
18 | ///
19 | /// # Arguments
20 | ///
21 | /// * `field_type` - The `Type` of the field.
22 | /// * `name` - A string slice that holds the name of the field.
23 | ///
24 | /// # Returns
25 | ///
26 | /// * `Self` - The new `Field` instance.
27 | pub fn new_with_type(field_type: Type, name: &str) -> Self {
28 | Self {
29 | fieldname: name.to_owned(),
30 | field_type,
31 | is_array: false,
32 | array_size: 1,
33 | is_constant: false,
34 | value: String::new(),
35 | }
36 | }
37 |
38 | /// Creates a new `Field` instance from a definition string.
39 | ///
40 | /// # Arguments
41 | ///
42 | /// * `definition` - A string slice that holds the definition of the field.
43 | ///
44 | /// # Returns
45 | ///
46 | /// * `Result` - A result containing the new `Field` instance or an error.
47 | ///
48 | /// # Errors
49 | ///
50 | /// This function will return an error if:
51 | /// - The regular expression for parsing the type, field, or array fails to compile.
52 | /// - The type, field, or array size cannot be extracted from the definition.
53 | /// - The array size is not a valid integer.
54 | pub fn new_with_definition(definition: &str) -> Result {
55 | let type_regex =
56 | Regex::new(r"[a-zA-Z][a-zA-Z0-9_]*(/[a-zA-Z][a-zA-Z0-9_]*){0,1}(\[[0-9]*\]){0,1}")?;
57 | let field_regex = Regex::new(r"[a-zA-Z][a-zA-Z0-9_]*")?;
58 | let array_regex = Regex::new(r"(.+)(\[(\d*)\])")?;
59 |
60 | let mut begin = definition;
61 |
62 | // Find type
63 | let mut type_ = if let Some(what) = type_regex.find(begin) {
64 | begin = &begin[what.end()..];
65 | what.as_str().to_owned()
66 | } else {
67 | return Err(anyhow::anyhow!("Bad type when parsing field: {definition}"));
68 | };
69 |
70 | // Find field
71 | let fieldname = if let Some(what) = field_regex.find(begin) {
72 | begin = &begin[what.end()..];
73 | what.as_str().to_owned()
74 | } else {
75 | return Err(anyhow::anyhow!(
76 | "Bad field when parsing field: {definition}"
77 | ));
78 | };
79 |
80 | // Find array size
81 | // Clone type_ to avoid borrowing issues
82 | let temp_type = type_.clone();
83 | let (is_array, array_size) = if let Some(what) = array_regex.captures(&temp_type) {
84 | type_ = what[1].to_string();
85 | if what.len() == 3 {
86 | (true, -1)
87 | } else if let Some(size) = what.get(3) {
88 | let array_size = if size.as_str().is_empty() {
89 | -1
90 | } else {
91 | isize::from_str(size.as_str())?
92 | };
93 | (true, array_size)
94 | } else {
95 | (true, -1)
96 | }
97 | } else {
98 | (false, 1)
99 | };
100 |
101 | // Find if constant or comment
102 | let (is_constant, value) = if let Some(what) = Regex::new(r"\S")?.find(begin) {
103 | if what.as_str() == "=" {
104 | begin = &begin[what.end()..];
105 | // Copy constant
106 | let value = if type_ == "string" {
107 | begin.to_owned()
108 | } else if let Some(what) = Regex::new(r"\s*#")?.find(begin) {
109 | begin[..what.start()].to_string()
110 | } else {
111 | begin.to_owned()
112 | }
113 | .trim()
114 | .to_owned();
115 | (true, value)
116 | } else if what.as_str() == "#" {
117 | // Ignore comment
118 | (false, String::default())
119 | } else {
120 | let value = if let Some(what) = Regex::new(r"\s*#")?.find(begin) {
121 | begin[..what.start()].to_string()
122 | } else {
123 | begin.to_owned()
124 | };
125 | (false, value)
126 | }
127 | } else {
128 | (false, String::default())
129 | };
130 |
131 | Ok(Self {
132 | fieldname,
133 | field_type: Type::new(type_.as_str())?,
134 | is_array,
135 | array_size,
136 | is_constant,
137 | value,
138 | })
139 | }
140 |
141 | /// Returns a reference to the type of the field.
142 | ///
143 | /// # Returns
144 | ///
145 | /// * `&Type` - A reference to the `Type` of the field.
146 | pub fn type_(&self) -> &Type {
147 | &self.field_type
148 | }
149 |
150 | /// Changes the type of the field.
151 | ///
152 | /// # Arguments
153 | ///
154 | /// * `new_type` - The new `Type` of the field.
155 | pub fn change_type(&mut self, new_type: Type) {
156 | self.field_type = new_type;
157 | }
158 |
159 | /// Returns the name of the field.
160 | ///
161 | /// # Returns
162 | ///
163 | /// * `&str` - A string slice that holds the name of the field.
164 | pub fn name(&self) -> &str {
165 | &self.fieldname
166 | }
167 |
168 | /// Returns whether the field is an array.
169 | ///
170 | /// # Returns
171 | ///
172 | /// * `bool` - `true` if the field is an array, `false` otherwise.
173 | pub fn is_array(&self) -> bool {
174 | self.is_array
175 | }
176 |
177 | /// Returns whether the field is a constant.
178 | ///
179 | /// # Returns
180 | ///
181 | /// * `bool` - `true` if the field is a constant, `false` otherwise.
182 | pub fn is_constant(&self) -> bool {
183 | self.is_constant
184 | }
185 |
186 | /// Returns the array size of the field.
187 | ///
188 | /// # Returns
189 | ///
190 | /// * `isize` - The array size of the field.
191 | pub fn array_size(&self) -> isize {
192 | self.array_size
193 | }
194 |
195 | /// Returns the value of the field.
196 | ///
197 | /// # Returns
198 | ///
199 | /// * `&str` - A string slice that holds the value of the field.
200 | pub fn value(&self) -> &str {
201 | &self.value
202 | }
203 | }
204 |
205 | #[cfg(test)]
206 | mod tests {
207 | use super::*;
208 | use crate::ros_introspection::Type;
209 |
210 | #[test]
211 | fn test_new_with_type() {
212 | let field_type = Type::new("int32").unwrap();
213 | let field = Field::new_with_type(field_type.clone(), "test_field");
214 |
215 | assert_eq!(field.fieldname, "test_field");
216 | assert_eq!(field.field_type, field_type);
217 | assert!(!field.is_array);
218 | assert_eq!(field.array_size, 1);
219 | assert!(!field.is_constant);
220 | assert_eq!(field.value, "");
221 | }
222 |
223 | #[test]
224 | fn test_new_with_definition() {
225 | let field = Field::new_with_definition("int32 test_field").unwrap();
226 | assert_eq!(field.fieldname, "test_field");
227 | assert_eq!(field.field_type, Type::new("int32").unwrap());
228 | assert!(!field.is_array);
229 | assert_eq!(field.array_size, 1);
230 | assert!(!field.is_constant);
231 | assert_eq!(field.value, "");
232 |
233 | let field = Field::new_with_definition("string[10] test_array").unwrap();
234 | assert_eq!(field.fieldname, "test_array");
235 | assert_eq!(field.field_type, Type::new("string").unwrap());
236 | assert!(field.is_array);
237 | assert_eq!(field.array_size, 10);
238 | assert!(!field.is_constant);
239 | assert_eq!(field.value, "");
240 |
241 | let field = Field::new_with_definition("float64 PI = 3.14159").unwrap();
242 | assert_eq!(field.fieldname, "PI");
243 | assert_eq!(field.field_type, Type::new("float64").unwrap());
244 | assert!(!field.is_array);
245 | assert_eq!(field.array_size, 1);
246 | assert!(field.is_constant);
247 | assert_eq!(field.value, "3.14159");
248 | }
249 |
250 | #[test]
251 | fn test_getters() {
252 | let field = Field::new_with_type(Type::new("int32").unwrap(), "test_field");
253 |
254 | assert_eq!(field.type_(), &Type::new("int32").unwrap());
255 | assert_eq!(field.name(), "test_field");
256 | assert!(!field.is_array());
257 | assert!(!field.is_constant());
258 | assert_eq!(field.array_size(), 1);
259 | assert_eq!(field.value(), "");
260 | }
261 |
262 | #[test]
263 | fn test_change_type() {
264 | let mut field = Field::new_with_type(Type::new("int32").unwrap(), "test_field");
265 | let new_type = Type::new("float64").unwrap();
266 | field.change_type(new_type.clone());
267 |
268 | assert_eq!(field.type_(), &new_type);
269 | }
270 | }
271 |
--------------------------------------------------------------------------------
/src/ros_introspection/message.rs:
--------------------------------------------------------------------------------
1 | use std::sync::Arc;
2 |
3 | use anyhow::{anyhow, Error};
4 | use regex::Regex;
5 |
6 | use crate::ros_introspection::Field;
7 | use crate::ros_introspection::Type;
8 |
9 | #[derive(Debug, Clone)]
10 | pub struct Message {
11 | msg_type: Type,
12 | fields: Vec,
13 | }
14 |
15 | impl Message {
16 | /// Creates a new `Message` instance from a definition string.
17 | ///
18 | /// # Arguments
19 | ///
20 | /// * `def` - A string slice that holds the definition of the message.
21 | ///
22 | /// # Returns
23 | ///
24 | /// * `Result` - A result containing the new `Message` instance or an error.
25 | ///
26 | /// # Errors
27 | ///
28 | /// This function will return an error if:
29 | /// - The regular expression for parsing the message definition fails to compile.
30 | /// - The message type cannot be extracted from the definition.
31 | /// - A field cannot be created from the definition.
32 | pub fn new(def: &str) -> Result {
33 | let mut msg_type = Type::new("")?;
34 | let mut fields = Vec::new();
35 |
36 | let re = Regex::new(r"(^\s*$|^\s*#)")?;
37 |
38 | let lines: Vec<&str> = def.lines().collect();
39 | for line in lines {
40 | if re.is_match(line) {
41 | continue;
42 | }
43 |
44 | let line = line.trim();
45 |
46 | if line.starts_with("MSG:") {
47 | let line = &line[("MSG:".len() + 1)..];
48 | msg_type = Type::new(line)?;
49 | } else {
50 | let new_field = Field::new_with_definition(line)?;
51 | fields.push(new_field);
52 | }
53 | }
54 |
55 | Ok(Self { msg_type, fields })
56 | }
57 |
58 | /// Returns a reference to the type of the message.
59 | ///
60 | /// # Returns
61 | ///
62 | /// * `&Type` - A reference to the `Type` of the message.
63 | pub fn type_(&self) -> &Type {
64 | &self.msg_type
65 | }
66 |
67 | /// Sets the type of the message.
68 | ///
69 | /// # Arguments
70 | ///
71 | /// * `new_type` - The new `Type` of the message.
72 | pub fn set_type(&mut self, new_type: Type) {
73 | self.msg_type = new_type;
74 | }
75 |
76 | /// Returns a reference to the fields of the message.
77 | ///
78 | /// # Returns
79 | ///
80 | /// * `&Vec` - A reference to the vector of `Field` instances.
81 | pub fn fields(&self) -> &Vec {
82 | &self.fields
83 | }
84 |
85 | /// Returns a mutable reference to the fields of the message.
86 | ///
87 | /// # Returns
88 | ///
89 | /// * `&mut Vec` - A mutable reference to the vector of `Field` instances.
90 | pub fn fields_mut(&mut self) -> &mut Vec {
91 | &mut self.fields
92 | }
93 | }
94 |
95 | /// Splits a string containing multiple message definitions into individual message definitions.
96 | ///
97 | /// # Arguments
98 | ///
99 | /// * `multi_def` - A string slice that holds the multiple message definitions.
100 | ///
101 | /// # Returns
102 | ///
103 | /// * `Vec` - A vector containing individual message definitions.
104 | fn split_multiple_message_definitions(multi_def: &str) -> Vec {
105 | let mut parts = Vec::new();
106 | let mut part = String::new();
107 |
108 | for line in multi_def.lines() {
109 | let line = line.trim();
110 | if line.starts_with("========") {
111 | parts.push(part.clone());
112 | part.clear();
113 | } else {
114 | part.push_str(line);
115 | part.push('\n');
116 | }
117 | }
118 | parts.push(part);
119 |
120 | parts
121 | }
122 |
123 | /// Parses multiple message definitions and returns a vector of `Message` instances.
124 | ///
125 | /// # Arguments
126 | ///
127 | /// * `multi_def` - A string slice that holds the multiple message definitions.
128 | /// * `root_type` - A reference to the root `Type`.
129 | ///
130 | /// # Returns
131 | ///
132 | /// * `Result>, Error>` - A result containing a vector of `Message` instances or an error.
133 | ///
134 | /// # Errors
135 | ///
136 | /// This function will return an error if:
137 | /// - The message type is invalid.
138 | /// - A mutable reference to a message cannot be obtained.
139 | /// - The message type is unspecified.
140 | pub fn parse_message_definitions(
141 | multi_def: &str,
142 | root_type: &Type,
143 | ) -> Result>, Error> {
144 | let parts = split_multiple_message_definitions(multi_def);
145 | let mut known_type = Vec::new();
146 | let mut parsed_msgs = Vec::new();
147 |
148 | let no_type = Type::new("")?;
149 |
150 | for i in (0..parts.len()).rev() {
151 | let mut msg = Arc::new(Message::new(&parts[i])?);
152 |
153 | if i == 0 {
154 | if msg.type_() == &no_type && root_type != &no_type {
155 | Arc::get_mut(&mut msg)
156 | .ok_or(anyhow!("Could not get mutable reference to message",))?
157 | .set_type(root_type.clone());
158 | } else if msg.type_() == &no_type && root_type == &no_type {
159 | panic!("Message type unspecified");
160 | }
161 | }
162 |
163 | parsed_msgs.push(msg.clone());
164 | known_type.push(msg.type_().clone());
165 | }
166 |
167 | for msg in &mut parsed_msgs {
168 | let msg =
169 | Arc::get_mut(msg).ok_or(anyhow!("Could not get mutable reference to message",))?;
170 | for field in msg.fields_mut() {
171 | if field.type_().pkg_name().is_empty() {
172 | let mut guessed_type = Vec::new();
173 |
174 | for known_type in &known_type {
175 | if field.type_().msg_name() == known_type.msg_name() {
176 | guessed_type.push(known_type.clone());
177 | }
178 | }
179 |
180 | if !guessed_type.is_empty() {
181 | let mut better_match = false;
182 |
183 | for guessed_type in &guessed_type {
184 | if guessed_type.pkg_name() == root_type.pkg_name() {
185 | field.change_type(guessed_type.clone());
186 | better_match = true;
187 | break;
188 | }
189 | }
190 |
191 | if !better_match {
192 | field.change_type(guessed_type[0].clone());
193 | }
194 | }
195 | }
196 | }
197 | }
198 |
199 | parsed_msgs.reverse();
200 | Ok(parsed_msgs)
201 | }
202 |
203 | #[cfg(test)]
204 | mod tests {
205 | use super::*;
206 | use crate::ros_introspection::Type;
207 |
208 | #[test]
209 | fn test_new() -> Result<(), Error> {
210 | let def = r#"
211 | MSG: std_msgs/String
212 | string data
213 | "#;
214 | let msg = Message::new(def)?;
215 |
216 | assert_eq!(msg.type_().name(), "std_msgs/String");
217 | assert_eq!(msg.fields().len(), 1);
218 | assert_eq!(msg.fields()[0].name(), "data");
219 | assert_eq!(msg.fields()[0].type_().name(), "string");
220 | Ok(())
221 | }
222 |
223 | #[test]
224 | fn test_getters() -> Result<(), Error> {
225 | let def = r#"
226 | MSG: std_msgs/String
227 | string data
228 | "#;
229 | let msg = Message::new(def)?;
230 |
231 | assert_eq!(msg.type_().name(), "std_msgs/String");
232 | assert_eq!(msg.fields().len(), 1);
233 | assert_eq!(msg.fields()[0].name(), "data");
234 | assert_eq!(msg.fields()[0].type_().name(), "string");
235 |
236 | Ok(())
237 | }
238 |
239 | #[test]
240 | fn test_set_type() {
241 | let def = r#"
242 | string data
243 | "#;
244 | let mut msg = Message::new(def).unwrap();
245 | let new_type = Type::new("std_msgs/String").unwrap();
246 | msg.set_type(new_type.clone());
247 |
248 | assert_eq!(msg.type_(), &new_type);
249 | }
250 |
251 | #[test]
252 | fn test_split_multiple_message_definitions() {
253 | let multi_def = r#"
254 | std_msgs/String
255 | string data
256 | ========
257 | std_msgs/Int32
258 | int32 data
259 | "#;
260 | let parts = split_multiple_message_definitions(multi_def);
261 |
262 | assert_eq!(parts.len(), 2);
263 | assert!(parts[0].contains("std_msgs/String"));
264 | assert!(parts[1].contains("std_msgs/Int32"));
265 | }
266 |
267 | #[test]
268 | fn test_parse_message_definitions() {
269 | let multi_def = r#"
270 | MSG: std_msgs/String
271 | string data
272 | ========
273 | MSG: std_msgs/Int32
274 | int32 data
275 | "#;
276 | let root_type = Type::new("std_msgs/String").unwrap();
277 | let msgs = parse_message_definitions(multi_def, &root_type).unwrap();
278 |
279 | assert_eq!(msgs.len(), 2);
280 | assert_eq!(msgs[0].type_().name(), "std_msgs/String");
281 | assert_eq!(msgs[0].fields().len(), 1);
282 | assert_eq!(msgs[0].fields()[0].name(), "data");
283 | assert_eq!(msgs[0].fields()[0].type_().name(), "string");
284 |
285 | assert_eq!(msgs[1].type_().name(), "std_msgs/Int32");
286 | assert_eq!(msgs[1].fields().len(), 1);
287 | assert_eq!(msgs[1].fields()[0].name(), "data");
288 | assert_eq!(msgs[1].fields()[0].type_().name(), "int32");
289 | }
290 | }
291 |
--------------------------------------------------------------------------------
/LICENSE-APACHE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/Cargo.lock:
--------------------------------------------------------------------------------
1 | # This file is automatically @generated by Cargo.
2 | # It is not intended for manual editing.
3 | version = 3
4 |
5 | [[package]]
6 | name = "aho-corasick"
7 | version = "1.1.3"
8 | source = "registry+https://github.com/rust-lang/crates.io-index"
9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
10 | dependencies = [
11 | "memchr",
12 | ]
13 |
14 | [[package]]
15 | name = "ament_rs"
16 | version = "0.2.1"
17 | source = "registry+https://github.com/rust-lang/crates.io-index"
18 | checksum = "b901da946b7b1620d44563e10c7634681af855a7f5fb59bd09b6eb801dcf6e49"
19 | dependencies = [
20 | "itertools",
21 | "walkdir",
22 | ]
23 |
24 | [[package]]
25 | name = "anstream"
26 | version = "0.6.15"
27 | source = "registry+https://github.com/rust-lang/crates.io-index"
28 | checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
29 | dependencies = [
30 | "anstyle",
31 | "anstyle-parse",
32 | "anstyle-query",
33 | "anstyle-wincon",
34 | "colorchoice",
35 | "is_terminal_polyfill",
36 | "utf8parse",
37 | ]
38 |
39 | [[package]]
40 | name = "anstyle"
41 | version = "1.0.8"
42 | source = "registry+https://github.com/rust-lang/crates.io-index"
43 | checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
44 |
45 | [[package]]
46 | name = "anstyle-parse"
47 | version = "0.2.5"
48 | source = "registry+https://github.com/rust-lang/crates.io-index"
49 | checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
50 | dependencies = [
51 | "utf8parse",
52 | ]
53 |
54 | [[package]]
55 | name = "anstyle-query"
56 | version = "1.1.1"
57 | source = "registry+https://github.com/rust-lang/crates.io-index"
58 | checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
59 | dependencies = [
60 | "windows-sys 0.52.0",
61 | ]
62 |
63 | [[package]]
64 | name = "anstyle-wincon"
65 | version = "3.0.4"
66 | source = "registry+https://github.com/rust-lang/crates.io-index"
67 | checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
68 | dependencies = [
69 | "anstyle",
70 | "windows-sys 0.52.0",
71 | ]
72 |
73 | [[package]]
74 | name = "anyhow"
75 | version = "1.0.87"
76 | source = "registry+https://github.com/rust-lang/crates.io-index"
77 | checksum = "10f00e1f6e58a40e807377c75c6a7f97bf9044fab57816f2414e6f5f4499d7b8"
78 |
79 | [[package]]
80 | name = "autocfg"
81 | version = "1.3.0"
82 | source = "registry+https://github.com/rust-lang/crates.io-index"
83 | checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
84 |
85 | [[package]]
86 | name = "bindgen"
87 | version = "0.66.1"
88 | source = "registry+https://github.com/rust-lang/crates.io-index"
89 | checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7"
90 | dependencies = [
91 | "bitflags",
92 | "cexpr",
93 | "clang-sys",
94 | "lazy_static",
95 | "lazycell",
96 | "log",
97 | "peeking_take_while",
98 | "prettyplease",
99 | "proc-macro2",
100 | "quote",
101 | "regex",
102 | "rustc-hash",
103 | "shlex",
104 | "syn",
105 | "which",
106 | ]
107 |
108 | [[package]]
109 | name = "bitflags"
110 | version = "2.6.0"
111 | source = "registry+https://github.com/rust-lang/crates.io-index"
112 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
113 |
114 | [[package]]
115 | name = "byteorder"
116 | version = "1.5.0"
117 | source = "registry+https://github.com/rust-lang/crates.io-index"
118 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
119 |
120 | [[package]]
121 | name = "cdr"
122 | version = "0.2.4"
123 | source = "registry+https://github.com/rust-lang/crates.io-index"
124 | checksum = "9617422bf43fde9280707a7e90f8f7494389c182f5c70b0f67592d0f06d41dfa"
125 | dependencies = [
126 | "byteorder",
127 | "serde",
128 | ]
129 |
130 | [[package]]
131 | name = "cexpr"
132 | version = "0.6.0"
133 | source = "registry+https://github.com/rust-lang/crates.io-index"
134 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
135 | dependencies = [
136 | "nom",
137 | ]
138 |
139 | [[package]]
140 | name = "cfg-if"
141 | version = "1.0.0"
142 | source = "registry+https://github.com/rust-lang/crates.io-index"
143 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
144 |
145 | [[package]]
146 | name = "clang-sys"
147 | version = "1.8.1"
148 | source = "registry+https://github.com/rust-lang/crates.io-index"
149 | checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4"
150 | dependencies = [
151 | "glob",
152 | "libc",
153 | "libloading",
154 | ]
155 |
156 | [[package]]
157 | name = "clap"
158 | version = "4.5.17"
159 | source = "registry+https://github.com/rust-lang/crates.io-index"
160 | checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac"
161 | dependencies = [
162 | "clap_builder",
163 | "clap_derive",
164 | ]
165 |
166 | [[package]]
167 | name = "clap_builder"
168 | version = "4.5.17"
169 | source = "registry+https://github.com/rust-lang/crates.io-index"
170 | checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73"
171 | dependencies = [
172 | "anstream",
173 | "anstyle",
174 | "clap_lex",
175 | "strsim",
176 | ]
177 |
178 | [[package]]
179 | name = "clap_derive"
180 | version = "4.5.13"
181 | source = "registry+https://github.com/rust-lang/crates.io-index"
182 | checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0"
183 | dependencies = [
184 | "heck",
185 | "proc-macro2",
186 | "quote",
187 | "syn",
188 | ]
189 |
190 | [[package]]
191 | name = "clap_lex"
192 | version = "0.7.2"
193 | source = "registry+https://github.com/rust-lang/crates.io-index"
194 | checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97"
195 |
196 | [[package]]
197 | name = "colorchoice"
198 | version = "1.0.2"
199 | source = "registry+https://github.com/rust-lang/crates.io-index"
200 | checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
201 |
202 | [[package]]
203 | name = "either"
204 | version = "1.13.0"
205 | source = "registry+https://github.com/rust-lang/crates.io-index"
206 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
207 |
208 | [[package]]
209 | name = "equivalent"
210 | version = "1.0.1"
211 | source = "registry+https://github.com/rust-lang/crates.io-index"
212 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
213 |
214 | [[package]]
215 | name = "errno"
216 | version = "0.3.9"
217 | source = "registry+https://github.com/rust-lang/crates.io-index"
218 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
219 | dependencies = [
220 | "libc",
221 | "windows-sys 0.52.0",
222 | ]
223 |
224 | [[package]]
225 | name = "fastrand"
226 | version = "2.1.1"
227 | source = "registry+https://github.com/rust-lang/crates.io-index"
228 | checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
229 |
230 | [[package]]
231 | name = "futures"
232 | version = "0.3.30"
233 | source = "registry+https://github.com/rust-lang/crates.io-index"
234 | checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
235 | dependencies = [
236 | "futures-channel",
237 | "futures-core",
238 | "futures-executor",
239 | "futures-io",
240 | "futures-sink",
241 | "futures-task",
242 | "futures-util",
243 | ]
244 |
245 | [[package]]
246 | name = "futures-channel"
247 | version = "0.3.30"
248 | source = "registry+https://github.com/rust-lang/crates.io-index"
249 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
250 | dependencies = [
251 | "futures-core",
252 | "futures-sink",
253 | ]
254 |
255 | [[package]]
256 | name = "futures-core"
257 | version = "0.3.30"
258 | source = "registry+https://github.com/rust-lang/crates.io-index"
259 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
260 |
261 | [[package]]
262 | name = "futures-executor"
263 | version = "0.3.30"
264 | source = "registry+https://github.com/rust-lang/crates.io-index"
265 | checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
266 | dependencies = [
267 | "futures-core",
268 | "futures-task",
269 | "futures-util",
270 | ]
271 |
272 | [[package]]
273 | name = "futures-io"
274 | version = "0.3.30"
275 | source = "registry+https://github.com/rust-lang/crates.io-index"
276 | checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
277 |
278 | [[package]]
279 | name = "futures-macro"
280 | version = "0.3.30"
281 | source = "registry+https://github.com/rust-lang/crates.io-index"
282 | checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
283 | dependencies = [
284 | "proc-macro2",
285 | "quote",
286 | "syn",
287 | ]
288 |
289 | [[package]]
290 | name = "futures-sink"
291 | version = "0.3.30"
292 | source = "registry+https://github.com/rust-lang/crates.io-index"
293 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
294 |
295 | [[package]]
296 | name = "futures-task"
297 | version = "0.3.30"
298 | source = "registry+https://github.com/rust-lang/crates.io-index"
299 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
300 |
301 | [[package]]
302 | name = "futures-util"
303 | version = "0.3.30"
304 | source = "registry+https://github.com/rust-lang/crates.io-index"
305 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
306 | dependencies = [
307 | "futures-channel",
308 | "futures-core",
309 | "futures-io",
310 | "futures-macro",
311 | "futures-sink",
312 | "futures-task",
313 | "memchr",
314 | "pin-project-lite",
315 | "pin-utils",
316 | "slab",
317 | ]
318 |
319 | [[package]]
320 | name = "glob"
321 | version = "0.3.1"
322 | source = "registry+https://github.com/rust-lang/crates.io-index"
323 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
324 |
325 | [[package]]
326 | name = "hashbrown"
327 | version = "0.14.5"
328 | source = "registry+https://github.com/rust-lang/crates.io-index"
329 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
330 |
331 | [[package]]
332 | name = "heck"
333 | version = "0.5.0"
334 | source = "registry+https://github.com/rust-lang/crates.io-index"
335 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
336 |
337 | [[package]]
338 | name = "home"
339 | version = "0.5.9"
340 | source = "registry+https://github.com/rust-lang/crates.io-index"
341 | checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5"
342 | dependencies = [
343 | "windows-sys 0.52.0",
344 | ]
345 |
346 | [[package]]
347 | name = "indexmap"
348 | version = "2.5.0"
349 | source = "registry+https://github.com/rust-lang/crates.io-index"
350 | checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5"
351 | dependencies = [
352 | "equivalent",
353 | "hashbrown",
354 | ]
355 |
356 | [[package]]
357 | name = "is_terminal_polyfill"
358 | version = "1.70.1"
359 | source = "registry+https://github.com/rust-lang/crates.io-index"
360 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
361 |
362 | [[package]]
363 | name = "itertools"
364 | version = "0.8.2"
365 | source = "registry+https://github.com/rust-lang/crates.io-index"
366 | checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484"
367 | dependencies = [
368 | "either",
369 | ]
370 |
371 | [[package]]
372 | name = "lazy_static"
373 | version = "1.5.0"
374 | source = "registry+https://github.com/rust-lang/crates.io-index"
375 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
376 |
377 | [[package]]
378 | name = "lazycell"
379 | version = "1.3.0"
380 | source = "registry+https://github.com/rust-lang/crates.io-index"
381 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
382 |
383 | [[package]]
384 | name = "libc"
385 | version = "0.2.158"
386 | source = "registry+https://github.com/rust-lang/crates.io-index"
387 | checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
388 |
389 | [[package]]
390 | name = "libloading"
391 | version = "0.8.5"
392 | source = "registry+https://github.com/rust-lang/crates.io-index"
393 | checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
394 | dependencies = [
395 | "cfg-if",
396 | "windows-targets",
397 | ]
398 |
399 | [[package]]
400 | name = "linux-raw-sys"
401 | version = "0.4.14"
402 | source = "registry+https://github.com/rust-lang/crates.io-index"
403 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89"
404 |
405 | [[package]]
406 | name = "log"
407 | version = "0.4.22"
408 | source = "registry+https://github.com/rust-lang/crates.io-index"
409 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
410 |
411 | [[package]]
412 | name = "memchr"
413 | version = "2.7.4"
414 | source = "registry+https://github.com/rust-lang/crates.io-index"
415 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
416 |
417 | [[package]]
418 | name = "minimal-lexical"
419 | version = "0.2.1"
420 | source = "registry+https://github.com/rust-lang/crates.io-index"
421 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
422 |
423 | [[package]]
424 | name = "nom"
425 | version = "7.1.3"
426 | source = "registry+https://github.com/rust-lang/crates.io-index"
427 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
428 | dependencies = [
429 | "memchr",
430 | "minimal-lexical",
431 | ]
432 |
433 | [[package]]
434 | name = "once_cell"
435 | version = "1.19.0"
436 | source = "registry+https://github.com/rust-lang/crates.io-index"
437 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
438 |
439 | [[package]]
440 | name = "peeking_take_while"
441 | version = "0.1.2"
442 | source = "registry+https://github.com/rust-lang/crates.io-index"
443 | checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
444 |
445 | [[package]]
446 | name = "pin-project-lite"
447 | version = "0.2.14"
448 | source = "registry+https://github.com/rust-lang/crates.io-index"
449 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
450 |
451 | [[package]]
452 | name = "pin-utils"
453 | version = "0.1.0"
454 | source = "registry+https://github.com/rust-lang/crates.io-index"
455 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
456 |
457 | [[package]]
458 | name = "prettyplease"
459 | version = "0.2.22"
460 | source = "registry+https://github.com/rust-lang/crates.io-index"
461 | checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba"
462 | dependencies = [
463 | "proc-macro2",
464 | "syn",
465 | ]
466 |
467 | [[package]]
468 | name = "proc-macro2"
469 | version = "1.0.86"
470 | source = "registry+https://github.com/rust-lang/crates.io-index"
471 | checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
472 | dependencies = [
473 | "unicode-ident",
474 | ]
475 |
476 | [[package]]
477 | name = "quote"
478 | version = "1.0.37"
479 | source = "registry+https://github.com/rust-lang/crates.io-index"
480 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
481 | dependencies = [
482 | "proc-macro2",
483 | ]
484 |
485 | [[package]]
486 | name = "rclrs"
487 | version = "0.4.1"
488 | source = "git+https://github.com/esteve/ros2_rust.git?branch=generic-subscriptions#1a51d39ac1f41801f1e1017aed9332f1e6560382"
489 | dependencies = [
490 | "ament_rs",
491 | "bindgen",
492 | "cfg-if",
493 | "futures",
494 | "libloading",
495 | "rosidl_runtime_rs",
496 | ]
497 |
498 | [[package]]
499 | name = "regex"
500 | version = "1.10.6"
501 | source = "registry+https://github.com/rust-lang/crates.io-index"
502 | checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
503 | dependencies = [
504 | "aho-corasick",
505 | "memchr",
506 | "regex-automata",
507 | "regex-syntax",
508 | ]
509 |
510 | [[package]]
511 | name = "regex-automata"
512 | version = "0.4.7"
513 | source = "registry+https://github.com/rust-lang/crates.io-index"
514 | checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
515 | dependencies = [
516 | "aho-corasick",
517 | "memchr",
518 | "regex-syntax",
519 | ]
520 |
521 | [[package]]
522 | name = "regex-syntax"
523 | version = "0.8.4"
524 | source = "registry+https://github.com/rust-lang/crates.io-index"
525 | checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
526 |
527 | [[package]]
528 | name = "rerun_ros"
529 | version = "0.1.0"
530 | dependencies = [
531 | "ament_rs",
532 | "anyhow",
533 | "cdr",
534 | "clap",
535 | "log",
536 | "rclrs",
537 | "regex",
538 | "rosidl_runtime_rs",
539 | "serde",
540 | "serde_derive",
541 | "tempfile",
542 | "toml",
543 | "widestring",
544 | ]
545 |
546 | [[package]]
547 | name = "rosidl_runtime_rs"
548 | version = "0.4.1"
549 | source = "git+https://github.com/esteve/ros2_rust.git?branch=generic-subscriptions#1a51d39ac1f41801f1e1017aed9332f1e6560382"
550 | dependencies = [
551 | "cfg-if",
552 | ]
553 |
554 | [[package]]
555 | name = "rustc-hash"
556 | version = "1.1.0"
557 | source = "registry+https://github.com/rust-lang/crates.io-index"
558 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
559 |
560 | [[package]]
561 | name = "rustix"
562 | version = "0.38.36"
563 | source = "registry+https://github.com/rust-lang/crates.io-index"
564 | checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36"
565 | dependencies = [
566 | "bitflags",
567 | "errno",
568 | "libc",
569 | "linux-raw-sys",
570 | "windows-sys 0.52.0",
571 | ]
572 |
573 | [[package]]
574 | name = "same-file"
575 | version = "1.0.6"
576 | source = "registry+https://github.com/rust-lang/crates.io-index"
577 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
578 | dependencies = [
579 | "winapi-util",
580 | ]
581 |
582 | [[package]]
583 | name = "serde"
584 | version = "1.0.210"
585 | source = "registry+https://github.com/rust-lang/crates.io-index"
586 | checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
587 | dependencies = [
588 | "serde_derive",
589 | ]
590 |
591 | [[package]]
592 | name = "serde_derive"
593 | version = "1.0.210"
594 | source = "registry+https://github.com/rust-lang/crates.io-index"
595 | checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
596 | dependencies = [
597 | "proc-macro2",
598 | "quote",
599 | "syn",
600 | ]
601 |
602 | [[package]]
603 | name = "serde_spanned"
604 | version = "0.6.7"
605 | source = "registry+https://github.com/rust-lang/crates.io-index"
606 | checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d"
607 | dependencies = [
608 | "serde",
609 | ]
610 |
611 | [[package]]
612 | name = "shlex"
613 | version = "1.3.0"
614 | source = "registry+https://github.com/rust-lang/crates.io-index"
615 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
616 |
617 | [[package]]
618 | name = "slab"
619 | version = "0.4.9"
620 | source = "registry+https://github.com/rust-lang/crates.io-index"
621 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
622 | dependencies = [
623 | "autocfg",
624 | ]
625 |
626 | [[package]]
627 | name = "strsim"
628 | version = "0.11.1"
629 | source = "registry+https://github.com/rust-lang/crates.io-index"
630 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
631 |
632 | [[package]]
633 | name = "syn"
634 | version = "2.0.77"
635 | source = "registry+https://github.com/rust-lang/crates.io-index"
636 | checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
637 | dependencies = [
638 | "proc-macro2",
639 | "quote",
640 | "unicode-ident",
641 | ]
642 |
643 | [[package]]
644 | name = "tempfile"
645 | version = "3.12.0"
646 | source = "registry+https://github.com/rust-lang/crates.io-index"
647 | checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64"
648 | dependencies = [
649 | "cfg-if",
650 | "fastrand",
651 | "once_cell",
652 | "rustix",
653 | "windows-sys 0.59.0",
654 | ]
655 |
656 | [[package]]
657 | name = "toml"
658 | version = "0.8.19"
659 | source = "registry+https://github.com/rust-lang/crates.io-index"
660 | checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
661 | dependencies = [
662 | "serde",
663 | "serde_spanned",
664 | "toml_datetime",
665 | "toml_edit",
666 | ]
667 |
668 | [[package]]
669 | name = "toml_datetime"
670 | version = "0.6.8"
671 | source = "registry+https://github.com/rust-lang/crates.io-index"
672 | checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41"
673 | dependencies = [
674 | "serde",
675 | ]
676 |
677 | [[package]]
678 | name = "toml_edit"
679 | version = "0.22.20"
680 | source = "registry+https://github.com/rust-lang/crates.io-index"
681 | checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d"
682 | dependencies = [
683 | "indexmap",
684 | "serde",
685 | "serde_spanned",
686 | "toml_datetime",
687 | "winnow",
688 | ]
689 |
690 | [[package]]
691 | name = "unicode-ident"
692 | version = "1.0.12"
693 | source = "registry+https://github.com/rust-lang/crates.io-index"
694 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
695 |
696 | [[package]]
697 | name = "utf8parse"
698 | version = "0.2.2"
699 | source = "registry+https://github.com/rust-lang/crates.io-index"
700 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
701 |
702 | [[package]]
703 | name = "walkdir"
704 | version = "2.5.0"
705 | source = "registry+https://github.com/rust-lang/crates.io-index"
706 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
707 | dependencies = [
708 | "same-file",
709 | "winapi-util",
710 | ]
711 |
712 | [[package]]
713 | name = "which"
714 | version = "4.4.2"
715 | source = "registry+https://github.com/rust-lang/crates.io-index"
716 | checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7"
717 | dependencies = [
718 | "either",
719 | "home",
720 | "once_cell",
721 | "rustix",
722 | ]
723 |
724 | [[package]]
725 | name = "widestring"
726 | version = "1.1.0"
727 | source = "registry+https://github.com/rust-lang/crates.io-index"
728 | checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311"
729 |
730 | [[package]]
731 | name = "winapi-util"
732 | version = "0.1.9"
733 | source = "registry+https://github.com/rust-lang/crates.io-index"
734 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
735 | dependencies = [
736 | "windows-sys 0.59.0",
737 | ]
738 |
739 | [[package]]
740 | name = "windows-sys"
741 | version = "0.52.0"
742 | source = "registry+https://github.com/rust-lang/crates.io-index"
743 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
744 | dependencies = [
745 | "windows-targets",
746 | ]
747 |
748 | [[package]]
749 | name = "windows-sys"
750 | version = "0.59.0"
751 | source = "registry+https://github.com/rust-lang/crates.io-index"
752 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
753 | dependencies = [
754 | "windows-targets",
755 | ]
756 |
757 | [[package]]
758 | name = "windows-targets"
759 | version = "0.52.6"
760 | source = "registry+https://github.com/rust-lang/crates.io-index"
761 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
762 | dependencies = [
763 | "windows_aarch64_gnullvm",
764 | "windows_aarch64_msvc",
765 | "windows_i686_gnu",
766 | "windows_i686_gnullvm",
767 | "windows_i686_msvc",
768 | "windows_x86_64_gnu",
769 | "windows_x86_64_gnullvm",
770 | "windows_x86_64_msvc",
771 | ]
772 |
773 | [[package]]
774 | name = "windows_aarch64_gnullvm"
775 | version = "0.52.6"
776 | source = "registry+https://github.com/rust-lang/crates.io-index"
777 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
778 |
779 | [[package]]
780 | name = "windows_aarch64_msvc"
781 | version = "0.52.6"
782 | source = "registry+https://github.com/rust-lang/crates.io-index"
783 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
784 |
785 | [[package]]
786 | name = "windows_i686_gnu"
787 | version = "0.52.6"
788 | source = "registry+https://github.com/rust-lang/crates.io-index"
789 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
790 |
791 | [[package]]
792 | name = "windows_i686_gnullvm"
793 | version = "0.52.6"
794 | source = "registry+https://github.com/rust-lang/crates.io-index"
795 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
796 |
797 | [[package]]
798 | name = "windows_i686_msvc"
799 | version = "0.52.6"
800 | source = "registry+https://github.com/rust-lang/crates.io-index"
801 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
802 |
803 | [[package]]
804 | name = "windows_x86_64_gnu"
805 | version = "0.52.6"
806 | source = "registry+https://github.com/rust-lang/crates.io-index"
807 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
808 |
809 | [[package]]
810 | name = "windows_x86_64_gnullvm"
811 | version = "0.52.6"
812 | source = "registry+https://github.com/rust-lang/crates.io-index"
813 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
814 |
815 | [[package]]
816 | name = "windows_x86_64_msvc"
817 | version = "0.52.6"
818 | source = "registry+https://github.com/rust-lang/crates.io-index"
819 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
820 |
821 | [[package]]
822 | name = "winnow"
823 | version = "0.6.18"
824 | source = "registry+https://github.com/rust-lang/crates.io-index"
825 | checksum = "68a9bda4691f099d435ad181000724da8e5899daa10713c2d432552b9ccd3a6f"
826 | dependencies = [
827 | "memchr",
828 | ]
829 |
830 | [[patch.unused]]
831 | name = "rerun_ros"
832 | version = "0.1.0"
833 |
--------------------------------------------------------------------------------