photonic_interface_cli/
lib.rs

1use std::sync::Arc;
2
3use anyhow::Result;
4use palette::rgb::Rgb;
5use tokio::io::{AsyncBufReadExt, AsyncRead, AsyncWrite, AsyncWriteExt, BufReader, BufWriter};
6
7use photonic::attr::Range;
8use photonic::input::{InputSink, Trigger};
9use photonic::interface::Introspection;
10
11pub mod stdio;
12pub mod telnet;
13
14async fn run(i: impl AsyncRead + Unpin, o: impl AsyncWrite + Unpin, introspection: Arc<Introspection>) -> Result<()> {
15    let i = BufReader::new(i);
16    let mut o = BufWriter::new(o);
17
18    let mut lines = i.lines();
19    loop {
20        o.write_all("✨ » ".as_bytes()).await?;
21        o.flush().await?;
22
23        let line = match lines.next_line().await? {
24            Some(line) => line,
25            None => {
26                break;
27            }
28        };
29
30        let line = match shlex::split(&line) {
31            Some(line) => line,
32            None => {
33                o.write_all("Invalid input\n".as_bytes()).await?;
34                continue;
35            }
36        };
37
38        match line.first().map(String::as_str) {
39            Some("exit") => {
40                break;
41            }
42
43            Some("node") => {
44                if let Some(node) = line.get(1) {
45                    if let Some(node) = introspection.nodes.get(node) {
46                        o.write_all(format!("Node '{}':\n", node.name()).as_bytes()).await?;
47                        o.write_all(format!("  Kind: {}\n", node.kind()).as_bytes()).await?;
48                        o.write_all(format!("  Nodes: {}\n", node.kind()).as_bytes()).await?;
49                        for (name, info) in node.nodes().iter() {
50                            o.write_all(format!("    {} = [{}]\n", name, info.kind()).as_bytes()).await?;
51                        }
52                        o.write_all(format!("  Attributes: {}\n", node.kind()).as_bytes()).await?;
53                        for (name, info) in node.attrs().iter() {
54                            o.write_all(
55                                format!("    {} : {} = [{}]\n", name, info.value_type(), info.kind()).as_bytes(),
56                            )
57                            .await?;
58                            // TODO: Recurse into attrs
59                            // TODO: Show attached inputs
60                        }
61                    } else {
62                        o.write_all(format!("No such node: '{node}'\n").as_bytes()).await?;
63                    }
64                } else {
65                    for (name, info) in introspection.nodes.iter() {
66                        o.write_all(format!("{} = [{}]\n", name, info.kind()).as_bytes()).await?;
67                    }
68                }
69            }
70
71            Some("input") => {
72                if let Some(input) = line.get(1) {
73                    if let Some(input) = introspection.inputs.get(input) {
74                        if let Some(value) = line.get(2) {
75                            let res: Result<()> = (async {
76                                match &input.sink() {
77                                    InputSink::Trigger(sink) => sink.send(Trigger::next()).await,
78                                    InputSink::Boolean(sink) => sink.send(value.parse()?).await,
79                                    InputSink::Integer(sink) => sink.send(value.parse()?).await,
80                                    InputSink::Decimal(sink) => sink.send(value.parse()?).await,
81                                    InputSink::Color(sink) => {
82                                        sink.send(value.parse::<Rgb<_, u8>>()?.into_format()).await
83                                    }
84                                    InputSink::IntegerRange(sink) => sink.send(value.parse()?).await,
85                                    InputSink::DecimalRange(sink) => sink.send(value.parse()?).await,
86                                    InputSink::ColorRange(sink) => {
87                                        sink.send(value.parse::<Range<Rgb<_, u8>>>()?.map(Rgb::into_format)).await
88                                    }
89                                }
90                            })
91                            .await;
92
93                            match res {
94                                Ok(()) => {}
95                                Err(err) => {
96                                    o.write_all(
97                                        format!("Invalid value: '{}' for {}: {}", value, input.sink(), err).as_bytes(),
98                                    )
99                                    .await?;
100                                    continue;
101                                }
102                            }
103                        } else {
104                            o.write_all(format!("Input '{}':\n", input.name()).as_bytes()).await?;
105                            o.write_all(format!("  Value: {}\n", input.value_type()).as_bytes()).await?;
106                        }
107                    } else {
108                        o.write_all(format!("No such input: '{input}'\n").as_bytes()).await?;
109                    }
110                } else {
111                    for (name, info) in introspection.inputs.iter() {
112                        o.write_all(format!("{} : {}\n", name, info.value_type()).as_bytes()).await?;
113                    }
114                }
115            }
116
117            Some(unknown) => {
118                o.write_all(format!("Unknown command: '{unknown}'\n").as_bytes()).await?;
119                continue;
120            }
121            None => {
122                continue;
123            }
124        }
125    }
126
127    return Ok(());
128}