@@ -64,6 +64,88 @@ def convert_lower_case_underscore_to_camel_case(word):
6464 return '' .join (x .capitalize () or '_' for x in word .split ('_' ))
6565
6666
67+ def _namespace_from_namespaced_type (namespaced_type ):
68+ namespaces = list (namespaced_type .namespaces )
69+ if not namespaces or namespaces [0 ] != package_name :
70+ raise ValueError (
71+ f"Expected namespace to start with package '{ package_name } ', got { namespaces } " )
72+ if len (namespaces ) != 2 :
73+ raise ValueError (
74+ f"Expected exactly one namespace component after package '{ package_name } ', got { namespaces } " )
75+ namespace = namespaces [1 ]
76+ if not namespace .isidentifier () or get_rs_name (namespace ) != namespace :
77+ raise ValueError (
78+ f"Namespace '{ namespace } ' cannot be emitted as a Rust module name" )
79+ return namespace
80+
81+
82+ def _namespace_from_message (message ):
83+ return _namespace_from_namespaced_type (message .structure .namespaced_type )
84+
85+
86+ def _namespace_from_service (service ):
87+ return _namespace_from_namespaced_type (service .namespaced_type )
88+
89+
90+ def _namespace_from_action (action ):
91+ return _namespace_from_namespaced_type (action .namespaced_type )
92+
93+
94+ def _group_specs_by_namespace (specs , namespace_getter ):
95+ grouped = {}
96+ for spec in specs :
97+ namespace = namespace_getter (spec )
98+ grouped .setdefault (namespace , []).append (spec )
99+ return grouped
100+
101+
102+ def _validate_single_kind_per_namespace (namespace_to_kinds ):
103+ for namespace , kinds in namespace_to_kinds .items ():
104+ direct_kinds = {kind for kind , _ in kinds }
105+ if len (direct_kinds ) > 1 :
106+ raise ValueError (
107+ f"Namespace '{ namespace } ' contains multiple top-level kinds: { sorted (direct_kinds )} " )
108+
109+
110+ def _expand_namespace_templates (template_dir , output_dir , namespace , spec_kind , specs ,
111+ latest_target_timestamp , data ):
112+ if not specs :
113+ return
114+
115+ if spec_kind == 'msg' :
116+ mappings = {
117+ os .path .join (template_dir , 'msg.rs.em' ): [f'rust/src/{ namespace } .rs' ],
118+ os .path .join (template_dir , 'msg/rmw.rs.em' ): [f'rust/src/{ namespace } /rmw.rs' ],
119+ }
120+ template_specs = 'msg_specs'
121+ elif spec_kind == 'srv' :
122+ mappings = {
123+ os .path .join (template_dir , 'srv.rs.em' ): [f'rust/src/{ namespace } .rs' ],
124+ os .path .join (template_dir , 'srv/rmw.rs.em' ): [f'rust/src/{ namespace } /rmw.rs' ],
125+ }
126+ template_specs = 'srv_specs'
127+ elif spec_kind == 'action' :
128+ mappings = {
129+ os .path .join (template_dir , 'action.rs.em' ): [f'rust/src/{ namespace } .rs' ],
130+ os .path .join (template_dir , 'action/rmw.rs.em' ): [f'rust/src/{ namespace } /rmw.rs' ],
131+ }
132+ template_specs = 'action_specs'
133+ else :
134+ raise ValueError (f'Unknown spec kind { spec_kind } ' )
135+
136+ namespace_data = data .copy ()
137+ namespace_data [template_specs ] = [(namespace , spec ) for spec in specs ]
138+
139+ for template_file , generated_filenames in mappings .items ():
140+ for generated_filename in generated_filenames :
141+ generated_file = os .path .join (output_dir , generated_filename )
142+ rosidl_pycommon .expand_template (
143+ os .path .join (template_dir , template_file ),
144+ namespace_data .copy (),
145+ generated_file ,
146+ minimum_timestamp = latest_target_timestamp )
147+
148+
67149def generate_rs (generator_arguments_file , typesupport_impls ):
68150 args = rosidl_pycommon .read_generator_arguments (generator_arguments_file )
69151
@@ -99,31 +181,17 @@ def generate_rs(generator_arguments_file, typesupport_impls):
99181
100182 template_dir = args ['template_dir' ]
101183
102- mapping_msgs = {
103- os .path .join (template_dir , 'msg.rs.em' ): ['rust/src/%s' ],
104- os .path .join (template_dir , 'msg/rmw.rs.em' ): ['rust/src/msg/%s' ],
105- }
106-
107- mapping_srvs = {
108- os .path .join (template_dir , 'srv.rs.em' ): ['rust/src/%s' ],
109- os .path .join (template_dir , 'srv/rmw.rs.em' ): ['rust/src/srv/%s' ],
110- }
111-
112- mapping_actions = {
113- os .path .join (template_dir , 'action.rs.em' ): ['rust/src/%s' ],
114- os .path .join (template_dir , 'action/rmw.rs.em' ): ['rust/src/action/%s' ],
115- }
116-
117184 # Ensure the required templates exist
118- for template_file in mapping_msgs .keys ():
119- assert os .path .exists (template_file ), \
120- 'Messages template file %s not found' % template_file
121- for template_file in mapping_srvs .keys ():
122- assert os .path .exists (template_file ), \
123- 'Services template file %s not found' % template_file
124- for template_file in mapping_actions .keys ():
185+ for template_file in [
186+ os .path .join (template_dir , 'msg.rs.em' ),
187+ os .path .join (template_dir , 'msg/rmw.rs.em' ),
188+ os .path .join (template_dir , 'srv.rs.em' ),
189+ os .path .join (template_dir , 'srv/rmw.rs.em' ),
190+ os .path .join (template_dir , 'action.rs.em' ),
191+ os .path .join (template_dir , 'action/rmw.rs.em' ),
192+ ]:
125193 assert os .path .exists (template_file ), \
126- 'Actions template file %s not found' % template_file
194+ 'Template file %s not found' % template_file
127195
128196 data = {
129197 'pre_field_serde' : pre_field_serde ,
@@ -146,53 +214,44 @@ def generate_rs(generator_arguments_file, typesupport_impls):
146214 latest_target_timestamp = rosidl_pycommon .get_newest_modification_time (
147215 args ['target_dependencies' ])
148216
149- for message in idl_content .get_elements_of_type (Message ):
150- data ['msg_specs' ].append (('msg' , message ))
151-
152- for service in idl_content .get_elements_of_type (Service ):
153- data ['srv_specs' ].append (('srv' , service ))
154-
155- for action in idl_content .get_elements_of_type (Action ):
156- data ['action_specs' ].append (('action' , action ))
157-
158- if data ['msg_specs' ]:
159- for template_file , generated_filenames in mapping_msgs .items ():
160- stem = _removesuffix (Path (template_file ).stem , ".em" )
161-
162- for generated_filename in generated_filenames :
163- generated_file = os .path .join (args ['output_dir' ],
164- generated_filename % stem )
165- rosidl_pycommon .expand_template (
166- os .path .join (template_dir , template_file ),
167- data .copy (),
168- generated_file ,
169- minimum_timestamp = latest_target_timestamp )
170-
171- if data ['srv_specs' ]:
172- for template_file , generated_filenames in mapping_srvs .items ():
173- stem = _removesuffix (Path (template_file ).stem , ".em" )
174-
175- for generated_filename in generated_filenames :
176- generated_file = os .path .join (args ['output_dir' ],
177- generated_filename % stem )
178- rosidl_pycommon .expand_template (
179- os .path .join (template_dir , template_file ),
180- data .copy (),
181- generated_file ,
182- minimum_timestamp = latest_target_timestamp )
183-
184- if data ['action_specs' ]:
185- for template_file , generated_filenames in mapping_actions .items ():
186- stem = _removesuffix (Path (template_file ).stem , ".em" )
187-
188- for generated_filename in generated_filenames :
189- generated_file = os .path .join (args ['output_dir' ],
190- generated_filename % stem )
191- rosidl_pycommon .expand_template (
192- os .path .join (template_dir , template_file ),
193- data .copy (),
194- generated_file ,
195- minimum_timestamp = latest_target_timestamp )
217+ message_specs = list (idl_content .get_elements_of_type (Message ))
218+ service_specs = list (idl_content .get_elements_of_type (Service ))
219+ action_specs = list (idl_content .get_elements_of_type (Action ))
220+
221+ messages_by_namespace = _group_specs_by_namespace (
222+ message_specs , _namespace_from_message )
223+ services_by_namespace = _group_specs_by_namespace (
224+ service_specs , _namespace_from_service )
225+ actions_by_namespace = _group_specs_by_namespace (
226+ action_specs , _namespace_from_action )
227+
228+ namespace_to_kinds = {}
229+ for namespace , specs in messages_by_namespace .items ():
230+ namespace_to_kinds .setdefault (namespace , []).append (('msg' , specs ))
231+ for namespace , specs in services_by_namespace .items ():
232+ namespace_to_kinds .setdefault (namespace , []).append (('srv' , specs ))
233+ for namespace , specs in actions_by_namespace .items ():
234+ namespace_to_kinds .setdefault (namespace , []).append (('action' , specs ))
235+
236+ _validate_single_kind_per_namespace (namespace_to_kinds )
237+
238+ for namespace in sorted (namespace_to_kinds ):
239+ kinds = namespace_to_kinds [namespace ]
240+ kind , specs = kinds [0 ]
241+ _expand_namespace_templates (
242+ template_dir , args ['output_dir' ], namespace , kind , specs ,
243+ latest_target_timestamp , data )
244+
245+ data ['generated_namespaces' ] = sorted (namespace_to_kinds )
246+ data ['msg_specs' ] = [
247+ (_namespace_from_message (message ), message )
248+ for message in message_specs ]
249+ data ['srv_specs' ] = [
250+ (_namespace_from_service (service ), service )
251+ for service in service_specs ]
252+ data ['action_specs' ] = [
253+ (_namespace_from_action (action ), action )
254+ for action in action_specs ]
196255
197256 rosidl_pycommon .expand_template (
198257 os .path .join (template_dir , 'lib.rs.em' ),
0 commit comments