1
1
mirror of https://github.com/tweag/nickel.git synced 2024-11-12 23:44:28 +03:00

Improve query inteface (#1447)

* Improve :query interface

Previously, the query command of the REPL would accept a first argument
that could be a record access chain, followed by a second one which
could also be a field path. This means that, for querying a given path
`foo.bar.baz.blo`, there are 3 different but equivalent query
invocations:

- :query foo bar.baz.blo
- :query foo.bar baz.blo
- :query foo.bar.baz blo

Moreover, `:query foo.bar.baz.blo` without a second argument wouldn't
fail but pretend that there are no metadata, even if there are.

This commit simplify the overall interfaceo of the query command by just
accepting one argument which is a field path.

* Update core/src/program.rs

Co-authored-by: Viktor Kleen <viktor.kleen@tweag.io>

* Update core/src/repl/mod.rs

Co-authored-by: Viktor Kleen <viktor.kleen@tweag.io>

---------

Co-authored-by: Viktor Kleen <viktor.kleen@tweag.io>
This commit is contained in:
Yann Hamdaoui 2023-07-12 08:38:00 +02:00 committed by GitHub
parent 4a99f6a5f4
commit 081fc927a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 28 additions and 31 deletions

View File

@ -55,6 +55,12 @@ impl QueryPath {
/// Identifiers can be enclosed by double quotes when they contain characters that aren't
/// allowed inside bare identifiers. The accepted grammar is the same as a sequence of record
/// accesses in Nickel, although string interpolation is forbidden.
///
/// # Post-conditions
///
/// If this function succeeds and returns `Ok(query_path)`, then `query_path.0` is non empty.
/// Indeed, there's no such thing as a valid empty field path. If `input` is empty, or consists
/// only of spaces, `parse` returns a parse error.
pub fn parse(cache: &mut Cache, input: String) -> Result<Self, ParseError> {
use crate::parser::{
grammar::FieldPathParser, lexer::Lexer, utils::FieldPathElem, ErrorTolerantParser,

View File

@ -25,10 +25,7 @@ impl CommandType {
pub enum Command {
Load(OsString),
Typecheck(String),
Query {
target: String,
path: Option<String>,
},
Query(String),
Print(String),
Help(Option<String>),
Exit,
@ -130,22 +127,7 @@ impl FromStr for Command {
}
CommandType::Query => {
require_arg(cmd, &arg, None)?;
let mut args_iter = arg.chars();
let first_arg = args_iter.by_ref().take_while(|c| *c != ' ').collect();
let rest = args_iter.collect::<String>();
let rest = rest.trim();
let path = if !rest.is_empty() {
Some(String::from(rest))
} else {
None
};
Ok(Command::Query {
target: first_arg,
path,
})
Ok(Command::Query(arg))
}
CommandType::Print => {
require_arg(cmd, &arg, None)?;

View File

@ -63,7 +63,7 @@ pub trait Repl {
/// Typecheck an expression and return its [apparent type][crate::typecheck::ApparentType].
fn typecheck(&mut self, exp: &str) -> Result<Types, Error>;
/// Query the metadata of an expression.
fn query(&mut self, target: String, path: Option<String>) -> Result<Field, Error>;
fn query(&mut self, path: String) -> Result<Field, Error>;
/// Required for error reporting on the frontend.
fn cache_mut(&mut self) -> &mut Cache;
}
@ -285,15 +285,21 @@ impl<EC: EvalCache> Repl for ReplImpl<EC> {
.into())
}
fn query(&mut self, target: String, path: Option<String>) -> Result<Field, Error> {
fn query(&mut self, path: String) -> Result<Field, Error> {
use crate::program;
let mut query_path = QueryPath::parse(self.vm.import_resolver_mut(), path)?;
// remove(): this is safe because there is no such thing as an empty field path. If `path`
// is empty, the parser will error out. Hence, `QueryPath::parse` always returns a non-empty
// vector.
let target = query_path.0.remove(0);
let file_id = self
.vm
.import_resolver_mut()
.replace_string("<repl-query>", target);
.replace_string("<repl-query>", target.label().into());
let query_path = QueryPath::parse_opt(self.vm.import_resolver_mut(), path)?;
program::query(&mut self.vm, file_id, &self.env, query_path)
}
@ -415,15 +421,18 @@ pub fn print_help(out: &mut impl Write, arg: Option<&str>) -> std::io::Result<()
)?;
}
Ok(c @ CommandType::Query) => {
writeln!(out, ":{c} <identifier> [field path]")?;
writeln!(out, ":{c} <field path>")?;
print_aliases(out, c)?;
writeln!(out, "Print the metadata attached to a field")?;
writeln!(
out,
"<identifier> is valid Nickel identifier representing the record to look into."
"<field path> is a dot-separated sequence of identifiers pointing to a field. \
Fields can be quoted if they contain special characters, \
just like in normal Nickel source code.\n"
)?;
writeln!(out, "<field path> is a dot-separated sequence of identifiers pointing to a field.\n")?;
writeln!(out, "Example: `:{c} mylib contracts.\"special#chars\".bar`")?;
writeln!(out, "Examples:")?;
writeln!(out, "- `:{c} std.array.any`")?;
writeln!(out, "- `:{c} mylib.contracts.\"special#chars.\".bar`")?;
}
Ok(c @ CommandType::Load) => {
writeln!(out, ":{c} <file>")?;

View File

@ -88,7 +88,7 @@ pub fn repl(histfile: PathBuf, color_opt: ColorOpt) -> Result<(), InitError> {
Ok(Command::Typecheck(exp)) => {
repl.typecheck(&exp).map(|types| println!("Ok: {types}"))
}
Ok(Command::Query {target, path}) => repl.query(target, path).map(|field| {
Ok(Command::Query(path)) => repl.query(path).map(|field| {
query_print::write_query_result(
&mut stdout,
&field,

View File

@ -49,8 +49,8 @@ pub fn input<R: Repl>(repl: &mut R, line: &str) -> Result<InputResult, InputErro
.typecheck(&exp)
.map(|types| InputResult::Success(format!("Ok: {types}")))
.map_err(InputError::from),
Ok(Command::Query { target, path }) => repl
.query(target, path)
Ok(Command::Query(path)) => repl
.query(path)
.map(|t| {
let mut buffer = Cursor::new(Vec::<u8>::new());
query_print::write_query_result(