Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

no recursion depth const generic #256

Merged
merged 43 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
aae80da
pad: add
jordens Oct 21, 2024
50fca7c
leaf: add
jordens Oct 21, 2024
6655e03
[wip] use leaf marker newtypes instead of trait generic
jordens Oct 21, 2024
23aeca3
keys finish
jordens Oct 21, 2024
77cd719
[wip] docs
jordens Oct 21, 2024
8203433
[wip] tuple
jordens Oct 22, 2024
1fbe3b4
tuple/option
jordens Oct 22, 2024
e9593b4
key next error simplification
jordens Oct 22, 2024
8b435e9
finish/finalize
jordens Oct 22, 2024
a3e3efc
remove redundant checks
jordens Oct 22, 2024
1368a33
remove impl for &{mut,} T
jordens Oct 22, 2024
64a8b27
hack generics
jordens Oct 22, 2024
d8779e9
finish lifetimes and generics bounds
jordens Oct 23, 2024
c5b50ca
[wip] enum work
jordens Oct 23, 2024
0b1ecd0
remove some old recursion depth mentions
jordens Oct 23, 2024
84ae6f8
update embedded example
jordens Oct 23, 2024
1d3d186
Revert "remove impl for &{mut,} T"
jordens Oct 23, 2024
3f79f0b
add more blanket tree impls
jordens Oct 23, 2024
68f0cb2
un-hack enum switching as ByName
jordens Oct 23, 2024
1f5bea4
adapt validate etc
jordens Oct 23, 2024
b4fe631
update changelog, revert paths test change
jordens Oct 23, 2024
237b1ce
TreeAny for ByName: reject at runtime
jordens Oct 23, 2024
7c19eec
docs
jordens Oct 23, 2024
6e900f9
fix bounds compiletest
jordens Oct 23, 2024
dc0e951
fix skip error message
jordens Oct 23, 2024
976d680
fix compiletest
jordens Oct 23, 2024
8388898
rm non-ui trait bound test
jordens Oct 23, 2024
b06d858
ByName -> StrLeaf
jordens Oct 23, 2024
b0e35d9
merge impls
jordens Oct 23, 2024
9ec907e
less inline, fmt
jordens Oct 24, 2024
0c97a0f
derive: add via attribute
jordens Oct 24, 2024
aa458e8
disallow internal nodes without leaves
jordens Oct 24, 2024
c6b1b03
via->defer
jordens Oct 24, 2024
4b89d33
KeyLookup: NonZeroUsize len
jordens Oct 24, 2024
87cc615
NonZeroUsize -> NonZero<usize>
jordens Oct 24, 2024
3f0c44d
spurious try_into.unwrap
jordens Oct 24, 2024
c66e601
changelog, assrrt tweaks
jordens Oct 24, 2024
a3294a3
be explicit in darling support
jordens Oct 24, 2024
9749065
docs
jordens Oct 24, 2024
489047d
add max_bits to Metadata
jordens Oct 24, 2024
76fce8e
update packed test
jordens Oct 24, 2024
a367b4e
docs
jordens Oct 24, 2024
023bd2a
changelog
jordens Oct 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,39 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased](https://github.com/quartiq/miniconf/compare/v0.16.3...HEAD) - DATE

### Changed

* The "recursion depth" const generic of the `Tree*` traits has been removed in favor of
explicit newtypes implementing the traits for leaf values `Leaf<T>` and `StrLeaf<T>`.
This reduces code duplication, macro usage and complexity.
It enables any recursion depth, does away with most manual tracking of recursion depth
in proc macro attributes, and simplifies code and derive macros, at the expense of having
to wrap leaves in newtypes and having to pass an indices length to `TreeKey::nodes()`.
* `TreeKey::nodes` requires the indices length as a const generic.
* `get`, `get_mut`, `validate` proc macro attributes are now `Expr`
* `Key::find` and `Keys::finalize` return a `Result`, not an `Option` to reduce code duplication
* Derive macro lifetime and type param trait bound heuristics have been improved.
They should now yield the correct result in mpst cases.
* Internal nodes must always have at least one leaf. Trait impls for `[T; 0]` and `()`
have been removed. The `len` argument to the `traverse_by_key` closure is now a
`NonZero<usize>`.

### Added

* `Leaf` to explicitly manage `Serialize/Deserialize` leaf values.
* `StrLeaf` to manage values via `AsRef<str>`/`TryFrom<&str>` (e.g. Enums via `strum`)
* `Tree*` impls for heterogeneous inline tuples `(T0, T1, ...)` up to length 8 (also useful
for enum variants)
* `impl Tree* for &{mut,} T where T: Tree*` blanket impls to simplify usage downstream
* `defer` derive attribute to quickly defer to a downstream field without having to write accessors
* `Metadata` now also computes maximum `Packed` bits usage

### Removed

* `TreeSerialize`/`TreeDeserialize`/`TreeAny` don't need `TreeKey`

## [0.16.3](https://github.com/quartiq/miniconf/compare/v0.16.2...v0.16.3) - 2024-10-20

### Added
Expand Down
49 changes: 20 additions & 29 deletions miniconf/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,58 +19,51 @@ providers are supported.

```rust
use serde::{Deserialize, Serialize};
use miniconf::{Error, json, JsonPath, Traversal, Tree, TreeKey, Path, Packed, Node};
use miniconf::{Error, json, JsonPath, Traversal, Tree, TreeKey, Path, Packed, Node, Leaf};

#[derive(Deserialize, Serialize, Default, Tree)]
enum Either {
pub enum Either {
#[default]
Bad,
Good,
A(i32),
B(#[tree(depth=1)] Inner),
C(#[tree(depth=2)] [Inner; 2]),
A(Leaf<i32>),
B(Inner),
C([Inner; 2]),
}

#[derive(Deserialize, Serialize, Default, Tree)]
struct Inner {
a: i32,
b: i32,
pub struct Inner {
a: Leaf<i32>,
b: Leaf<i32>,
}

#[derive(Tree, Default)]
struct Settings {
foo: bool,
enum_: Either,
struct_: Inner,
array: [i32; 2],
option: Option<i32>,
pub struct Settings {
foo: Leaf<bool>,
enum_: Leaf<Either>,
struct_: Leaf<Inner>,
array: Leaf<[i32; 2]>,
option: Leaf<Option<i32>>,

#[tree(skip)]
#[allow(unused)]
skipped: (),

#[tree(depth=1)]
struct_tree: Inner,
#[tree(depth=3)]
enum_tree: Either,
#[tree(depth=1)]
array_tree: [i32; 2],
#[tree(depth=2)]
array_tree: [Leaf<i32>; 2],
array_tree2: [Inner; 2],

#[tree(depth=1)]
option_tree: Option<i32>,
#[tree(depth=2)]
option_tree: Option<Leaf<i32>>,
option_tree2: Option<Inner>,
#[tree(depth=3)]
array_option_tree: [Option<Inner>; 2],
}

let mut settings = Settings::default();

// Atomic updates by field name
json::set(&mut settings,"/foo", b"true")?;
assert_eq!(settings.foo, true);
assert_eq!(*settings.foo, true);
json::set(&mut settings, "/enum_", br#""Good""#)?;
json::set(&mut settings, "/struct_", br#"{"a": 3, "b": 3}"#)?;
json::set(&mut settings, "/array", b"[6, 6]")?;
Expand All @@ -96,7 +89,7 @@ json::set_by_key(&mut settings, &JsonPath(".array_tree2[1].b"), b"10")?;

// Hiding paths by setting an Option to `None` at runtime
assert_eq!(json::set(&mut settings, "/option_tree", b"13"), Err(Traversal::Absent(1).into()));
settings.option_tree = Some(0);
settings.option_tree = Some(0.into());
json::set(&mut settings, "/option_tree", b"13")?;
// Hiding a path and descending into the inner `Tree`
settings.option_tree2 = Some(Inner::default());
Expand All @@ -112,7 +105,7 @@ let len = json::get(&settings, "/struct_", &mut buf).unwrap();
assert_eq!(&buf[..len], br#"{"a":3,"b":3}"#);

// Iterating over all paths
for path in Settings::nodes::<Path<heapless::String<32>, '/'>>() {
for path in Settings::nodes::<Path<heapless::String<32>, '/'>, 4>() {
let (path, node) = path.unwrap();
assert!(node.is_leaf());
// Serialize each
Expand Down Expand Up @@ -173,8 +166,6 @@ Leaf fields/items need to support the respective [`serde`] (and the desired `ser
backend) or [`core::any`] trait.

