stinger-ipc 0.0.10__py3-none-any.whl → 0.0.26__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. {stinger_ipc-0.0.10.dist-info → stinger_ipc-0.0.26.dist-info}/METADATA +3 -2
  2. {stinger_ipc-0.0.10.dist-info → stinger_ipc-0.0.26.dist-info}/RECORD +35 -29
  3. stingeripc/asyncapi.py +3 -0
  4. stingeripc/components.py +61 -10
  5. stingeripc/schema/schema.yaml +240 -0
  6. stingeripc/templates/cpp/examples/client_main.cpp.jinja2 +18 -0
  7. stingeripc/templates/cpp/include/broker.hpp.jinja2 +18 -10
  8. stingeripc/templates/cpp/include/client.hpp.jinja2 +80 -11
  9. stingeripc/templates/cpp/include/ibrokerconnection.hpp.jinja2 +24 -4
  10. stingeripc/templates/cpp/include/property_structs.hpp.jinja2 +40 -2
  11. stingeripc/templates/cpp/include/server.hpp.jinja2 +5 -1
  12. stingeripc/templates/cpp/include/structs.hpp.jinja2 +2 -0
  13. stingeripc/templates/cpp/partials/args.jinja2 +11 -0
  14. stingeripc/templates/cpp/partials/deserialize.jinja2 +42 -0
  15. stingeripc/templates/cpp/partials/serialize.jinja2 +18 -0
  16. stingeripc/templates/cpp/src/broker.cpp.jinja2 +46 -24
  17. stingeripc/templates/cpp/src/client.cpp.jinja2 +111 -38
  18. stingeripc/templates/cpp/src/property_structs.cpp.jinja2 +25 -0
  19. stingeripc/templates/cpp/src/server.cpp.jinja2 +17 -25
  20. stingeripc/templates/cpp/src/structs.cpp.jinja2 +13 -0
  21. stingeripc/templates/html/app.js.jinja2 +130 -29
  22. stingeripc/templates/html/index.html.jinja2 +96 -8
  23. stingeripc/templates/html/styles.css.jinja2 +135 -0
  24. stingeripc/templates/markdown/index.md.jinja2 +151 -10
  25. stingeripc/templates/python/server.py.jinja2 +138 -18
  26. stingeripc/templates/rust/client/src/lib.rs.jinja2 +24 -5
  27. stingeripc/templates/rust/server/examples/server.rs.jinja2 +59 -23
  28. stingeripc/templates/rust/server/src/lib.rs.jinja2 +72 -51
  29. stingeripc/tools/cli.py +33 -7
  30. stingeripc/tools/cpp_generator.py +90 -0
  31. stingeripc/tools/rust_generator.py +1 -1
  32. stingeripc/templates/rust/payloads/src/handler.rs.jinja2 +0 -0
  33. {stinger_ipc-0.0.10.dist-info → stinger_ipc-0.0.26.dist-info}/WHEEL +0 -0
  34. {stinger_ipc-0.0.10.dist-info → stinger_ipc-0.0.26.dist-info}/entry_points.txt +0 -0
  35. {stinger_ipc-0.0.10.dist-info → stinger_ipc-0.0.26.dist-info}/licenses/LICENSE +0 -0
  36. {stinger_ipc-0.0.10.dist-info → stinger_ipc-0.0.26.dist-info}/top_level.txt +0 -0
@@ -5,14 +5,22 @@ on the next generation.
5
5
  This is the Server for the {{stinger.name}} interface.
6
6
  */
7
7
 
8
+
9
+
8
10
  use mqttier::{MqttierClient{%if stinger.methods|length > 0 or stinger.properties|length > 0 %}, ReceivedMessage{%endif%}};
9
11
 
10
12
  #[allow(unused_imports)]
11
13
  use {{stinger.rust.common_package_name}}::payloads::{*, MethodResultCode};
14
+ use std::any::Any;
15
+ {%if stinger.methods|length > 0 %}
16
+ //pub mod handler;
17
+ //pub mod init;
18
+ //pub use handler::{{stinger.name | UpperCamelCase }}MethodHandlers;
19
+ use std::sync::{Arc, Mutex};
20
+ {%endif%}
12
21
  {%if stinger.methods|length > 0 or stinger.properties|length > 0 %}
