Line data Source code
1 : //! The commands of `dashql agent` that agents call. The table makes the usage of the command line tool and the
2 : //! command table of the guide and of the instructions of harness runs.
3 :
4 : /// A command of `dashql agent` for the open DashQL session
5 : pub struct AgentCommand {
6 : /// The words of the command, e.g. `vis edit`
7 : pub name: &'static str,
8 : /// The arguments and the options of the command
9 : pub args: &'static str,
10 : /// What the command does, Markdown
11 : pub does: &'static str,
12 : }
13 :
14 : impl AgentCommand {
15 : /// The command of a call to the session, e.g. `vis.edit`. None for `status`, which the server answers, and for
16 : /// `guide`, which the command line tool answers.
17 109 : pub fn call(&self) -> Option<String> {
18 109 : (self.name != "status" && self.name != "guide").then(|| self.name.replace(' ', "."))
19 109 : }
20 :
21 804 : fn synopsis(&self) -> String {
22 804 : if self.args.is_empty() { self.name.to_string() } else { format!("{} {}", self.name, self.args) }
23 804 : }
24 : }
25 :
26 : pub const AGENT_COMMANDS: [AgentCommand; 12] = [
27 : AgentCommand { name: "status", args: "", does: "Connected clients, sessions, the focused session" },
28 : AgentCommand { name: "guide", args: "", does: "The commands and how DashQL works, read it first" },
29 : AgentCommand { name: "scripts", args: "", does: "Scripts of the session with their connections and attachments" },
30 : AgentCommand { name: "read", args: "<script>", does: "Script text" },
31 : AgentCommand {
32 : name: "edit",
33 : args: "<script> [--file f] [--connection c]",
34 : does: "Replace or create a script with the text of the file or stdin",
35 : },
36 : AgentCommand {
37 : name: "catalog",
38 : args: "[connection]",
39 : does: "`CREATE TABLE` statements of the tables you can query",
40 : },
41 : AgentCommand { name: "lint", args: "<script>", does: "Parser and analyzer diagnostics of a script" },
42 : AgentCommand {
43 : name: "run",
44 : args: "<script> [--max-rows N] [--force]",
45 : does: "Run a script: columns, first rows, row count or error",
46 : },
47 : AgentCommand { name: "result", args: "<script> [--page N]", does: "More rows of the last result of a script" },
48 : AgentCommand {
49 : name: "vis read",
50 : args: "<script>",
51 : does: "The chart of a script: its renderer and Vega-Lite spec",
52 : },
53 : AgentCommand {
54 : name: "vis edit",
55 : args: "<script> [--file f]",
56 : does: "Chart the last query of a script with the Vega-Lite spec of the file or stdin",
57 : },
58 : AgentCommand {
59 : name: "vis image",
60 : args: "<script> [--out f] [--width N] [--height N] [--scale N]",
61 : does: "Render the chart of a script with its last result as PNG, default `<script>.png`",
62 : },
63 : ];
64 :
65 : /// Is this a command that a client runs in its session?
66 13 : pub fn is_session_command(call: &str) -> bool {
67 97 : AGENT_COMMANDS.iter().any(|command| command.call().as_deref() == Some(call))
68 13 : }
69 :
70 : /// The subcommands of `vis`
71 14 : pub fn vis_commands() -> impl Iterator<Item = &'static str> {
72 160 : AGENT_COMMANDS.iter().filter_map(|command| command.name.strip_prefix("vis "))
73 14 : }
74 :
75 : /// The commands as a Markdown table, for the guide and the instructions
76 52 : pub fn command_table() -> String {
77 52 : let mut lines = vec!["| Command | Does |".to_string(), "|---|---|".to_string()];
78 624 : lines.extend(AGENT_COMMANDS.iter().map(|command| format!("| `dashql agent {}` | {} |", command.synopsis(), command.does)));
79 52 : lines.join("\n")
80 52 : }
81 :
82 : /// The width of the first column of the usage
83 : const USAGE_COLUMN: usize = 38;
84 :
85 : /// The commands in the usage of the command line tool
86 15 : pub fn usage_lines() -> String {
87 15 : let mut lines = Vec::new();
88 180 : for command in &AGENT_COMMANDS {
89 180 : let synopsis = format!(" {}", command.synopsis());
90 180 : let does = command.does.replace('`', "");
91 180 : if synopsis.len() < USAGE_COLUMN {
92 135 : lines.push(format!("{synopsis:<USAGE_COLUMN$}{does}"));
93 135 : } else {
94 45 : lines.push(synopsis);
95 45 : lines.push(format!("{:USAGE_COLUMN$}{does}", ""));
96 45 : }
97 : }
98 15 : lines.join("\n")
99 15 : }
100 :
101 : #[cfg(test)]
102 : mod tests {
103 : use super::*;
104 :
105 : #[test]
106 1 : fn names_the_calls_of_the_commands() {
107 1 : assert!(is_session_command("vis.edit"));
108 1 : assert!(is_session_command("catalog"));
109 1 : assert!(!is_session_command("guide"));
110 1 : assert!(!is_session_command("status"));
111 1 : assert!(!is_session_command("vis edit"));
112 1 : assert_eq!(vis_commands().collect::<Vec<_>>(), ["read", "edit", "image"]);
113 1 : }
114 :
115 : #[test]
116 1 : fn lists_the_commands_for_the_usage() {
117 1 : let usage = usage_lines();
118 1 : assert!(usage.contains("\n read <script> Script text\n"));
119 1 : assert!(usage.contains(
120 1 : "\n vis image <script> [--out f] [--width N] [--height N] [--scale N]\n Render"
121 1 : ));
122 1 : assert!(usage.contains("CREATE TABLE statements"));
123 1 : }
124 : }
|