2018-10-03 19:21:45 +01:00
|
|
|
extern crate crc;
|
2018-02-26 23:10:53 +00:00
|
|
|
#[macro_use]
|
|
|
|
|
extern crate nom;
|
2018-10-05 19:52:51 +01:00
|
|
|
extern crate pelite;
|
2018-02-26 23:10:53 +00:00
|
|
|
extern crate regex;
|
2018-10-06 10:02:27 +01:00
|
|
|
extern crate unicase;
|
2018-02-26 23:10:53 +00:00
|
|
|
|
2018-10-01 20:44:48 +01:00
|
|
|
#[cfg(test)]
|
|
|
|
|
extern crate tempfile;
|
|
|
|
|
|
2018-10-07 10:18:13 +01:00
|
|
|
mod error;
|
2018-10-03 21:52:37 +01:00
|
|
|
mod function;
|
|
|
|
|
mod version;
|
|
|
|
|
|
2018-10-03 19:21:45 +01:00
|
|
|
use std::collections::{HashMap, HashSet};
|
2018-10-04 20:01:25 +01:00
|
|
|
use std::ffi::OsStr;
|
2018-10-06 10:36:39 +01:00
|
|
|
use std::fmt;
|
2018-10-07 22:13:48 +01:00
|
|
|
use std::ops::DerefMut;
|
2018-10-04 20:01:25 +01:00
|
|
|
use std::path::{Path, PathBuf};
|
2018-09-29 10:52:38 +01:00
|
|
|
use std::str;
|
2018-10-07 22:13:48 +01:00
|
|
|
use std::sync::{PoisonError, RwLock, RwLockWriteGuard};
|
2018-02-26 23:10:53 +00:00
|
|
|
|
2018-10-07 10:18:13 +01:00
|
|
|
use nom::types::CompleteStr;
|
|
|
|
|
use nom::IResult;
|
2018-10-01 20:44:48 +01:00
|
|
|
|
2018-10-07 10:18:13 +01:00
|
|
|
pub use error::{Error, ParsingError};
|
2018-09-29 13:01:59 +01:00
|
|
|
use function::Function;
|
|
|
|
|
|
2018-10-07 10:18:13 +01:00
|
|
|
type ParsingResult<'a, T> = IResult<CompleteStr<'a>, T, ParsingError>;
|
2018-10-06 22:30:47 +01:00
|
|
|
|
2019-04-07 10:39:48 +01:00
|
|
|
// GameType variants must not change order, as their integer values are used as
|
|
|
|
|
// constants in the C API.
|
2018-10-06 13:28:03 +01:00
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
2018-10-01 20:44:48 +01:00
|
|
|
pub enum GameType {
|
2018-10-04 19:14:28 +01:00
|
|
|
Tes4,
|
|
|
|
|
Tes5,
|
|
|
|
|
Tes5se,
|
|
|
|
|
Tes5vr,
|
|
|
|
|
Fo3,
|
|
|
|
|
Fonv,
|
|
|
|
|
Fo4,
|
|
|
|
|
Fo4vr,
|
2019-04-07 10:39:48 +01:00
|
|
|
Tes3,
|
2018-10-01 20:44:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl GameType {
|
2018-10-25 18:34:00 +01:00
|
|
|
fn supports_light_plugins(self) -> bool {
|
2018-10-01 20:44:48 +01:00
|
|
|
match self {
|
2018-10-04 19:14:28 +01:00
|
|
|
GameType::Tes5se | GameType::Tes5vr | GameType::Fo4 | GameType::Fo4vr => true,
|
2018-10-01 20:44:48 +01:00
|
|
|
_ => false,
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-10-04 20:01:25 +01:00
|
|
|
|
2018-10-25 18:34:00 +01:00
|
|
|
fn is_plugin_filename(self, path: &Path) -> bool {
|
2018-10-04 20:01:25 +01:00
|
|
|
match path.extension().and_then(OsStr::to_str) {
|
|
|
|
|
Some("esp") | Some("esm") => true,
|
|
|
|
|
Some("esl") if self.supports_light_plugins() => true,
|
|
|
|
|
Some("ghost") => path
|
|
|
|
|
.file_stem()
|
|
|
|
|
.map(|s| self.is_plugin_filename(Path::new(s)))
|
|
|
|
|
.unwrap_or(false),
|
|
|
|
|
_ => false,
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-10-01 20:44:48 +01:00
|
|
|
}
|
|
|
|
|
|
2018-10-06 13:28:03 +01:00
|
|
|
#[derive(Debug)]
|
2018-10-01 20:44:48 +01:00
|
|
|
pub struct State {
|
|
|
|
|
game_type: GameType,
|
2018-10-06 11:39:52 +01:00
|
|
|
/// Game Data folder path.
|
2018-10-01 20:44:48 +01:00
|
|
|
data_path: PathBuf,
|
2018-10-06 11:39:52 +01:00
|
|
|
/// Path to the LOOT executable, used to resolve conditions that use the "LOOT" path.
|
2018-10-03 19:21:45 +01:00
|
|
|
loot_path: PathBuf,
|
2018-10-06 11:39:52 +01:00
|
|
|
/// Lowercased plugin filenames.
|
|
|
|
|
active_plugins: HashSet<String>,
|
|
|
|
|
/// Lowercased paths.
|
|
|
|
|
crc_cache: RwLock<HashMap<String, u32>>,
|
|
|
|
|
/// Lowercased plugin filenames and their versions as found in description fields.
|
|
|
|
|
plugin_versions: HashMap<String, String>,
|
|
|
|
|
/// Conditions that have already been evaluated, and their results.
|
|
|
|
|
condition_cache: RwLock<HashMap<Function, bool>>,
|
2018-10-01 20:44:48 +01:00
|
|
|
}
|
|
|
|
|
|
2018-10-06 13:28:03 +01:00
|
|
|
impl State {
|
|
|
|
|
pub fn new(game_type: GameType, data_path: PathBuf, loot_path: PathBuf) -> Self {
|
|
|
|
|
State {
|
|
|
|
|
game_type,
|
|
|
|
|
data_path,
|
|
|
|
|
loot_path,
|
|
|
|
|
active_plugins: HashSet::default(),
|
|
|
|
|
crc_cache: RwLock::default(),
|
|
|
|
|
plugin_versions: HashMap::default(),
|
|
|
|
|
condition_cache: RwLock::default(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn with_plugin_versions<T: AsRef<str>, V: ToString>(
|
|
|
|
|
mut self,
|
|
|
|
|
plugin_versions: &[(T, V)],
|
|
|
|
|
) -> Self {
|
2018-10-07 22:13:48 +01:00
|
|
|
self.set_plugin_versions(plugin_versions);
|
2018-10-06 13:28:03 +01:00
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn with_active_plugins<T: AsRef<str>>(mut self, active_plugins: &[T]) -> Self {
|
2018-10-07 22:13:48 +01:00
|
|
|
self.set_active_plugins(active_plugins);
|
|
|
|
|
self
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn set_active_plugins<T: AsRef<str>>(&mut self, active_plugins: &[T]) {
|
2018-10-06 13:28:03 +01:00
|
|
|
self.active_plugins = active_plugins
|
2019-06-16 20:33:28 +01:00
|
|
|
.iter()
|
2018-10-06 13:28:03 +01:00
|
|
|
.map(|s| s.as_ref().to_lowercase())
|
|
|
|
|
.collect();
|
2018-10-07 22:13:48 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn set_plugin_versions<T: AsRef<str>, V: ToString>(&mut self, plugin_versions: &[(T, V)]) {
|
|
|
|
|
self.plugin_versions = plugin_versions
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|(p, v)| (p.as_ref().to_lowercase(), v.to_string()))
|
|
|
|
|
.collect();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn set_cached_crcs<T: AsRef<str>>(
|
|
|
|
|
&mut self,
|
|
|
|
|
plugin_crcs: &[(T, u32)],
|
|
|
|
|
) -> Result<(), PoisonError<RwLockWriteGuard<HashMap<String, u32>>>> {
|
|
|
|
|
let mut writer = self.crc_cache.write()?;
|
|
|
|
|
|
|
|
|
|
writer.deref_mut().clear();
|
|
|
|
|
writer.deref_mut().extend(
|
|
|
|
|
plugin_crcs
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|(p, v)| (p.as_ref().to_lowercase(), *v)),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
Ok(())
|
2018-10-06 13:28:03 +01:00
|
|
|
}
|
2018-10-20 21:11:12 +01:00
|
|
|
|
|
|
|
|
pub fn clear_condition_cache(
|
|
|
|
|
&mut self,
|
|
|
|
|
) -> Result<(), PoisonError<RwLockWriteGuard<HashMap<Function, bool>>>> {
|
|
|
|
|
self.condition_cache.write().map(|mut c| c.clear())
|
|
|
|
|
}
|
2018-10-06 13:28:03 +01:00
|
|
|
}
|
|
|
|
|
|
2018-10-06 11:39:52 +01:00
|
|
|
/// Compound conditions joined by 'or'
|
|
|
|
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
|
2018-09-29 13:01:59 +01:00
|
|
|
pub struct Expression(Vec<CompoundCondition>);
|
2018-02-26 23:10:53 +00:00
|
|
|
|
|
|
|
|
impl Expression {
|
2018-10-01 20:44:48 +01:00
|
|
|
pub fn eval(&self, state: &State) -> Result<bool, Error> {
|
2018-02-26 23:10:53 +00:00
|
|
|
for compound_condition in &self.0 {
|
2018-10-01 20:44:48 +01:00
|
|
|
if compound_condition.eval(state)? {
|
2018-02-26 23:10:53 +00:00
|
|
|
return Ok(true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Ok(false)
|
|
|
|
|
}
|
2018-10-06 15:08:26 +01:00
|
|
|
}
|
2018-02-26 23:10:53 +00:00
|
|
|
|
2018-10-06 15:08:26 +01:00
|
|
|
impl str::FromStr for Expression {
|
|
|
|
|
type Err = Error;
|
|
|
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
2018-10-07 10:18:13 +01:00
|
|
|
parse_expression(nom::types::CompleteStr(s))
|
2018-10-06 15:08:26 +01:00
|
|
|
.map_err(Error::from)
|
2019-01-26 09:44:01 +00:00
|
|
|
.and_then(|(remaining_input, expression)| {
|
|
|
|
|
if remaining_input.is_empty() {
|
|
|
|
|
Ok(expression)
|
|
|
|
|
} else {
|
|
|
|
|
Err(Error::UnconsumedInput(remaining_input.to_string()))
|
|
|
|
|
}
|
|
|
|
|
})
|
2018-02-26 23:10:53 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-07 10:18:13 +01:00
|
|
|
fn parse_expression(input: nom::types::CompleteStr) -> ParsingResult<Expression> {
|
2018-10-06 15:08:26 +01:00
|
|
|
do_parse!(
|
|
|
|
|
input,
|
2018-10-07 10:18:13 +01:00
|
|
|
compound_conditions:
|
|
|
|
|
separated_list!(
|
|
|
|
|
fix_error!(ParsingError, ws!(tag!("or"))),
|
|
|
|
|
CompoundCondition::parse
|
|
|
|
|
)
|
2018-10-06 15:08:26 +01:00
|
|
|
>> (Expression(compound_conditions))
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-06 10:36:39 +01:00
|
|
|
impl fmt::Display for Expression {
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
|
let strings: Vec<String> = self.0.iter().map(CompoundCondition::to_string).collect();
|
|
|
|
|
write!(f, "{}", strings.join(" or "))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-06 11:39:52 +01:00
|
|
|
/// Conditions joined by 'and'
|
|
|
|
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
|
2018-02-26 23:10:53 +00:00
|
|
|
struct CompoundCondition(Vec<Condition>);
|
|
|
|
|
|
|
|
|
|
impl CompoundCondition {
|
2018-10-01 20:44:48 +01:00
|
|
|
fn eval(&self, state: &State) -> Result<bool, Error> {
|
2018-02-26 23:10:53 +00:00
|
|
|
for condition in &self.0 {
|
2018-10-01 20:44:48 +01:00
|
|
|
if !condition.eval(state)? {
|
2018-02-26 23:10:53 +00:00
|
|
|
return Ok(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Ok(true)
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-07 10:18:13 +01:00
|
|
|
fn parse(input: nom::types::CompleteStr) -> ParsingResult<CompoundCondition> {
|
2018-09-29 10:52:38 +01:00
|
|
|
do_parse!(
|
|
|
|
|
input,
|
2018-10-07 10:18:13 +01:00
|
|
|
conditions:
|
|
|
|
|
separated_list!(fix_error!(ParsingError, ws!(tag!("and"))), Condition::parse)
|
2018-09-29 10:52:38 +01:00
|
|
|
>> (CompoundCondition(conditions))
|
|
|
|
|
)
|
2018-02-26 23:10:53 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-06 10:36:39 +01:00
|
|
|
impl fmt::Display for CompoundCondition {
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
|
let strings: Vec<String> = self.0.iter().map(Condition::to_string).collect();
|
|
|
|
|
write!(f, "{}", strings.join(" and "))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-06 11:39:52 +01:00
|
|
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
2018-02-26 23:10:53 +00:00
|
|
|
enum Condition {
|
|
|
|
|
Function(Function),
|
2018-09-29 10:52:38 +01:00
|
|
|
InvertedFunction(Function),
|
2018-02-26 23:10:53 +00:00
|
|
|
Expression(Expression),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Condition {
|
2018-10-01 20:44:48 +01:00
|
|
|
fn eval(&self, state: &State) -> Result<bool, Error> {
|
2018-10-06 16:26:18 +01:00
|
|
|
match self {
|
|
|
|
|
Condition::Function(f) => f.eval(state),
|
|
|
|
|
Condition::InvertedFunction(f) => f.eval(state).map(|r| !r),
|
|
|
|
|
Condition::Expression(e) => e.eval(state),
|
2018-02-26 23:10:53 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-07 10:18:13 +01:00
|
|
|
fn parse(input: nom::types::CompleteStr) -> ParsingResult<Condition> {
|
2018-09-29 10:52:38 +01:00
|
|
|
do_parse!(
|
|
|
|
|
input,
|
|
|
|
|
condition:
|
|
|
|
|
alt!(
|
2018-12-22 22:40:16 +00:00
|
|
|
call!(Function::parse) => {
|
|
|
|
|
|f| Condition::Function(f)
|
|
|
|
|
} |
|
|
|
|
|
preceded!(fix_error!(ParsingError, ws!(tag!("not"))), call!(Function::parse)) => {
|
|
|
|
|
|f| Condition::InvertedFunction(f)
|
|
|
|
|
} |
|
|
|
|
|
delimited!(fix_error!(ParsingError, ws!(tag!("("))), call!(parse_expression), fix_error!(ParsingError, ws!(tag!(")")))) => {
|
|
|
|
|
|e| Condition::Expression(e)
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
>> (condition)
|
2018-09-29 10:52:38 +01:00
|
|
|
)
|
2018-02-26 23:10:53 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-06 10:36:39 +01:00
|
|
|
impl fmt::Display for Condition {
|
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
|
use Condition::*;
|
|
|
|
|
match self {
|
|
|
|
|
Function(function) => write!(f, "{}", function),
|
|
|
|
|
InvertedFunction(function) => write!(f, "not {}", function),
|
|
|
|
|
Expression(e) => write!(f, "({})", e),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-26 23:10:53 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
2018-10-01 20:44:48 +01:00
|
|
|
use std::fs::create_dir;
|
2018-10-06 15:08:26 +01:00
|
|
|
use std::str::FromStr;
|
2018-10-01 20:44:48 +01:00
|
|
|
|
|
|
|
|
fn state<T: Into<PathBuf>>(data_path: T) -> State {
|
|
|
|
|
let data_path = data_path.into();
|
|
|
|
|
if !data_path.exists() {
|
|
|
|
|
create_dir(&data_path).unwrap();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
State {
|
2018-10-04 19:14:28 +01:00
|
|
|
game_type: GameType::Tes4,
|
2018-10-25 18:34:00 +01:00
|
|
|
data_path,
|
2018-10-03 19:21:45 +01:00
|
|
|
loot_path: PathBuf::new(),
|
2018-10-03 18:31:26 +01:00
|
|
|
active_plugins: HashSet::new(),
|
2018-10-03 19:21:45 +01:00
|
|
|
crc_cache: RwLock::default(),
|
2018-10-03 21:52:37 +01:00
|
|
|
plugin_versions: HashMap::default(),
|
2018-10-06 11:39:52 +01:00
|
|
|
condition_cache: RwLock::default(),
|
2018-10-01 20:44:48 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-04 20:01:25 +01:00
|
|
|
#[test]
|
|
|
|
|
fn game_type_supports_light_plugins_should_be_true_for_tes5se_tes5vr_fo4_and_fo4vr() {
|
|
|
|
|
assert!(GameType::Tes5se.supports_light_plugins());
|
|
|
|
|
assert!(GameType::Tes5vr.supports_light_plugins());
|
|
|
|
|
assert!(GameType::Fo4.supports_light_plugins());
|
|
|
|
|
assert!(GameType::Fo4vr.supports_light_plugins());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2019-04-07 10:39:48 +01:00
|
|
|
fn game_type_supports_light_master_should_be_false_for_tes3_to_5_fo3_and_fonv() {
|
|
|
|
|
assert!(!GameType::Tes3.supports_light_plugins());
|
2018-10-04 20:01:25 +01:00
|
|
|
assert!(!GameType::Tes4.supports_light_plugins());
|
|
|
|
|
assert!(!GameType::Tes5.supports_light_plugins());
|
|
|
|
|
assert!(!GameType::Fo3.supports_light_plugins());
|
|
|
|
|
assert!(!GameType::Fonv.supports_light_plugins());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn game_type_is_plugin_filename_should_be_true_for_esp_for_all_game_types() {
|
|
|
|
|
let filename = Path::new("Blank.esp");
|
|
|
|
|
|
2019-04-07 10:39:48 +01:00
|
|
|
assert!(GameType::Tes3.is_plugin_filename(filename));
|
2018-10-04 20:01:25 +01:00
|
|
|
assert!(GameType::Tes4.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Tes5.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Tes5se.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Tes5vr.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Fo3.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Fonv.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Fo4.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Fo4vr.is_plugin_filename(filename));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn game_type_is_plugin_filename_should_be_true_for_esm_for_all_game_types() {
|
|
|
|
|
let filename = Path::new("Blank.esm");
|
|
|
|
|
|
2019-04-07 10:39:48 +01:00
|
|
|
assert!(GameType::Tes3.is_plugin_filename(filename));
|
2018-10-04 20:01:25 +01:00
|
|
|
assert!(GameType::Tes4.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Tes5.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Tes5se.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Tes5vr.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Fo3.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Fonv.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Fo4.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Fo4vr.is_plugin_filename(filename));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn game_type_is_plugin_filename_should_be_true_for_esl_for_tes5se_tes5vr_fo4_and_fo4vr() {
|
|
|
|
|
let filename = Path::new("Blank.esl");
|
|
|
|
|
|
|
|
|
|
assert!(GameType::Tes5se.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Tes5vr.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Fo4.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Fo4vr.is_plugin_filename(filename));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2019-04-07 10:39:48 +01:00
|
|
|
fn game_type_is_plugin_filename_should_be_false_for_esl_for_tes3_to_5_fo3_and_fonv() {
|
2018-10-04 20:01:25 +01:00
|
|
|
let filename = Path::new("Blank.esl");
|
|
|
|
|
|
2019-04-07 10:39:48 +01:00
|
|
|
assert!(!GameType::Tes3.is_plugin_filename(filename));
|
2018-10-04 20:01:25 +01:00
|
|
|
assert!(!GameType::Tes4.is_plugin_filename(filename));
|
|
|
|
|
assert!(!GameType::Tes5.is_plugin_filename(filename));
|
|
|
|
|
assert!(!GameType::Fo3.is_plugin_filename(filename));
|
|
|
|
|
assert!(!GameType::Fonv.is_plugin_filename(filename));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn game_type_is_plugin_filename_should_be_true_for_esp_dot_ghost_for_all_game_types() {
|
|
|
|
|
let filename = Path::new("Blank.esp.ghost");
|
|
|
|
|
|
2019-04-07 10:39:48 +01:00
|
|
|
assert!(GameType::Tes3.is_plugin_filename(filename));
|
2018-10-04 20:01:25 +01:00
|
|
|
assert!(GameType::Tes4.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Tes5.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Tes5se.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Tes5vr.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Fo3.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Fonv.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Fo4.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Fo4vr.is_plugin_filename(filename));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn game_type_is_plugin_filename_should_be_true_for_esm_dot_ghost_for_all_game_types() {
|
|
|
|
|
let filename = Path::new("Blank.esm.ghost");
|
|
|
|
|
|
2019-04-07 10:39:48 +01:00
|
|
|
assert!(GameType::Tes3.is_plugin_filename(filename));
|
2018-10-04 20:01:25 +01:00
|
|
|
assert!(GameType::Tes4.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Tes5.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Tes5se.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Tes5vr.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Fo3.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Fonv.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Fo4.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Fo4vr.is_plugin_filename(filename));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn game_type_is_plugin_filename_should_be_true_for_esl_dot_ghost_for_tes5se_tes5vr_fo4_and_fo4vr(
|
2018-12-22 22:40:16 +00:00
|
|
|
) {
|
2018-10-04 20:01:25 +01:00
|
|
|
let filename = Path::new("Blank.esl.ghost");
|
|
|
|
|
|
|
|
|
|
assert!(GameType::Tes5se.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Tes5vr.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Fo4.is_plugin_filename(filename));
|
|
|
|
|
assert!(GameType::Fo4vr.is_plugin_filename(filename));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
2019-04-07 10:39:48 +01:00
|
|
|
fn game_type_is_plugin_filename_should_be_false_for_esl_dot_ghost_for_tes3_to_5_fo3_and_fonv() {
|
2018-10-04 20:01:25 +01:00
|
|
|
let filename = Path::new("Blank.esl.ghost");
|
|
|
|
|
|
2019-04-07 10:39:48 +01:00
|
|
|
assert!(!GameType::Tes3.is_plugin_filename(filename));
|
2018-10-04 20:01:25 +01:00
|
|
|
assert!(!GameType::Tes4.is_plugin_filename(filename));
|
|
|
|
|
assert!(!GameType::Tes5.is_plugin_filename(filename));
|
|
|
|
|
assert!(!GameType::Fo3.is_plugin_filename(filename));
|
|
|
|
|
assert!(!GameType::Fonv.is_plugin_filename(filename));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn game_type_is_plugin_filename_should_be_false_for_non_esp_esm_esl_for_all_game_types() {
|
|
|
|
|
let filename = Path::new("Blank.txt");
|
|
|
|
|
|
2019-04-07 10:39:48 +01:00
|
|
|
assert!(!GameType::Tes3.is_plugin_filename(filename));
|
2018-10-04 20:01:25 +01:00
|
|
|
assert!(!GameType::Tes4.is_plugin_filename(filename));
|
|
|
|
|
assert!(!GameType::Tes5.is_plugin_filename(filename));
|
|
|
|
|
assert!(!GameType::Tes5se.is_plugin_filename(filename));
|
|
|
|
|
assert!(!GameType::Tes5vr.is_plugin_filename(filename));
|
|
|
|
|
assert!(!GameType::Fo3.is_plugin_filename(filename));
|
|
|
|
|
assert!(!GameType::Fonv.is_plugin_filename(filename));
|
|
|
|
|
assert!(!GameType::Fo4.is_plugin_filename(filename));
|
|
|
|
|
assert!(!GameType::Fo4vr.is_plugin_filename(filename));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn game_type_is_plugin_filename_should_be_false_for_non_esp_esm_esl_dot_ghost_for_all_game_types(
|
2018-12-22 22:40:16 +00:00
|
|
|
) {
|
2018-10-04 20:01:25 +01:00
|
|
|
let filename = Path::new("Blank.txt.ghost");
|
|
|
|
|
|
2019-04-07 10:39:48 +01:00
|
|
|
assert!(!GameType::Tes3.is_plugin_filename(filename));
|
2018-10-04 20:01:25 +01:00
|
|
|
assert!(!GameType::Tes4.is_plugin_filename(filename));
|
|
|
|
|
assert!(!GameType::Tes5.is_plugin_filename(filename));
|
|
|
|
|
assert!(!GameType::Tes5se.is_plugin_filename(filename));
|
|
|
|
|
assert!(!GameType::Tes5vr.is_plugin_filename(filename));
|
|
|
|
|
assert!(!GameType::Fo3.is_plugin_filename(filename));
|
|
|
|
|
assert!(!GameType::Fonv.is_plugin_filename(filename));
|
|
|
|
|
assert!(!GameType::Fo4.is_plugin_filename(filename));
|
|
|
|
|
assert!(!GameType::Fo4vr.is_plugin_filename(filename));
|
|
|
|
|
}
|
|
|
|
|
|
2018-10-07 10:18:13 +01:00
|
|
|
#[test]
|
|
|
|
|
fn expression_from_str_should_error_with_input_on_incomplete_input() {
|
|
|
|
|
let error = Expression::from_str("file(\"Carg").unwrap_err();
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
"An error was encountered in the parser \"SeparatedList\" while parsing the expression \"file(\\\"Carg\"",
|
|
|
|
|
error.to_string()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn expression_from_str_should_error_with_input_on_invalid_regex() {
|
|
|
|
|
let error = Expression::from_str("file(\"Carg\\.*(\")").unwrap_err();
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
"An error was encountered while parsing the expression \"Carg\\.*(\": regex parse error:\n Carg\\.*(\n ^\nerror: unclosed group",
|
|
|
|
|
error.to_string()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn expression_from_str_should_error_with_input_on_invalid_crc() {
|
|
|
|
|
let error = Expression::from_str("checksum(\"Cargo.toml\", DEADBEEFDEAD)").unwrap_err();
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
"An error was encountered while parsing the expression \"DEADBEEFDEAD\": number too large to fit in target type",
|
|
|
|
|
error.to_string()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn expression_from_str_should_error_with_input_on_directory_regex() {
|
|
|
|
|
let error = Expression::from_str("file(\"targ.*et/\")").unwrap_err();
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
"An error was encountered while parsing the expression \"targ.*et/\\\")\": \"targ.*et/\" ends in a directory separator",
|
|
|
|
|
error.to_string()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn expression_from_str_should_error_with_input_on_path_outside_game_directory() {
|
|
|
|
|
let error = Expression::from_str("file(\"../../Cargo.toml\")").unwrap_err();
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
"An error was encountered while parsing the expression \"../../Cargo.toml\\\")\": \"../../Cargo.toml\" is not in the game directory",
|
|
|
|
|
error.to_string()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-29 10:52:38 +01:00
|
|
|
#[test]
|
|
|
|
|
fn expression_parse_should_handle_a_single_compound_condition() {
|
2018-10-06 15:08:26 +01:00
|
|
|
let result = Expression::from_str("file(\"Cargo.toml\")").unwrap();
|
2018-09-29 10:52:38 +01:00
|
|
|
|
|
|
|
|
match result.0.as_slice() {
|
|
|
|
|
[CompoundCondition(_)] => {}
|
|
|
|
|
_ => panic!("Expected an expression with one compound condition"),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn expression_parse_should_handle_multiple_compound_conditions() {
|
2018-10-06 15:08:26 +01:00
|
|
|
let result = Expression::from_str("file(\"Cargo.toml\") or file(\"Cargo.toml\")").unwrap();
|
2018-09-29 10:52:38 +01:00
|
|
|
|
|
|
|
|
match result.0.as_slice() {
|
|
|
|
|
[CompoundCondition(_), CompoundCondition(_)] => {}
|
|
|
|
|
v => panic!(
|
|
|
|
|
"Expected an expression with two compound conditions, got {:?}",
|
|
|
|
|
v
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-01-26 09:44:01 +00:00
|
|
|
#[test]
|
|
|
|
|
fn expression_parse_should_error_if_it_does_not_consume_the_whole_input() {
|
|
|
|
|
let error = Expression::from_str("file(\"Cargo.toml\") foobar").unwrap_err();
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
"The parser did not consume the following input: \" foobar\"",
|
|
|
|
|
error.to_string()
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2018-09-29 10:52:38 +01:00
|
|
|
#[test]
|
|
|
|
|
fn compound_condition_parse_should_handle_a_single_condition() {
|
2018-10-07 10:18:13 +01:00
|
|
|
let result = CompoundCondition::parse("file(\"Cargo.toml\")".into())
|
|
|
|
|
.unwrap()
|
|
|
|
|
.1;
|
2018-09-29 10:52:38 +01:00
|
|
|
|
|
|
|
|
match result.0.as_slice() {
|
|
|
|
|
[Condition::Function(Function::FilePath(f))] => {
|
|
|
|
|
assert_eq!(&PathBuf::from("Cargo.toml"), f)
|
|
|
|
|
}
|
|
|
|
|
v => panic!(
|
|
|
|
|
"Expected an expression with two compound conditions, got {:?}",
|
|
|
|
|
v
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn compound_condition_parse_should_handle_multiple_conditions() {
|
2018-10-07 10:18:13 +01:00
|
|
|
let result =
|
|
|
|
|
CompoundCondition::parse("file(\"Cargo.toml\") and file(\"README.md\")".into())
|
|
|
|
|
.unwrap()
|
|
|
|
|
.1;
|
2018-09-29 10:52:38 +01:00
|
|
|
|
|
|
|
|
match result.0.as_slice() {
|
|
|
|
|
[Condition::Function(Function::FilePath(f1)), Condition::Function(Function::FilePath(f2))] =>
|
|
|
|
|
{
|
|
|
|
|
assert_eq!(&PathBuf::from("Cargo.toml"), f1);
|
|
|
|
|
assert_eq!(&PathBuf::from("README.md"), f2);
|
|
|
|
|
}
|
|
|
|
|
v => panic!(
|
|
|
|
|
"Expected an expression with two compound conditions, got {:?}",
|
|
|
|
|
v
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn condition_parse_should_handle_a_function() {
|
2018-10-07 10:18:13 +01:00
|
|
|
let result = Condition::parse("file(\"Cargo.toml\")".into()).unwrap().1;
|
2018-09-29 10:52:38 +01:00
|
|
|
|
|
|
|
|
match result {
|
|
|
|
|
Condition::Function(Function::FilePath(f)) => {
|
|
|
|
|
assert_eq!(PathBuf::from("Cargo.toml"), f)
|
|
|
|
|
}
|
|
|
|
|
v => panic!(
|
|
|
|
|
"Expected an expression with two compound conditions, got {:?}",
|
|
|
|
|
v
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn condition_parse_should_handle_a_inverted_function() {
|
2018-10-07 10:18:13 +01:00
|
|
|
let result = Condition::parse("not file(\"Cargo.toml\")".into())
|
|
|
|
|
.unwrap()
|
|
|
|
|
.1;
|
2018-09-29 10:52:38 +01:00
|
|
|
|
|
|
|
|
match result {
|
|
|
|
|
Condition::InvertedFunction(Function::FilePath(f)) => {
|
|
|
|
|
assert_eq!(PathBuf::from("Cargo.toml"), f)
|
|
|
|
|
}
|
|
|
|
|
v => panic!(
|
|
|
|
|
"Expected an expression with two compound conditions, got {:?}",
|
|
|
|
|
v
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn condition_parse_should_handle_an_expression_in_parentheses() {
|
2018-10-07 10:18:13 +01:00
|
|
|
let result = Condition::parse("(not file(\"Cargo.toml\"))".into())
|
|
|
|
|
.unwrap()
|
|
|
|
|
.1;
|
2018-09-29 10:52:38 +01:00
|
|
|
|
|
|
|
|
match result {
|
|
|
|
|
Condition::Expression(_) => {}
|
|
|
|
|
v => panic!(
|
|
|
|
|
"Expected an expression with two compound conditions, got {:?}",
|
|
|
|
|
v
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-11-14 20:47:49 +00:00
|
|
|
#[test]
|
|
|
|
|
fn condition_parse_should_handle_an_expression_in_parentheses_with_whitespace() {
|
|
|
|
|
let result = Condition::parse("( not file(\"Cargo.toml\") )".into())
|
|
|
|
|
.unwrap()
|
|
|
|
|
.1;
|
|
|
|
|
|
|
|
|
|
match result {
|
|
|
|
|
Condition::Expression(_) => {}
|
|
|
|
|
v => panic!(
|
|
|
|
|
"Expected an expression with two compound conditions, got {:?}",
|
|
|
|
|
v
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-26 23:10:53 +00:00
|
|
|
#[test]
|
|
|
|
|
fn condition_eval_should_return_function_eval_for_a_function_condition() {
|
2018-10-01 20:44:48 +01:00
|
|
|
let state = state(".");
|
|
|
|
|
|
2018-02-26 23:10:53 +00:00
|
|
|
let condition = Condition::Function(Function::FilePath(PathBuf::from("Cargo.toml")));
|
|
|
|
|
|
2018-10-01 20:44:48 +01:00
|
|
|
assert!(condition.eval(&state).unwrap());
|
2018-02-26 23:10:53 +00:00
|
|
|
|
|
|
|
|
let condition = Condition::Function(Function::FilePath(PathBuf::from("missing")));
|
|
|
|
|
|
2018-10-01 20:44:48 +01:00
|
|
|
assert!(!condition.eval(&state).unwrap());
|
2018-02-26 23:10:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn condition_eval_should_return_expression_eval_for_an_expression_condition() {
|
2018-10-01 20:44:48 +01:00
|
|
|
let state = state(".");
|
|
|
|
|
|
2018-09-29 10:52:38 +01:00
|
|
|
let condition = Condition::Expression(Expression(vec![CompoundCondition(vec![
|
|
|
|
|
Condition::Function(Function::FilePath(PathBuf::from("Cargo.toml"))),
|
|
|
|
|
])]));
|
2018-02-26 23:10:53 +00:00
|
|
|
|
2018-10-01 20:44:48 +01:00
|
|
|
assert!(condition.eval(&state).unwrap());
|
2018-02-26 23:10:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn condition_eval_should_return_inverse_of_function_eval_for_a_not_function_condition() {
|
2018-10-01 20:44:48 +01:00
|
|
|
let state = state(".");
|
|
|
|
|
|
2018-09-29 10:52:38 +01:00
|
|
|
let condition =
|
|
|
|
|
Condition::InvertedFunction(Function::FilePath(PathBuf::from("Cargo.toml")));
|
2018-02-26 23:10:53 +00:00
|
|
|
|
2018-10-01 20:44:48 +01:00
|
|
|
assert!(!condition.eval(&state).unwrap());
|
2018-02-26 23:10:53 +00:00
|
|
|
|
2018-09-29 10:52:38 +01:00
|
|
|
let condition = Condition::InvertedFunction(Function::FilePath(PathBuf::from("missing")));
|
2018-02-26 23:10:53 +00:00
|
|
|
|
2018-10-01 20:44:48 +01:00
|
|
|
assert!(condition.eval(&state).unwrap());
|
2018-02-26 23:10:53 +00:00
|
|
|
}
|
|
|
|
|
|
2018-10-06 10:36:39 +01:00
|
|
|
#[test]
|
|
|
|
|
fn condition_fmt_should_format_function_correctly() {
|
|
|
|
|
let condition = Condition::Function(Function::FilePath(PathBuf::from("Cargo.toml")));
|
|
|
|
|
|
|
|
|
|
assert_eq!("file(\"Cargo.toml\")", &format!("{}", condition));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn condition_fmt_should_format_inverted_function_correctly() {
|
|
|
|
|
let condition =
|
|
|
|
|
Condition::InvertedFunction(Function::FilePath(PathBuf::from("Cargo.toml")));
|
|
|
|
|
|
|
|
|
|
assert_eq!("not file(\"Cargo.toml\")", &format!("{}", condition));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn condition_fmt_should_format_expression_correctly() {
|
|
|
|
|
let condition = Condition::Expression(Expression(vec![CompoundCondition(vec![
|
|
|
|
|
Condition::Function(Function::FilePath(PathBuf::from("Cargo.toml"))),
|
|
|
|
|
])]));
|
|
|
|
|
|
|
|
|
|
assert_eq!("(file(\"Cargo.toml\"))", &format!("{}", condition));
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-26 23:10:53 +00:00
|
|
|
#[test]
|
|
|
|
|
fn compound_condition_eval_should_be_true_if_all_conditions_are_true() {
|
2018-10-01 20:44:48 +01:00
|
|
|
let state = state(".");
|
|
|
|
|
|
2018-02-26 23:10:53 +00:00
|
|
|
let compound_condition = CompoundCondition(vec![
|
|
|
|
|
Condition::Function(Function::FilePath(PathBuf::from("Cargo.toml"))),
|
|
|
|
|
Condition::Function(Function::FilePath(PathBuf::from("Cargo.toml"))),
|
|
|
|
|
]);
|
|
|
|
|
|
2018-10-01 20:44:48 +01:00
|
|
|
assert!(compound_condition.eval(&state).unwrap());
|
2018-02-26 23:10:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn compound_condition_eval_should_be_false_if_any_condition_is_false() {
|
2018-10-01 20:44:48 +01:00
|
|
|
let state = state(".");
|
|
|
|
|
|
2018-02-26 23:10:53 +00:00
|
|
|
let compound_condition = CompoundCondition(vec![
|
|
|
|
|
Condition::Function(Function::FilePath(PathBuf::from("Cargo.toml"))),
|
|
|
|
|
Condition::Function(Function::FilePath(PathBuf::from("missing"))),
|
|
|
|
|
]);
|
|
|
|
|
|
2018-10-01 20:44:48 +01:00
|
|
|
assert!(!compound_condition.eval(&state).unwrap());
|
2018-02-26 23:10:53 +00:00
|
|
|
}
|
|
|
|
|
|
2018-10-06 10:36:39 +01:00
|
|
|
#[test]
|
|
|
|
|
fn compound_condition_fmt_should_format_correctly() {
|
|
|
|
|
let compound_condition = CompoundCondition(vec![
|
|
|
|
|
Condition::Function(Function::FilePath(PathBuf::from("Cargo.toml"))),
|
|
|
|
|
Condition::Function(Function::FilePath(PathBuf::from("missing"))),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
"file(\"Cargo.toml\") and file(\"missing\")",
|
|
|
|
|
&format!("{}", compound_condition)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let compound_condition = CompoundCondition(vec![Condition::Function(Function::FilePath(
|
|
|
|
|
PathBuf::from("Cargo.toml"),
|
|
|
|
|
))]);
|
|
|
|
|
|
|
|
|
|
assert_eq!("file(\"Cargo.toml\")", &format!("{}", compound_condition));
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-26 23:10:53 +00:00
|
|
|
#[test]
|
|
|
|
|
fn expression_eval_should_be_true_if_any_compound_condition_is_true() {
|
2018-10-01 20:44:48 +01:00
|
|
|
let state = state(".");
|
|
|
|
|
|
2018-02-26 23:10:53 +00:00
|
|
|
let expression = Expression(vec![
|
2018-09-29 10:52:38 +01:00
|
|
|
CompoundCondition(vec![Condition::Function(Function::FilePath(
|
|
|
|
|
PathBuf::from("Cargo.toml"),
|
|
|
|
|
))]),
|
|
|
|
|
CompoundCondition(vec![Condition::Function(Function::FilePath(
|
|
|
|
|
PathBuf::from("missing"),
|
|
|
|
|
))]),
|
2018-02-26 23:10:53 +00:00
|
|
|
]);
|
2018-10-01 20:44:48 +01:00
|
|
|
assert!(expression.eval(&state).unwrap());
|
2018-02-26 23:10:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn expression_eval_should_be_false_if_all_compound_conditions_are_false() {
|
2018-10-01 20:44:48 +01:00
|
|
|
let state = state(".");
|
|
|
|
|
|
2018-02-26 23:10:53 +00:00
|
|
|
let expression = Expression(vec![
|
2018-09-29 10:52:38 +01:00
|
|
|
CompoundCondition(vec![Condition::Function(Function::FilePath(
|
|
|
|
|
PathBuf::from("missing"),
|
|
|
|
|
))]),
|
|
|
|
|
CompoundCondition(vec![Condition::Function(Function::FilePath(
|
|
|
|
|
PathBuf::from("missing"),
|
|
|
|
|
))]),
|
2018-02-26 23:10:53 +00:00
|
|
|
]);
|
2018-10-01 20:44:48 +01:00
|
|
|
assert!(!expression.eval(&state).unwrap());
|
2018-02-26 23:10:53 +00:00
|
|
|
}
|
2018-10-06 10:36:39 +01:00
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn expression_fmt_should_format_correctly() {
|
|
|
|
|
let expression = Expression(vec![
|
|
|
|
|
CompoundCondition(vec![Condition::Function(Function::FilePath(
|
|
|
|
|
PathBuf::from("Cargo.toml"),
|
|
|
|
|
))]),
|
|
|
|
|
CompoundCondition(vec![Condition::Function(Function::FilePath(
|
|
|
|
|
PathBuf::from("missing"),
|
|
|
|
|
))]),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
"file(\"Cargo.toml\") or file(\"missing\")",
|
|
|
|
|
&format!("{}", expression)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let expression = Expression(vec![CompoundCondition(vec![Condition::Function(
|
|
|
|
|
Function::FilePath(PathBuf::from("Cargo.toml")),
|
|
|
|
|
)])]);
|
|
|
|
|
|
|
|
|
|
assert_eq!("file(\"Cargo.toml\")", &format!("{}", expression));
|
|
|
|
|
}
|
2018-02-26 23:10:53 +00:00
|
|
|
}
|