|
11 | 11 | use std::{ |
12 | 12 | collections::{BTreeMap, BTreeSet, VecDeque}, |
13 | 13 | fmt::{Debug, Display}, |
14 | | - path::PathBuf, |
| 14 | + path::{Path, PathBuf}, |
15 | 15 | }; |
16 | 16 |
|
17 | 17 | use log::*; |
@@ -55,6 +55,14 @@ impl Ros2Hash { |
55 | 55 | pub fn to_hash_string(&self) -> String { |
56 | 56 | format!("RIHS01_{}", hex::encode(self.0)) |
57 | 57 | } |
| 58 | + |
| 59 | + pub fn from_string(hash_str: &str) -> Self { |
| 60 | + // Remove "RIHS01_" prefix if present |
| 61 | + let hex_str = hash_str.trim_start_matches("RIHS01_"); |
| 62 | + let mut bytes = [0u8; 32]; |
| 63 | + hex::decode_to_slice(hex_str, &mut bytes).expect("Invalid hex string"); |
| 64 | + Ros2Hash(bytes) |
| 65 | + } |
58 | 66 | } |
59 | 67 |
|
60 | 68 | // Conversion from Ros2Hash to TokenStream for use in generated code |
@@ -341,6 +349,53 @@ impl ServiceFile { |
341 | 349 | } |
342 | 350 | } |
343 | 351 |
|
| 352 | +/// Resolved action file with type hashes for ROS 2 action service wrappers |
| 353 | +pub struct ActionWithHashes { |
| 354 | + pub parsed: ParsedActionFile, |
| 355 | + pub send_goal_hash: Ros2Hash, |
| 356 | + pub get_result_hash: Ros2Hash, |
| 357 | + pub feedback_message_hash: Ros2Hash, |
| 358 | +} |
| 359 | + |
| 360 | +impl ActionWithHashes { |
| 361 | + /// Creates an ActionWithHashes with type hashes loaded from ROS 2 JSON metadata |
| 362 | + pub fn from_json_metadata(parsed: ParsedActionFile, json_path: &Path) -> Option<Self> { |
| 363 | + use std::fs; |
| 364 | + |
| 365 | + let json_content = fs::read_to_string(json_path).ok()?; |
| 366 | + let json: serde_json::Value = serde_json::from_str(&json_content).ok()?; |
| 367 | + |
| 368 | + let type_hashes = json.get("type_hashes")?.as_array()?; |
| 369 | + |
| 370 | + // Helper to find hash by suffix |
| 371 | + let find_hash = |suffix: &str| -> Option<Ros2Hash> { |
| 372 | + type_hashes.iter().find_map(|type_hash| { |
| 373 | + let type_name = type_hash.get("type_name")?.as_str()?; |
| 374 | + let hash_string = type_hash.get("hash_string")?.as_str()?; |
| 375 | + |
| 376 | + type_name |
| 377 | + .ends_with(suffix) |
| 378 | + .then(|| Ros2Hash::from_string(hash_string)) |
| 379 | + }) |
| 380 | + }; |
| 381 | + |
| 382 | + Some(ActionWithHashes { |
| 383 | + parsed, |
| 384 | + send_goal_hash: find_hash("_SendGoal")?, |
| 385 | + get_result_hash: find_hash("_GetResult")?, |
| 386 | + feedback_message_hash: find_hash("_FeedbackMessage")?, |
| 387 | + }) |
| 388 | + } |
| 389 | + |
| 390 | + pub fn get_package_name(&self) -> String { |
| 391 | + self.parsed.package.clone() |
| 392 | + } |
| 393 | + |
| 394 | + pub fn get_short_name(&self) -> String { |
| 395 | + self.parsed.name.clone() |
| 396 | + } |
| 397 | +} |
| 398 | + |
344 | 399 | /// Stores the ROS string representation of a literal |
345 | 400 | #[derive(Clone, Debug)] |
346 | 401 | pub struct RosLiteral { |
@@ -836,6 +891,26 @@ pub(crate) fn parse_ros_files( |
836 | 891 | Ok((parsed_messages, parsed_services, parsed_actions)) |
837 | 892 | } |
838 | 893 |
|
| 894 | +/// Resolves parsed actions into ActionWithHashes with type hashes from JSON metadata |
| 895 | +pub fn resolve_action_hashes(parsed_actions: Vec<ParsedActionFile>) -> Vec<ActionWithHashes> { |
| 896 | + parsed_actions |
| 897 | + .into_iter() |
| 898 | + .filter_map(|parsed_action| { |
| 899 | + // The JSON file should be in the same directory as the .action file |
| 900 | + let json_path = parsed_action.path.with_extension("json"); |
| 901 | + |
| 902 | + ActionWithHashes::from_json_metadata(parsed_action.clone(), &json_path).or_else(|| { |
| 903 | + log::warn!( |
| 904 | + "Failed to resolve action hashes for {}/{}", |
| 905 | + parsed_action.package, |
| 906 | + parsed_action.name |
| 907 | + ); |
| 908 | + None |
| 909 | + }) |
| 910 | + }) |
| 911 | + .collect() |
| 912 | +} |
| 913 | + |
839 | 914 | #[cfg(test)] |
840 | 915 | mod test { |
841 | 916 | use crate::find_and_generate_ros_messages; |
|
0 commit comments