Remove need to give JSON file path

This commit is contained in:
Guillaume Gomez 2022-08-17 12:55:00 +02:00
parent 9c20b2a8cc
commit 57c85bd97d
2 changed files with 53 additions and 107 deletions

View file

@ -1,77 +1,31 @@
use crate::error::CkError; use crate::config::Config;
use serde_json::Value; use serde_json::Value;
use std::collections::HashMap; use std::collections::HashMap;
use std::io; use std::path::Path;
use std::path::{Path, PathBuf};
use fs_err as fs; use fs_err as fs;
#[derive(Debug)] #[derive(Debug)]
pub struct Cache { pub struct Cache {
root: PathBuf, value: Value,
files: HashMap<PathBuf, String>,
values: HashMap<PathBuf, Value>,
pub variables: HashMap<String, Value>, pub variables: HashMap<String, Value>,
last_path: Option<PathBuf>,
} }
impl Cache { impl Cache {
/// Create a new cache, used to read files only once and otherwise store their contents. /// Create a new cache, used to read files only once and otherwise store their contents.
pub fn new(doc_dir: &str) -> Cache { pub fn new(config: &Config) -> Cache {
let root = Path::new(&config.doc_dir);
let filename = Path::new(&config.template).file_stem().unwrap();
let file_path = root.join(&Path::with_extension(Path::new(filename), "json"));
let content = fs::read_to_string(&file_path).expect("failed to read JSON file");
Cache { Cache {
root: Path::new(doc_dir).to_owned(), value: serde_json::from_str::<Value>(&content).expect("failed to convert from JSON"),
files: HashMap::new(),
values: HashMap::new(),
variables: HashMap::new(), variables: HashMap::new(),
last_path: None,
} }
} }
fn resolve_path(&mut self, path: &String) -> PathBuf { pub fn value(&self) -> &Value {
if path != "-" { &self.value
let resolve = self.root.join(path);
self.last_path = Some(resolve.clone());
resolve
} else {
self.last_path
.as_ref()
// FIXME: Point to a line number
.expect("No last path set. Make sure to specify a full path before using `-`")
.clone()
}
}
fn read_file(&mut self, path: PathBuf) -> Result<String, io::Error> {
if let Some(f) = self.files.get(&path) {
return Ok(f.clone());
}
let file = fs::read_to_string(&path)?;
self.files.insert(path, file.clone());
Ok(file)
}
/// Get the text from a file. If called multiple times, the file will only be read once
pub fn get_file(&mut self, path: &String) -> Result<String, io::Error> {
let path = self.resolve_path(path);
self.read_file(path)
}
/// Parse the JSON from a file. If called multiple times, the file will only be read once.
pub fn get_value(&mut self, path: &String) -> Result<Value, CkError> {
let path = self.resolve_path(path);
if let Some(v) = self.values.get(&path) {
return Ok(v.clone());
}
let content = self.read_file(path.clone())?;
let val = serde_json::from_str::<Value>(&content)?;
self.values.insert(path, val.clone());
Ok(val)
} }
} }

View file

