Skip to content
This repository was archived by the owner on Sep 12, 2018. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build/version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use rustc_version::version_matches;

/// MIN_VERSION should be changed when there's a new minimum version of rustc required
/// to build the project.
static MIN_VERSION: &'static str = ">= 1.15.1";
static MIN_VERSION: &'static str = ">= 1.17.0";

fn main() {
if !version_matches(MIN_VERSION) {
Expand Down
2 changes: 1 addition & 1 deletion db/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ mod entids;
pub mod errors;
mod metadata;
mod schema;
mod types;
pub mod types;
mod internal_types;
mod upsert_resolution;
mod tx;
Expand Down
12 changes: 12 additions & 0 deletions tools/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,15 @@ 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"
197 changes: 183 additions & 14 deletions tools/cli/src/mentat_cli/command_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@
// specific language governing permissions and limitations under the License.

use combine::{
any,
eof,
look_ahead,
many1,
parser,
satisfy,
sep_end_by,
token,
Expand All @@ -26,16 +29,24 @@ use combine::combinator::{
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(Vec<String>),
Query(Vec<String>),
Transact(String),
Query(String),
Help(Vec<String>),
Open(String),
Close,
Expand All @@ -48,13 +59,35 @@ impl Command {
/// 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(_) |
&Command::Transact(_) => false,
&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> {
Expand All @@ -72,34 +105,55 @@ pub fn command(s: &str) -> Result<Command, cli::Error> {
.with(arguments())
.map(|args| {
if args.len() < 1 {
return Err(cli::ErrorKind::CommandParse("Missing required argument".to_string()).into());
bail!(cli::ErrorKind::CommandParse("Missing required argument".to_string()));
}
if args.len() > 1 {
return Err(cli::ErrorKind::CommandParse(format!("Unrecognized argument {:?}", args[1])).into());
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 {
return Err(cli::ErrorKind::CommandParse(format!("Unrecognized argument {:?}", args[0])).into());
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>>; 3], _>
.with(choice::<[&mut Parser<Input = _, Output = Result<Command, cli::Error>>; 5], _>
([&mut try(help_parser),
&mut try(open_parser),
&mut try(close_parser),]))
.skip(spaces())
.skip(eof())
&mut try(close_parser),
&mut try(query_parser),
&mut try(transact_parser)]))
.parse(s)
.map(|x| x.0)
.unwrap_or(Err(cli::ErrorKind::CommandParse(format!("Invalid command {:?}", s)).into()))
.unwrap_or((Err(cli::ErrorKind::CommandParse(format!("Invalid command {:?}", s)).into()), "")).0
}

#[cfg(test)]
Expand Down Expand Up @@ -198,14 +252,21 @@ mod tests {
_ => 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");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that expect_err is in 1.17.0 stable, so we can use it -- but we need to bump

static MIN_VERSION: &'static str = ">= 1.15.1";
as a Pre: part to stop folks failing here (as I just did locally!).

assert_eq!(err.to_string(), "Missing required argument");
}

#[test]
fn test_close_parser_with_args() {
let input = ".close arg1";
Expand Down Expand Up @@ -233,6 +294,114 @@ mod tests {
}
}

#[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 ";
Expand Down
Loading