#![cfg_attr(feature = "nightly", feature(map_get_key_value))]
extern crate serde;
pub extern crate serde_json;
extern crate regex;
use std::collections::HashMap;
use std::path::Path;
use std::fs::File;
use std::fmt::{self, Display, Formatter};
use regex::Regex;
type Context<'a> = HashMap<String, HashMap<String, Value<'a>>>;
#[derive(Debug)]
pub struct JSONGetTextBuilder<'a> {
default_key: String,
context: Context<'a>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Value<'a> {
Str(&'a str),
JSONValue(serde_json::Value),
JSONValueRef(&'a serde_json::Value),
}
impl<'a> Value<'a> {
pub fn from_str(s: &'a str) -> Value<'a> {
Value::Str(s)
}
pub fn from_string(s: String) -> Value<'a> {
Value::JSONValue(serde_json::Value::String(s))
}
pub fn from_bool(b: bool) -> Value<'a> {
Value::JSONValue(serde_json::Value::Bool(b))
}
pub fn from_i64(n: i64) -> Value<'a> {
Value::JSONValue(serde_json::Value::Number(serde_json::Number::from(serde_json::de::ParserNumber::I64(n))))
}
pub fn from_u64(n: u64) -> Value<'a> {
Value::JSONValue(serde_json::Value::Number(serde_json::Number::from(serde_json::de::ParserNumber::U64(n))))
}
pub fn from_f64(n: f64) -> Value<'a> {
Value::JSONValue(serde_json::Value::Number(serde_json::Number::from(serde_json::de::ParserNumber::F64(n))))
}
pub fn from_json_value(v: serde_json::Value) -> Value<'a> {
Value::JSONValue(v)
}
pub fn from_json_value_ref(v: &'a serde_json::Value) -> Value<'a> {
Value::JSONValueRef(v)
}
pub fn null() -> Value<'a> {
Value::JSONValue(serde_json::Value::Null)
}
pub fn to_json(&self) -> String {
match self {
Value::Str(s) => {
let mut string = String::with_capacity(s.len() + 2);
string.push('"');
let mut from = 0;
for (i, c) in s.char_indices() {
let esc = c.escape_debug();
if esc.len() != 1 {
string.push_str(&s[from..i]);
for c in esc {
string.push(c);
}
from = i + c.len_utf8();
}
}
string.push_str(&s[from..]);
string.push('"');
string
}
Value::JSONValue(v) => {
v.to_string()
}
Value::JSONValueRef(v) => {
v.to_string()
}
}
}
}
impl<'a> serde::ser::Serialize for Value<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer {
match self {
Value::Str(s) => s.serialize(serializer),
Value::JSONValue(v) => v.serialize(serializer),
Value::JSONValueRef(v) => v.serialize(serializer)
}
}
}
impl<'a> PartialEq<Value<'a>> for str {
fn eq(&self, other: &Value) -> bool {
match other {
Value::Str(s) => s.eq(&self),
Value::JSONValue(v) => v.eq(self),
Value::JSONValueRef(v) => v.eq(&self)
}
}
}
impl<'a> PartialEq<Value<'a>> for &'a str {
fn eq(&self, other: &Value) -> bool {
match other {
Value::Str(s) => s.eq(self),
Value::JSONValue(v) => v.eq(self),
Value::JSONValueRef(v) => v.eq(self)
}
}
}
impl<'a> Display for Value<'a> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Value::Str(s) => s.fmt(f),
Value::JSONValue(v) => {
match v.as_str() {
Some(s) => s.fmt(f),
None => v.fmt(f)
}
}
Value::JSONValueRef(v) => {
match v.as_str() {
Some(s) => s.fmt(f),
None => v.fmt(f)
}
}
}
}
}
impl<'a> JSONGetTextBuilder<'a> {
pub fn new(default_key: &str) -> JSONGetTextBuilder {
JSONGetTextBuilder {
default_key: default_key.to_string(),
context: HashMap::new(),
}
}
pub fn add_json_string_to_context(&mut self, key: &str, json: &str) -> Result<&Self, String> {
if self.context.contains_key(key) {
return Err("The key exists.".to_string());
}
let data: HashMap<String, serde_json::Value> = serde_json::from_str(json).map_err(|err| { err.to_string() })?;
let mut map = HashMap::new();
for (k, v) in data {
map.insert(k, Value::JSONValue(v));
}
self.context.insert(key.to_string(), map);
Ok(self)
}
pub fn add_json_bytes_to_context(&mut self, key: &str, json: &[u8]) -> Result<&Self, String> {
if self.context.contains_key(key) {
return Err("The key exists.".to_string());
}
let data: HashMap<String, serde_json::Value> = serde_json::from_slice(json).map_err(|err| { err.to_string() })?;
let mut map = HashMap::new();
for (k, v) in data {
map.insert(k, Value::JSONValue(v));
}
self.context.insert(key.to_string(), map);
Ok(self)
}
pub fn add_json_file_to_context<P: AsRef<Path>>(&mut self, key: &str, path: P) -> Result<&Self, String> {
if self.context.contains_key(key) {
return Err("The key exists.".to_string());
}
let file = File::open(path).map_err(|err| { err.to_string() })?;
let data: HashMap<String, serde_json::Value> = serde_json::from_reader(&file).map_err(|err| { err.to_string() })?;
let mut map = HashMap::new();
for (k, v) in data {
map.insert(k, Value::JSONValue(v));
}
self.context.insert(key.to_string(), map);
Ok(self)
}
pub fn add_map_to_context<P: AsRef<Path>>(&mut self, key: &str, map: HashMap<String, Value<'a>>) -> Result<&Self, String> {
if self.context.contains_key(key) {
return Err("The key exists.".to_string());
}
self.context.insert(key.to_string(), map);
Ok(self)
}
pub fn build(self) -> Result<JSONGetText<'a>, String> {
JSONGetText::from_context_inner(self.default_key, self.context)
}
}
#[derive(Debug)]
pub struct JSONGetText<'a> {
default_key: String,
context: Context<'a>,
}
impl<'a> JSONGetText<'a> {
pub fn build(default_key: &str) -> JSONGetTextBuilder {
JSONGetTextBuilder::new(default_key)
}
pub fn from_context(default_key: &str, context: Context<'a>) -> Result<JSONGetText<'a>, String> {
JSONGetText::from_context_inner(default_key.to_string(), context)
}
fn from_context_inner(default_key: String, mut context: Context<'a>) -> Result<JSONGetText<'a>, String> {
if !context.contains_key(&default_key) {
return Err("Cannot find the default key in the context.".to_string());
}
if !context.contains_key(&default_key) {
return Err("Context should contain the default key.".to_string());
}
let default_map = context.remove(&default_key).unwrap();
let mut inner_context = HashMap::new();
{
for (key, mut map) in context {
{
for map_key in map.keys() {
if !default_map.contains_key(map_key) {
return Err(format! {"The text `{}` in the key `{}` is not in the map of the default key.", map_key, key});
}
}
}
{
for map_key in default_map.keys() {
if !map.contains_key(map_key) {
map.insert(map_key.clone(), default_map.get(map_key).unwrap().clone());
}
}
}
inner_context.insert(key.clone(), map);
}
inner_context.insert(default_key.clone(), default_map);
}
Ok(JSONGetText {
default_key: default_key,
context: inner_context,
})
}
pub fn get_keys(&self) -> Vec<&str> {
let mut vec = Vec::new();
for key in self.context.keys() {
vec.push(key.as_str());
}
vec
}
pub fn get_default_key(&self) -> &str {
&self.default_key
}
pub fn get(&self, key: &str) -> &HashMap<String, Value> {
match self.context.get(key) {
Some(m) => m,
None => self.context.get(&self.default_key).unwrap()
}
}
pub fn get_text(&self, text: &str) -> Option<Value> {
let map = self.context.get(&self.default_key).unwrap();
map.get(text).map(|s| match s {
Value::JSONValue(v) => Value::JSONValueRef(v),
_ => Value::Str("")
})
}
pub fn get_text_with_key(&self, key: &str, text: &str) -> Option<Value> {
let map = match self.context.get(key) {
Some(m) => m,
None => self.context.get(&self.default_key).unwrap()
};
map.get(text).map(|s| match s {
Value::JSONValue(v) => Value::JSONValueRef(v),
_ => Value::Str("")
})
}
#[cfg(feature = "nightly")]
pub fn get_multiple_text(&self, text_array: &[&str]) -> Option<HashMap<&str, Value>> {
let map = self.context.get(&self.default_key).unwrap();
let mut new_map = HashMap::new();
for &text in text_array.iter() {
let (key, value) = map.get_key_value(text)?;
new_map.insert(key.as_str(), Value::JSONValueRef(match value {
Value::JSONValue(v) => v,
_ => return None
}));
}
Some(new_map)
}
#[cfg(feature = "nightly")]
pub fn get_multiple_text_with_key(&self, key: &str, text_array: &[&str]) -> Option<HashMap<&str, Value>> {
let map = match self.context.get(key) {
Some(m) => m,
None => self.context.get(&self.default_key).unwrap()
};
let mut new_map = HashMap::new();
for &text in text_array.iter() {
let (key, value) = map.get_key_value(text)?;
new_map.insert(key.as_str(), Value::JSONValueRef(match value {
Value::JSONValue(v) => v,
_ => return None
}));
}
Some(new_map)
}
pub fn get_filtered_text(&self, regex: &Regex) -> Option<HashMap<&str, Value>> {
let map = self.context.get(&self.default_key).unwrap();
let mut new_map = HashMap::new();
for (key, value) in map.iter() {
if !regex.is_match(key) {
continue;
}
new_map.insert(key.as_str(), Value::JSONValueRef(match value {
Value::JSONValue(v) => v,
_ => return None
}));
}
Some(new_map)
}
pub fn get_filtered_text_with_key(&self, key: &str, regex: &Regex) -> Option<HashMap<&str, Value>> {
let map = match self.context.get(key) {
Some(m) => m,
None => self.context.get(&self.default_key).unwrap()
};
let mut new_map = HashMap::new();
for (key, value) in map.iter() {
if !regex.is_match(key) {
continue;
}
new_map.insert(key.as_str(), Value::JSONValueRef(match value {
Value::JSONValue(v) => v,
_ => return None
}));
}
Some(new_map)
}
}
#[macro_export]
macro_rules! static_json_gettext_build {
( $default_key:expr, $($key:expr, $path:expr), * ) => {
{
use ::json_gettext::JSONGetText;
let mut builder = JSONGetText::build($default_key);
lazy_static_include_bytes_vec!(DATA $(, $path)* );
let mut p = 0usize;
$(
{
let data = DATA[p];
p += 1;
builder.add_json_bytes_to_context($key, data).unwrap();
}
)*
builder.build()
}
};
}
#[macro_export]
macro_rules! get_text {
( $ctx:ident, $text:expr ) => {
{
$ctx.get_text($text)
}
};
( $ctx:ident, $key:expr, $text:expr ) => {
{
$ctx.get_text_with_key($key, $text)
}
};
( $ctx:ident, $key:expr, $text:expr, $($text_array:expr), + ) => {
{
let mut text_array = vec![$text];
$(
{
text_array.push($text_array);
}
)*
$ctx.get_multiple_text_with_key($key, &text_array)
}
};
}