]> git.proxmox.com Git - cargo.git/blob - src/cargo/core/package_id_spec.rs
Auto merge of #1122 - alexcrichton:issue-1119, r=brson
[cargo.git] / src / cargo / core / package_id_spec.rs
1 use std::fmt;
2 use semver::Version;
3 use url::{self, Url, UrlParser};
4
5 use core::PackageId;
6 use util::{CargoResult, ToUrl, human, ToSemver, ChainError};
7
8 #[derive(Clone, PartialEq, Eq)]
9 pub struct PackageIdSpec {
10 name: String,
11 version: Option<Version>,
12 url: Option<Url>,
13 }
14
15 impl PackageIdSpec {
16 pub fn parse(spec: &str) -> CargoResult<PackageIdSpec> {
17 if spec.contains("/") {
18 match spec.to_url() {
19 Ok(url) => return PackageIdSpec::from_url(url),
20 Err(..) => {}
21 }
22 if !spec.contains("://") {
23 match url(format!("cargo://{}", spec).as_slice()) {
24 Ok(url) => return PackageIdSpec::from_url(url),
25 Err(..) => {}
26 }
27 }
28 }
29 let mut parts = spec.as_slice().splitn(1, ':');
30 let name = parts.next().unwrap();
31 let version = match parts.next() {
32 Some(version) => Some(try!(Version::parse(version).map_err(human))),
33 None => None,
34 };
35 for ch in name.chars() {
36 if !ch.is_alphanumeric() && ch != '_' && ch != '-' {
37 return Err(human(format!("invalid character in pkgid `{}`: `{}`",
38 spec, ch)))
39 }
40 }
41 Ok(PackageIdSpec {
42 name: name.to_string(),
43 version: version,
44 url: None,
45 })
46 }
47
48 pub fn from_package_id(package_id: &PackageId) -> PackageIdSpec {
49 PackageIdSpec {
50 name: package_id.get_name().to_string(),
51 version: Some(package_id.get_version().clone()),
52 url: Some(package_id.get_source_id().get_url().clone()),
53 }
54 }
55
56 fn from_url(mut url: Url) -> CargoResult<PackageIdSpec> {
57 if url.query.is_some() {
58 return Err(human(format!("cannot have a query string in a pkgid: {}",
59 url)));
60 }
61 let frag = url.fragment.take();
62 let (name, version) = {
63 let path = try!(url.path().chain_error(|| {
64 human(format!("pkgid urls must have a path: {}", url))
65 }));
66 let path_name = try!(path.last().chain_error(|| {
67 human(format!("pkgid urls must have at least one path \
68 component: {}", url))
69 }));
70 match frag {
71 Some(fragment) => {
72 let mut parts = fragment.as_slice().splitn(1, ':');
73 let name_or_version = parts.next().unwrap();
74 match parts.next() {
75 Some(part) => {
76 let version = try!(part.to_semver().map_err(human));
77 (name_or_version.to_string(), Some(version))
78 }
79 None => {
80 if name_or_version.char_at(0).is_alphabetic() {
81 (name_or_version.to_string(), None)
82 } else {
83 let version = try!(name_or_version.to_semver()
84 .map_err(human));
85 (path_name.to_string(), Some(version))
86 }
87 }
88 }
89 }
90 None => (path_name.to_string(), None),
91 }
92 };
93 Ok(PackageIdSpec {
94 name: name,
95 version: version,
96 url: Some(url),
97 })
98 }
99
100 pub fn get_name(&self) -> &str { self.name.as_slice() }
101 pub fn get_version(&self) -> Option<&Version> { self.version.as_ref() }
102 pub fn get_url(&self) -> Option<&Url> { self.url.as_ref() }
103
104 pub fn matches(&self, package_id: &PackageId) -> bool {
105 if self.get_name() != package_id.get_name() { return false }
106
107 match self.version {
108 Some(ref v) => if v != package_id.get_version() { return false },
109 None => {}
110 }
111
112 match self.url {
113 Some(ref u) => u == package_id.get_source_id().get_url(),
114 None => true
115 }
116 }
117 }
118
119 fn url(s: &str) -> url::ParseResult<Url> {
120 return UrlParser::new().scheme_type_mapper(mapper).parse(s);
121
122 fn mapper(scheme: &str) -> url::SchemeType {
123 if scheme == "cargo" {
124 url::SchemeType::Relative(1)
125 } else {
126 url::whatwg_scheme_type_mapper(scheme)
127 }
128 }
129
130 }
131
132 impl fmt::Show for PackageIdSpec {
133 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
134 let mut printed_name = false;
135 match self.url {
136 Some(ref url) => {
137 if url.scheme.as_slice() == "cargo" {
138 try!(write!(f, "{}/{}", url.host().unwrap(),
139 url.path().unwrap().connect("/")));
140 } else {
141 try!(write!(f, "{}", url));
142 }
143 if url.path().unwrap().last().unwrap() != &self.name {
144 printed_name = true;
145 try!(write!(f, "#{}", self.name));
146 }
147 }
148 None => { printed_name = true; try!(write!(f, "{}", self.name)) }
149 }
150 match self.version {
151 Some(ref v) => {
152 try!(write!(f, "{}{}", if printed_name {":"} else {"#"}, v));
153 }
154 None => {}
155 }
156 Ok(())
157 }
158 }
159
160 #[cfg(test)]
161 mod tests {
162 use core::{PackageId, SourceId};
163 use super::{PackageIdSpec, url};
164 use semver::Version;
165
166 #[test]
167 fn good_parsing() {
168 fn ok(spec: &str, expected: PackageIdSpec) {
169 let parsed = PackageIdSpec::parse(spec).unwrap();
170 assert_eq!(parsed, expected);
171 assert_eq!(parsed.to_string().as_slice(), spec);
172 }
173
174 ok("http://crates.io/foo#1.2.3", PackageIdSpec {
175 name: "foo".to_string(),
176 version: Some(Version::parse("1.2.3").unwrap()),
177 url: Some(url("http://crates.io/foo").unwrap()),
178 });
179 ok("http://crates.io/foo#bar:1.2.3", PackageIdSpec {
180 name: "bar".to_string(),
181 version: Some(Version::parse("1.2.3").unwrap()),
182 url: Some(url("http://crates.io/foo").unwrap()),
183 });
184 ok("crates.io/foo", PackageIdSpec {
185 name: "foo".to_string(),
186 version: None,
187 url: Some(url("cargo://crates.io/foo").unwrap()),
188 });
189 ok("crates.io/foo#1.2.3", PackageIdSpec {
190 name: "foo".to_string(),
191 version: Some(Version::parse("1.2.3").unwrap()),
192 url: Some(url("cargo://crates.io/foo").unwrap()),
193 });
194 ok("crates.io/foo#bar", PackageIdSpec {
195 name: "bar".to_string(),
196 version: None,
197 url: Some(url("cargo://crates.io/foo").unwrap()),
198 });
199 ok("crates.io/foo#bar:1.2.3", PackageIdSpec {
200 name: "bar".to_string(),
201 version: Some(Version::parse("1.2.3").unwrap()),
202 url: Some(url("cargo://crates.io/foo").unwrap()),
203 });
204 ok("foo", PackageIdSpec {
205 name: "foo".to_string(),
206 version: None,
207 url: None,
208 });
209 ok("foo:1.2.3", PackageIdSpec {
210 name: "foo".to_string(),
211 version: Some(Version::parse("1.2.3").unwrap()),
212 url: None,
213 });
214 }
215
216 #[test]
217 fn bad_parsing() {
218 assert!(PackageIdSpec::parse("baz:").is_err());
219 assert!(PackageIdSpec::parse("baz:1.0").is_err());
220 assert!(PackageIdSpec::parse("http://baz:1.0").is_err());
221 assert!(PackageIdSpec::parse("http://#baz:1.0").is_err());
222 }
223
224 #[test]
225 fn matching() {
226 let sid = SourceId::for_central().unwrap();
227 let foo = PackageId::new("foo", "1.2.3", &sid).unwrap();
228 let bar = PackageId::new("bar", "1.2.3", &sid).unwrap();
229
230 assert!( PackageIdSpec::parse("foo").unwrap().matches(&foo));
231 assert!(!PackageIdSpec::parse("foo").unwrap().matches(&bar));
232 assert!( PackageIdSpec::parse("foo:1.2.3").unwrap().matches(&foo));
233 assert!(!PackageIdSpec::parse("foo:1.2.2").unwrap().matches(&foo));
234 }
235 }