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

semver example: find the latest version satsifying given range #371

Merged
merged 1 commit into from Dec 3, 2017

Conversation

Projects
None yet
3 participants
@anna-liao
Copy link
Contributor

anna-liao commented Nov 25, 2017

fixes #285 r? @budziq

src/app.md Outdated
fn run() -> Result<()> {
let mut ver = Version::from_str("0.0.0").unwrap();
let vset = ["0.9.0", "1.0.0", "1.0.1"];
let r = VersionReq::parse("<= 1.0.0").unwrap();

This comment has been minimized.

@ludwigpacifici

ludwigpacifici Nov 25, 2017

Contributor

Let's use error_chain and replace unwrap() by ?. If the reader copy paste the snippet and modify the version with a wrong value, the program will exit with a message error more useful than a panic.

@ludwigpacifici

This comment has been minimized.

Copy link
Contributor

ludwigpacifici commented Nov 25, 2017

Hello @anna-liao

Thank you for your PR!

Here are some suggestions to help you improve your semver example:

  • do not forget to update intro.md file so that it also appears in the table of contents
  • @budziq, should the example be enriched by adding pre-release tag into the versions? If yes, the crate as a specific behaviour that is not obvious and might be worth to highlight (crate code).
src/app.md Outdated
let vset = ["0.9.0", "1.0.0", "1.0.1"];
let r = VersionReq::parse("<= 1.0.0").unwrap();
for v in vset.iter() {

This comment has been minimized.

@ludwigpacifici

ludwigpacifici Nov 25, 2017

Contributor

Let's try to make this for loop more Rust idiomatic by using iterators. For example:

  1. filter_map to convert valid vset string versions to semver::Version
  2. filter versions which does not match your VersionReq
  3. and use max to get the latest version from the range

@anna-liao anna-liao force-pushed the anna-liao:issue285 branch 2 times, most recently from 95bce82 to 355cea1 Nov 26, 2017

@anna-liao

This comment has been minimized.

Copy link
Contributor Author

anna-liao commented Nov 26, 2017

@ludwigpacifici Thanks for the review! I have updated my PR, though have not added the pre-release tag yet, pending response from @budziq

src/app.md Outdated
use semver::Version;
use semver::VersionReq;
mod errors {

This comment has been minimized.

@ludwigpacifici

ludwigpacifici Nov 26, 2017

Contributor

You can make your example more concise by removing mod errors scope.

This comment has been minimized.

@budziq

budziq Nov 27, 2017

Collaborator

this whole mod can be deleted.

src/app.md Outdated
let r = VersionReq::parse("<= 1.0.0")
.chain_err(|| "unable to parse VersionReq")?;
let vmax = vset.iter()

This comment has been minimized.

@ludwigpacifici

ludwigpacifici Nov 26, 2017

Contributor

You can make your example more concise by removing vset and go ["0.9.0", "1.0.0", "1.0.1"].iter()...

This comment has been minimized.

@budziq

budziq Nov 27, 2017

Collaborator

agreed 👍

This comment has been minimized.

@budziq

budziq Nov 27, 2017

Collaborator

also we can use slightly longer variable names to improve readabaility

src/app.md Outdated
## Find the latest version satisfying given range
[![semver-badge]][semver] [![cat-config-badge]][cat-config]
Given an array of unparsed version `&str`s, iterate and filter against a semver `VersionReq` and find the latest `Version`.

This comment has been minimized.

@ludwigpacifici

ludwigpacifici Nov 26, 2017

Contributor

I would give more details in the description. For example, explain the range is obtained via the matches and the max.

This comment has been minimized.

@budziq

budziq Nov 27, 2017

Collaborator

Agreed. I'd remove the "an array" and "unparsed". I'd also add the hyperlinks to key identifiers like @ludwigpacifici suggests, Imho these would be VersionReq::parse, VersionReq::matches and optionally Version::parse.

Additionally "latest" is quite imprecise, I'd suggest rewriting it as 'satisfying the given range "<= 1.0.0"' .

@ludwigpacifici

This comment has been minimized.

Copy link
Contributor

ludwigpacifici commented Nov 26, 2017

Thanks @anna-liao. I added some more comments, but I would suggest you to wait for @budziq feedback and then proceed to the changes. I just saw he mentioned another solution (with sets) to implement it. Let's see which is more suitable for this example.

@budziq
Copy link
Collaborator

budziq left a comment

@anna-liao Thanks. Few suggestions.

src/app.md Outdated
use semver::Version;
use semver::VersionReq;
mod errors {

This comment has been minimized.

@budziq

budziq Nov 27, 2017

Collaborator

this whole mod can be deleted.

src/app.md Outdated
error_chain! { }
}
error_chain! {

This comment has been minimized.

@budziq

budziq Nov 27, 2017

Collaborator

please checkout our note about error handling.

The error boilerplate (error definition and error-chain import) should be hidden with '#'

src/app.md Outdated
error_chain! {
foreign_links {
SemVer(semver::SemVerError);

This comment has been minimized.

@budziq

budziq Nov 27, 2017

Collaborator

SemVerError is not actually used in this example

src/app.md Outdated
fn run() -> Result<()> {
let vset = ["0.9.0", "1.0.0", "1.0.1"];
let r = VersionReq::parse("<= 1.0.0")
.chain_err(|| "unable to parse VersionReq")?;

This comment has been minimized.

@budziq

budziq Nov 27, 2017

Collaborator

you can drop the chain_err if you add semver::ReqParseError to the foreign_links

src/app.md Outdated
let r = VersionReq::parse("<= 1.0.0")
.chain_err(|| "unable to parse VersionReq")?;
let vmax = vset.iter()

This comment has been minimized.

@budziq

budziq Nov 27, 2017

Collaborator

agreed 👍

src/app.md Outdated
Ok(())
}
quick_main!(run);

This comment has been minimized.

@budziq

budziq Nov 27, 2017

Collaborator

lets hide the error boilerplate

src/app.md Outdated
extern crate error_chain;
extern crate semver;
use semver::Version;

This comment has been minimized.

@budziq

budziq Nov 27, 2017

Collaborator

lets import both Version and VersionReq with a single statement

src/app.md Outdated
.max()
.chain_err(|| "unable to find max Version")?;
assert_eq!(vmax, Version { major: 1, minor: 0, patch: 0, pre: vec!(), build: vec!() });

This comment has been minimized.

@budziq

budziq Nov 27, 2017

Collaborator

lets run rustfmt against the example

src/app.md Outdated
let vmax = vset.iter()
.filter_map(|s| Version::parse(s).ok())
.filter(|s| r.matches(&s))

This comment has been minimized.

@budziq

budziq Nov 27, 2017

Collaborator

the & is unnecessary here

src/app.md Outdated
## Find the latest version satisfying given range
[![semver-badge]][semver] [![cat-config-badge]][cat-config]
Given an array of unparsed version `&str`s, iterate and filter against a semver `VersionReq` and find the latest `Version`.

This comment has been minimized.

@budziq

budziq Nov 27, 2017

Collaborator

Agreed. I'd remove the "an array" and "unparsed". I'd also add the hyperlinks to key identifiers like @ludwigpacifici suggests, Imho these would be VersionReq::parse, VersionReq::matches and optionally Version::parse.

Additionally "latest" is quite imprecise, I'd suggest rewriting it as 'satisfying the given range "<= 1.0.0"' .

@anna-liao

This comment has been minimized.

Copy link
Contributor Author

anna-liao commented Nov 27, 2017

Thanks for the reviews, @ludwigpacifici and @budziq ! Really appreciate you both taking the time to go over my PR with a fine tooth comb.

I am having an issue while revising the PR:
When I click on the link I added in intro.md after running mdbook serve -o, it jumps to the wrong example. Specifically, it jumps to the ex-semver-command example. I am pretty sure I wrote the link format correctly. I am running this on Ubuntu 16.04

@anna-liao anna-liao force-pushed the anna-liao:issue285 branch from 355cea1 to fc7ddbe Nov 27, 2017

src/app.md Outdated
Given a list of version `&str`s, find the latest [`semver::Version`] that satisfies the given range
"<= 1.0.0". A [`semver::VersionReq`] is used to compare the parsed version to a
minimum version requirement. Filter for version `&str` that are verified as [`semver::Version`] with [`Version::parse`] and satisfies the [`semver::VersionReq`] using [`semver::VersionReq::matches`](https://docs.rs/semver/*/semver/struct.VersionReq.html#method.matches). Return the latest [`semver::Version`] using [`std::cmp::max`](https://doc.rust-lang.org/std/cmp/fn.max.html).

This comment has been minimized.

@ludwigpacifici

ludwigpacifici Nov 27, 2017

Contributor

To ease readability, links are placed at the bottom of the file in alphabetic order.

No need for double space after a dot. Maybe I am too picky? :-)

  • "requirement. Filter"
  • "matches). Return"
@ludwigpacifici

This comment has been minimized.

Copy link
Contributor

ludwigpacifici commented Nov 27, 2017

I am having an issue while revising the PR:
When I click on the link I added in intro.md after running mdbook serve -o, it jumps to the wrong example. Specifically, it jumps to the ex-semver-command example. I am pretty sure I wrote the link format correctly. I am running this on Ubuntu 16.04

Nice catch @anna-liao! I do not see something obvious in your PR that can explain that. Also, I can reproduce your issue with Firefox with different links:

With Chrome it is fine. Not sure if the issue is cookbook or mdbook related.

@budziq, in case you missed it, did you have time to look at the below (from top of this thread)? :-)

@budziq, should the example be enriched by adding pre-release tag into the versions? If yes, the crate as a specific behaviour that is not obvious and might be worth to highlight (crate code).

@anna-liao anna-liao force-pushed the anna-liao:issue285 branch from fc7ddbe to f2167e1 Nov 27, 2017

@budziq

This comment has been minimized.

Copy link
Collaborator

budziq commented Nov 28, 2017

With Chrome it is fine. Not sure if the issue is cookbook or mdbook related.

I can reproduce it only on Firefox 57 which due to stylo is more prone to incomplete style flashes. What we experience here is firefox pointing to correct page location but then a highlight.js and mdbooks custom js kicks-in to style the code snippets and hide the lines starting with '#'. The links you are mentioning are in places particularly heavy with '#'. Hiding the '#' lines causes the page to get shorter but firefox viewport does not move anymore. I'm not versed in cross-browser compatible html+js+css so I cannot say if that this is a problem with either firefox or mdbook but it is certainly not a problem with the cookbook. I guess that we should start with posting the issue to mdbook bugtracker. Anyone care to do that? :)

should the example be enriched by adding pre-release tag into the versions?

I'm not particularly sure if we should focus on teaching semver intricacies as the mentioned behavior is just part of the standard but if we were able to present it in a compact form then I'm game! If we were to go that way I'd suggest to extract the comparing/searching logic into a function and call it two times (once on a dataset that we already have and once on a version with prerelease tag)

@budziq
Copy link
Collaborator

budziq left a comment

Minor suggestions below 👍

src/app.md Outdated
Given a list of version `&str`s, find the latest [`semver::Version`] that satisfies the given range
"<= 1.0.0". A [`semver::VersionReq`] is used to compare the parsed version to a
minimum version requirement. Filter for version `&str` that are verified as [`semver::Version`] with [`Version::parse`] and satisfies the [`semver::VersionReq`] using [`VersionReq::matches`]. Return the latest [`semver::Version`] using [`max`].

This comment has been minimized.

@budziq

budziq Nov 28, 2017

Collaborator

I'd probably refrain from describing all of the code step by step (especially the last sentence might be omitted).

This comment has been minimized.

@anna-liao

anna-liao Nov 28, 2017

Author Contributor

@budziq @ludwigpacifici I updated the description so that it is concise.

Regarding an example with the pre-release tags, I am working on that in a separate branch and will submit as a PR when it is working.

src/app.md Outdated
[![semver-badge]][semver] [![cat-config-badge]][cat-config]
Given a list of version `&str`s, find the latest [`semver::Version`] that satisfies the given range
"<= 1.0.0". A [`semver::VersionReq`] is used to compare the parsed version to a

This comment has been minimized.

@budziq

budziq Nov 28, 2017

Collaborator

I'd probably try to keep the description more compact. Haw about something along these lines?:

Given a list of version &strs, finds the latest [semver::Version] that satisfying given [semver::VersionReq] "<= 1.0.0" using [VersionReq::matches].

@anna-liao anna-liao force-pushed the anna-liao:issue285 branch 2 times, most recently from 75f982e to cec54b2 Nov 28, 2017

@ludwigpacifici ludwigpacifici referenced this pull request Nov 29, 2017

Closed

Issue285enh semver #374

@ludwigpacifici

This comment has been minimized.

Copy link
Contributor

ludwigpacifici commented Nov 29, 2017

Thanks @anna-liao for raising an mdBook issue.

I'm not particularly sure if we should focus on teaching semver intricacies as the mentioned behavior is just part of the standard but if we were able to present it in a compact form then I'm game! If we were to go that way I'd suggest to extract the comparing/searching logic into a function and call it two times (once on a dataset that we already have and once on a version with prerelease tag)

I raised this idea because I tried this example with these inputs ["0.9.0", "1.0.1", "0.9.0-abc"] and I was surprised to see 0.9.0 as a result. I though it was bug. In the end I found out, it was by definition. I agree, it's a bit border line between cookbook and semver specifications. Refactor the code in a function and call it on two different examples is a good approach and not too intrusive.

@anna-liao anna-liao force-pushed the anna-liao:issue285 branch from cec54b2 to 9e3df3f Nov 30, 2017

@anna-liao

This comment has been minimized.

Copy link
Contributor Author

anna-liao commented Nov 30, 2017

Added new version with pre-release example. r? @ludwigpacifici @budziq

src/app.md Outdated
Ok(
version_str_intoiter
//.iter()

This comment has been minimized.

@ludwigpacifici

ludwigpacifici Nov 30, 2017

Contributor

You can remove this comment

src/app.md Outdated
"3.4.5-alpha.9",
].into_iter()
)?,
Some(Version {

This comment has been minimized.

@ludwigpacifici

ludwigpacifici Nov 30, 2017

Contributor

Since the expected result can be surprising, I wonder if a comment should be added like: "Shows Semver precedence for pre-release tags"

@budziq
Copy link
Collaborator

budziq left a comment

few more suggestions

src/app.md Outdated
# }
# }
fn find_max_matching_version(

This comment has been minimized.

@budziq

budziq Nov 30, 2017

Collaborator

If you go with signature such as this one:

fn find_max_matching_version<'a, I>(
    version_req_str: &str,
    iterable: I,
) -> Result<Option<Version>>
where
    I: IntoIterator<Item = &'a str>,
{

Then you will be able to omit both into_iter() calls below and

also rustfmt allows us to put whole function declaration in a single line so lets do it for readability.

This comment has been minimized.

@anna-liao

anna-liao Dec 2, 2017

Author Contributor

@budziq When I run the code with this signature, I get this error:

error[E0599]: no method named `filter_map` found for type `I` in the current scope
  --> src/main.rs:33:10
   |
33 |         .filter_map(|s| Version::parse(s).ok())
   |          ^^^^^^^^^^
   |
   = note: the method `filter_map` exists but the following trait bounds were not satisfied:
           `&mut I : std::iter::Iterator`
   = help: items from traits can only be used if the trait is implemented and in scope
   = note: the following trait defines an item `filter_map`, perhaps you need to implement it:
           candidate #1: `std::iter::Iterator`

I couldn't figure out how to resolve this compiler error. Do I need to implement filter_map for I?

This comment has been minimized.

@ludwigpacifici

ludwigpacifici Dec 2, 2017

Contributor

@anna-liao , have you tried to remove the two below into_iter() calls (in the asserts) and go iterable.into_iter().filter_map(... in find_max_matching_version function?

This comment has been minimized.

@budziq

budziq Dec 2, 2017

Collaborator

Do I need to implement filter_map for I?

Nope. Actually the error message could be a lot better here.

Let me try to explain what is going on here. IntoIterator is a trait bound here making the find_max_matching_version function generic over type implementing the trait. So it will accept now things like Vec, HashSet (and almost any other container) or any Iterator as long as their Item is &str.

But while all these types are accepted as the parameter, they are not an iterator yet (which itself implement filter_map). You will have to actually use the into_iter trait method to turn the argument into an Iterator.

This is how almost all conversion trait bounds are used (AsRef<T>, Into<T>, etc). You use the trait bound to accept a range of types and use the trait method inside the function to get parameter converted into the type you want. I hope this helps :)

This comment has been minimized.

@anna-liao

anna-liao Dec 2, 2017

Author Contributor

@ludwigpacifici @budziq Ah thanks for the explanation! I have tested the code as suggested, and updated the PR

@anna-liao anna-liao force-pushed the anna-liao:issue285 branch from 9e3df3f to 8857d11 Dec 2, 2017

@budziq
Copy link
Collaborator

budziq left a comment

Nice! One final nit and we are ready to merge!

src/app.md Outdated
extern crate semver;
use semver::{AlphaNumeric, Numeric, Version, VersionReq};
use std::vec::IntoIter;

This comment has been minimized.

@budziq

budziq Dec 2, 2017

Collaborator

this import is unused leading to warnings.

src/app.md Outdated
"3.4.5-alpha.9",
]
)?,
Some(Version {

This comment has been minimized.

@budziq

budziq Dec 2, 2017

Collaborator

I'm not sure if we should not replace this multiline Some(Version{...}) with just Some(Version::parse("1.0.0")?) for brevity in both cases (also reducing number of required imports). But it will not be a disaster if we leave it as it is. What are your thoughts on it @anna-liao , @ludwigpacifici ?

@anna-liao anna-liao force-pushed the anna-liao:issue285 branch from 8857d11 to b4bf27a Dec 3, 2017

@anna-liao

This comment has been minimized.

Copy link
Contributor Author

anna-liao commented Dec 3, 2017

@budziq I think that's a good idea, as it is more concise and readable. I have updated the PR with the revision.

@ludwigpacifici

This comment has been minimized.

Copy link
Contributor

ludwigpacifici commented Dec 3, 2017

I have one question below, otherwise looks good to me!

# }
# }
fn find_max_matching_version<'a, I>(version_req_str: &str, iterable: I) -> Result<Option<Version>>

This comment has been minimized.

@ludwigpacifici

ludwigpacifici Dec 3, 2017

Contributor

The semantic of the return type Result<Option<T>> looks off to me. However, I have no better suggestion. Does it mean something specific?

This comment has been minimized.

@budziq

budziq Dec 3, 2017

Collaborator

Does it mean something specific?

Yep. Typically Result is used to handle possibility of error while Option expresses possibility of nonexistence. So as long as nonexistence of searched value is not considered a hard error it would be best expressed as an Option while having to deal with parsing errors leaves us with Result so Result<Option<T>> gives the reader of our API a clear indication that the operation can both fail as well as yield no usable return value even if there is no error.

This comment has been minimized.

@ludwigpacifici

ludwigpacifici Dec 3, 2017

Contributor

Thanks @budziq for your explanation. I appreciate it!

@budziq budziq merged commit 0b3d6ff into rust-lang-nursery:master Dec 3, 2017

2 checks passed

continuous-integration/appveyor/pr AppVeyor build succeeded
Details
continuous-integration/travis-ci/pr The Travis CI build passed
Details
@budziq

This comment has been minimized.

Copy link
Collaborator

budziq commented Dec 3, 2017

Nicely done @anna-liao !
Thanks for the help with the review @ludwigpacifici !

@ludwigpacifici

This comment has been minimized.

Copy link
Contributor

ludwigpacifici commented Dec 3, 2017

Well done @anna-liao !
You are welcome @budziq !

@anna-liao anna-liao deleted the anna-liao:issue285 branch Dec 3, 2017

@anna-liao

This comment has been minimized.

Copy link
Contributor Author

anna-liao commented Dec 3, 2017

Thanks @budziq and @ludwigpacifici :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.