use super::{Error, Result};
use indexmap::IndexMap;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::{collections::HashSet, fmt};
const PLACEHOLDERS: &str = "placeholders";
#[derive(Debug, Serialize, Deserialize)]
pub struct FileDiff {
pub create: HashSet<String>,
pub delete: HashSet<String>,
pub update: HashSet<String>,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct ArbFile {
#[serde(flatten)]
pub(crate) contents: IndexMap<String, Value>,
}
impl ArbFile {
pub fn len(&self) -> usize {
self.contents.len()
}
pub fn is_empty(&self) -> bool {
self.contents.is_empty()
}
pub fn entries(&self) -> Vec<ArbEntry<'_>> {
self.contents
.iter()
.map(|(k, v)| ArbEntry(ArbKey(k), ArbValue(v)))
.collect()
}
pub fn lookup<'a>(&'a self, key: &'a str) -> Option<ArbEntry<'a>> {
self.contents
.get(key)
.map(|v| ArbEntry(ArbKey(key), ArbValue(v)))
}
pub fn insert_translation<'a>(&mut self, key: &ArbKey<'a>, text: String) {
self.contents.insert(key.to_string(), Value::String(text));
}
pub fn shift_insert_translation<'a>(&mut self, index: usize, key: &ArbKey<'a>, text: String) {
self.contents
.shift_insert(index, key.to_string(), Value::String(text));
}
pub fn insert_entry<'a>(&mut self, entry: ArbEntry<'a>) {
self.contents
.insert(entry.key().to_string(), entry.value().into());
}
pub fn remove(&mut self, key: &str) -> Option<Value> {
self.contents.shift_remove(key)
}
pub fn placeholders<'a>(&self, key: &ArbKey<'a>) -> Result<Option<Placeholders<'_>>> {
if key.as_ref().starts_with('@') {
return Err(Error::AlreadyPrefixed(key.to_string()));
}
let meta_key = format!("@{}", key.as_ref());
if let Some(value) = self.contents.get(&meta_key) {
if let Value::Object(map) = value {
if let Some(Value::Object(placeholders)) = map.get(PLACEHOLDERS) {
let keys = placeholders.keys().map(|k| &k[..]).collect::<Vec<_>>();
Ok(Some(Placeholders::new(keys)))
} else {
Ok(None)
}
} else {
Ok(None)
}
} else {
Ok(None)
}
}
pub fn diff<'a>(&'a self, other: &'a ArbFile, cache: Option<&'a ArbFile>) -> FileDiff {
let lhs = self.contents.keys().collect::<HashSet<_>>();
let rhs = other.contents.keys().collect::<HashSet<_>>();
let create = lhs
.difference(&rhs)
.map(|s| s.to_string())
.collect::<HashSet<_>>();
let delete = rhs
.difference(&lhs)
.map(|s| s.to_string())
.collect::<HashSet<_>>();
let mut update = HashSet::new();
if let Some(cache) = cache {
for entry in cache.entries() {
if let (Some(current), Some(cached)) = (
self.contents.get(entry.key().as_ref()),
cache.contents.get(entry.key().as_ref()),
) {
if current != cached {
update.insert(entry.key().as_ref().to_string());
}
}
}
}
FileDiff {
create,
delete,
update,
}
}
}
#[derive(Debug, Clone)]
pub struct ArbEntry<'a>(ArbKey<'a>, ArbValue<'a>);
impl<'a> ArbEntry<'a> {
pub fn new(key: &'a str, value: &'a Value) -> Self {
Self(ArbKey::new(key), ArbValue::new(value))
}
pub fn key(&self) -> &ArbKey<'a> {
&self.0
}
pub fn value(&self) -> &ArbValue<'a> {
&self.1
}
pub fn is_translatable(&self) -> bool {
self.0.is_translatable() && self.1.is_translatable()
}
}
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct ArbKey<'a>(&'a str);
impl<'a> ArbKey<'a> {
pub fn new(key: &'a str) -> Self {
Self(key)
}
pub fn is_prefixed(&self) -> bool {
self.0.starts_with('@')
}
fn is_translatable(&self) -> bool {
!self.is_prefixed()
}
}
impl<'a> AsRef<str> for ArbKey<'a> {
fn as_ref(&self) -> &str {
self.0
}
}
impl<'a> fmt::Display for ArbKey<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct ArbValue<'a>(&'a Value);
impl<'a> ArbValue<'a> {
pub fn new(value: &'a Value) -> Self {
Self(value)
}
pub fn as_str(&self) -> Option<&str> {
if let Value::String(val) = self.0 {
Some(val)
} else {
None
}
}
fn is_translatable(&self) -> bool {
matches!(self.0, Value::String(_))
}
}
impl<'a> From<&ArbValue<'a>> for Value {
fn from(value: &ArbValue<'a>) -> Self {
value.0.clone()
}
}
impl<'a> From<&'a Value> for ArbValue<'a> {
fn from(value: &'a Value) -> Self {
ArbValue(value)
}
}
#[derive(Debug)]
pub struct Placeholders<'a>(Vec<&'a str>);
impl<'a> Placeholders<'a> {
pub fn new(names: Vec<&'a str>) -> Self {
Self(names)
}
pub fn names(&self) -> &[&'a str] {
self.0.as_slice()
}
pub fn to_vec(&self) -> Vec<&'a str> {
self.0.clone()
}
pub fn verify(&self, source: &str) -> Result<()> {
for name in &self.0 {
let needle = format!("{{{}}}", name);
if !source.contains(&*needle) {
return Err(Error::PlaceholderNotDefined(
name.to_string(),
source.to_string(),
));
}
}
Ok(())
}
}