├── CHANGELOG.md ├── requirements.txt ├── .gitattributes ├── README.md ├── src ├── lib.rs ├── ros_introspection │ ├── mod.rs │ ├── msgspec.rs │ ├── type.rs │ ├── field.rs │ └── message.rs ├── main.rs └── config.rs ├── .typos.toml ├── config └── example.toml ├── .mypy.ini ├── .gitignore ├── rust-toolchain ├── .github ├── workflows │ ├── typos.yml │ ├── labels.yml │ ├── links.yml │ └── rust.yml └── pull_request_template.md ├── package.xml ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── LICENSE-MIT ├── .clang-format ├── pixi.toml ├── pyproject.toml ├── clippy.toml ├── deny.toml ├── scripts ├── clippy_wasm │ └── clippy.toml ├── generate_changelog.py └── template_update.py ├── lychee.toml ├── CODE_OF_CONDUCT.md ├── Cargo.toml ├── LICENSE-APACHE └── Cargo.lock /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | rerun-sdk>=0.15.0,<0.16.0 2 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | Cargo.lock linguist-generated=false 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rerun ROS bridge 2 | A bridge for connecting Rerun with ROS 2 written in Rust. 3 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This module provides functionalities for parsing ROS messages. 2 | pub mod config; 3 | pub mod ros_introspection; 4 | -------------------------------------------------------------------------------- /.typos.toml: -------------------------------------------------------------------------------- 1 | # https://github.com/crate-ci/typos 2 | # install: cargo install typos-cli 3 | # run: typos 4 | 5 | [default.extend-words] 6 | teh = "teh" # part of @teh-cmc 7 | -------------------------------------------------------------------------------- /src/ros_introspection/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod field; 2 | pub mod message; 3 | pub mod msgspec; 4 | pub mod r#type; 5 | 6 | pub use field::*; 7 | pub use message::*; 8 | pub use msgspec::*; 9 | pub use r#type::*; 10 | -------------------------------------------------------------------------------- /config/example.toml: -------------------------------------------------------------------------------- 1 | [[conversion]] 2 | topic = "topic/foo" 3 | frame_id = "frame1" 4 | ros_type = "std_msgs/msg/String" 5 | entity_path = "foo/bar1" 6 | 7 | [[conversion]] 8 | topic = "topic/bar" 9 | frame_id = "frame2" 10 | ros_type = "std_msgs/msg/Int32" 11 | entity_path = "foo/bar2" 12 | -------------------------------------------------------------------------------- /.mypy.ini: -------------------------------------------------------------------------------- 1 | [mypy] 2 | files = . 3 | exclude = build 4 | namespace_packages = True 5 | show_error_codes = True 6 | strict = True 7 | enable_error_code = redundant-expr, truthy-bool, ignore-without-code 8 | ; plugins = numpy.typing.mypy_plugin 9 | ignore_missing_imports = True 10 | no_implicit_reexport = False 11 | disallow_untyped_calls = False 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Mac stuff: 2 | .DS_Store 3 | 4 | # C++ build directory 5 | build 6 | 7 | # Rust compile target directories: 8 | target 9 | target_ra 10 | target_wasm 11 | 12 | # https://github.com/lycheeverse/lychee 13 | .lycheecache 14 | 15 | # Pixi environment 16 | .pixi 17 | 18 | # Python stuff: 19 | __pycache__ 20 | .mypy_cache 21 | .ruff_cache 22 | venv 23 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | # If you see this, run "rustup self update" to get rustup 1.23 or newer. 2 | 3 | # NOTE: above comment is for older `rustup` (before TOML support was added), 4 | # which will treat the first line as the toolchain name, and therefore show it 5 | # to the user in the error, instead of "error: invalid channel name '[toolchain]'". 6 | 7 | [toolchain] 8 | channel = "1.76.0" 9 | components = ["rustfmt", "clippy"] 10 | targets = ["wasm32-unknown-unknown"] 11 | -------------------------------------------------------------------------------- /.github/workflows/typos.yml: -------------------------------------------------------------------------------- 1 | # Copied from https://github.com/rerun-io/rerun_template 2 | 3 | # https://github.com/crate-ci/typos 4 | # Add exceptions to `.typos.toml` 5 | # install and run locally: cargo install typos-cli && typos 6 | 7 | name: Spell Check 8 | on: [pull_request] 9 | 10 | jobs: 11 | run: 12 | name: Spell Check 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Checkout Actions Repository 16 | uses: actions/checkout@v4 17 | 18 | - name: Check spelling of entire workspace 19 | uses: crate-ci/typos@master 20 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 12 | 13 | * Closes #ISSUE_NUMBER 14 | -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | rerun_ros 5 | 0.1.0 6 | rerun-ros 7 | rerun.io 8 | Apache License 2.0 9 | MIT 10 | 11 | rclrs 12 | rosidl_runtime_rs 13 | std_msgs 14 | 15 | rclrs 16 | rosidl_runtime_rs 17 | std_msgs 18 | 19 | 20 | ament_cargo 21 | 22 | 23 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 3 | // for the documentation about the extensions.json format 4 | "recommendations": [ 5 | "charliermarsh.ruff", 6 | "gaborv.flatbuffers", 7 | "github.vscode-github-actions", 8 | "josetr.cmake-language-support-vscode", 9 | "ms-python.mypy-type-checker", 10 | "ms-python.python", 11 | "ms-vscode.cmake-tools", 12 | "ms-vscode.cpptools-extension-pack", 13 | "ms-vsliveshare.vsliveshare", 14 | "polymeilex.wgsl", 15 | "rust-lang.rust-analyzer", 16 | "serayuzgur.crates", 17 | "streetsidesoftware.code-spell-checker", 18 | "tamasfe.even-better-toml", 19 | "vadimcn.vscode-lldb", 20 | "wayou.vscode-todo-highlight", 21 | "webfreak.debug", 22 | "xaver.clang-format", // C++ formatter 23 | "zxh404.vscode-proto3", 24 | "esbenp.prettier-vscode" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/labels.yml: -------------------------------------------------------------------------------- 1 | # Copied from https://github.com/rerun-io/rerun_template 2 | 3 | # https://github.com/marketplace/actions/require-labels 4 | # Check for existence of labels 5 | # See all our labels at https://github.com/rerun-io/rerun/issues/labels 6 | 7 | name: PR Labels 8 | 9 | on: 10 | pull_request: 11 | types: 12 | - opened 13 | - synchronize 14 | - reopened 15 | - labeled 16 | - unlabeled 17 | 18 | jobs: 19 | label: 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Check for a "do-not-merge" label 23 | uses: mheap/github-action-required-labels@v3 24 | with: 25 | mode: exactly 26 | count: 0 27 | labels: "do-not-merge" 28 | 29 | - name: Require label "include in changelog" or "exclude from changelog" 30 | uses: mheap/github-action-required-labels@v3 31 | with: 32 | mode: minimum 33 | count: 1 34 | labels: "exclude from changelog, include in changelog" 35 | -------------------------------------------------------------------------------- /.github/workflows/links.yml: -------------------------------------------------------------------------------- 1 | # Copied from https://github.com/rerun-io/rerun_template 2 | on: [push, pull_request] 3 | 4 | name: Link checker 5 | 6 | jobs: 7 | link-checker: 8 | name: Check links 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | 13 | - name: Restore link checker cache 14 | uses: actions/cache@v3 15 | with: 16 | path: .lycheecache 17 | key: cache-lychee-${{ github.sha }} 18 | restore-keys: cache-lychee- 19 | 20 | # Check https://github.com/lycheeverse/lychee on how to run locally. 21 | - name: Link Checker 22 | id: lychee 23 | uses: lycheeverse/lychee-action@v1.9.0 24 | with: 25 | fail: true 26 | lycheeVersion: "0.14.3" 27 | # When given a directory, lychee checks only markdown, html and text files, everything else we have to glob in manually. 28 | args: | 29 | --base . --cache --max-cache-age 1d . "**/*.rs" "**/*.toml" "**/*.hpp" "**/*.cpp" "**/CMakeLists.txt" "**/*.py" "**/*.yml" 30 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024 Rerun Technologies AB 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | 3 | # Make it slightly more similar to Rust. 4 | # Based loosely on https://gist.github.com/YodaEmbedding/c2c77dc693d11f3734d78489f9a6eea4 5 | AccessModifierOffset: -2 6 | AlignAfterOpenBracket: BlockIndent 7 | AllowAllArgumentsOnNextLine: false 8 | AllowShortBlocksOnASingleLine: false 9 | AllowShortCaseLabelsOnASingleLine: false 10 | AllowShortFunctionsOnASingleLine: Empty 11 | AllowShortIfStatementsOnASingleLine: Never 12 | AlwaysBreakAfterReturnType: None 13 | AlwaysBreakBeforeMultilineStrings: true 14 | BinPackArguments: false 15 | BreakStringLiterals: false 16 | ColumnLimit: 100 17 | ContinuationIndentWidth: 4 18 | DerivePointerAlignment: false 19 | EmptyLineBeforeAccessModifier: LogicalBlock 20 | IndentWidth: 4 21 | IndentWrappedFunctionNames: true 22 | InsertBraces: true 23 | InsertTrailingCommas: Wrapped 24 | MaxEmptyLinesToKeep: 1 25 | NamespaceIndentation: All 26 | PointerAlignment: Left 27 | ReflowComments: false 28 | SeparateDefinitionBlocks: Always 29 | SpacesBeforeTrailingComments: 1 30 | 31 | # Don't change include blocks, we want to control this manually. 32 | # Sorting headers however is allowed as all our headers should be standalone. 33 | IncludeBlocks: Preserve 34 | SortIncludes: CaseInsensitive 35 | -------------------------------------------------------------------------------- /pixi.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "rerun-ros" 3 | authors = ["rerun.io "] 4 | channels = ["robostack-staging", "conda-forge"] 5 | description = "rerun-ros" 6 | homepage = "https://rerun.io" 7 | license = "MIT OR Apache-2.0" 8 | 9 | platforms = ["linux-64"] 10 | readme = "README.md" 11 | repository = "https://github.com/rerun-io/rerun-ros" 12 | version = "0.1.0" 13 | 14 | [tasks] 15 | install-cargo-ament-build = "cargo install cargo-ament-build" 16 | build = { cmd = "colcon build", depends-on = ["install-cargo-ament-build"] } 17 | 18 | [activation] 19 | env = { ROS_DISTRO = "humble" } 20 | scripts = ["install/setup.bash", "install/local_setup.bash"] 21 | 22 | [dependencies] 23 | ros-humble-rcl = ">=5.3.7,<6" 24 | ros-humble-builtin-interfaces = ">=1.2.1,<2" 25 | ros-humble-rcl-interfaces = ">=1.2.1,<2" 26 | ros-humble-rosgraph-msgs = ">=1.2.1,<2" 27 | clangdev = ">=18.1.8,<19" 28 | rust = "1.76.0.*" 29 | ros-humble-rosidl-default-generators = ">=1.2.0,<2" 30 | ros-humble-ros2cli = ">=0.18.8,<0.19" 31 | ros-humble-ros2run = ">=0.18.8,<0.19" 32 | ros-humble-ros2cli-common-extensions = ">=0.1.1,<0.2" 33 | ros-humble-ros-core = ">=0.10.0,<0.11" 34 | ros-humble-geometry-msgs = ">=4.2.3,<5" 35 | 36 | [build-dependencies] 37 | colcon-common-extensions = ">=0.3.0,<0.4" 38 | 39 | [pypi-dependencies] 40 | colcon-cargo = { git = "https://github.com/colcon/colcon-cargo.git" } 41 | colcon-ros-cargo = { git = "https://github.com/colcon/colcon-ros-cargo.git" } 42 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | // Python 8 | { 9 | "name": "Python Debugger: Current File", 10 | "type": "debugpy", 11 | "request": "launch", 12 | "program": "${file}", 13 | "console": "integratedTerminal" 14 | }, 15 | // Rust: 16 | { 17 | "name": "Debug 'PROJ_NAME'", 18 | "type": "lldb", 19 | "request": "launch", 20 | "cargo": { 21 | "args": [ 22 | "build" 23 | ], 24 | "filter": { 25 | "name": "PROJ_NAME", 26 | "kind": "bin" 27 | } 28 | }, 29 | "args": [], 30 | "cwd": "${workspaceFolder}", 31 | "env": { 32 | "RUST_LOG": "debug" 33 | } 34 | }, 35 | { 36 | "name": "Launch Rust tests", 37 | "type": "lldb", 38 | "request": "launch", 39 | "cargo": { 40 | "args": [ 41 | "test", 42 | "--no-run", 43 | "--lib", 44 | "--all-features" 45 | ], 46 | "filter": { 47 | "kind": "lib" 48 | } 49 | }, 50 | "cwd": "${workspaceFolder}", 51 | "env": { 52 | "RUST_LOG": "debug" 53 | } 54 | }, 55 | ] 56 | } 57 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{Error, Result}; 2 | use clap::Parser; 3 | use rerun_ros::config::ConfigParser; 4 | use std::env; 5 | use std::sync::Arc; 6 | 7 | /// A bridge between rerun and ROS 8 | #[derive(Parser, Debug)] 9 | #[command(version, about, long_about = None)] 10 | struct BridgeArgs { 11 | /// Path to the configuration file in TOML format 12 | #[arg(short, long)] 13 | config_file: String, 14 | } 15 | 16 | fn main() -> Result<(), Error> { 17 | let bridge_args = BridgeArgs::parse(); 18 | 19 | if bridge_args.config_file.is_empty() { 20 | return Ok(()); 21 | } 22 | 23 | println!("Starting bridge"); 24 | let config_parser = ConfigParser::new(&bridge_args.config_file)?; 25 | 26 | let context = rclrs::Context::new(env::args())?; 27 | let node = rclrs::create_node(&context, "rerun_ros_bridge")?; 28 | // Clippy does not like iterating over the keys of a HashMap, so we collect it into a Vec 29 | let config_entries: Vec<_> = config_parser.conversions().iter().collect(); 30 | 31 | // Prevent the subscriptions from being dropped 32 | let mut _subscriptions = Vec::new(); 33 | for ((topic_name, _frame_id), (ros_type, _entity_path)) in config_entries { 34 | let msg_spec = rerun_ros::ros_introspection::MsgSpec::new(ros_type)?; 35 | 36 | println!("Subscribing to topic: {topic_name} with type: {ros_type}"); 37 | let _generic_subscription = node.create_generic_subscription( 38 | topic_name, 39 | ros_type, 40 | rclrs::QOS_PROFILE_DEFAULT, 41 | move |_msg: rclrs::SerializedMessage| { 42 | let _msg_spec = Arc::new(&msg_spec); 43 | // Process message and pass it to rerun 44 | }, 45 | )?; 46 | _subscriptions.push(_generic_subscription); 47 | } 48 | Ok(()) 49 | } 50 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.semanticTokenColorCustomizations": { 4 | "rules": { 5 | "*.unsafe:rust": "#eb5046" 6 | } 7 | }, 8 | "files.autoGuessEncoding": true, 9 | "files.insertFinalNewline": true, 10 | "files.trimTrailingWhitespace": true, 11 | // don't share a cargo lock with rust-analyzer. 12 | // see https://github.com/rerun-io/rerun/pull/519 for rationale 13 | "rust-analyzer.check.overrideCommand": [ 14 | "cargo", 15 | "clippy", 16 | "--target-dir=target_ra", 17 | "--workspace", 18 | "--message-format=json", 19 | "--all-targets", 20 | "--all-features" 21 | ], 22 | "rust-analyzer.cargo.buildScripts.overrideCommand": [ 23 | "cargo", 24 | "check", 25 | "--quiet", 26 | "--target-dir=target_ra", 27 | "--workspace", 28 | "--message-format=json", 29 | "--all-targets", 30 | "--all-features", 31 | ], 32 | // Our build scripts are generating code. 33 | // Having Rust Analyzer do this while doing other builds can lead to catastrophic failures. 34 | // INCLUDING attempts to publish a new release! 35 | "rust-analyzer.cargo.buildScripts.enable": false, 36 | "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", // Use cmake-tools to grab configs. 37 | "C_Cpp.autoAddFileAssociations": false, 38 | "cmake.buildDirectory": "${workspaceRoot}/build/debug", 39 | "cmake.generator": "Ninja", // Use Ninja, just like we do in our just/pixi command. 40 | "rust-analyzer.showUnlinkedFileNotification": false, 41 | "ruff.format.args": [ 42 | "--config=pyproject.toml" 43 | ], 44 | "ruff.lint.args": [ 45 | "--config=pyproject.toml" 46 | ], 47 | "prettier.requireConfig": true, 48 | "prettier.configPath": ".prettierrc.toml", 49 | "[python]": { 50 | "editor.defaultFormatter": "charliermarsh.ruff" 51 | }, 52 | "files.exclude": { 53 | "**/build": true, 54 | "**/install": true, 55 | "**/log": true 56 | }, 57 | } 58 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | # Copied from https://github.com/rerun-io/rerun_template 2 | 3 | [tool.ruff] 4 | # https://beta.ruff.rs/docs/configuration/ 5 | 6 | target-version = "py38" 7 | 8 | # Enable unsafe fixes to allow ruff to apply fixes that may change the behavior of the code. 9 | # This is needed because otherwise ruff will not be able to trim whitespaces in (codegened) docstrings. 10 | unsafe-fixes = true 11 | 12 | # Allow preview lints to be enabled (like `PLW1514` to force `encoding` on open). 13 | preview = true 14 | # But we only want to opt-in to certain preview rules! 15 | lint.explicit-preview-rules = true 16 | 17 | extend-exclude = [ 18 | # Automatically generated test artifacts 19 | "venv/", 20 | "target/", 21 | ] 22 | 23 | lint.ignore = [ 24 | # These makes sense to ignore in example code, but for a proper library we should not ignore these. 25 | "D100", # Missing docstring in public module 26 | "D101", # Missing docstring in public class 27 | "D103", # Missing docstring in public function 28 | 29 | # No blank lines allowed after function docstring. 30 | "D202", 31 | 32 | # npydocstyle: http://www.pydocstyle.org/en/stable/error_codes.html 33 | # numpy convention with a few additional lints 34 | "D107", 35 | "D203", 36 | "D212", 37 | "D401", 38 | "D402", 39 | "D415", 40 | "D416", 41 | 42 | # Ruff can't fix this error on its own (yet) 43 | # Having ruff check this causes errors that prevent the code-formatting process from completing. 44 | "E501", 45 | 46 | # allow relative imports 47 | "TID252", 48 | 49 | "UP007", # We need this, or `ruff format` will convert `Union[X, Y]` to `X | Y` which break on Python 3.8 50 | ] 51 | 52 | line-length = 120 53 | lint.select = [ 54 | "D", # pydocstyle codes https://www.pydocstyle.org/en/latest/error_codes.html 55 | "E", # pycodestyle error codes: https://pycodestyle.pycqa.org/en/latest/intro.html#error-codes 56 | "F", # Flake8 error codes https://flake8.pycqa.org/en/latest/user/error-codes.html 57 | "I", # Isort 58 | "TID", # flake8-tidy-imports 59 | "W", # pycodestyle warning codes: https://pycodestyle.pycqa.org/en/latest/intro.html#error-codes 60 | "UP", # pyupgrade (ensures idomatic code for supported python version) 61 | "PLW1514", # Force setting `encoding` for open calls. This is in order to prevent issues when opening utf8 files on windows where the default encoding may depend on the active locale. https://docs.astral.sh/ruff/rules/unspecified-encoding/ 62 | ] 63 | 64 | lint.unfixable = [ 65 | "PLW1514", # Automatic fix for `encoding` doesn't do what we want - it queries the locale for the preferred encoding which is exactly what we want to avoid. 66 | ] 67 | 68 | [tool.ruff.lint.per-file-ignores] 69 | "__init__.py" = ["F401", "F403"] 70 | 71 | [tool.ruff.lint.isort] 72 | required-imports = ["from __future__ import annotations"] 73 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | # Copied from https://github.com/rerun-io/rerun_template 2 | # 3 | # There is also a scripts/clippy_wasm/clippy.toml which forbids some methods that are not available in wasm. 4 | 5 | # ----------------------------------------------------------------------------- 6 | # Section identical to scripts/clippy_wasm/clippy.toml: 7 | 8 | msrv = "1.76" 9 | 10 | allow-unwrap-in-tests = true 11 | 12 | # https://doc.rust-lang.org/nightly/clippy/lint_configuration.html#avoid-breaking-exported-api 13 | # We want suggestions, even if it changes public API. 14 | avoid-breaking-exported-api = false 15 | 16 | excessive-nesting-threshold = 8 17 | 18 | max-fn-params-bools = 1 19 | 20 | # https://rust-lang.github.io/rust-clippy/master/index.html#/large_include_file 21 | max-include-file-size = 1000000 22 | 23 | # https://rust-lang.github.io/rust-clippy/master/index.html#/large_stack_frames 24 | stack-size-threshold = 512000 25 | 26 | too-many-lines-threshold = 200 27 | 28 | # ----------------------------------------------------------------------------- 29 | 30 | # https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_macros 31 | disallowed-macros = ['dbg'] 32 | 33 | # https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods 34 | disallowed-methods = [ 35 | { path = "egui_extras::TableBody::row", reason = "`row` doesn't scale. Use `rows` instead." }, 36 | { path = "glam::Vec2::normalize", reason = "normalize() can create NaNs. Use try_normalize or normalize_or_zero" }, 37 | { path = "glam::Vec3::normalize", reason = "normalize() can create NaNs. Use try_normalize or normalize_or_zero" }, 38 | { path = "sha1::Digest::new", reason = "SHA1 is cryptographically broken" }, 39 | { path = "std::env::temp_dir", reason = "Use the tempdir crate instead" }, 40 | { path = "std::panic::catch_unwind", reason = "We compile with `panic = 'abort'`" }, 41 | { path = "std::thread::spawn", reason = "Use `std::thread::Builder` and name the thread" }, 42 | 43 | # There are many things that aren't allowed on wasm, 44 | # but we cannot disable them all here (because of e.g. https://github.com/rust-lang/rust-clippy/issues/10406) 45 | # so we do that in `scripts/clippy_wasm/clippy.toml` instead. 46 | ] 47 | 48 | # https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_names 49 | disallowed-names = [] 50 | 51 | # https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types 52 | disallowed-types = [ 53 | { path = "ring::digest::SHA1_FOR_LEGACY_USE_ONLY", reason = "SHA1 is cryptographically broken" }, 54 | 55 | { path = "std::sync::Condvar", reason = "Use parking_lot instead" }, 56 | { path = "std::sync::Mutex", reason = "Use parking_lot instead" }, 57 | { path = "std::sync::RwLock", reason = "Use parking_lot instead" }, 58 | 59 | # "std::sync::Once", # enabled for now as the `log_once` macro uses it internally 60 | ] 61 | 62 | # Allow-list of words for markdown in docstrings https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown 63 | doc-valid-idents = [ 64 | # You must also update the same list in `scripts/clippy_wasm/clippy.toml`! 65 | "GitHub", 66 | "GLB", 67 | "GLTF", 68 | "iOS", 69 | "macOS", 70 | "NaN", 71 | "OBJ", 72 | "OpenGL", 73 | "PyPI", 74 | "sRGB", 75 | "sRGBA", 76 | "WebGL", 77 | "WebSocket", 78 | "WebSockets", 79 | ] 80 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | # Copied from https://github.com/rerun-io/rerun_template 2 | # 3 | # https://github.com/EmbarkStudios/cargo-deny 4 | # 5 | # cargo-deny checks our dependency tree for copy-left licenses, 6 | # duplicate dependencies, and rustsec advisories (https://rustsec.org/advisories). 7 | # 8 | # Install: `cargo install cargo-deny` 9 | # Check: `cargo deny check`. 10 | 11 | 12 | # Note: running just `cargo deny check` without a `--target` can result in 13 | # false positives due to https://github.com/EmbarkStudios/cargo-deny/issues/324 14 | [graph] 15 | targets = [ 16 | { triple = "aarch64-apple-darwin" }, 17 | { triple = "i686-pc-windows-gnu" }, 18 | { triple = "i686-pc-windows-msvc" }, 19 | { triple = "i686-unknown-linux-gnu" }, 20 | { triple = "wasm32-unknown-unknown" }, 21 | { triple = "x86_64-apple-darwin" }, 22 | { triple = "x86_64-pc-windows-gnu" }, 23 | { triple = "x86_64-pc-windows-msvc" }, 24 | { triple = "x86_64-unknown-linux-gnu" }, 25 | { triple = "x86_64-unknown-linux-musl" }, 26 | { triple = "x86_64-unknown-redox" }, 27 | ] 28 | all-features = true 29 | 30 | 31 | [advisories] 32 | version = 2 33 | ignore = [] 34 | 35 | 36 | [bans] 37 | multiple-versions = "deny" 38 | wildcards = "deny" 39 | deny = [ 40 | { name = "openssl", reason = "Use rustls" }, 41 | { name = "openssl-sys", reason = "Use rustls" }, 42 | ] 43 | skip = [] 44 | skip-tree = [] 45 | 46 | 47 | [licenses] 48 | version = 2 49 | private = { ignore = true } 50 | confidence-threshold = 0.93 # We want really high confidence when inferring licenses from text 51 | allow = [ 52 | "Apache-2.0 WITH LLVM-exception", # https://spdx.org/licenses/LLVM-exception.html 53 | "Apache-2.0", # https://tldrlegal.com/license/apache-license-2.0-(apache-2.0) 54 | "BSD-2-Clause", # https://tldrlegal.com/license/bsd-2-clause-license-(freebsd) 55 | "BSD-3-Clause", # https://tldrlegal.com/license/bsd-3-clause-license-(revised) 56 | "BSL-1.0", # https://tldrlegal.com/license/boost-software-license-1.0-explained 57 | "CC0-1.0", # https://creativecommons.org/publicdomain/zero/1.0/ 58 | "ISC", # https://www.tldrlegal.com/license/isc-license 59 | "LicenseRef-UFL-1.0", # no official SPDX, see https://github.com/emilk/egui/issues/2321 60 | "MIT-0", # https://choosealicense.com/licenses/mit-0/ 61 | "MIT", # https://tldrlegal.com/license/mit-license 62 | "MPL-2.0", # https://www.mozilla.org/en-US/MPL/2.0/FAQ/ - see Q11. Used by webpki-roots on Linux. 63 | "OFL-1.1", # https://spdx.org/licenses/OFL-1.1.html 64 | "OpenSSL", # https://www.openssl.org/source/license.html - used on Linux 65 | "Unicode-DFS-2016", # https://spdx.org/licenses/Unicode-DFS-2016.html 66 | "Zlib", # https://tldrlegal.com/license/zlib-libpng-license-(zlib) 67 | ] 68 | exceptions = [] 69 | 70 | [[licenses.clarify]] 71 | name = "webpki" 72 | expression = "ISC" 73 | license-files = [{ path = "LICENSE", hash = 0x001c7e6c }] 74 | 75 | [[licenses.clarify]] 76 | name = "ring" 77 | expression = "MIT AND ISC AND OpenSSL" 78 | license-files = [{ path = "LICENSE", hash = 0xbd0eed23 }] 79 | 80 | 81 | [sources] 82 | unknown-registry = "deny" 83 | unknown-git = "deny" 84 | 85 | [sources.allow-org] 86 | github = ["emilk", "rerun-io", "esteve"] 87 | -------------------------------------------------------------------------------- /scripts/clippy_wasm/clippy.toml: -------------------------------------------------------------------------------- 1 | # Copied from https://github.com/rerun-io/rerun_template 2 | 3 | # This is used by the CI so we can forbid some methods that are not available in wasm. 4 | # 5 | # We cannot forbid all these methods in the main `clippy.toml` because of 6 | # https://github.com/rust-lang/rust-clippy/issues/10406 7 | 8 | # ----------------------------------------------------------------------------- 9 | # Section identical to the main clippy.toml: 10 | 11 | msrv = "1.76" 12 | 13 | allow-unwrap-in-tests = true 14 | 15 | # https://doc.rust-lang.org/nightly/clippy/lint_configuration.html#avoid-breaking-exported-api 16 | # We want suggestions, even if it changes public API. 17 | avoid-breaking-exported-api = false 18 | 19 | excessive-nesting-threshold = 8 20 | 21 | max-fn-params-bools = 1 22 | 23 | # https://rust-lang.github.io/rust-clippy/master/index.html#/large_include_file 24 | max-include-file-size = 1000000 25 | 26 | too-many-lines-threshold = 200 27 | 28 | # ----------------------------------------------------------------------------- 29 | 30 | # https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_methods 31 | disallowed-methods = [ 32 | { path = "crossbeam::channel::Receiver::into_iter", reason = "Cannot block on Web" }, 33 | { path = "crossbeam::channel::Receiver::iter", reason = "Cannot block on Web" }, 34 | { path = "crossbeam::channel::Receiver::recv_timeout", reason = "Cannot block on Web" }, 35 | { path = "crossbeam::channel::Receiver::recv", reason = "Cannot block on Web" }, 36 | { path = "poll_promise::Promise::block_and_take", reason = "Cannot block on Web" }, 37 | { path = "poll_promise::Promise::block_until_ready_mut", reason = "Cannot block on Web" }, 38 | { path = "poll_promise::Promise::block_until_ready", reason = "Cannot block on Web" }, 39 | { path = "rayon::spawn", reason = "Cannot spawn threads on wasm" }, 40 | { path = "std::sync::mpsc::Receiver::into_iter", reason = "Cannot block on Web" }, 41 | { path = "std::sync::mpsc::Receiver::iter", reason = "Cannot block on Web" }, 42 | { path = "std::sync::mpsc::Receiver::recv_timeout", reason = "Cannot block on Web" }, 43 | { path = "std::sync::mpsc::Receiver::recv", reason = "Cannot block on Web" }, 44 | { path = "std::thread::spawn", reason = "Cannot spawn threads on wasm" }, 45 | { path = "std::time::Duration::elapsed", reason = "use `web-time` crate instead for wasm/web compatibility" }, 46 | { path = "std::time::Instant::now", reason = "use `web-time` crate instead for wasm/web compatibility" }, 47 | { path = "std::time::SystemTime::now", reason = "use `web-time` or `time` crates instead for wasm/web compatibility" }, 48 | ] 49 | 50 | # https://rust-lang.github.io/rust-clippy/master/index.html#disallowed_types 51 | disallowed-types = [ 52 | { path = "instant::SystemTime", reason = "Known bugs. Use web-time." }, 53 | { path = "std::thread::Builder", reason = "Cannot spawn threads on wasm" }, 54 | # { path = "std::path::PathBuf", reason = "Can't read/write files on web" }, // Used in build.rs files (which is fine). 55 | ] 56 | 57 | # Allow-list of words for markdown in docstrings https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown 58 | doc-valid-idents = [ 59 | # You must also update the same list in the root `clippy.toml`! 60 | "..", 61 | "GitHub", 62 | "GLB", 63 | "GLTF", 64 | "iOS", 65 | "macOS", 66 | "NaN", 67 | "OBJ", 68 | "OpenGL", 69 | "PyPI", 70 | "sRGB", 71 | "sRGBA", 72 | "WebGL", 73 | "WebSocket", 74 | "WebSockets", 75 | ] 76 | -------------------------------------------------------------------------------- /lychee.toml: -------------------------------------------------------------------------------- 1 | # Copied from https://github.com/rerun-io/rerun_template 2 | 3 | ################################################################################ 4 | # Config for the link checker lychee. 5 | # 6 | # Download & learn more at: 7 | # https://github.com/lycheeverse/lychee 8 | # 9 | # Example config: 10 | # https://github.com/lycheeverse/lychee/blob/master/lychee.example.toml 11 | # 12 | # Run `lychee . --dump` to list all found links that are being checked. 13 | # 14 | # Note that by default lychee will only check markdown and html files, 15 | # to check any other files you have to point to them explicitly, e.g.: 16 | # `lychee **/*.rs` 17 | # To make things worse, `exclude_path` is ignored for these globs, 18 | # so local runs with lots of gitignored files will be slow. 19 | # (https://github.com/lycheeverse/lychee/issues/1405) 20 | # 21 | # This unfortunately doesn't list anything for non-glob checks. 22 | ################################################################################ 23 | 24 | # Maximum number of concurrent link checks. 25 | # Workaround for "too many open files" error on MacOS, see https://github.com/lycheeverse/lychee/issues/1248 26 | max_concurrency = 32 27 | 28 | # Check links inside `` 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 | 


--------------------------------------------------------------------------------