use crate::{ReadOnly, ReadableKey, StringKey, WriteableKey};
use bitflags::bitflags;
use std::convert::TryInto;
#[derive(Debug)]
pub struct KeySet {
ptr: std::ptr::NonNull<elektra_sys::KeySet>,
_marker: std::marker::PhantomData<elektra_sys::KeySet>,
}
pub type Cursor = elektra_sys::elektraCursor;
bitflags! {
#[derive(Default)]
pub struct LookupOption: elektra_sys::elektraLookupFlags {
const KDB_O_NONE = elektra_sys::KDB_O_NONE as elektra_sys::elektraLookupFlags;
const KDB_O_POP = elektra_sys::KDB_O_POP as elektra_sys::elektraLookupFlags;
}
}
#[macro_export]
macro_rules! replace_expr {
($_t:expr, $sub:expr) => {
$sub
};
}
#[macro_export]
macro_rules! count_exprs {
($($key:expr),*) => {<[()]>::len(&[$($crate::replace_expr!($key, ())),*])};
}
#[macro_export]
macro_rules! keyset {
() => { KeySet::new() };
($($key:expr),*) => {{
let capacity = $crate::count_exprs!( $($key),*);
let mut keyset = KeySet::with_capacity(capacity);
$(
keyset.append_key($key);
)*
keyset
}};
($($key:expr,)*) => {{
keyset!($($key),*)
}};
}
impl Drop for KeySet {
fn drop(&mut self) {
unsafe {
elektra_sys::ksDel(self.as_ptr());
}
}
}
impl Default for KeySet {
fn default() -> Self {
Self::new()
}
}
impl AsRef<elektra_sys::KeySet> for KeySet {
fn as_ref(&self) -> &elektra_sys::KeySet {
unsafe { self.ptr.as_ref() }
}
}
impl KeySet {
pub fn as_ptr(&mut self) -> *mut elektra_sys::KeySet {
self.ptr.as_ptr()
}
pub fn new() -> Self {
let ks_ptr = unsafe { elektra_sys::ksNew(0, elektra_sys::KS_END) };
unsafe { KeySet::from_ptr(ks_ptr) }
}
pub fn with_capacity(capacity: usize) -> Self {
let ks_ptr = unsafe { elektra_sys::ksNew(capacity, elektra_sys::KS_END) };
unsafe { Self::from_ptr(ks_ptr) }
}
unsafe fn from_ptr(keyset_ptr: *mut elektra_sys::KeySet) -> KeySet {
KeySet {
ptr: std::ptr::NonNull::new(keyset_ptr).unwrap(),
_marker: std::marker::PhantomData,
}
}
pub fn append(&mut self, to_append: &KeySet) -> isize {
unsafe { elektra_sys::ksAppend(self.as_ptr(), to_append.as_ref()) }
}
pub fn append_key<T: WriteableKey>(&mut self, mut key: T) {
let ret_val = unsafe { elektra_sys::ksAppendKey(self.as_ptr(), key.as_ptr()) };
if ret_val == -1 {
panic!("ksAppendKey: Out of memory");
}
}
pub fn duplicate(&self) -> Self {
let ks_ptr = unsafe { elektra_sys::ksDup(self.as_ref()) };
unsafe { Self::from_ptr(ks_ptr) }
}
pub fn copy(&mut self, source: &Self) {
unsafe { elektra_sys::ksCopy(self.as_ptr(), source.as_ref()) };
}
pub fn size(&self) -> usize {
unsafe { elektra_sys::ksGetSize(self.as_ref()).try_into().unwrap() }
}
pub fn rewind(&mut self) {
unsafe {
elektra_sys::ksRewind(self.as_ptr());
}
}
pub fn pop<'a, 'b>(&'a mut self) -> Option<StringKey<'b>> {
let key_ptr = unsafe { elektra_sys::ksPop(self.as_ptr()) };
if key_ptr.is_null() {
None
} else {
Some(unsafe { StringKey::from_ptr(key_ptr) })
}
}
pub fn cut(&mut self, cut_point: &StringKey) -> KeySet {
let ks_ptr = unsafe { elektra_sys::ksCut(self.as_ptr(), cut_point.as_ref()) };
if ks_ptr.is_null() {
KeySet::new()
} else {
unsafe { KeySet::from_ptr(ks_ptr) }
}
}
pub fn head(&self) -> Option<StringKey> {
let key_ptr = unsafe { elektra_sys::ksAtCursor(self.as_ref(), 0) };
if key_ptr.is_null() {
None
} else {
Some(unsafe { StringKey::from_ptr(key_ptr) })
}
}
pub fn current(&self) -> Option<StringKey> {
let key_ptr = unsafe { elektra_sys::ksCurrent(self.as_ref()) };
if key_ptr.is_null() {
None
} else {
Some(unsafe { StringKey::from_ptr(key_ptr) })
}
}
pub fn tail(&self) -> Option<StringKey> {
let key_ptr = unsafe { elektra_sys::ksAtCursor(self.as_ref(), (self.size() - 1).try_into().unwrap()) };
if key_ptr.is_null() {
None
} else {
Some(unsafe { StringKey::from_ptr(key_ptr) })
}
}
pub fn at_cursor(&mut self, cursor: Cursor) -> Option<StringKey> {
let key_ptr = unsafe { elektra_sys::ksAtCursor(self.as_ptr(), cursor) };
if key_ptr.is_null() {
None
} else {
Some(unsafe { StringKey::from_ptr(key_ptr) })
}
}
pub fn lookup(&mut self, mut key: StringKey, options: LookupOption) -> Option<StringKey<'_>> {
let key_ptr = unsafe {
elektra_sys::ksLookup(
self.as_ptr(),
key.as_ptr(),
options.bits() as elektra_sys::elektraLookupFlags,
)
};
if key_ptr.is_null() {
None
} else {
Some(unsafe { StringKey::from_ptr(key_ptr) })
}
}
pub fn lookup_by_name(&mut self, name: &str, options: LookupOption) -> Option<StringKey> {
if let Ok(key) = StringKey::new(name) {
return self.lookup(key, options);
}
None
}
pub fn iter(&self) -> ReadOnlyStringKeyIter<'_> {
ReadOnlyStringKeyIter {
cursor: Some(0),
keyset: self,
}
}
pub fn iter_mut(&mut self) -> StringKeyIter<'_> {
StringKeyIter {
cursor: Some(0),
keyset: self,
}
}
}
impl<'a> std::iter::FromIterator<StringKey<'a>> for KeySet {
fn from_iter<I: IntoIterator<Item = StringKey<'a>>>(iter: I) -> Self {
let mut ks = KeySet::new();
for item in iter {
ks.append_key(item);
}
ks
}
}
impl<'a> Extend<StringKey<'a>> for KeySet {
fn extend<T: IntoIterator<Item = StringKey<'a>>>(&mut self, iter: T) {
for item in iter {
self.append_key(item);
}
}
}
fn next<T: ReadableKey>(cursor: &Option<Cursor>, keyset: &KeySet) -> (Option<Cursor>, Option<T>) {
match cursor {
None => (None, None),
Some(cursor) => {
let key_ptr = unsafe {
elektra_sys::ksAtCursor(
keyset.as_ref() as *const elektra_sys::KeySet as *mut elektra_sys::KeySet,
*cursor,
)
};
if key_ptr.is_null() {
(None, None)
} else {
let new_cursor = Some(cursor + 1);
(new_cursor, Some(unsafe { T::from_ptr(key_ptr) }))
}
}
}
}
pub struct ReadOnlyStringKeyIter<'a> {
cursor: Option<Cursor>,
keyset: &'a KeySet,
}
impl<'a> Iterator for ReadOnlyStringKeyIter<'a> {
type Item = ReadOnly<StringKey<'a>>;
fn next(&mut self) -> Option<Self::Item> {
let (new_cursor, item) = next(&self.cursor, &self.keyset);
self.cursor = new_cursor;
item
}
}
pub struct StringKeyIter<'a> {
cursor: Option<Cursor>,
keyset: &'a mut KeySet,
}
impl<'a> Iterator for StringKeyIter<'a> {
type Item = StringKey<'a>;
fn next(&mut self) -> Option<Self::Item> {
let (new_cursor, item) = next(&self.cursor, &self.keyset);
self.cursor = new_cursor;
item
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{KeyBuilder, KeyNameInvalidError, CopyOption};
use std::iter::FromIterator;
#[test]
fn can_build_simple_keyset() -> Result<(), KeyNameInvalidError> {
let mut ks = KeySet::new();
ks.append_key(
KeyBuilder::<StringKey>::new("user:/sw/org/app/bool")?
.value("true")
.build(),
);
{
let head = ks.head().unwrap();
assert_eq!(head.value(), "true");
}
{
let head = ks.head().unwrap();
assert_eq!(head.value(), "true");
}
Ok(())
}
#[test]
fn can_iterate_simple_keyset() -> Result<(), KeyNameInvalidError> {
let names = ["user:/test/key1", "user:/test/key2", "user:/test/key3"];
let values = ["value1", "value2", "value3"];
let mut ks = KeySet::from_iter(vec![
KeyBuilder::<StringKey>::new(names[0])?
.value(values[0])
.build(),
KeyBuilder::<StringKey>::new(names[1])?
.value(values[1])
.build(),
KeyBuilder::<StringKey>::new(names[2])?
.value(values[2])
.build(),
]);
let new_values = ["Newvalue1", "Newvalue2", "Newvalue3"];
let mut did_iterate = false;
for (i, mut key) in ks.iter_mut().enumerate() {
did_iterate = true;
assert_eq!(key.value(), values[i]);
key.set_value(new_values[i]);
}
assert!(did_iterate);
did_iterate = false;
for (i, key) in ks.iter().enumerate() {
did_iterate = true;
assert_eq!(key.value(), new_values[i]);
assert_eq!(key.name(), names[i]);
}
assert!(did_iterate);
assert_eq!(ks.size(), 3);
Ok(())
}
#[test]
fn can_use_popped_key_after_keyset_freed() {
let popped_key;
{
let key = StringKey::new("user:/key/k2").unwrap();
let mut keyset = KeySet::with_capacity(1);
keyset.append_key(key);
popped_key = keyset.pop().unwrap();
assert_eq!(keyset.size(), 0);
}
assert_eq!(popped_key.basename(), "k2");
}
#[test]
fn pop_on_empty_keyset_returns_none() {
let mut keyset = KeySet::new();
let p = keyset.pop();
assert!(p.is_none());
}
fn setup_keyset() -> KeySet {
let names = ["system:/test/key", "user:/test/key"];
let values = ["value1", "value2"];
KeySet::from_iter(vec![
KeyBuilder::<StringKey>::new(names[0])
.unwrap()
.value(values[0])
.build(),
KeyBuilder::<StringKey>::new(names[1])
.unwrap()
.value(values[1])
.build(),
])
}
#[test]
fn can_duplicate_keyset() {
let ks_dup;
{
let ks = setup_keyset();
ks_dup = ks.duplicate();
}
assert_eq!(ks_dup.size(), 2);
}
#[test]
fn extend_keyset_and_append_are_equal() {
let mut ks = setup_keyset();
let mut ks2 = KeySet::with_capacity(1);
let k = StringKey::new("user:/test/key").unwrap();
ks2.append_key(k);
ks.append(&ks2);
assert_eq!(ks.size(), 2);
assert_eq!(ks.head().unwrap().name(), "user:/test/key");
assert_eq!(ks.head().unwrap().value(), "");
let mut ksext = setup_keyset();
ksext.extend(ks2.iter_mut());
assert_eq!(ksext.size(), 2);
assert_eq!(ksext.head().unwrap().name(), "user:/test/key");
assert_eq!(ksext.head().unwrap().value(), "");
}
#[test]
fn can_lookup_key_with_none_option() {
let mut ks = setup_keyset();
let lookup_key = StringKey::new("/test/key").unwrap();
let ret_val = ks.lookup(lookup_key, LookupOption::KDB_O_NONE);
assert_eq!(ret_val.unwrap().name(), "user:/test/key");
assert_eq!(ks.size(), 2);
assert_eq!(ks.head().unwrap().name(), "user:/test/key");
assert_eq!(ks.tail().unwrap().name(), "system:/test/key");
}
#[test]
fn can_lookup_key_with_pop_option() {
let mut ks = setup_keyset();
let lookup_key = StringKey::new("/test/key").unwrap();
let key = ks.lookup(lookup_key, LookupOption::KDB_O_POP);
assert_eq!(key.unwrap().name(), "user:/test/key");
assert_eq!(ks.size(), 1);
assert_eq!(ks.head().unwrap().name(), "system:/test/key");
}
#[test]
fn can_lookup_by_name_and_duplicate_key() -> Result<(), KeyNameInvalidError> {
let key;
{
let mut ks = setup_keyset();
key = ks
.lookup_by_name("/test/key", LookupOption::KDB_O_NONE)
.unwrap()
.duplicate(CopyOption::KEY_CP_ALL);
assert_eq!(ks.size(), 2);
assert_eq!(ks.head().unwrap().name(), "user:/test/key");
assert_eq!(ks.tail().unwrap().name(), "system:/test/key");
}
assert_eq!(key.name(), "user:/test/key");
Ok(())
}
#[test]
fn test_keyset_macro() {
let ks = keyset![];
assert_eq!(0, ks.size());
let ks = keyset![
StringKey::new("user:/test1").unwrap(),
StringKey::new("user:/test2").unwrap(),
StringKey::new("user:/test3").unwrap(),
StringKey::new("user:/test4").unwrap()
];
assert_eq!(4, ks.size());
assert_eq!("user:/test1", ks.head().unwrap().name());
assert_eq!("user:/test4", ks.tail().unwrap().name());
let ks = keyset![
KeyBuilder::<StringKey>::new("user:/test1")
.unwrap()
.meta("metakey1", "metavalue1")
.unwrap()
.meta("metakey2", "metavalue2")
.unwrap()
.build(),
StringKey::new("user:/test2").unwrap(),
];
assert_eq!(2, ks.size());
assert_eq!("user:/test1", ks.head().unwrap().name());
assert_eq!(
"metavalue2",
ks.head().unwrap().meta("metakey2").unwrap().value()
);
}
}