semver-parser tutorial

1 Set up basic project structure and metadata

Welcome to the semver-parser tutorial!

This tutorial is an example of a "literate git" project. For an explanation of the motivations for this project, view the report here.

Our goal is to build a Rust library that can parse semver version strings. The maintainer of the semver crate has decided to split off the parsing operations to a separate crate, and he assigned us to implement it.

We'll document both the steps we take to build the crate and the reasons (or lack thereof) behind our implementation choices. When someone comes along later with an idea for improving our program, they can read our tutorial for more information about the project's structure and history.

There are two main public functions we need to expose to be able to plug in to the semver crate. The version::parse function transforms a version string into a struct with individual fields for each part of the version number. The range::parse function takes a string that specifies a range of matching versions and returns a struct containing one or more predicates which can be compared to individual versions.

Lucky for us, there is already a large set of test cases that we can use to verify our results. Since the function names and return types are already mostly taken care of, we can just focus on passing those tests. Ah, the life of a junior programmer!

Before we start putting together our functions, let's do some basic setup. We're going to use the Cargo package manager and build system to help streamline development, since the semver crate is already using it.

Check out the initial project structure below, then click the arrow above to move on to the next step.

.gitignore

Cargo automatically creates a basic .gitignore file for us. The target directory is where build artifacts land, so we don't want to be tracking that. Because this is a library, we also ignore the Cargo.lock file. If we were building an executable application, we would not ignore it.

Cargo.toml

The Cargo.toml file contains metadata about our crate. This includes links to related resources, dependency lists, license information, and many other possible fields. Since we have great foresight, we'll just fill this all in right now and forget about it. If we didn't have a crystal ball, we'd be updating this along the way.

.gitignore

1
target
2
Cargo.lock

Cargo.toml

1
[package]
2
name = "semver-parser"
3
version = "0.6.2"
4
authors = ["Steve Klabnik <[email protected]>"]
5
license = "MIT/Apache-2.0"
6
repository = "https://github.com/steveklabnik/semver-parser"
7
homepage = "https://github.com/steveklabnik/semver-parser"
8
documentation = "https://docs.rs/semver-parser"
9
description = """
10
Parsing of the semver spec
11
"""
12
13
[dependencies]
14
regex = "0.1"
15
lazy_static = "0.2.1"

LICENSE-APACHE

1
                              Apache License
2
                        Version 2.0, January 2004
3
                     http://www.apache.org/licenses/
4
5
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
7
1. Definitions.
8
9
   "License" shall mean the terms and conditions for use, reproduction,
10
   and distribution as defined by Sections 1 through 9 of this document.
11
12
   "Licensor" shall mean the copyright owner or entity authorized by
13
   the copyright owner that is granting the License.
14
15
   "Legal Entity" shall mean the union of the acting entity and all
16
   other entities that control, are controlled by, or are under common
17
   control with that entity. For the purposes of this definition,
18
   "control" means (i) the power, direct or indirect, to cause the
19
   direction or management of such entity, whether by contract or
20
   otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
   outstanding shares, or (iii) beneficial ownership of such entity.
22
23
   "You" (or "Your") shall mean an individual or Legal Entity
24
   exercising permissions granted by this License.
25
26
   "Source" form shall mean the preferred form for making modifications,
27
   including but not limited to software source code, documentation
28
   source, and configuration files.
29
30
   "Object" form shall mean any form resulting from mechanical
31
   transformation or translation of a Source form, including but
32
   not limited to compiled object code, generated documentation,
33
   and conversions to other media types.
34
35
   "Work" shall mean the work of authorship, whether in Source or
36
   Object form, made available under the License, as indicated by a
37
   copyright notice that is included in or attached to the work
38
   (an example is provided in the Appendix below).
39
40
   "Derivative Works" shall mean any work, whether in Source or Object
41
   form, that is based on (or derived from) the Work and for which the
42
   editorial revisions, annotations, elaborations, or other modifications
43
   represent, as a whole, an original work of authorship. For the purposes
44
   of this License, Derivative Works shall not include works that remain
45
   separable from, or merely link (or bind by name) to the interfaces of,
46
   the Work and Derivative Works thereof.
47
48
   "Contribution" shall mean any work of authorship, including
49
   the original version of the Work and any modifications or additions
50
   to that Work or Derivative Works thereof, that is intentionally
51
   submitted to Licensor for inclusion in the Work by the copyright owner
52
   or by an individual or Legal Entity authorized to submit on behalf of
53
   the copyright owner. For the purposes of this definition, "submitted"
54
   means any form of electronic, verbal, or written communication sent
55
   to the Licensor or its representatives, including but not limited to
56
   communication on electronic mailing lists, source code control systems,
57
   and issue tracking systems that are managed by, or on behalf of, the
58
   Licensor for the purpose of discussing and improving the Work, but
59
   excluding communication that is conspicuously marked or otherwise
60
   designated in writing by the copyright owner as "Not a Contribution."
61
62
   "Contributor" shall mean Licensor and any individual or Legal Entity
63
   on behalf of whom a Contribution has been received by Licensor and
64
   subsequently incorporated within the Work.
65
66
2. Grant of Copyright License. Subject to the terms and conditions of
67
   this License, each Contributor hereby grants to You a perpetual,
68
   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
   copyright license to reproduce, prepare Derivative Works of,
70
   publicly display, publicly perform, sublicense, and distribute the
71
   Work and such Derivative Works in Source or Object form.
72
73
3. Grant of Patent License. Subject to the terms and conditions of
74
   this License, each Contributor hereby grants to You a perpetual,
75
   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
   (except as stated in this section) patent license to make, have made,
77
   use, offer to sell, sell, import, and otherwise transfer the Work,
78
   where such license applies only to those patent claims licensable
79
   by such Contributor that are necessarily infringed by their
80
   Contribution(s) alone or by combination of their Contribution(s)
81
   with the Work to which such Contribution(s) was submitted. If You
82
   institute patent litigation against any entity (including a
83
   cross-claim or counterclaim in a lawsuit) alleging that the Work
84
   or a Contribution incorporated within the Work constitutes direct
85
   or contributory patent infringement, then any patent licenses
86
   granted to You under this License for that Work shall terminate
87
   as of the date such litigation is filed.
88
89
4. Redistribution. You may reproduce and distribute copies of the
90
   Work or Derivative Works thereof in any medium, with or without
91
   modifications, and in Source or Object form, provided that You
92
   meet the following conditions:
93
94
   (a) You must give any other recipients of the Work or
95
       Derivative Works a copy of this License; and
96
97
   (b) You must cause any modified files to carry prominent notices
98
       stating that You changed the files; and
99
100
   (c) You must retain, in the Source form of any Derivative Works
101
       that You distribute, all copyright, patent, trademark, and
102
       attribution notices from the Source form of the Work,
103
       excluding those notices that do not pertain to any part of
104
       the Derivative Works; and
105
106
   (d) If the Work includes a "NOTICE" text file as part of its
107
       distribution, then any Derivative Works that You distribute must
108
       include a readable copy of the attribution notices contained
109
       within such NOTICE file, excluding those notices that do not
110
       pertain to any part of the Derivative Works, in at least one
111
       of the following places: within a NOTICE text file distributed
112
       as part of the Derivative Works; within the Source form or
113
       documentation, if provided along with the Derivative Works; or,
114
       within a display generated by the Derivative Works, if and
115
       wherever such third-party notices normally appear. The contents
116
       of the NOTICE file are for informational purposes only and
117
       do not modify the License. You may add Your own attribution
118
       notices within Derivative Works that You distribute, alongside
119
       or as an addendum to the NOTICE text from the Work, provided
120
       that such additional attribution notices cannot be construed
121
       as modifying the License.
122
123
   You may add Your own copyright statement to Your modifications and
124
   may provide additional or different license terms and conditions
125
   for use, reproduction, or distribution of Your modifications, or
126
   for any such Derivative Works as a whole, provided Your use,
127
   reproduction, and distribution of the Work otherwise complies with
128
   the conditions stated in this License.
129
130
5. Submission of Contributions. Unless You explicitly state otherwise,
131
   any Contribution intentionally submitted for inclusion in the Work
132
   by You to the Licensor shall be under the terms and conditions of
133
   this License, without any additional terms or conditions.
134
   Notwithstanding the above, nothing herein shall supersede or modify
135
   the terms of any separate license agreement you may have executed
136
   with Licensor regarding such Contributions.
137
138
6. Trademarks. This License does not grant permission to use the trade
139
   names, trademarks, service marks, or product names of the Licensor,
140
   except as required for reasonable and customary use in describing the
141
   origin of the Work and reproducing the content of the NOTICE file.
142
143
7. Disclaimer of Warranty. Unless required by applicable law or
144
   agreed to in writing, Licensor provides the Work (and each
145
   Contributor provides its Contributions) on an "AS IS" BASIS,
146
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
   implied, including, without limitation, any warranties or conditions
148
   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
   PARTICULAR PURPOSE. You are solely responsible for determining the
150
   appropriateness of using or redistributing the Work and assume any
151
   risks associated with Your exercise of permissions under this License.
152
153
8. Limitation of Liability. In no event and under no legal theory,
154
   whether in tort (including negligence), contract, or otherwise,
155
   unless required by applicable law (such as deliberate and grossly
156
   negligent acts) or agreed to in writing, shall any Contributor be
157
   liable to You for damages, including any direct, indirect, special,
158
   incidental, or consequential damages of any character arising as a
159
   result of this License or out of the use or inability to use the
160
   Work (including but not limited to damages for loss of goodwill,
161
   work stoppage, computer failure or malfunction, or any and all
162
   other commercial damages or losses), even if such Contributor
163
   has been advised of the possibility of such damages.
164
165
9. Accepting Warranty or Additional Liability. While redistributing
166
   the Work or Derivative Works thereof, You may choose to offer,
167
   and charge a fee for, acceptance of support, warranty, indemnity,
168
   or other liability obligations and/or rights consistent with this
169
   License. However, in accepting such obligations, You may act only
170
   on Your own behalf and on Your sole responsibility, not on behalf
171
   of any other Contributor, and only if You agree to indemnify,
172
   defend, and hold each Contributor harmless for any liability
173
   incurred by, or claims asserted against, such Contributor by reason
174
   of your accepting any such warranty or additional liability.
175
176
END OF TERMS AND CONDITIONS
177
178
APPENDIX: How to apply the Apache License to your work.
179
180
   To apply the Apache License to your work, attach the following
181
   boilerplate notice, with the fields enclosed by brackets "[]"
182
   replaced with your own identifying information. (Don't include
183
   the brackets!)  The text should be enclosed in the appropriate
184
   comment syntax for the file format. We also recommend that a
185
   file or class name and description of purpose be included on the
186
   same "printed page" as the copyright notice for easier
187
   identification within third-party archives.
188
189
Copyright [yyyy] [name of copyright owner]
190
191
Licensed under the Apache License, Version 2.0 (the "License");
192
you may not use this file except in compliance with the License.
193
You may obtain a copy of the License at
194
195
	http://www.apache.org/licenses/LICENSE-2.0
196
197
Unless required by applicable law or agreed to in writing, software
198
distributed under the License is distributed on an "AS IS" BASIS,
199
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
See the License for the specific language governing permissions and
201
limitations under the License.

LICENSE-MIT

1
Copyright (c) 2016 Steve Klabnik
2
3
Permission is hereby granted, free of charge, to any
4
person obtaining a copy of this software and associated
5
documentation files (the "Software"), to deal in the
6
Software without restriction, including without
7
limitation the rights to use, copy, modify, merge,
8
publish, distribute, sublicense, and/or sell copies of
9
the Software, and to permit persons to whom the Software
10
is furnished to do so, subject to the following
11
conditions:
12
13
The above copyright notice and this permission notice
14
shall be included in all copies or substantial portions
15
of the Software.
16
17
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
18
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
19
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
20
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
21
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
24
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25
DEALINGS IN THE SOFTWARE.

1.1 Introduce .gitignore and Cargo.toml

.gitignore

1
target
2
Cargo.lock

Cargo.toml

1
[package]
2
name = "semver-parser"
3
version = "0.1.0"
4
authors = ["Steve Klabnik <[email protected]>"]
5
6
[dependencies]
7
regex = "0.1.69"

1.2 Add extra metadata

Cargo.toml

2 2
name = "semver-parser"
3 3
version = "0.1.0"
4 4
authors = ["Steve Klabnik <[email protected]>"]
5
license = "MIT/Apache-2.0"
6
repository = "https://github.com/steveklabnik/semver-parser"
7
homepage = "https://github.com/steveklabnik/semver-parser"
8
documentation = "http://steveklabnik.github.io/semver-parser"
9
description = """
10
Parsing of the semver spec
11
"""
5 12
6 13
[dependencies]
7 14
regex = "0.1.69"

1.3 bump to 0.2.0

Cargo.toml

1 1
[package]
2 2
name = "semver-parser"
3
version = "0.1.0"
3
version = "0.2.0"
4 4
authors = ["Steve Klabnik <[email protected]>"]
5 5
license = "MIT/Apache-2.0"
6 6
repository = "https://github.com/steveklabnik/semver-parser"

1.4 bump to 0.3.0

Cargo.toml

1 1
[package]
2 2
name = "semver-parser"
3
version = "0.2.0"
3
version = "0.3.0"
4 4
authors = ["Steve Klabnik <[email protected]>"]
5 5
license = "MIT/Apache-2.0"
6 6
repository = "https://github.com/steveklabnik/semver-parser"

1.5 bump to 0.4.0

Cargo.toml

1 1
[package]
2 2
name = "semver-parser"
3
version = "0.3.0"
3
version = "0.4.0"
4 4
authors = ["Steve Klabnik <[email protected]>"]
5 5
license = "MIT/Apache-2.0"
6 6
repository = "https://github.com/steveklabnik/semver-parser"

1.6 Bump to 0.4.1

Cargo.toml

1 1
[package]
2 2
name = "semver-parser"
3
version = "0.4.0"
3
version = "0.4.1"
4 4
authors = ["Steve Klabnik <[email protected]>"]
5 5
license = "MIT/Apache-2.0"
6 6
repository = "https://github.com/steveklabnik/semver-parser"

1.7 bump version to 0.5.0

Cargo.toml

1 1
[package]
2 2
name = "semver-parser"
3
version = "0.4.1"
3
version = "0.5.0"
4 4
authors = ["Steve Klabnik <[email protected]>"]
5 5
license = "MIT/Apache-2.0"
6 6
repository = "https://github.com/steveklabnik/semver-parser"

1.8 bump for 0.5.1

Cargo.toml

1 1
[package]
2 2
name = "semver-parser"
3
version = "0.5.0"
3
version = "0.5.1"
4 4
authors = ["Steve Klabnik <[email protected]>"]
5 5
license = "MIT/Apache-2.0"
6 6
repository = "https://github.com/steveklabnik/semver-parser"

1.9 relax regex requirement a bit

Conflicts: Cargo.toml

Cargo.toml

