use anyhow::Result;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::collections::{HashMap, HashSet};
use std::fs::File;
use crate::commands::execution::{Execution, ExecutionResult, FnResult};
use crate::context::PicoContext;
use crate::errors::{PicoError, RuleFileError};
use crate::loader::PicoRuntime as PicoState;
use crate::rules::RuleFile;
use crate::rules::RuleFileRoot;
use crate::values::PicoValue;
#[derive(Debug)]
pub struct IncludeFileDriver {
pub filename: String,
}
impl<'de> Deserialize<'de> for IncludeFileDriver {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
debug!("deserializing.1 ");
let s = String::deserialize(deserializer)?;
debug!("deserializing {:?}", s);
Ok(IncludeFileDriver { filename: s })
}
}
impl Serialize for IncludeFileDriver {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.filename)
}
}
#[derive(Serialize, Deserialize, Debug)]
pub struct IncludeFile {
pub include: IncludeFileDriver,
}
impl Execution for IncludeFile {
fn name(&self) -> String {
format!("include [{:?}]", self.include).to_string()
}
fn run_with_context(&self, state: &mut PicoState, ctx: &mut PicoContext) -> FnResult {
info!("running included module {}", self.include.filename);
let rf_result = state.rulefile_cache.get(&self.include.filename);
match rf_result {
Some(rf) => {
state.put_include_path(&self.include.filename);
let include_result = rf.run_with_context(state, ctx);
state.pop_include_path();
trace!(
"result from included [{}] = {:?}",
&self.include.filename,
include_result
);
include_result
}
None => {
warn!("unusable file XXX");
Err(PicoError::UnusableFile {
filename: String::from(&self.include.filename),
})
}
}
}
}
#[derive(Debug)]
pub enum LoadResult {
Ok,
NotLoaded,
NoSuchFile,
ParseError,
UnknownError,
}
pub type LoadedRuleMap = HashMap<String, LoadedRuleFile>;
#[derive(Debug)]
pub struct LoadedRuleFile {
filename: String,
include_path: Vec<String>,
result: LoadResult,
pub content: Option<RuleFile>,
include_set: HashSet<String>,
}
pub enum RequireModule {
File(FileRequire),
Null(NullRequire),
}
pub struct Require {
url: String,
r: RequireModule,
}
impl Require {
pub fn new(include_reference: &str) -> Require {
trace!("Require [{}]", include_reference);
if include_reference.starts_with("file://") {
let slice = &include_reference[7..]; trace!("slice [{}]", slice);
Self {
url: include_reference.to_string(),
r: RequireModule::File(FileRequire::new(slice)),
}
} else {
Self {
url: include_reference.to_string(),
r: RequireModule::Null(NullRequire::new(&include_reference)),
}
}
}
fn require(&self) -> Result<RuleFile, RuleFileError> {
match &self.r {
RequireModule::File(f) => f.require(),
RequireModule::Null(n) => n.require(),
}
}
}
pub trait Requireer {
fn require(&self) -> Result<RuleFile, RuleFileError>;
}
pub struct NullRequire {
url: String,
}
impl NullRequire {
pub fn new(url: &str) -> Self {
Self {
url: url.to_string(),
}
}
}
impl Requireer for NullRequire {
fn require(&self) -> Result<RuleFile, RuleFileError> {
Err(RuleFileError::Unsuported {
url: self.url.to_string(),
})
}
}
pub struct FileRequire {
filename: String,
}
impl FileRequire {
pub fn new(filename: &str) -> Self {
Self {
filename: filename.to_string(),
}
}
}
impl Requireer for FileRequire {
fn require(&self) -> Result<RuleFile, RuleFileError> {
let opened_file =
File::open(&self.filename).map_err(|source| RuleFileError::ReadError {
source,
filename: self.filename.to_string(),
})?;
let rule_file: RuleFile =
serde_json::from_reader(opened_file).map_err(|source| RuleFileError::ParseError {
source,
filename: self.filename.to_string(),
})?;
Ok(rule_file)
}
}
impl LoadedRuleFile {
pub fn new(filename: &str, parent_path: &[String]) -> Self {
let mut include_path: Vec<String> = parent_path.iter().map(|s| String::from(s)).collect();
include_path.push(String::from(filename));
trace!("LoadedRuleFile::new [{:?}]", include_path);
let mut include_set: HashSet<&str> = HashSet::new();
for i in include_path.iter() {
include_set.insert(i);
}
LoadedRuleFile {
filename: String::from(filename),
include_path,
result: LoadResult::NotLoaded,
content: None,
include_set: HashSet::new(),
}
}
fn included_filenames(&self) -> Vec<String> {
match &self.content {
None => Vec::new(),
Some(content) => {
let included_filenames: Vec<String> = content
.root
.iter()
.filter_map(|v| match v {
RuleFileRoot::IncludeFile(f) => Some(f.include.filename.clone()),
_ => None,
})
.collect();
included_filenames
}
}
}
pub fn require2(&mut self, requireer: Require) -> Result<&Self, RuleFileError> {
let rule_file = requireer.require()?;
self.result = LoadResult::Ok;
self.content = Some(rule_file);
Ok(self)
}
pub fn require(&mut self) -> Result<&Self, RuleFileError> {
let opened_file =
File::open(&self.filename).map_err(|source| RuleFileError::ReadError {
source,
filename: String::from(&self.filename),
})?;
let rule_file: RuleFile =
serde_json::from_reader(opened_file).map_err(|source| RuleFileError::ParseError {
source,
filename: String::from(&self.filename),
})?;
self.result = LoadResult::Ok;
self.content = Some(rule_file);
Ok(self)
}
pub fn loader(mut self, root_cache: &mut LoadedRuleMap) {
match self.require2(Require::new(&self.filename)) {
Ok(loaded) => {
let files_to_require: Vec<LoadedRuleFile> = loaded
.included_filenames()
.iter()
.map(|s| Self::new(s, &loaded.include_path))
.collect();
for file in files_to_require {
file.loader(root_cache);
}
}
Err(e) => {
self.result = LoadResult::UnknownError;
error!("{}", e);
warn!("loader failed {:?}", self);
}
}
root_cache.insert(String::from(&self.filename), self);
}
pub fn run_with_context(&self, state: &mut PicoState, context: &mut PicoContext) -> FnResult {
match self.result {
LoadResult::Ok => {}
_ => {
return Err(PicoError::UnusableFile {
filename: String::from(&self.filename),
})
}
}
match &self.content {
Some(pico_rule) => pico_rule.run_with_context(state, context),
None => Err(PicoError::Crash(String::from("no such rule"))),
}
}
}
pub enum LoaderError {
NoSuchFile,
ParseFailure,
}
pub type RuleFileName = String;