Compare commits
15 commits
master
...
fluffyemil
Author | SHA1 | Date | |
---|---|---|---|
|
36f6efa1d6 | ||
|
c03b88eb43 | ||
|
002edeae1e | ||
|
825726ddf9 | ||
|
99e1c35377 | ||
|
525f8766b4 | ||
|
00af587682 | ||
|
e0548e9be2 | ||
|
a8bb996e4f | ||
|
102e528310 | ||
|
b3ff534690 | ||
|
45d00c43ac | ||
|
f3d39d4194 | ||
|
5a6c3f6598 | ||
|
9b30a2c0a7 |
11 changed files with 1070 additions and 3 deletions
|
@ -11,7 +11,7 @@ version = "0.4.0"
|
||||||
build = "build/version.rs"
|
build = "build/version.rs"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
members = []
|
members = ["tools/cli"]
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
rustc_version = "0.1.7"
|
rustc_version = "0.1.7"
|
||||||
|
|
|
@ -16,7 +16,7 @@ use rustc_version::version_matches;
|
||||||
|
|
||||||
/// MIN_VERSION should be changed when there's a new minimum version of rustc required
|
/// MIN_VERSION should be changed when there's a new minimum version of rustc required
|
||||||
/// to build the project.
|
/// to build the project.
|
||||||
static MIN_VERSION: &'static str = ">= 1.15.1";
|
static MIN_VERSION: &'static str = ">= 1.17.0";
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
if !version_matches(MIN_VERSION) {
|
if !version_matches(MIN_VERSION) {
|
||||||
|
|
|
@ -43,7 +43,7 @@ mod entids;
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
mod metadata;
|
mod metadata;
|
||||||
mod schema;
|
mod schema;
|
||||||
mod types;
|
pub mod types;
|
||||||
mod internal_types;
|
mod internal_types;
|
||||||
mod upsert_resolution;
|
mod upsert_resolution;
|
||||||
mod tx;
|
mod tx;
|
||||||
|
|
45
tools/cli/Cargo.toml
Normal file
45
tools/cli/Cargo.toml
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
[package]
|
||||||
|
name = "mentat_cli"
|
||||||
|
version = "0.0.1"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "mentat_cli"
|
||||||
|
path = "src/mentat_cli/lib.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "mentat_cli"
|
||||||
|
doc = false
|
||||||
|
test = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
getopts = "0.2"
|
||||||
|
env_logger = "0.3"
|
||||||
|
linefeed = "0.1"
|
||||||
|
log = "0.3"
|
||||||
|
tempfile = "1.1"
|
||||||
|
combine = "2.2.2"
|
||||||
|
lazy_static = "0.2.2"
|
||||||
|
error-chain = "0.8.1"
|
||||||
|
|
||||||
|
[dependencies.rusqlite]
|
||||||
|
version = "0.11"
|
||||||
|
# System sqlite might be very old.
|
||||||
|
features = ["bundled", "limits"]
|
||||||
|
|
||||||
|
[dependencies.mentat]
|
||||||
|
path = "../.."
|
||||||
|
|
||||||
|
[dependencies.mentat_parser_utils]
|
||||||
|
path = "../../parser-utils"
|
||||||
|
|
||||||
|
[dependencies.edn]
|
||||||
|
path = "../../edn"
|
||||||
|
|
||||||
|
[dependencies.mentat_query]
|
||||||
|
path = "../../query"
|
||||||
|
|
||||||
|
[dependencies.mentat_core]
|
||||||
|
path = "../../core"
|
||||||
|
|
||||||
|
[dependencies.mentat_db]
|
||||||
|
path = "../../db"
|
15
tools/cli/src/bin/mentat_cli.rs
Normal file
15
tools/cli/src/bin/mentat_cli.rs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
// Copyright 2017 Mozilla
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||||
|
// this file except in compliance with the License. You may obtain a copy of the
|
||||||
|
// License at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
// Unless required by applicable law or agreed to in writing, software distributed
|
||||||
|
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||||
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
|
// specific language governing permissions and limitations under the License.
|
||||||
|
extern crate mentat_cli;
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let status = mentat_cli::run();
|
||||||
|
std::process::exit(status);
|
||||||
|
}
|
428
tools/cli/src/mentat_cli/command_parser.rs
Normal file
428
tools/cli/src/mentat_cli/command_parser.rs
Normal file
|
@ -0,0 +1,428 @@
|
||||||
|
// Copyright 2017 Mozilla
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||||
|
// this file except in compliance with the License. You may obtain a copy of the
|
||||||
|
// License at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
// Unless required by applicable law or agreed to in writing, software distributed
|
||||||
|
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||||
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
|
use combine::{
|
||||||
|
any,
|
||||||
|
eof,
|
||||||
|
look_ahead,
|
||||||
|
many1,
|
||||||
|
parser,
|
||||||
|
satisfy,
|
||||||
|
sep_end_by,
|
||||||
|
token,
|
||||||
|
Parser
|
||||||
|
};
|
||||||
|
use combine::char::{
|
||||||
|
space,
|
||||||
|
spaces,
|
||||||
|
string
|
||||||
|
};
|
||||||
|
use combine::combinator::{
|
||||||
|
choice,
|
||||||
|
try
|
||||||
|
};
|
||||||
|
|
||||||
|
use combine::primitives::Consumed;
|
||||||
|
|
||||||
|
use errors as cli;
|
||||||
|
|
||||||
|
use edn;
|
||||||
|
|
||||||
|
pub static HELP_COMMAND: &'static str = &"help";
|
||||||
|
pub static OPEN_COMMAND: &'static str = &"open";
|
||||||
|
pub static CLOSE_COMMAND: &'static str = &"close";
|
||||||
|
pub static LONG_QUERY_COMMAND: &'static str = &"query";
|
||||||
|
pub static SHORT_QUERY_COMMAND: &'static str = &"q";
|
||||||
|
pub static LONG_TRANSACT_COMMAND: &'static str = &"transact";
|
||||||
|
pub static SHORT_TRANSACT_COMMAND: &'static str = &"t";
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub enum Command {
|
||||||
|
Transact(String),
|
||||||
|
Query(String),
|
||||||
|
Help(Vec<String>),
|
||||||
|
Open(String),
|
||||||
|
Close,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Command {
|
||||||
|
/// is_complete returns true if no more input is required for the command to be successfully executed.
|
||||||
|
/// false is returned if the command is not considered valid.
|
||||||
|
/// Defaults to true for all commands except Query and Transact.
|
||||||
|
/// TODO: for query and transact commands, they will be considered complete if a parsable EDN has been entered as an argument
|
||||||
|
pub fn is_complete(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
&Command::Query(ref args) |
|
||||||
|
&Command::Transact(ref args) => {
|
||||||
|
edn::parse::value(&args).is_ok()
|
||||||
|
},
|
||||||
|
&Command::Help(_) |
|
||||||
|
&Command::Open(_) |
|
||||||
|
&Command::Close => true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn output(&self) -> String {
|
||||||
|
match self {
|
||||||
|
&Command::Query(ref args) => {
|
||||||
|
format!(".{} {}", LONG_QUERY_COMMAND, args)
|
||||||
|
},
|
||||||
|
&Command::Transact(ref args) => {
|
||||||
|
format!(".{} {}", LONG_TRANSACT_COMMAND, args)
|
||||||
|
},
|
||||||
|
&Command::Help(ref args) => {
|
||||||
|
format!(".{} {:?}", HELP_COMMAND, args)
|
||||||
|
},
|
||||||
|
&Command::Open(ref args) => {
|
||||||
|
format!(".{} {}", OPEN_COMMAND, args)
|
||||||
|
}
|
||||||
|
&Command::Close => {
|
||||||
|
format!(".{}", CLOSE_COMMAND)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn command(s: &str) -> Result<Command, cli::Error> {
|
||||||
|
let arguments = || sep_end_by::<Vec<_>, _, _>(many1(satisfy(|c: char| !c.is_whitespace())), many1::<Vec<_>, _>(space())).expected("arguments");
|
||||||
|
|
||||||
|
let help_parser = string(HELP_COMMAND)
|
||||||
|
.with(spaces())
|
||||||
|
.with(arguments())
|
||||||
|
.map(|args| {
|
||||||
|
Ok(Command::Help(args.clone()))
|
||||||
|
});
|
||||||
|
|
||||||
|
let open_parser = string(OPEN_COMMAND)
|
||||||
|
.with(spaces())
|
||||||
|
.with(arguments())
|
||||||
|
.map(|args| {
|
||||||
|
if args.len() < 1 {
|
||||||
|
bail!(cli::ErrorKind::CommandParse("Missing required argument".to_string()));
|
||||||
|
}
|
||||||
|
if args.len() > 1 {
|
||||||
|
bail!(cli::ErrorKind::CommandParse(format!("Unrecognized argument {:?}", args[1])));
|
||||||
|
}
|
||||||
|
Ok(Command::Open(args[0].clone()))
|
||||||
|
});
|
||||||
|
|
||||||
|
let close_parser = string(CLOSE_COMMAND)
|
||||||
|
.with(arguments())
|
||||||
|
.skip(spaces())
|
||||||
|
.skip(eof())
|
||||||
|
.map(|args| {
|
||||||
|
if args.len() > 0 {
|
||||||
|
bail!(cli::ErrorKind::CommandParse(format!("Unrecognized argument {:?}", args[0])) );
|
||||||
|
}
|
||||||
|
Ok(Command::Close)
|
||||||
|
});
|
||||||
|
|
||||||
|
let edn_arg_parser = || spaces()
|
||||||
|
.with(look_ahead(string("[").or(string("{")))
|
||||||
|
.with(many1::<Vec<_>, _>(try(any())))
|
||||||
|
.and_then(|args| -> Result<String, cli::Error> {
|
||||||
|
Ok(args.iter().collect())
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
let query_parser = try(string(LONG_QUERY_COMMAND)).or(try(string(SHORT_QUERY_COMMAND)))
|
||||||
|
.with(edn_arg_parser())
|
||||||
|
.map(|x| {
|
||||||
|
Ok(Command::Query(x))
|
||||||
|
});
|
||||||
|
|
||||||
|
let transact_parser = try(string(LONG_TRANSACT_COMMAND)).or(try(string(SHORT_TRANSACT_COMMAND)))
|
||||||
|
.with(edn_arg_parser())
|
||||||
|
.map( |x| {
|
||||||
|
Ok(Command::Transact(x))
|
||||||
|
});
|
||||||
|
|
||||||
|
spaces()
|
||||||
|
.skip(token('.'))
|
||||||
|
.with(choice::<[&mut Parser<Input = _, Output = Result<Command, cli::Error>>; 5], _>
|
||||||
|
([&mut try(help_parser),
|
||||||
|
&mut try(open_parser),
|
||||||
|
&mut try(close_parser),
|
||||||
|
&mut try(query_parser),
|
||||||
|
&mut try(transact_parser)]))
|
||||||
|
.parse(s)
|
||||||
|
.unwrap_or((Err(cli::ErrorKind::CommandParse(format!("Invalid command {:?}", s)).into()), "")).0
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_help_parser_multiple_args() {
|
||||||
|
let input = ".help command1 command2";
|
||||||
|
let cmd = command(&input).expect("Expected help command");
|
||||||
|
match cmd {
|
||||||
|
Command::Help(args) => {
|
||||||
|
assert_eq!(args, vec!["command1", "command2"]);
|
||||||
|
},
|
||||||
|
_ => assert!(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_help_parser_dot_arg() {
|
||||||
|
let input = ".help .command1";
|
||||||
|
let cmd = command(&input).expect("Expected help command");
|
||||||
|
match cmd {
|
||||||
|
Command::Help(args) => {
|
||||||
|
assert_eq!(args, vec![".command1"]);
|
||||||
|
},
|
||||||
|
_ => assert!(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_help_parser_no_args() {
|
||||||
|
let input = ".help";
|
||||||
|
let cmd = command(&input).expect("Expected help command");
|
||||||
|
match cmd {
|
||||||
|
Command::Help(args) => {
|
||||||
|
let empty: Vec<String> = vec![];
|
||||||
|
assert_eq!(args, empty);
|
||||||
|
},
|
||||||
|
_ => assert!(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_help_parser_no_args_trailing_whitespace() {
|
||||||
|
let input = ".help ";
|
||||||
|
let cmd = command(&input).expect("Expected help command");
|
||||||
|
match cmd {
|
||||||
|
Command::Help(args) => {
|
||||||
|
let empty: Vec<String> = vec![];
|
||||||
|
assert_eq!(args, empty);
|
||||||
|
},
|
||||||
|
_ => assert!(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_open_parser_multiple_args() {
|
||||||
|
let input = ".open database1 database2";
|
||||||
|
let err = command(&input).expect_err("Expected an error");
|
||||||
|
assert_eq!(err.to_string(), "Unrecognized argument \"database2\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_open_parser_single_arg() {
|
||||||
|
let input = ".open database1";
|
||||||
|
let cmd = command(&input).expect("Expected open command");
|
||||||
|
match cmd {
|
||||||
|
Command::Open(arg) => {
|
||||||
|
assert_eq!(arg, "database1".to_string());
|
||||||
|
},
|
||||||
|
_ => assert!(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_open_parser_path_arg() {
|
||||||
|
let input = ".open /path/to/my.db";
|
||||||
|
let cmd = command(&input).expect("Expected open command");
|
||||||
|
match cmd {
|
||||||
|
Command::Open(arg) => {
|
||||||
|
assert_eq!(arg, "/path/to/my.db".to_string());
|
||||||
|
},
|
||||||
|
_ => assert!(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_open_parser_file_arg() {
|
||||||
|
let input = ".open my.db";
|
||||||
|
let cmd = command(&input).expect("Expected open command");
|
||||||
|
match cmd {
|
||||||
|
Command::Open(arg) => {
|
||||||
|
assert_eq!(arg, "my.db".to_string());
|
||||||
|
},
|
||||||
|
_ => assert!(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_open_parser_no_args() {
|
||||||
|
let input = ".open";
|
||||||
|
let err = command(&input).expect_err("Expected an error");
|
||||||
|
assert_eq!(err.to_string(), "Missing required argument");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_open_parser_no_args_trailing_whitespace() {
|
||||||
|
let input = ".open ";
|
||||||
|
let err = command(&input).expect_err("Expected an error");
|
||||||
|
assert_eq!(err.to_string(), "Missing required argument");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_close_parser_with_args() {
|
||||||
|
let input = ".close arg1";
|
||||||
|
let err = command(&input).expect_err("Expected an error");
|
||||||
|
assert_eq!(err.to_string(), format!("Invalid command {:?}", input));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_close_parser_no_args() {
|
||||||
|
let input = ".close";
|
||||||
|
let cmd = command(&input).expect("Expected close command");
|
||||||
|
match cmd {
|
||||||
|
Command::Close => assert!(true),
|
||||||
|
_ => assert!(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_close_parser_no_args_trailing_whitespace() {
|
||||||
|
let input = ".close ";
|
||||||
|
let cmd = command(&input).expect("Expected close command");
|
||||||
|
match cmd {
|
||||||
|
Command::Close => assert!(true),
|
||||||
|
_ => assert!(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_query_parser_complete_edn() {
|
||||||
|
let input = ".q [:find ?x :where [?x foo/bar ?y]]";
|
||||||
|
let cmd = command(&input).expect("Expected query command");
|
||||||
|
match cmd {
|
||||||
|
Command::Query(edn) => assert_eq!(edn, "[:find ?x :where [?x foo/bar ?y]]"),
|
||||||
|
_ => assert!(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_query_parser_alt_query_command() {
|
||||||
|
let input = ".query [:find ?x :where [?x foo/bar ?y]]";
|
||||||
|
let cmd = command(&input).expect("Expected query command");
|
||||||
|
match cmd {
|
||||||
|
Command::Query(edn) => assert_eq!(edn, "[:find ?x :where [?x foo/bar ?y]]"),
|
||||||
|
_ => assert!(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_query_parser_incomplete_edn() {
|
||||||
|
let input = ".q [:find ?x\r\n";
|
||||||
|
let cmd = command(&input).expect("Expected query command");
|
||||||
|
match cmd {
|
||||||
|
Command::Query(edn) => assert_eq!(edn, "[:find ?x\r\n"),
|
||||||
|
_ => assert!(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_query_parser_empty_edn() {
|
||||||
|
let input = ".q {}";
|
||||||
|
let cmd = command(&input).expect("Expected query command");
|
||||||
|
match cmd {
|
||||||
|
Command::Query(edn) => assert_eq!(edn, "{}"),
|
||||||
|
_ => assert!(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_query_parser_no_edn() {
|
||||||
|
let input = ".q ";
|
||||||
|
let err = command(&input).expect_err("Expected an error");
|
||||||
|
assert_eq!(err.to_string(), format!("Invalid command {:?}", input));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_query_parser_invalid_start_char() {
|
||||||
|
let input = ".q :find ?x";
|
||||||
|
let err = command(&input).expect_err("Expected an error");
|
||||||
|
assert_eq!(err.to_string(), format!("Invalid command {:?}", input));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_transact_parser_complete_edn() {
|
||||||
|
let input = ".t [[:db/add \"s\" :db/ident :foo/uuid] [:db/add \"r\" :db/ident :bar/uuid]]";
|
||||||
|
let cmd = command(&input).expect("Expected transact command");
|
||||||
|
match cmd {
|
||||||
|
Command::Transact(edn) => assert_eq!(edn, "[[:db/add \"s\" :db/ident :foo/uuid] [:db/add \"r\" :db/ident :bar/uuid]]"),
|
||||||
|
_ => assert!(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_transact_parser_alt_command() {
|
||||||
|
let input = ".transact [[:db/add \"s\" :db/ident :foo/uuid] [:db/add \"r\" :db/ident :bar/uuid]]";
|
||||||
|
let cmd = command(&input).expect("Expected transact command");
|
||||||
|
match cmd {
|
||||||
|
Command::Transact(edn) => assert_eq!(edn, "[[:db/add \"s\" :db/ident :foo/uuid] [:db/add \"r\" :db/ident :bar/uuid]]"),
|
||||||
|
_ => assert!(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_transact_parser_incomplete_edn() {
|
||||||
|
let input = ".t {\r\n";
|
||||||
|
let cmd = command(&input).expect("Expected transact command");
|
||||||
|
match cmd {
|
||||||
|
Command::Transact(edn) => assert_eq!(edn, "{\r\n"),
|
||||||
|
_ => assert!(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_transact_parser_empty_edn() {
|
||||||
|
let input = ".t {}";
|
||||||
|
let cmd = command(&input).expect("Expected transact command");
|
||||||
|
match cmd {
|
||||||
|
Command::Transact(edn) => assert_eq!(edn, "{}"),
|
||||||
|
_ => assert!(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_transact_parser_no_edn() {
|
||||||
|
let input = ".t ";
|
||||||
|
let err = command(&input).expect_err("Expected an error");
|
||||||
|
assert_eq!(err.to_string(), format!("Invalid command {:?}", input));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_transact_parser_invalid_start_char() {
|
||||||
|
let input = ".t :db/add \"s\" :db/ident :foo/uuid";
|
||||||
|
let err = command(&input).expect_err("Expected an error");
|
||||||
|
assert_eq!(err.to_string(), format!("Invalid command {:?}", input));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parser_preceeding_trailing_whitespace() {
|
||||||
|
let input = " .close ";
|
||||||
|
let cmd = command(&input).expect("Expected close command");
|
||||||
|
match cmd {
|
||||||
|
Command::Close => assert!(true),
|
||||||
|
_ => assert!(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_command_parser_no_dot() {
|
||||||
|
let input = "help command1 command2";
|
||||||
|
let err = command(&input).expect_err("Expected an error");
|
||||||
|
assert_eq!(err.to_string(), format!("Invalid command {:?}", input));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_command_parser_invalid_cmd() {
|
||||||
|
let input = ".foo command1";
|
||||||
|
let err = command(&input).expect_err("Expected an error");
|
||||||
|
assert_eq!(err.to_string(), format!("Invalid command {:?}", input));
|
||||||
|
}
|
||||||
|
}
|
36
tools/cli/src/mentat_cli/errors.rs
Normal file
36
tools/cli/src/mentat_cli/errors.rs
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
// Copyright 2016 Mozilla
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||||
|
// this file except in compliance with the License. You may obtain a copy of the
|
||||||
|
// License at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
// Unless required by applicable law or agreed to in writing, software distributed
|
||||||
|
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||||
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use rusqlite;
|
||||||
|
|
||||||
|
use mentat::errors as mentat;
|
||||||
|
|
||||||
|
error_chain! {
|
||||||
|
types {
|
||||||
|
Error, ErrorKind, ResultExt, Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreign_links {
|
||||||
|
Rusqlite(rusqlite::Error);
|
||||||
|
}
|
||||||
|
|
||||||
|
links {
|
||||||
|
MentatError(mentat::Error, mentat::ErrorKind);
|
||||||
|
}
|
||||||
|
|
||||||
|
errors {
|
||||||
|
CommandParse(message: String) {
|
||||||
|
description("An error occured parsing the entered command")
|
||||||
|
display("{}", message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
157
tools/cli/src/mentat_cli/input.rs
Normal file
157
tools/cli/src/mentat_cli/input.rs
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
// Copyright 2017 Mozilla
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||||
|
// this file except in compliance with the License. You may obtain a copy of the
|
||||||
|
// License at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
// Unless required by applicable law or agreed to in writing, software distributed
|
||||||
|
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||||
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
|
use std::io::stdin;
|
||||||
|
|
||||||
|
use linefeed::Reader;
|
||||||
|
use linefeed::terminal::DefaultTerminal;
|
||||||
|
|
||||||
|
use self::InputResult::*;
|
||||||
|
|
||||||
|
use command_parser::{
|
||||||
|
Command,
|
||||||
|
command
|
||||||
|
};
|
||||||
|
|
||||||
|
use errors as cli;
|
||||||
|
|
||||||
|
/// Starting prompt
|
||||||
|
const DEFAULT_PROMPT: &'static str = "mentat=> ";
|
||||||
|
/// Prompt when further input is being read
|
||||||
|
// TODO: Should this actually reflect the current open brace?
|
||||||
|
const MORE_PROMPT: &'static str = "mentat.> ";
|
||||||
|
|
||||||
|
/// Possible results from reading input from `InputReader`
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum InputResult {
|
||||||
|
/// mentat command as input; (name, rest of line)
|
||||||
|
MetaCommand(Command),
|
||||||
|
/// An empty line
|
||||||
|
Empty,
|
||||||
|
/// Needs more input
|
||||||
|
More,
|
||||||
|
/// End of file reached
|
||||||
|
Eof,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads input from `stdin`
|
||||||
|
pub struct InputReader {
|
||||||
|
buffer: String,
|
||||||
|
reader: Option<Reader<DefaultTerminal>>,
|
||||||
|
in_process_cmd: Option<Command>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputReader {
|
||||||
|
/// Constructs a new `InputReader` reading from `stdin`.
|
||||||
|
pub fn new() -> InputReader {
|
||||||
|
let r = match Reader::new("mentat") {
|
||||||
|
Ok(mut r) => {
|
||||||
|
r.set_word_break_chars(" \t\n!\"#$%&'()*+,-./:;<=>?@[\\]^`");
|
||||||
|
Some(r)
|
||||||
|
}
|
||||||
|
Err(_) => None
|
||||||
|
};
|
||||||
|
|
||||||
|
InputReader{
|
||||||
|
buffer: String::new(),
|
||||||
|
reader: r,
|
||||||
|
in_process_cmd: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether the `InputReader` is reading from a TTY.
|
||||||
|
pub fn is_tty(&self) -> bool {
|
||||||
|
self.reader.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reads a single command, item, or statement from `stdin`.
|
||||||
|
/// Returns `More` if further input is required for a complete result.
|
||||||
|
/// In this case, the input received so far is buffered internally.
|
||||||
|
pub fn read_input(&mut self) -> Result<InputResult, cli::Error> {
|
||||||
|
let prompt = if self.in_process_cmd.is_some() { MORE_PROMPT } else { DEFAULT_PROMPT };
|
||||||
|
let line = match self.read_line(prompt) {
|
||||||
|
Some(s) => s,
|
||||||
|
None => return Ok(Eof),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.buffer.push_str(&line);
|
||||||
|
|
||||||
|
if self.buffer.is_empty() {
|
||||||
|
return Ok(Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.add_history(&line);
|
||||||
|
|
||||||
|
// if we have a command in process (i.e. in incomplete query or transaction),
|
||||||
|
// then we already know which type of command it is and so we don't need to parse the
|
||||||
|
// command again, only the content, which we do later.
|
||||||
|
// Therefore, we add the newly read in line to the existing command args.
|
||||||
|
// If there is no in process command, we parse the read in line as a new command.
|
||||||
|
let cmd = match &self.in_process_cmd {
|
||||||
|
&Some(Command::Query(ref args)) => {
|
||||||
|
Command::Query(args.clone() + " " + &line)
|
||||||
|
},
|
||||||
|
&Some(Command::Transact(ref args)) => {
|
||||||
|
Command::Transact(args.clone() + " " + &line)
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
try!(command(&self.buffer))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match cmd {
|
||||||
|
Command::Query(_) |
|
||||||
|
Command::Transact(_) if !cmd.is_complete() => {
|
||||||
|
// a query or transact is complete if it contains a valid edn.
|
||||||
|
// if the command is not complete, ask for more from the repl and remember
|
||||||
|
// which type of command we've found here.
|
||||||
|
self.in_process_cmd = Some(cmd);
|
||||||
|
Ok(More)
|
||||||
|
},
|
||||||
|
_ => {
|
||||||
|
self.buffer.clear();
|
||||||
|
self.in_process_cmd = None;
|
||||||
|
Ok(InputResult::MetaCommand(cmd))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_line(&mut self, prompt: &str) -> Option<String> {
|
||||||
|
match self.reader {
|
||||||
|
Some(ref mut r) => {
|
||||||
|
r.set_prompt(prompt);
|
||||||
|
r.read_line().ok().and_then(|line| line)
|
||||||
|
},
|
||||||
|
None => self.read_stdin()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_stdin(&self) -> Option<String> {
|
||||||
|
let mut s = String::new();
|
||||||
|
|
||||||
|
match stdin().read_line(&mut s) {
|
||||||
|
Ok(0) | Err(_) => None,
|
||||||
|
Ok(_) => Some(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_history(&mut self, line: &str) {
|
||||||
|
if let Some(ref mut r) = self.reader {
|
||||||
|
r.add_history(line.to_owned());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
}
|
||||||
|
}
|
121
tools/cli/src/mentat_cli/lib.rs
Normal file
121
tools/cli/src/mentat_cli/lib.rs
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
// Copyright 2017 Mozilla
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||||
|
// this file except in compliance with the License. You may obtain a copy of the
|
||||||
|
// License at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
// Unless required by applicable law or agreed to in writing, software distributed
|
||||||
|
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||||
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
|
#![crate_name = "mentat_cli"]
|
||||||
|
|
||||||
|
#[macro_use] extern crate log;
|
||||||
|
#[macro_use] extern crate lazy_static;
|
||||||
|
#[macro_use] extern crate error_chain;
|
||||||
|
|
||||||
|
extern crate combine;
|
||||||
|
extern crate env_logger;
|
||||||
|
extern crate getopts;
|
||||||
|
extern crate linefeed;
|
||||||
|
extern crate rusqlite;
|
||||||
|
|
||||||
|
extern crate mentat;
|
||||||
|
extern crate edn;
|
||||||
|
extern crate mentat_query;
|
||||||
|
extern crate mentat_core;
|
||||||
|
extern crate mentat_db;
|
||||||
|
|
||||||
|
use getopts::Options;
|
||||||
|
|
||||||
|
pub mod command_parser;
|
||||||
|
pub mod store;
|
||||||
|
pub mod input;
|
||||||
|
pub mod repl;
|
||||||
|
pub mod errors;
|
||||||
|
|
||||||
|
pub fn run() -> i32 {
|
||||||
|
env_logger::init().unwrap();
|
||||||
|
|
||||||
|
let args = std::env::args().collect::<Vec<_>>();
|
||||||
|
let mut opts = Options::new();
|
||||||
|
|
||||||
|
opts.optopt("d", "", "The path to a database to open", "DATABASE");
|
||||||
|
opts.optflag("h", "help", "Print this help message and exit");
|
||||||
|
opts.optmulti("q", "query", "Execute a query on startup. Queries are executed after any transacts.", "QUERY");
|
||||||
|
opts.optmulti("t", "transact", "Execute a transact on startup. Transacts are executed before queries.", "TRANSACT");
|
||||||
|
opts.optflag("v", "version", "Print version and exit");
|
||||||
|
|
||||||
|
let matches = match opts.parse(&args[1..]) {
|
||||||
|
Ok(m) => m,
|
||||||
|
Err(e) => {
|
||||||
|
println!("{}: {}", args[0], e);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if matches.opt_present("version") {
|
||||||
|
print_version();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches.opt_present("help") {
|
||||||
|
print_usage(&args[0], &opts);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut last_arg: Option<&str> = None;
|
||||||
|
let cmds:Vec<command_parser::Command> = args.iter().filter_map(|arg| {
|
||||||
|
match last_arg {
|
||||||
|
Some("-d") => {
|
||||||
|
last_arg = None;
|
||||||
|
Some(command_parser::Command::Open(arg.clone()))
|
||||||
|
},
|
||||||
|
Some("-q") => {
|
||||||
|
last_arg = None;
|
||||||
|
Some(command_parser::Command::Query(arg.clone()))
|
||||||
|
},
|
||||||
|
Some("-t") => {
|
||||||
|
last_arg = None;
|
||||||
|
Some(command_parser::Command::Transact(arg.clone()))
|
||||||
|
},
|
||||||
|
Some(_) |
|
||||||
|
None => {
|
||||||
|
last_arg = Some(&arg);
|
||||||
|
None
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}).collect();
|
||||||
|
|
||||||
|
let repl = repl::Repl::new();
|
||||||
|
if repl.is_ok() {
|
||||||
|
repl.unwrap().run(Some(cmds));
|
||||||
|
|
||||||
|
} else {
|
||||||
|
println!("{}", repl.err().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a version string.
|
||||||
|
pub fn version() -> &'static str {
|
||||||
|
env!("CARGO_PKG_VERSION")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_usage(arg0: &str, opts: &Options) {
|
||||||
|
print!("{}", opts.usage(&format!(
|
||||||
|
"Usage: {} [OPTIONS] [FILE]", arg0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_version() {
|
||||||
|
println!("mentat {}", version());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
}
|
||||||
|
}
|
204
tools/cli/src/mentat_cli/repl.rs
Normal file
204
tools/cli/src/mentat_cli/repl.rs
Normal file
|
@ -0,0 +1,204 @@
|
||||||
|
// Copyright 2017 Mozilla
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||||
|
// this file except in compliance with the License. You may obtain a copy of the
|
||||||
|
// License at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
// Unless required by applicable law or agreed to in writing, software distributed
|
||||||
|
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||||
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use mentat::query::QueryResults;
|
||||||
|
use mentat_core::TypedValue;
|
||||||
|
|
||||||
|
use command_parser::{
|
||||||
|
Command,
|
||||||
|
HELP_COMMAND,
|
||||||
|
OPEN_COMMAND,
|
||||||
|
LONG_QUERY_COMMAND,
|
||||||
|
SHORT_QUERY_COMMAND,
|
||||||
|
LONG_TRANSACT_COMMAND,
|
||||||
|
SHORT_TRANSACT_COMMAND,
|
||||||
|
};
|
||||||
|
use input::InputReader;
|
||||||
|
use input::InputResult::{
|
||||||
|
MetaCommand,
|
||||||
|
Empty,
|
||||||
|
More,
|
||||||
|
Eof
|
||||||
|
};
|
||||||
|
use store::{
|
||||||
|
Store,
|
||||||
|
db_output_name
|
||||||
|
};
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref COMMAND_HELP: HashMap<&'static str, &'static str> = {
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
map.insert(HELP_COMMAND, "Show help for commands.");
|
||||||
|
map.insert(OPEN_COMMAND, "Open a database at path.");
|
||||||
|
map.insert(LONG_QUERY_COMMAND, "Execute a query against the current open database.");
|
||||||
|
map.insert(SHORT_QUERY_COMMAND, "Shortcut for `.query`. Execute a query against the current open database.");
|
||||||
|
map.insert(LONG_TRANSACT_COMMAND, "Execute a transact against the current open database.");
|
||||||
|
map.insert(SHORT_TRANSACT_COMMAND, "Shortcut for `.transact`. Execute a transact against the current open database.");
|
||||||
|
map
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Executes input and maintains state of persistent items.
|
||||||
|
pub struct Repl {
|
||||||
|
store: Store
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Repl {
|
||||||
|
/// Constructs a new `Repl`.
|
||||||
|
pub fn new() -> Result<Repl, String> {
|
||||||
|
let store = Store::new(None).map_err(|e| e.to_string())?;
|
||||||
|
Ok(Repl{
|
||||||
|
store: store,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs the REPL interactively.
|
||||||
|
pub fn run(&mut self, startup_commands: Option<Vec<Command>>) {
|
||||||
|
let mut input = InputReader::new();
|
||||||
|
|
||||||
|
if let Some(cmds) = startup_commands {
|
||||||
|
for command in cmds.iter() {
|
||||||
|
println!("{}", command.output());
|
||||||
|
self.handle_command(command.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let res = input.read_input();
|
||||||
|
|
||||||
|
match res {
|
||||||
|
Ok(MetaCommand(cmd)) => {
|
||||||
|
debug!("read command: {:?}", cmd);
|
||||||
|
self.handle_command(cmd);
|
||||||
|
},
|
||||||
|
Ok(Empty) |
|
||||||
|
Ok(More) => (),
|
||||||
|
Ok(Eof) => {
|
||||||
|
if input.is_tty() {
|
||||||
|
println!("");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
},
|
||||||
|
Err(e) => println!("{}", e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Runs a single command input.
|
||||||
|
fn handle_command(&mut self, cmd: Command) {
|
||||||
|
match cmd {
|
||||||
|
Command::Help(args) => self.help_command(args),
|
||||||
|
Command::Open(db) => {
|
||||||
|
match self.store.open(Some(db.clone())) {
|
||||||
|
Ok(_) => println!("Database {:?} opened", db_output_name(&db)),
|
||||||
|
Err(e) => println!("{}", e.to_string())
|
||||||
|
};
|
||||||
|
},
|
||||||
|
Command::Close => {
|
||||||
|
let old_db_name = self.store.db_name.clone();
|
||||||
|
match self.store.close() {
|
||||||
|
Ok(_) => println!("Database {:?} closed", db_output_name(&old_db_name)),
|
||||||
|
Err(e) => println!("{}", e.to_string())
|
||||||
|
};
|
||||||
|
},
|
||||||
|
Command::Query(query) => self.execute_query(query),
|
||||||
|
Command::Transact(transaction) => self.execute_transact(transaction),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn help_command(&self, args: Vec<String>) {
|
||||||
|
if args.is_empty() {
|
||||||
|
for (cmd, msg) in COMMAND_HELP.iter() {
|
||||||
|
println!(".{} - {}", cmd, msg);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for mut arg in args {
|
||||||
|
if arg.chars().nth(0).unwrap() == '.' {
|
||||||
|
arg.remove(0);
|
||||||
|
}
|
||||||
|
let msg = COMMAND_HELP.get(arg.as_str());
|
||||||
|
if msg.is_some() {
|
||||||
|
println!(".{} - {}", arg, msg.unwrap());
|
||||||
|
} else {
|
||||||
|
println!("Unrecognised command {}", arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn execute_query(&self, query: String) {
|
||||||
|
let results = match self.store.query(query){
|
||||||
|
Result::Ok(vals) => {
|
||||||
|
vals
|
||||||
|
},
|
||||||
|
Result::Err(err) => return println!("{:?}.", err),
|
||||||
|
};
|
||||||
|
|
||||||
|
if results.is_empty() {
|
||||||
|
println!("No results found.")
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut output:String = String::new();
|
||||||
|
match results {
|
||||||
|
QueryResults::Scalar(Some(val)) => {
|
||||||
|
output.push_str(&self.typed_value_as_string(val) );
|
||||||
|
},
|
||||||
|
QueryResults::Tuple(Some(vals)) => {
|
||||||
|
for val in vals {
|
||||||
|
output.push_str(&format!("{}\t", self.typed_value_as_string(val)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
QueryResults::Coll(vv) => {
|
||||||
|
for val in vv {
|
||||||
|
output.push_str(&format!("{}\n", self.typed_value_as_string(val)));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
QueryResults::Rel(vvv) => {
|
||||||
|
for vv in vvv {
|
||||||
|
for v in vv {
|
||||||
|
output.push_str(&format!("{}\t", self.typed_value_as_string(v)));
|
||||||
|
}
|
||||||
|
output.push_str("\n");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
_ => output.push_str(&format!("No results found."))
|
||||||
|
}
|
||||||
|
println!("\n{}", output);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn execute_transact(&mut self, transaction: String) {
|
||||||
|
match self.store.transact(transaction) {
|
||||||
|
Result::Ok(report) => println!("{:?}", report),
|
||||||
|
Result::Err(err) => println!("{:?}.", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn typed_value_as_string(&self, value: TypedValue) -> String {
|
||||||
|
match value {
|
||||||
|
TypedValue::Boolean(b) => if b { "true".to_string() } else { "false".to_string() },
|
||||||
|
TypedValue::Double(d) => format!("{}", d),
|
||||||
|
TypedValue::Instant(i) => format!("{}", i),
|
||||||
|
TypedValue::Keyword(k) => format!("{}", k),
|
||||||
|
TypedValue::Long(l) => format!("{}", l),
|
||||||
|
TypedValue::Ref(r) => format!("{}", r),
|
||||||
|
TypedValue::String(s) => format!("{:?}", s.to_string()),
|
||||||
|
TypedValue::Uuid(u) => format!("{}", u),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[test]
|
||||||
|
fn it_works() {
|
||||||
|
}
|
||||||
|
}
|
61
tools/cli/src/mentat_cli/store.rs
Normal file
61
tools/cli/src/mentat_cli/store.rs
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
// Copyright 2017 Mozilla
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use
|
||||||
|
// this file except in compliance with the License. You may obtain a copy of the
|
||||||
|
// License at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
// Unless required by applicable law or agreed to in writing, software distributed
|
||||||
|
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||||
|
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||||
|
// specific language governing permissions and limitations under the License.
|
||||||
|
|
||||||
|
use rusqlite;
|
||||||
|
|
||||||
|
use errors as cli;
|
||||||
|
|
||||||
|
use mentat::{
|
||||||
|
new_connection,
|
||||||
|
};
|
||||||
|
use mentat::query::QueryResults;
|
||||||
|
|
||||||
|
use mentat::conn::Conn;
|
||||||
|
use mentat_db::types::TxReport;
|
||||||
|
|
||||||
|
pub struct Store {
|
||||||
|
handle: rusqlite::Connection,
|
||||||
|
conn: Conn,
|
||||||
|
pub db_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn db_output_name(db_name: &String) -> String {
|
||||||
|
if db_name.is_empty() { "in-memory db".to_string() } else { db_name.clone() }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Store {
|
||||||
|
pub fn new(database: Option<String>) -> Result<Store, cli::Error> {
|
||||||
|
let db_name = database.unwrap_or("".to_string());
|
||||||
|
|
||||||
|
let mut handle = try!(new_connection(&db_name));
|
||||||
|
let conn = try!(Conn::connect(&mut handle));
|
||||||
|
Ok(Store { handle, conn, db_name })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open(&mut self, database: Option<String>) -> Result<(), cli::Error> {
|
||||||
|
self.db_name = database.unwrap_or("".to_string());
|
||||||
|
self.handle = try!(new_connection(&self.db_name));
|
||||||
|
self.conn = try!(Conn::connect(&mut self.handle));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn close(&mut self) -> Result<(), cli::Error> {
|
||||||
|
self.db_name = "".to_string();
|
||||||
|
self.open(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn query(&self, query: String) -> Result<QueryResults, cli::Error> {
|
||||||
|
Ok(try!(self.conn.q_once(&self.handle, &query, None)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transact(&mut self, transaction: String) -> Result<TxReport, cli::Error> {
|
||||||
|
Ok(try!(self.conn.transact(&mut self.handle, &transaction)))
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue