Skip to content

Commit 81e2ccf

Browse files
authored
Add function to get non-ROS arguments (#210)
1 parent 214470a commit 81e2ccf

File tree

2 files changed

+183
-0
lines changed

2 files changed

+183
-0
lines changed

rclrs/src/arguments.rs

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
use crate::error::*;
2+
use crate::rcl_bindings::*;
3+
use libc::c_void;
4+
use std::ffi::CString;
5+
use std::os::raw::c_char;
6+
use std::ptr::null_mut;
7+
8+
/// Extract non-ROS arguments from program's input arguments.
9+
///
10+
/// `args` is expected to be the input arguments of the program (e.g. [`std::env::args()`]),
11+
/// which are expected to contain at least one element - the executable name.
12+
///
13+
/// ROS arguments are arguments between `--ros-args` and `--`, with the final `--` being optional.
14+
/// Everything else is considered as non-ROS arguments and will be left unparsed by
15+
/// [`Context::new()`][1] etc.
16+
/// Extracted non-ROS arguments are returned in the order that they appear by this function.
17+
///
18+
/// # Example
19+
/// ```
20+
/// # use rclrs::RclrsError;
21+
/// let input_args = [
22+
/// "arg1", "--ros-args", "some", "args", "--", "arg2"
23+
/// ].map(|x| x.to_string());
24+
/// let non_ros_args = rclrs::extract_non_ros_args(input_args)?;
25+
/// assert_eq!(non_ros_args.len(), 2);
26+
/// assert_eq!(non_ros_args[0], "arg1");
27+
/// assert_eq!(non_ros_args[1], "arg2");
28+
/// # Ok::<(), RclrsError>(())
29+
/// ```
30+
///
31+
/// [1]: crate::Context::new
32+
pub fn extract_non_ros_args(
33+
args: impl IntoIterator<Item = String>,
34+
) -> Result<Vec<String>, RclrsError> {
35+
// SAFETY: Getting a zero-initialized value is always safe.
36+
let mut rcl_arguments = unsafe { rcl_get_zero_initialized_arguments() };
37+
38+
let (args, cstring_args): (Vec<String>, Vec<Result<CString, RclrsError>>) = args
39+
.into_iter()
40+
.map(|arg| {
41+
let cstring_arg =
42+
CString::new(arg.as_str()).map_err(|err| RclrsError::StringContainsNul {
43+
err,
44+
s: arg.clone(),
45+
});
46+
(arg, cstring_arg)
47+
})
48+
.unzip();
49+
let cstring_args: Vec<CString> = cstring_args
50+
.into_iter()
51+
.collect::<Result<Vec<CString>, RclrsError>>()?;
52+
// Vector of pointers into cstring_args
53+
let c_args: Vec<*const c_char> = cstring_args.iter().map(|arg| arg.as_ptr()).collect();
54+
55+
let argv = if c_args.is_empty() {
56+
std::ptr::null()
57+
} else {
58+
c_args.as_ptr()
59+
};
60+
61+
unsafe {
62+
// SAFETY: Getting a default value is always safe.
63+
let allocator = rcutils_get_default_allocator();
64+
// SAFETY: No preconditions for this function.
65+
rcl_parse_arguments(c_args.len() as i32, argv, allocator, &mut rcl_arguments).ok()?;
66+
}
67+
68+
let ret = get_rcl_arguments(
69+
rcl_arguments_get_count_unparsed,
70+
rcl_arguments_get_unparsed,
71+
&rcl_arguments,
72+
&args,
73+
);
74+
unsafe {
75+
// SAFETY: No preconditions for this function.
76+
rcl_arguments_fini(&mut rcl_arguments).ok()?;
77+
}
78+
ret
79+
}
80+
81+
/// Returns arguments type held by `rcl_arguments` basing on `rcl_get_count` and `rcl_get_indices` function pointers.
82+
///
83+
/// This function must be called after `rcl_arguments` was initialized. `args` must be array of input arguments passed to node/program.
84+
///
85+
/// SAFETY: `rcl_get_count` and `rcl_get_indices` has to be corresponding rcl API functions, e.g.:
86+
/// `rcl_arguments_get_count_unparsed` -> `rcl_arguments_get_unparsed`
87+
/// `rcl_arguments_get_count_unparsed_ros` -> `rcl_arguments_get_count_ros`
88+
/// ...
89+
pub(crate) fn get_rcl_arguments(
90+
rcl_get_count: unsafe extern "C" fn(*const rcl_arguments_t) -> std::os::raw::c_int,
91+
rcl_get_indices: unsafe extern "C" fn(
92+
*const rcl_arguments_t,
93+
rcl_allocator_t,
94+
*mut *mut std::os::raw::c_int,
95+
) -> rcl_ret_t,
96+
rcl_arguments: *const rcl_arguments_t,
97+
args: &[String],
98+
) -> Result<Vec<String>, RclrsError> {
99+
// SAFETY: No preconditions for this function.
100+
let args_count = unsafe { rcl_get_count(rcl_arguments) };
101+
debug_assert!(args_count != -1);
102+
// All possible negative args_count values were handled above.
103+
let args_count = usize::try_from(args_count).unwrap();
104+
if args_count == 0 {
105+
return Ok(Vec::new());
106+
}
107+
let mut extracted_args: Vec<String> = Vec::with_capacity(args_count);
108+
let mut indices_ptr: *mut i32 = null_mut();
109+
unsafe {
110+
// SAFETY: No preconditions for this function.
111+
let allocator = rcutils_get_default_allocator();
112+
// SAFETY: The indices_ptr is an output parameter, so it is expected that it contains null.
113+
// The indices_ptr will need to be freed by the caller, which happens later in this function.
114+
rcl_get_indices(rcl_arguments, allocator, &mut indices_ptr).ok()?;
115+
116+
for i in 0..args_count {
117+
// SAFETY: rcl_get_indices finished with success and rcl_get_count is matching function
118+
// according to documentation of this function
119+
let index = *(indices_ptr.add(i));
120+
// SAFETY: rcl_get_indices and rcl_get_count are matching functions according
121+
// to documentation of this function
122+
let arg = args.get(index as usize).unwrap();
123+
extracted_args.push(arg.clone());
124+
}
125+
// SAFETY: No preconditions for this function.
126+
let allocator = rcutils_get_default_allocator();
127+
// SAFETY: indices_ptr was allocated with given allocator
128+
allocator.deallocate.unwrap()(indices_ptr as *mut c_void, null_mut());
129+
}
130+
Ok(extracted_args)
131+
}
132+
133+
#[cfg(test)]
134+
mod tests {
135+
use super::*;
136+
137+
#[test]
138+
fn test_non_ros_arguments() -> Result<(), String> {
139+
// ROS args are expected to be between '--ros-args' and '--'. Everything beside that is 'non-ROS'.
140+
let input_args: [String; 6] = [
141+
"non-ros1",
142+
"--ros-args",
143+
"ros-args",
144+
"--",
145+
"non-ros2",
146+
"non-ros3",
147+
]
148+
.map(|x| x.to_string());
149+
150+
let non_ros_args: Vec<String> = extract_non_ros_args(input_args).unwrap();
151+
let expected = vec!["non-ros1", "non-ros2", "non-ros3"];
152+
153+
if non_ros_args.len() != expected.len() {
154+
return Err(format!(
155+
"Expected vector size: {}, actual: {}",
156+
expected.len(),
157+
non_ros_args.len()
158+
));
159+
} else {
160+
for i in 0..non_ros_args.len() {
161+
if non_ros_args[i] != expected[i] {
162+
let msg = format!(
163+
"Mismatching elements at position: {}. Expected: {}, got: {}",
164+
i, expected[i], non_ros_args[i]
165+
);
166+
return Err(msg);
167+
}
168+
}
169+
}
170+
171+
Ok(())
172+
}
173+
174+
#[test]
175+
fn test_empty_non_ros_arguments() -> Result<(), RclrsError> {
176+
let empty_non_ros_args = extract_non_ros_args(vec![])?;
177+
assert_eq!(empty_non_ros_args.len(), 0);
178+
179+
Ok(())
180+
}
181+
}

rclrs/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
//!
66
//! [1]: https://github.com/ros2-rust/ros2_rust/blob/main/README.md
77
8+
mod arguments;
89
mod context;
910
mod error;
1011
mod node;
@@ -14,6 +15,7 @@ mod wait;
1415

1516
mod rcl_bindings;
1617

18+
pub use arguments::*;
1719
pub use context::*;
1820
pub use error::*;
1921
pub use node::*;

0 commit comments

Comments
 (0)