@browsersync/bslive 0.0.7 → 0.0.13
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.
- package/Cargo.toml +31 -2
- package/bin.js +3 -2
- package/bslive/Cargo.toml +16 -1
- package/bslive/build.rs +1 -1
- package/bslive/src/lib.rs +125 -4
- package/bsnext/Cargo.toml +21 -0
- package/bsnext/src/main.rs +116 -0
- package/crates/bsnext_client/Cargo.toml +8 -0
- package/crates/bsnext_client/build.rs +53 -0
- package/crates/bsnext_client/generated/dto.ts +147 -0
- package/crates/bsnext_client/generated/schema.ts +237 -0
- package/crates/bsnext_client/inject/dist/index.js +6540 -0
- package/crates/bsnext_client/inject/package.json +12 -0
- package/crates/bsnext_client/inject/src/console.ts +25 -0
- package/crates/bsnext_client/inject/src/index.ts +73 -0
- package/crates/bsnext_client/package-lock.json +2145 -0
- package/crates/bsnext_client/package.json +27 -0
- package/crates/bsnext_client/src/lib.rs +11 -0
- package/crates/bsnext_client/tsconfig.json +22 -0
- package/crates/bsnext_client/ui/dist/index.css +78 -0
- package/crates/bsnext_client/ui/dist/index.js +997 -0
- package/crates/bsnext_client/ui/index.html +18 -0
- package/crates/bsnext_client/ui/input.yml +19 -0
- package/crates/bsnext_client/ui/package.json +18 -0
- package/crates/bsnext_client/ui/src/components/bs-debug.ts +27 -0
- package/crates/bsnext_client/ui/src/components/bs-header.ts +33 -0
- package/crates/bsnext_client/ui/src/components/bs-icon.ts +101 -0
- package/crates/bsnext_client/ui/src/components/bs-server-detail.ts +21 -0
- package/crates/bsnext_client/ui/src/components/bs-server-identity.ts +24 -0
- package/crates/bsnext_client/ui/src/components/bs-server-list.ts +39 -0
- package/crates/bsnext_client/ui/src/index.ts +39 -0
- package/crates/bsnext_client/ui/styles/base.css.ts +17 -0
- package/crates/bsnext_client/ui/styles/reset.css +52 -0
- package/crates/bsnext_client/ui/styles/style.css +29 -0
- package/crates/bsnext_client/ui/svg/wordmark-white.svg +38 -0
- package/crates/bsnext_core/Cargo.toml +44 -0
- package/crates/bsnext_core/src/common_layers.rs +62 -0
- package/crates/bsnext_core/src/dir_loader.rs +230 -0
- package/crates/bsnext_core/src/dto.rs +341 -0
- package/crates/bsnext_core/src/handlers/mod.rs +1 -0
- package/crates/bsnext_core/src/handlers/proxy.rs +95 -0
- package/crates/bsnext_core/src/lib.rs +12 -0
- package/crates/bsnext_core/src/meta/mod.rs +5 -0
- package/crates/bsnext_core/src/not_found/mod.rs +1 -0
- package/crates/bsnext_core/src/not_found/not_found_service.rs +35 -0
- package/crates/bsnext_core/src/panic_handler.rs +32 -0
- package/crates/bsnext_core/src/raw_loader.rs +196 -0
- package/crates/bsnext_core/src/server/actor.rs +92 -0
- package/crates/bsnext_core/src/server/error.rs +41 -0
- package/crates/bsnext_core/src/server/handler_change.rs +85 -0
- package/crates/bsnext_core/src/server/handler_listen.rs +163 -0
- package/crates/bsnext_core/src/server/handler_patch.rs +42 -0
- package/crates/bsnext_core/src/server/handler_routes_updated.rs +27 -0
- package/crates/bsnext_core/src/server/handler_stop.rs +38 -0
- package/crates/bsnext_core/src/server/mod.rs +10 -0
- package/crates/bsnext_core/src/server/router/assets.rs +39 -0
- package/crates/bsnext_core/src/server/router/mod.rs +123 -0
- package/crates/bsnext_core/src/server/router/pub_api.rs +39 -0
- package/crates/bsnext_core/src/server/router/snapshots/bsnext_core__server__router__tests__test__handlers.snap +9 -0
- package/crates/bsnext_core/src/server/router/tests.rs +209 -0
- package/crates/bsnext_core/src/server/signals.rs +11 -0
- package/crates/bsnext_core/src/server/state.rs +23 -0
- package/crates/bsnext_core/src/servers_supervisor/actor.rs +199 -0
- package/crates/bsnext_core/src/servers_supervisor/file_changed_handler.rs +41 -0
- package/crates/bsnext_core/src/servers_supervisor/get_servers_handler.rs +24 -0
- package/crates/bsnext_core/src/servers_supervisor/input_changed_handler.rs +21 -0
- package/crates/bsnext_core/src/servers_supervisor/mod.rs +6 -0
- package/crates/bsnext_core/src/servers_supervisor/start_handler.rs +86 -0
- package/crates/bsnext_core/src/servers_supervisor/stop_handler.rs +26 -0
- package/crates/bsnext_core/src/ws/mod.rs +164 -0
- package/crates/bsnext_example/Cargo.toml +10 -0
- package/crates/bsnext_example/src/basic.rs +51 -0
- package/crates/bsnext_example/src/lib.rs +26 -0
- package/crates/bsnext_example/src/lit.rs +37 -0
- package/crates/bsnext_example/src/md.rs +18 -0
- package/crates/bsnext_fs/Cargo.toml +22 -0
- package/crates/bsnext_fs/src/actor.rs +123 -0
- package/crates/bsnext_fs/src/buffered_debounce.rs +166 -0
- package/crates/bsnext_fs/src/filter.rs +39 -0
- package/crates/bsnext_fs/src/inner_fs_event_handler.rs +94 -0
- package/crates/bsnext_fs/src/lib.rs +141 -0
- package/crates/bsnext_fs/src/remove_path_handler.rs +46 -0
- package/crates/bsnext_fs/src/stop_handler.rs +15 -0
- package/crates/bsnext_fs/src/stream.rs +167 -0
- package/crates/bsnext_fs/src/test/mod.rs +213 -0
- package/crates/bsnext_fs/src/watch_path_handler.rs +67 -0
- package/crates/bsnext_fs/src/watcher.rs +349 -0
- package/crates/bsnext_input/Cargo.toml +25 -0
- package/crates/bsnext_input/src/input_test/mod.rs +185 -0
- package/crates/bsnext_input/src/input_test/snapshots/bsnext_input__input_test__deserialize_3_headers.snap +29 -0
- package/crates/bsnext_input/src/input_test/snapshots/bsnext_input__input_test__deserialize_3_headers_control.snap +25 -0
- package/crates/bsnext_input/src/lib.rs +153 -0
- package/crates/bsnext_input/src/md.rs +541 -0
- package/crates/bsnext_input/src/paths.rs +64 -0
- package/crates/bsnext_input/src/route.rs +189 -0
- package/crates/bsnext_input/src/route_manifest.rs +186 -0
- package/crates/bsnext_input/src/server_config.rs +88 -0
- package/crates/bsnext_input/src/target.rs +7 -0
- package/crates/bsnext_input/src/watch_opt_test/mod.rs +68 -0
- package/crates/bsnext_input/src/yml.rs +1 -0
- package/crates/bsnext_output/Cargo.toml +16 -0
- package/crates/bsnext_output/src/json.rs +11 -0
- package/crates/bsnext_output/src/lib.rs +25 -0
- package/crates/bsnext_output/src/pretty.rs +147 -0
- package/crates/bsnext_resp/Cargo.toml +13 -0
- package/crates/bsnext_resp/src/js/snippet.html +1 -0
- package/crates/bsnext_resp/src/js/ws.js +1 -0
- package/crates/bsnext_resp/src/lib.rs +79 -0
- package/crates/bsnext_system/Cargo.toml +29 -0
- package/crates/bsnext_system/src/args.rs +47 -0
- package/crates/bsnext_system/src/lib.rs +227 -0
- package/crates/bsnext_system/src/monitor.rs +241 -0
- package/crates/bsnext_system/src/monitor_any_watchables.rs +133 -0
- package/crates/bsnext_system/src/start_kind/snapshots/bsnext_system__start_kind__start_from_paths__test__test-2.snap +12 -0
- package/crates/bsnext_system/src/start_kind/snapshots/bsnext_system__start_kind__start_from_paths__test__test.snap +30 -0
- package/crates/bsnext_system/src/start_kind/start_from_example.rs +49 -0
- package/crates/bsnext_system/src/start_kind/start_from_inputs.rs +67 -0
- package/crates/bsnext_system/src/start_kind/start_from_paths.rs +51 -0
- package/crates/bsnext_system/src/start_kind.rs +56 -0
- package/crates/bsnext_system/src/startup.rs +51 -0
- package/crates/bsnext_tracing/Cargo.toml +10 -0
- package/crates/bsnext_tracing/src/lib.rs +127 -0
- package/examples/basic/input.yml +5 -0
- package/examples/basic/public/index.html +15 -0
- package/examples/basic/public/reset.css +52 -0
- package/examples/basic/public/script.js +1 -0
- package/examples/basic/public/styles.css +3 -0
- package/examples/kitchen-sink/a-file.md +1 -0
- package/examples/kitchen-sink/api/1.json +1 -0
- package/examples/kitchen-sink/api/2.json +3 -0
- package/examples/kitchen-sink/app.js +1 -0
- package/examples/kitchen-sink/input.html +185 -0
- package/examples/kitchen-sink/input.yml +133 -0
- package/examples/kitchen-sink/styles-2.css +3 -0
- package/examples/lit/index.html +21 -0
- package/examples/lit/input.yml +5 -0
- package/examples/lit/lit.js +82 -0
- package/examples/lit/package-lock.json +62 -0
- package/examples/lit/package.json +15 -0
- package/examples/md-single/frontmatter.md +35 -0
- package/examples/md-single/md-single.md +35 -0
- package/examples/openai/.nvm +1 -0
- package/examples/openai/build.mjs +21 -0
- package/examples/openai/index.html +13 -0
- package/examples/openai/input.yml +59 -0
- package/examples/openai/package-lock.json +720 -0
- package/examples/openai/package.json +21 -0
- package/examples/openai/src/index.js +21 -0
- package/examples/openai/sse/01.txt +8 -0
- package/examples/proxy-simple/input.yml +9 -0
- package/examples/single/input.yaml +26 -0
- package/index.d.ts +2 -0
- package/index.js +2 -1
- package/package.json +17 -17
- package/run.sh +6 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
use crate::target::TargetKind;
|
|
2
|
+
|
|
3
|
+
use std::fmt::{Display, Formatter};
|
|
4
|
+
use std::fs;
|
|
5
|
+
use std::fs::read_to_string;
|
|
6
|
+
use std::net::AddrParseError;
|
|
7
|
+
use std::path::{Path, PathBuf};
|
|
8
|
+
|
|
9
|
+
#[cfg(test)]
|
|
10
|
+
pub mod input_test;
|
|
11
|
+
pub mod md;
|
|
12
|
+
pub mod paths;
|
|
13
|
+
pub mod route;
|
|
14
|
+
pub mod route_manifest;
|
|
15
|
+
pub mod server_config;
|
|
16
|
+
pub mod target;
|
|
17
|
+
#[cfg(test)]
|
|
18
|
+
pub mod watch_opt_test;
|
|
19
|
+
pub mod yml;
|
|
20
|
+
|
|
21
|
+
#[derive(Debug, Default, serde::Deserialize, serde::Serialize)]
|
|
22
|
+
pub struct Input {
|
|
23
|
+
pub servers: Vec<server_config::ServerConfig>,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
impl Input {
|
|
27
|
+
pub fn from_input_path<P: AsRef<Path>>(path: P) -> Result<Self, anyhow::Error> {
|
|
28
|
+
match path.as_ref().extension().and_then(|x| x.to_str()) {
|
|
29
|
+
None => Err(anyhow::anyhow!(
|
|
30
|
+
"paths without extensions are not supported"
|
|
31
|
+
)),
|
|
32
|
+
Some("yml") | Some("yaml") => Input::from_yaml_path(path),
|
|
33
|
+
Some("md") | Some("markdown") => Input::from_md_path(path),
|
|
34
|
+
_ => Err(anyhow::anyhow!("unsupported extension")),
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
fn from_yaml_path<P: AsRef<Path>>(path: P) -> Result<Self, anyhow::Error> {
|
|
38
|
+
let str = read_to_string(path)?;
|
|
39
|
+
let output = serde_yaml::from_str::<Self>(str.as_str())?;
|
|
40
|
+
// todo: don't allow duplicates.
|
|
41
|
+
Ok(output)
|
|
42
|
+
}
|
|
43
|
+
fn from_md_path<P: AsRef<Path>>(path: P) -> Result<Self, anyhow::Error> {
|
|
44
|
+
let str = read_to_string(path)?;
|
|
45
|
+
let input = md::md_to_input(&str)?;
|
|
46
|
+
// todo: don't allow duplicates.
|
|
47
|
+
Ok(input)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
#[derive(Debug, thiserror::Error)]
|
|
52
|
+
pub enum InputError {
|
|
53
|
+
#[error("no suitable inputs could be found")]
|
|
54
|
+
MissingInputs,
|
|
55
|
+
#[error("could not read input, error: {0}")]
|
|
56
|
+
InvalidInput(String),
|
|
57
|
+
#[error("Could not find the input file: {0}")]
|
|
58
|
+
NotFound(PathBuf),
|
|
59
|
+
#[error("InputWriteError prevented startup {0}")]
|
|
60
|
+
InputWriteError(#[from] InputWriteError),
|
|
61
|
+
#[error("Input path error prevented startup {0}")]
|
|
62
|
+
PathError(#[from] PathError),
|
|
63
|
+
#[error("Input port error prevented startup {0}")]
|
|
64
|
+
PortError(#[from] PortError),
|
|
65
|
+
#[error("Input directory error prevented startup {0}")]
|
|
66
|
+
DirError(#[from] DirError),
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
#[derive(Debug, thiserror::Error)]
|
|
70
|
+
pub enum WatchError {
|
|
71
|
+
#[error("don't add `.` before the extension.")]
|
|
72
|
+
InvalidExtensionFilter,
|
|
73
|
+
#[error("empty")]
|
|
74
|
+
EmptyExtensionFilter,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
#[derive(Debug, thiserror::Error)]
|
|
78
|
+
pub enum InputWriteError {
|
|
79
|
+
#[error("couldn't write input to {path}")]
|
|
80
|
+
FailedWrite { path: PathBuf },
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
#[derive(Debug, thiserror::Error, serde::Serialize, serde::Deserialize)]
|
|
84
|
+
pub enum PathError {
|
|
85
|
+
#[error("path(s) not found \n{paths}")]
|
|
86
|
+
MissingPaths { paths: PathDefs },
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
#[derive(Debug, thiserror::Error)]
|
|
90
|
+
#[error(transparent)]
|
|
91
|
+
pub enum PortError {
|
|
92
|
+
#[error("could not use that port: {port} {err}")]
|
|
93
|
+
InvalidPort { port: u16, err: AddrParseError },
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
#[derive(Debug, thiserror::Error)]
|
|
97
|
+
#[error(transparent)]
|
|
98
|
+
pub enum DirError {
|
|
99
|
+
#[error("could not create that dir: {path}")]
|
|
100
|
+
CannotCreate { path: PathBuf },
|
|
101
|
+
#[error("could not change the process CWD to: {path}")]
|
|
102
|
+
CannotMove { path: PathBuf },
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
#[derive(Debug, thiserror::Error, serde::Serialize, serde::Deserialize)]
|
|
106
|
+
pub struct PathDefs(Vec<PathDefinition>);
|
|
107
|
+
|
|
108
|
+
#[derive(Debug, thiserror::Error, serde::Serialize, serde::Deserialize)]
|
|
109
|
+
struct PathDefinition {
|
|
110
|
+
pub input: String,
|
|
111
|
+
pub cwd: PathBuf,
|
|
112
|
+
pub absolute: PathBuf,
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
impl Display for PathDefs {
|
|
116
|
+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
117
|
+
for pd in self.0.iter() {
|
|
118
|
+
writeln!(f, " cwd: {}", pd.cwd.display())?;
|
|
119
|
+
writeln!(f, " input: {}", pd.input)?;
|
|
120
|
+
}
|
|
121
|
+
Ok(())
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
impl Display for PathDefinition {
|
|
125
|
+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
126
|
+
f.debug_struct("PathDefinition").finish()
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
pub fn rand_word() -> String {
|
|
131
|
+
random_word::gen(random_word::Lang::En).to_string()
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
pub fn fs_write_input(
|
|
135
|
+
cwd: &Path,
|
|
136
|
+
input: &Input,
|
|
137
|
+
target_kind: TargetKind,
|
|
138
|
+
) -> Result<PathBuf, InputWriteError> {
|
|
139
|
+
let string = match target_kind {
|
|
140
|
+
TargetKind::Yaml => serde_yaml::to_string(&input).expect("create yaml?"),
|
|
141
|
+
TargetKind::Toml => toml::to_string_pretty(&input).expect("create toml?"),
|
|
142
|
+
TargetKind::Md => md::input_to_str(input),
|
|
143
|
+
};
|
|
144
|
+
let name = match target_kind {
|
|
145
|
+
TargetKind::Yaml => "input.yml",
|
|
146
|
+
TargetKind::Toml => "input.toml",
|
|
147
|
+
TargetKind::Md => "input.md",
|
|
148
|
+
};
|
|
149
|
+
let next_path = cwd.join(name);
|
|
150
|
+
fs::write(&next_path, string)
|
|
151
|
+
.map(|()| next_path.clone())
|
|
152
|
+
.map_err(|_e| InputWriteError::FailedWrite { path: next_path })
|
|
153
|
+
}
|
|
@@ -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
|
+
}
|