use std::collections::HashMap;
use chrono::NaiveDateTime;
#[derive(Debug, Clone, PartialOrd, PartialEq)]
pub(crate) enum ConfigValue {
Base64Binary(String),
Boolean(bool),
DateTime(NaiveDateTime),
Double(f64),
Int(i32),
Long(i64),
Short(i16),
String(String),
}
impl ConfigValue {}
impl From<&str> for ConfigValue {
fn from(v: &str) -> Self {
ConfigValue::String(v.into())
}
}
impl From<String> for ConfigValue {
fn from(v: String) -> Self {
ConfigValue::String(v)
}
}
impl From<bool> for ConfigValue {
fn from(v: bool) -> Self {
ConfigValue::Boolean(v)
}
}
impl From<NaiveDateTime> for ConfigValue {
fn from(v: NaiveDateTime) -> Self {
ConfigValue::DateTime(v)
}
}
impl From<f64> for ConfigValue {
fn from(v: f64) -> Self {
ConfigValue::Double(v)
}
}
impl From<i16> for ConfigValue {
fn from(v: i16) -> Self {
ConfigValue::Short(v)
}
}
impl From<i32> for ConfigValue {
fn from(v: i32) -> Self {
ConfigValue::Int(v)
}
}
impl From<u32> for ConfigValue {
fn from(v: u32) -> Self {
ConfigValue::Int(v as i32)
}
}
impl From<i64> for ConfigValue {
fn from(v: i64) -> Self {
ConfigValue::Long(v)
}
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct ConfigMap {
key_index: HashMap<String, usize>,
values: Vec<(String, ConfigItem)>,
}
impl ConfigMap {
pub(crate) fn new() -> Self {
Self {
key_index: Default::default(),
values: Default::default(),
}
}
pub(crate) fn iter(&self) -> ConfigIter<'_> {
ConfigIter {
it: Some(self.values.iter()),
}
}
pub(crate) fn insert<S, V>(&mut self, name: S, item: V)
where
S: AsRef<str>,
V: Into<ConfigItem>,
{
let idx = self.key_index.get(name.as_ref());
if let Some(idx) = idx {
self.values.get_mut(*idx).unwrap().1 = item.into();
} else {
self.values.push((name.as_ref().to_string(), item.into()));
self.key_index
.insert(name.as_ref().to_string(), self.values.len() - 1);
}
}
pub(crate) fn get<S>(&self, name: S) -> Option<&ConfigItem>
where
S: AsRef<str>,
{
let idx = self.key_index.get(name.as_ref());
if let Some(idx) = idx {
self.values.get(*idx).map(|v| &v.1)
} else {
None
}
}
pub(crate) fn get_or_create<S, F>(&mut self, name: S, default: F) -> &mut ConfigItem
where
S: AsRef<str>,
F: Fn() -> ConfigItem,
{
let idx = self.key_index.get(name.as_ref());
let idx = if let Some(idx) = idx {
*idx
} else {
self.values.push((name.as_ref().to_string(), default()));
self.key_index
.insert(name.as_ref().to_string(), self.values.len() - 1);
self.values.len() - 1
};
&mut self.values.get_mut(idx).unwrap().1
}
}
pub(crate) struct ConfigIter<'a> {
it: Option<core::slice::Iter<'a, (String, ConfigItem)>>,
}
impl<'a> Iterator for ConfigIter<'a> {
type Item = (&'a String, &'a ConfigItem);
fn next(&mut self) -> Option<Self::Item> {
if let Some(it) = &mut self.it {
it.next().map(|v| (&v.0, &v.1))
} else {
None
}
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum ConfigItemType {
Value,
Set,
Vec,
Map,
Entry,
}
impl From<&ConfigItem> for ConfigItemType {
fn from(item: &ConfigItem) -> Self {
match item {
ConfigItem::Value(_) => ConfigItemType::Value,
ConfigItem::Set(_) => ConfigItemType::Set,
ConfigItem::Vec(_) => ConfigItemType::Vec,
ConfigItem::Map(_) => ConfigItemType::Map,
ConfigItem::Entry(_) => ConfigItemType::Entry,
}
}
}
impl From<&mut ConfigItem> for ConfigItemType {
fn from(item: &mut ConfigItem) -> Self {
match item {
ConfigItem::Value(_) => ConfigItemType::Value,
ConfigItem::Set(_) => ConfigItemType::Set,
ConfigItem::Vec(_) => ConfigItemType::Vec,
ConfigItem::Map(_) => ConfigItemType::Map,
ConfigItem::Entry(_) => ConfigItemType::Entry,
}
}
}
impl PartialEq<ConfigItem> for ConfigItemType {
fn eq(&self, other: &ConfigItem) -> bool {
other == self
}
}
impl PartialEq<ConfigItemType> for ConfigItem {
fn eq(&self, other: &ConfigItemType) -> bool {
match self {
ConfigItem::Value(_) => matches!(other, ConfigItemType::Value),
ConfigItem::Set(_) => matches!(other, ConfigItemType::Set),
ConfigItem::Vec(_) => matches!(other, ConfigItemType::Vec),
ConfigItem::Map(_) => matches!(other, ConfigItemType::Map),
ConfigItem::Entry(_) => matches!(other, ConfigItemType::Entry),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum ConfigItem {
Value(ConfigValue),
Set(ConfigMap),
Vec(ConfigMap),
Map(ConfigMap),
Entry(ConfigMap),
}
impl<T> From<T> for ConfigItem
where
ConfigValue: From<T>,
{
fn from(v: T) -> Self {
ConfigItem::Value(ConfigValue::from(v))
}
}
impl Default for ConfigItem {
fn default() -> Self {
ConfigItem::new_set()
}
}
impl ConfigItem {
pub(crate) fn new(itype: ConfigItemType) -> Self {
match itype {
ConfigItemType::Value => panic!("new with type works only for map-types"),
ConfigItemType::Set => ConfigItem::Set(ConfigMap::new()),
ConfigItemType::Vec => ConfigItem::Vec(ConfigMap::new()),
ConfigItemType::Map => ConfigItem::Map(ConfigMap::new()),
ConfigItemType::Entry => ConfigItem::Entry(ConfigMap::new()),
}
}
pub(crate) fn new_set() -> Self {
Self::Set(ConfigMap::new())
}
pub(crate) fn new_vec() -> Self {
Self::Vec(ConfigMap::new())
}
pub(crate) fn new_map() -> Self {
Self::Map(ConfigMap::new())
}
pub(crate) fn new_entry() -> Self {
Self::Entry(ConfigMap::new())
}
fn as_value(&self) -> Option<&ConfigValue> {
match self {
ConfigItem::Value(v) => Some(v),
ConfigItem::Set(_) => None,
ConfigItem::Vec(_) => None,
ConfigItem::Map(_) => None,
ConfigItem::Entry(_) => None,
}
}
fn is_map(&self) -> bool {
match self {
ConfigItem::Value(_) => false,
ConfigItem::Set(_) => true,
ConfigItem::Vec(_) => true,
ConfigItem::Map(_) => true,
ConfigItem::Entry(_) => true,
}
}
fn as_map(&self) -> Option<&ConfigMap> {
match self {
ConfigItem::Value(_) => None,
ConfigItem::Set(m) => Some(m),
ConfigItem::Vec(m) => Some(m),
ConfigItem::Map(m) => Some(m),
ConfigItem::Entry(m) => Some(m),
}
}
fn as_map_mut(&mut self) -> Option<&mut ConfigMap> {
match self {
ConfigItem::Value(_) => None,
ConfigItem::Set(m) => Some(m),
ConfigItem::Vec(m) => Some(m),
ConfigItem::Map(m) => Some(m),
ConfigItem::Entry(m) => Some(m),
}
}
pub(crate) fn iter(&self) -> ConfigIter<'_> {
if let Some(m) = self.as_map() {
m.iter()
} else {
ConfigIter { it: None }
}
}
pub(crate) fn insert<S, V>(&mut self, name: S, item: V)
where
S: Into<String>,
V: Into<ConfigItem>,
{
if let Some(m) = self.as_map_mut() {
m.insert(name.into(), item.into());
} else {
panic!();
}
}
pub(crate) fn get<S>(&self, name: S) -> Option<&ConfigItem>
where
S: AsRef<str>,
{
if let Some(m) = self.as_map() {
m.get(name.as_ref())
} else {
panic!()
}
}
pub(crate) fn create_path<S>(&mut self, names: &[(S, ConfigItemType)]) -> &mut ConfigItem
where
S: AsRef<str>,
{
if self.is_map() {
if let Some(((name, itype), rest)) = names.split_first() {
let item = self
.as_map_mut()
.unwrap()
.get_or_create(name, || ConfigItem::new(*itype));
if !(item == itype) {
panic!(
"types don't match {:?} <> {:?}",
ConfigItemType::from(item),
itype
);
} else {
item.create_path(rest)
}
} else {
self
}
} else {
panic!("path ends in a value");
}
}
pub(crate) fn get_rec<S>(&self, names: &[S]) -> Option<&ConfigItem>
where
S: AsRef<str>,
{
if let Some(map) = self.as_map() {
if let Some((name, rest)) = names.split_first() {
if let Some(item) = map.get(name.as_ref()) {
item.get_rec(rest)
} else {
None
}
} else {
Some(self)
}
} else {
if names.is_empty() {
Some(self)
} else {
None
}
}
}
pub(crate) fn get_value_rec<S>(&self, names: &[S]) -> Option<&ConfigValue>
where
S: AsRef<str>,
{
if let Some(map) = self.as_map() {
if let Some((name, rest)) = names.split_first() {
if let Some(item) = map.get(name.as_ref()) {
item.get_value_rec(rest)
} else {
None
}
} else {
None
}
} else {
if names.is_empty() {
self.as_value()
} else {
None
}
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct Config {
config: ConfigItem,
}
impl Default for Config {
fn default() -> Self {
Config::new()
}
}
impl Config {
pub(crate) fn new() -> Self {
Self {
config: Default::default(),
}
}
pub(crate) fn iter(&self) -> ConfigIter<'_> {
self.config.iter()
}
pub(crate) fn insert<S, V>(&mut self, name: S, item: V)
where
S: Into<String>,
V: Into<ConfigItem>,
{
self.config.insert(name.into(), item.into());
}
pub(crate) fn get<S>(&self, names: &[S]) -> Option<&ConfigItem>
where
S: AsRef<str>,
{
self.config.get_rec(names)
}
pub(crate) fn get_value<S>(&self, names: &[S]) -> Option<&ConfigValue>
where
S: AsRef<str>,
{
self.config.get_value_rec(names)
}
pub(crate) fn create_path<S>(&mut self, names: &[(S, ConfigItemType)]) -> &mut ConfigItem
where
S: AsRef<str>,
{
self.config.create_path(names)
}
}
#[cfg(test)]
mod tests {
use crate::config::{Config, ConfigItem, ConfigItemType, ConfigMap, ConfigValue};
fn setup_config() -> Config {
let mut config = Config::new();
{
let mut view_settings = ConfigItem::new_set();
view_settings.insert("VisibleAreaTop", 903);
config.insert("ooo:view-settings", view_settings);
}
{
let mut configuration_settings = ConfigItem::new_set();
configuration_settings.insert("HasSheetTabs".to_string(), true);
configuration_settings.insert("ShowNotes", true);
configuration_settings.insert("GridColor", 12632256);
configuration_settings.insert("LinkUpdateMode", 3i16);
configuration_settings.insert(
"PrinterSetup",
ConfigValue::Base64Binary("unknown_garbage".to_string()),
);
{
let mut script_configuration = ConfigItem::new_map();
{
let mut tabelle1 = ConfigItem::new_entry();
tabelle1.insert("CodeName", "Tabelle1");
script_configuration.insert("Tabelle1", tabelle1);
}
configuration_settings.insert("ScriptConfiguration", script_configuration);
}
config.insert("ooo:configuration-settings", configuration_settings);
}
config
}
#[test]
fn test_config() {
let mut config = setup_config();
assert_eq!(config.get_value(&["ooo:view-settings", "ShowNotes"]), None);
assert_eq!(config.get_value(&["ooo:view-settings", "ShowNotes"]), None);
assert_eq!(
config.get_value(&["ooo:view-settings", "VisibleAreaTop"]),
Some(&ConfigValue::Int(903))
);
assert_eq!(
config.get_value(&["ooo:configuration-settings", "ShowNotes"]),
Some(&ConfigValue::Boolean(true))
);
assert_eq!(
config.get_value(&[
"ooo:configuration-settings",
"ScriptConfiguration",
"Tabelle1",
"CodeName"
]),
Some(&ConfigValue::String("Tabelle1".to_string()))
);
let v = config.create_path(&[
("ooo:configuration-settings", ConfigItemType::Set),
("ScriptConfiguration", ConfigItemType::Map),
("Tabelle2", ConfigItemType::Entry),
]);
assert_eq!(v, &ConfigItem::Entry(ConfigMap::new()));
}
#[test]
#[should_panic]
fn test_create_path() {
let mut config = setup_config();
let _v = config.create_path(&[
("ooo:configuration-settings", ConfigItemType::Map), ("ScriptConfiguration", ConfigItemType::Map),
("Tabelle2", ConfigItemType::Entry),
]);
}
#[test]
#[should_panic]
fn test_create_path2() {
let mut config = setup_config();
let _v = config.create_path(&[
("ooo:configuration-settings", ConfigItemType::Set),
("ScriptConfiguration", ConfigItemType::Map),
("Tabelle1", ConfigItemType::Entry),
("CodeName", ConfigItemType::Value), ]);
}
}