@browsersync/bslive 0.0.5 → 0.0.9

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 (137) hide show
  1. package/Cargo.toml +32 -18
  2. package/bin.js +6 -0
  3. package/bslive/Cargo.toml +35 -0
  4. package/{build.rs → bslive/build.rs} +1 -1
  5. package/bslive/src/lib.rs +130 -0
  6. package/bsnext/Cargo.toml +21 -0
  7. package/bsnext/src/main.rs +110 -0
  8. package/crates/bsnext_client/Cargo.toml +8 -0
  9. package/crates/bsnext_client/build.rs +53 -0
  10. package/crates/bsnext_client/dist/index.js +6493 -0
  11. package/crates/bsnext_client/generated/dto.ts +116 -0
  12. package/crates/bsnext_client/generated/schema.ts +187 -0
  13. package/crates/bsnext_client/index.html +14 -0
  14. package/crates/bsnext_client/package-lock.json +2059 -0
  15. package/crates/bsnext_client/package.json +25 -0
  16. package/crates/bsnext_client/src/lib.rs +1 -0
  17. package/crates/bsnext_client/style.css +3 -0
  18. package/crates/bsnext_client/ts/console.ts +25 -0
  19. package/crates/bsnext_client/ts/index.ts +73 -0
  20. package/crates/bsnext_client/tsconfig.json +16 -0
  21. package/crates/bsnext_core/Cargo.toml +43 -0
  22. package/crates/bsnext_core/src/dir_loader.rs +230 -0
  23. package/crates/bsnext_core/src/dto.rs +281 -0
  24. package/crates/bsnext_core/src/handlers/mod.rs +1 -0
  25. package/crates/bsnext_core/src/handlers/proxy.rs +95 -0
  26. package/crates/bsnext_core/src/lib.rs +11 -0
  27. package/crates/bsnext_core/src/meta/mod.rs +5 -0
  28. package/crates/bsnext_core/src/not_found/mod.rs +2 -0
  29. package/crates/bsnext_core/src/not_found/not_found.html +20 -0
  30. package/crates/bsnext_core/src/not_found/not_found_service.rs +41 -0
  31. package/crates/bsnext_core/src/not_found/route_list.rs +49 -0
  32. package/crates/bsnext_core/src/panic_handler.rs +32 -0
  33. package/crates/bsnext_core/src/raw_loader.rs +226 -0
  34. package/crates/bsnext_core/src/server/actor.rs +92 -0
  35. package/crates/bsnext_core/src/server/error.rs +41 -0
  36. package/crates/bsnext_core/src/server/handler_change.rs +85 -0
  37. package/crates/bsnext_core/src/server/handler_listen.rs +157 -0
  38. package/crates/bsnext_core/src/server/handler_patch.rs +42 -0
  39. package/crates/bsnext_core/src/server/handler_routes_updated.rs +27 -0
  40. package/crates/bsnext_core/src/server/handler_stop.rs +38 -0
  41. package/crates/bsnext_core/src/server/mod.rs +10 -0
  42. package/crates/bsnext_core/src/server/router/mod.rs +112 -0
  43. package/crates/bsnext_core/src/server/router/tests.rs +204 -0
  44. package/crates/bsnext_core/src/server/signals.rs +11 -0
  45. package/crates/bsnext_core/src/server/state.rs +19 -0
  46. package/crates/bsnext_core/src/servers_supervisor/actor.rs +199 -0
  47. package/crates/bsnext_core/src/servers_supervisor/file_changed_handler.rs +41 -0
  48. package/crates/bsnext_core/src/servers_supervisor/get_servers_handler.rs +23 -0
  49. package/crates/bsnext_core/src/servers_supervisor/input_changed_handler.rs +21 -0
  50. package/crates/bsnext_core/src/servers_supervisor/mod.rs +6 -0
  51. package/crates/bsnext_core/src/servers_supervisor/start_handler.rs +82 -0
  52. package/crates/bsnext_core/src/servers_supervisor/stop_handler.rs +26 -0
  53. package/crates/bsnext_core/src/ws/mod.rs +164 -0
  54. package/crates/bsnext_example/Cargo.toml +10 -0
  55. package/crates/bsnext_example/src/basic.rs +51 -0
  56. package/crates/bsnext_example/src/lib.rs +26 -0
  57. package/crates/bsnext_example/src/lit.rs +37 -0
  58. package/crates/bsnext_example/src/md.rs +18 -0
  59. package/crates/bsnext_fs/Cargo.toml +22 -0
  60. package/crates/bsnext_fs/src/actor.rs +122 -0
  61. package/crates/bsnext_fs/src/buffered_debounce.rs +166 -0
  62. package/crates/bsnext_fs/src/filter.rs +30 -0
  63. package/crates/bsnext_fs/src/inner_fs_event_handler.rs +94 -0
  64. package/crates/bsnext_fs/src/lib.rs +141 -0
  65. package/crates/bsnext_fs/src/remove_path_handler.rs +46 -0
  66. package/crates/bsnext_fs/src/stop_handler.rs +15 -0
  67. package/crates/bsnext_fs/src/stream.rs +167 -0
  68. package/crates/bsnext_fs/src/test/mod.rs +213 -0
  69. package/crates/bsnext_fs/src/watch_path_handler.rs +67 -0
  70. package/crates/bsnext_fs/src/watcher.rs +348 -0
  71. package/crates/bsnext_input/Cargo.toml +22 -0
  72. package/crates/bsnext_input/src/input_test/mod.rs +151 -0
  73. package/crates/bsnext_input/src/lib.rs +153 -0
  74. package/crates/bsnext_input/src/md.rs +541 -0
  75. package/crates/bsnext_input/src/paths.rs +64 -0
  76. package/crates/bsnext_input/src/route.rs +185 -0
  77. package/crates/bsnext_input/src/route_manifest.rs +186 -0
  78. package/crates/bsnext_input/src/server_config.rs +88 -0
  79. package/crates/bsnext_input/src/target.rs +7 -0
  80. package/crates/bsnext_input/src/watch_opt_test/mod.rs +68 -0
  81. package/crates/bsnext_input/src/yml.rs +1 -0
  82. package/crates/bsnext_output/Cargo.toml +16 -0
  83. package/crates/bsnext_output/src/json.rs +11 -0
  84. package/crates/bsnext_output/src/lib.rs +25 -0
  85. package/crates/bsnext_output/src/pretty.rs +147 -0
  86. package/crates/bsnext_resp/Cargo.toml +13 -0
  87. package/crates/bsnext_resp/src/js/snippet.html +1 -0
  88. package/crates/bsnext_resp/src/js/ws.js +1 -0
  89. package/crates/bsnext_resp/src/lib.rs +79 -0
  90. package/crates/bsnext_system/Cargo.toml +29 -0
  91. package/crates/bsnext_system/src/args.rs +43 -0
  92. package/crates/bsnext_system/src/lib.rs +227 -0
  93. package/crates/bsnext_system/src/monitor.rs +241 -0
  94. package/crates/bsnext_system/src/monitor_any_watchables.rs +127 -0
  95. package/crates/bsnext_system/src/start_kind/snapshots/bsnext_system__start_kind__start_from_paths__test__test-2.snap +11 -0
  96. package/crates/bsnext_system/src/start_kind/snapshots/bsnext_system__start_kind__start_from_paths__test__test.snap +29 -0
  97. package/crates/bsnext_system/src/start_kind/start_from_example.rs +49 -0
  98. package/crates/bsnext_system/src/start_kind/start_from_inputs.rs +67 -0
  99. package/crates/bsnext_system/src/start_kind/start_from_paths.rs +51 -0
  100. package/crates/bsnext_system/src/start_kind.rs +56 -0
  101. package/crates/bsnext_system/src/startup.rs +51 -0
  102. package/crates/bsnext_tracing/Cargo.toml +10 -0
  103. package/crates/bsnext_tracing/src/lib.rs +89 -0
  104. package/examples/basic/input.yml +5 -0
  105. package/examples/basic/public/index.html +15 -0
  106. package/examples/basic/public/reset.css +52 -0
  107. package/examples/basic/public/script.js +1 -0
  108. package/examples/basic/public/styles.css +3 -0
  109. package/examples/kitchen-sink/a-file.md +1 -0
  110. package/examples/kitchen-sink/api/1.json +1 -0
  111. package/examples/kitchen-sink/api/2.json +3 -0
  112. package/examples/kitchen-sink/app.js +1 -0
  113. package/examples/kitchen-sink/input.html +185 -0
  114. package/examples/kitchen-sink/input.yml +133 -0
  115. package/examples/kitchen-sink/styles-2.css +3 -0
  116. package/examples/lit/index.html +21 -0
  117. package/examples/lit/input.yml +5 -0
  118. package/examples/lit/lit.js +82 -0
  119. package/examples/lit/package-lock.json +62 -0
  120. package/examples/lit/package.json +15 -0
  121. package/examples/md-single/frontmatter.md +35 -0
  122. package/examples/md-single/md-single.md +35 -0
  123. package/examples/openai/.nvm +1 -0
  124. package/examples/openai/build.mjs +21 -0
  125. package/examples/openai/index.html +13 -0
  126. package/examples/openai/input.yml +59 -0
  127. package/examples/openai/package-lock.json +720 -0
  128. package/examples/openai/package.json +21 -0
  129. package/examples/openai/src/index.js +21 -0
  130. package/examples/openai/sse/01.txt +8 -0
  131. package/examples/proxy-simple/input.yml +9 -0
  132. package/examples/single/input.yaml +26 -0
  133. package/index.d.ts +3 -1
  134. package/index.js +3 -2
  135. package/package.json +22 -19
  136. package/run.sh +6 -0
  137. package/src/lib.rs +0 -9
