stinger-ipc 0.0.5__py3-none-any.whl → 0.0.7__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 (28) hide show
  1. {stinger_ipc-0.0.5.dist-info → stinger_ipc-0.0.7.dist-info}/METADATA +2 -2
  2. {stinger_ipc-0.0.5.dist-info → stinger_ipc-0.0.7.dist-info}/RECORD +27 -23
  3. {stinger_ipc-0.0.5.dist-info → stinger_ipc-0.0.7.dist-info}/entry_points.txt +1 -0
  4. stingeripc/components.py +5 -5
  5. stingeripc/lang_symb.py +21 -4
  6. stingeripc/templates/html/app.js.jinja2 +117 -0
  7. stingeripc/templates/html/index.html.jinja2 +38 -0
  8. stingeripc/templates/html/style.css.jinja2 +187 -0
  9. stingeripc/templates/rust/Cargo.toml.jinja2 +1 -1
  10. stingeripc/templates/rust/client/Cargo.toml.jinja2 +7 -5
  11. stingeripc/templates/rust/client/examples/client.rs.jinja2 +9 -10
  12. stingeripc/templates/rust/client/src/lib.rs.jinja2 +112 -55
  13. stingeripc/templates/rust/{connection → payloads}/Cargo.toml.jinja2 +8 -4
  14. stingeripc/templates/rust/{connection → payloads}/examples/pub_and_recv.rs.jinja2 +11 -11
  15. stingeripc/templates/rust/payloads/src/lib.rs.jinja2 +4 -0
  16. stingeripc/templates/rust/{connection → payloads}/src/payloads.rs.jinja2 +10 -2
  17. stingeripc/templates/rust/server/Cargo.toml.jinja2 +4 -4
  18. stingeripc/templates/rust/server/examples/server.rs.jinja2 +8 -12
  19. stingeripc/templates/rust/server/src/lib.rs.jinja2 +92 -69
  20. stingeripc/tools/cli.py +73 -0
  21. stingeripc/tools/markdown_generator.py +4 -2
  22. stingeripc/tools/python_generator.py +12 -10
  23. stingeripc/tools/rust_generator.py +46 -27
  24. stingeripc/templates/rust/connection/src/lib.rs.jinja2 +0 -262
  25. {stinger_ipc-0.0.5.dist-info → stinger_ipc-0.0.7.dist-info}/WHEEL +0 -0
  26. {stinger_ipc-0.0.5.dist-info → stinger_ipc-0.0.7.dist-info}/licenses/LICENSE +0 -0
  27. {stinger_ipc-0.0.5.dist-info → stinger_ipc-0.0.7.dist-info}/top_level.txt +0 -0
  28. /stingeripc/templates/rust/{connection → payloads}/src/handler.rs.jinja2 +0 -0
@@ -5,39 +5,38 @@ on the next generation.
5
5
  This is the Server for the {{stinger.name}} interface.
6
6
  */
7
7
 
8
- extern crate paho_mqtt as mqtt;
9
- use connection::{MessagePublisher, Connection, ReceivedMessage};
8
+ use mqttier::{MqttierClient{%if stinger.methods|length > 0 or stinger.properties|length > 0 %}, ReceivedMessage{%endif%}};
10
9
 
11
10
  #[allow(unused_imports)]
12
- use connection::payloads::{*, MethodResultCode};
13
-
11
+ use {{stinger.rust.common_package_name}}::payloads::{*, MethodResultCode};
12
+ {%if stinger.methods|length > 0 or stinger.properties|length > 0 %}
13
+ use serde_json;
14
14
  use std::sync::{Arc, Mutex};
15
- use tokio::sync::{mpsc};
16
- use tokio::join;
15
+ use tokio::sync::mpsc;
16
+ {%endif%}
17
17
  use tokio::task::JoinError;
18
- use serde::{Serialize, Deserialize};
19
- use serde_json;
20
18
 
19
+ {%if stinger.methods|length > 0 or stinger.properties|length > 0 %}
21
20
  /// This struct is used to store all the MQTTv5 subscription ids
22
21
  /// for the subscriptions the client will make.
23
22
  #[derive(Clone, Debug)]
