Skip to main content

rudb_cli/
lib.rs

1//! The command line shell.
2//!
3//! Rank 15 in the layer rule. See `xtask/layers.toml` and `spec/18-package-layout.md`.
4//!
5//! The binary is a few lines over this, so that the shell can be driven from a test without
6//! spawning a process and so that `rudb-compat` can drive it as a target the same way it drives the
7//! library. Everything the shell does goes through [`rudb::Database`], which means the shell has no
8//! way to reach anything the embedding API cannot, which is the point: if the prompt can do it, a
9//! program can do it.
10//!
11//! The command line and the dot commands are DuckDB's, down to the single dash long options it
12//! inherits from SQLite. The output modes are DuckDB's too, byte for byte, because the reason
13//! anybody pipes a shell into another program is that they already know what comes out.
14
15#![forbid(unsafe_code)]
16
17pub mod args;
18pub mod format;
19pub mod help;
20pub mod shell;
21
22use std::io::{IsTerminal, Read, Write};
23use std::process::ExitCode;
24
25pub use args::{Action, Command, Options, parse};
26pub use format::{Format, Settings};
27pub use shell::{Shell, Stop};
28
29/// The version, which `-version` prints and which the greeting carries.
30pub const VERSION: &str = env!("CARGO_PKG_VERSION");
31
32/// The whole program, minus the process it runs in.
33///
34/// `out` and `err` are handed in rather than taken from the process so that a test can read what
35/// came out of each without a pipe and without a temporary file.
36pub fn run(arguments: &[String], out: Box<dyn Write>, err: Box<dyn Write>) -> ExitCode {
37    let mut err = err;
38    match parse(arguments) {
39        Action::Version => {
40            let mut out = out;
41            let _ = writeln!(out, "rudb {VERSION}");
42            ExitCode::SUCCESS
43        }
44        Action::Help => {
45            let mut out = out;
46            let _ = write!(out, "{}", help::USAGE);
47            ExitCode::SUCCESS
48        }
49        Action::Config => {
50            let mut out = out;
51            print_config(&mut out);
52            ExitCode::SUCCESS
53        }
54        Action::Wrong(why) => {
55            let _ = writeln!(err, "rudb: {why}");
56            let _ = writeln!(err, "rudb: try `rudb -help`");
57            ExitCode::FAILURE
58        }
59        Action::Run(options) => {
60            if options.database != ":memory:" {
61                let _ = writeln!(
62                    err,
63                    "rudb: cannot open {}, because there is no storage format yet. See https://github.com/tamnd/rudb/issues/103",
64                    options.database
65                );
66                return ExitCode::FAILURE;
67            }
68            let mut shell = Shell::new(&options, out, err);
69            let mut stop = shell.run_commands(&options.commands);
70            if stop == Stop::Done && !options.stop_after_commands {
71                stop = read_input(&mut shell, &options);
72            }
73            let _ = stop;
74            if shell.failed() { ExitCode::FAILURE } else { ExitCode::SUCCESS }
75        }
76    }
77}
78
79/// Reads whatever is on standard input, with a prompt if that is a terminal.
80///
81/// There is no line editing, so no history, no arrow keys and no completion. That wants a
82/// dependency and the dependency budget in `spec/18-package-layout.md` is a decision to make on
83/// purpose rather than in passing, so it is a separate change. Everything else about the prompt
84/// works, including multi line statements.
85fn read_input(shell: &mut Shell, options: &Options) -> Stop {
86    let stdin = std::io::stdin();
87    let interactive = options.interactive.unwrap_or_else(|| stdin.is_terminal());
88    if !interactive {
89        let mut text = String::new();
90        if stdin.lock().read_to_string(&mut text).is_err() {
91            return Stop::Done;
92        }
93        return shell.run_input(&text);
94    }
95    shell.greet();
96    shell.prompt(&stdin)
97}
98
99/// The settled decisions from `spec/00-README.md` that a reader would otherwise have to take on
100/// trust. Printing them is cheap and it makes a bug report say which build it came from.
101fn print_config(out: &mut dyn Write) {
102    let _ = writeln!(out, "version: {VERSION}");
103    let _ = writeln!(out, "vector-size: 1024");
104    let _ = writeln!(out, "row-group-size: 122880");
105    let _ = writeln!(out, "storage-format: native (rudb v1), DuckDB import and export");
106    let _ = writeln!(out, "execution-tiers: interpreted");
107    let _ = writeln!(out, "duckdb-compat-level: 0 (nothing is implemented yet)");
108    let _ = writeln!(out, "target: {}", std::env::consts::ARCH);
109    let _ = writeln!(out, "os: {}", std::env::consts::OS);
110}