jsondocck: command -> directive

This commit is contained in:
Alona Enraght-Moony 2025-05-28 21:22:21 +00:00
parent 2c9fb22f04
commit 14db1b5b1c
4 changed files with 41 additions and 39 deletions

View file

@ -4,7 +4,7 @@ use getopts::Options;
pub struct Config {
/// The directory documentation output was generated in
pub doc_dir: String,
/// The file documentation was generated for, with docck commands to check
/// The file documentation was generated for, with docck directives to check
pub template: String,
}

View file

@ -1,16 +1,18 @@
use std::borrow::Cow;
use serde_json::Value;
use crate::cache::Cache;
#[derive(Debug)]
pub struct Command {
pub kind: CommandKind,
pub struct Directive {
pub kind: DirectiveKind,
pub path: String,
pub lineno: usize,
}
#[derive(Debug)]
pub enum CommandKind {
pub enum DirectiveKind {
/// `//@ has <path>`
///
/// Checks the path exists.
@ -55,16 +57,16 @@ pub enum CommandKind {
Set { variable: String },
}
impl CommandKind {
impl DirectiveKind {
/// Returns both the kind and the path.
///
/// Returns `None` if the command isn't from jsondocck (e.g. from compiletest).
/// Returns `None` if the directive isn't from jsondocck (e.g. from compiletest).
pub fn parse<'a>(
command_name: &str,
directive_name: &str,
negated: bool,
args: &'a [String],
) -> Option<(Self, &'a str)> {
let kind = match (command_name, negated) {
let kind = match (directive_name, negated) {
("count", false) => {
assert_eq!(args.len(), 2);
let expected = args[1].parse().expect("invalid number for `count`");
@ -104,11 +106,11 @@ impl CommandKind {
_ => panic!("`//@ !has` must have 2 or 3 arguments, but got {args:?}"),
},
(_, false) if KNOWN_DIRECTIVE_NAMES.contains(&command_name) => {
(_, false) if KNOWN_DIRECTIVE_NAMES.contains(&directive_name) => {
return None;
}
_ => {
panic!("Invalid command `//@ {}{command_name}`", if negated { "!" } else { "" })
panic!("Invalid directive `//@ {}{directive_name}`", if negated { "!" } else { "" })
}
};
@ -116,22 +118,22 @@ impl CommandKind {
}
}
impl Command {
/// Performs the actual work of ensuring a command passes.
impl Directive {
/// Performs the actual work of ensuring a directive passes.
pub fn check(&self, cache: &mut Cache) -> Result<(), String> {
let matches = cache.select(&self.path);
match &self.kind {
CommandKind::HasPath => {
DirectiveKind::HasPath => {
if matches.is_empty() {
return Err("matched to no values".to_owned());
}
}
CommandKind::HasNotPath => {
DirectiveKind::HasNotPath => {
if !matches.is_empty() {
return Err(format!("matched to {matches:?}, but wanted no matches"));
}
}
CommandKind::HasValue { value } => {
DirectiveKind::HasValue { value } => {
let want_value = string_to_value(value, cache);
if !matches.contains(&want_value.as_ref()) {
return Err(format!(
@ -139,7 +141,7 @@ impl Command {
));
}
}
CommandKind::HasNotValue { value } => {
DirectiveKind::HasNotValue { value } => {
let wantnt_value = string_to_value(value, cache);
if matches.contains(&wantnt_value.as_ref()) {
return Err(format!(
@ -152,14 +154,14 @@ impl Command {
}
}
CommandKind::Is { value } => {
DirectiveKind::Is { value } => {
let want_value = string_to_value(value, cache);
let matched = get_one(&matches)?;
if matched != want_value.as_ref() {
return Err(format!("matched to {matched:?} but want {want_value:?}"));
}
}
CommandKind::IsNot { value } => {
DirectiveKind::IsNot { value } => {
let wantnt_value = string_to_value(value, cache);
let matched = get_one(&matches)?;
if matched == wantnt_value.as_ref() {
@ -167,7 +169,7 @@ impl Command {
}
}
CommandKind::IsMany { values } => {
DirectiveKind::IsMany { values } => {
// Serde json doesn't implement Ord or Hash for Value, so we must
// use a Vec here. While in theory that makes setwize equality
// O(n^2), in practice n will never be large enough to matter.
@ -187,7 +189,7 @@ impl Command {
}
}
}
CommandKind::CountIs { expected } => {
DirectiveKind::CountIs { expected } => {
if *expected != matches.len() {
return Err(format!(
"matched to `{matches:?}` with length {}, but expected length {expected}",
@ -195,7 +197,7 @@ impl Command {
));
}
}
CommandKind::Set { variable } => {
DirectiveKind::Set { variable } => {
let value = get_one(&matches)?;
let r = cache.variables.insert(variable.to_owned(), value.clone());
assert!(r.is_none(), "name collision: {variable:?} is duplicated");
@ -227,4 +229,4 @@ fn string_to_value<'a>(s: &str, cache: &'a Cache) -> Cow<'a, Value> {
} else {
Cow::Owned(serde_json::from_str(s).expect(&format!("Cannot convert `{}` to json", s)))
}
}
}

View file

@ -1,7 +1,7 @@
use crate::Command;
use crate::Directive;
#[derive(Debug)]
pub struct CkError {
pub message: String,
pub command: Command,
pub directive: Directive,
}

View file

@ -11,7 +11,7 @@ mod error;
use cache::Cache;
use config::parse_config;
use directive::{Command, CommandKind};
use directive::{Directive, DirectiveKind};
use error::CkError;
fn main() -> ExitCode {
@ -19,14 +19,14 @@ fn main() -> ExitCode {
let mut failed = Vec::new();
let mut cache = Cache::new(&config);
let Ok(commands) = get_commands(&config.template) else {
let Ok(directives) = get_directives(&config.template) else {
eprintln!("Jsondocck failed for {}", &config.template);
return ExitCode::FAILURE;
};
for command in commands {
if let Err(message) = command.check( &mut cache) {
failed.push(CkError { command, message });
for directive in directives {
if let Err(message) = directive.check(&mut cache) {
failed.push(CkError { directive, message });
}
}
@ -34,7 +34,7 @@ fn main() -> ExitCode {
ExitCode::SUCCESS
} else {
for i in failed {
eprintln!("{}:{}, command failed", config.template, i.command.lineno);
eprintln!("{}:{}, directive failed", config.template, i.directive.lineno);
eprintln!("{}", i.message)
}
ExitCode::FAILURE
@ -47,7 +47,7 @@ static LINE_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
^\s*
//@\s+
(?P<negated>!?)
(?P<cmd>[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)
(?P<directive>[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*)
(?P<args>.*)$
"#,
)
@ -70,12 +70,12 @@ static DEPRECATED_LINE_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
});
fn print_err(msg: &str, lineno: usize) {
eprintln!("Invalid command: {} on line {}", msg, lineno)
eprintln!("Invalid directive: {} on line {}", msg, lineno)
}
/// Get a list of commands from a file.
fn get_commands(template: &str) -> Result<Vec<Command>, ()> {
let mut commands = Vec::new();
/// Get a list of directives from a file.
fn get_directives(template: &str) -> Result<Vec<Directive>, ()> {
let mut directives = Vec::new();
let mut errors = false;
let file = fs::read_to_string(template).unwrap();
@ -83,7 +83,7 @@ fn get_commands(template: &str) -> Result<Vec<Command>, ()> {
let lineno = lineno + 1;
if DEPRECATED_LINE_PATTERN.is_match(line) {
print_err("Deprecated command syntax, replace `// @` with `//@ `", lineno);
print_err("Deprecated directive syntax, replace `// @` with `//@ `", lineno);
errors = true;
continue;
}
@ -101,10 +101,10 @@ fn get_commands(template: &str) -> Result<Vec<Command>, ()> {
continue;
};
if let Some((kind, path)) = CommandKind::parse(&cap["cmd"], negated, &args) {
commands.push(Command { kind, lineno, path: path.to_owned() })
if let Some((kind, path)) = DirectiveKind::parse(&cap["directive"], negated, &args) {
directives.push(Directive { kind, lineno, path: path.to_owned() })
}
}
if !errors { Ok(commands) } else { Err(()) }
if !errors { Ok(directives) } else { Err(()) }
}