aboutsummaryrefslogtreecommitdiff
path: root/src/repl.rs
blob: 339405e664ce40b4d483335129b39b4b36df0b30 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
use crate::common::expr::Error as ProtoError;
use crate::common::expr::Expr;
use crate::common::term::Term;
use std::io::BufReader;
use std::io::Write;
use std::net::TcpStream;
use std::process::ExitCode;

pub const USAGE: &str = "
Usage: blom start

 Starts the blom repl.

 -b, --bind               Bind to the given ADDRESS:PORT. Default is
                          0.0.0.0:4902

";

enum Error {
    Connect,
}

pub fn cmd(args: &[String]) -> ExitCode {
    let mut addr = "0.0.0.0:4902";

    let mut arg_index: usize = 0;
    while arg_index < args.len() {
        match args[arg_index].as_str() {
            "--addr" | "-a" => {
                addr = args
                    .get(arg_index + 1)
                    .expect("Missing address after --addr.")
                    .as_str();
                arg_index += 1;
            }
            _ => {
                println!("Too many arguments.");
                return ExitCode::FAILURE;
            }
        }

        arg_index += 1;
    }

    match start(addr) {
        Ok(()) => ExitCode::SUCCESS,
        Err(_) => ExitCode::FAILURE,
    }
}

fn start(addr: &str) -> Result<(), Error> {
    let mut term = Term::new();

    term.setup_prompt(format!("{addr}> ").as_bytes());
    term.setup_hints(hints);

    while let Some(query) = input(&mut term) {
        if query.is_empty() {
            continue;
        }

        let mut stream = match try_connect(addr) {
            Ok(stream) => stream,
            Err(_) => continue,
        };

        let expr = Expr::from_query(&query);

        if stream.write(&expr.encode()).is_err() {
            println!("Couldn't connect to blom at {addr}");
            continue;
        }

        let mut resp = BufReader::new(&stream);

        match Expr::from_bytes(&mut resp) {
            Ok(resp) => println!("{resp}"),
            Err(ProtoError::ConnectionClosed) => {
                println!("Couldn't connect to blom at {addr}");
                continue;
            }
            Err(e) => {
                println!("{e}");
                break;
            }
        }
    }

    Ok(())
}

fn try_connect(addr: &str) -> Result<TcpStream, Error> {
    match TcpStream::connect(addr) {
        Ok(stream) => Ok(stream),
        Err(_) => {
            println!("Couldn't connect to blom at {addr}.");
            Err(Error::Connect)
        }
    }
}

fn input(term: &mut Term) -> Option<String> {
    let line = term.edit()?;
    let query = std::str::from_utf8(&line).unwrap().trim();

    if query == "exit" {
        return None;
    }

    Some(query.to_string())
}

// TODO: Refactor
fn hints(line: &[u8]) -> &[u8] {
    match line {
        b"ECHO" | b"echo" => b" MESSAGE",
        b"SET" | b"set" => b" BLOCK META",
        b"GET" | b"get" => b" BLOCK",
        b"DEL" | b"del" => b" BLOCK",
        b"EXISTS" | b"exists" => b" BLOCK",
        b"INFO" | b"info" => b" BLOCK",
        b"PARENT" | b"parent" => b" BLOCK",
        b"CHILDREN" | b"children" => b" BLOCK",
        b"ECHO " | b"echo " => b"MESSAGE",
        b"SET " | b"set " => b"BLOCK META",
        b"GET " | b"get " => b"BLOCK",
        b"DEL " | b"del " => b"BLOCK",
        b"EXISTS " | b"exists " => b"BLOCK",
        b"INFO " | b"info " => b"BLOCK",
        b"PARENT " | b"parent " => b"BLOCK",
        b"CHILDREN " | b"children " => b"BLOCK",
        _ => b"",
    }
}