24
23
  struct {{stinger.name | UpperCamelCase }}ServerSubscriptionIds {
25
24
  {%for method_name, method in stinger.methods.items()-%}
26
- {{method_name | snake_case}}_method_req: i32,
25
+ {{method_name | snake_case}}_method_req: usize,
27
26
  {%endfor%}
28
27
  {%for prop_name, prop in stinger.properties.items()-%}{%if not prop.read_only %}
29
- {{prop_name | snake_case}}_property_update: i32,
28
+ {{prop_name | snake_case}}_property_update: usize,
30
29
  {%endif%}{%endfor%}
31
30
  }
32
-
31
+ {%if stinger.methods|length > 0 %}
33
32
  #[derive(Clone)]
34
33
  struct {{stinger.name | UpperCamelCase }}ServerMethodHandlers {
35
34
  {%for method_name, method in stinger.methods.items()-%}
36
35
  /// Pointer to a function to handle the {{method_name}} method request.
37
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>>>,
38
37
  {%endfor%}
39
- }
40
-
38
+ }{%endif%}{# method handlers struct #}
39
+ {%if stinger.properties | length > 0%}
41
40
  #[derive(Clone)]
42
41
  struct {{stinger.name | UpperCamelCase}}Properties {
43
42
  {%for prop_name, prop in stinger.properties.items()-%}
@@ -49,63 +48,64 @@ struct {{stinger.name | UpperCamelCase}}Properties {
49
48
  {%endif%}
50
49
  {%-endfor%}
51
50
  }
51
+ {%endif%}
52
+ {%endif%}{# any methods or properties #}
52
53
 
53
- pub struct {{stinger.name | UpperCamelCase}}Server {
54
+ pub struct {{stinger.rust.server_struct_name}} {
55
+ mqttier_client: MqttierClient,
56
+ {%if stinger.methods|length > 0 or stinger.properties|length > 0 %}
54
57
  /// Temporarily holds the receiver for the MPSC channel. The Receiver will be moved
55
58
  /// to a process loop when it is needed. MQTT messages will be received with this.
56
59
  msg_streamer_rx: Option<mpsc::Receiver<ReceivedMessage>>,
57
60
 
58
61
  /// The Sender side of MQTT messages that are received from the broker. This tx
59
62
  /// side is cloned for each subscription made.
63
+ #[allow(dead_code)]
60
64
  msg_streamer_tx: mpsc::Sender<ReceivedMessage>,
61
-
62
- /// Through this MessagePublisher object, we can publish messages to MQTT.
63
- msg_publisher: MessagePublisher,
64
-
65
+ {%endif%}
66
+ {%if stinger.methods|length > 0 %}
65
67
  /// Struct contains all the handlers for the various methods.
66
- method_handlers: {{stinger.name | UpperCamelCase }}ServerMethodHandlers,
68
+ method_handlers: {{stinger.name | UpperCamelCase }}ServerMethodHandlers,{%endif%}
67
69
  {%if stinger.properties|length > 0%}
68
70
  /// Struct contains all the properties.
69
71
  properties: {{stinger.name | UpperCamelCase}}Properties,
70
72
  {%endif%}
73
+ {%if stinger.methods|length > 0 or stinger.properties|length > 0 %}
71
74
  /// Subscription IDs for all the subscriptions this makes.
72
75
  subscription_ids: {{stinger.name | UpperCamelCase }}ServerSubscriptionIds,
76
+ {%endif%}
73
77
 
74
78
  /// Copy of MQTT Client ID
75
- client_id: String,
79
+ #[allow(dead_code)]
80
+ pub client_id: String,
76
81
  }
77
82
 
78
- impl {{stinger.name | UpperCamelCase}}Server {
79
- pub async fn new(connection: &mut Connection) -> Self {
80
- let _ = connection.connect().await.expect("Could not connect to MQTT broker");
81
-
82
- //let interface_info = String::from(r#"{{stinger.interface_info.1 | tojson}}"#);
83
- //connection.publish("{{stinger.interface_info.0}}".to_string(), interface_info, 1).await;
84
-
85
- // Create a channel for messages to get from the Connection object to this {{stinger.name | UpperCamelCase }}Client object.
83
+ impl {{stinger.rust.server_struct_name}} {
84
+ pub async fn new(connection: &mut MqttierClient) -> Self {
85
+ {%if stinger.methods|length > 0 or stinger.properties|length > 0 %}
86
+ // Create a channel for messages to get from the MqttierClient object to this {{stinger.rust.server_struct_name}} object.
86
87
  // The Connection object uses a clone of the tx side of the channel.
87
- let (message_received_tx, message_received_rx) = mpsc::channel(64);
88
-
89
- let publisher = connection.get_publisher();
88
+ let (message_received_tx, message_received_rx) = mpsc::channel::<ReceivedMessage>(64);
90
89
 
91
90
  // Create method handler struct
92
91
  {%for method_name, method in stinger.methods.items()-%}
93
- let subscription_id_{{method_name | snake_case}}_method_req = connection.subscribe("{{method.topic}}", message_received_tx.clone()).await;
94
- let subscription_id_{{method_name | snake_case}}_method_req = subscription_id_{{method_name | snake_case}}_method_req.unwrap_or_else(|_| -1);
92
+ let subscription_id_{{method_name | snake_case}}_method_req = connection.subscribe("{{method.topic}}".to_string(), 2, message_received_tx.clone()).await;
93
+ let subscription_id_{{method_name | snake_case}}_method_req = subscription_id_{{method_name | snake_case}}_method_req.unwrap_or_else(|_| usize::MAX);
95
94
  {%endfor%}
96
-
97
95
  {%for prop_name, prop in stinger.properties.items()-%}{%if not prop.read_only %}
98
- let subscription_id_{{prop_name | snake_case}}_property_update = connection.subscribe("{{prop.update_topic}}", message_received_tx.clone()).await;
99
- let subscription_id_{{prop_name | snake_case}}_property_update = subscription_id_{{prop_name | snake_case}}_property_update.unwrap_or_else(|_| -1);
96
+ let subscription_id_{{prop_name | snake_case}}_property_update = connection.subscribe("{{prop.update_topic}}".to_string(), 2, message_received_tx.clone()).await;
97
+ let subscription_id_{{prop_name | snake_case}}_property_update = subscription_id_{{prop_name | snake_case}}_property_update.unwrap_or_else(|_| usize::MAX);
98
+ {%else-%}
100
99
  {%endif%}{%endfor%}
101
-
100
+ {%if stinger.methods | length > 0 %}
102
101
  // Create structure for method handlers.
103
102
  let method_handlers = {{stinger.name | UpperCamelCase }}ServerMethodHandlers {
104
103
  {%-for method_name, method in stinger.methods.items()-%}
105
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) } ))),
106
105
  {%endfor%}
107
106
  };
108
-
107
+ {%endif%}{# method handlers #}
108
+
109
109
  // Create structure for subscription ids.
110
110
  let sub_ids = {{stinger.name | UpperCamelCase }}ServerSubscriptionIds {
111
111
  {%for method_name, method in stinger.methods.items()-%}
@@ -115,6 +115,7 @@ impl {{stinger.name | UpperCamelCase}}Server {
115
115
  {{prop_name | snake_case}}_property_update: subscription_id_{{prop_name | snake_case}}_property_update,
116
116
  {%endif%}{%endfor%}
117
117
  };
118
+ {%endif%}
118
119
 
119
120
  {%if stinger.properties|length > 0%}
120
121
  let property_values = {{stinger.name | UpperCamelCase}}Properties {
@@ -129,56 +130,62 @@ impl {{stinger.name | UpperCamelCase}}Server {
129
130
  };
130
131
  {%endif%}
131
132
 
132
- {{stinger.name | UpperCamelCase}}Server {
133
-
133
+ {{stinger.rust.server_struct_name}} {
134
+ mqttier_client: connection.clone(),
135
+ {%if stinger.methods|length > 0 or stinger.properties|length > 0 %}
134
136
  msg_streamer_rx: Some(message_received_rx),
135
- msg_streamer_tx: message_received_tx,
136
- msg_publisher: publisher,
137
- method_handlers: method_handlers,{%if stinger.properties|length > 0%}
137
+ msg_streamer_tx: message_received_tx,{%if stinger.methods | length > 0 %}
138
+ method_handlers: method_handlers,{%endif%}{%if stinger.properties | length > 0 %}
138
139
  properties: property_values,{%endif%}
139
140
  subscription_ids: sub_ids,
141
+ {%endif%}
140
142
  client_id: connection.client_id.to_string(),
141
143
  }
142
144
  }
143
145
 
144
146
  {%for sig_name, sig in stinger.signals.items()-%}
147
+ /// Emits the {{sig_name}} signal with the given arguments.
145
148
  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%}) {
146
- let data = connection::payloads::{{sig_name|UpperCamelCase}}SignalPayload {
149
+ let data = {{sig_name|UpperCamelCase}}SignalPayload {
147
150
  {%for arg in sig.arg_list%}
148
151
  {{arg.name}}: {{arg.name|snake_case}},
149
152
  {%endfor%}
150
153
  };
151
- self.msg_publisher.publish_structure("{{sig.topic}}".to_string(), &data).await;
154
+ let _ = self.mqttier_client.publish_state("{{sig.topic}}".to_string(), &data, 1).await;
152
155
  }
153
156
  {%endfor%}
154
157
 
155
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.
156
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) {
157
161
  self.method_handlers.method_handler_for_{{method_name|snake_case}} = Arc::new(Mutex::new(Box::new(cb)));
158
162
  }
159
163
  {%endfor%}
160
164
 
161
-
162
165
  {%for method_name, method in stinger.methods.items()-%}
163
- async fn handle_{{method_name|snake_case}}_request(publisher: &mut MessagePublisher, handlers: &mut {{stinger.name | UpperCamelCase }}ServerMethodHandlers, msg: mqtt::Message) {
164
- let props = msg.properties();
165
- let opt_corr_id_bin: Option<Vec<u8>> = props.get_binary(mqtt::PropertyCode::CorrelationData);
166
- let opt_resp_topic = props.get_string(mqtt::PropertyCode::ResponseTopic);
167
- let payload_str = msg.payload_str();
168
- let payload = serde_json::from_str::<{{method_name | UpperCamelCase}}RequestObject>(&payload_str).unwrap();
169
-
166
+ /// 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) {
168
+ let opt_corr_data = msg.correlation_data;
169
+ let opt_resp_topic = msg.response_topic;
170
+ {%if method.return_value_type != false -%}
171
+ let payload_vec = msg.payload;
172
+ let payload = serde_json::from_slice::<{{method_name | UpperCamelCase}}RequestObject>(&payload_vec).unwrap();
173
+ {%endif%}
170
174
  // call the method handler
171
175
  let rv: {{method.return_value_rust_type}} = {
172
176
  let func_guard = handlers.method_handler_for_{{method_name|snake_case}}.lock().unwrap();
173
177
  (*func_guard)({%for arg in method.arg_list%}payload.{{arg.name}}{%if not loop.last%}, {%endif%}{%endfor%}).unwrap()
174
178
  };
179
+
175
180
  {%-if method.return_value_type == "primitive" -%}
176
181
  let rv = {{method_name | UpperCamelCase}}ReturnValue {
177
182
  {{method.return_value_property_name}}: rv,
178
183
  };
179
184
  {%endif%}
185
+
180
186
  if let Some(resp_topic) = opt_resp_topic {
181
- publisher.publish_response_structure(resp_topic, &rv, opt_corr_id_bin).await.expect("Failed to publish response structure");
187
+ 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");
182
189
  } else {
183
190
  eprintln!("No response topic found in message properties.");
184
191
  }
@@ -186,21 +193,21 @@ impl {{stinger.name | UpperCamelCase}}Server {
186
193
  {%endfor%}
187
194
 
188
195
  {%-for prop_name, prop in stinger.properties.items()%}
189
- async fn publish_{{prop_name}}_value(mut publisher: MessagePublisher, topic: String, data: {{prop.rust_type}})
196
+ async fn publish_{{prop_name}}_value(publisher: MqttierClient, topic: String, data: {{prop.rust_type}})
190
197
  {
191
198
  {%-if prop.arg_list | length == 1%}
192
199
  let new_data = {{prop_name | UpperCamelCase}}Property {
193
200
  {{prop.arg_list[0].name}}: data,
194
201
  };
195
- let _pub_result = publisher.publish_retained_structure(topic, &new_data).await;
202
+ let _pub_result = publisher.publish_state(topic, &new_data, 1).await;
196
203
  {%else%}
197
- let _pub_result = publisher.publish_retained_structure(topic, &data).await;
204
+ let _pub_result = publisher.publish_state(topic, &data, 1).await;
198
205
  {%endif%}
199
206
  }
200
207
  {%if not prop.read_only %}
201
- async fn update_{{prop_name}}_value(publisher: &mut MessagePublisher, topic: Arc<String>, data: Arc<Mutex<Option<{{prop.rust_type}}>>>, msg: mqtt::Message)
208
+ async fn update_{{prop_name}}_value(publisher: MqttierClient, topic: Arc<String>, data: Arc<Mutex<Option<{{prop.rust_type}}>>>, msg: ReceivedMessage)
202
209
  {
203
- let payload_str = msg.payload_str();
210
+ let payload_str = String::from_utf8_lossy(&msg.payload).to_string();
204
211
  let new_data: {{prop_name | UpperCamelCase}}Property = serde_json::from_str(&payload_str).unwrap();
205
212
  let mut locked_data = data.lock().unwrap();
206
213
  {%-if prop.arg_list | length == 1%}
@@ -216,7 +223,7 @@ impl {{stinger.name | UpperCamelCase}}Server {
216
223
  let data2 = new_data;
217
224
  {%endif%}
218
225
  let _ = tokio::spawn(async move {
219
- {{stinger.name | UpperCamelCase}}Server::publish_{{prop_name}}_value(publisher2, topic2, data2).await;
226
+ {{stinger.rust.server_struct_name}}::publish_{{prop_name}}_value(publisher2, topic2, data2).await;
220
227
  });
221
228
  }
222
229
  {%endif%}{# not read only property #}
@@ -228,11 +235,11 @@ impl {{stinger.name | UpperCamelCase}}Server {
228
235
  *locked_data = Some(data.clone());
229
236
  }
230
237
 
231
- let publisher2 = self.msg_publisher.clone();
238
+ let publisher2 = self.mqttier_client.clone();
232
239
  let topic2 = self.properties.{{prop_name}}_topic.as_ref().clone();
233
240
  let _ = tokio::spawn(async move {
234
241
  println!("Will publish property {{prop_name}} of type {{prop.rust_type}} to {}", topic2);
235
- {{stinger.name | UpperCamelCase}}Server::publish_{{prop_name}}_value(publisher2, topic2, data).await;
242
+ {{stinger.rust.server_struct_name}}::publish_{{prop_name}}_value(publisher2, topic2, data).await;
236
243
  });
237
244
  }
238
245
  {%endfor%}
@@ -242,31 +249,47 @@ impl {{stinger.name | UpperCamelCase}}Server {
242
249
  /// Based on the subscription id of the received message, it will call a function to handle the
243
250
  /// received message.
244
251
  pub async fn receive_loop(&mut self) -> Result<(), JoinError> {
252
+ // Make sure the MqttierClient is connected and running.
253
+ let _ = self.mqttier_client.run_loop().await;
245
254
 
255
+ {%if stinger.methods|length > 0 or stinger.properties|length > 0 %}
246
256
  // Take ownership of the RX channel that receives MQTT messages. This will be moved into the loop_task.
247
257
  let mut message_receiver = self.msg_streamer_rx.take().expect("msg_streamer_rx should be Some");
258
+
259
+ {%if stinger.methods|length > 0 -%}
248
260
  let mut method_handlers = self.method_handlers.clone();
261
+ {%endif-%}
262
+
249
263
  let sub_ids = self.subscription_ids.clone();
250
- let mut publisher = self.msg_publisher.clone();
264
+
265
+ {%-if stinger.methods|length > 0 %}
266
+ let publisher = self.mqttier_client.clone();
267
+ {%endif%}
268
+
251
269
  {%-if stinger.properties | length > 0 %}
252
270
  let properties = self.properties.clone();
253
271
  {%endif%}
254
- let _loop_task = tokio::spawn(async move {
272
+
273
+ let loop_task = tokio::spawn(async move {
255
274
  while let Some(msg) = message_receiver.recv().await {
256
275
  {%-for method_name, method in stinger.methods.items()%}
257
276
  {%if not loop.first%}else {%endif%}if msg.subscription_id == sub_ids.{{method_name | snake_case}}_method_req {
258
- {{stinger.name | UpperCamelCase}}Server::handle_{{method_name|snake_case}}_request(&mut publisher, &mut method_handlers, msg.message).await;
277
+ {{stinger.rust.server_struct_name}}::handle_{{method_name|snake_case}}_request(publisher.clone(), &mut method_handlers, msg).await;
259
278
  }
260
279
  {%-endfor%}{# methods #}
261
280
  {%-for prop_name, prop in stinger.properties.items()%}{%if not prop.read_only %}
262
281
  {%if not loop.first or (stinger.methods | length > 0) %}else {%endif%}if msg.subscription_id == sub_ids.{{prop_name | snake_case}}_property_update {
263
- {{stinger.name | UpperCamelCase}}Server::update_{{prop_name | snake_case}}_value(&mut publisher, properties.{{prop_name | snake_case}}_topic.clone(), properties.{{prop_name | snake_case}}.clone(), msg.message).await;
282
+ {{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;
264
283
  }
265
284
  {%-endif%}{%endfor%}{# properties #}
266
- }
285
+ }
286
+ println!("No more messages from message_receiver channel");
267
287
  });
268
-
269
- println!("Started client receive task");
288
+ let _ = tokio::join!(loop_task);
289
+ {%endif%} {# any methods or properties #}
290
+
291
+ println!("Server receive loop completed [error?]");
270
292
  Ok(())
271
293
  }
294
+
272
295
  }
@@ -0,0 +1,73 @@
1
+
2
+ import os
3
+ from rich import print
4
+ from pathlib import Path
5
+ import typer
6
+ from typing_extensions import Annotated
7
+ from jacobsjinjatoo import templator as jj2
8
+
9
+ from stingeripc.interface import StingerInterface
10
+
11
+ from . import markdown_generator
12
+ from . import python_generator
13
+ from . import rust_generator
14
+
15
+
16
+ app = typer.Typer(help="stinger-ipc generator CLI")
17
+
18
+
19
+ @app.command()
20
+ def generate(
21
+ language: Annotated[str, typer.Argument(...)],
22
+ input_file: Annotated[Path, typer.Argument(..., exists=True, file_okay=True, dir_okay=False, readable=True)],
23
+ output_dir: Annotated[Path, typer.Argument(..., file_okay=False, dir_okay=True, writable=True, readable=True)],
24
+ ):
25
+ """Generate code for a Stinger interface.
26
+
27
+ LANGUAGE must be one of: rust, python, markdown
28
+ INPUT_FILE is the .stinger.yaml file
29
+ OUTPUT_DIR is the directory that will receive generated files
30
+ """
31
+ 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")
34
+
35
+ if lang == "python":
36
+ # python_generator.main expects Path arguments via typer
37
+ python_generator.main(input_file, output_dir)
38
+ elif lang == "markdown":
39
+ # markdown_generator.main expects Path arguments via typer
40
+ markdown_generator.main(input_file, output_dir)
41
+ elif lang == "web":
42
+ wt = jj2.WebTemplator(output_dir=output_dir)
43
+ ct = jj2.CodeTemplator(output_dir=output_dir)
44
+ wt.add_template_dir(
45
+ os.path.join(os.path.dirname(__file__), "../templates", "html")
46
+ )
47
+ ct.add_template_dir(
48
+ os.path.join(os.path.dirname(__file__), "../templates", "html")
49
+ )
50
+ with open(input_file, "r") as f:
51
+ stinger = StingerInterface.from_yaml(f)
52
+ for output_file in [
53
+ "app.js",
54
+ "styles.css",
55
+ ]:
56
+ ct.render_template(f"{output_file}.jinja2", output_file, stinger=stinger)
57
+ wt.render_template("index.html.jinja2", "index.html", stinger=stinger)
58
+ else: # rust
59
+ rust_generator.main(input_file, output_dir)
60
+
61
+
62
+ print(f"Generation for '{lang}' completed.")
63
+
64
+ @app.command()
65
+ def hello():
66
+ print("Hello world")
67
+
68
+ def run():
69
+ app()
70
+
71
+ if __name__ == "__main__":
72
+ run()
73
+
@@ -15,14 +15,16 @@ def main(inname: Annotated[Path, typer.Argument(exists=True, file_okay=True, dir
15
15
  params = {
16
16
  "stinger": stinger,
17
17
  }
18
- t = jj2.CodeTemplator(output_dir=os.path.dirname(outdir))
18
+ output_dir = Path(outdir).resolve()
19
+ print(f"[bold green]Output directory:[/bold green] {output_dir}")
20
+ t = jj2.CodeTemplator(output_dir=output_dir)
19
21
  t.add_template_dir(
20
22
  os.path.join(os.path.dirname(__file__), "../templates", "markdown")
21
23
  )
22
24
  for output_file in [
23
25
  "index.md",
24
26
  ]:
25
- print(f"[bold red]GENERATING:[/bold red] {output_file}")
27
+ print(f"[bold red]GENERATING:[/bold red] {os.path.join(outdir, output_file)}")
26
28
  t.render_template(f"{output_file}.jinja2", output_file, **params)
27
29
 
28
30
  def run():
@@ -1,6 +1,5 @@
1
1
  from jacobsjinjatoo import templator as jj2
2
2
  from jacobsjinjatoo import stringmanip
3
- import sys
4
3
  import os.path
5
4
  import typer
6
5
  from typing_extensions import Annotated
@@ -10,25 +9,27 @@ from pathlib import Path
10
9
 
11
10
  def main(inname: Annotated[Path, typer.Argument(exists=True, file_okay=True, dir_okay=False, readable=True)], outdir: Annotated[Path, typer.Argument(file_okay=False, dir_okay=True, writable=True, readable=True)]):
12
11
 
13
- print(f"[bold green]Reading:[/bold green] {inname}")
12
+ print(f"[bold]Reading:[/bold] {inname}")
14
13
  with open(inname, "r") as f:
15
14
  stinger = StingerInterface.from_yaml(f)
16
15
  params = {
17
16
  "stinger": stinger,
18
17
  }
19
- t = jj2.CodeTemplator(output_dir=os.path.dirname(outdir))
18
+ output_dir = Path(outdir).resolve()
19
+ print(f"[bold]Output directory:[/bold] {output_dir}")
20
+ t = jj2.CodeTemplator(output_dir=output_dir)
20
21
  t.add_template_dir(
21
22
  os.path.join(os.path.dirname(__file__), "../templates", "python")
22
23
  )
23
24
  for output_file in [
24
25
  "pyproject.toml",
25
26
  ]:
26
- print(f"[bold red]Generating:[/bold red] {output_file}")
27
- t.render_template(f"{output_file}.jinja2", os.path.join(outdir, output_file), **params)
27
+ print(f"[bold green]Generating:[/bold green] {output_file}")
28
+ t.render_template(f"{output_file}.jinja2", output_file, **params)
28
29
 
29
30
  package_name = f"{stringmanip.lower_camel_case(stinger.name).lower()}ipc"
30
- generated_pkg_dir = os.path.join(outdir, package_name)
31
- print(f"[bold red]Making Directory:[/bold red] {generated_pkg_dir}")
31
+ generated_pkg_dir = output_dir / package_name
32
+ print(f"[bold]Making Directory:[/bold] {generated_pkg_dir}")
32
33
  os.makedirs(generated_pkg_dir, exist_ok=True)
33
34
  for output_file in [
34
35
  "server.py",
@@ -37,10 +38,11 @@ def main(inname: Annotated[Path, typer.Argument(exists=True, file_okay=True, dir
37
38
  "__init__.py",
38
39
  "method_codes.py",
39
40
  ]:
40
- print(f"[bold red]Generating:[/bold red] {output_file}")
41
- t.render_template(f"{output_file}.jinja2", os.path.join(generated_pkg_dir, output_file), **params)
41
+ of = generated_pkg_dir / output_file
42
+ print(f"[bold green]Generating:[/bold green] {of}")
43
+ output = t.render_template(f"{output_file}.jinja2", of, **params)
42
44
 
43
- t.render_template("interface_types.py.jinja2", os.path.join(generated_pkg_dir, f"{stinger.get_enum_module_name()}.py"), **params)
45
+ t.render_template("interface_types.py.jinja2", f"{generated_pkg_dir}/{stinger.get_enum_module_name()}.py", **params)
44
46
 
45
47
  def run():
46
48
  typer.run(main)
@@ -1,50 +1,69 @@
1
1
  from jacobsjinjatoo import templator as jj2
2
- import sys
3
- import yaml
4
2
  import os
5
3
  import shutil
4
+ import typer
5
+ from typing_extensions import Annotated
6
+ from pathlib import Path
7
+ from rich import print
6
8
 
7
9
  from stingeripc import StingerInterface
8
10
 
9
- def main():
10
- inname = sys.argv[1] # Path to the stinger file
11
- outdir = sys.argv[2] # Directory to which the files should be written
12
- with open(inname, "r") as f:
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 Rust output for a Stinger interface."""
17
+
18
+ with inname.open(mode="r") as f:
13
19
  stinger = StingerInterface.from_yaml(f)
14
- params = {
15
- "stinger": stinger,
16
- }
17
20
 
18
- if not os.path.exists(outdir):
21
+ params = {"stinger": stinger}
22
+
23
+ if outdir.is_file():
24
+ raise RuntimeError("Output directory is a file!")
25
+
26
+ if not outdir.is_dir():
19
27
  os.makedirs(outdir)
20
28
 
21
- template_dir = os.path.join(os.path.dirname(__file__), "../templates", "rust")
29
+ print(f"OUPUT: {outdir}")
22
30
 
23
- t = jj2.CodeTemplator(output_dir=os.path.dirname(outdir))
31
+ this_file = Path(__file__)
32
+ this_dir = this_file.parent
33
+ template_dir = (this_dir / "../templates" / "rust").resolve()
34
+
35
+ t = jj2.CodeTemplator(output_dir=outdir)
24
36
  t.add_template_dir(template_dir)
25
37
 
26
38
  def recursive_render_templates(local_dir: str):
27
- cur_template_dir = os.path.join(template_dir, local_dir)
39
+ local_dir = Path(local_dir)
40
+ cur_template_dir = template_dir / local_dir
28
41
  for entry in os.listdir(cur_template_dir):
29
- if entry == 'target':
42
+ if entry == "target":
30
43
  # Do not copy 'target' dir
31
44
  continue
32
- entry_full_path = os.path.join(cur_template_dir, entry)
33
- entry_local_path = os.path.join(local_dir, entry)
45
+ entry_full_path = (cur_template_dir / entry).resolve()
46
+ entry_local_path = (local_dir / entry)
34
47
  if entry.endswith(".jinja2"):
35
- print(f"GENER: {entry_local_path}")
36
- t.render_template(entry_local_path, entry_local_path[:-len(".jinja2")], **params)
37
- elif os.path.isdir(entry_full_path):
38
- print(f"MKDIR: {entry_local_path}")
39
- new_dir = os.path.join(outdir, entry_local_path)
40
- if not os.path.exists(new_dir):
41
- os.makedirs(new_dir)
48
+ destpath = str(entry_local_path)[:-len(".jinja2")]
49
+ print(f"GENER: {entry_local_path} -> {destpath}")
50
+ t.render_template(entry_local_path, destpath, **params)
51
+ elif entry_full_path.is_dir():
52
+ new_dir = outdir / entry_local_path
53
+ print(f"MKDIR: {new_dir.resolve()}")
54
+ if not new_dir.exists():
55
+ new_dir.mkdir(parents=True)
42
56
  recursive_render_templates(entry_local_path)
43
- elif os.path.isfile(entry_full_path):
44
- shutil.copyfile(entry_full_path, os.path.join(outdir, entry_local_path))
45
- print(f"COPY: {entry_local_path}")
57
+ elif entry_full_path.is_file():
58
+ shutil.copyfile(entry_full_path, outdir / entry_local_path)
59
+ print(f"COPY: {entry_full_path}")
46
60
 
47
61
  recursive_render_templates(".")
48
62
 
63
+
64
+ def run():
65
+ typer.run(main)
66
+
67
+
49
68
  if __name__ == "__main__":
50
- main()
69
+ run()