@@ -0,0 +1,541 @@
1
+ use crate::route::{Route, RouteKind};
2
+ use crate::server_config::ServerConfig;
3
+ use crate::Input;
4
+
5
+ use markdown::mdast::Node;
6
+ use markdown::{Constructs, ParseOptions};
7
+ use mime_guess::get_mime_extensions_str;
8
+ use nom::branch::alt;
9
+ use nom::combinator::map;
10
+ use nom::multi::many0;
11
+ use nom::sequence::separated_pair;
12
+ use nom::{error::ParseError, IResult};
13
+ use serde_json::json;
14
+ use std::cmp::PartialEq;
15
+
16
+ fn parser_for(k: BsLiveKinds) -> impl Fn(&[Node]) -> IResult<&[Node], &Node> {
17
+ move |input: &[Node]| {
18
+ if input.is_empty() || input[0].kind() != k {
19
+ Err(nom::Err::Error(ParseError::from_error_kind(
20
+ input,
21
+ nom::error::ErrorKind::Tag,
22
+ )))
23
+ } else {
24
+ Ok((&input[1..], &input[0]))
25
+ }
26
+ }
27
+ }
28
+
29
+ #[derive(Debug, PartialEq, PartialOrd)]
30
+ pub enum BsLiveKinds {
31
+ Input,
32
+ Route,
33
+ Body,
34
+ Ignored,
35
+ }
36
+
37
+ trait BsLive {
38
+ fn kind(&self) -> BsLiveKinds;
39
+ fn is_input(&self) -> bool;
40
+ fn is_route(&self) -> bool;
41
+ fn is_body(&self) -> bool;
42
+ fn raw_value(&self) -> Option<String>;
43
+ }
44
+
45
+ impl TryInto<Input> for &Node {
46
+ type Error = anyhow::Error;
47
+
48
+ fn try_into(self) -> Result<Input, Self::Error> {
49
+ if !self.is_input() {
50
+ return Err(anyhow::anyhow!("not an input type"));
51
+ }
52
+ match self {
53
+ Node::Code(code) => {
54
+ let config: Input = serde_yaml::from_str(&code.value)?;
55
+ Ok(config)
56
+ }
57
+ Node::Yaml(yaml) => {
58
+ let config: Input = serde_yaml::from_str(&yaml.value)?;
59
+ Ok(config)
60
+ }
61
+ _ => Err(anyhow::anyhow!("unreachable")),
62
+ }
63
+ }
64
+ }
65
+
66
+ impl TryInto<Input> for Vec<Node> {
67
+ type Error = anyhow::Error;
68
+ fn try_into(self) -> Result<Input, Self::Error> {
69
+ nodes_to_input(&self)
70
+ }
71
+ }
72
+
73
+ impl TryInto<Route> for (&Node, &Node) {
74
+ type Error = anyhow::Error;
75
+
76
+ fn try_into(self) -> Result<Route, Self::Error> {
77
+ match (self.0.is_route(), self.1.is_body()) {
78
+ (true, true) => match self.0 {
79
+ Node::Code(code)
80
+ if code
81
+ .lang
82
+ .as_ref()
83
+ .is_some_and(|l| l == "yaml" || l == "yml") =>
84
+ {
85
+ #[derive(serde::Deserialize)]
86
+ struct PathOnly {
87
+ path: String,
88
+ }
89
+ let r: PathOnly = serde_yaml::from_str(&code.value)?;
90
+ let route_kind = route_kind_from_body_node(self.1)?;
91
+ let route = Route {
92
+ path: r.path,
93
+ kind: route_kind,
94
+ ..Default::default()
95
+ };
96
+ Ok(route)
97
+ }
98
+ _ => Err(anyhow::anyhow!("unreachlable")),
99
+ },
100
+ _ => Err(anyhow::anyhow!("cannot create")),
101
+ }
102
+ }
103
+ }
104
+
105
+ fn route_kind_from_body_node(node: &Node) -> anyhow::Result<RouteKind> {
106
+ match node {
107
+ Node::Code(code) => {
108
+ let value = code.value.clone();
109
+ let rk = match code.lang.as_deref() {
110
+ Some("html") => RouteKind::Html { html: value },
111
+ Some("json") => RouteKind::Json {
112
+ json: serde_json::from_str(&value)?,
113
+ },
114
+ Some("sse") => RouteKind::Sse { sse: value },
115
+ Some(..) | None => RouteKind::Raw { raw: value },
116
+ };
117
+ Ok(rk)
118
+ }
119
+ _ => Err(anyhow::anyhow!("unsupported route kind")),
120
+ }
121
+ }
122
+
123
+ impl BsLive for Node {
124
+ fn kind(&self) -> BsLiveKinds {
125
+ if self.is_input() {
126
+ return BsLiveKinds::Input;
127
+ }
128
+ if self.is_route() {
129
+ return BsLiveKinds::Route;
130
+ }
131
+ if self.is_body() {
132
+ return BsLiveKinds::Body;
133
+ }
134
+ BsLiveKinds::Ignored
135
+ }
136
+ fn is_input(&self) -> bool {
137
+ match self {
138
+ // this is from front matter
139
+ Node::Yaml(_yaml) => true,
140
+ // code block with annotations
141
+ Node::Code(code) => code
142
+ .meta
143
+ .as_ref()
144
+ .is_some_and(|v| v.contains("bslive_input")),
145
+ _ => false,
146
+ }
147
+ }
148
+
149
+ fn is_route(&self) -> bool {
150
+ match self {
151
+ Node::Code(code) => code
152
+ .meta
153
+ .as_ref()
154
+ .is_some_and(|v| v.contains("bslive_route")),
155
+ _ => false,
156
+ }
157
+ }
158
+
159
+ fn is_body(&self) -> bool {
160
+ if self.is_input() || self.is_route() {
161
+ return false;
162
+ }
163
+ matches!(self, Node::Code(..))
164
+ }
165
+
166
+ fn raw_value(&self) -> Option<String> {
167
+ if self.is_body() {
168
+ let Node::Code(code) = self else {
169
+ unreachable!("shouldnt get here");
170
+ };
171
+ Some(code.value.clone())
172
+ } else {
173
+ None
174
+ }
175
+ }
176
+ }
177
+
178
+ enum Convert {
179
+ None,
180
+ Input(Input),
181
+ Route(Route),
182
+ }
183
+
184
+ pub fn nodes_to_input(nodes: &[Node]) -> Result<Input, anyhow::Error> {
185
+ let mut routes = vec![];
186
+ let mut server_config: Option<Input> = None;
187
+ let mut parser = many0(alt((
188
+ map(parser_for(BsLiveKinds::Ignored), |_v| Convert::None),
189
+ map(parser_for(BsLiveKinds::Input), |v: &Node| {
190
+ let as_config: Result<Input, _> = v.try_into();
191
+ match as_config {
192
+ Ok(config) => Convert::Input(config),
193
+ Err(e) => unreachable!("? creating server config {:?}", e),
194
+ }
195
+ }),
196
+ map(
197
+ separated_pair(
198
+ parser_for(BsLiveKinds::Route),
199
+ many0(parser_for(BsLiveKinds::Ignored)),
200
+ parser_for(BsLiveKinds::Body),
201
+ ),
202
+ |pair| {
203
+ let as_route: Result<Route, _> = pair.try_into();
204
+ match as_route {
205
+ Ok(route) => Convert::Route(route),
206
+ Err(e) => unreachable!("? {:?}", e),
207
+ }
208
+ },
209
+ ),
210
+ )));
211
+
212
+ let results = parser(nodes);
213
+
214
+ match results {
215
+ Ok((_rest, matched)) => {
216
+ for item in matched {
217
+ match item {
218
+ Convert::None => {}
219
+ Convert::Input(input_from_md) => {
220
+ // todo: handle server config
221
+ if server_config.is_none() {
222
+ server_config = Some(input_from_md)
223
+ } else {
224
+ unreachable!("todo: support multiple 'input' blocks")
225
+ }
226
+ }
227
+ Convert::Route(route) => {
228
+ routes.push(route);
229
+ }
230
+ }
231
+ }
232
+ }
233
+ Err(e) => return Err(anyhow::anyhow!(e.to_string())),
234
+ }
235
+
236
+ match server_config.take() {
237
+ // config was not set, use default
238
+ None => {
239
+ let mut input = Input::default();
240
+ let server = ServerConfig {
241
+ routes,
242
+ ..Default::default()
243
+ };
244
+ input.servers.push(server);
245
+ Ok(input)
246
+ }
247
+ // got some server config, use it.
248
+ Some(mut input) => {
249
+ if let Some(server) = input.servers.first_mut() {
250
+ server.routes.extend(routes)
251
+ }
252
+ Ok(input)
253
+ }
254
+ }
255
+ }
256
+
257
+ pub fn str_to_nodes(input: &str) -> Result<Vec<Node>, anyhow::Error> {
258
+ let opts = ParseOptions {
259
+ constructs: Constructs {
260
+ frontmatter: true,
261
+ ..Default::default()
262
+ },
263
+ ..Default::default()
264
+ };
265
+ let root = markdown::to_mdast(input, &opts).map_err(|e| anyhow::anyhow!(e))?;
266
+ match root {
267
+ Node::Root(root) => Ok(root.children),
268
+ _ => {
269
+ unreachable!("?");
270
+ }
271
+ }
272
+ }
273
+
274
+ pub fn md_to_input(input: &str) -> Result<Input, anyhow::Error> {
275
+ let root = str_to_nodes(input)?;
276
+ nodes_to_input(&root)
277
+ }
278
+
279
+ #[cfg(test)]
280
+ mod test {
281
+ use super::*;
282
+ use crate::server_config::Identity;
283
+
284
+ #[test]
285
+ fn test_single() -> anyhow::Result<()> {
286
+ // let input = include_str!("../../examples/md-single/md-single.md");
287
+ let input = r#"
288
+
289
+ # Demo 2
290
+
291
+ ```yaml bslive_route
292
+ path: /app.css
293
+ ```
294
+
295
+ ```css
296
+ body {
297
+ background: blue
298
+ }
299
+ ```
300
+ "#;
301
+ let config = md_to_input(&input).expect("unwrap");
302
+ let server_1 = config.servers.first().unwrap();
303
+ assert_eq!(
304
+ server_1.routes[0],
305
+ Route {
306
+ path: "/app.css".into(),
307
+ kind: RouteKind::Raw {
308
+ raw: "body {\n background: blue\n}".into(),
309
+ },
310
+ ..Default::default()
311
+ }
312
+ );
313
+ Ok(())
314
+ }
315
+
316
+ #[test]
317
+ fn test_2_consecutive() -> anyhow::Result<()> {
318
+ // let input = include_str!("../../examples/md-single/md-single.md");
319
+ let input = r#"
320
+
321
+ ```yaml bslive_route
322
+ path: /app.css
323
+ ```
324
+
325
+ ```css
326
+ body {
327
+ background: blue
328
+ }
329
+ ```
330
+
331
+ Some other text
332
+
333
+ ```yaml bslive_route
334
+ path: /app2.css
335
+ ```
336
+
337
+ ```css
338
+ body {
339
+ background: blue
340
+ }
341
+ ```
342
+ "#;
343
+ let config = md_to_input(&input).expect("unwrap");
344
+ let server_1 = config.servers.first().unwrap();
345
+ assert_eq!(
346
+ server_1.routes[0],
347
+ Route {
348
+ path: "/app.css".into(),
349
+ kind: RouteKind::Raw {
350
+ raw: "body {\n background: blue\n}".into(),
351
+ },
352
+ ..Default::default()
353
+ }
354
+ );
355
+ assert_eq!(
356
+ server_1.routes[1],
357
+ Route {
358
+ path: "/app2.css".into(),
359
+ kind: RouteKind::Raw {
360
+ raw: "body {\n background: blue\n}".into(),
361
+ },
362
+ ..Default::default()
363
+ }
364
+ );
365
+ Ok(())
366
+ }
367
+
368
+ #[test]
369
+ fn test_parse_with_elements_in_gaps() -> anyhow::Result<()> {
370
+ let input = r#"
371
+ # Before
372
+
373
+ ```yaml bslive_input
374
+ servers:
375
+ - bind_address: 0.0.0.0:3001
376
+ routes:
377
+ - path: /health
378
+ raw: OK
379
+ ```
380
+
381
+ ```yaml bslive_route
382
+ path: /
383
+ ```
384
+
385
+ in between?
386
+
387
+ ```html
388
+ <p>hello world</p>
389
+ ```
390
+
391
+ ```yaml bslive_route
392
+ path: /abc
393
+ ```
394
+ ```html
395
+ <p>hello world 2</p>
396
+ ```
397
+
398
+ # Before
399
+ "#;
400
+ let config = md_to_input(&input).expect("unwrap");
401
+ let server_1 = config.servers.first().unwrap();
402
+ let expected_id = Identity::Address {
403
+ bind_address: "0.0.0.0:3001".into(),
404
+ };
405
+ assert_eq!(server_1.identity, expected_id);
406
+ assert_eq!(server_1.routes.len(), 3);
407
+ assert_eq!(
408
+ server_1.routes[0],
409
+ Route {
410
+ path: "/health".into(),
411
+ kind: RouteKind::Raw { raw: "OK".into() },
412
+ ..Default::default()
413
+ }
414
+ );
415
+ assert_eq!(
416
+ server_1.routes[1],
417
+ Route {
418
+ path: "/".into(),
419
+ kind: RouteKind::Html {
420
+ html: "<p>hello world</p>".into(),
421
+ },
422
+ ..Default::default()
423
+ }
424
+ );
425
+ assert_eq!(
426
+ server_1.routes[2],
427
+ Route {
428
+ path: "/abc".into(),
429
+ kind: RouteKind::Html {
430
+ html: "<p>hello world 2</p>".into(),
431
+ },
432
+ ..Default::default()
433
+ }
434
+ );
435
+ Ok(())
436
+ }
437
+
438
+ fn default_md_assertions(input: &str) -> anyhow::Result<()> {
439
+ let input = md_to_input(&input).expect("unwrap");
440
+ let server_1 = input.servers.first().unwrap();
441
+ let expected_id = Identity::Address {
442
+ bind_address: "0.0.0.0:5001".into(),
443
+ };
444
+ assert_eq!(server_1.identity, expected_id);
445
+ assert_eq!(server_1.routes.len(), 2);
446
+ let paths = server_1
447
+ .routes
448
+ .iter()
449
+ .map(|r| r.path.as_str())
450
+ .collect::<Vec<&str>>();
451
+
452
+ assert_eq!(paths, vec!["/app.css", "/"]);
453
+ Ok(())
454
+ }
455
+
456
+ #[test]
457
+ fn test_from_example_str() -> anyhow::Result<()> {
458
+ let input_str = include_str!("../../../examples/md-single/md-single.md");
459
+ default_md_assertions(input_str)
460
+ }
461
+
462
+ #[test]
463
+ fn test_from_example_str_frontmatter() -> anyhow::Result<()> {
464
+ let input_str = include_str!("../../../examples/md-single/frontmatter.md");
465
+ default_md_assertions(input_str)
466
+ }
467
+ }
468
+
469
+ pub fn input_to_str(input: &Input) -> String {
470
+ let mut chunks = vec![];
471
+ if let Some(server_config) = input.servers.first() {
472
+ let without_routes = Input {
473
+ servers: vec![ServerConfig {
474
+ identity: server_config.identity.clone(),
475
+ routes: vec![],
476
+ ..Default::default()
477
+ }],
478
+ };
479
+ let yml = serde_yaml::to_string(&without_routes).expect("never fail here");
480
+
481
+ chunks.push(fenced_input(&yml));
482
+
483
+ for route in &server_config.routes {
484
+ let path_only = json!({"path": route.path});
485
+ let route_yaml = serde_yaml::to_string(&path_only).expect("never fail here on route?");
486
+ chunks.push(fenced_route(&route_yaml));
487
+ chunks.push(route_to_markdown(&route.kind, &route.path));
488
+ }
489
+ }
490
+ for _x in input.servers.iter().skip(1) {
491
+ todo!("not supported yet!")
492
+ }
493
+ chunks.join("\n")
494
+ }
495
+
496
+ fn route_to_markdown(kind: &RouteKind, path: &str) -> String {
497
+ match kind {
498
+ RouteKind::Html { html } => fenced_body("html", html),
499
+ RouteKind::Json { .. } => todo!("unsupported json"),
500
+ RouteKind::Raw { raw } => {
501
+ let mime = mime_guess::from_path(path);
502
+ let as_str = mime.first_or_text_plain();
503
+ let as_str = get_mime_extensions_str(as_str.as_ref());
504
+ if let Some(v) = as_str.and_then(|x| x.first()) {
505
+ fenced_body(v, raw)
506
+ } else {
507
+ fenced_body("", raw)
508
+ }
509
+ }
510
+ RouteKind::Sse { .. } => todo!("unsupported"),
511
+ RouteKind::Proxy(_) => todo!("unsupported"),
512
+ RouteKind::Dir(_) => todo!("unsupported"),
513
+ }
514
+ }
515
+
516
+ fn fenced_input(code: &str) -> String {
517
+ format!("```yaml bslive_input\n{}```", code)
518
+ }
519
+
520
+ fn fenced_route(code: &str) -> String {
521
+ format!("```yaml bslive_route\n{}```", code)
522
+ }
523
+
524
+ fn fenced_body(lang: &str, code: &str) -> String {
525
+ format!("```{lang}\n{code}\n```")
526
+ }
527
+
528
+ #[cfg(test)]
529
+ mod test_serialize {
530
+ use super::*;
531
+ #[test]
532
+ fn test_input_to_str() -> anyhow::Result<()> {
533
+ let input_str = include_str!("../../../examples/md-single/md-single.md");
534
+ let input = md_to_input(&input_str).expect("unwrap");
535
+ let _output = input_to_str(&input);
536
+ let input = md_to_input(&input_str).expect("unwrapped 2");
537
+ assert_eq!(input.servers.len(), 1);
538
+ assert_eq!(input.servers.first().unwrap().routes.len(), 2);
539
+ Ok(())
540
+ }
541
+ }
@@ -0,0 +1,64 @@
1
+ use crate::route::{DirRoute, Route, RouteKind};
2
+ use crate::server_config::{Identity, ServerConfig};
3
+ use crate::{Input, PathDefinition, PathDefs, PathError};
4
+ use std::path::{Path, PathBuf};
5
+
6
+ pub fn from_paths<T: AsRef<str>>(
7
+ cwd: &Path,
8
+ paths: &[T],
9
+ identity: Identity,
10
+ ) -> Result<Input, PathError> {
11
+ let path_defs = paths
12
+ .iter()
13
+ .map(|p| {
14
+ let pb = PathBuf::from(p.as_ref());
15
+ if pb.is_absolute() {
16
+ return PathDefinition {
17
+ input: p.as_ref().to_string(),
18
+ cwd: cwd.to_path_buf(),
19
+ absolute: pb,
20
+ };
21
+ } else {
22
+ return PathDefinition {
23
+ input: p.as_ref().to_string(),
24
+ cwd: cwd.to_path_buf(),
25
+ absolute: cwd.join(pb),
26
+ };
27
+ }
28
+ })
29
+ .map(|path_def| {
30
+ let exists = path_def.absolute.exists();
31
+ (path_def, exists)
32
+ })
33
+ .collect::<Vec<(PathDefinition, bool)>>();
34
+
35
+ let invalid = path_defs
36
+ .into_iter()
37
+ .filter_map(|(pb, exists)| if exists { None } else { Some(pb) })
38
+ .collect::<Vec<_>>();
39
+
40
+ if !invalid.is_empty() {
41
+ return Err(PathError::MissingPaths {
42
+ paths: PathDefs(invalid),
43
+ });
44
+ }
45
+
46
+ let server = ServerConfig {
47
+ identity,
48
+ routes: paths
49
+ .iter()
50
+ .map(|p| -> Route {
51
+ let str = p.as_ref();
52
+ Route {
53
+ path: "/".to_string(),
54
+ kind: RouteKind::Dir(DirRoute { dir: str.into() }),
55
+ ..Default::default()
56
+ }
57
+ })
58
+ .collect(),
59
+ ..Default::default()
60
+ };
61
+ Ok(Input {
62
+ servers: vec![server],
63
+ })
64
+ }