Remove need to give JSON file path
This commit is contained in:
parent
9c20b2a8cc
commit
57c85bd97d
2 changed files with 53 additions and 107 deletions
|
|
@ -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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue