Skip to main content

yog_cli/
serve.rs

1//! `yog serve` — the CLI as a service for tools (Yog-IDLE): frames of
2//! binary Yog Data (`yog_data::io`) on stdin and stdout, each request a
3//! [`Request`] and each answer a [`Reply`]. Every format is read, written
4//! and converted here, so a tool needn't be rebuilt when a format changes —
5//! only this binary. Answers hold plain values: a typed one (`@item:x`) is
6//! sent as its string.
7//!
8//! Methods: the service's own (`data`), a mod's resources (`media`) and
9//! the designers' (`design::service`).
10
11mod check;
12mod data;
13mod error;
14mod media;
15
16pub(crate) use error::ServiceError;
17
18/// What an editor's live check (`source.check`) finds in `text`, the file
19/// at `path` as edited.
20pub fn source_problems(path: &str, text: &str) -> Vec<yog_data::Diagnostic> {
21    check::check(path, text).problems
22}
23
24use yog_data::Schema;
25
26/// What a tool asks: a method, and its parameters.
27#[derive(Debug, Clone, Schema)]
28pub(crate) struct Request {
29    pub(crate) method: String,
30    #[data(default)]
31    pub(crate) params: yog_data::Value,
32}
33
34/// The answer: the method's result, or what went wrong.
35pub(crate) type Reply = Result<yog_data::Value, ServiceError>;
36
37/// Serves requests until stdin ends.
38pub fn run() -> Result<(), String> {
39    let mut input = std::io::stdin().lock();
40    let mut out = std::io::stdout().lock();
41    loop {
42        let reply = match yog_data::io::read_frame_bytes(&mut input) {
43            Ok(None) => return Ok(()),
44            Ok(Some(bytes)) => match yog_data::read_bytes::<Request>(&bytes) {
45                Ok(request) => answer(&request),
46                Err(e) => Err(format!("not a request: {e:?}").into()),
47            },
48            Err(e) => return Err(e.to_string()),
49        };
50        yog_data::io::write_frame(&mut out, &reply).map_err(|e| e.to_string())?;
51    }
52}
53
54/// `request` answered, in plain values.
55pub(crate) fn answer(request: &Request) -> Reply {
56    let (method, params) = (request.method.as_str(), &request.params);
57    let reply = data::method(method, params)
58        .or_else(|| media::method(method, params))
59        // The designers: drawing, pickers' choices, edits of a mod's files.
60        .or_else(|| crate::design::service::dispatch(method, params))
61        .unwrap_or_else(|| Err(format!("no method `{method}`").into()));
62    reply
63        .map(|v| yog_data::typed::flatten(&v))
64        .map_err(|mut e| {
65            e.file = e.file.take().or_else(|| file_of(params));
66            e
67        })
68}
69
70/// The file a request is about: its `path`, or its arguments'.
71fn file_of(params: &yog_data::Value) -> Option<String> {
72    let path = |v: &yog_data::Value| {
73        v.get(yog_ids::serve_param::PATH)
74            .and_then(yog_data::Value::as_str)
75            .map(str::to_string)
76    };
77    path(params).or_else(|| params.get(yog_ids::serve_param::ARGS).and_then(path))
78}
79
80#[cfg(test)]
81mod tests;