use cpp::ROS_TYPE_TO_CPP_TYPE_MAP;
use minijinja::{context, Template};
use roslibrust_codegen::{MessageFile, ServiceFile};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use roslibrust_codegen::utils::Package;
mod cpp;
mod helpers;
mod spec;
use spec::{MessageSpecification, ServiceSpecification};
#[derive(Clone, Debug)]
pub struct IncludedNamespace {
pub package: String,
pub path: PathBuf,
}
impl From<Package> for IncludedNamespace {
fn from(value: Package) -> Self {
IncludedNamespace {
package: value.name,
path: value.path,
}
}
}
impl From<&Package> for IncludedNamespace {
fn from(value: &Package) -> Self {
IncludedNamespace {
package: value.name.clone(),
path: value.path.clone(),
}
}
}
pub struct MessageGenOutput {
pub message_name: String,
pub package_name: String,
pub message_source: String,
}
pub struct ServiceGenOutput {
pub service_name: String,
pub package_name: String,
pub request_source: String,
pub response_source: String,
pub service_source: String,
}
type Filter = dyn Fn(minijinja::value::Value) -> minijinja::value::Value + Send + Sync;
pub struct CodeGeneratorBuilder<'a> {
msg_paths: Vec<PathBuf>,
msg_template: &'a str,
srv_template: Option<&'a str>,
typename_conversion_mapping: Option<HashMap<String, String>>,
filters: Vec<(String, Box<Filter>)>,
}
impl<'a> CodeGeneratorBuilder<'a> {
pub fn new<P: AsRef<Path>>(search_paths: &[P], msg_template: &'a str) -> Self {
Self {
msg_paths: search_paths.iter().map(|p| p.as_ref().to_owned()).collect(),
msg_template,
srv_template: None,
typename_conversion_mapping: None,
filters: vec![],
}
}
pub fn build(self) -> std::io::Result<CodeGenerator<'a>> {
let (messages, services, _actions) =
roslibrust_codegen::find_and_parse_ros_messages(&self.msg_paths).unwrap();
let (messages, services) =
roslibrust_codegen::resolve_dependency_graph(messages, services).unwrap();
let mut env = helpers::prepare_environment(
self.msg_template,
self.srv_template.unwrap_or(""),
self.typename_conversion_mapping.clone(),
);
self.filters
.into_iter()
.for_each(|(name, filter)| env.add_filter(name, filter));
Ok(CodeGenerator {
messages,
services,
template_environment: env,
})
}
pub fn service_template(mut self, srv_template: &'a str) -> Self {
self.srv_template = Some(srv_template);
self
}
pub fn add_type_mapping(mut self, map: HashMap<String, String>) -> Self {
self.typename_conversion_mapping = Some(map);
self
}
pub fn add_filter<F>(mut self, name: &str, filter: F) -> Self
where
F: Fn(minijinja::value::Value) -> minijinja::value::Value + Send + Sync + 'static,
{
self.filters.push((name.to_owned(), Box::new(filter)));
self
}
}
pub struct CodeGenerator<'a> {
messages: Vec<MessageFile>,
services: Vec<ServiceFile>,
template_environment: minijinja::Environment<'a>,
}
impl<'a> CodeGenerator<'a> {
pub fn generate_messages(&self) -> Result<Vec<MessageGenOutput>, minijinja::Error> {
self.messages
.iter()
.map(|msg| {
let message_source = fill_message_template(
&self.template_environment.get_template("message").unwrap(),
msg,
)?;
Ok(MessageGenOutput {
message_name: msg.get_short_name(),
package_name: msg.get_package_name(),
message_source,
})
})
.collect::<Result<Vec<_>, _>>()
}
pub fn generate_services(&self) -> Result<Vec<ServiceGenOutput>, minijinja::Error> {
self.services
.iter()
.map(|srv| {
let request_source = fill_message_template(
&self.template_environment.get_template("message").unwrap(),
srv.request(),
)?;
let response_source = fill_message_template(
&self.template_environment.get_template("message").unwrap(),
srv.response(),
)?;
let service_source = fill_service_template(
&self.template_environment.get_template("service").unwrap(),
srv,
)?;
Ok(ServiceGenOutput {
service_name: srv.get_short_name(),
package_name: srv.get_package_name(),
request_source,
response_source,
service_source,
})
})
.collect::<Result<Vec<_>, _>>()
}
}
pub fn make_cpp_generator<P: AsRef<Path>>(
search_paths: &[P],
) -> std::io::Result<CodeGenerator<'_>> {
CodeGeneratorBuilder::new(search_paths, cpp::MESSAGE_HEADER_TMPL)
.add_type_mapping(ROS_TYPE_TO_CPP_TYPE_MAP.clone())
.service_template(cpp::SERVICE_HEADER_TMPL)
.build()
}
fn fill_message_template(
template: &Template,
msg_data: &MessageFile,
) -> Result<String, minijinja::Error> {
let context = context! {
spec => MessageSpecification::from(msg_data),
};
template.render(&context)
}
fn fill_service_template(
template: &Template,
srv_data: &ServiceFile,
) -> Result<String, minijinja::Error> {
let context = context! {
spec => ServiceSpecification::from(srv_data),
};
template.render(&context)
}