-
Notifications
You must be signed in to change notification settings - Fork 69
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
Option in TimeUnitField using lazy_static #76
Option in TimeUnitField using lazy_static #76
Conversation
Also fix cherry-picking mistake ( see parsing.rs from_ordinal(_set)
Decided to write some tests, found a small mistake. |
Apparently is_specified was only in TimeUnitField.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is lazy_static
preferable to something like OnceCell
? I don't have much experience with either; if there's no great answer I'm happy to move ahead with lazy_static
.
cron
has a lot of formatting foibles. When we reach a stopping place in the round of PRs you're working on I'd like to do a pass with rustfmt
and add that to the CI build.
src/time_unit/days_of_week.rs
Outdated
|
||
impl TimeUnitField for DaysOfWeek { | ||
fn from_ordinal_set(ordinal_set: OrdinalSet) -> Self { | ||
DaysOfWeek(ordinal_set) | ||
fn from_ordinal_set(ordinal_set: Option<OrdinalSet>) -> Self { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Accepting an Option<_>
works fine, but to my eyes DaysOfWeek::from_ordinal_set(None)
looks like it's using the empty set rather than all values.
Another option here: the TimeUnitField
trait could have three methods, a low-level one that accepts an Option<OrdinalSet>
(essentially a rename of the existing from_ordinal_set
) and two default impls that expose a more self-documenting API for use elsewhere in the crate like parsing.rs
:
// Each type implements this:
fn from_optional_ordinal_set(ordinal_set: Option<OrdinalSet>) -> Self;
// === The trait provides these fn impls for free ===
fn all() -> Self {
Self::from_optional_ordinal_set(None)
}
fn from_ordinal_set(ordinal_set: OrdinalSet) -> Self {
Self::from_optional_ordinal_set(Some(ordinal_set))
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah no problem! I'll do that.
src/time_unit/mod.rs
Outdated
@@ -236,7 +237,7 @@ where | |||
use self::Specifier::*; | |||
//println!("ordinals_from_specifier for {} => {:?}", Self::name(), specifier); | |||
match *specifier { | |||
All => Ok(Self::supported_ordinals()), | |||
All => Ok(Self::supported_ordinals()), // TODO: change to Self::ALL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you tell me about this TODO
? Off-hand it seems like Self::supported_ordinals()
and Self::ALL.clone()
would do the same work. (The latter might even do more work because lazy_static
has a small amount of overhead to dereference.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Self::supported ordinals creates a new OrdinalSet. If ALL is already initialized, this should run quicker, otherwise ALL initializes the OrdinalSet for the next use (instead of throwing the OrdinalSet away)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ALL
is a static ref though, and would need to be clone()
ed to satisfy the function's return type.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would defeat the purpose then. I can remove the TODO
Never heard of OnceCell, but this is the first sentence I read on the link you send:
I can try to change it to OnceCell if you prefer. |
from_ordinal_set takes an OrdinalSet again, and is implemented in TimeUnitField
I'm looking more into OnceCell. It seems that there is a proposition to move it into std. Maybe it actually is better to use that instead of lazy_static. |
src/time_unit/mod.rs
Outdated
/// assert_eq!(true, schedule.days_of_month().is_specified()); | ||
/// assert_eq!(false, schedule.months().is_specified()); | ||
/// ``` | ||
fn is_specified(&self) -> bool; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a minor quibble with the name of this function: the field is specified; the user had to write *
. How would you feel about is_all
instead, which would return the inverse?
This is slightly different in that (for example) a DayOfWeek
field with *
, ?
, and 1-7
would all return true
for is_all()
regardless of whether its internal representations were None
or the equivalent of Some(ALL)
.
I like the feel of this better; would it address your use case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit afraid that this implementation would break when you get to years though. If I would type Years.is_all() on 1970-2070 it should actually always return false right? Or maybe the end is actually nigh :p
Yes you can specify 1-7 for weekdays, but then you actually did specify it, as being all weekdays.
My use case was (basically the reason why I came about this project) to change cron strings into launchd files. When you don't specify a year in a launchd file, it means that you want something to happen regardless of the year. I didn't want to print 100 or so years in my launchd file whenever I parsed a string with a * in it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit afraid that this implementation would break when you get to years though. If I would type Years.is_all() on 1970-2070 it should actually always return false right? Or maybe the end is actually nigh :p
Ha, good point. The impl I laid out would be the default impl, but I think we could easily justify overriding it in the impl for Years
to just look at is_some()
.
Yes you can specify 1-7 for weekdays, but then you actually did specify it, as being all weekdays.
I see the distinction you're making, but I'm not certain I want Schedule
to make that distinction. There's not currently a distinction between *
and ?
(for fields that accept it), and intuitively I would expect 1-7
(or whichever inclusive range) to be treated the same for fields that have a fixed range (all of them but Years
).
My use case was (basically the reason why I came about this project) to change cron strings into launchd files. When you don't specify a year in a launchd file, it means that you want something to happen regardless of the year. I didn't want to print 100 or so years in my launchd file whenever I parsed a string with a * in it.
Does the default impl override of is_all
for Years
address this for you?
To be clear, I could be persuaded to have an is_specified
method, it just feels a bit hazy semantically. I wanted to see if we could get by with something that wasn't strictly bound to the input expression syntax first.
src/time_unit/minutes.rs
Outdated
} | ||
} | ||
fn is_specified(&self) -> bool { | ||
self.ordinals.is_some() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If someone wrote 0-23
for the hours field, that would be equivalent to *
but this would return false. Is that the intended effect? What if this were something like:
self.ordinals.is_some() | |
let max_supported_ordinals = (Self::inclusive_max() - Self::inclusive_min()) + 1; | |
let is_all = self.ordinals | |
.map(|o| o.len() == max_supported_ordinals) | |
.unwrap_or(true); | |
!is_all |
This could be a default impl shared across all TimeUnitField
types.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(If this were renamed from is_specified
to is_all
we'd obviously drop the !
in the returned value.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thios could be an option. I feel like is_all is still a really weird function to call on years though
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I'd make that change, I would rather write it as:
fn is_all(&self) -> bool {
let max_supported_ordinals = Self::inclusive_max() - Self::inclusive_min() + 1
self.ordinals.map_or(true, |o| o.len() == max_supported_ordinals))
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, that's better. 👍 Didn't know about map_or
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Didn't know about map_or.
Me neither till I tried running rustfix --pedantic. Might not be a bad idea to run that after this PR cycle.
I can do this, but ordinals() in TimeUnitField doesn't return an Option so I just changed it to
fn is_all(&self) -> bool {
let max_supported_ordinals = Self::inclusive_max() - Self::inclusive_min() + 1;
self.ordinals().len() == max_supported_ordinals as usize
}
Which I'm sure is acceptable as well
src/time_unit/mod.rs
Outdated
@@ -236,7 +237,7 @@ where | |||
use self::Specifier::*; | |||
//println!("ordinals_from_specifier for {} => {:?}", Self::name(), specifier); | |||
match *specifier { | |||
All => Ok(Self::supported_ordinals()), | |||
All => Ok(Self::supported_ordinals()), // TODO: change to Self::ALL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ALL
is a static ref though, and would need to be clone()
ed to satisfy the function's return type.
From the once_cell docs. I can change it from lazy_static without any problems. Yea or nay? |
Yea. Thanks for looking into this! |
Ok those were a couple of extra pushes, but I think I'm done now. I changed lazy_static to once_cell. I changed is_specified to is_all. Then realized it was implemented in the wrong position, so I had to change that. And lastly I changed the test string to be |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Were you going to add an overriding impl of is_all
for Years
to check is_some
? Otherwise this looks great.
I had a bit of a problem with the name is_all in relation to years. That's why I preferred is_specified. But I do need to check in some way if * is used for years. At the moment it checks if years contains all the years that this library can handle. If it is decided that the bounds of years are changed, this check will still work. |
Nit on line #160 Co-authored-by: Zack Slayton <zack.slayton@gmail.com>
My objection to Now that we have an Would that combination of
Not sure I followed this. Could you give an example? |
I'm going to go ahead and merge this PR, we can resolve the outstanding discussion points in a follow-on. Thanks again! |
Yeah my problem with is_all was largely a naming problem as well. I but I don't thionk we have to agree on every nitpick. It's your project after all.
The current bounds are 1970-2070, if someone comes along and decides that these bounds are insufficient. Then the is_all function on years will still work. of course it's not literaly all years, but yeah... nitpick |
Same as #73 Just cleaned up the git mess.
Changes the internals of all TimeUnitFields to be optional. If the
TimeUnitField
specified, then its populated with anOrdinalSet
. If it's not specified (the cron field is *) then it's None. If then the theOrdinalSet
is requested, it will simply return a reference to a staticOrdinalSet
that is filled using[TimeUnitField]::supported_ordinals()
.TimeUnitField::all()
will create a TimeUnitField that will point to this static set.TimeUnitSpec gains an extra function:
is_specified()
I've chosen to populate this set lazily, but I had to make it statically, because the following code simply doesn't work:
Lastly I cleaned up some small stuff in parsing.rs
Seconds::from_ordinal_set(iter::once(0).collect())
becomesSeconds::from_ordinal(0)
(etc.)