Skip to content

Commit

Permalink
Remove need to give JSON file path
Browse files Browse the repository at this point in the history
  • Loading branch information
GuillaumeGomez committed Aug 18, 2022
1 parent 9c20b2a commit 57c85bd
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 107 deletions.
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

0 comments on commit 57c85bd

Please sign in to comment.