11 11
"""
12 12
13 13
[dependencies]
14
regex = "0.1.69"
14
regex = "0.1"
15
lazy_static = "0.2.1"

1.10 bump to 0.6.0

Cargo.toml

1 1
[package]
2 2
name = "semver-parser"
3
version = "0.5.1"
3
version = "0.6.0"
4 4
authors = ["Steve Klabnik <[email protected]>"]
5 5
license = "MIT/Apache-2.0"
6 6
repository = "https://github.com/steveklabnik/semver-parser"

1.11 Link to documentation on docs.rs

Fixes #3

Cargo.toml

5 5
license = "MIT/Apache-2.0"
6 6
repository = "https://github.com/steveklabnik/semver-parser"
7 7
homepage = "https://github.com/steveklabnik/semver-parser"
8
documentation = "http://steveklabnik.github.io/semver-parser"
8
documentation = "https://docs.rs/semver-parser"
9 9
description = """
10 10
Parsing of the semver spec
11 11
"""

1.12 Update to 0.6.2

Cargo.toml

1 1
[package]
2 2
name = "semver-parser"
3
version = "0.6.0"
3
version = "0.6.2"
4 4
authors = ["Steve Klabnik <[email protected]>"]
5 5
license = "MIT/Apache-2.0"
6 6
repository = "https://github.com/steveklabnik/semver-parser"

1.13 include LICENSE files

These were already in the Cargo.toml, but we should include them anyway.

LICENSE-APACHE

1
                              Apache License
2
                        Version 2.0, January 2004
3
                     http://www.apache.org/licenses/
4
5
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
7
1. Definitions.
8
9
   "License" shall mean the terms and conditions for use, reproduction,
10
   and distribution as defined by Sections 1 through 9 of this document.
11
12
   "Licensor" shall mean the copyright owner or entity authorized by
13
   the copyright owner that is granting the License.
14
15
   "Legal Entity" shall mean the union of the acting entity and all
16
   other entities that control, are controlled by, or are under common
17
   control with that entity. For the purposes of this definition,
18
   "control" means (i) the power, direct or indirect, to cause the
19
   direction or management of such entity, whether by contract or
20
   otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
   outstanding shares, or (iii) beneficial ownership of such entity.
22
23
   "You" (or "Your") shall mean an individual or Legal Entity
24
   exercising permissions granted by this License.
25
26
   "Source" form shall mean the preferred form for making modifications,
27
   including but not limited to software source code, documentation
28
   source, and configuration files.
29
30
   "Object" form shall mean any form resulting from mechanical
31
   transformation or translation of a Source form, including but
32
   not limited to compiled object code, generated documentation,
33
   and conversions to other media types.
34
35
   "Work" shall mean the work of authorship, whether in Source or
36
   Object form, made available under the License, as indicated by a
37
   copyright notice that is included in or attached to the work
38
   (an example is provided in the Appendix below).
39
40
   "Derivative Works" shall mean any work, whether in Source or Object
41
   form, that is based on (or derived from) the Work and for which the
42
   editorial revisions, annotations, elaborations, or other modifications
43
   represent, as a whole, an original work of authorship. For the purposes
44
   of this License, Derivative Works shall not include works that remain
45
   separable from, or merely link (or bind by name) to the interfaces of,
46
   the Work and Derivative Works thereof.
47
48
   "Contribution" shall mean any work of authorship, including
49
   the original version of the Work and any modifications or additions
50
   to that Work or Derivative Works thereof, that is intentionally
51
   submitted to Licensor for inclusion in the Work by the copyright owner
52
   or by an individual or Legal Entity authorized to submit on behalf of
53
   the copyright owner. For the purposes of this definition, "submitted"
54
   means any form of electronic, verbal, or written communication sent
55
   to the Licensor or its representatives, including but not limited to
56
   communication on electronic mailing lists, source code control systems,
57
   and issue tracking systems that are managed by, or on behalf of, the
58
   Licensor for the purpose of discussing and improving the Work, but
59
   excluding communication that is conspicuously marked or otherwise
60
   designated in writing by the copyright owner as "Not a Contribution."
61
62
   "Contributor" shall mean Licensor and any individual or Legal Entity
63
   on behalf of whom a Contribution has been received by Licensor and
64
   subsequently incorporated within the Work.
65
66
2. Grant of Copyright License. Subject to the terms and conditions of
67
   this License, each Contributor hereby grants to You a perpetual,
68
   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
   copyright license to reproduce, prepare Derivative Works of,
70
   publicly display, publicly perform, sublicense, and distribute the
71
   Work and such Derivative Works in Source or Object form.
72
73
3. Grant of Patent License. Subject to the terms and conditions of
74
   this License, each Contributor hereby grants to You a perpetual,
75
   worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
   (except as stated in this section) patent license to make, have made,
77
   use, offer to sell, sell, import, and otherwise transfer the Work,
78
   where such license applies only to those patent claims licensable
79
   by such Contributor that are necessarily infringed by their
80
   Contribution(s) alone or by combination of their Contribution(s)
81
   with the Work to which such Contribution(s) was submitted. If You
82
   institute patent litigation against any entity (including a
83
   cross-claim or counterclaim in a lawsuit) alleging that the Work
84
   or a Contribution incorporated within the Work constitutes direct
85
   or contributory patent infringement, then any patent licenses
86
   granted to You under this License for that Work shall terminate
87
   as of the date such litigation is filed.
88
89
4. Redistribution. You may reproduce and distribute copies of the
90
   Work or Derivative Works thereof in any medium, with or without
91
   modifications, and in Source or Object form, provided that You
92
   meet the following conditions:
93
94
   (a) You must give any other recipients of the Work or
95
       Derivative Works a copy of this License; and
96
97
   (b) You must cause any modified files to carry prominent notices
98
       stating that You changed the files; and
99
100
   (c) You must retain, in the Source form of any Derivative Works
101
       that You distribute, all copyright, patent, trademark, and
102
       attribution notices from the Source form of the Work,
103
       excluding those notices that do not pertain to any part of
104
       the Derivative Works; and
105
106
   (d) If the Work includes a "NOTICE" text file as part of its
107
       distribution, then any Derivative Works that You distribute must
108
       include a readable copy of the attribution notices contained
109
       within such NOTICE file, excluding those notices that do not
110
       pertain to any part of the Derivative Works, in at least one
111
       of the following places: within a NOTICE text file distributed
112
       as part of the Derivative Works; within the Source form or
113
       documentation, if provided along with the Derivative Works; or,
114
       within a display generated by the Derivative Works, if and
115
       wherever such third-party notices normally appear. The contents
116
       of the NOTICE file are for informational purposes only and
117
       do not modify the License. You may add Your own attribution
118
       notices within Derivative Works that You distribute, alongside
119
       or as an addendum to the NOTICE text from the Work, provided
120
       that such additional attribution notices cannot be construed
121
       as modifying the License.
122
123
   You may add Your own copyright statement to Your modifications and
124
   may provide additional or different license terms and conditions
125
   for use, reproduction, or distribution of Your modifications, or
126
   for any such Derivative Works as a whole, provided Your use,
127
   reproduction, and distribution of the Work otherwise complies with
128
   the conditions stated in this License.
129
130
5. Submission of Contributions. Unless You explicitly state otherwise,
131
   any Contribution intentionally submitted for inclusion in the Work
132
   by You to the Licensor shall be under the terms and conditions of
133
   this License, without any additional terms or conditions.
134
   Notwithstanding the above, nothing herein shall supersede or modify
135
   the terms of any separate license agreement you may have executed
136
   with Licensor regarding such Contributions.
137
138
6. Trademarks. This License does not grant permission to use the trade
139
   names, trademarks, service marks, or product names of the Licensor,
140
   except as required for reasonable and customary use in describing the
141
   origin of the Work and reproducing the content of the NOTICE file.
142
143
7. Disclaimer of Warranty. Unless required by applicable law or
144
   agreed to in writing, Licensor provides the Work (and each
145
   Contributor provides its Contributions) on an "AS IS" BASIS,
146
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
   implied, including, without limitation, any warranties or conditions
148
   of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
   PARTICULAR PURPOSE. You are solely responsible for determining the
150
   appropriateness of using or redistributing the Work and assume any
151
   risks associated with Your exercise of permissions under this License.
152
153
8. Limitation of Liability. In no event and under no legal theory,
154
   whether in tort (including negligence), contract, or otherwise,
155
   unless required by applicable law (such as deliberate and grossly
156
   negligent acts) or agreed to in writing, shall any Contributor be
157
   liable to You for damages, including any direct, indirect, special,
158
   incidental, or consequential damages of any character arising as a
159
   result of this License or out of the use or inability to use the
160
   Work (including but not limited to damages for loss of goodwill,
161
   work stoppage, computer failure or malfunction, or any and all
162
   other commercial damages or losses), even if such Contributor
163
   has been advised of the possibility of such damages.
164
165
9. Accepting Warranty or Additional Liability. While redistributing
166
   the Work or Derivative Works thereof, You may choose to offer,
167
   and charge a fee for, acceptance of support, warranty, indemnity,
168
   or other liability obligations and/or rights consistent with this
169
   License. However, in accepting such obligations, You may act only
170
   on Your own behalf and on Your sole responsibility, not on behalf
171
   of any other Contributor, and only if You agree to indemnify,
172
   defend, and hold each Contributor harmless for any liability
173
   incurred by, or claims asserted against, such Contributor by reason
174
   of your accepting any such warranty or additional liability.
175
176
END OF TERMS AND CONDITIONS
177
178
APPENDIX: How to apply the Apache License to your work.
179
180
   To apply the Apache License to your work, attach the following
181
   boilerplate notice, with the fields enclosed by brackets "[]"
182
   replaced with your own identifying information. (Don't include
183
   the brackets!)  The text should be enclosed in the appropriate
184
   comment syntax for the file format. We also recommend that a
185
   file or class name and description of purpose be included on the
186
   same "printed page" as the copyright notice for easier
187
   identification within third-party archives.
188
189
Copyright [yyyy] [name of copyright owner]
190
191
Licensed under the Apache License, Version 2.0 (the "License");
192
you may not use this file except in compliance with the License.
193
You may obtain a copy of the License at
194
195
	http://www.apache.org/licenses/LICENSE-2.0
196
197
Unless required by applicable law or agreed to in writing, software
198
distributed under the License is distributed on an "AS IS" BASIS,
199
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
See the License for the specific language governing permissions and
201
limitations under the License.

LICENSE-MIT

1
Copyright (c) 2016 Steve Klabnik
2
3
Permission is hereby granted, free of charge, to any
4
person obtaining a copy of this software and associated
5
documentation files (the "Software"), to deal in the
6
Software without restriction, including without
7
limitation the rights to use, copy, modify, merge,
8
publish, distribute, sublicense, and/or sell copies of
9
the Software, and to permit persons to whom the Software
10
is furnished to do so, subject to the following
11
conditions:
12
13
The above copyright notice and this permission notice
14
shall be included in all copies or substantial portions
15
of the Software.
16
17
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
18
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
19
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
20
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
21
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
22
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
23
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
24
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
25
DEALINGS IN THE SOFTWARE.

2 Implement `version::parse`

The version::parse function takes a semver version string as input and returns a Result containing either a Version struct or an error string.

The Version struct contains a field for each possible component of a semver version string. The major, minor, and patch fields are all required integers. The pre and build fields consist of zero or more alphanumeric or numeric identifiers.

This is a bit too much code to explain at once, so click the + symbol on the left for a step-by-step explanation. Once you're done, click the arrow at the top right to continue on to the range parsing function.

What about that test suite you mentioned?

There are a lot of tests! To keep things clean, we'll show them all in the final step of the tutorial.

src/common.rs

1
use regex::Regex;
2
use version::Identifier;
3
4
// by the time we get here, we know that it's all valid characters, so this doesn't need to return
5
// a result or anything
6
pub fn parse_meta(s: &str) -> Vec<Identifier> {
7
    // Originally, I wanted to implement this method via calling parse, but parse is tolerant of
8
    // leading zeroes, and we want anything with leading zeroes to be considered alphanumeric, not
9
    // numeric. So the strategy is to check with a regex first, and then call parse once we've
10
    // determined that it's a number without a leading zero.
11
    s.split(".")
12
        .map(|part| {
13
            // another wrinkle: we made sure that any number starts with a
14
            // non-zero. But there's a problem: an actual zero is a number, yet
15
            // gets left out by this heuristic. So let's also check for the
16
            // single, lone zero.
17
            if is_alpha_numeric(part) {
18
                Identifier::AlphaNumeric(part.to_string())
19
            } else {
20
                // we can unwrap here because we know it is only digits due to the regex
21
                Identifier::Numeric(part.parse().unwrap())
22
            }
23
        }).collect()
24
}
25
26
pub fn is_alpha_numeric(s: &str) -> bool {
27
    lazy_static! {
28
        static ref REGEX: Regex = Regex::new(r"^(0|[1-9][0-9]*)$").unwrap();
29
    };
30
    !REGEX.is_match(s)
31
}

src/lib.rs

1
extern crate regex;
2
3
#[macro_use]
4
extern crate lazy_static;
5
6
pub mod version;
7
8
// for private stuff the two share
9
mod common;

src/version.rs

1
use std::fmt;
2
3
use regex::Regex;
4
use common;
5
6
lazy_static! {
7
    static ref REGEX: Regex = {
8
        // a numeric identifier is either zero or multiple numbers without a leading zero
9
        let numeric_identifier = r"0|(?:[1-9][0-9]*)";
10
11
        let major = numeric_identifier;
12
        let minor = numeric_identifier;
13
        let patch = numeric_identifier;
14
15
        let letters_numbers_dash_dot = r"[-.A-Za-z0-9]+";
16
17
        // This regex does not fully parse prereleases, just extracts the whole prerelease string.
18
        // parse_version() will parse this further.
19
        let pre = letters_numbers_dash_dot;
20
21
        // This regex does not fully parse builds, just extracts the whole build string.
22
        // parse_version() will parse this further.
23
        let build = letters_numbers_dash_dot;
24
25
        let regex = format!(r"^(?x) # heck yes x mode
26
            (?P<major>{})           # major version
27
            \.                      # dot
28
            (?P<minor>{})           # minor version
29
            \.                      # dot
30
            (?P<patch>{})           # patch version
31
            (?:-(?P<pre>{}))?       # optional prerelease version
32
            (?:\+(?P<build>{}))?    # optional build metadata
33
            $",
34
            major,
35
            minor,
36
            patch,
37
            pre,
38
            build);
39
        let regex = Regex::new(&regex);
40
41
        // this unwrap is okay because everything above here is const, so this will never fail.
42
        regex.unwrap()
43
    };
44
}
45
46
#[derive(Clone, Debug, PartialEq, Eq)]
47
pub struct Version {
48
    pub major: u64,
49
    pub minor: u64,
50
    pub patch: u64,
51
    pub pre: Vec<Identifier>,
52
    pub build: Vec<Identifier>,
53
}
54
55
#[derive(Clone, Debug, PartialEq, Eq)]
56
pub enum Identifier {
57
    /// An identifier that's solely numbers.
58
    Numeric(u64),
59
    /// An identifier with letters and numbers.
60
    AlphaNumeric(String),
61
}
62
63
pub fn parse(version: &str) -> Result<Version, String> {
64
    let captures = match REGEX.captures(version.trim()) {
65
        Some(captures) => captures,
66
        None => return Err(From::from("Version did not parse properly.")),
67
    };
68
69
    let pre = captures.name("pre").map(common::parse_meta).unwrap_or(vec![]);
70
71
    let build = captures.name("build").map(common::parse_meta).unwrap_or(vec![]);
72
73
    Ok(Version {
74
        major: captures.name("major").unwrap().parse().unwrap(),
75
        minor: captures.name("minor").unwrap().parse().unwrap(),
76
        patch: captures.name("patch").unwrap().parse().unwrap(),
77
        pre: pre,
78
        build: build,
79
    })
80
}
81
82
impl fmt::Display for Version {
83
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
84
        try!(write!(f, "{}.{}.{}", self.major, self.minor, self.patch));
85
        if !self.pre.is_empty() {
86
            let strs: Vec<_> =
87
                self.pre.iter().map(ToString::to_string).collect();
88
            try!(write!(f, "-{}", strs.join(".")));
89
        }
90
        if !self.build.is_empty() {
91
            let strs: Vec<_> =
92
                self.build.iter().map(ToString::to_string).collect();
93
            try!(write!(f, "+{}", strs.join(".")));
94
        }
95
        Ok(())
96
    }
97
}
98
99
impl fmt::Display for Identifier {
100
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
101
        match *self {
102
            Identifier::Numeric(ref id) => id.fmt(f),
103
            Identifier::AlphaNumeric(ref id) => id.fmt(f),
104
        }
105
    }
106
}

2.1 Humble beginnings

Time to write some Rust code! It's all very basic for now, but we have to start somewhere.

src/lib.rs

Since this is a library crate, we list our public modules in src/lib.rs. For now, that's just our version module. While we're at it, let's pull in the regex crate we listed as a dependency. If we list it here, we can use it from any future modules, like the range parsing one we will make in the next step.

src/version.rs

There are three main components here.

On the first line, we bring the Regex module into our local scope so we can refer to it with a short name.

Next, we introduce a basic Version struct. If you're reading carefully, you'll see we don't even have the pre and build fields yet. Let's just start here and add those in later. We don't have to pass all the tests until we're done!

Note that Version and its fields are all marked public. Unlike some other languages, in Rust you must mark each field of a struct public if you want to expose it. Our users will be able to use the struct and all its fields.

The parse_version function uses Regex to extract the major, minor, and patch levels from a version string. This isn't robust enough to handle all of semver yet, but we have a mostly working module in under 20 lines of code.

Are you worried about all those calls to unwrap()? You should be! If an error occurs, the unwrap() will panic and crash our program. We'll have to implement error handling at some point, but for now let's just keep things simple.

But the function is supposed to be version::parse, you called it parse_version!

Oops, we'll have to fix that in a future step.

src/lib.rs

1
extern crate regex;
2
3
pub mod version;

src/version.rs

1
use regex::Regex;
2
3
pub struct Version {
4
    pub major: u64,
5
    pub minor: u64,
6
    pub patch: u64,
7
}
8
9
pub fn parse_version(version: &str) -> Version {
10
    let re = Regex::new(r"(\d+).(\d+).(\d+)").unwrap();
11
12
    let captures = re.captures(version).unwrap();
13
14
    Version {
15
        major: captures.at(1).unwrap().parse().unwrap(),
16
        minor: captures.at(2).unwrap().parse().unwrap(),
17
        patch: captures.at(3).unwrap().parse().unwrap(),
18
    }
19
}

2.1.1 Introduce version.rs

src/version.rs

1
extern crate regex;
2
3
use regex::Regex;
4
5
pub struct Version {
6
    major: u64,
7
    minor: u64,
8
    patch: u64,
9
}
10
11
pub fn parse_version(version: &str) -> Version {
12
    let re = Regex::new(r"(\d+).(\d+).(\d+)").unwrap();
13
14
    let captures = re.captures(version).unwrap();
15
16
    Version {
17
        major: captures.at(1).unwrap().parse().unwrap(),
18
        minor: captures.at(2).unwrap().parse().unwrap(),
19
        patch: captures.at(3).unwrap().parse().unwrap(),
20
    }
21
}

2.1.2 Make these pub

src/version.rs

3 3
use regex::Regex;
4 4
5 5
pub struct Version {
6
    major: u64,
7
    minor: u64,
8
    patch: u64,
6
    pub major: u64,
7
    pub minor: u64,
8
    pub patch: u64,
9 9
}
10 10
11 11
pub fn parse_version(version: &str) -> Version {

2.1.3 Add lib.rs

Conflicts: src/version.rs

src/lib.rs

1
extern crate regex;
2
3
pub mod version;

src/version.rs

1
extern crate regex;
2
3 1
use regex::Regex;
4 2
5 3
pub struct Version {

2.2 Disallow leading zeros and return Result

When we started applying our test suite to the result of the previous step, we found an error. Time to fix it!

src/lib.rs

Remember the lazy_static dependency we listed in Cargo.toml? The lazy static crate gives us a macro that allows us to declare statics that are evaluated at runtime.

What?

A static in rust is like a global constant. Statics live for the entire lifetime of your program at a fixed memory location. Normally they are limited to simple values, but with the lazy_static! macro, we can create statics that require function calls or heap allocations. We'll use it to make a static Regex that matches semver version strings.

src/version.rs

There are two separate things happening at once here. We made some upgrades to our regex, then changed our return type.

First, we moved the regex creation up out of parse_version into the lazy_static! macro. The result of the macro is the static called REGEX which we can use to match version strings.

The regex declaration went from one line to over a dozen, and we switched from matching on \d+ to matching on the more sophisticated numeric_identifier pattern. This pattern allows a plain zero or a number starting with any other digit, but not a number with a leading zero.

Back in parse_version, we have changed from an unwrap() on REGEX.captures() to a match. The match lets us return an error string when the supplied version number is not matched by the regex. In the version of our program from the previous step, any error in matching the regex would have caused a panic.

Since we can now return either a Version on success or a String on an error, we have changed the return type from Version to Result<Version, String>. When our result is an error, we return Err(...), and when the result is a Version, we return Ok(Version...).

How did you pass any tests if you had the wrong return type at the previous step!

This is hard, we'll get there eventually!

src/lib.rs

1 1
extern crate regex;
2 2
3
#[macro_use]
4
extern crate lazy_static;
5
3 6
pub mod version;

src/version.rs

1 1
use regex::Regex;
2 2
3
lazy_static! {
4
    static ref REGEX: Regex = {
5
        // a numeric identifier is either zero or multiple numbers without a leading zero
6
        let numeric_identifier = r"0|(?:[1-9][0-9]*)";
7
8
        let major = numeric_identifier;
9
        let minor = numeric_identifier;
10
        let patch = numeric_identifier;
11
12
        let regex = format!(r"^(?P<major>{})\.(?P<minor>{})\.(?P<patch>{})$", major, minor, patch);
13
        let regex = Regex::new(&regex);
14
15
        // this unwrap is okay because everything above here is const, so this will never fail.
16
        regex.unwrap()
17
    };
18
}
19
3 20
pub struct Version {
4 21
    pub major: u64,
5 22
    pub minor: u64,
6 23
    pub patch: u64,
7 24
}
8 25
9
pub fn parse_version(version: &str) -> Version {
10
    let re = Regex::new(r"(\d+).(\d+).(\d+)").unwrap();
11
12
    let captures = re.captures(version).unwrap();
26
pub fn parse_version(version: &str) -> Result<Version, String> {
27
    let captures = match REGEX.captures(version.trim()) {
28
        Some(captures) => captures,
29
        None => return Err(From::from("Version did not parse properly.")),
30
    };
13 31
14
    Version {
15
        major: captures.at(1).unwrap().parse().unwrap(),
16
        minor: captures.at(2).unwrap().parse().unwrap(),
17
        patch: captures.at(3).unwrap().parse().unwrap(),
18
    }
32
    Ok(Version {
33
        major: captures.name("major").unwrap().parse().unwrap(),
34
        minor: captures.name("minor").unwrap().parse().unwrap(),
35
        patch: captures.name("patch").unwrap().parse().unwrap(),
36
    })
19 37
}

2.2.1 Introduce lazy_static and return Result

Conflicts: Cargo.toml src/version.rs

src/version.rs

1
#[macro_use]
2
extern crate lazy_static;
3
1 4
use regex::Regex;
2 5
6
use std::error::Error;
7
8
lazy_static! {
9
    static ref REGEX: Regex = {
10
        let major = r"0|(:?[1-9][0-9]*)";
11
        let minor = r"0|(:?[1-9][0-9]*)";
12
        let patch = r"0|(:?[1-9][0-9]*)";
13
14
        let regex = format!(r"^(?P<major>{})\.(?P<minor>{})\.(?P<patch>{})$", major, minor, patch);
15
        let regex = Regex::new(&regex);
16
17
        // this unwrap is okay because everything above here is const, so this will never fail.
18
        regex.unwrap()
19
    };
20
}
21
3 22
pub struct Version {
4 23
    pub major: u64,
5 24
    pub minor: u64,
6 25
    pub patch: u64,
7 26
}
8 27
9
pub fn parse_version(version: &str) -> Version {
10
    let re = Regex::new(r"(\d+).(\d+).(\d+)").unwrap();
11
12
    let captures = re.captures(version).unwrap();
28
pub fn parse_version(version: &str) -> Result<Version, Box<Error>> {
29
    let captures = match REGEX.captures(version) {
30
        Some(captures) => captures,
31
        None => return Err(From::from("Version did not parse properly.")),
32
    };
13 33
14
    Version {
15
        major: captures.at(1).unwrap().parse().unwrap(),
16
        minor: captures.at(2).unwrap().parse().unwrap(),
17
        patch: captures.at(3).unwrap().parse().unwrap(),
18
    }
34
    Ok(Version {
35
        major: captures.name("major").unwrap().parse().unwrap(),
36
        minor: captures.name("minor").unwrap().parse().unwrap(),
37
        patch: captures.name("patch").unwrap().parse().unwrap(),
38
    })
19 39
}

2.2.2 Refactor repeated regexes

src/version.rs

7 7
8 8
lazy_static! {
9 9
    static ref REGEX: Regex = {
10
        let major = r"0|(:?[1-9][0-9]*)";
11
        let minor = r"0|(:?[1-9][0-9]*)";
12
        let patch = r"0|(:?[1-9][0-9]*)";
10
        // a numeric identifier is either zero or multiple numbers without a leading zero
11
        let numeric_identifier = r"0|(:?[1-9][0-9]*)";
13 12
13
        let major = numeric_identifier;
14
        let minor = numeric_identifier;
15
        let patch = numeric_identifier;
16
14 17
        let regex = format!(r"^(?P<major>{})\.(?P<minor>{})\.(?P<patch>{})$", major, minor, patch);
15 18
        let regex = Regex::new(&regex);
16 19

2.2.3 Trim version string

src/version.rs

29 29
}
30 30
31 31
pub fn parse_version(version: &str) -> Result<Version, Box<Error>> {
32
    let captures = match REGEX.captures(version) {
32
    let captures = match REGEX.captures(version.trim()) {
33 33
        Some(captures) => captures,
34 34
        None => return Err(From::from("Version did not parse properly.")),
35 35
    };

2.2.4 Change error type from Box to String

src/version.rs

3 3
4 4
use regex::Regex;
5 5
6
use std::error::Error;
7
8 6
lazy_static! {
9 7
    static ref REGEX: Regex = {
10 8
        // a numeric identifier is either zero or multiple numbers without a leading zero

28 26
    pub patch: u64,
29 27
}
30 28
31
pub fn parse_version(version: &str) -> Result<Version, Box<Error>> {
29
pub fn parse_version(version: &str) -> Result<Version, String> {
32 30
    let captures = match REGEX.captures(version.trim()) {
33 31
        Some(captures) => captures,
34 32
        None => return Err(From::from("Version did not parse properly.")),

2.2.5 Move lazy_static to lib.rs

src/lib.rs

1 1
extern crate regex;
2 2
3
#[macro_use]
4
extern crate lazy_static;
5
3 6
pub mod version;

src/version.rs

1
#[macro_use]
2
extern crate lazy_static;
3
4 1
use regex::Regex;
5 2
6 3
lazy_static! {

2.2.6 Remove trailing whitespace

Conflicts: src/version.rs

src/version.rs

8 8
        let major = numeric_identifier;
9 9
        let minor = numeric_identifier;
10 10
        let patch = numeric_identifier;
11
11
12 12
        let regex = format!(r"^(?P<major>{})\.(?P<minor>{})\.(?P<patch>{})$", major, minor, patch);
13 13
        let regex = Regex::new(&regex);
14
14
15 15
        // this unwrap is okay because everything above here is const, so this will never fail.
16 16
        regex.unwrap()
17 17
    };

2.2.7 Fix regex

src/version.rs

3 3
lazy_static! {
4 4
    static ref REGEX: Regex = {
5 5
        // a numeric identifier is either zero or multiple numbers without a leading zero
6
        let numeric_identifier = r"0|(:?[1-9][0-9]*)";
6
        let numeric_identifier = r"0|(?:[1-9][0-9]*)";
7 7
8 8
        let major = numeric_identifier;
9 9
        let minor = numeric_identifier;

2.3 Add support for prerelease tags

Semver allows prerelease tags, denoted by appending a hyphen and a series of dot-separated identifiers immediately following the patch version. Let's add support for them! Or at least partial support. This might take a couple steps.

src/version.rs

The allowed characters in the prerelease label are alphanumerics, hyphens, and periods. We'll call that letters_numbers_dash_dot and tack on an optional prerelease version in our regex. Since that line was getting a bit long, we'll add x mode, which allows us to describe the regex over multiple lines, with comments for each important bit.

We also have to add the pre field to the Version struct and capture it from the regex. For now, we're just reading the whole string into an optional single-element vector. Later on, we will have to break it apart into individual dot-separated identifiers.

src/version.rs

9 9
        let minor = numeric_identifier;
10 10
        let patch = numeric_identifier;
11 11
12
        let regex = format!(r"^(?P<major>{})\.(?P<minor>{})\.(?P<patch>{})$", major, minor, patch);
12
        let letters_numbers_dash_dot = r"[-.A-Za-z0-9]+";
13
14
        // This regex does not fully parse prereleases, just extracts the whole prerelease string.
15
        // parse_version() will parse this further.
16
        let pre = letters_numbers_dash_dot;
17
18
        let regex = format!(r"^(?x) # heck yes x mode
19
            (?P<major>{})           # major version
20
            \.                      # dot
21
            (?P<minor>{})           # minor version
22
            \.                      # dot
23
            (?P<patch>{})           # patch version
24
            (?:-(?P<pre>{}))?       # optional prerelease version
25
            $",
26
            major,
27
            minor,
28
            patch,
29
            pre);
13 30
        let regex = Regex::new(&regex);
14 31
15 32
        // this unwrap is okay because everything above here is const, so this will never fail.

21 38
    pub major: u64,
22 39
    pub minor: u64,
23 40
    pub patch: u64,
41
    pub pre: Option<Vec<String>>,
24 42
}
25 43
26 44
pub fn parse_version(version: &str) -> Result<Version, String> {

29 47
        None => return Err(From::from("Version did not parse properly.")),
30 48
    };
31 49
50
    let pre = captures.name("pre").map(|pre| {
51
        vec![pre.to_string()]
52
    });
53
32 54
    Ok(Version {
33 55
        major: captures.name("major").unwrap().parse().unwrap(),
34 56
        minor: captures.name("minor").unwrap().parse().unwrap(),
35 57
        patch: captures.name("patch").unwrap().parse().unwrap(),
58
        pre: pre,
36 59
    })
37 60
}

2.3.1 Add basic prerelease handling

Conflicts: src/version.rs

src/version.rs

9 9
        let minor = numeric_identifier;
10 10
        let patch = numeric_identifier;
11 11
12
        let regex = format!(r"^(?P<major>{})\.(?P<minor>{})\.(?P<patch>{})$", major, minor, patch);
12
        let pre = r"\w+";
13
14
        let regex = format!(r"^(?P<major>{})\.(?P<minor>{})\.(?P<patch>{})(:?-(?P<pre>{}))?$", major, minor, patch, pre);
15
        println!("{}", regex);
13 16
        let regex = Regex::new(&regex);
14 17
15 18
        // this unwrap is okay because everything above here is const, so this will never fail.

21 24
    pub major: u64,
22 25
    pub minor: u64,
23 26
    pub patch: u64,
27
    pub pre: Option<String>,
24 28
}
25 29
26 30
pub fn parse_version(version: &str) -> Result<Version, String> {

33 37
        major: captures.name("major").unwrap().parse().unwrap(),
34 38
        minor: captures.name("minor").unwrap().parse().unwrap(),
35 39
        patch: captures.name("patch").unwrap().parse().unwrap(),
40
        pre: captures.name("pre").map(ToString::to_string)
36 41
    })
37 42
}

2.3.2 Add named pattern to prerelease handling

Conflicts: src/version.rs

src/version.rs

9 9
        let minor = numeric_identifier;
10 10
        let patch = numeric_identifier;
11 11
12
        let pre = r"\w+";
12
        let letters_numbers_dash_dot = r"[.-A-Za-z0-9]+";
13
14
        // This regex does not fully parse prereleases, just extracts the whole prerelease string.
15
        // parse_version() will parse this further.
16
        let pre = letters_numbers_dash_dot;
13 17
14 18
        let regex = format!(r"^(?P<major>{})\.(?P<minor>{})\.(?P<patch>{})(:?-(?P<pre>{}))?$", major, minor, patch, pre);
15 19
        println!("{}", regex);

24 28
    pub major: u64,
25 29
    pub minor: u64,
26 30
    pub patch: u64,
27
    pub pre: Option<String>,
31
    pub pre: Option<Vec<String>>,
28 32
}
29 33
30 34
pub fn parse_version(version: &str) -> Result<Version, String> {

33 37
        None => return Err(From::from("Version did not parse properly.")),
34 38
    };
35 39
40
    let pre = captures.name("pre").map(|pre| {
41
        vec![pre.to_string()]
42
    });
43
36 44
    Ok(Version {
37 45
        major: captures.name("major").unwrap().parse().unwrap(),
38 46
        minor: captures.name("minor").unwrap().parse().unwrap(),
39 47
        patch: captures.name("patch").unwrap().parse().unwrap(),
40
        pre: captures.name("pre").map(ToString::to_string)
48
        pre: pre,
41 49
    })
42 50
}

2.3.3 Remove this println, lol

Conflicts: src/version.rs

src/version.rs

16 16
        let pre = letters_numbers_dash_dot;
17 17
18 18
        let regex = format!(r"^(?P<major>{})\.(?P<minor>{})\.(?P<patch>{})(:?-(?P<pre>{}))?$", major, minor, patch, pre);
19
        println!("{}", regex);
20 19
        let regex = Regex::new(&regex);
21 20
22 21
        // this unwrap is okay because everything above here is const, so this will never fail.

2.3.4 Fix regex for uppercase prerelease and build strings

Conflicts: src/version.rs

src/version.rs

9 9
        let minor = numeric_identifier;
10 10
        let patch = numeric_identifier;
11 11
12
        let letters_numbers_dash_dot = r"[.-A-Za-z0-9]+";
12
        let letters_numbers_dash_dot = r"[-.A-Za-z0-9]+";
13 13
14 14
        // This regex does not fully parse prereleases, just extracts the whole prerelease string.
15 15
        // parse_version() will parse this further.
16 16
        let pre = letters_numbers_dash_dot;
17 17
18
        let regex = format!(r"^(?P<major>{})\.(?P<minor>{})\.(?P<patch>{})(:?-(?P<pre>{}))?$", major, minor, patch, pre);
18
        let regex = format!(r"^(?x) # heck yes x mode
19
            (?P<major>{})           # major version
20
            \.                      # dot
21
            (?P<minor>{})           # minor version
22
            \.                      # dot
23
            (?P<patch>{})           # patch version
24
            (:?-(?P<pre>{}))?       # optional prerelease version
25
            (:?\+(?P<build>{}))?    # optional build metadata
26
            $",
27
            major,
28
            minor,
29
            patch,
30
            pre,
31
            build);
19 32
        let regex = Regex::new(&regex);
20 33
21 34
        // this unwrap is okay because everything above here is const, so this will never fail.

2.3.5 Remove println

2.3.6 Remove build stuff that snuck in

src/version.rs

21 21
            (?P<minor>{})           # minor version
22 22
            \.                      # dot
23 23
            (?P<patch>{})           # patch version
24
            (:?-(?P<pre>{}))?       # optional prerelease version
25
            (:?\+(?P<build>{}))?    # optional build metadata
24
            (?:-(?P<pre>{}))?       # optional prerelease version
26 25
            $",
27 26
            major,
28 27
            minor,
29 28
            patch,
30
            pre,
31
            build);
29
            pre);
32 30
        let regex = Regex::new(&regex);
33 31
34 32
        // this unwrap is okay because everything above here is const, so this will never fail.

2.4 Add support for build tags

This should look familiar.

The build tag is denoted by a plus sign followed by a series of dot-separated identifiers. We'll use the exact same pattern we followed to add prerelease tag support.

src/version.rs

15 15
        // parse_version() will parse this further.
16 16
        let pre = letters_numbers_dash_dot;
17 17
18
        // This regex does not fully parse builds, just extracts the whole build string.
19
        // parse_version() will parse this further.
20
        let build = letters_numbers_dash_dot;
21
18 22
        let regex = format!(r"^(?x) # heck yes x mode
19 23
            (?P<major>{})           # major version
20 24
            \.                      # dot

22 26
            \.                      # dot
23 27
            (?P<patch>{})           # patch version
24 28
            (?:-(?P<pre>{}))?       # optional prerelease version
29
            (?:\+(?P<build>{}))?    # optional build metadata
25 30
            $",
26 31
            major,
27 32
            minor,
28 33
            patch,
29
            pre);
34
            pre,
35
            build);
30 36
        let regex = Regex::new(&regex);
31 37
32 38
        // this unwrap is okay because everything above here is const, so this will never fail.

39 45
    pub minor: u64,
40 46
    pub patch: u64,
41 47
    pub pre: Option<Vec<String>>,
48
    pub build: Option<Vec<String>>,
42 49
}
43 50
44 51
pub fn parse_version(version: &str) -> Result<Version, String> {

51 58
        vec![pre.to_string()]
52 59
    });
53 60
61
    let build = captures.name("build").map(|build| {
62
        vec![build.to_string()]
63
    });
64
54 65
    Ok(Version {
55 66
        major: captures.name("major").unwrap().parse().unwrap(),
56 67
        minor: captures.name("minor").unwrap().parse().unwrap(),
57 68
        patch: captures.name("patch").unwrap().parse().unwrap(),
58 69
        pre: pre,
70
        build: build,
59 71
    })
60 72
}

2.4.1 Add basic build string handling

Conflicts: src/version.rs

src/version.rs

15 15
        // parse_version() will parse this further.
16 16
        let pre = letters_numbers_dash_dot;
17 17
18
        // This regex does not fully parse builds, just extracts the whole build string.
19
        // parse_version() will parse this further.
20
        let build = letters_numbers_dash_dot;
21
18 22
        let regex = format!(r"^(?x) # heck yes x mode
19 23
            (?P<major>{})           # major version
20 24
            \.                      # dot

22 26
            \.                      # dot
23 27
            (?P<patch>{})           # patch version
24 28
            (?:-(?P<pre>{}))?       # optional prerelease version
29
            (:?\+(?P<build>{}))?    # optional build metadata
25 30
            $",
26 31
            major,
27 32
            minor,
28 33
            patch,
29
            pre);
34
            pre,
35
            build);
36
        println!("{}", regex);
30 37
        let regex = Regex::new(&regex);
31 38
32 39
        // this unwrap is okay because everything above here is const, so this will never fail.

39 46
    pub minor: u64,
40 47
    pub patch: u64,
41 48
    pub pre: Option<Vec<String>>,
49
    pub build: Option<Vec<String>>,
42 50
}
43 51
44 52
pub fn parse_version(version: &str) -> Result<Version, String> {

51 59
        vec![pre.to_string()]
52 60
    });
53 61
62
    let build = captures.name("build").map(|build| {
63
        vec![build.to_string()]
64
    });
65
54 66
    Ok(Version {
55 67
        major: captures.name("major").unwrap().parse().unwrap(),
56 68
        minor: captures.name("minor").unwrap().parse().unwrap(),
57 69
        patch: captures.name("patch").unwrap().parse().unwrap(),
58 70
        pre: pre,
71
        build: build,
59 72
    })
60 73
}

2.4.2 Fix regex

Conflicts: src/version.rs

src/version.rs

26 26
            \.                      # dot
27 27
            (?P<patch>{})           # patch version
28 28
            (?:-(?P<pre>{}))?       # optional prerelease version
29
            (:?\+(?P<build>{}))?    # optional build metadata
29
            (?:\+(?P<build>{}))?    # optional build metadata
30 30
            $",
31 31
            major,
32 32
            minor,
33 33
            patch,
34 34
            pre,
35 35
            build);
36
        println!("{}", regex);
37 36
        let regex = Regex::new(&regex);
38 37
39 38
        // this unwrap is okay because everything above here is const, so this will never fail.

2.5 Properly parse prerelease and build tags

Time to fix the types on our prerelease and build tags. Instead of just reading the whole thing into a string, we will break them apart on periods into either alphanumeric or numeric identifiers.

src/version.rs

Semver's prerelease and build tag specification describes tags as either alphanumeric or numeric. Instead of using the type Option<Vec<String>> for the pre and build fields, let's create the Identifier enum. Identifiers may be either AlphaNumeric or Numeric.

Note that we removed the Option around the type of pre and build. Our tests expect just Vec<Identifier>, so we will return an empty vector if nothing is present, rather than a None. The unwrap_or(vec![]) on the lines assigning pre and build assigns them empty vectors when no match was found.

Since we have to parse out the metadata twice, we'll use the parse_meta function. The operation is fairly straightforward. Just split on periods. If all the characters in the string are digits, call it a Numeric, otherwise call it AlphaNumeric.

src/version.rs

44 44
    pub major: u64,
45 45
    pub minor: u64,
46 46
    pub patch: u64,
47
    pub pre: Option<Vec<String>>,
48
    pub build: Option<Vec<String>>,
47
    pub pre: Vec<Identifier>,
48
    pub build: Vec<Identifier>,
49
}
50
51
pub enum Identifier {
52
    /// An identifier that's solely numbers.
53
    Numeric(u64),
54
    /// An identifier with letters and numbers.
55
    AlphaNumeric(String),
49 56
}
50 57
51 58
pub fn parse_version(version: &str) -> Result<Version, String> {

54 61
        None => return Err(From::from("Version did not parse properly.")),
55 62
    };
56 63
57
    let pre = captures.name("pre").map(|pre| {
58
        vec![pre.to_string()]
59
    });
64
    let pre = captures.name("pre").map(parse_meta).unwrap_or(vec![]);
60 65
61
    let build = captures.name("build").map(|build| {
62
        vec![build.to_string()]
63
    });
66
    let build = captures.name("build").map(parse_meta).unwrap_or(vec![]);
64 67
65 68
    Ok(Version {
66 69
        major: captures.name("major").unwrap().parse().unwrap(),

70 73
        build: build,
71 74
    })
72 75
}
76
77
// by the time we get here, we know that it's all valid characters, so this doesn't need to return
78
// a result or anything
79
fn parse_meta(pre: &str) -> Vec<Identifier> {
80
    // Originally, I wanted to implement this method via calling parse, but parse is tolerant of
81
    // leading zeroes, and we want anything with leading zeroes to be considered alphanumeric, not
82
    // numeric. So the strategy is to check with a regex first, and then call parse once we've
83
    // determined that it's a number without a leading zero.
84
    let regex = Regex::new(r"^[1-9][0-9]*$").unwrap();
85
86
    pre.split(".")
87
        .map(|part| {
88
            // another wrinkle: we made sure that any number starts with a non-zero. But there's a
89
            // problem: an actual zero is a number, yet gets left out by this heuristic. So let's
90
            // also check for the single, lone zero.
91
            if regex.is_match(part) || part == "0" {
92
                // we can unwrap here because we know it is only digits due to the regex
93
                Identifier::Numeric(part.parse().unwrap())
94
            } else {
95
                Identifier::AlphaNumeric(part.to_string())
96
            }
97
        }).collect()
98
}

2.5.1 Introduce parse_meta and Identifier for metadata strings

Conflicts: src/version.rs

src/version.rs

44 44
    pub major: u64,
45 45
    pub minor: u64,
46 46
    pub patch: u64,
47
    pub pre: Option<Vec<String>>,
48
    pub build: Option<Vec<String>>,
47
    pub pre: Option<Vec<Identifier>>,
48
    pub build: Option<Vec<Identifier>>,
49
}
50
51
#[derive(Debug,PartialEq)]
52
pub enum Identifier {
53
    /// An identifier that's solely numbers.
54
    Numeric(u64),
55
    /// An identifier with letters and numbers.
56
    AlphaNumeric(String),
49 57
}
50 58
51 59
pub fn parse_version(version: &str) -> Result<Version, String> {

54 62
        None => return Err(From::from("Version did not parse properly.")),
55 63
    };
56 64
57
    let pre = captures.name("pre").map(|pre| {
58
        vec![pre.to_string()]
59
    });
65
    let pre = captures.name("pre").map(parse_meta);
60 66
61
    let build = captures.name("build").map(|build| {
62
        vec![build.to_string()]
63
    });
67
    let build = captures.name("build").map(parse_meta);
64 68
65 69
    Ok(Version {
66 70
        major: captures.name("major").unwrap().parse().unwrap(),

70 74
        build: build,
71 75
    })
72 76
}
77
78
// by the time we get here, we know that it's all valid characters, so this doesn't need to return
79
// a result or anything
80
fn parse_meta(pre: &str) -> Vec<Identifier> {
81
    // Originally, I wanted to implement this method via calling parse, but parse is tolerant of
82
    // leading zeroes, and we want anything with leading zeroes to be considered alphanumeric, not
83
    // numeric. So the strategy is to check with a regex first, and then call parse once we've
84
    // determined that it's a number without a leading zero.
85
    let regex = Regex::new(r"^[1-9][0-9]*$").unwrap();
86
87
    pre.split(".")
88
        .map(|part| {
89
            if regex.is_match(part) {
90
                // we can unwrap here because we know it is only digits due to the regex
91
                Identifier::Numeric(part.parse().unwrap())
92
            } else {
93
                Identifier::AlphaNumeric(part.to_string())
94
            }
95
        }).collect()
96
}

2.5.2 Fix zero checking

src/version.rs

86 86
87 87
    pre.split(".")
88 88
        .map(|part| {
89
            if regex.is_match(part) {
89
            // another wrinkle: we made sure that any number starts with a non-zero. But there's a
90
            // problem: an actual zero is a number, yet gets left out by this heuristic. So let's
91
            // also check for the single, lone zero.
92
            if regex.is_match(part) || part == "0" {
90 93
                // we can unwrap here because we know it is only digits due to the regex
91 94
                Identifier::Numeric(part.parse().unwrap())
92 95
            } else {

2.5.3 Remove early trait

src/version.rs

48 48
    pub build: Option<Vec<Identifier>>,
49 49
}
50 50
51
#[derive(Debug,PartialEq)]
52 51
pub enum Identifier {
53 52
    /// An identifier that's solely numbers.
54 53
    Numeric(u64),

2.5.4 Remove Option from pre and build fields

Conflicts: src/version.rs

src/version.rs

44 44
    pub major: u64,
45 45
    pub minor: u64,
46 46
    pub patch: u64,
47
    pub pre: Option<Vec<Identifier>>,
48
    pub build: Option<Vec<Identifier>>,
47
    pub pre: Vec<Identifier>,
48
    pub build: Vec<Identifier>,
49 49
}
50 50
51 51
pub enum Identifier {

61 61
        None => return Err(From::from("Version did not parse properly.")),
62 62
    };
63 63
64
    let pre = captures.name("pre").map(parse_meta);
64
    let pre = captures.name("pre").map(parse_meta).unwrap_or(vec![]);
65 65
66
    let build = captures.name("build").map(parse_meta);
66
    let build = captures.name("build").map(parse_meta).unwrap_or(vec![]);
67 67
68 68
    Ok(Version {
69 69
        major: captures.name("major").unwrap().parse().unwrap(),

2.6 Add traits to structs and enums in version.rs

We can make Identifier and Version more useful by adding some traits. Traits act like interfaces; the compiler knows that a type with a trait implemented on it can perform certain operations.

src/version.rs

We can add the Clone, Debug, PartialEq, and Eq traits by simply attaching a derive attribute. Rust automatically generates the code for these traits for us.

To add the Display trait, we need to implement it ourselves. We just need to fill out the fmt function for each. The implementation for Version makes use of the write! macro.

src/version.rs

1
use std::fmt;
2
1 3
use regex::Regex;
2 4
3 5
lazy_static! {

40 42
    };
41 43
}
42 44
45
#[derive(Clone, Debug, PartialEq, Eq)]
43 46
pub struct Version {
44 47
    pub major: u64,
45 48
    pub minor: u64,

48 51
    pub build: Vec<Identifier>,
49 52
}
50 53
54
#[derive(Clone, Debug, PartialEq, Eq)]
51 55
pub enum Identifier {
52 56
    /// An identifier that's solely numbers.
53 57
    Numeric(u64),

95 99
                Identifier::AlphaNumeric(part.to_string())
96 100
            }
97 101
        }).collect()
102
103
impl fmt::Display for Version {
104
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
105
        try!(write!(f, "{}.{}.{}", self.major, self.minor, self.patch));
106
        if !self.pre.is_empty() {
107
            let strs: Vec<_> =
108
                self.pre.iter().map(ToString::to_string).collect();
109
            try!(write!(f, "-{}", strs.join(".")));
110
        }
111
        if !self.build.is_empty() {
112
            let strs: Vec<_> =
113
                self.build.iter().map(ToString::to_string).collect();
114
            try!(write!(f, "+{}", strs.join(".")));
115
        }
116
        Ok(())
117
    }
118
}
119
120
impl fmt::Display for Identifier {
121
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
122
        match *self {
123
            Identifier::Numeric(ref id) => id.fmt(f),
124
            Identifier::AlphaNumeric(ref id) => id.fmt(f),
125
        }
126
    }
98 127
}

2.6.1 Implement traits for Identifier and Version

Conflicts: src/version.rs

src/version.rs

1
use std::fmt;
2
1 3
use regex::Regex;
2 4
3 5
lazy_static! {

40 42
    };
41 43
}
42 44
45
#[derive(Clone, Debug, PartialEq, Eq)]
43 46
pub struct Version {
44 47
    pub major: u64,
45 48
    pub minor: u64,

48 51
    pub build: Vec<Identifier>,
49 52
}
50 53
54
#[derive(Clone, Debug, PartialEq, Eq)]
51 55
pub enum Identifier {
52 56
    /// An identifier that's solely numbers.
53 57
    Numeric(u64),

95 99
                Identifier::AlphaNumeric(part.to_string())
96 100
            }
97 101
        }).collect()
102
103
impl fmt::Display for Version {
104
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
105
        try!(write!(f, "{}.{}.{}", self.major, self.minor, self.patch));
106
        if !self.pre.is_empty() {
107
            let strs: Vec<_> =
108
                self.pre.iter().map(ToString::to_string).collect();
109
            try!(write!(f, "-{}", strs.join(".")));
110
        }
111
        if !self.build.is_empty() {
112
            let strs: Vec<_> =
113
                self.build.iter().map(ToString::to_string).collect();
114
            try!(write!(f, "+{}", strs.join(".")));
115
        }
116
        Ok(())
117
    }
118
}
119
120
impl fmt::Display for Identifier {
121
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
122
        match *self {
123
            Identifier::Numeric(ref id) => id.fmt(f),
124
            Identifier::AlphaNumeric(ref id) => id.fmt(f),
125
        }
126
    }
98 127
}

2.7 Finish up, move parse_meta to common.rs

We're almost done! Just some little cleanup things remain.

src/version.rs

Turns out our tests and the semver package we're writing this for expect the function to be called parse rather than parse_version. We'll fix that now.

Also, it seems likely that the parse_meta function will be useful in the range parsing module we will be making in the next step. Let's move that to a new file, common.rs.

Now that parse_meta livse in another file, we will use common and call it with common::parse_meta.

src/lib.rs

Add in the (private) common module. We don't need to add this to our public interface.

src/common.rs

Let's make a couple changes to parse_meta while we're at it. First, we will change the argument name from pre to s, since it is used for both prerelease and build tags. We'll also split out the alphanumeric check to a separate function. Finally, we need to accept a plain number 0 as a Numeric, so a slight adjustment is needed on the regex here.

src/common.rs

1
use regex::Regex;
2
use version::Identifier;
3
4
// by the time we get here, we know that it's all valid characters, so this doesn't need to return
5
// a result or anything
6
pub fn parse_meta(s: &str) -> Vec<Identifier> {
7
    // Originally, I wanted to implement this method via calling parse, but parse is tolerant of
8
    // leading zeroes, and we want anything with leading zeroes to be considered alphanumeric, not
9
    // numeric. So the strategy is to check with a regex first, and then call parse once we've
10
    // determined that it's a number without a leading zero.
11
    s.split(".")
12
        .map(|part| {
13
            // another wrinkle: we made sure that any number starts with a
14
            // non-zero. But there's a problem: an actual zero is a number, yet
15
            // gets left out by this heuristic. So let's also check for the
16
            // single, lone zero.
17
            if is_alpha_numeric(part) {
18
                Identifier::AlphaNumeric(part.to_string())
19
            } else {
20
                // we can unwrap here because we know it is only digits due to the regex
21
                Identifier::Numeric(part.parse().unwrap())
22
            }
23
        }).collect()
24
}
25
26
pub fn is_alpha_numeric(s: &str) -> bool {
27
    lazy_static! {
28
        static ref REGEX: Regex = Regex::new(r"^(0|[1-9][0-9]*)$").unwrap();
29
    };
30
    !REGEX.is_match(s)
31
}

src/lib.rs

4 4
extern crate lazy_static;
5 5
6 6
pub mod version;
7
8
// for private stuff the two share
9
mod common;

src/version.rs

1 1
use std::fmt;
2 2
3 3
use regex::Regex;
4
use common;
4 5
5 6
lazy_static! {
6 7
    static ref REGEX: Regex = {

59 60
    AlphaNumeric(String),
60 61
}
61 62
62
pub fn parse_version(version: &str) -> Result<Version, String> {
63
pub fn parse(version: &str) -> Result<Version, String> {
63 64
    let captures = match REGEX.captures(version.trim()) {
64 65
        Some(captures) => captures,
65 66
        None => return Err(From::from("Version did not parse properly.")),
66 67
    };
67 68
68
    let pre = captures.name("pre").map(parse_meta).unwrap_or(vec![]);
69
    let pre = captures.name("pre").map(common::parse_meta).unwrap_or(vec![]);
69 70
70
    let build = captures.name("build").map(parse_meta).unwrap_or(vec![]);
71
    let build = captures.name("build").map(common::parse_meta).unwrap_or(vec![]);
71 72
72 73
    Ok(Version {
73 74
        major: captures.name("major").unwrap().parse().unwrap(),

78 79
    })
79 80
}
80 81
81
// by the time we get here, we know that it's all valid characters, so this doesn't need to return
82
// a result or anything
83
fn parse_meta(pre: &str) -> Vec<Identifier> {
84
    // Originally, I wanted to implement this method via calling parse, but parse is tolerant of
85
    // leading zeroes, and we want anything with leading zeroes to be considered alphanumeric, not
86
    // numeric. So the strategy is to check with a regex first, and then call parse once we've
87
    // determined that it's a number without a leading zero.
88
    let regex = Regex::new(r"^[1-9][0-9]*$").unwrap();
89
90
    pre.split(".")
91
        .map(|part| {
92
            // another wrinkle: we made sure that any number starts with a non-zero. But there's a
93
            // problem: an actual zero is a number, yet gets left out by this heuristic. So let's
94
            // also check for the single, lone zero.
95
            if regex.is_match(part) || part == "0" {
96
                // we can unwrap here because we know it is only digits due to the regex
97
                Identifier::Numeric(part.parse().unwrap())
98
            } else {
99
                Identifier::AlphaNumeric(part.to_string())
100
            }
101
        }).collect()
102
103 82
impl fmt::Display for Version {
104 83
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
105 84
        try!(write!(f, "{}.{}.{}", self.major, self.minor, self.patch));

2.7.1 Move parse_meta to common.rs and rename parse_version

Conflicts: src/version.rs

src/common.rs

1
use regex::Regex;
2
use version::Identifier;
3
4
// by the time we get here, we know that it's all valid characters, so this doesn't need to return
5
// a result or anything
6
pub fn parse_meta(pre: &str) -> Vec<Identifier> {
7
    // Originally, I wanted to implement this method via calling parse, but parse is tolerant of
8
    // leading zeroes, and we want anything with leading zeroes to be considered alphanumeric, not
9
    // numeric. So the strategy is to check with a regex first, and then call parse once we've
10
    // determined that it's a number without a leading zero.
11
    let regex = Regex::new(r"^[1-9][0-9]*$").unwrap();
12
13
    pre.split(".")
14
        .map(|part| {
15
            // another wrinkle: we made sure that any number starts with a non-zero. But there's a
16
            // problem: an actual zero is a number, yet gets left out by this heuristic. So let's
17
            // also check for the single, lone zero.
18
            if regex.is_match(part) || part == "0" {
19
                // we can unwrap here because we know it is only digits due to the regex
20
                Identifier::Numeric(part.parse().unwrap())
21
            } else {
22
                Identifier::AlphaNumeric(part.to_string())
23
            }
24
        }).collect()
25
}
26

src/lib.rs

4 4
extern crate lazy_static;
5 5
6 6
pub mod version;
7
8
// for private stuff the two share
9
mod common;

src/version.rs

1 1
use std::fmt;
2 2
3 3
use regex::Regex;
4
use common;
4 5
5 6
lazy_static! {
6 7
    static ref REGEX: Regex = {

59 60
    AlphaNumeric(String),
60 61
}
61 62
62
pub fn parse_version(version: &str) -> Result<Version, String> {
63
pub fn parse(version: &str) -> Result<Version, String> {
63 64
    let captures = match REGEX.captures(version.trim()) {
64 65
        Some(captures) => captures,
65 66
        None => return Err(From::from("Version did not parse properly.")),
66 67
    };
67 68
68
    let pre = captures.name("pre").map(parse_meta).unwrap_or(vec![]);
69
    let pre = captures.name("pre").map(common::parse_meta).unwrap_or(vec![]);
69 70
70
    let build = captures.name("build").map(parse_meta).unwrap_or(vec![]);
71
    let build = captures.name("build").map(common::parse_meta).unwrap_or(vec![]);
71 72
72 73
    Ok(Version {
73 74
        major: captures.name("major").unwrap().parse().unwrap(),

78 79
    })
79 80
}
80 81
81
// by the time we get here, we know that it's all valid characters, so this doesn't need to return
82
// a result or anything
83
fn parse_meta(pre: &str) -> Vec<Identifier> {
84
    // Originally, I wanted to implement this method via calling parse, but parse is tolerant of
85
    // leading zeroes, and we want anything with leading zeroes to be considered alphanumeric, not
86
    // numeric. So the strategy is to check with a regex first, and then call parse once we've
87
    // determined that it's a number without a leading zero.
88
    let regex = Regex::new(r"^[1-9][0-9]*$").unwrap();
89
90
    pre.split(".")
91
        .map(|part| {
92
            // another wrinkle: we made sure that any number starts with a non-zero. But there's a
93
            // problem: an actual zero is a number, yet gets left out by this heuristic. So let's
94
            // also check for the single, lone zero.
95
            if regex.is_match(part) || part == "0" {
96
                // we can unwrap here because we know it is only digits due to the regex
97
                Identifier::Numeric(part.parse().unwrap())
98
            } else {
99
                Identifier::AlphaNumeric(part.to_string())
100
            }
101
        }).collect()
102
103 82
impl fmt::Display for Version {
104 83
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
105 84
        try!(write!(f, "{}.{}.{}", self.major, self.minor, self.patch));

2.7.2 Update parse_meta with is_alpha_numeric

src/common.rs

3 3
4 4
// by the time we get here, we know that it's all valid characters, so this doesn't need to return
5 5
// a result or anything
6
pub fn parse_meta(pre: &str) -> Vec<Identifier> {
6
pub fn parse_meta(s: &str) -> Vec<Identifier> {
7 7
    // Originally, I wanted to implement this method via calling parse, but parse is tolerant of
8 8
    // leading zeroes, and we want anything with leading zeroes to be considered alphanumeric, not
9 9
    // numeric. So the strategy is to check with a regex first, and then call parse once we've
10 10
    // determined that it's a number without a leading zero.
11
    let regex = Regex::new(r"^[1-9][0-9]*$").unwrap();
12
13
    pre.split(".")
11
    s.split(".")
14 12
        .map(|part| {
15
            // another wrinkle: we made sure that any number starts with a non-zero. But there's a
16
            // problem: an actual zero is a number, yet gets left out by this heuristic. So let's
17
            // also check for the single, lone zero.
18
            if regex.is_match(part) || part == "0" {
13
            // another wrinkle: we made sure that any number starts with a
14
            // non-zero. But there's a problem: an actual zero is a number, yet
15
            // gets left out by this heuristic. So let's also check for the
16
            // single, lone zero.
17
            if is_alpha_numeric(part) {
18
                Identifier::AlphaNumeric(part.to_string())
19
            } else {
19 20
                // we can unwrap here because we know it is only digits due to the regex
20 21
                Identifier::Numeric(part.parse().unwrap())
21
            } else {
22
                Identifier::AlphaNumeric(part.to_string())
23 22
            }
24 23
        }).collect()
25 24
}
26 25
26
pub fn is_alpha_numeric(s: &str) -> bool {
27
    lazy_static! {
28
        static ref REGEX: Regex = Regex::new(r"^(0|[1-9][0-9]*)$").unwrap();
29
    };
30
    !REGEX.is_match(s)
31
}

3 Implement `range::parse`

The range::parse function takes a string containing one or more range specifications and returns a VersionReq with the parsed representation.

The VersionReq contains a vector of Predicate. For a semver version to match the range, it must match each predicate. We don't have to do the matching, that's handled by the semver crate. Here, we just need to parse the string into the individual predicates and return the result.

Predicate is similar to the Version struct we worked with in the version parsing module. It contains the major, minor, patch, and pre fields, but their types are a little different. Minor and patch versions are now optional, since a range can omit them.

There is also an Op field. Op stands for operator. Ranges look like a version string with an optional operator in front. The operators are defined in the Cargo documentation. One of the major, minor, or patch versions can also be a wildcard. If a wildcard is used, the Wildcard operator is chosen.

Let's go through the creation of range::parse step-by-step. Similar to our tutorial for version::parse, we will start with a partial implementation and fill it out as we move along. By the end, we will be passing our entire test suite.

Click the plus sign below to view the step-by-step guide.

But where are those tests!?

They're all listed in the final step of this guide. There are so many that they clutter up the diffs and make the guide hard to follow.

src/lib.rs

4 4
extern crate lazy_static;
5 5
6 6
pub mod version;
7
pub mod range;
7 8
8 9
// for private stuff the two share
9 10
mod common;

src/range.rs

1
use regex::Regex;
2
use common;
3
use version::Identifier;
4
use std::str::FromStr;
5
use std::error::Error;
6
use std::num::ParseIntError;
7
8
lazy_static! {
9
    static ref REGEX: Regex = {
10
        // an operation can be:
11
        //
12
        // * =
13
        // * >
14
        // * >=
15
        // * <
16
        // * <=
17
        // * ~
18
        // * ^
19
        let operation = r"=|>|>=|<|<=|~|\^";
20
21
        // a numeric identifier is either zero or multiple numbers without a leading zero
22
        let numeric_identifier = r"0|[1-9][0-9]*";
23
24
        let major = numeric_identifier;
25
26
        // minor can be either a number or a wildcard. *, x, and X are wildcards.
27
        let minor = format!(r"{}|\*|[xX]", numeric_identifier);
28
29
        // patch can be either a number or a wildcard. *, x, and X are wildcards.
30
        let patch = format!(r"{}|\*|[xX]", numeric_identifier);
31
32
        let letters_numbers_dash_dot = r"[-.A-Za-z0-9]+";
33
34
        // This regex does not fully parse prereleases, just extracts the whole prerelease string.
35
        // parse_version() will parse this further.
36
        let pre = letters_numbers_dash_dot;
37
38
        // This regex does not fully parse builds, just extracts the whole build string.
39
        // parse_version() will parse this further.
40
        let build = letters_numbers_dash_dot;
41
42
        let regex = format!(r"(?x) # heck yes x mode
43
            ^\s*                    # leading whitespace
44
            (?P<operation>{})?\s*   # optional operation
45
            (?P<major>{})           # major version
46
            (?:\.(?P<minor>{}))?    # optional dot and then minor
47
            (?:\.(?P<patch>{}))?    # optional dot and then patch
48
            (?:-(?P<pre>{}))?       # optional prerelease version
49
            (?:\+(?P<build>{}))?    # optional build metadata
50
            \s*$                    # trailing whitespace
51
            ",
52
            operation,
53
            major,
54
            minor,
55
            patch,
56
            pre,
57
            build);
58
        let regex = Regex::new(&regex);
59
60
        // this unwrap is okay because everything above here is const, so this will never fail.
61
        regex.unwrap()
62
    };
63
}
64
65
#[derive(Debug)]
66
pub struct VersionReq {
67
    pub predicates: Vec<Predicate>,
68
}
69
70
#[derive(PartialEq,Debug)]
71
pub enum WildcardVersion {
72
    Major,
73
    Minor,
74
    Patch,
75
}
76
77
#[derive(PartialEq,Debug)]
78
pub enum Op {
79
    Ex, // Exact
80
    Gt, // Greater than
81
    GtEq, // Greater than or equal to
82
    Lt, // Less than
83
    LtEq, // Less than or equal to
84
    Tilde, // e.g. ~1.0.0
85
    Compatible, // compatible by definition of semver, indicated by ^
86
    Wildcard(WildcardVersion), // x.y.*, x.*, *
87
}
88
89
impl FromStr for Op {
90
    type Err = String;
91
92
    fn from_str(s: &str) -> Result<Op, String> {
93
        match s {
94
            "=" => Ok(Op::Ex),
95
            ">" => Ok(Op::Gt),
96
            ">=" => Ok(Op::GtEq),
97
            "<" => Ok(Op::Lt),
98
            "<=" => Ok(Op::LtEq),
99
            "~" => Ok(Op::Tilde),
100
            "^" => Ok(Op::Compatible),
101
            _ => Err(String::from("Could not parse Op")),
102
        }
103
    }
104
}
105
106
#[derive(PartialEq,Debug)]
107
pub struct Predicate {
108
    pub op: Op,
109
    pub major: u64,
110
    pub minor: Option<u64>,
111
    pub patch: Option<u64>,
112
    pub pre: Vec<Identifier>,
113
}
114
115
pub fn parse(ranges: &str) -> Result<VersionReq, String> {
116
    // null is an error
117
    if ranges == "\0" {
118
        return Err(String::from("Null is not a valid VersionReq"));
119
    }
120
121
    // an empty range is a major version wildcard
122
    // so is a lone * or x of either capitalization
123
    if (ranges == "")
124
    || (ranges == "*")
125
    || (ranges == "x")
126
    || (ranges == "X") {
127
        return Ok(VersionReq {
128
            predicates: vec![Predicate {
129
                op: Op::Wildcard(WildcardVersion::Major),
130
                major: 0,
131
                minor: None,
132
                patch: None,
133
                pre: Vec::new(),
134
            }],
135
        });
136
    }
137
138
139
    let ranges = ranges.trim();
140
141
    let predicates: Result<Vec<_>, String> = ranges
142
        .split(",")
143
        .map(|range| {
144
            parse_predicate(range)
145
        })
146
        .collect();
147
148
    let predicates = try!(predicates);
149
150
    if predicates.len() == 0 {
151
        return Err(String::from("VersionReq did not parse properly"));
152
    }
153
154
    Ok(VersionReq {
155
        predicates: predicates,
156
    })
157
}
158
159
pub fn parse_predicate(range: &str) -> Result<Predicate, String> {
160
    let captures = match REGEX.captures(range.trim()) {
161
        Some(captures) => captures,
162
        None => return Err(From::from("VersionReq did not parse properly.")),
163
    };
164
165
    // operations default to Compatible
166
    // unwrap is okay because we validate that we only have correct strings in the regex
167
    let mut operation = captures.name("operation")
168
                                .map(str::parse)
169
                                .map(Result::unwrap)
170
                                .unwrap_or(Op::Compatible);
171
172
    // unwrap is okay because we always have major
173
    let major: Result<_, ParseIntError> = captures.name("major")
174
                        .unwrap()
175
                        .parse();
176
177
    let major = match major {
178
                            Ok(number) => number,
179
                            Err(err) => return Err("Error parsing major version number: ".to_string() + err.description())
180
                };
181
182
    let minor = match captures.name("minor") {
183
        Some(minor) => {
184
            match minor.parse::<u64>() {
185
                Ok(number) => Some(number),
186
                Err(err) => {
187
                    match minor {
188
                        "*" | "x" | "X"  => {
189
                            operation = Op::Wildcard(WildcardVersion::Minor);
190
                            None
191
                        },
192
                        _ => return Err("Error parsing minor version number: ".to_string() + err.description()),
193
                    }
194
                },
195
            }
196
        },
197
        None => None,
198
    };
199
200
    let patch = match captures.name("patch") {
201
        Some(patch) => {
202
            match patch.parse::<u64>() {
203
                Ok(number) => Some(number),
204
                Err(err) => {
205
                    match patch {
206
                        "*" | "x" | "X"  => {
207
                            operation = Op::Wildcard(WildcardVersion::Patch);
208
                            None
209
                        },
210
                        _ => return Err("Error parsing patch version number: ".to_string() + err.description()),
211
                    }
212
                },
213
            }
214
        },
215
        None => None,
216
    };
217
218
    let pre = captures.name("pre").map(common::parse_meta).unwrap_or_else(Vec::new);
219
220
    Ok(Predicate {
221
        op: operation,
222
        major: major,
223
        minor: minor,
224
        patch: patch,
225
        pre: pre,
226
    })
227
}

3.1 Start out with the range-matching regex

This is going to be a big file, before we get a working version, let's just introduce the pieces in smaller chunks to make sure we explain each part.

src/lib.rs

We need to add the range module to our public interface.

src/range.rs

Our use of lazy_static! is similar to the way we used it in version parsing. The main differences are we now look for an operation before the major version, and each of the major, minor, and patch versions can be either a number or a wildcard.

There is no matching for build tags, either. Apparently build tags are not to be used on range specifications...or are they?

src/lib.rs

4 4
extern crate lazy_static;
5 5
6 6
pub mod version;
7
pub mod range;
7 8
8 9
// for private stuff the two share
9 10
mod common;

src/range.rs

1
use regex::Regex;
2
3
lazy_static! {
4
    static ref REGEX: Regex = {
5
        // an operation can be:
6
        //
7
        // * =
8
        // * >
9
        // * >=
10
        // * <
11
        // * <=
12
        // * ~
13
        // * ^
14
        let operation = r"=|>|>=|<|<=|~|\^";
15
16
        // a numeric identifier is either zero or multiple numbers without a leading zero
17
        let numeric_identifier = r"0|[1-9][0-9]*";
18
19
        let major = numeric_identifier;
20
21
        // minor can be either a number or a wildcard. *, x, and X are wildcards.
22
        let minor = format!(r"{}|\*|[xX]", numeric_identifier);
23
24
        // patch can be either a number or a wildcard. *, x, and X are wildcards.
25
        let patch = format!(r"{}|\*|[xX]", numeric_identifier);
26
27
        let letters_numbers_dash_dot = r"[-.A-Za-z0-9]+";
28
29
        // This regex does not fully parse prereleases, just extracts the whole prerelease string.
30
        // parse_version() will parse this further.
31
        let pre = letters_numbers_dash_dot;
32
33
        let regex = format!(r"(?x) # heck yes x mode
34
            ^\s*                    # leading whitespace
35
            (?P<operation>{})?\s*   # optional operation
36
            (?P<major>{})           # major version
37
            (?:\.(?P<minor>{}))?    # optional dot and then minor
38
            (?:\.(?P<patch>{}))?    # optional dot and then patch
39
            (?:-(?P<pre>{}))?       # optional prerelease version
40
            \s*$                    # trailing whitespace
41
            ",
42
            operation,
43
            major,
44
            minor,
45
            patch,
46
            pre);
47
        let regex = Regex::new(&regex);
48
49
        // this unwrap is okay because everything above here is const, so this will never fail.
50
        regex.unwrap()
51
    };
52
}

3.1.1 Begin range parsing with regex to match strings

src/lib.rs

4 4
extern crate lazy_static;
5 5
6 6
pub mod version;
7
pub mod range;
7 8
8 9
// for private stuff the two share
9 10
mod common;

src/range.rs

1
use regex::Regex;
2
3
lazy_static! {
4
    static ref REGEX: Regex = {
5
        // an operation can be:
6
        //
7
        // * =
8
        // * >
9
        // * >=
10
        // * <
11
        // * <=
12
        // * ~
13
        // * ^
14
        let operation = r"=|>|(:?>=)|<|(:?<=)|~|\^";
15
16
        // a numeric identifier is either zero or multiple numbers without a leading zero
17
        let numeric_identifier = r"0|(:?[1-9][0-9]*)";
18
19
        let major = numeric_identifier;
20
21
        // minor can be either a number or a wildcard. *, x, and X are wildcards.
22
        let minor = format!(r"(:?{})|\*|[xX]", numeric_identifier);
23
24
        // patch can be either a number or a wildcard. *, x, and X are wildcards.
25
        let patch = format!(r"(:?{})|\*|[xX]", numeric_identifier);
26
27
        let letters_numbers_dash_dot = r"[-.A-Za-z0-9]+";
28
29
        // This regex does not fully parse prereleases, just extracts the whole prerelease string.
30
        // parse_version() will parse this further.
31
        let pre = letters_numbers_dash_dot;
32
33
        let regex = format!(r"^(?x) # heck yes x mode
34
            (?P<operation>{})?\s*   # optional operation
35
            (?P<major>{})           # major version
36
            (:?\.(?P<minor>{}))?    # optional dot and then minor
37
            (:?\.(?P<patch>{}))?    # optional dot and then patch
38
            (:?-(?P<pre>{}))?       # optional prerelease version
39
            $",
40
            operation,
41
            major,
42
            minor,
43
            patch,
44
            pre);
45
        let regex = Regex::new(&regex);
46
47
        // this unwrap is okay because everything above here is const, so this will never fail.
48
        regex.unwrap()
49
    };
50
}

3.1.2 Fix some regexes

Conflicts: src/range.rs

src/range.rs

11 11
        // * <=
12 12
        // * ~
13 13
        // * ^
14
        let operation = r"=|>|(:?>=)|<|(:?<=)|~|\^";
14
        let operation = r"=|>|>=|<|<=|~|\^";
15 15
16 16
        // a numeric identifier is either zero or multiple numbers without a leading zero
17
        let numeric_identifier = r"0|(:?[1-9][0-9]*)";
17
        let numeric_identifier = r"0|[1-9][0-9]*";
18 18
19 19
        let major = numeric_identifier;
20 20
21 21
        // minor can be either a number or a wildcard. *, x, and X are wildcards.
22
        let minor = format!(r"(:?{})|\*|[xX]", numeric_identifier);
22
        let minor = format!(r"{}|\*|[xX]", numeric_identifier);
23 23
24 24
        // patch can be either a number or a wildcard. *, x, and X are wildcards.
25
        let patch = format!(r"(:?{})|\*|[xX]", numeric_identifier);
25
        let patch = format!(r"{}|\*|[xX]", numeric_identifier);
26 26
27 27
        let letters_numbers_dash_dot = r"[-.A-Za-z0-9]+";
28 28
29 29
        // This regex does not fully parse prereleases, just extracts the whole prerelease string.
30 30
        // parse_version() will parse this further.
31 31
        let pre = letters_numbers_dash_dot;
32
33
        let regex = format!(r"^(?x) # heck yes x mode
32
33
        let regex = format!(r"(?x) # heck yes x mode
34
            ^\s*                    # leading whitespace
34 35
            (?P<operation>{})?\s*   # optional operation
35 36
            (?P<major>{})           # major version
36
            (:?\.(?P<minor>{}))?    # optional dot and then minor
37
            (:?\.(?P<patch>{}))?    # optional dot and then patch
38
            (:?-(?P<pre>{}))?       # optional prerelease version
39
            $",
37
            (?:\.(?P<minor>{}))?    # optional dot and then minor
38
            (?:\.(?P<patch>{}))?    # optional dot and then patch
39
            (?:-(?P<pre>{}))?       # optional prerelease version
40
            \s*$                    # trailing whitespace
41
            ",
40 42
            operation,
41 43
            major,
42 44
            minor,
43 45
            patch,
44 46
            pre);
45 47
        let regex = Regex::new(&regex);
46
48
47 49
        // this unwrap is okay because everything above here is const, so this will never fail.
48 50
        regex.unwrap()
49 51
    };

3.2 Add enums and structs for range parsing

Let's define all the nifty types we'll need!

src/range.rs

We've described some of these back in the introduction to range parsing, but a little more won't hurt.

VersionReq contains one or more predicates. This is what we need to supply to the semver crate.

Predicate is similar to the Version struct we used in the version parsing module, but the types are slightly different. We have added the Op field and the minor and patch versions are now optional.

Op stands for operator. The operator defines how to match a specific version to this predicate.

WildcardVersion is used for the Wildcard operator. There might be a wildcard at the major, minor, or patch version. Since they are numeric types, we don't assign Wildcard to the field directly, instead we list it as the operator.

We'll also pull in version::Identifier, for use with the prerelease strings. I wonder if that should have been common::Identifier. Hmm.

src/range.rs

1 1
use regex::Regex;
2
use version::Identifier;
2 3
3 4
lazy_static! {
4 5
    static ref REGEX: Regex = {

50 51
        regex.unwrap()
51 52
    };
52 53
}
54
55
pub struct VersionReq {
56
    pub predicates: Vec<Predicate>,
57
}
58
59
pub enum WildcardVersion {
60
    Major,
61
    Minor,
62
    Patch,
63
}
64
65
pub enum Op {
66
    Ex, // Exact
67
    Gt, // Greater than
68
    GtEq, // Greater than or equal to
69
    Lt, // Less than
70
    LtEq, // Less than or equal to
71
    Tilde, // e.g. ~1.0.0
72
    Compatible, // compatible by definition of semver, indicated by ^
73
    Wildcard(WildcardVersion), // x.y.*, x.*, *
74
}
75
76
pub struct Predicate {
77
    pub op: Op,
78
    pub major: u64,
79
    pub minor: Option<u64>,
80
    pub patch: Option<u64>,
81
    pub pre: Vec<Identifier>,
82
}

3.2.1 Add structs and enums for ranges

src/range.rs

1 1
use regex::Regex;
2
use version::Identifier;
2 3
3 4
lazy_static! {
4 5
    static ref REGEX: Regex = {

50 51
        regex.unwrap()
51 52
    };
52 53
}
54
55
pub struct VersionReq {
56
    pub predicates: Vec<Predicate>,
57
}
58
59
pub enum WildcardVersion {
60
    Major,
61
    Minor,
62
    Patch,
63
}
64
65
pub enum Op {
66
    Ex, // Exact
67
    Gt, // Greater than
68
    GtEq, // Greater than or equal to
69
    Lt, // Less than
70
    LtEq, // Less than or equal to
71
    Tilde, // e.g. ~1.0.0
72
    Compatible, // compatible by definition of semver, indicated by ^
73
    Wildcard(WildcardVersion), // x.y.*, x.*, *
74
}
75
76
pub struct Predicate {
77
    op: Op,
78
    major: u64,
79
    minor: Option<u64>,
80
    patch: Option<u64>,
81
    pre: Vec<Identifier>,
82
}

3.2.2 Make predicate fields public

src/range.rs

74 74
}
75 75
76 76
pub struct Predicate {
77
    op: Op,
78
    major: u64,
79
    minor: Option<u64>,
80
    patch: Option<u64>,
81
    pre: Vec<Identifier>,
77
    pub op: Op,
78
    pub major: u64,
79
    pub minor: Option<u64>,
80
    pub patch: Option<u64>,
81
    pub pre: Vec<Identifier>,
82 82
}

3.3 Add traits for range parsing

Implementing traits will give our new structs and enums some extra behavior. Most of them come along automatically!

src/range.rs

We can derive Debug and PartialEq with just the derive annotation.

The FromStr trait lets us turn a string into a value of the type. So when we find an equal sign, we can automatically turn it into the Op::Ex operator.

src/range.rs

1 1
use regex::Regex;
2 2
use version::Identifier;
3
use std::str::FromStr;
3 4
4 5
lazy_static! {
5 6
    static ref REGEX: Regex = {

52 53
    };
53 54
}
54 55
56
#[derive(Debug)]
55 57
pub struct VersionReq {
56 58
    pub predicates: Vec<Predicate>,
57 59
}
58 60
61
#[derive(PartialEq,Debug)]
59 62
pub enum WildcardVersion {
60 63
    Major,
61 64
    Minor,
62 65
    Patch,
63 66
}
64 67
68
#[derive(PartialEq,Debug)]
65 69
pub enum Op {
66 70
    Ex, // Exact
67 71
    Gt, // Greater than

73 77
    Wildcard(WildcardVersion), // x.y.*, x.*, *
74 78
}
75 79
80
impl FromStr for Op {
81
    type Err = String;
82
83
    fn from_str(s: &str) -> Result<Op, String> {
84
        match s {
85
            "=" => Ok(Op::Ex),
86
            ">" => Ok(Op::Gt),
87
            ">=" => Ok(Op::GtEq),
88
            "<" => Ok(Op::Lt),
89
            "<=" => Ok(Op::LtEq),
90
            "~" => Ok(Op::Tilde),
91
            "^" => Ok(Op::Compatible),
92
            _ => Err(String::from("Could not parse Op")),
93
        }
94
    }
95
}
96
97
#[derive(PartialEq,Debug)]
76 98
pub struct Predicate {
77 99
    pub op: Op,
78 100
    pub major: u64,

3.3.1 Add traits for range structs

src/range.rs

1 1
use regex::Regex;
2 2
use version::Identifier;
3
use std::str::FromStr;
3 4
4 5
lazy_static! {
5 6
    static ref REGEX: Regex = {

56 57
    pub predicates: Vec<Predicate>,
57 58
}
58 59
60
#[derive(PartialEq,Debug)]
59 61
pub enum WildcardVersion {
60 62
    Major,
61 63
    Minor,
62 64
    Patch,
63 65
}
64 66
67
#[derive(PartialEq,Debug)]
65 68
pub enum Op {
66 69
    Ex, // Exact
67 70
    Gt, // Greater than

73 76
    Wildcard(WildcardVersion), // x.y.*, x.*, *
74 77
}
75 78
79
impl FromStr for Op {
80
    type Err = String;
81
82
    fn from_str(s: &str) -> Result<Op, String> {
83
        match s {
84
            "=" => Ok(Op::Ex),
85
            ">" => Ok(Op::Gt),
86
            ">=" => Ok(Op::GtEq),
87
            "<" => Ok(Op::Lt),
88
            "<=" => Ok(Op::LtEq),
89
            "~" => Ok(Op::Tilde),
90
            "^" => Ok(Op::Compatible),
91
            _ => Err(String::from("Could not parse Op")),
92
        }
93
    }
94
}
95
96
#[derive(PartialEq,Debug)]
76 97
pub struct Predicate {
77 98
    pub op: Op,
78 99
    pub major: u64,

3.3.2 Add Debug for VersionReq

src/range.rs

53 53
    };
54 54
}
55 55
56
#[derive(Debug)]
56 57
pub struct VersionReq {
57 58
    pub predicates: Vec<Predicate>,
58 59
}

3.4 Add the parsing function, with just major wildcard support

Finally, it's here! The function that does the real work. We'll put this together in a couple steps to keep things simple.

src/range.rs

So, we parse the range string into a Result<VersionReq, String>. This is similar to the signature of version::Parse.

If the string was null, we'll just respond with an error.

The only kind of version we'll accept for now is a wildcard in the major version number. This can be represented in four different ways. We just create a single-element vector with a Predicate inside and return it.

src/range.rs

102 102
    pub patch: Option<u64>,
103 103
    pub pre: Vec<Identifier>,
104 104
}
105
106
pub fn parse(ranges: &str) -> Result<VersionReq, String> {
107
    // null is an error
108
    if ranges == "\0" {
109
        return Err(String::from("Null is not a valid VersionReq"));
110
    }
111
112
    // an empty range is a major version wildcard
113
    // so is a lone * or x of either capitalization
114
    if (ranges == "")
115
    || (ranges == "*")
116
    || (ranges == "x")
117
    || (ranges == "X") {
118
        return Ok(VersionReq {
119
            predicates: vec![Predicate {
120
                op: Op::Wildcard(WildcardVersion::Major),
121
                major: 0,
122
                minor: None,
123
                patch: None,
124
                pre: Vec::new(),
125
            }],
126
        });
127
    }
128
}

3.4.1 Add range parsing for major version wildcard

src/range.rs

102 102
    pub patch: Option<u64>,
103 103
    pub pre: Vec<Identifier>,
104 104
}
105
106
pub fn parse(range: &str) -> Result<VersionReq, String> {
107
    // an empty range is a major version wildcard
108
    // so is a lone * or x of either capitalization
109
    if (range == "")
110
    || (range == "*")
111
    || (range == "x")
112
    || (range == "X") {
113
        return Ok(VersionReq {
114
            predicates: vec![Predicate {
115
                op: Op::Wildcard(WildcardVersion::Major),
116
                major: 0,
117
                minor: None,
118
                patch: None,
119
                pre: Vec::new(),
120
            }],
121
        });
122
    }
123
}

3.4.2 Add check for empty

src/range.rs

104 104
}
105 105
106 106
pub fn parse(range: &str) -> Result<VersionReq, String> {
107
    // null is an error
108
    if range == "\0" {
109
        return Err(String::from("Null is not a valid VersionReq"));
110
    }
111
107 112
    // an empty range is a major version wildcard
108 113
    // so is a lone * or x of either capitalization
109 114
    if (range == "")

3.4.3 Change range to ranges

src/range.rs

103 103
    pub pre: Vec<Identifier>,
104 104
}
105 105
106
pub fn parse(range: &str) -> Result<VersionReq, String> {
106
pub fn parse(ranges: &str) -> Result<VersionReq, String> {
107 107
    // null is an error
108
    if range == "\0" {
108
    if ranges == "\0" {
109 109
        return Err(String::from("Null is not a valid VersionReq"));
110 110
    }
111
111
112 112
    // an empty range is a major version wildcard
113 113
    // so is a lone * or x of either capitalization
114
    if (range == "")
115
    || (range == "*")
116
    || (range == "x")
117
    || (range == "X") {
114
    if (ranges == "")
115
    || (ranges == "*")
116
    || (ranges == "x")
117
    || (ranges == "X") {
118 118
        return Ok(VersionReq {
119 119
            predicates: vec![Predicate {
120 120
                op: Op::Wildcard(WildcardVersion::Major),

3.5 Add major, minor, patch, and pre support

So much for splitting this into multiple steps! Turns out, there is a lot of repetition going on here. We can describe what's going on in a lot less space than the large diff takes up.

src/range.rs

First, we run the regex on our string and get the captures variable. Then we get a variable out of each field we captured. Finally, we build a Predicate with them and return it.

In gathering the operation, the map(str::parse) call runs the FromStr code we implemented in a previous step. If no operation is listed, we use the default, Op::Compatible.

Parsing the major version number might result in an error such as integer overflow, so we need to be a little careful with a match instead of just unwrapping.

The minor and patch fields go through very similar steps. They are both optional and could also encounter errors, so multiple layers of match are used. The innermost match checks if the version was a wildcard. If it was, we override the Op that was set, instead changing it to the applicable WildcardVersion.

src/range.rs

1 1
use regex::Regex;
2
use common;
2 3
use version::Identifier;
3 4
use std::str::FromStr;
5
use std::error::Error;
6
use std::num::ParseIntError;
4 7
5 8
lazy_static! {
6 9
    static ref REGEX: Regex = {

125 128
            }],
126 129
        });
127 130
    }
131
132
    let captures = match REGEX.captures(range.trim()) {
133
        Some(captures) => captures,
134
        None => return Err(From::from("VersionReq did not parse properly.")),
135
    };
136
137
    // operations default to Compatible
138
    // unwrap is okay because we validate that we only have correct strings in the regex
139
    let mut operation = captures.name("operation")
140
                                .map(str::parse)
141
                                .map(Result::unwrap)
142
                                .unwrap_or(Op::Compatible);
143
144
    // unwrap is okay because we always have major
145
    let major: Result<_, ParseIntError> = captures.name("major")
146
                        .unwrap()
147
                        .parse();
148
149
    let major = match major {
150
                            Ok(number) => number,
151
                            Err(err) => return Err("Error parsing major version number: ".to_string() + err.description())
152
                };
153
154
    let minor = match captures.name("minor") {
155
        Some(minor) => {
156
            match minor.parse::<u64>() {
157
                Ok(number) => Some(number),
158
                Err(err) => {
159
                    match minor {
160
                        "*" | "x" | "X"  => {
161
                            operation = Op::Wildcard(WildcardVersion::Minor);
162
                            None
163
                        },
164
                        _ => return Err("Error parsing minor version number: ".to_string() + err.description()),
165
                    }
166
                },
167
            }
168
        },
169
        None => None,
170
    };
171
172
    let patch = match captures.name("patch") {
173
        Some(patch) => {
174
            match patch.parse::<u64>() {
175
                Ok(number) => Some(number),
176
                Err(err) => {
177
                    match patch {
178
                        "*" | "x" | "X"  => {
179
                            operation = Op::Wildcard(WildcardVersion::Patch);
180
                            None
181
                        },
182
                        _ => return Err("Error parsing patch version number: ".to_string() + err.description()),
183
                    }
184
                },
185
            }
186
        },
187
        None => None,
188
    };
189
190
    let pre = captures.name("pre").map(common::parse_meta).unwrap_or_else(Vec::new);
191
192
    let predicate = Predicate {
193
        op: operation,
194
        major: major,
195
        minor: minor,
196
        patch: patch,
197
        pre: pre,
198
    };
199
200
    Ok(VersionReq {
201
        predicates: vec![predicate],
202
    })
128 203
}

3.5.1 Add support for other range types

src/range.rs

1 1
use regex::Regex;
2
use common;
2 3
use version::Identifier;
3 4
use std::str::FromStr;
4 5

125 126
            }],
126 127
        });
127 128
    }
129
130
    let captures = match REGEX.captures(range.trim()) {
131
        Some(captures) => captures,
132
        None => return Err(From::from("VersionReq did not parse properly.")),
133
    };
134
135
    // operations default to Compatible
136
    // unwrap is okay because we validate that we only have correct strings in the regex
137
    let mut operation = captures.name("operation")
138
                                .map(str::parse)
139
                                .map(Result::unwrap)
140
                                .unwrap_or(Op::Compatible);
141
142
    // first unwrap is okay because we always have major
143
    // second unwrap is okay becasue we know it's a number
144
    let major = captures.name("major")
145
                        .unwrap()
146
                        .parse()
147
                        .unwrap();
148
149
    let minor = captures.name("minor");
150
151
    // oh my what have I done? This code is gross.
152
    let minor = if minor.is_some() {
153
        let minor = minor.unwrap();
154
        match minor.parse() {
155
            Ok(number) => Some(number),
156
            Err(_) => {
157
                // if we get an error, it's because it's a wildcard
158
                operation = Op::Wildcard(WildcardVersion::Minor);
159
160
                None
161
            },
162
        }
163
    } else {
164
        None
165
    };
166
167
    let patch = captures.name("patch");
168
169
    // oh my what have I done? This code is gross.
170
    let patch = if patch.is_some() {
171
        let patch = patch.unwrap();
172
        match patch.parse() {
173
            Ok(number) => Some(number),
174
            Err(_) => {
175
                // if we get an error, it's because it's a wildcard
176
                operation = Op::Wildcard(WildcardVersion::Patch);
177
178
                None
179
            },
180
        }
181
    } else {
182
        None
183
    };
184
185
    let pre = captures.name("pre").map(common::parse_meta).unwrap_or_else(Vec::new);
186
187
    let predicate = Predicate {
188
        op: operation,
189
        major: major,
190
        minor: minor,
191
        patch: patch,
192
        pre: pre,
193
    };
194
195
    Ok(VersionReq {
196
        predicates: vec![predicate],
197
    })
128 198
}

3.5.2 Remove trailing whitespace

src/range.rs

147 147
                        .unwrap();
148 148
149 149
    let minor = captures.name("minor");
150
150
151 151
    // oh my what have I done? This code is gross.
152 152
    let minor = if minor.is_some() {
153 153
        let minor = minor.unwrap();

163 163
    } else {
164 164
        None
165 165
    };
166
166
167 167
    let patch = captures.name("patch");
168
168
169 169
    // oh my what have I done? This code is gross.
170 170
    let patch = if patch.is_some() {
171 171
        let patch = patch.unwrap();

3.5.3 Check for integer overflow on major version number

src/range.rs

2 2
use common;
3 3
use version::Identifier;
4 4
use std::str::FromStr;
5
use std::error::Error;
6
use std::num::ParseIntError;
5 7
6 8
lazy_static! {
7 9
    static ref REGEX: Regex = {

140 142
                                .unwrap_or(Op::Compatible);
141 143
142 144
    // first unwrap is okay because we always have major
143
    // second unwrap is okay becasue we know it's a number
144
    let major = captures.name("major")
145
    let major: Result<_, ParseIntError> = captures.name("major")
145 146
                        .unwrap()
146
                        .parse()
147
                        .unwrap();
147
                        .parse();
148
149
    let major = match major {
150
                            Ok(number) => number,
151
                            Err(err) => return Err("Error parsing major version number: ".to_string() + err.description())
152
                };
148 153
149 154
    let minor = captures.name("minor");
150 155

3.5.4 remove unnecessary 'first'

src/range.rs

141 141
                                .map(Result::unwrap)
142 142
                                .unwrap_or(Op::Compatible);
143 143
144
    // first unwrap is okay because we always have major
144
    // unwrap is okay because we always have major
145 145
    let major: Result<_, ParseIntError> = captures.name("major")
146 146
                        .unwrap()
147 147
                        .parse();

3.5.5 Check for integer overflow in minor parsing

src/range.rs

151 151
                            Err(err) => return Err("Error parsing major version number: ".to_string() + err.description())
152 152
                };
153 153
154
    let minor = captures.name("minor");
155
156
    // oh my what have I done? This code is gross.
157
    let minor = if minor.is_some() {
158
        let minor = minor.unwrap();
159
        match minor.parse() {
160
            Ok(number) => Some(number),
161
            Err(_) => {
162
                // if we get an error, it's because it's a wildcard
163
                operation = Op::Wildcard(WildcardVersion::Minor);
164
165
                None
166
            },
167
        }
168
    } else {
169
        None
154
    let minor = match captures.name("minor") {
155
        Some(minor) => {
156
            match minor.parse::<u64>() {
157
                Ok(number) => Some(number),
158
                Err(err) => {
159
                    match minor {
160
                        "*" | "x" | "X"  => {
161
                            operation = Op::Wildcard(WildcardVersion::Minor);
162
                            None
163
                        },
164
                        _ => return Err("Error parsing minor version number: ".to_string() + err.description()),
165
                    }
166
                },
167
            }
168
        },
169
        None => None,
170 170
    };
171 171
172 172
    let patch = captures.name("patch");

3.5.6 Check for integer overflow in patch

src/range.rs

169 169
        None => None,
170 170
    };
171 171
172
    let patch = captures.name("patch");
173
174
    // oh my what have I done? This code is gross.
175
    let patch = if patch.is_some() {
176
        let patch = patch.unwrap();
177
        match patch.parse() {
178
            Ok(number) => Some(number),
179
            Err(_) => {
180
                // if we get an error, it's because it's a wildcard
181
                operation = Op::Wildcard(WildcardVersion::Patch);
182
183
                None
184
            },
185
        }
186
    } else {
187
        None
172
    let patch = match captures.name("patch") {
173
        Some(patch) => {
174
            match patch.parse::<u64>() {
175
                Ok(number) => Some(number),
176
                Err(err) => {
177
                    match patch {
178
                        "*" | "x" | "X"  => {
179
                            operation = Op::Wildcard(WildcardVersion::Patch);
180
                            None
181
                        },
182
                        _ => return Err("Error parsing patch version number: ".to_string() + err.description()),
183
                    }
184
                },
185
            }
186
        },
187
        None => None,
188 188
    };
189 189
190 190
    let pre = captures.name("pre").map(common::parse_meta).unwrap_or_else(Vec::new);

3.6 Add support for multiple predicates

We're supposed to be able to handle multiple predicates, but so far our code only works with one.

src/range.rs

Let's split the parse function up. Most of what we already had will be called parse_predicate. It works on one predicate at a time.

We'll use parse to split apart the string on commas and send each portion to parse_predicate. A quick bit of error checking verifies that we have at least one predicate, then we can return our VersionReq.

src/range.rs

129 129
        });
130 130
    }
131 131
132
133
    let ranges = ranges.trim();
134
135
    let predicates: Result<Vec<_>, String> = ranges
136
        .split(",")
137
        .map(|range| {
138
            parse_predicate(range)
139
        })
140
        .collect();
141
142
    let predicates = try!(predicates);
143
144
    if predicates.len() == 0 {
145
        return Err(String::from("VersionReq did not parse properly"));
146
    }
147
148
    Ok(VersionReq {
149
        predicates: predicates,
150
    })
151
}
152
153
pub fn parse_predicate(range: &str) -> Result<Predicate, String> {
132 154
    let captures = match REGEX.captures(range.trim()) {
133 155
        Some(captures) => captures,
134 156
        None => return Err(From::from("VersionReq did not parse properly.")),

189 211
190 212
    let pre = captures.name("pre").map(common::parse_meta).unwrap_or_else(Vec::new);
191 213
192
    let predicate = Predicate {
214
    Ok(Predicate {
193 215
        op: operation,
194 216
        major: major,
195 217
        minor: minor,
196 218
        patch: patch,
197 219
        pre: pre,
198
    };
199
200
    Ok(VersionReq {
201
        predicates: vec![predicate],
202 220
    })
203 221
}

3.6.1 Add support for multiple range predicates

Conflicts: src/range.rs

src/range.rs

129 129
        });
130 130
    }
131 131
132
133
    let range = range.trim();
134
135
    let predicates: Result<Vec<_>, String> = REGEX.find_iter(range)
136
        .map(|pos| {
137
            let range = &range[pos.0..pos.1];
138
139
            parse_predicate(range)
140
        }).collect();
141
142
    let predicates = try!(predicates);
143
144
    if predicates.len() == 0 {
145
        return Err(String::from("VersionReq did not parse properly"));
146
    }
147
148
    Ok(VersionReq {
149
        predicates: predicates,
150
    })
151
}
152
153
pub fn parse_predicate(range: &str) -> Result<Predicate, String> {
132 154
    let captures = match REGEX.captures(range.trim()) {
133 155
        Some(captures) => captures,
134 156
        None => return Err(From::from("VersionReq did not parse properly.")),

189 211
190 212
    let pre = captures.name("pre").map(common::parse_meta).unwrap_or_else(Vec::new);
191 213
192
    let predicate = Predicate {
214
    Ok(Predicate {
193 215
        op: operation,
194 216
        major: major,
195 217
        minor: minor,
196 218
        patch: patch,
197 219
        pre: pre,
198
    };
199
200
    Ok(VersionReq {
201
        predicates: vec![predicate],
202 220
    })
203 221
}

3.6.2 Change range to ranges

src/range.rs

130 130
    }
131 131
132 132
133
    let range = range.trim();
134
135
    let predicates: Result<Vec<_>, String> = REGEX.find_iter(range)
136
        .map(|pos| {
137
            let range = &range[pos.0..pos.1];
133
    let ranges = ranges.trim();
138 134
135
    let predicates: Result<Vec<_>, String> = ranges
136
        .split(",")
137
        .map(|range| {
139 138
            parse_predicate(range)
140
        }).collect();
139
        })
140
        .collect();
141 141
142 142
    let predicates = try!(predicates);
143 143

3.7 Add build tag to regex

As hinted earlier, apparently we do need to allow for a build string in the range specification.

src/range.rs

If there was a build string in the predicate, we'll at least accept it. So far we aren't doing anything with this capture group, but it's there.

src/range.rs

35 35
        // parse_version() will parse this further.
36 36
        let pre = letters_numbers_dash_dot;
37 37
38
        // This regex does not fully parse builds, just extracts the whole build string.
39
        // parse_version() will parse this further.
40
        let build = letters_numbers_dash_dot;
41
38 42
        let regex = format!(r"(?x) # heck yes x mode
39 43
            ^\s*                    # leading whitespace
40 44
            (?P<operation>{})?\s*   # optional operation

42 46
            (?:\.(?P<minor>{}))?    # optional dot and then minor
43 47
            (?:\.(?P<patch>{}))?    # optional dot and then patch
44 48
            (?:-(?P<pre>{}))?       # optional prerelease version
49
            (?:\+(?P<build>{}))?    # optional build metadata
45 50
            \s*$                    # trailing whitespace
46 51
            ",
47 52
            operation,
48 53
            major,
49 54
            minor,
50 55
            patch,
51
            pre);
56
            pre,
57
            build);
52 58
        let regex = Regex::new(&regex);
53 59
54 60
        // this unwrap is okay because everything above here is const, so this will never fail.

3.7.1 Add build to range.rs

src/range.rs

35 35
        // parse_version() will parse this further.
36 36
        let pre = letters_numbers_dash_dot;
37 37
38
        // This regex does not fully parse builds, just extracts the whole build string.
39
        // parse_version() will parse this further.
40
        let build = letters_numbers_dash_dot;
41
38 42
        let regex = format!(r"(?x) # heck yes x mode
39 43
            ^\s*                    # leading whitespace
40 44
            (?P<operation>{})?\s*   # optional operation

42 46
            (?:\.(?P<minor>{}))?    # optional dot and then minor
43 47
            (?:\.(?P<patch>{}))?    # optional dot and then patch
44 48
            (?:-(?P<pre>{}))?       # optional prerelease version
49
            (:?\+(?P<build>{}))?    # optional build metadata
45 50
            \s*$                    # trailing whitespace
46 51
            ",
47 52
            operation,
48 53
            major,
49 54
            minor,
50 55
            patch,
51
            pre);
56
            pre,
57
            build);
52 58
        let regex = Regex::new(&regex);
53 59
54 60
        // this unwrap is okay because everything above here is const, so this will never fail.

3.7.2 Fix regex

src/range.rs

46 46
            (?:\.(?P<minor>{}))?    # optional dot and then minor
47 47
            (?:\.(?P<patch>{}))?    # optional dot and then patch
48 48
            (?:-(?P<pre>{}))?       # optional prerelease version
49
            (:?\+(?P<build>{}))?    # optional build metadata
49
            (?:\+(?P<build>{}))?    # optional build metadata
50 50
            \s*$                    # trailing whitespace
51 51
            ",
52 52
            operation,

4 Add tests

Here they are! All the tests.

We're done implementing everything. All the tests pass. We made a Rust crate!

src/range.rs

225 225
        pre: pre,
226 226
    })
227 227
}
228
229
#[cfg(test)]
230
mod tests {
231
    use super::*;
232
    use range;
233
    use version::Identifier;
234
235
    #[test]
236
    fn test_parsing_default() {
237
        let r = range::parse("1.0.0").unwrap();
238
239
        assert_eq!(Predicate {
240
                op: Op::Compatible,
241
                major: 1,
242
                minor: Some(0),
243
                patch: Some(0),
244
                pre: Vec::new(),
245
            },
246
            r.predicates[0]
247
        );
248
    }
249
250
    #[test]
251
    fn test_parsing_exact_01() {
252
        let r = range::parse("=1.0.0").unwrap();
253
254
        assert_eq!(Predicate {
255
                op: Op::Ex,
256
                major: 1,
257
                minor: Some(0),
258
                patch: Some(0),
259
                pre: Vec::new(),
260
            },
261
            r.predicates[0]
262
        );
263
    }
264
265
    #[test]
266
    fn test_parsing_exact_02() {
267
        let r = range::parse("=0.9.0").unwrap();
268
269
        assert_eq!(Predicate {
270
                op: Op::Ex,
271
                major: 0,
272
                minor: Some(9),
273
                patch: Some(0),
274
                pre: Vec::new(),
275
            },
276
            r.predicates[0]
277
        );
278
    }
279
280
    #[test]
281
    fn test_parsing_exact_03() {
282
        let r = range::parse("=0.1.0-beta2.a").unwrap();
283
284
        assert_eq!(Predicate {
285
                op: Op::Ex,
286
                major: 0,
287
                minor: Some(1),
288
                patch: Some(0),
289
                pre: vec![Identifier::AlphaNumeric(String::from("beta2")),
290
                          Identifier::AlphaNumeric(String::from("a"))],
291
            },
292
            r.predicates[0]
293
        );
294
    }
295
296
    #[test]
297
    pub fn test_parsing_greater_than() {
298
        let r = range::parse("> 1.0.0").unwrap();
299
300
        assert_eq!(Predicate {
301
                op: Op::Gt,
302
                major: 1,
303
                minor: Some(0),
304
                patch: Some(0),
305
                pre: Vec::new(),
306
            },
307
            r.predicates[0]
308
        );
309
    }
310
311
    #[test]
312
    pub fn test_parsing_greater_than_01() {
313
        let r = range::parse(">= 1.0.0").unwrap();
314
315
        assert_eq!(Predicate {
316
                op: Op::GtEq,
317
                major: 1,
318
                minor: Some(0),
319
                patch: Some(0),
320
                pre: Vec::new(),
321
            },
322
            r.predicates[0]
323
        );
324
    }
325
326
    #[test]
327
    pub fn test_parsing_greater_than_02() {
328
        let r = range::parse(">= 2.1.0-alpha2").unwrap();
329
330
        assert_eq!(Predicate {
331
                op: Op::GtEq,
332
                major: 2,
333
                minor: Some(1),
334
                patch: Some(0),
335
                pre: vec![Identifier::AlphaNumeric(String::from("alpha2"))],
336
            },
337
            r.predicates[0]
338
        );
339
    }
340
341
    #[test]
342
    pub fn test_parsing_less_than() {
343
        let r = range::parse("< 1.0.0").unwrap();
344
345
        assert_eq!(Predicate {
346
                op: Op::Lt,
347
                major: 1,
348
                minor: Some(0),
349
                patch: Some(0),
350
                pre: Vec::new(),
351
            },
352
            r.predicates[0]
353
        );
354
    }
355
356
    #[test]
357
    pub fn test_parsing_less_than_eq() {
358
        let r = range::parse("<= 2.1.0-alpha2").unwrap();
359
360
        assert_eq!(Predicate {
361
                op: Op::LtEq,
362
                major: 2,
363
                minor: Some(1),
364
                patch: Some(0),
365
                pre: vec![Identifier::AlphaNumeric(String::from("alpha2"))],
366
            },
367
            r.predicates[0]
368
        );
369
    }
370
371
    #[test]
372
    pub fn test_parsing_tilde() {
373
        let r = range::parse("~1").unwrap();
374
375
        assert_eq!(Predicate {
376
                op: Op::Tilde,
377
                major: 1,
378
                minor: None,
379
                patch: None,
380
                pre: Vec::new(),
381
            },
382
            r.predicates[0]
383
        );
384
    }
385
386
    #[test]
387
    pub fn test_parsing_compatible() {
388
        let r = range::parse("^0").unwrap();
389
390
        assert_eq!(Predicate {
391
                op: Op::Compatible,
392
                major: 0,
393
                minor: None,
394
                patch: None,
395
                pre: Vec::new(),
396
            },
397
            r.predicates[0]
398
        );
399
    }
400
401
    #[test]
402
    fn test_parsing_blank() {
403
        let r = range::parse("").unwrap();
404
405
        assert_eq!(Predicate {
406
                op: Op::Wildcard(WildcardVersion::Major),
407
                major: 0,
408
                minor: None,
409
                patch: None,
410
                pre: Vec::new(),
411
            },
412
            r.predicates[0]
413
        );
414
    }
415
416
    #[test]
417
    fn test_parsing_wildcard() {
418
        let r = range::parse("*").unwrap();
419
420
        assert_eq!(Predicate {
421
                op: Op::Wildcard(WildcardVersion::Major),
422
                major: 0,
423
                minor: None,
424
                patch: None,
425
                pre: Vec::new(),
426
            },
427
            r.predicates[0]
428
        );
429
    }
430
431
    #[test]
432
    fn test_parsing_x() {
433
        let r = range::parse("x").unwrap();
434
435
        assert_eq!(Predicate {
436
                op: Op::Wildcard(WildcardVersion::Major),
437
                major: 0,
438
                minor: None,
439
                patch: None,
440
                pre: Vec::new(),
441
            },
442
            r.predicates[0]
443
        );
444
    }
445
446
    #[test]
447
    fn test_parsing_capital_x() {
448
        let r = range::parse("X").unwrap();
449
450
        assert_eq!(Predicate {
451
                op: Op::Wildcard(WildcardVersion::Major),
452
                major: 0,
453
                minor: None,
454
                patch: None,
455
                pre: Vec::new(),
456
            },
457
            r.predicates[0]
458
        );
459
    }
460
461
    #[test]
462
    fn test_parsing_minor_wildcard_star() {
463
        let r = range::parse("1.*").unwrap();
464
465
        assert_eq!(Predicate {
466
                op: Op::Wildcard(WildcardVersion::Minor),
467
                major: 1,
468
                minor: None,
469
                patch: None,
470
                pre: Vec::new(),
471
            },
472
            r.predicates[0]
473
        );
474
    }
475
476
    #[test]
477
    fn test_parsing_minor_wildcard_x() {
478
        let r = range::parse("1.x").unwrap();
479
480
        assert_eq!(Predicate {
481
                op: Op::Wildcard(WildcardVersion::Minor),
482
                major: 1,
483
                minor: None,
484
                patch: None,
485
                pre: Vec::new(),
486
            },
487
            r.predicates[0]
488
        );
489
    }
490
491
    #[test]
492
    fn test_parsing_minor_wildcard_capital_x() {
493
        let r = range::parse("1.X").unwrap();
494
495
        assert_eq!(Predicate {
496
                op: Op::Wildcard(WildcardVersion::Minor),
497
                major: 1,
498
                minor: None,
499
                patch: None,
500
                pre: Vec::new(),
501
            },
502
            r.predicates[0]
503
        );
504
    }
505
506
    #[test]
507
    fn test_parsing_patch_wildcard_star() {
508
        let r = range::parse("1.2.*").unwrap();
509
510
        assert_eq!(Predicate {
511
                op: Op::Wildcard(WildcardVersion::Patch),
512
                major: 1,
513
                minor: Some(2),
514
                patch: None,
515
                pre: Vec::new(),
516
            },
517
            r.predicates[0]
518
        );
519
    }
520
521
    #[test]
522
    fn test_parsing_patch_wildcard_x() {
523
        let r = range::parse("1.2.x").unwrap();
524
525
        assert_eq!(Predicate {
526
                op: Op::Wildcard(WildcardVersion::Patch),
527
                major: 1,
528
                minor: Some(2),
529
                patch: None,
530
                pre: Vec::new(),
531
            },
532
            r.predicates[0]
533
        );
534
    }
535
536
    #[test]
537
    fn test_parsing_patch_wildcard_capital_x() {
538
        let r = range::parse("1.2.X").unwrap();
539
540
        assert_eq!(Predicate {
541
                op: Op::Wildcard(WildcardVersion::Patch),
542
                major: 1,
543
                minor: Some(2),
544
                patch: None,
545
                pre: Vec::new(),
546
            },
547
            r.predicates[0]
548
        );
549
    }
550
551
    #[test]
552
    pub fn test_multiple_01() {
553
        let r = range::parse("> 0.0.9, <= 2.5.3").unwrap();
554
555
        assert_eq!(Predicate {
556
                op: Op::Gt,
557
                major: 0,
558
                minor: Some(0),
559
                patch: Some(9),
560
                pre: Vec::new(),
561
            },
562
            r.predicates[0]
563
        );
564
565
        assert_eq!(Predicate {
566
                op: Op::LtEq,
567
                major: 2,
568
                minor: Some(5),
569
                patch: Some(3),
570
                pre: Vec::new(),
571
            },
572
            r.predicates[1]
573
        );
574
    }
575
576
    #[test]
577
    pub fn test_multiple_02() {
578
        let r = range::parse("0.3.0, 0.4.0").unwrap();
579
580
        assert_eq!(Predicate {
581
                op: Op::Compatible,
582
                major: 0,
583
                minor: Some(3),
584
                patch: Some(0),
585
                pre: Vec::new(),
586
            },
587
            r.predicates[0]
588
        );
589
590
        assert_eq!(Predicate {
591
                op: Op::Compatible,
592
                major: 0,
593
                minor: Some(4),
594
                patch: Some(0),
595
                pre: Vec::new(),
596
            },
597
            r.predicates[1]
598
        );
599
    }
600
601
    #[test]
602
    pub fn test_multiple_03() {
603
        let r = range::parse("<= 0.2.0, >= 0.5.0").unwrap();
604
605
        assert_eq!(Predicate {
606
                op: Op::LtEq,
607
                major: 0,
608
                minor: Some(2),
609
                patch: Some(0),
610
                pre: Vec::new(),
611
            },
612
            r.predicates[0]
613
        );
614
615
        assert_eq!(Predicate {
616
                op: Op::GtEq,
617
                major: 0,
618
                minor: Some(5),
619
                patch: Some(0),
620
                pre: Vec::new(),
621
            },
622
            r.predicates[1]
623
        );
624
    }
625
626
    #[test]
627
    pub fn test_multiple_04() {
628
        let r = range::parse("0.1.0, 0.1.4, 0.1.6").unwrap();
629
630
        assert_eq!(Predicate {
631
                op: Op::Compatible,
632
                major: 0,
633
                minor: Some(1),
634
                patch: Some(0),
635
                pre: Vec::new(),
636
            },
637
            r.predicates[0]
638
        );
639
640
        assert_eq!(Predicate {
641
                op: Op::Compatible,
642
                major: 0,
643
                minor: Some(1),
644
                patch: Some(4),
645
                pre: Vec::new(),
646
            },
647
            r.predicates[1]
648
        );
649
650
        assert_eq!(Predicate {
651
                op: Op::Compatible,
652
                major: 0,
653
                minor: Some(1),
654
                patch: Some(6),
655
                pre: Vec::new(),
656
            },
657
            r.predicates[2]
658
        );
659
    }
660
661
    #[test]
662
    pub fn test_multiple_05() {
663
        let r = range::parse(">=0.5.1-alpha3, <0.6").unwrap();
664
665
        assert_eq!(Predicate {
666
                op: Op::GtEq,
667
                major: 0,
668
                minor: Some(5),
669
                patch: Some(1),
670
                pre: vec![Identifier::AlphaNumeric(String::from("alpha3"))],
671
            },
672
            r.predicates[0]
673
        );
674
675
        assert_eq!(Predicate {
676
                op: Op::Lt,
677
                major: 0,
678
                minor: Some(6),
679
                patch: None,
680
                pre: Vec::new(),
681
            },
682
            r.predicates[1]
683
        );
684
    }
685
686
    #[test]
687
    fn test_parse_build_metadata_with_predicate() {
688
        assert_eq!(range::parse("^1.2.3+meta").unwrap().predicates[0].op,
689
                   Op::Compatible);
690
        assert_eq!(range::parse("~1.2.3+meta").unwrap().predicates[0].op,
691
                   Op::Tilde);
692
        assert_eq!(range::parse("=1.2.3+meta").unwrap().predicates[0].op,
693
                   Op::Ex);
694
        assert_eq!(range::parse("<=1.2.3+meta").unwrap().predicates[0].op,
695
                   Op::LtEq);
696
        assert_eq!(range::parse(">=1.2.3+meta").unwrap().predicates[0].op,
697
                   Op::GtEq);
698
        assert_eq!(range::parse("<1.2.3+meta").unwrap().predicates[0].op,
699
                   Op::Lt);
700
        assert_eq!(range::parse(">1.2.3+meta").unwrap().predicates[0].op,
701
                   Op::Gt);
702
    }
703
704
    #[test]
705
    pub fn test_parse_errors() {
706
        assert!(range::parse("\0").is_err());
707
        assert!(range::parse(">= >= 0.0.2").is_err());
708
        assert!(range::parse(">== 0.0.2").is_err());
709
        assert!(range::parse("a.0.0").is_err());
710
        assert!(range::parse("1.0.0-").is_err());
711
        assert!(range::parse(">=").is_err());
712
        assert!(range::parse("> 0.1.0,").is_err());
713
        assert!(range::parse("> 0.3.0, ,").is_err());
714
    }
715
716
    #[test]
717
    pub fn test_large_major_version() {
718
        assert!(range::parse("18446744073709551617.0.0").is_err());
719
    }
720
721
    #[test]
722
    pub fn test_large_minor_version() {
723
        assert!(range::parse("0.18446744073709551617.0").is_err());
724
    }
725
726
    #[test]
727
    pub fn test_large_patch_version() {
728
        assert!(range::parse("0.0.18446744073709551617").is_err());
729
    }
730
}

src/version.rs

104 104
        }
105 105
    }
106 106
}
107
108
#[cfg(test)]
109
mod tests {
110
    use version;
111
    use super::*;
112
113
    #[test]
114
    fn parse_empty() {
115
        let version = "";
116
117
        let parsed = version::parse(version);
118
119
        assert!(parsed.is_err(), "empty string incorrectly considered a valid parse");
120
    }
121
122
    #[test]
123
    fn parse_blank() {
124
        let version = "  ";
125
126
        let parsed = version::parse(version);
127
128
        assert!(parsed.is_err(), "blank string incorrectly considered a valid parse");
129
    }
130
131
    #[test]
132
    fn parse_no_minor_patch() {
133
        let version = "1";
134
135
        let parsed = version::parse(version);
136
137
        assert!(parsed.is_err(), format!("'{}' incorrectly considered a valid parse", version));
138
    }
139
140
    #[test]
141
    fn parse_no_patch() {
142
        let version = "1.2";
143
144
        let parsed = version::parse(version);
145
146
        assert!(parsed.is_err(), format!("'{}' incorrectly considered a valid parse", version));
147
    }
148
149
    #[test]
150
    fn parse_empty_pre() {
151
        let version = "1.2.3-";
152
153
        let parsed = version::parse(version);
154
155
        assert!(parsed.is_err(), format!("'{}' incorrectly considered a valid parse", version));
156
    }
157
158
    #[test]
159
    fn parse_letters() {
160
        let version = "a.b.c";
161
162
        let parsed = version::parse(version);
163
164
        assert!(parsed.is_err(), format!("'{}' incorrectly considered a valid parse", version));
165
    }
166
167
    #[test]
168
    fn parse_with_letters() {
169
        let version = "1.2.3 a.b.c";
170
171
        let parsed = version::parse(version);
172
173
        assert!(parsed.is_err(), format!("'{}' incorrectly considered a valid parse", version));
174
    }
175
176
    #[test]
177
    fn parse_basic_version() {
178
        let version = "1.2.3";
179
180
        let parsed = version::parse(version).unwrap();
181
182
        assert_eq!(1, parsed.major);
183
        assert_eq!(2, parsed.minor);
184
        assert_eq!(3, parsed.patch);
185
    }
186
187
    #[test]
188
    fn parse_trims_input() {
189
        let version = "  1.2.3  ";
190
191
        let parsed = version::parse(version).unwrap();
192
193
        assert_eq!(1, parsed.major);
194
        assert_eq!(2, parsed.minor);
195
        assert_eq!(3, parsed.patch);
196
    }
197
198
    #[test]
199
    fn parse_no_major_leading_zeroes() {
200
        let version = "01.0.0";
201
202
        let parsed = version::parse(version);
203
204
        assert!(parsed.is_err(), "01 incorrectly considered a valid major version");
205
    }
206
207
    #[test]
208
    fn parse_no_minor_leading_zeroes() {
209
        let version = "0.01.0";
210
211
        let parsed = version::parse(version);
212
213
        assert!(parsed.is_err(), "01 incorrectly considered a valid minor version");
214
    }
215
216
    #[test]
217
    fn parse_no_patch_leading_zeroes() {
218
        let version = "0.0.01";
219
220
        let parsed = version::parse(version);
221
222
        assert!(parsed.is_err(), "01 incorrectly considered a valid patch version");
223
    }
224
225
    #[test]
226
    fn parse_basic_prerelease() {
227
        let version = "1.2.3-pre";
228
229
        let parsed = version::parse(version).unwrap();
230
231
        let expected_pre = vec![Identifier::AlphaNumeric(String::from("pre"))];
232
        assert_eq!(expected_pre, parsed.pre);
233
    }
234
235
    #[test]
236
    fn parse_prerelease_alphanumeric() {
237
        let version = "1.2.3-alpha1";
238
239
        let parsed = version::parse(version).unwrap();
240
241
        let expected_pre = vec![Identifier::AlphaNumeric(String::from("alpha1"))];
242
        assert_eq!(expected_pre, parsed.pre);
243
    }
244
245
    #[test]
246
    fn parse_prerelease_zero() {
247
        let version = "1.2.3-pre.0";
248
249
        let parsed = version::parse(version).unwrap();
250
251
        let expected_pre = vec![Identifier::AlphaNumeric(String::from("pre")),
252
                                Identifier::Numeric(0)];
253
        assert_eq!(expected_pre, parsed.pre);
254
    }
255
256
    #[test]
257
    fn parse_basic_build() {
258
        let version = "1.2.3+build";
259
260
        let parsed = version::parse(version).unwrap();
261
262
        let expected_build = vec![Identifier::AlphaNumeric(String::from("build"))];
263
        assert_eq!(expected_build, parsed.build);
264
    }
265
266
    #[test]
267
    fn parse_build_alphanumeric() {
268
        let version = "1.2.3+build5";
269
270
        let parsed = version::parse(version).unwrap();
271
272
        let expected_build = vec![Identifier::AlphaNumeric(String::from("build5"))];
273
        assert_eq!(expected_build, parsed.build);
274
    }
275
276
    #[test]
277
    fn parse_pre_and_build() {
278
        let version = "1.2.3-alpha1+build5";
279
280
        let parsed = version::parse(version).unwrap();
281
282
        let expected_pre = vec![Identifier::AlphaNumeric(String::from("alpha1"))];
283
        assert_eq!(expected_pre, parsed.pre);
284
285
        let expected_build = vec![Identifier::AlphaNumeric(String::from("build5"))];
286
        assert_eq!(expected_build, parsed.build);
287
    }
288
289
    #[test]
290
    fn parse_complex_metadata_01() {
291
        let version = "1.2.3-1.alpha1.9+build5.7.3aedf  ";
292
293
        let parsed = version::parse(version).unwrap();
294
295
        let expected_pre = vec![Identifier::Numeric(1),
296
                                Identifier::AlphaNumeric(String::from("alpha1")),
297
                                Identifier::Numeric(9)];
298
        assert_eq!(expected_pre, parsed.pre);
299
300
        let expected_build = vec![Identifier::AlphaNumeric(String::from("build5")),
301
                                  Identifier::Numeric(7),
302
                                  Identifier::AlphaNumeric(String::from("3aedf"))];
303
        assert_eq!(expected_build, parsed.build);
304
    }
305
306
    #[test]
307
    fn parse_complex_metadata_02() {
308
        let version = "0.4.0-beta.1+0851523";
309
310
        let parsed = version::parse(version).unwrap();
311
312
        let expected_pre = vec![Identifier::AlphaNumeric(String::from("beta")),
313
                                Identifier::Numeric(1)];
314
        assert_eq!(expected_pre, parsed.pre);
315
316
        let expected_build = vec![Identifier::AlphaNumeric(String::from("0851523"))];
317
        assert_eq!(expected_build, parsed.build);
318
    }
319
320
    #[test]
321
    fn parse_regression_01() {
322
        let version = "0.0.0-WIP";
323
324
        let parsed = version::parse(version).unwrap();
325
326
        assert_eq!(0, parsed.major);
327
        assert_eq!(0, parsed.minor);
328
        assert_eq!(0, parsed.patch);
329
330
        let expected_pre = vec![Identifier::AlphaNumeric(String::from("WIP"))];
331
        assert_eq!(expected_pre, parsed.pre);
332
    }
333
}

4.1 Add tests

src/version.rs

104 104
        }
105 105
    }
106 106
}
107
108
#[cfg(test)]
109
mod tests {
110
    use super::*;
111
112
    #[test]
113
    fn parse_version_core_01() {
114
        let version = "1.2.3";
115
116
        let parsed = parse_version(version);
117
118
        assert_eq!(1, parsed.major);
119
        assert_eq!(2, parsed.minor);
120
        assert_eq!(3, parsed.patch);
121
    }
122
}

4.2 Add tests

src/version.rs

110 110
    use super::*;
111 111
112 112
    #[test]
113
    fn parse_version_core_01() {
113
    fn parse_version_core() {
114 114
        let version = "1.2.3";
115 115
116
        let parsed = parse_version(version);
116
        let parsed = parse_version(version).unwrap();
117 117
118 118
        assert_eq!(1, parsed.major);
119 119
        assert_eq!(2, parsed.minor);
120 120
        assert_eq!(3, parsed.patch);
121 121
    }
122
123
    #[test]
124
    fn parse_version_no_major_leading_zeroes() {
125
        let version = "01.0.0";
126
127
        let parsed = parse_version(version);
128
129
        assert!(parsed.is_err(), "01 incorrectly considered a valid major version");
130
    }
131
132
    #[test]
133
    fn parse_version_no_minor_leading_zeroes() {
134
        let version = "0.01.0";
135
136
        let parsed = parse_version(version);
137
138
        assert!(parsed.is_err(), "01 incorrectly considered a valid minor version");
139
    }
140
141
    #[test]
142
    fn parse_version_no_patch_leading_zeroes() {
143
        let version = "0.0.01";
144
145
        let parsed = parse_version(version);
146
147
        assert!(parsed.is_err(), "01 incorrectly considered a valid patch version");
148
    }
122 149
}

4.3 Add tests

src/version.rs

146 146
147 147
        assert!(parsed.is_err(), "01 incorrectly considered a valid patch version");
148 148
    }
149
150
    #[test]
151
    fn parse_version_basic_prerelease() {
152
        let version = "1.2.3-pre";
153
154
        let parsed = parse_version(version).unwrap();
155
156
        assert_eq!(Some(String::from("pre")), parsed.pre);
157
    }
149 158
}

4.4 Add tests

src/version.rs

110 110
    use super::*;
111 111
112 112
    #[test]
113
    fn parse_version_core() {
113
    fn parse_empty() {
114
        let version = "";
115
116
        let parsed = parse_version(version);
117
118
        assert!(parsed.is_err(), "empty string incorrectly considered a valid parse");
119
    }
120
121
    #[test]
122
    fn parse_blank() {
123
        let version = "  ";
124
125
        let parsed = parse_version(version);
126
127
        assert!(parsed.is_err(), "blank string incorrectly considered a valid parse");
128
    }
129
130
    #[test]
131
    fn parse_no_minor_patch() {
132
        let version = "1";
133
134
        let parsed = parse_version(version);
135
136
        assert!(parsed.is_err(), format!("'{}' incorrectly considered a valid parse", version));
137
    }
138
139
    #[test]
140
    fn parse_no_patch() {
141
        let version = "1.2";
142
143
        let parsed = parse_version(version);
144
145
        assert!(parsed.is_err(), format!("'{}' incorrectly considered a valid parse", version));
146
    }
147
148
    #[test]
149
    fn parse_empty_pre() {
150
        let version = "1.2.3-";
151
152
        let parsed = parse_version(version);
153
154
        assert!(parsed.is_err(), format!("'{}' incorrectly considered a valid parse", version));
155
    }
156
157
    #[test]
158
    fn parse_letters() {
159
        let version = "a.b.c";
160
161
        let parsed = parse_version(version);
162
163
        assert!(parsed.is_err(), format!("'{}' incorrectly considered a valid parse", version));
164
    }
165
166
    #[test]
167
    fn parse_version_with_letters() {
168
        let version = "1.2.3 a.b.c";
169
170
        let parsed = parse_version(version);
171
172
        assert!(parsed.is_err(), format!("'{}' incorrectly considered a valid parse", version));
173
    }
174
175
    #[test]
176
    fn parse_basic_version() {
114 177
        let version = "1.2.3";
115 178
116 179
        let parsed = parse_version(version).unwrap();

121 184
    }
122 185
123 186
    #[test]
187
    fn parse_trims_input() {
188
        let version = "  1.2.3  ";
189
190
        let parsed = parse_version(version).unwrap();
191
192
        assert_eq!(1, parsed.major);
193
        assert_eq!(2, parsed.minor);
194
        assert_eq!(3, parsed.patch);
195
    }
196
197
    #[test]
124 198
    fn parse_version_no_major_leading_zeroes() {
125 199
        let version = "01.0.0";
126 200

153 227
154 228
        let parsed = parse_version(version).unwrap();
155 229
156
        assert_eq!(Some(String::from("pre")), parsed.pre);
230
        let expected_pre = Some(vec![String::from("pre")]);
231
        assert_eq!(expected_pre, parsed.pre);
232
    }
233
234
    #[test]
235
    fn parse_version_prerelease_alphanumeric() {
236
        let version = "1.2.3-alpha1";
237
238
        let parsed = parse_version(version).unwrap();
239
240
        let expected_pre = Some(vec![String::from("alpha1")]);
241
        assert_eq!(expected_pre, parsed.pre);
242
    }
243
244
    #[test]
245
    fn parse_version_basic_build() {
246
        let version = "1.2.3+build";
247
248
        let parsed = parse_version(version).unwrap();
249
250
        let expected_build = Some(vec![String::from("build")]);
251
        assert_eq!(expected_build, parsed.build);
252
    }
253
254
    #[test]
255
    fn parse_version_build_alphanumeric() {
256
        let version = "1.2.3+build5";
257
258
        let parsed = parse_version(version).unwrap();
259
260
        let expected_build = Some(vec![String::from("build5")]);
261
        assert_eq!(expected_build, parsed.build);
262
    }
263
264
    #[test]
265
    fn parse_version_pre_and_build() {
266
        let version = "1.2.3-alpha1+build5";
267
268
        let parsed = parse_version(version).unwrap();
269
270
        let expected_pre = Some(vec![String::from("alpha1")]);
271
        assert_eq!(expected_pre, parsed.pre);
272
273
        let expected_build = Some(vec![String::from("build5")]);
274
        assert_eq!(expected_build, parsed.build);
275
    }
276
277
    #[test]
278
    fn parse_version_complex_metadata_01() {
279
        let version = "1.2.3-1.alpha1.9+build5.7.3aedf  ";
280
281
        let parsed = parse_version(version).unwrap();
282
283
        let expected_pre = Some(vec![String::from("1.alpha1.9")]);
284
        assert_eq!(expected_pre, parsed.pre);
285
286
        let expected_build = Some(vec![String::from("build5.7.3aedf")]);
287
        assert_eq!(expected_build, parsed.build);
288
    }
289
290
    #[test]
291
    fn parse_version_complex_metadata_02() {
292
        let version = "0.4.0-beta.1+0851523";
293
294
        let parsed = parse_version(version).unwrap();
295
296
        let expected_pre = Some(vec![String::from("beta.1")]);
297
        assert_eq!(expected_pre, parsed.pre);
298
299
        let expected_build = Some(vec![String::from("0851523")]);
300
        assert_eq!(expected_build, parsed.build);
157 301
    }
158 302
}

4.5 Update tests

src/version.rs

227 227
228 228
        let parsed = parse_version(version).unwrap();
229 229
230
        let expected_pre = Some(vec![String::from("pre")]);
230
        let expected_pre = Some(vec![Identifier::AlphaNumeric(String::from("pre"))]);
231 231
        assert_eq!(expected_pre, parsed.pre);
232 232
    }
233 233

237 237
238 238
        let parsed = parse_version(version).unwrap();
239 239
240
        let expected_pre = Some(vec![String::from("alpha1")]);
240
        let expected_pre = Some(vec![Identifier::AlphaNumeric(String::from("alpha1"))]);
241 241
        assert_eq!(expected_pre, parsed.pre);
242 242
    }
243 243

247 247
248 248
        let parsed = parse_version(version).unwrap();
249 249
250
        let expected_build = Some(vec![String::from("build")]);
250
        let expected_build = Some(vec![Identifier::AlphaNumeric(String::from("build"))]);
251 251
        assert_eq!(expected_build, parsed.build);
252 252
    }
253 253

257 257
258 258
        let parsed = parse_version(version).unwrap();
259 259
260
        let expected_build = Some(vec![String::from("build5")]);
260
        let expected_build = Some(vec![Identifier::AlphaNumeric(String::from("build5"))]);
261 261
        assert_eq!(expected_build, parsed.build);
262 262
    }
263 263

267 267
268 268
        let parsed = parse_version(version).unwrap();
269 269
270
        let expected_pre = Some(vec![String::from("alpha1")]);
270
        let expected_pre = Some(vec![Identifier::AlphaNumeric(String::from("alpha1"))]);
271 271
        assert_eq!(expected_pre, parsed.pre);
272 272
273
        let expected_build = Some(vec![String::from("build5")]);
273
        let expected_build = Some(vec![Identifier::AlphaNumeric(String::from("build5"))]);
274 274
        assert_eq!(expected_build, parsed.build);
275 275
    }
276 276

280 280
281 281
        let parsed = parse_version(version).unwrap();
282 282
283
        let expected_pre = Some(vec![String::from("1.alpha1.9")]);
283
        let expected_pre = Some(vec![Identifier::Numeric(1),
284
                                     Identifier::AlphaNumeric(String::from("alpha1")),
285
                                     Identifier::Numeric(9)]);
284 286
        assert_eq!(expected_pre, parsed.pre);
285 287
286
        let expected_build = Some(vec![String::from("build5.7.3aedf")]);
288
        let expected_build = Some(vec![Identifier::AlphaNumeric(String::from("build5")),
289
                                       Identifier::Numeric(7),
290
                                       Identifier::AlphaNumeric(String::from("3aedf"))]);
287 291
        assert_eq!(expected_build, parsed.build);
288 292
    }
289 293

293 297
294 298
        let parsed = parse_version(version).unwrap();
295 299
296
        let expected_pre = Some(vec![String::from("beta.1")]);
300
        let expected_pre = Some(vec![Identifier::AlphaNumeric(String::from("beta")),
301
                                     Identifier::Numeric(1)]);
297 302
        assert_eq!(expected_pre, parsed.pre);
298 303
299
        let expected_build = Some(vec![String::from("0851523")]);
304
        let expected_build = Some(vec![Identifier::AlphaNumeric(String::from("0851523"))]);
300 305
        assert_eq!(expected_build, parsed.build);
301 306
    }
302 307
}

4.6 Add test

src/version.rs

242 242
    }
243 243
244 244
    #[test]
245
    fn parse_version_prerelease_zero() {
246
        let version = "1.2.3-pre.0";
247
248
        let parsed = parse_version(version).unwrap();
249
250
        let expected_pre = Some(vec![Identifier::AlphaNumeric(String::from("pre")),
251
                                     Identifier::Numeric(0)]);
252
        assert_eq!(expected_pre, parsed.pre);
253
    }
254
255
    #[test]
245 256
    fn parse_version_basic_build() {
246 257
        let version = "1.2.3+build";
247 258

4.7 Add test

src/version.rs

315 315
        let expected_build = Some(vec![Identifier::AlphaNumeric(String::from("0851523"))]);
316 316
        assert_eq!(expected_build, parsed.build);
317 317
    }
318
319
    #[test]
320
    fn parse_regression_01() {
321
        let version = "0.0.0-WIP";
322
323
        let parsed = parse_version(version).unwrap();
324
325
        assert_eq!(0, parsed.major);
326
        assert_eq!(0, parsed.minor);
327
        assert_eq!(0, parsed.patch);
328
329
        let expected_pre = Some(vec![Identifier::AlphaNumeric(String::from("WIP"))]);
330
        assert_eq!(expected_pre, parsed.pre);
331
    }
318 332
}

4.8 Add tests

src/range.rs

225 225
        pre: pre,
226 226
    })
227 227
}
228
229
#[cfg(test)]
230
mod tests {
231
    use super::*;
232
    use range;
233
    use version::Identifier;
234
235
    #[test]
236
    fn test_parsing_default() {
237
        let r = range::parse("1.0.0").unwrap();
238
239
        assert_eq!(Predicate {
240
                op: Op::Compatible,
241
                major: 1,
242
                minor: Some(0),
243
                patch: Some(0),
244
                pre: Vec::new(),
245
            },
246
            r.predicates[0]
247
        );
248
    }
249
250
    #[test]
251
    fn test_parsing_exact_01() {
252
        let r = range::parse("=1.0.0").unwrap();
253
254
        assert_eq!(Predicate {
255
                op: Op::Ex,
256
                major: 1,
257
                minor: Some(0),
258
                patch: Some(0),
259
                pre: Vec::new(),
260
            },
261
            r.predicates[0]
262
        );
263
    }
264
265
    #[test]
266
    fn test_parsing_exact_02() {
267
        let r = range::parse("=0.9.0").unwrap();
268
269
        assert_eq!(Predicate {
270
                op: Op::Ex,
271
                major: 0,
272
                minor: Some(9),
273
                patch: Some(0),
274
                pre: Vec::new(),
275
            },
276
            r.predicates[0]
277
        );
278
    }
279
280
    #[test]
281
    fn test_parsing_exact_03() {
282
        let r = range::parse("=0.1.0-beta2.a").unwrap();
283
284
        assert_eq!(Predicate {
285
                op: Op::Ex,
286
                major: 0,
287
                minor: Some(1),
288
                patch: Some(0),
289
                pre: vec![Identifier::AlphaNumeric(String::from("beta2")),
290
                          Identifier::AlphaNumeric(String::from("a"))],
291
            },
292
            r.predicates[0]
293
        );
294
    }
295
296
    #[test]
297
    pub fn test_parsing_greater_than() {
298
        let r = range::parse("> 1.0.0").unwrap();
299
300
        assert_eq!(Predicate {
301
                op: Op::Gt,
302
                major: 1,
303
                minor: Some(0),
304
                patch: Some(0),
305
                pre: Vec::new(),
306
            },
307
            r.predicates[0]
308
        );
309
    }
310
311
    #[test]
312
    pub fn test_parsing_greater_than_01() {
313
        let r = range::parse(">= 1.0.0").unwrap();
314
315
        assert_eq!(Predicate {
316
                op: Op::GtEq,
317
                major: 1,
318
                minor: Some(0),
319
                patch: Some(0),
320
                pre: Vec::new(),
321
            },
322
            r.predicates[0]
323
        );
324
    }
325
326
    #[test]
327
    pub fn test_parsing_greater_than_02() {
328
        let r = range::parse(">= 2.1.0-alpha2").unwrap();
329
330
        assert_eq!(Predicate {
331
                op: Op::GtEq,
332
                major: 2,
333
                minor: Some(1),
334
                patch: Some(0),
335
                pre: vec![Identifier::AlphaNumeric(String::from("alpha2"))],
336
            },
337
            r.predicates[0]
338
        );
339
    }
340
341
    #[test]
342
    pub fn test_parsing_less_than() {
343
        let r = range::parse("< 1.0.0").unwrap();
344
345
        assert_eq!(Predicate {
346
                op: Op::Lt,
347
                major: 1,
348
                minor: Some(0),
349
                patch: Some(0),
350
                pre: Vec::new(),
351
            },
352
            r.predicates[0]
353
        );
354
    }
355
356
    #[test]
357
    pub fn test_parsing_less_than_eq() {
358
        let r = range::parse("<= 2.1.0-alpha2").unwrap();
359
360
        assert_eq!(Predicate {
361
                op: Op::LtEq,
362
                major: 2,
363
                minor: Some(1),
364
                patch: Some(0),
365
                pre: vec![Identifier::AlphaNumeric(String::from("alpha2"))],
366
            },
367
            r.predicates[0]
368
        );
369
    }
370
371
    #[test]
372
    pub fn test_parsing_tilde() {
373
        let r = range::parse("~1").unwrap();
374
375
        assert_eq!(Predicate {
376
                op: Op::Tilde,
377
                major: 1,
378
                minor: None,
379
                patch: None,
380
                pre: Vec::new(),
381
            },
382
            r.predicates[0]
383
        );
384
    }
385
386
    #[test]
387
    pub fn test_parsing_compatible() {
388
        let r = range::parse("^0").unwrap();
389
390
        assert_eq!(Predicate {
391
                op: Op::Compatible,
392
                major: 0,
393
                minor: None,
394
                patch: None,
395
                pre: Vec::new(),
396
            },
397
            r.predicates[0]
398
        );
399
    }
400
401
    #[test]
402
    fn test_parsing_blank() {
403
        let r = range::parse("").unwrap();
404
405
        assert_eq!(Predicate {
406
                op: Op::Wildcard(WildcardVersion::Major),
407
                major: 0,
408
                minor: None,
409
                patch: None,
410
                pre: Vec::new(),
411
            },
412
            r.predicates[0]
413
        );
414
    }
415
416
    #[test]
417
    fn test_parsing_wildcard() {
418
        let r = range::parse("*").unwrap();
419
420
        assert_eq!(Predicate {
421
                op: Op::Wildcard(WildcardVersion::Major),
422
                major: 0,
423
                minor: None,
424
                patch: None,
425
                pre: Vec::new(),
426
            },
427
            r.predicates[0]
428
        );
429
    }
430
431
    #[test]
432
    fn test_parsing_x() {
433
        let r = range::parse("x").unwrap();
434
435
        assert_eq!(Predicate {
436
                op: Op::Wildcard(WildcardVersion::Major),
437
                major: 0,
438
                minor: None,
439
                patch: None,
440
                pre: Vec::new(),
441
            },
442
            r.predicates[0]
443
        );
444
    }
445
446
    #[test]
447
    fn test_parsing_capital_x() {
448
        let r = range::parse("X").unwrap();
449
450
        assert_eq!(Predicate {
451
                op: Op::Wildcard(WildcardVersion::Major),
452
                major: 0,
453
                minor: None,
454
                patch: None,
455
                pre: Vec::new(),
456
            },
457
            r.predicates[0]
458
        );
459
    }
460
461
    #[test]
462
    fn test_parsing_minor_wildcard_star() {
463
        let r = range::parse("1.*").unwrap();
464
465
        assert_eq!(Predicate {
466
                op: Op::Wildcard(WildcardVersion::Minor),
467
                major: 1,
468
                minor: None,
469
                patch: None,
470
                pre: Vec::new(),
471
            },
472
            r.predicates[0]
473
        );
474
    }
475
476
    #[test]
477
    fn test_parsing_minor_wildcard_x() {
478
        let r = range::parse("1.x").unwrap();
479
480
        assert_eq!(Predicate {
481
                op: Op::Wildcard(WildcardVersion::Minor),
482
                major: 1,
483
                minor: None,
484
                patch: None,
485
                pre: Vec::new(),
486
            },
487
            r.predicates[0]
488
        );
489
    }
490
491
    #[test]
492
    fn test_parsing_minor_wildcard_capital_x() {
493
        let r = range::parse("1.X").unwrap();
494
495
        assert_eq!(Predicate {
496
                op: Op::Wildcard(WildcardVersion::Minor),
497
                major: 1,
498
                minor: None,
499
                patch: None,
500
                pre: Vec::new(),
501
            },
502
            r.predicates[0]
503
        );
504
    }
505
506
    #[test]
507
    fn test_parsing_patch_wildcard_star() {
508
        let r = range::parse("1.2.*").unwrap();
509
510
        assert_eq!(Predicate {
511
                op: Op::Wildcard(WildcardVersion::Patch),
512
                major: 1,
513
                minor: Some(2),
514
                patch: None,
515
                pre: Vec::new(),
516
            },
517
            r.predicates[0]
518
        );
519
    }
520
521
    #[test]
522
    fn test_parsing_patch_wildcard_x() {
523
        let r = range::parse("1.2.x").unwrap();
524
525
        assert_eq!(Predicate {
526
                op: Op::Wildcard(WildcardVersion::Patch),
527
                major: 1,
528
                minor: Some(2),
529
                patch: None,
530
                pre: Vec::new(),
531
            },
532
            r.predicates[0]
533
        );
534
    }
535
536
    #[test]
537
    fn test_parsing_patch_wildcard_capital_x() {
538
        let r = range::parse("1.2.X").unwrap();
539
540
        assert_eq!(Predicate {
541
                op: Op::Wildcard(WildcardVersion::Patch),
542
                major: 1,
543
                minor: Some(2),
544
                patch: None,
545
                pre: Vec::new(),
546
            },
547
            r.predicates[0]
548
        );
549
    }
550
551
//    #[test]
552
//    pub fn test_multiple() {
553
//        let r = req("> 0.0.9, <= 2.5.3");
554
//        assert_eq!(r.to_string(), "> 0.0.9, <= 2.5.3".to_string());
555
//        assert_match(&r, &["0.0.10", "1.0.0", "2.5.3"]);
556
//        assert_not_match(&r, &["0.0.8", "2.5.4"]);
557
//
558
//        let r = req("0.3.0, 0.4.0");
559
//        assert_eq!(r.to_string(), "^0.3.0, ^0.4.0".to_string());
560
//        assert_not_match(&r, &["0.0.8", "0.3.0", "0.4.0"]);
561
//
562
//        let r = req("<= 0.2.0, >= 0.5.0");
563
//        assert_eq!(r.to_string(), "<= 0.2.0, >= 0.5.0".to_string());
564
//        assert_not_match(&r, &["0.0.8", "0.3.0", "0.5.1"]);
565
//
566
//        let r = req("0.1.0, 0.1.4, 0.1.6");
567
//        assert_eq!(r.to_string(), "^0.1.0, ^0.1.4, ^0.1.6".to_string());
568
//        assert_match(&r, &["0.1.6", "0.1.9"]);
569
//        assert_not_match(&r, &["0.1.0", "0.1.4", "0.2.0"]);
570
//
571
//        assert!(VersionReq::parse("> 0.1.0,").is_err());
572
//        assert!(VersionReq::parse("> 0.3.0, ,").is_err());
573
//
574
//        let r = req(">=0.5.1-alpha3, <0.6");
575
//        assert_eq!(r.to_string(), ">= 0.5.1-alpha3, < 0.6".to_string());
576
//        assert_match(&r,
577
//                     &["0.5.1-alpha3", "0.5.1-alpha4", "0.5.1-beta", "0.5.1", "0.5.5"]);
578
//        assert_not_match(&r,
579
//                         &["0.5.1-alpha1", "0.5.2-alpha3", "0.5.5-pre", "0.5.0-pre"]);
580
//        assert_not_match(&r, &["0.6.0", "0.6.0-pre"]);
581
//    }
582
583
    #[test]
584
    pub fn test_parse_errors() {
585
        assert!(range::parse("\0").is_err());
586
        assert!(range::parse(">= >= 0.0.2").is_err());
587
        assert!(range::parse(">== 0.0.2").is_err());
588
        assert!(range::parse("a.0.0").is_err());
589
        assert!(range::parse("1.0.0-").is_err());
590
        assert!(range::parse(">=").is_err());
591
    }
592
593
}

src/version.rs

107 107
108 108
#[cfg(test)]
109 109
mod tests {
110
    use version;
110 111
    use super::*;
111 112
112 113
    #[test]
113 114
    fn parse_empty() {
114 115
        let version = "";
115 116
116
        let parsed = parse_version(version);
117
        let parsed = version::parse(version);
117 118
118 119
        assert!(parsed.is_err(), "empty string incorrectly considered a valid parse");
119 120
    }

122 123
    fn parse_blank() {
123 124
        let version = "  ";
124 125
125
        let parsed = parse_version(version);
126
        let parsed = version::parse(version);
126 127
127 128
        assert!(parsed.is_err(), "blank string incorrectly considered a valid parse");
128 129
    }

131 132
    fn parse_no_minor_patch() {
132 133
        let version = "1";
133 134
134
        let parsed = parse_version(version);
135
        let parsed = version::parse(version);
135 136
136 137
        assert!(parsed.is_err(), format!("'{}' incorrectly considered a valid parse", version));
137 138
    }

140 141
    fn parse_no_patch() {
141 142
        let version = "1.2";
142 143
143
        let parsed = parse_version(version);
144
        let parsed = version::parse(version);
144 145
145 146
        assert!(parsed.is_err(), format!("'{}' incorrectly considered a valid parse", version));
146 147
    }

149 150
    fn parse_empty_pre() {
150 151
        let version = "1.2.3-";
151 152
152
        let parsed = parse_version(version);
153
        let parsed = version::parse(version);
153 154
154 155
        assert!(parsed.is_err(), format!("'{}' incorrectly considered a valid parse", version));
155 156
    }

158 159
    fn parse_letters() {
159 160
        let version = "a.b.c";
160 161
161
        let parsed = parse_version(version);
162
        let parsed = version::parse(version);
162 163
163 164
        assert!(parsed.is_err(), format!("'{}' incorrectly considered a valid parse", version));
164 165
    }
165 166
166 167
    #[test]
167
    fn parse_version_with_letters() {
168
    fn parse_with_letters() {
168 169
        let version = "1.2.3 a.b.c";
169 170
170
        let parsed = parse_version(version);
171
        let parsed = version::parse(version);
171 172
172 173
        assert!(parsed.is_err(), format!("'{}' incorrectly considered a valid parse", version));
173 174
    }

176 177
    fn parse_basic_version() {
177 178
        let version = "1.2.3";
178 179
179
        let parsed = parse_version(version).unwrap();
180
        let parsed = version::parse(version).unwrap();
180 181
181 182
        assert_eq!(1, parsed.major);
182 183
        assert_eq!(2, parsed.minor);

187 188
    fn parse_trims_input() {
188 189
        let version = "  1.2.3  ";
189 190
190
        let parsed = parse_version(version).unwrap();
191
        let parsed = version::parse(version).unwrap();
191 192
192 193
        assert_eq!(1, parsed.major);
193 194
        assert_eq!(2, parsed.minor);

195 196
    }
196 197
197 198
    #[test]
198
    fn parse_version_no_major_leading_zeroes() {
199
    fn parse_no_major_leading_zeroes() {
199 200
        let version = "01.0.0";
200 201
201
        let parsed = parse_version(version);
202
        let parsed = version::parse(version);
202 203
203 204
        assert!(parsed.is_err(), "01 incorrectly considered a valid major version");
204 205
    }
205 206
206 207
    #[test]
207
    fn parse_version_no_minor_leading_zeroes() {
208
    fn parse_no_minor_leading_zeroes() {
208 209
        let version = "0.01.0";
209 210
210
        let parsed = parse_version(version);
211
        let parsed = version::parse(version);
211 212
212 213
        assert!(parsed.is_err(), "01 incorrectly considered a valid minor version");
213 214
    }
214 215
215 216
    #[test]
216
    fn parse_version_no_patch_leading_zeroes() {
217
    fn parse_no_patch_leading_zeroes() {
217 218
        let version = "0.0.01";
218 219
219
        let parsed = parse_version(version);
220
        let parsed = version::parse(version);
220 221
221 222
        assert!(parsed.is_err(), "01 incorrectly considered a valid patch version");
222 223
    }
223 224
224 225
    #[test]
225
    fn parse_version_basic_prerelease() {
226
    fn parse_basic_prerelease() {
226 227
        let version = "1.2.3-pre";
227 228
228
        let parsed = parse_version(version).unwrap();
229
        let parsed = version::parse(version).unwrap();
229 230
230 231
        let expected_pre = Some(vec![Identifier::AlphaNumeric(String::from("pre"))]);
231 232
        assert_eq!(expected_pre, parsed.pre);
232 233
    }
233 234
234 235
    #[test]
235
    fn parse_version_prerelease_alphanumeric() {
236
    fn parse_prerelease_alphanumeric() {
236 237
        let version = "1.2.3-alpha1";
237 238
238
        let parsed = parse_version(version).unwrap();
239
        let parsed = version::parse(version).unwrap();
239 240
240 241
        let expected_pre = Some(vec![Identifier::AlphaNumeric(String::from("alpha1"))]);
241 242
        assert_eq!(expected_pre, parsed.pre);
242 243
    }
243 244
244 245
    #[test]
245
    fn parse_version_prerelease_zero() {
246
    fn parse_prerelease_zero() {
246 247
        let version = "1.2.3-pre.0";
247 248
248
        let parsed = parse_version(version).unwrap();
249
        let parsed = version::parse(version).unwrap();
249 250
250 251
        let expected_pre = Some(vec![Identifier::AlphaNumeric(String::from("pre")),
251 252
                                     Identifier::Numeric(0)]);

253 254
    }
254 255
255 256
    #[test]
256
    fn parse_version_basic_build() {
257
    fn parse_basic_build() {
257 258
        let version = "1.2.3+build";
258 259
259
        let parsed = parse_version(version).unwrap();
260
        let parsed = version::parse(version).unwrap();
260 261
261 262
        let expected_build = Some(vec![Identifier::AlphaNumeric(String::from("build"))]);
262 263
        assert_eq!(expected_build, parsed.build);
263 264
    }
264 265
265 266
    #[test]
266
    fn parse_version_build_alphanumeric() {
267
    fn parse_build_alphanumeric() {
267 268
        let version = "1.2.3+build5";
268 269
269
        let parsed = parse_version(version).unwrap();
270
        let parsed = version::parse(version).unwrap();
270 271
271 272
        let expected_build = Some(vec![Identifier::AlphaNumeric(String::from("build5"))]);
272 273
        assert_eq!(expected_build, parsed.build);
273 274
    }
274 275
275 276
    #[test]
276
    fn parse_version_pre_and_build() {
277
    fn parse_pre_and_build() {
277 278
        let version = "1.2.3-alpha1+build5";
278 279
279
        let parsed = parse_version(version).unwrap();
280
        let parsed = version::parse(version).unwrap();
280 281
281 282
        let expected_pre = Some(vec![Identifier::AlphaNumeric(String::from("alpha1"))]);
282 283
        assert_eq!(expected_pre, parsed.pre);

286 287
    }
287 288
288 289
    #[test]
289
    fn parse_version_complex_metadata_01() {
290
    fn parse_complex_metadata_01() {
290 291
        let version = "1.2.3-1.alpha1.9+build5.7.3aedf  ";
291 292
292
        let parsed = parse_version(version).unwrap();
293
        let parsed = version::parse(version).unwrap();
293 294
294 295
        let expected_pre = Some(vec![Identifier::Numeric(1),
295 296
                                     Identifier::AlphaNumeric(String::from("alpha1")),

303 304
    }
304 305
305 306
    #[test]
306
    fn parse_version_complex_metadata_02() {
307
    fn parse_complex_metadata_02() {
307 308
        let version = "0.4.0-beta.1+0851523";
308 309
309
        let parsed = parse_version(version).unwrap();
310
        let parsed = version::parse(version).unwrap();
310 311
311 312
        let expected_pre = Some(vec![Identifier::AlphaNumeric(String::from("beta")),
312 313
                                     Identifier::Numeric(1)]);

320 321
    fn parse_regression_01() {
321 322
        let version = "0.0.0-WIP";
322 323
323
        let parsed = parse_version(version).unwrap();
324
        let parsed = version::parse(version).unwrap();
324 325
325 326
        assert_eq!(0, parsed.major);
326 327
        assert_eq!(0, parsed.minor);

4.9 Update tests

src/range.rs

548 548
        );
549 549
    }
550 550
551
//    #[test]
552
//    pub fn test_multiple() {
553
//        let r = req("> 0.0.9, <= 2.5.3");
554
//        assert_eq!(r.to_string(), "> 0.0.9, <= 2.5.3".to_string());
555
//        assert_match(&r, &["0.0.10", "1.0.0", "2.5.3"]);
556
//        assert_not_match(&r, &["0.0.8", "2.5.4"]);
557
//
558
//        let r = req("0.3.0, 0.4.0");
559
//        assert_eq!(r.to_string(), "^0.3.0, ^0.4.0".to_string());
560
//        assert_not_match(&r, &["0.0.8", "0.3.0", "0.4.0"]);
561
//
562
//        let r = req("<= 0.2.0, >= 0.5.0");
563
//        assert_eq!(r.to_string(), "<= 0.2.0, >= 0.5.0".to_string());
564
//        assert_not_match(&r, &["0.0.8", "0.3.0", "0.5.1"]);
565
//
566
//        let r = req("0.1.0, 0.1.4, 0.1.6");
567
//        assert_eq!(r.to_string(), "^0.1.0, ^0.1.4, ^0.1.6".to_string());
568
//        assert_match(&r, &["0.1.6", "0.1.9"]);
569
//        assert_not_match(&r, &["0.1.0", "0.1.4", "0.2.0"]);
570
//
571
//        assert!(VersionReq::parse("> 0.1.0,").is_err());
572
//        assert!(VersionReq::parse("> 0.3.0, ,").is_err());
573
//
574
//        let r = req(">=0.5.1-alpha3, <0.6");
575
//        assert_eq!(r.to_string(), ">= 0.5.1-alpha3, < 0.6".to_string());
576
//        assert_match(&r,
577
//                     &["0.5.1-alpha3", "0.5.1-alpha4", "0.5.1-beta", "0.5.1", "0.5.5"]);
578
//        assert_not_match(&r,
579
//                         &["0.5.1-alpha1", "0.5.2-alpha3", "0.5.5-pre", "0.5.0-pre"]);
580
//        assert_not_match(&r, &["0.6.0", "0.6.0-pre"]);
581
//    }
551
    #[test]
552
    pub fn test_multiple_01() {
553
        let r = range::parse("> 0.0.9, <= 2.5.3").unwrap();
554
555
        assert_eq!(Predicate {
556
                op: Op::Gt,
557
                major: 0,
558
                minor: Some(0),
559
                patch: Some(9),
560
                pre: Vec::new(),
561
            },
562
            r.predicates[0]
563
        );
564
565
        assert_eq!(Predicate {
566
                op: Op::LtEq,
567
                major: 2,
568
                minor: Some(5),
569
                patch: Some(3),
570
                pre: Vec::new(),
571
            },
572
            r.predicates[1]
573
        );
574
    }
575
576
    #[test]
577
    pub fn test_multiple_02() {
578
        let r = range::parse("0.3.0, 0.4.0").unwrap();
579
580
        assert_eq!(Predicate {
581
                op: Op::Compatible,
582
                major: 0,
583
                minor: Some(3),
584
                patch: Some(0),
585
                pre: Vec::new(),
586
            },
587
            r.predicates[0]
588
        );
589
590
        assert_eq!(Predicate {
591
                op: Op::Compatible,
592
                major: 0,
593
                minor: Some(4),
594
                patch: Some(0),
595
                pre: Vec::new(),
596
            },
597
            r.predicates[1]
598
        );
599
    }
600
601
    #[test]
602
    pub fn test_multiple_03() {
603
        let r = range::parse("<= 0.2.0, >= 0.5.0").unwrap();
604
605
        assert_eq!(Predicate {
606
                op: Op::LtEq,
607
                major: 0,
608
                minor: Some(2),
609
                patch: Some(0),
610
                pre: Vec::new(),
611
            },
612
            r.predicates[0]
613
        );
614
615
        assert_eq!(Predicate {
616
                op: Op::GtEq,
617
                major: 0,
618
                minor: Some(5),
619
                patch: Some(0),
620
                pre: Vec::new(),
621
            },
622
            r.predicates[1]
623
        );
624
    }
625
626
    #[test]
627
    pub fn test_multiple_04() {
628
        let r = range::parse("0.1.0, 0.1.4, 0.1.6").unwrap();
629
630
        assert_eq!(Predicate {
631
                op: Op::Compatible,
632
                major: 0,
633
                minor: Some(1),
634
                patch: Some(0),
635
                pre: Vec::new(),
636
            },
637
            r.predicates[0]
638
        );
639
640
        assert_eq!(Predicate {
641
                op: Op::Compatible,
642
                major: 0,
643
                minor: Some(1),
644
                patch: Some(4),
645
                pre: Vec::new(),
646
            },
647
            r.predicates[1]
648
        );
649
650
        assert_eq!(Predicate {
651
                op: Op::Compatible,
652
                major: 0,
653
                minor: Some(1),
654
                patch: Some(6),
655
                pre: Vec::new(),
656
            },
657
            r.predicates[2]
658
        );
659
    }
660
661
    #[test]
662
    pub fn test_multiple_05() {
663
        let r = range::parse(">=0.5.1-alpha3, <0.6").unwrap();
664
665
        assert_eq!(Predicate {
666
                op: Op::GtEq,
667
                major: 0,
668
                minor: Some(5),
669
                patch: Some(1),
670
                pre: vec![Identifier::AlphaNumeric(String::from("alpha3"))],
671
            },
672
            r.predicates[0]
673
        );
674
675
        assert_eq!(Predicate {
676
                op: Op::Lt,
677
                major: 0,
678
                minor: Some(6),
679
                patch: None,
680
                pre: Vec::new(),
681
            },
682
            r.predicates[1]
683
        );
684
    }
582 685
583 686
    #[test]
584 687
    pub fn test_parse_errors() {
585 688
        assert!(range::parse("\0").is_err());
586
        assert!(range::parse(">= >= 0.0.2").is_err());
587
        assert!(range::parse(">== 0.0.2").is_err());
588
        assert!(range::parse("a.0.0").is_err());
589
        assert!(range::parse("1.0.0-").is_err());
689
//        assert!(range::parse(">= >= 0.0.2").is_err());
690
//        assert!(range::parse(">== 0.0.2").is_err());
691
//        assert!(range::parse("a.0.0").is_err());
692
//        assert!(range::parse("1.0.0-").is_err());
590 693
        assert!(range::parse(">=").is_err());
694
//        assert!(range::parse("> 0.1.0,").is_err());
695
//        assert!(range::parse("> 0.3.0, ,").is_err());
591 696
    }
592 697
593 698
}

4.10 Uncomment tests

src/range.rs

686 686
    #[test]
687 687
    pub fn test_parse_errors() {
688 688
        assert!(range::parse("\0").is_err());
689
//        assert!(range::parse(">= >= 0.0.2").is_err());
690
//        assert!(range::parse(">== 0.0.2").is_err());
691
//        assert!(range::parse("a.0.0").is_err());
692
//        assert!(range::parse("1.0.0-").is_err());
689
        assert!(range::parse(">= >= 0.0.2").is_err());
690
        assert!(range::parse(">== 0.0.2").is_err());
691
        assert!(range::parse("a.0.0").is_err());
692
        assert!(range::parse("1.0.0-").is_err());
693 693
        assert!(range::parse(">=").is_err());
694
//        assert!(range::parse("> 0.1.0,").is_err());
695
//        assert!(range::parse("> 0.3.0, ,").is_err());
694
        assert!(range::parse("> 0.1.0,").is_err());
695
        assert!(range::parse("> 0.3.0, ,").is_err());
696 696
    }
697
697
698 698
}

4.11 Update tests

src/version.rs

107 107
108 108
#[cfg(test)]
109 109
mod tests {
110
    use common::is_alpha_numeric;
110 111
    use version;
111 112
    use super::*;
112 113

228 229
229 230
        let parsed = version::parse(version).unwrap();
230 231
231
        let expected_pre = Some(vec![Identifier::AlphaNumeric(String::from("pre"))]);
232
        let expected_pre = vec![Identifier::AlphaNumeric(String::from("pre"))];
232 233
        assert_eq!(expected_pre, parsed.pre);
233 234
    }
234 235

238 239
239 240
        let parsed = version::parse(version).unwrap();
240 241
241
        let expected_pre = Some(vec![Identifier::AlphaNumeric(String::from("alpha1"))]);
242
        let expected_pre = vec![Identifier::AlphaNumeric(String::from("alpha1"))];
242 243
        assert_eq!(expected_pre, parsed.pre);
243 244
    }
244 245

248 249
249 250
        let parsed = version::parse(version).unwrap();
250 251
251
        let expected_pre = Some(vec![Identifier::AlphaNumeric(String::from("pre")),
252
                                     Identifier::Numeric(0)]);
252
        let expected_pre = vec![Identifier::AlphaNumeric(String::from("pre")),
253
                                Identifier::Numeric(0)];
253 254
        assert_eq!(expected_pre, parsed.pre);
254 255
    }
255 256

259 260
260 261
        let parsed = version::parse(version).unwrap();
261 262
262
        let expected_build = Some(vec![Identifier::AlphaNumeric(String::from("build"))]);
263
        let expected_build = vec![Identifier::AlphaNumeric(String::from("build"))];
263 264
        assert_eq!(expected_build, parsed.build);
264 265
    }
265 266

269 270
270 271
        let parsed = version::parse(version).unwrap();
271 272
272
        let expected_build = Some(vec![Identifier::AlphaNumeric(String::from("build5"))]);
273
        let expected_build = vec![Identifier::AlphaNumeric(String::from("build5"))];
273 274
        assert_eq!(expected_build, parsed.build);
274 275
    }
275 276

279 280
280 281
        let parsed = version::parse(version).unwrap();
281 282
282
        let expected_pre = Some(vec![Identifier::AlphaNumeric(String::from("alpha1"))]);
283
        let expected_pre = vec![Identifier::AlphaNumeric(String::from("alpha1"))];
283 284
        assert_eq!(expected_pre, parsed.pre);
284 285
285
        let expected_build = Some(vec![Identifier::AlphaNumeric(String::from("build5"))]);
286
        let expected_build = vec![Identifier::AlphaNumeric(String::from("build5"))];
286 287
        assert_eq!(expected_build, parsed.build);
287 288
    }
288 289

292 293
293 294
        let parsed = version::parse(version).unwrap();
294 295
295
        let expected_pre = Some(vec![Identifier::Numeric(1),
296
                                     Identifier::AlphaNumeric(String::from("alpha1")),
297
                                     Identifier::Numeric(9)]);
296
        let expected_pre = vec![Identifier::Numeric(1),
297
                                Identifier::AlphaNumeric(String::from("alpha1")),
298
                                Identifier::Numeric(9)];
298 299
        assert_eq!(expected_pre, parsed.pre);
299 300
300
        let expected_build = Some(vec![Identifier::AlphaNumeric(String::from("build5")),
301
                                       Identifier::Numeric(7),
302
                                       Identifier::AlphaNumeric(String::from("3aedf"))]);
301
        let expected_build = vec![Identifier::AlphaNumeric(String::from("build5")),
302
                                  Identifier::Numeric(7),
303
                                  Identifier::AlphaNumeric(String::from("3aedf"))];
303 304
        assert_eq!(expected_build, parsed.build);
304 305
    }
305 306

309 310
310 311
        let parsed = version::parse(version).unwrap();
311 312
312
        let expected_pre = Some(vec![Identifier::AlphaNumeric(String::from("beta")),
313
                                     Identifier::Numeric(1)]);
313
        let expected_pre = vec![Identifier::AlphaNumeric(String::from("beta")),
314
                                Identifier::Numeric(1)];
314 315
        assert_eq!(expected_pre, parsed.pre);
315 316
316
        let expected_build = Some(vec![Identifier::AlphaNumeric(String::from("0851523"))]);
317
        let expected_build = vec![Identifier::AlphaNumeric(String::from("0851523"))];
317 318
        assert_eq!(expected_build, parsed.build);
318 319
    }
319 320

327 328
        assert_eq!(0, parsed.minor);
328 329
        assert_eq!(0, parsed.patch);
329 330
330
        let expected_pre = Some(vec![Identifier::AlphaNumeric(String::from("WIP"))]);
331
        let expected_pre = vec![Identifier::AlphaNumeric(String::from("WIP"))];
331 332
        assert_eq!(expected_pre, parsed.pre);
332 333
    }
333 334
}

4.12 this import isn't used

src/version.rs

107 107
108 108
#[cfg(test)]
109 109
mod tests {
110
    use common::is_alpha_numeric;
111 110
    use version;
112 111
    use super::*;
113 112

4.13 Add tests

src/range.rs

684 684
    }
685 685
686 686
    #[test]
687
    fn test_parse_build_metadata_with_predicate() {
688
        assert_eq!(range::parse("^1.2.3+meta").unwrap().predicates[0].op,
689
                   Op::Compatible);
690
        assert_eq!(range::parse("~1.2.3+meta").unwrap().predicates[0].op,
691
                   Op::Tilde);
692
        assert_eq!(range::parse("=1.2.3+meta").unwrap().predicates[0].op,
693
                   Op::Ex);
694
        assert_eq!(range::parse("<=1.2.3+meta").unwrap().predicates[0].op,
695
                   Op::LtEq);
696
        assert_eq!(range::parse(">=1.2.3+meta").unwrap().predicates[0].op,
697
                   Op::GtEq);
698
        assert_eq!(range::parse("<1.2.3+meta").unwrap().predicates[0].op,
699
                   Op::Lt);
700
        assert_eq!(range::parse(">1.2.3+meta").unwrap().predicates[0].op,
701
                   Op::Gt);
702
    }
703
704
    #[test]
687 705
    pub fn test_parse_errors() {
688 706
        assert!(range::parse("\0").is_err());
689 707
        assert!(range::parse(">= >= 0.0.2").is_err());

4.14 Add test

src/range.rs

712 712
        assert!(range::parse("> 0.1.0,").is_err());
713 713
        assert!(range::parse("> 0.3.0, ,").is_err());
714 714
    }
715
716
    #[test]
717
    pub fn test_large_version() {
718
        assert!(range::parse("18446744073709551617.0.0").is_err());
719
    }
715 720
716 721
}

4.15 Add tests

src/range.rs

712 712
        assert!(range::parse("> 0.1.0,").is_err());
713 713
        assert!(range::parse("> 0.3.0, ,").is_err());
714 714
    }
715
715
716 716
    #[test]
717
    pub fn test_large_version() {
717
    pub fn test_large_major_version() {
718 718
        assert!(range::parse("18446744073709551617.0.0").is_err());
719 719
    }
720 720
721
    #[test]
722
    pub fn test_large_minor_version() {
723
        assert!(range::parse("0.18446744073709551617.0").is_err());
724
    }
725
721 726
}

4.16 Add test

src/range.rs

723 723
        assert!(range::parse("0.18446744073709551617.0").is_err());
724 724
    }
725 725
726
    #[test]
727
    pub fn test_large_patch_version() {
728
        assert!(range::parse("0.0.18446744073709551617").is_err());
729
    }
726 730
}