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

ObjectBuilder: add property_if(), property_if_some(), property_from_iter() ... #1377

Merged
merged 1 commit into from
Jul 5, 2024

Conversation

fengalin
Copy link
Contributor

@fengalin fengalin commented Apr 29, 2024

... & property_if_not_empty()

This commit adds an ObjectBuilderPropertySetter trait which implements
functions to improve usability when setting properties.

property_if() allows setting a property from an Option only if a given
predicate evaluates to true. Otherwise, the Object's default value or
any previously set value is kept.

property_if_some() allows setting a property from an Option only if the
Option is Some. Otherwise, the Object's default value or any previously
set value is kept.

For instance, assuming an Args struct which was built from the application's
CLI arguments:

let args = Args {
    name: Some("test"),
    anwser: None,
};

... without this change, we need to write something like this:

let mut builder = Object::builder::<SimpleObject>();

if let Some(ref name) = args.name {
    builder = builder.property("name", name)
}

if let Some(ref answer) = args.answer {
    builder = builder.property("answer", &args.answer)
}

let obj = builder.build();

With property_if_some(), we can write:

let obj = Object::builder::<SimpleObject>()
    .property_if_some("name", &args.name)
    .property_if_some("answer", &args.answer)
    .build();

property_from_iter() allows building a collection-like Value property
from a collection of items:

let obj = Object::builder::<SimpleObject>()
    .property_from_iter::<ValueArray>("array", ["val0", "val1"])
    .build();

property_if_not_empty() allows building a collection-like Value property
from a collection of items but does nothing if the provided collection if empty,
thus keeping the default value of the property, if any:

let obj = Object::builder::<SimpleObject>()
    .property_if_not_empty::<ValueArray>("array", ["val0", "val1"])
    .build();

@bilelmoussaoui
Copy link
Member

Haven't had the time to review the PR yet but at some point there was a discussion about adding the possibility to set a property only if a condition is truthful. The use case was setting a property only if a certain gstreamer version is available iirc

cc @sdroege, do you remember where was that needed? or maybe it was someone else.

@fengalin
Copy link
Contributor Author

@bilelmoussaoui: indeed that would be useful. Something like Option::take_if in reverse? I can add that too yes.

@fengalin
Copy link
Contributor Author

Noteworthy annoyance with these changes is that we need to use glib::prelude::*; (or gst::prelude::*;) in some places were it wasn't before.

@felinira
Copy link
Contributor

I propose renaming property_if_any() to property_if_not_empty() to clarify its purpose and to be more similar to the common is_empty() collection method.

@bilelmoussaoui
Copy link
Member

Noteworthy annoyance with these changes is that we need to use glib::prelude::*; (or gst::prelude::*;) in some places were it wasn't before.

That is ok, I already migrated a bunch of files to use prelude instead of including traits manually. Going forward in that direction is good from my point of view

@bilelmoussaoui
Copy link
Member

bilelmoussaoui commented Apr 29, 2024

I propose renaming property_if_any() to property_if_not_empty() to clarify its purpose and to be more similar to the common is_empty() collection method.

I would name it property_if_some instead.

@fengalin
Copy link
Contributor Author

I propose renaming property_if_any() to property_if_not_empty() to clarify its purpose and to be more similar to the common is_empty() collection method

Makes sense.

I would name it property_if_some instead.

Already taken by the Option<_> variant :)

glib/src/object.rs Outdated Show resolved Hide resolved
glib/src/object.rs Outdated Show resolved Hide resolved
glib/src/object.rs Outdated Show resolved Hide resolved
glib/src/object.rs Outdated Show resolved Hide resolved
Copy link
Member

@sdroege sdroege left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me otherwise and the method names also make sense. @bilelmoussaoui @felinira do you prefer any different names, or would like to have something else changed?

@sdroege
Copy link
Member

sdroege commented Apr 30, 2024

I propose renaming property_if_any() to property_if_not_empty() to clarify its purpose and to be more similar to the common is_empty() collection method

Makes sense.

Also seems fine to me

@fengalin fengalin changed the title ObjectBuilder: add property_if_some(), property_from_iter() & … ObjectBuilder: add property_if(), property_if_some(), property_from_iter() ... Apr 30, 2024
Copy link
Member

