blue_lang_runtime/
host_surface.rs1use std::path::{Path, PathBuf};
2
3pub const FILE: &str = "blue-host.json";
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub struct HostName {
7 pub name: String,
8 pub params: Vec<String>,
9 pub required: usize,
10}
11
12impl HostName {
13 #[must_use]
14 pub fn nullary(&self) -> bool {
15 self.params.is_empty()
16 }
17}
18
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct Surface {
21 pub file: PathBuf,
22 pub functions: Vec<HostName>,
23}
24
25#[derive(Debug, thiserror::Error)]
26pub enum SurfaceError {
27 #[error("{file}: {reason}")]
28 Unreadable { file: String, reason: String },
29}
30
31#[must_use]
32pub fn find(entry: &Path) -> Option<PathBuf> {
33 entry
34 .ancestors()
35 .skip(1)
36 .map(|dir| dir.join(FILE))
37 .find(|candidate| candidate.is_file())
38}
39
40pub fn near(entry: &Path) -> Result<Option<Surface>, SurfaceError> {
41 find(entry).map(|file| read(&file)).transpose()
42}
43
44pub fn read(file: &Path) -> Result<Surface, SurfaceError> {
45 let shown = file.display().to_string();
46 let unreadable = |reason: String| SurfaceError::Unreadable {
47 file: shown.clone(),
48 reason,
49 };
50 let text = std::fs::read_to_string(file).map_err(|e| unreadable(e.to_string()))?;
51 parse(&text)
52 .map(|functions| Surface {
53 file: file.to_path_buf(),
54 functions,
55 })
56 .map_err(unreadable)
57}
58
59pub fn parse(text: &str) -> Result<Vec<HostName>, String> {
60 let doc: serde_json::Value = serde_json::from_str(text).map_err(|e| e.to_string())?;
61 let functions = doc
62 .get("functions")
63 .and_then(serde_json::Value::as_array)
64 .ok_or("expected an object with a `functions` list")?;
65 functions
66 .iter()
67 .enumerate()
68 .map(|(i, f)| {
69 let name = f
70 .get("name")
71 .and_then(serde_json::Value::as_str)
72 .ok_or(format!("functions[{i}] has no string `name`"))?;
73 let params = f
74 .get("params")
75 .and_then(serde_json::Value::as_array)
76 .ok_or(format!("{name} has no `params` list"))?
77 .iter()
78 .map(|p| p.as_str().map(str::to_owned))
79 .collect::<Option<Vec<_>>>()
80 .ok_or(format!("{name}'s params are not all strings"))?;
81 let required = f
82 .get("required")
83 .and_then(serde_json::Value::as_u64)
84 .and_then(|n| usize::try_from(n).ok())
85 .unwrap_or(params.len());
86 if required > params.len() {
87 return Err(format!(
88 "{name} requires {required} of {} params",
89 params.len()
90 ));
91 }
92 Ok(HostName {
93 name: name.to_owned(),
94 params,
95 required,
96 })
97 })
98 .collect()
99}
100
101#[must_use]
102pub fn render<'a>(
103 functions: impl IntoIterator<Item = (&'a str, &'a [&'a str], usize, &'a str)>,
104) -> String {
105 let functions: Vec<serde_json::Value> = functions
106 .into_iter()
107 .map(|(name, params, required, doc)| {
108 serde_json::json!({
109 "name": name,
110 "params": params,
111 "required": required,
112 "doc": doc,
113 })
114 })
115 .collect();
116 let mut out = serde_json::to_string_pretty(&serde_json::json!({ "functions": functions }))
117 .unwrap_or_default();
118 out.push('\n');
119 out
120}