Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Next Next commit
Remove need to give JSON file path
  • Loading branch information
GuillaumeGomez committed Aug 18, 2022
commit 57c85bd97da336a2199d1b157fb18e70efcafd35
70 changes: 12 additions & 58 deletions src/tools/jsondocck/src/cache.rs
Original file line number Diff line number Diff line change
@@ -1,77 +1,31 @@
use crate::error::CkError;
use crate::config::Config;
use serde_json::Value;
use std::collections::HashMap;
use std::io;
use std::path::{Path, PathBuf};
use std::path::Path;

use fs_err as fs;

#[derive(Debug)]
pub struct Cache {
root: PathBuf,
files: HashMap<PathBuf, String>,
values: HashMap<PathBuf, Value>,
value: Value,
pub variables: HashMap<String, Value>,
last_path: Option<PathBuf>,
}

impl Cache {
/// 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 {
root: Path::new(doc_dir).to_owned(),
files: HashMap::new(),
values: HashMap::new(),
value: serde_json::from_str::<Value>(&content).expect("failed to convert from JSON"),
variables: HashMap::new(),
last_path: None,
}
}

fn resolve_path(&mut self, path: &String) -> PathBuf {
if path != "-" {
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)
pub fn value(&self) -> &Value {
&self.value
}
}
90 changes: 41 additions & 49 deletions src/tools/jsondocck/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ fn main() -> Result<(), String> {
let config = parse_config(env::args().collect());

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)
.map_err(|_| format!("Jsondocck failed for {}", &config.template))?;

Expand Down Expand Up @@ -55,28 +55,23 @@ pub enum 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 {
CommandKind::Has => (1..=3).contains(&args.len()),
CommandKind::IsMany => args.len() >= 3,
CommandKind::Count | CommandKind::Is => 3 == args.len(),
CommandKind::Set => 4 == args.len(),
CommandKind::Has => (1..=2).contains(&args.len()),
CommandKind::IsMany => args.len() >= 2,
CommandKind::Count | CommandKind::Is => 2 == args.len(),
CommandKind::Set => 3 == args.len(),
};

if !count {
print_err(&format!("Incorrect number of arguments to `@{}`", self), lineno);
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 args[2].parse::<usize>().is_err() {
if args[1].parse::<usize>().is_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,
);
return false;
Expand Down Expand Up @@ -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;
continue;
}
Expand All @@ -199,26 +194,24 @@ fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> {
let result = match command.kind {
CommandKind::Has => {
match command.args.len() {
// @has <path> = file existence
1 => cache.get_file(&command.args[0]).is_ok(),
// @has <path> <jsonpath> = check path exists
2 => {
let val = cache.get_value(&command.args[0])?;
let results = select(&val, &command.args[1]).unwrap();
// @has <jsonpath> = check path exists
1 => {
let val = cache.value();
let results = select(val, &command.args[0]).unwrap();
!results.is_empty()
}
// @has <path> <jsonpath> <value> = check *any* item matched by path equals value
3 => {
let val = cache.get_value(&command.args[0])?;
let results = select(&val, &command.args[1]).unwrap();
let pat = string_to_value(&command.args[2], cache);
// @has <jsonpath> <value> = check *any* item matched by path equals value
2 => {
let val = cache.value().clone();
let results = select(&val, &command.args[0]).unwrap();
let pat = string_to_value(&command.args[1], cache);
let has = results.contains(&pat.as_ref());
// Give better error for when @has check fails
if !command.negated && !has {
return Err(CkError::FailedCheck(
format!(
"{} matched to {:?} but didn't have {:?}",
&command.args[1],
&command.args[0],
results,
pat.as_ref()
),
Expand All @@ -233,13 +226,13 @@ fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> {
}
CommandKind::IsMany => {
// @ismany <path> <jsonpath> <value>...
let (path, query, values) = if let [path, query, values @ ..] = &command.args[..] {
(path, query, values)
let (query, values) = if let [query, values @ ..] = &command.args[..] {
(query, values)
} else {
unreachable!("Checked in CommandKind::validate")
};
let val = cache.get_value(path)?;
let got_values = select(&val, &query).unwrap();
let val = cache.value();
let got_values = select(val, &query).unwrap();
assert!(!command.negated, "`@!ismany` is not supported");

// Serde json doesn't implement Ord or Hash for Value, so we must
Expand Down Expand Up @@ -270,18 +263,17 @@ fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> {
true
}
CommandKind::Count => {
// @count <path> <jsonpath> <count> = Check that the jsonpath matches exactly [count] times
assert_eq!(command.args.len(), 3);
let expected: usize = command.args[2].parse().unwrap();

let val = cache.get_value(&command.args[0])?;
let results = select(&val, &command.args[1]).unwrap();
// @count <jsonpath> <count> = Check that the jsonpath matches exactly [count] times
assert_eq!(command.args.len(), 2);
let expected: usize = command.args[1].parse().unwrap();
let val = cache.value();
let results = select(val, &command.args[0]).unwrap();
let eq = results.len() == expected;
if !command.negated && !eq {
return Err(CkError::FailedCheck(
format!(
"`{}` matched to `{:?}` with length {}, but expected length {}",
&command.args[1],
&command.args[0],
results,
results.len(),
expected
Expand All @@ -293,17 +285,17 @@ fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> {
}
}
CommandKind::Is => {
// @has <path> <jsonpath> <value> = check *exactly one* item matched by path, and it equals value
assert_eq!(command.args.len(), 3);
let val = cache.get_value(&command.args[0])?;
let results = select(&val, &command.args[1]).unwrap();
let pat = string_to_value(&command.args[2], cache);
// @has <jsonpath> <value> = check *exactly one* item matched by path, and it equals value
assert_eq!(command.args.len(), 2);
let val = cache.value().clone();
let results = select(&val, &command.args[0]).unwrap();
let pat = string_to_value(&command.args[1], cache);
let is = results.len() == 1 && results[0] == pat.as_ref();
if !command.negated && !is {
return Err(CkError::FailedCheck(
format!(
"{} matched to {:?}, but expected {:?}",
&command.args[1],
&command.args[0],
results,
pat.as_ref()
),
Expand All @@ -314,16 +306,16 @@ fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> {
}
}
CommandKind::Set => {
// @set <name> = <path> <jsonpath>
assert_eq!(command.args.len(), 4);
// @set <name> = <jsonpath>
assert_eq!(command.args.len(), 3);
assert_eq!(command.args[1], "=", "Expected an `=`");
let val = cache.get_value(&command.args[2])?;
let results = select(&val, &command.args[3]).unwrap();
let val = cache.value().clone();
let results = select(&val, &command.args[2]).unwrap();
assert_eq!(
results.len(),
1,
"Expected 1 match for `{}` (because of @set): matched to {:?}",
command.args[3],
command.args[2],
results
);
match results.len() {
Expand All @@ -336,7 +328,7 @@ fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> {
_ => {
panic!(
"Got multiple results in `@set` for `{}`: {:?}",
&command.args[3], results
&command.args[2], results,
);
}
}
Expand Down