Structs, enums, arrays, and Options can then be cascaded to construct more complex trees.
When using the derive macro, the behavior and tree recursion depth can be configured for each
struct field using the `#[tree(depth(Y))]` attribute.

See also the [`TreeKey`] trait documentation for details.

Expand All @@ -191,7 +182,7 @@ It implements [`Keys`].

## Limitations

* `enum`: The derive macros don't support enums with record (named fields) variants or tuple variants with more than one field. Only unit, newtype and skipped variants are supported. Without the derive macros, these `enums` are still however usable in their atomic `serde` form as leaf nodes.
* `enum`: The derive macros don't support enums with record (named fields) variants or tuple variants with more than one field. Only unit, newtype and skipped variants are supported. Without the derive macros, these `enums` are still however usable in their atomic `serde` form as leaf nodes. Inline tuple variants are supported.
* The derive macros don't handle `std`/`alloc` smart pointers ( `Box`, `Rc`, `Arc`) in any special way. They however still be handled with accessors (`get`, `get_mut`, `validate`).
* The derive macros only support flattening in non-ambiguous situations (single field structs and single variant enums, both modulo skipped fields/variants and unit variants).

Expand Down
2 changes: 1 addition & 1 deletion miniconf/examples/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ fn main() -> anyhow::Result<()> {

// Dump settings
let mut buf = vec![0; 1024];
for (key, _node) in Settings::nodes::<Path<String, '-'>>().map(Result::unwrap) {
for (key, _node) in Settings::nodes::<Path<String, '-'>, 4>().map(Result::unwrap) {
match json::get_by_key(&settings, &key, &mut buf[..]) {
Ok(len) => {
println!(
Expand Down
35 changes: 14 additions & 21 deletions miniconf/examples/common.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use miniconf::Tree;
use miniconf::{Leaf, Tree};
use serde::{Deserialize, Serialize};

// Either/Inner/Settings are straight from README.md
Expand All @@ -8,50 +8,43 @@ pub enum Either {
#[default]
Bad,
Good,
A(i32),
B(#[tree(depth = 1)] Inner),
C(#[tree(depth = 2)] [Inner; 2]),
A(Leaf<i32>),
B(Inner),
C([Inner; 2]),
}

#[derive(Deserialize, Serialize, Default, Tree)]
pub struct Inner {
a: i32,
b: i32,
a: Leaf<i32>,
b: Leaf<i32>,
}

#[derive(Tree, Default)]
pub struct Settings {
foo: bool,
enum_: Either,
struct_: Inner,
array: [i32; 2],
option: Option<i32>,
foo: Leaf<bool>,
enum_: Leaf<Either>,
struct_: Leaf<Inner>,
array: Leaf<[i32; 2]>,
option: Leaf<Option<i32>>,

#[tree(skip)]
#[allow(unused)]
skipped: (),

#[tree(depth = 1)]
struct_tree: Inner,
#[tree(depth = 3)]
enum_tree: Either,
#[tree(depth = 1)]
array_tree: [i32; 2],
#[tree(depth = 2)]
array_tree: [Leaf<i32>; 2],
array_tree2: [Inner; 2],

#[tree(depth = 1)]
option_tree: Option<i32>,
#[tree(depth = 2)]
option_tree: Option<Leaf<i32>>,
option_tree2: Option<Inner>,
#[tree(depth = 3)]
array_option_tree: [Option<Inner>; 2],
}

impl Settings {
/// Fill some of the Options
pub fn enable(&mut self) {
self.option_tree = Some(8);
self.option_tree = Some(8.into());
self.enum_tree = Either::B(Default::default());
self.option_tree2 = Some(Default::default());
self.array_option_tree[1] = Some(Default::default());
Expand Down
24 changes: 12 additions & 12 deletions miniconf/examples/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,23 +61,23 @@ impl<I> From<usize> for Error<I> {
pub const SEPARATOR: char = '/';

#[derive(Debug, PartialEq, PartialOrd)]
pub struct Menu<M, const Y: usize, const D: usize = Y> {
pub struct Menu<M, const D: usize> {
key: Packed,
_m: PhantomData<M>,
}

impl<M, const Y: usize> Default for Menu<M, Y>
impl<M, const D: usize> Default for Menu<M, D>
where
M: TreeKey<Y> + TreeSerialize<Y> + TreeDeserializeOwned<Y> + Default,
M: TreeKey + TreeSerialize + TreeDeserializeOwned + Default,
{
fn default() -> Self {
Self::new(Packed::default())
}
}

impl<M, const Y: usize> Menu<M, Y>
impl<M, const D: usize> Menu<M, D>
where
M: TreeKey<Y> + TreeSerialize<Y> + TreeDeserializeOwned<Y> + Default,
M: TreeKey + TreeSerialize + TreeDeserializeOwned + Default,
{
pub fn new(key: Packed) -> Self {
Self {
Expand All @@ -92,7 +92,7 @@ where
}

fn pop(&self, levels: usize) -> Result<(Self, Node), Traversal> {
let (idx, node): (Indices<[_; Y]>, _) = M::transcode(self.key)?;
let (idx, node): (Indices<[_; D]>, _) = M::transcode(self.key)?;
if let Some(idx) = idx.get(
..node
.depth()
Expand Down Expand Up @@ -121,7 +121,7 @@ where
pub fn list<S: core::fmt::Write + Default>(
&self,
) -> Result<impl Iterator<Item = Result<S, usize>>, Traversal> {
Ok(M::nodes::<Path<S, SEPARATOR>>()
Ok(M::nodes::<Path<S, SEPARATOR>, D>()
.root(self.key)?
.map(|pn| pn.map(|(p, _n)| p.into_inner())))
}
Expand All @@ -148,7 +148,7 @@ where
buf: &mut [u8],
) -> Result<(), miniconf::Error<::postcard::Error>> {
let def = M::default();
for keys in M::nodes::<Packed>().root(self.key)? {
for keys in M::nodes::<Packed, D>().root(self.key)? {
// Slight abuse of TooLong for "keys to long for packed"
let (keys, node) = keys.map_err(|depth| Traversal::TooLong(depth))?;
debug_assert!(node.is_leaf());
Expand Down Expand Up @@ -180,11 +180,11 @@ where
let def = M::default();
let bl = buf.len();
let mut sl = &mut buf[..];
Path::<_, SEPARATOR>::from(WriteWrap(&mut sl)).transcode::<M, Y, _>(self.key)?;
Path::<_, SEPARATOR>::from(WriteWrap(&mut sl)).transcode::<M, _>(self.key)?;
let root_len = bl - sl.len();
awrite(&mut write, &buf[..root_len]).await?;
awrite(&mut write, ">\n".as_bytes()).await?;
for keys in M::nodes::<Packed>().root(self.key)? {
for keys in M::nodes::<Packed, D>().root(self.key)? {
let (keys, node) = keys?;
let (val, rest) = match json::get_by_key(instance, keys, &mut buf[..]) {
Err(miniconf::Error::Traversal(Traversal::TooShort(_))) => {
Expand All @@ -204,7 +204,7 @@ where
awrite(&mut write, " ".as_bytes()).await?;
let rl = rest.len();
let mut sl = &mut rest[..];
Path::<_, SEPARATOR>::from(WriteWrap(&mut sl)).transcode::<M, Y, _>(keys)?;
Path::<_, SEPARATOR>::from(WriteWrap(&mut sl)).transcode::<M, _>(keys)?;
let path_len = rl - sl.len();
awrite(&mut write, &rest[root_len..path_len]).await?;
awrite(&mut write, ": ".as_bytes()).await?;
Expand Down Expand Up @@ -279,7 +279,7 @@ async fn main() -> Result<()> {

let mut stdout = embedded_io_adapters::tokio_1::FromTokio::new(tokio::io::stdout());
let mut stdin = tokio::io::BufReader::new(tokio::io::stdin()).lines();
let mut menu = Menu::default();
let mut menu = Menu::<_, 4>::default();

while let Some(line) = stdin.next_line().await? {
let ret = menu
Expand Down
11 changes: 6 additions & 5 deletions miniconf/examples/scpi.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use core::str;

use miniconf::{
json, IntoKeys, Key, KeyLookup, Keys, PathIter, TreeDeserializeOwned, TreeSerialize,
json, IntoKeys, Key, KeyLookup, Keys, PathIter, Traversal, TreeDeserializeOwned, TreeSerialize,
};

mod common;
Expand All @@ -19,7 +19,7 @@ use common::Settings;
struct ScpiKey<T: ?Sized>(T);

impl<T: AsRef<str> + ?Sized> Key for ScpiKey<T> {
fn find(&self, lookup: &KeyLookup) -> Option<usize> {
fn find(&self, lookup: &KeyLookup) -> Result<usize, Traversal> {
let s = self.0.as_ref();
if let Some(names) = lookup.names {
let mut truncated = None;
Expand All @@ -35,7 +35,7 @@ impl<T: AsRef<str> + ?Sized> Key for ScpiKey<T> {
}
if name.len() == s.len() {
// Exact match: return immediately
return Some(i);
return Ok(i);
}
if truncated.is_some() {
// Multiple truncated matches: ambiguous unless there is an additional exact match
Expand All @@ -53,6 +53,7 @@ impl<T: AsRef<str> + ?Sized> Key for ScpiKey<T> {
} else {
s.parse().ok()
}
.ok_or(Traversal::NotFound(1))
}
}

Expand Down Expand Up @@ -89,9 +90,9 @@ enum Error {
Utf8(#[from] core::str::Utf8Error),
}

struct ScpiCtrl<M, const Y: usize>(M);
struct ScpiCtrl<M>(M);

impl<M: TreeSerialize<Y> + TreeDeserializeOwned<Y>, const Y: usize> ScpiCtrl<M, Y> {
impl<M: TreeSerialize + TreeDeserializeOwned> ScpiCtrl<M> {
fn new(settings: M) -> Self {
Self(settings)
}
Expand Down
Loading
Loading