@bilelmoussaoui bilelmoussaoui left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

otherwise lgtm, can't comment on the namings because i am bad at that

glib/src/value_array.rs Outdated Show resolved Hide resolved
glib/src/object.rs Outdated Show resolved Hide resolved
@fengalin
Copy link
Contributor Author

Sorry for the lack of progress on this one. fwiw, I'm planning on working on it during the hackfest.

@sdroege sdroege added this to the 0.20 milestone Jun 11, 2024
…ter() ...

... & property_if_not_empty()

This commit adds functions to improve usability when setting properties.

**`property_if()`** allows setting a property from an `Option` only if a given
`predicate` evaluates to `true`. Otherwise, the `Object`'s default value or
any previously set value is kept.

**`property_if_some()`** allows setting a property from an `Option` only if the
`Option` is `Some`. Otherwise, the `Object`'s default value or any previously
set value is kept.

For instance, assuming an `Args` `struct` which was built from the application's
CLI arguments:

```rust
let args = Args {
    name: Some("test"),
    anwser: None,
};
```

... without this change, we need to write something like this:

```rust
let mut builder = Object::builder::<SimpleObject>();

if let Some(ref name) = args.name {
    builder = builder.property("name", name)
}

if let Some(ref answer) = args.answer {
    builder = builder.property("answer", &args.answer)
}

let obj = builder.build();
```

With `property_if_some()`, we can write:

```rust
let obj = Object::builder::<SimpleObject>()
    .property_if_some("name", &args.name)
    .property_if_some("answer", &args.answer)
    .build();
```

**`property_from_iter()`** allows building a collection-like `Value` property
from a collection of items:

```rust
let obj = Object::builder::<SimpleObject>()
    .property_from_iter::<ValueArray>("array", ["val0", "val1"])
    .build();
```

**`property_if_not_empty()`** allows building a collection-like `Value` property
from a collection of items but does nothing if the provided collection if empty,
thus keeping the default value of the property, if any:

```rust
let obj = Object::builder::<SimpleObject>()
    .property_if_not_empty::<ValueArray>("array", ["val0", "val1"])
    .build();
```
@fengalin
Copy link
Contributor Author

Should be ready now

gstreamer-github pushed a commit to sdroege/gstreamer-rs that referenced this pull request Jun 14, 2024
E.g. (also applies to `property`):

* `field_from_iter()`,
* `field_if_not_empty()`.

Use a macro to factorize implementation & documentation of `field` / `property`
convenience setters.

Also:

* add some `*_if_not_empty` for some iterator based setters.
* add `*_if` for predicate based setters.

Related to gtk-rs/gtk-rs-core#1377

Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer-rs/-/merge_requests/1431>
@andy128k
Copy link
Contributor

andy128k commented Jul 5, 2024

Another option to keep ergonomic and flexible builder syntax is to use something similar to Kotlin's scope functions.

trait BuilderScopeExt {
    fn apply(self, scope: &mut FnMut(&Self)) -> Self;
}

impl BuilderScopeExt {
    fn apply(mut self, scope: &mut FnMut(&Self)) -> Self {
        (scope)(&mut self);
        self
    }
}

let obj = Object::builder::<SimpleObject>()
     .apply(|builder| {
         if let Some(ref name) = args.name {
              builder.property("name", name);
         }
         if let Some(ref answer) = args.answer {
              builder.property("answer", &args.answer);
         }
     })
     .build();

@fengalin
Copy link
Contributor Author

fengalin commented Jul 5, 2024

@andy128k, thanks for the suggestion! My main intention with this PR was to avoid constructions such as:

    if let Some(ref name) = args.name {
          [...]
    }

Do you want to propose the apply() approach in a separate PR?

@sdroege sdroege merged commit b269ab2 into gtk-rs:master Jul 5, 2024
48 checks passed
@fengalin fengalin deleted the iterable-value branch July 5, 2024 10:02
@andy128k
Copy link
Contributor

andy128k commented Jul 5, 2024

@fengalin There will always be specific inconvenient cases. Now it is Option, later someone may ask for property_if_some_deref, property_if_ok, or property_if_err etc. I'm just saying, instead of polluting builder scope with a variety of corner cases it could be a more universal approach, even if it is a bit more verbose.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants