use std::sync::Arc;
use babel47::LangString;
use language_tags::LanguageTag;
use minijinja::{
State, Value, context,
value::{Enumerator, Object, ObjectRepr},
};
use taganak_core::{
graphs::{Graph, GraphView},
terms::Term,
};
use taganak_framework::prelude::{StreamExt, TryStreamExt};
use taganak_orm::re::GraphError;
use tokio::runtime::Handle;
use tracing::{error, trace};
use crate::{
Params, ParamsWithEnv,
env::environment,
res::{str_to_term, term_to_value},
};
#[derive(Debug, Clone)]
pub struct GraphObject<'e, G> {
params: ParamsWithEnv<'e, G>,
}
impl<G> GraphObject<'static, G>
where
G: Graph + std::fmt::Debug + Sync + 'static,
{
pub fn new(mut params: Params<'static, G>) -> Self {
if params.environment.is_none() {
let param_clone = params.clone().environment(None);
let env = environment(param_clone, None);
params = params.environment(Some(env));
}
trace!(?params.base, "GraphObject for");
let params = params.to_with_env().unwrap();
Self { params }
}
pub fn into_rendered(self) -> Result<String, minijinja::Error>
where
Self: 'static,
{
let env = self.params.environment.clone();
let guard = env.read().unwrap();
let tmpl = guard
.get_template(&self.params.base.as_ref().unwrap().to_string())
.unwrap();
tmpl.render(context!(
this => Value::from_object(self),
))
}
}
impl<G> Object for GraphObject<'_, G>
where
G: Graph + std::fmt::Debug + Sync + 'static,
Self: 'static,
{
fn get_value(self: &Arc<Self>, key: &Value) -> Option<Value> {
trace!(
"Getting attribute {} on {}",
key,
self.params.base.as_ref().unwrap()
);
let predicate: Option<Term> =
str_to_term(self.params.clone().to_params(), &key.to_string()).ok();
if predicate.is_none() {
trace!(?key, "Requested attribute is not a term");
match key.as_str() {
Some("subject") => {
return Some(Value::from(self.params.base.as_ref().unwrap().to_string()));
}
Some("trans") => {
return Some(Value::from_object(TransFn::new(self.params.clone())));
}
Some("objects") => {
return Some(Value::from_object(ObjectsFn::new(self.params.clone())));
}
_ => return None,
}
};
let predicate = predicate.expect("we just checked");
trace!(?self.params.base, ?predicate, "resolving predicate");
let handle = Handle::current();
handle
.block_on(async {
let graph = self.params.graph.read().unwrap();
graph
.view()
.await
.object(Some(self.params.base.as_ref().unwrap()), Some(&predicate))
.await
})
.ok()?
.and_then(|t| super::term_to_value(self.params.clone(), &t))
}
fn enumerate(self: &Arc<Self>) -> minijinja::value::Enumerator {
trace!(?self.params.base, "Enumerating predicates");
let handle = Handle::current();
let predicates: Vec<Result<String, GraphError>> = handle.block_on(async {
let graph = self.params.graph.read().unwrap();
graph
.view()
.await
.predicates(Some(self.params.base.as_ref().unwrap()), None, None)
.await
.unwrap()
.map_ok(|t| t.to_string())
.collect()
.await
});
trace!(?self.params.base, ?predicates, "found predicates");
let mut values = Vec::new();
for res in predicates {
if let Ok(predicate) = res {
values.push(Value::from(predicate));
} else {
error!(?self.params.base, "Could not iterate ovee");
return Enumerator::NonEnumerable;
}
}
Enumerator::Values(values)
}
#[track_caller]
fn render(self: &Arc<Self>, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
where
Self: Sized + 'static,
{
tracing::trace!(caller = %core::panic::Location::caller(),?self.params.base,"render called");
let globals = self
.params
.environment
.read()
.unwrap()
.globals()
.map(|(n, _)| n)
.fold(String::new(), |mut acc, n| {
acc.push_str(n);
acc.push_str(", ");
acc
});
let read_lock = self.params.environment.read().unwrap();
let tmpl = read_lock
.get_template(&self.params.base.as_ref().unwrap().to_string())
.unwrap();
let self_clone = GraphObject {
params: self.params.clone(),
};
let rendered = tmpl
.render(context!(
this => Value::from_object(self_clone),
))
.unwrap();
write!(f, "{}", rendered)
}
}
#[derive(Debug, Clone)]
pub struct TransFn<'e, G> {
params: ParamsWithEnv<'e, G>,
}
impl<'e, G> TransFn<'e, G>
where
G: Graph + std::fmt::Debug + Sync + 'static,
{
pub fn new(params: ParamsWithEnv<'e, G>) -> Self {
Self { params }
}
}
impl<G> Object for TransFn<'_, G>
where
G: Graph + std::fmt::Debug + Sync + 'static,
Self: 'static,
{
fn call(
self: &Arc<Self>,
_state: &State<'_, '_>,
args: &[Value],
) -> Result<Value, minijinja::Error> {
if args.is_empty() {
return Err(minijinja::Error::new(
minijinja::ErrorKind::MissingArgument,
"needs predicate",
));
}
if args.len() > 2 {
return Err(minijinja::Error::new(
minijinja::ErrorKind::TooManyArguments,
"too many arguments",
));
}
let language: Option<LanguageTag> = if args.len() == 2 {
Some(args[1].as_str().unwrap().parse().unwrap())
} else {
self.params.language.clone()
};
let handle = Handle::current();
handle.block_on(async {
let predicate =
str_to_term(self.params.clone().to_params(), &args[0].to_string()).unwrap();
trace!(
"Called trans on {} for predicate {}",
self.params.base.as_ref().unwrap(),
predicate
);
let lang_string = {
let graph = self.params.graph.read().unwrap();
let view = graph.view().await;
LangString::from_graph(view, self.params.base.as_ref().unwrap(), &predicate)
.await
.unwrap()
};
Ok(Value::from(lang_string.get(language.as_ref())))
})
}
}
#[derive(Debug, Clone)]
pub struct ObjectsFn<'e, G> {
params: ParamsWithEnv<'e, G>,
}
impl<'a, G> ObjectsFn<'a, G> {
pub fn new(params: ParamsWithEnv<'a, G>) -> Self {
Self { params }
}
}
impl<G> Object for ObjectsFn<'_, G>
where
G: Graph + std::fmt::Debug + Sync + 'static,
Self: 'static,
{
fn call(
self: &Arc<Self>,
state: &State<'_, '_>,
args: &[Value],
) -> Result<Value, minijinja::Error> {
if args.is_empty() {
return Err(minijinja::Error::new(
minijinja::ErrorKind::MissingArgument,
"needs a predicate",
));
}
if args.len() > 1 {
return Err(minijinja::Error::new(
minijinja::ErrorKind::TooManyArguments,
"too many arguments",
));
}
trace!(
"called objects on {} for predicate {}",
self.params.base.as_ref().unwrap(),
&args[0].to_string()
);
let handle = Handle::current();
handle.block_on(async {
let predicate =
str_to_term(self.params.clone().to_params(), &args[0].to_string()).unwrap();
let graph = self.params.graph.read().unwrap();
const LIMIT: Option<usize> = Some(256);
let terms: Vec<Arc<Term>> = graph
.view()
.await
.objects(
Some(self.params.base.as_ref().unwrap()),
Some(&predicate),
LIMIT,
)
.await
.unwrap()
.try_collect()
.await
.unwrap();
tracing::trace!(?terms, "found terms");
Ok(Value::from_object(TermList::new(
self.params.clone(),
terms,
)))
})
}
}
#[derive(Debug, Clone)]
pub(crate) struct TermList<'e, G> {
params: ParamsWithEnv<'e, G>,
terms: Vec<Arc<Term>>,
}
impl<'e, G> TermList<'e, G>
where
G: Graph + std::fmt::Debug + Sync + 'static,
{
pub(crate) fn new(params: ParamsWithEnv<'e, G>, terms: Vec<Arc<Term>>) -> Self {
trace!(?params.graph, ?params.base, ?terms, "TermList for, containing terms");
Self { params, terms }
}
}
impl<G> Object for TermList<'static, G>
where
G: Graph + std::fmt::Debug + Sync + 'static,
Self: 'static,
{
fn repr(self: &Arc<Self>) -> ObjectRepr {
ObjectRepr::Iterable
}
fn enumerate(self: &Arc<Self>) -> Enumerator {
trace!(?self.params.base, "starting iteration from");
let self_clone = self.clone();
let terms = self_clone.terms.clone();
let params = self_clone.params.clone();
Enumerator::Iter(Box::new(
terms
.into_iter()
.map(move |t| term_to_value(params.clone(), &t).unwrap()),
))
}
}