13
22
  use serde_json;
14
- use std::sync::{Arc, Mutex};
15
- use tokio::sync::mpsc;
23
+ use tokio::sync::{mpsc{%if stinger.properties|length > 0 %}, watch{%endif%}};
16
24
  {%endif%}
17
25
  use tokio::task::JoinError;
18
26
 
@@ -28,14 +36,7 @@ struct {{stinger.name | UpperCamelCase }}ServerSubscriptionIds {
28
36
  {{prop_name | snake_case}}_property_update: usize,
29
37
  {%endif%}{%endfor%}
30
38
  }
31
- {%if stinger.methods|length > 0 %}
32
- #[derive(Clone)]
33
- struct {{stinger.name | UpperCamelCase }}ServerMethodHandlers {
34
- {%for method_name, method in stinger.methods.items()-%}
35
- /// Pointer to a function to handle the {{method_name}} method request.
36
- method_handler_for_{{method_name|snake_case}}: Arc<Mutex<Box<dyn Fn({%for arg in method.arg_list%}{{arg.rust_type}}{%if not loop.last%}, {%endif%}{%endfor%})->Result<{{method.return_value_rust_type}}, MethodResultCode> + Send>>>,
37
- {%endfor%}
38
- }{%endif%}{# method handlers struct #}
39
+
39
40
  {%if stinger.properties | length > 0%}
40
41
  #[derive(Clone)]
41
42
  struct {{stinger.name | UpperCamelCase}}Properties {
@@ -43,8 +44,10 @@ struct {{stinger.name | UpperCamelCase}}Properties {
43
44
  {{prop_name | snake_case}}_topic: Arc<String>,
44
45
  {%-if prop.arg_list | length > 1%}
45
46
  {{prop_name | snake_case}}: Arc<Mutex<Option<{{prop.rust_type}}>>>,
47
+ {{prop_name | snake_case}}_tx_channel: watch::Sender<Option<{{prop.rust_type}}>>,
46
48
  {%-else%}
47
49
  {{prop_name | snake_case}}: Arc<Mutex<Option<{{prop.arg_list[0].rust_type}}>>>,
50
+ {{prop_name | snake_case}}_tx_channel: watch::Sender<Option<{{prop.arg_list[0].rust_type}}>>,
48
51
  {%endif%}
49
52
  {%-endfor%}
50
53
  }
@@ -64,8 +67,9 @@ pub struct {{stinger.rust.server_struct_name}} {
64
67
  msg_streamer_tx: mpsc::Sender<ReceivedMessage>,
65
68
  {%endif%}
66
69
  {%if stinger.methods|length > 0 %}
67
- /// Struct contains all the handlers for the various methods.
68
- method_handlers: {{stinger.name | UpperCamelCase }}ServerMethodHandlers,{%endif%}
70
+ /// Struct contains all the method handlers.
71
+ method_handlers: Arc<Mutex<Box<dyn {{stinger.name | UpperCamelCase }}MethodHandlers>>>,
72
+ {%endif%}
69
73
  {%if stinger.properties|length > 0%}
70
74
  /// Struct contains all the properties.
71
75
  properties: {{stinger.name | UpperCamelCase}}Properties,
@@ -81,7 +85,7 @@ pub struct {{stinger.rust.server_struct_name}} {
81
85
  }
82
86
 
83
87
  impl {{stinger.rust.server_struct_name}} {
84
- pub async fn new(connection: &mut MqttierClient) -> Self {
88
+ pub async fn new(connection: &mut MqttierClient{%if stinger.methods|length > 0%}, method_handlers: Arc<Mutex<Box<dyn {{stinger.name | UpperCamelCase }}MethodHandlers>>> {%endif%}) -> Self {
85
89
  {%if stinger.methods|length > 0 or stinger.properties|length > 0 %}
86
90
  // Create a channel for messages to get from the MqttierClient object to this {{stinger.rust.server_struct_name}} object.
87
91
  // The Connection object uses a clone of the tx side of the channel.
@@ -97,14 +101,6 @@ impl {{stinger.rust.server_struct_name}} {
97
101
  let subscription_id_{{prop_name | snake_case}}_property_update = subscription_id_{{prop_name | snake_case}}_property_update.unwrap_or_else(|_| usize::MAX);
98
102
  {%else-%}
99
103
  {%endif%}{%endfor%}
100
- {%if stinger.methods | length > 0 %}
101
- // Create structure for method handlers.
102
- let method_handlers = {{stinger.name | UpperCamelCase }}ServerMethodHandlers {
103
- {%-for method_name, method in stinger.methods.items()-%}
104
- method_handler_for_{{method_name|snake_case}}: Arc::new(Mutex::new(Box::new( |{%for arg in method.arg_list-%}_{{loop.index}}{%if not loop.last%}, {%endif%}{%endfor%}| { Err(MethodResultCode::ServerError) } ))),
105
- {%endfor%}
106
- };
107
- {%endif%}{# method handlers #}
108
104
 
109
105
  // Create structure for subscription ids.
110
106
  let sub_ids = {{stinger.name | UpperCamelCase }}ServerSubscriptionIds {
@@ -121,11 +117,10 @@ impl {{stinger.rust.server_struct_name}} {
121
117
  let property_values = {{stinger.name | UpperCamelCase}}Properties {
122
118
  {%-for prop_name, prop in stinger.properties.items()%}
123
119
  {{prop_name | snake_case}}_topic: Arc::new(String::from("{{prop.value_topic}}")),
124
- {%if prop.arg_list | length > 1 -%}
125
- {{prop_name | snake_case}}: Arc::new(Mutex::new(None)),
126
- {%else%}
120
+
127
121
  {{prop_name | snake_case}}: Arc::new(Mutex::new(None)),
128
- {%-endif%}
122
+ {{prop_name | snake_case}}_tx_channel: watch::channel(None).0,
123
+
129
124
  {%-endfor%}
130
125
  };
131
126
  {%endif%}
@@ -148,44 +143,45 @@ impl {{stinger.rust.server_struct_name}} {
148
143
  pub async fn emit_{{sig_name|snake_case}}(&mut self, {%for arg in sig.arg_list%}{{arg.name|snake_case}}: {{arg.rust_type}}{%if not loop.last%}, {%endif%}{%endfor%}) {
149
144
  let data = {{sig_name|UpperCamelCase}}SignalPayload {
150
145
  {%for arg in sig.arg_list%}
151
- {{arg.name}}: {{arg.name|snake_case}},
146
+ {{arg.name}}: {{arg.name|snake_case}},
152
147
  {%endfor%}
153
148
  };
154
149
  let _ = self.mqttier_client.publish_structure("{{sig.topic}}".to_string(), &data).await;
155
150
  }
156
151
  {%endfor%}
157
152
 
158
- {%for method_name, method in stinger.methods.items()-%}
159
- /// Sets the function to be called when a request for the {{method_name}} method is received.
160
- pub fn set_method_handler_for_{{method_name|snake_case}}(&mut self, cb: impl Fn({%for arg in method.arg_list%}{{arg.rust_type}}{%if not loop.last%}, {%endif%}{%endfor%})->Result<{{method.return_value_rust_type}}, MethodResultCode> + 'static + Send) {
161
- self.method_handlers.method_handler_for_{{method_name|snake_case}} = Arc::new(Mutex::new(Box::new(cb)));
162
- }
163
- {%endfor%}
164
-
165
153
  {%for method_name, method in stinger.methods.items()-%}
166
154
  /// Handles a request message for the {{method_name}} method.
167
- async fn handle_{{method_name|snake_case}}_request(publisher: MqttierClient, handlers: &mut {{stinger.name | UpperCamelCase }}ServerMethodHandlers, msg: ReceivedMessage) {
155
+ async fn handle_{{method_name|snake_case}}_request(publisher: MqttierClient, handlers: Arc<Mutex<Box<dyn {{stinger.name | UpperCamelCase }}MethodHandlers>>>, msg: ReceivedMessage) {
168
156
  let opt_corr_data = msg.correlation_data;
169
157
  let opt_resp_topic = msg.response_topic;
170
- {%if method.return_value_type != false -%}
158
+ {%if method.arg_list | length > 0 -%}
171
159
  let payload_vec = msg.payload;
172
160
  let payload = serde_json::from_slice::<{{method_name | UpperCamelCase}}RequestObject>(&payload_vec).unwrap();
173
- {%endif%}
161
+ {%endif%}{# has arg_list #}
174
162
  // call the method handler
175
- let rv: {{method.return_value_rust_type}} = {
176
- let func_guard = handlers.method_handler_for_{{method_name|snake_case}}.lock().unwrap();
177
- (*func_guard)({%for arg in method.arg_list%}payload.{{arg.name}}{%if not loop.last%}, {%endif%}{%endfor%}).unwrap()
163
+ let rv: Result<{{method.return_value_rust_type}}, MethodResultCode> = {
164
+ let handler_guard = handlers.lock().unwrap();
165
+ (*handler_guard).handle_{{method_name|snake_case}}({%for arg in method.arg_list%}payload.{{arg.name}}{%if not loop.last%}, {%endif%}{%endfor%})
178
166
  };
179
167
 
180
- {%-if method.return_value_type == "primitive" -%}
181
- let rv = {{method_name | UpperCamelCase}}ReturnValue {
182
- {{method.return_value_property_name}}: rv,
183
- };
184
- {%endif%}
185
-
186
168
  if let Some(resp_topic) = opt_resp_topic {
187
169
  let corr_data = opt_corr_data.unwrap_or_default();
188
- publisher.publish_response(resp_topic, &rv, corr_data).await.expect("Failed to publish response structure");
170
+ match rv {
171
+ Ok({%if method.return_value_type is false %}_{%else%}retval{%endif%}) => {
172
+ {%-if method.return_value_type == "primitive" %}
173
+ let retval = {{method_name | UpperCamelCase}}ReturnValue {
174
+ {{method.return_value_property_name}}: retval,
175
+ };
176
+ {%elif method.return_value_type is false %}
177
+ let retval = {{method_name | UpperCamelCase}}ReturnValue {};
178
+ {%endif%}
179
+ publisher.publish_response(resp_topic, &retval, corr_data).await.expect("Failed to publish response structure");
180
+ }
181
+ Err(err) => {
182
+ eprintln!("Error occurred while handling {}: {:?}", stringify!({{method_name}}), err);
183
+ }
184
+ }
189
185
  } else {
190
186
  eprintln!("No response topic found in message properties.");
191
187
  }
@@ -206,7 +202,7 @@ impl {{stinger.rust.server_struct_name}} {
206
202
  {%endif%}
207
203
  }
208
204
  {%if not prop.read_only %}
209
- async fn update_{{prop_name}}_value(publisher: MqttierClient, topic: Arc<String>, data: Arc<Mutex<Option<{{prop.rust_type}}>>>, msg: ReceivedMessage)
205
+ async fn update_{{prop_name}}_value(publisher: MqttierClient, topic: Arc<String>, data: Arc<Mutex<Option<{{prop.rust_type}}>>>, watch_sender: watch::Sender<Option<{{prop.rust_type}}>>, msg: ReceivedMessage)
210
206
  {
211
207
  let payload_str = String::from_utf8_lossy(&msg.payload).to_string();
212
208
  let new_data: {{prop_name | UpperCamelCase}}Property = serde_json::from_str(&payload_str).unwrap();
@@ -223,10 +219,17 @@ impl {{stinger.rust.server_struct_name}} {
223
219
  {%-else%}
224
220
  let data2 = new_data;
225
221
  {%endif%}
222
+ let data_to_send_to_watchers = data2.clone();
223
+ let _ = watch_sender.send(Some(data_to_send_to_watchers));
226
224
  let _ = tokio::spawn(async move {
227
225
  {{stinger.rust.server_struct_name}}::publish_{{prop_name}}_value(publisher2, topic2, data2).await;
228
226
  });
229
227
  }
228
+
229
+ pub async fn watch_{{prop_name}}(&self) -> watch::Receiver<Option<{{prop.rust_type}}>> {
230
+ self.properties.{{prop_name}}_tx_channel.subscribe()
231
+ }
232
+
230
233
  {%endif%}{# not read only property #}
231
234
  pub async fn set_{{prop_name}}(&mut self, data: {{prop.rust_type}}) {
232
235
  println!("Setting {{prop_name}} of type {{prop.rust_type}}");
@@ -236,6 +239,9 @@ impl {{stinger.rust.server_struct_name}} {
236
239
  *locked_data = Some(data.clone());
237
240
  }
238
241
 
242
+ let data_to_send_to_watchers = data.clone();
243
+ let _ = self.properties.{{prop_name}}_tx_channel.send(Some(data_to_send_to_watchers));
244
+
239
245
  let publisher2 = self.mqttier_client.clone();
240
246
  let topic2 = self.properties.{{prop_name}}_topic.as_ref().clone();
241
247
  let _ = tokio::spawn(async move {
@@ -249,7 +255,7 @@ impl {{stinger.rust.server_struct_name}} {
249
255
  /// In the task, it loops over messages received from the rx side of the message_receiver channel.
250
256
  /// Based on the subscription id of the received message, it will call a function to handle the
251
257
  /// received message.
252
- pub async fn receive_loop(&mut self) -> Result<(), JoinError> {
258
+ pub async fn run_loop(&mut self) -> Result<(), JoinError> {
253
259
  // Make sure the MqttierClient is connected and running.
254
260
  let _ = self.mqttier_client.run_loop().await;
255
261
 
@@ -260,7 +266,8 @@ impl {{stinger.rust.server_struct_name}} {
260
266
  };
261
267
 
262
268
  {%if stinger.methods|length > 0 -%}
263
- let mut method_handlers = self.method_handlers.clone();
269
+ let method_handlers = self.method_handlers.clone();
270
+ self.method_handlers.lock().unwrap().initialize(self.clone()).expect("Failed to initialize method handlers");
264
271
  {%endif-%}
265
272
 
266
273
  let sub_ids = self.subscription_ids.clone();
@@ -277,12 +284,12 @@ impl {{stinger.rust.server_struct_name}} {
277
284
  while let Some(msg) = message_receiver.recv().await {
278
285
  {%-for method_name, method in stinger.methods.items()%}
279
286
  {%if not loop.first%}else {%endif%}if msg.subscription_id == sub_ids.{{method_name | snake_case}}_method_req {
280
- {{stinger.rust.server_struct_name}}::handle_{{method_name|snake_case}}_request(publisher.clone(), &mut method_handlers, msg).await;
287
+ {{stinger.rust.server_struct_name}}::handle_{{method_name|snake_case}}_request(publisher.clone(), method_handlers.clone(), msg).await;
281
288
  }
282
289
  {%-endfor%}{# methods #}
283
290
  {%-for prop_name, prop in stinger.properties.items()%}{%if not prop.read_only %}
284
291
  {%if not loop.first or (stinger.methods | length > 0) %}else {%endif%}if msg.subscription_id == sub_ids.{{prop_name | snake_case}}_property_update {
285
- {{stinger.rust.server_struct_name}}::update_{{prop_name | snake_case}}_value(publisher.clone(), properties.{{prop_name | snake_case}}_topic.clone(), properties.{{prop_name | snake_case}}.clone(), msg).await;
292
+ {{stinger.rust.server_struct_name}}::update_{{prop_name | snake_case}}_value(publisher.clone(), properties.{{prop_name | snake_case}}_topic.clone(), properties.{{prop_name | snake_case}}.clone(), properties.{{prop_name | snake_case}}_tx_channel.clone(), msg).await;
286
293
  }
287
294
  {%-endif%}{%endfor%}{# properties #}
288
295
  }
@@ -296,3 +303,17 @@ impl {{stinger.rust.server_struct_name}} {
296
303
  }
297
304
 
298
305
  }
306
+ {%if stinger.methods|length > 0 %}
307
+ pub trait {{stinger.name | UpperCamelCase }}MethodHandlers: Send + Sync {
308
+
309
+ fn initialize(&mut self, server: {{stinger.rust.server_struct_name}}) -> Result<(), MethodResultCode>;
310
+
311
+ {%for method_name, method in stinger.methods.items()-%}
312
+ /// Pointer to a function to handle the {{method_name}} method request.
313
+ fn handle_{{method_name|snake_case}}(&self, {%for arg in method.arg_list%}{{arg.name|snake_case}}: {{arg.rust_type}}{%if not loop.last%}, {%endif%}{%endfor%}) -> Result<{{method.return_value_rust_type}}, MethodResultCode>;
314
+
315
+ {%endfor%}
316
+
317
+ fn as_any(&self) -> &dyn Any;
318
+ }
319
+ {%endif%}{# methods #}
stingeripc/tools/cli.py CHANGED
@@ -1,17 +1,21 @@
1
1
 
2
2
  import os
3
+ import sys
4
+ from wsgiref.validate import validator
3
5
  from rich import print
4
6
  from pathlib import Path
5
7
  import typer
6
8
  from typing_extensions import Annotated
7
9
  from jacobsjinjatoo import templator as jj2
8
-
10
+ from jacobsjsonschema.draft7 import Validator
11
+ import yaml
12
+ import yamlloader
9
13
  from stingeripc.interface import StingerInterface
10
14
 
11
15
  from . import markdown_generator
12
16
  from . import python_generator
13
17
  from . import rust_generator
14
-
18
+ from . import cpp_generator
15
19
 
16
20
  app = typer.Typer(help="stinger-ipc generator CLI")
17
21
 
@@ -24,13 +28,13 @@ def generate(
24
28
  ):
25
29
  """Generate code for a Stinger interface.
26
30
 
27
- LANGUAGE must be one of: rust, python, markdown
31
+ LANGUAGE must be one of: rust, python, markdown, cpp, web
28
32
  INPUT_FILE is the .stinger.yaml file
29
33
  OUTPUT_DIR is the directory that will receive generated files
30
34
  """
31
35
  lang = language.lower()
32
- if lang not in ("rust", "python", "markdown", "web"):
33
- raise typer.BadParameter("language must be one of: rust, python, markdown, web")
36
+ if lang not in ("rust", "python", "markdown", "cpp", "web"):
37
+ raise typer.BadParameter("language must be one of: rust, python, markdown, cpp, web")
34
38
 
35
39
  if lang == "python":
36
40
  # python_generator.main expects Path arguments via typer
@@ -55,12 +59,34 @@ def generate(
55
59
  ]:
56
60
  ct.render_template(f"{output_file}.jinja2", output_file, stinger=stinger)
57
61
  wt.render_template("index.html.jinja2", "index.html", stinger=stinger)
58
- else: # rust
62
+ elif lang == "cpp":
63
+ cpp_generator.main(input_file, output_dir)
64
+ elif lang == "rust":
59
65
  rust_generator.main(input_file, output_dir)
60
-
66
+ else:
67
+ raise RuntimeError("Unreachable code reached")
61
68
 
62
69
  print(f"Generation for '{lang}' completed.")
63
70
 
71
+ @app.command()
72
+ def validate(input_file: Annotated[Path, typer.Argument(..., exists=True, file_okay=True, dir_okay=False, readable=True)]):
73
+ """Validate a Stinger interface YAML file.
74
+
75
+ INPUT_FILE is the .stinger.yaml file
76
+ """
77
+ schema_file = Path(__file__).parent.parent / "schema" / "schema.yaml"
78
+ schema_obj = yaml.load(schema_file.open("r"), Loader=yamlloader.ordereddict.Loader)
79
+ validator = Validator(schema_obj, lazy_error_reporting=False)
80
+
81
+ input_obj = yaml.load(input_file.open("r"), Loader=yamlloader.ordereddict.Loader)
82
+ if result := validator.validate(input_obj):
83
+ print("Validated: ", result)
84
+ else:
85
+ for error in validator.get_errors():
86
+ print(error)
87
+ sys.exit(1)
88
+ sys.exit(0)
89
+
64
90
  @app.command()
65
91
  def hello():
66
92
  print("Hello world")
@@ -0,0 +1,90 @@
1
+ from jacobsjinjatoo import templator as jj2
2
+ import os
3
+ import yaml
4
+ import typer
5
+ from typing_extensions import Annotated
6
+ from pathlib import Path
7
+ from rich import print
8
+
9
+ from stingeripc import StingerInterface
10
+
11
+
12
+ def main(
13
+ inname: Annotated[Path, typer.Argument(exists=True, file_okay=True, dir_okay=False, readable=True)],
14
+ outdir: Annotated[Path, typer.Argument(file_okay=False, dir_okay=True, writable=True, readable=True)],
15
+ ):
16
+ """Generate C++ output for a Stinger interface."""
17
+
18
+ with inname.open(mode="r") as f:
19
+ stinger = StingerInterface.from_yaml(f)
20
+
21
+ params = {
22
+ "stinger": stinger,
23
+ "source_files": [],
24
+ "header_files": [],
25
+ }
26
+
27
+ if outdir.is_file():
28
+ raise RuntimeError("Output directory is a file!")
29
+
30
+ if not outdir.is_dir():
31
+ outdir.mkdir(parents=True)
32
+
33
+ source_code_dir = outdir / "src"
34
+ source_code_dir.mkdir(parents=True, exist_ok=True)
35
+
36
+ headers_code_dir = outdir / "include"
37
+ headers_code_dir.mkdir(parents=True, exist_ok=True)
38
+
39
+ examples_code_dir = outdir / "examples"
40
+ examples_code_dir.mkdir(parents=True, exist_ok=True)
41
+
42
+ this_file = Path(__file__)
43
+ # The templates live under the package `stingeripc/templates/cpp` next to `tools`.
44
+ # compute that from the tools directory: tools -> parent (stingeripc) -> templates/cpp
45
+ template_dir = (this_file.parent / ".." / "templates" / "cpp").resolve()
46
+
47
+ if not template_dir.is_dir():
48
+ raise RuntimeError(f"C++ template directory not found: {template_dir}")
49
+
50
+ output_dir = outdir.resolve()
51
+ t = jj2.CodeTemplator(output_dir=output_dir)
52
+ t.add_template_dir(template_dir)
53
+
54
+ headers_template_dir = template_dir / "include"
55
+ for fname in os.listdir(headers_template_dir):
56
+ header_code_path = Path("include") / fname
57
+ if fname.endswith(".jinja2"):
58
+ print(f"GENERATING HEADER: {fname}")
59
+ header_dest_path = str(header_code_path)[:-len(".jinja2")]
60
+ params["header_files"].append(header_dest_path)
61
+ t.render_template(header_code_path, header_dest_path, **params)
62
+
63
+ src_template_dir = template_dir / "src"
64
+ for fname in os.listdir(src_template_dir):
65
+ source_code_path = Path("src") / fname
66
+ if fname.endswith(".jinja2"):
67
+ print(f"GENERATING SOURCE: {fname}")
68
+ source_dest_path = str(source_code_path)[:-len(".jinja2")]
69
+ params["source_files"].append(source_dest_path)
70
+ t.render_template(source_code_path, source_dest_path, **params)
71
+
72
+ example_template_dir = template_dir / "examples"
73
+ for fname in os.listdir(example_template_dir):
74
+ example_code_path = Path("examples") / fname
75
+ if fname.endswith(".jinja2"):
76
+ print(f"GENERATING EXAMPLE: {fname}")
77
+ example_dest_path = str(example_code_path)[:-len(".jinja2")]
78
+ t.render_template(example_code_path, example_dest_path, **params)
79
+
80
+ print(f"GENERATING CmakeLists")
81
+ t.render_template("CMakeLists.txt.jinja2", "CMakeLists.txt", **params)
82
+
83
+
84
+ def run():
85
+ typer.run(main)
86
+
87
+
88
+ if __name__ == "__main__":
89
+ run()
90
+
@@ -35,7 +35,7 @@ def main(
35
35
  t = jj2.CodeTemplator(output_dir=outdir)
36
36
  t.add_template_dir(template_dir)
37
37
 
38
- def recursive_render_templates(local_dir: str):
38
+ def recursive_render_templates(local_dir: str|Path):
39
39
  local_dir = Path(local_dir)
40
40
  cur_template_dir = template_dir / local_dir
41
41
  for entry in os.listdir(cur_template_dir):
File without changes