@ -17,7 +17,7 @@ fn main() -> Result<(), String> {
let config = parse_config(env::args().collect()); let config = parse_config(env::args().collect());
let mut failed = Vec::new(); let mut failed = Vec::new();
let mut cache = Cache::new(&config.doc_dir); let mut cache = Cache::new(&config);
let commands = get_commands(&config.template) let commands = get_commands(&config.template)
.map_err(|_| format!("Jsondocck failed for {}", &config.template))?; .map_err(|_| format!("Jsondocck failed for {}", &config.template))?;
@ -55,12 +55,12 @@ pub enum CommandKind {
} }
impl CommandKind { impl CommandKind {
fn validate(&self, args: &[String], command_num: usize, lineno: usize) -> bool { fn validate(&self, args: &[String], lineno: usize) -> bool {
let count = match self { let count = match self {
CommandKind::Has => (1..=3).contains(&args.len()), CommandKind::Has => (1..=2).contains(&args.len()),
CommandKind::IsMany => args.len() >= 3, CommandKind::IsMany => args.len() >= 2,
CommandKind::Count | CommandKind::Is => 3 == args.len(), CommandKind::Count | CommandKind::Is => 2 == args.len(),
CommandKind::Set => 4 == args.len(), CommandKind::Set => 3 == args.len(),
}; };
if !count { if !count {
@ -68,15 +68,10 @@ impl CommandKind {
return false; return false;
} }
if args[0] == "-" && command_num == 0 {
print_err(&format!("Tried to use the previous path in the first command"), lineno);
return false;
}
if let CommandKind::Count = self { if let CommandKind::Count = self {
if args[2].parse::<usize>().is_err() { if args[1].parse::<usize>().is_err() {
print_err( print_err(
&format!("Third argument to @count must be a valid usize (got `{}`)", args[2]), &format!("Second argument to @count must be a valid usize (got `{}`)", args[2]),
lineno, lineno,
); );
return false; return false;
@ -181,7 +176,7 @@ fn get_commands(template: &str) -> Result<Vec<Command>, ()> {
} }
}; };
if !cmd.validate(&args, commands.len(), lineno) { if !cmd.validate(&args, lineno) {
errors = true; errors = true;
continue; continue;
} }
@ -199,26 +194,24 @@ fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> {
let result = match command.kind { let result = match command.kind {
CommandKind::Has => { CommandKind::Has => {
match command.args.len() { match command.args.len() {
// @has <path> = file existence // @has <jsonpath> = check path exists
1 => cache.get_file(&command.args[0]).is_ok(), 1 => {
// @has <path> <jsonpath> = check path exists let val = cache.value();
2 => { let results = select(val, &command.args[0]).unwrap();
let val = cache.get_value(&command.args[0])?;
let results = select(&val, &command.args[1]).unwrap();
!results.is_empty() !results.is_empty()
} }
// @has <path> <jsonpath> <value> = check *any* item matched by path equals value // @has <jsonpath> <value> = check *any* item matched by path equals value
3 => { 2 => {
let val = cache.get_value(&command.args[0])?; let val = cache.value().clone();
let results = select(&val, &command.args[1]).unwrap(); let results = select(&val, &command.args[0]).unwrap();
let pat = string_to_value(&command.args[2], cache); let pat = string_to_value(&command.args[1], cache);
let has = results.contains(&pat.as_ref()); let has = results.contains(&pat.as_ref());
// Give better error for when @has check fails // Give better error for when @has check fails
if !command.negated && !has { if !command.negated && !has {
return Err(CkError::FailedCheck( return Err(CkError::FailedCheck(
format!( format!(
"{} matched to {:?} but didn't have {:?}", "{} matched to {:?} but didn't have {:?}",
&command.args[1], &command.args[0],
results, results,
pat.as_ref() pat.as_ref()
), ),
@ -233,13 +226,13 @@ fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> {
} }
CommandKind::IsMany => { CommandKind::IsMany => {
// @ismany <path> <jsonpath> <value>... // @ismany <path> <jsonpath> <value>...
let (path, query, values) = if let [path, query, values @ ..] = &command.args[..] { let (query, values) = if let [query, values @ ..] = &command.args[..] {
(path, query, values) (query, values)
} else { } else {
unreachable!("Checked in CommandKind::validate") unreachable!("Checked in CommandKind::validate")
}; };
let val = cache.get_value(path)?; let val = cache.value();
let got_values = select(&val, &query).unwrap(); let got_values = select(val, &query).unwrap();
assert!(!command.negated, "`@!ismany` is not supported"); assert!(!command.negated, "`@!ismany` is not supported");
// Serde json doesn't implement Ord or Hash for Value, so we must // Serde json doesn't implement Ord or Hash for Value, so we must
@ -270,18 +263,17 @@ fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> {
true true
} }
CommandKind::Count => { CommandKind::Count => {
// @count <path> <jsonpath> <count> = Check that the jsonpath matches exactly [count] times // @count <jsonpath> <count> = Check that the jsonpath matches exactly [count] times
assert_eq!(command.args.len(), 3); assert_eq!(command.args.len(), 2);
let expected: usize = command.args[2].parse().unwrap(); let expected: usize = command.args[1].parse().unwrap();
let val = cache.value();
let val = cache.get_value(&command.args[0])?; let results = select(val, &command.args[0]).unwrap();
let results = select(&val, &command.args[1]).unwrap();
let eq = results.len() == expected; let eq = results.len() == expected;
if !command.negated && !eq { if !command.negated && !eq {
return Err(CkError::FailedCheck( return Err(CkError::FailedCheck(
format!( format!(
"`{}` matched to `{:?}` with length {}, but expected length {}", "`{}` matched to `{:?}` with length {}, but expected length {}",
&command.args[1], &command.args[0],
results, results,
results.len(), results.len(),
expected expected
@ -293,17 +285,17 @@ fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> {
} }
} }
CommandKind::Is => { CommandKind::Is => {
// @has <path> <jsonpath> <value> = check *exactly one* item matched by path, and it equals value // @has <jsonpath> <value> = check *exactly one* item matched by path, and it equals value
assert_eq!(command.args.len(), 3); assert_eq!(command.args.len(), 2);
let val = cache.get_value(&command.args[0])?; let val = cache.value().clone();
let results = select(&val, &command.args[1]).unwrap(); let results = select(&val, &command.args[0]).unwrap();
let pat = string_to_value(&command.args[2], cache); let pat = string_to_value(&command.args[1], cache);
let is = results.len() == 1 && results[0] == pat.as_ref(); let is = results.len() == 1 && results[0] == pat.as_ref();
if !command.negated && !is { if !command.negated && !is {
return Err(CkError::FailedCheck( return Err(CkError::FailedCheck(
format!( format!(
"{} matched to {:?}, but expected {:?}", "{} matched to {:?}, but expected {:?}",
&command.args[1], &command.args[0],
results, results,
pat.as_ref() pat.as_ref()
), ),
@ -314,16 +306,16 @@ fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> {
} }
} }
CommandKind::Set => { CommandKind::Set => {
// @set <name> = <path> <jsonpath> // @set <name> = <jsonpath>
assert_eq!(command.args.len(), 4); assert_eq!(command.args.len(), 3);
assert_eq!(command.args[1], "=", "Expected an `=`"); assert_eq!(command.args[1], "=", "Expected an `=`");
let val = cache.get_value(&command.args[2])?; let val = cache.value().clone();
let results = select(&val, &command.args[3]).unwrap(); let results = select(&val, &command.args[2]).unwrap();
assert_eq!( assert_eq!(
results.len(), results.len(),
1, 1,
"Expected 1 match for `{}` (because of @set): matched to {:?}", "Expected 1 match for `{}` (because of @set): matched to {:?}",
command.args[3], command.args[2],
results results
); );
match results.len() { match results.len() {
@ -336,7 +328,7 @@ fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> {
_ => { _ => {
panic!( panic!(
"Got multiple results in `@set` for `{}`: {:?}", "Got multiple results in `@set` for `{}`: {:?}",
&command.args[3], results &command.args[2], results,
); );
} }
} }