]>
Commit | Line | Data |
---|---|---|
f8fb0a02 | 1 | use std::collections::BTreeMap; |
ee5e24ff | 2 | use std::env; |
7e66058a EH |
3 | use std::fs; |
4 | use std::path::Path; | |
7e66058a | 5 | |
10373f40 AO |
6 | use serde::{Deserialize, Deserializer}; |
7 | use serde::de; | |
16d3e72a | 8 | |
5d0cb3f2 | 9 | use git2::Config as GitConfig; |
4ebeb1f7 | 10 | use git2::Repository as GitRepository; |
a4272ef2 | 11 | |
58ddb28a | 12 | use core::Workspace; |
6633e290 | 13 | use ops::is_bad_artifact_name; |
7eca7f27 | 14 | use util::{GitRepo, HgRepo, PijulRepo, FossilRepo, internal}; |
7e66058a | 15 | use util::{Config, paths}; |
c7de4859 | 16 | use util::errors::{CargoError, CargoResult, CargoResultExt}; |
7e66058a EH |
17 | |
18 | use toml; | |
284c0ef9 | 19 | |
abe56727 | 20 | #[derive(Clone, Copy, Debug, PartialEq)] |
7eca7f27 | 21 | pub enum VersionControl { Git, Hg, Pijul, Fossil, NoVcs } |
16d3e72a | 22 | |
a4272ef2 | 23 | pub struct NewOptions<'a> { |
16d3e72a | 24 | pub version_control: Option<VersionControl>, |
a4272ef2 | 25 | pub bin: bool, |
a882abe7 | 26 | pub lib: bool, |
a4272ef2 | 27 | pub path: &'a str, |
99736d84 | 28 | pub name: Option<&'a str>, |
a4272ef2 AC |
29 | } |
30 | ||
800172fb VS |
31 | struct SourceFileInformation { |
32 | relative_path: String, | |
33 | target_name: String, | |
34 | bin: bool, | |
35 | } | |
36 | ||
37 | struct MkOptions<'a> { | |
38 | version_control: Option<VersionControl>, | |
39 | path: &'a Path, | |
40 | name: &'a str, | |
7e66058a | 41 | source_files: Vec<SourceFileInformation>, |
0e4fa582 | 42 | bin: bool, |
800172fb VS |
43 | } |
44 | ||
10373f40 AO |
45 | impl<'de> Deserialize<'de> for VersionControl { |
46 | fn deserialize<D: Deserializer<'de>>(d: D) -> Result<VersionControl, D::Error> { | |
47 | Ok(match &String::deserialize(d)?[..] { | |
16d3e72a WW |
48 | "git" => VersionControl::Git, |
49 | "hg" => VersionControl::Hg, | |
2b31978c | 50 | "pijul" => VersionControl::Pijul, |
7eca7f27 | 51 | "fossil" => VersionControl::Fossil, |
16d3e72a WW |
52 | "none" => VersionControl::NoVcs, |
53 | n => { | |
10373f40 AO |
54 | let value = de::Unexpected::Str(n); |
55 | let msg = "unsupported version control system"; | |
56 | return Err(de::Error::invalid_value(value, &msg)); | |
16d3e72a WW |
57 | } |
58 | }) | |
59 | } | |
60 | } | |
61 | ||
a882abe7 JT |
62 | impl<'a> NewOptions<'a> { |
63 | pub fn new(version_control: Option<VersionControl>, | |
64 | bin: bool, | |
65 | lib: bool, | |
66 | path: &'a str, | |
7e66058a | 67 | name: Option<&'a str>) -> NewOptions<'a> { |
a882abe7 JT |
68 | |
69 | // default to lib | |
70 | let is_lib = if !bin { | |
71 | true | |
72 | } | |
73 | else { | |
74 | lib | |
75 | }; | |
76 | ||
77 | NewOptions { | |
78 | version_control: version_control, | |
79 | bin: bin, | |
80 | lib: is_lib, | |
81 | path: path, | |
82 | name: name, | |
83 | } | |
84 | } | |
85 | } | |
86 | ||
de0f6041 AC |
87 | struct CargoNewConfig { |
88 | name: Option<String>, | |
89 | email: Option<String>, | |
16d3e72a | 90 | version_control: Option<VersionControl>, |
de0f6041 AC |
91 | } |
92 | ||
800172fb VS |
93 | fn get_name<'a>(path: &'a Path, opts: &'a NewOptions, config: &Config) -> CargoResult<&'a str> { |
94 | if let Some(name) = opts.name { | |
95 | return Ok(name); | |
96 | } | |
455f800c | 97 | |
800172fb VS |
98 | if path.file_name().is_none() { |
99 | bail!("cannot auto-detect project name from path {:?} ; use --name to override", | |
100 | path.as_os_str()); | |
101 | } | |
455f800c | 102 | |
e95044e3 | 103 | let dir_name = path.file_name().and_then(|s| s.to_str()).ok_or_else(|| { |
c7de4859 | 104 | CargoError::from(format!("cannot create a project with a non-unicode name: {:?}", |
105 | path.file_name().unwrap())) | |
82655b46 | 106 | })?; |
455f800c | 107 | |
800172fb VS |
108 | if opts.bin { |
109 | Ok(dir_name) | |
110 | } else { | |
111 | let new_name = strip_rust_affixes(dir_name); | |
112 | if new_name != dir_name { | |
f8fb0a02 AC |
113 | writeln!(config.shell().err(), |
114 | "note: package will be named `{}`; use --name to override", | |
115 | new_name)?; | |
99736d84 | 116 | } |
800172fb VS |
117 | Ok(new_name) |
118 | } | |
119 | } | |
120 | ||
6633e290 | 121 | fn check_name(name: &str, is_bin: bool) -> CargoResult<()> { |
466c74a9 SBI |
122 | |
123 | // Ban keywords + test list found at | |
124 | // https://doc.rust-lang.org/grammar.html#keywords | |
125 | let blacklist = ["abstract", "alignof", "as", "become", "box", | |
126 | "break", "const", "continue", "crate", "do", | |
127 | "else", "enum", "extern", "false", "final", | |
128 | "fn", "for", "if", "impl", "in", | |
129 | "let", "loop", "macro", "match", "mod", | |
130 | "move", "mut", "offsetof", "override", "priv", | |
131 | "proc", "pub", "pure", "ref", "return", | |
132 | "self", "sizeof", "static", "struct", | |
133 | "super", "test", "trait", "true", "type", "typeof", | |
134 | "unsafe", "unsized", "use", "virtual", "where", | |
135 | "while", "yield"]; | |
6633e290 | 136 | if blacklist.contains(&name) || (is_bin && is_bad_artifact_name(name)) { |
d30e963c TE |
137 | bail!("The name `{}` cannot be used as a crate name\n\ |
138 | use --name to override crate name", | |
139 | name) | |
140 | } | |
141 | ||
73aa1174 RS |
142 | if let Some(ref c) = name.chars().nth(0) { |
143 | if c.is_digit(10) { | |
144 | bail!("Package names starting with a digit cannot be used as a crate name\n\ | |
145 | use --name to override crate name") | |
146 | } | |
147 | } | |
148 | ||
9a387087 AK |
149 | for c in name.chars() { |
150 | if c.is_alphanumeric() { continue } | |
151 | if c == '_' || c == '-' { continue } | |
800172fb VS |
152 | bail!("Invalid character `{}` in crate name: `{}`\n\ |
153 | use --name to override crate name", | |
154 | c, name) | |
155 | } | |
156 | Ok(()) | |
157 | } | |
158 | ||
455f800c AC |
159 | fn detect_source_paths_and_types(project_path : &Path, |
160 | project_name: &str, | |
800172fb VS |
161 | detected_files: &mut Vec<SourceFileInformation>, |
162 | ) -> CargoResult<()> { | |
163 | let path = project_path; | |
164 | let name = project_name; | |
455f800c | 165 | |
800172fb VS |
166 | enum H { |
167 | Bin, | |
168 | Lib, | |
169 | Detect, | |
170 | } | |
455f800c | 171 | |
800172fb VS |
172 | struct Test { |
173 | proposed_path: String, | |
174 | handling: H, | |
175 | } | |
455f800c | 176 | |
800172fb | 177 | let tests = vec![ |
23591fe5 LL |
178 | Test { proposed_path: String::from("src/main.rs"), handling: H::Bin }, |
179 | Test { proposed_path: String::from("main.rs"), handling: H::Bin }, | |
800172fb | 180 | Test { proposed_path: format!("src/{}.rs", name), handling: H::Detect }, |
23591fe5 LL |
181 | Test { proposed_path: format!("{}.rs", name), handling: H::Detect }, |
182 | Test { proposed_path: String::from("src/lib.rs"), handling: H::Lib }, | |
183 | Test { proposed_path: String::from("lib.rs"), handling: H::Lib }, | |
800172fb | 184 | ]; |
455f800c | 185 | |
800172fb VS |
186 | for i in tests { |
187 | let pp = i.proposed_path; | |
455f800c | 188 | |
800172fb VS |
189 | // path/pp does not exist or is not a file |
190 | if !fs::metadata(&path.join(&pp)).map(|x| x.is_file()).unwrap_or(false) { | |
191 | continue; | |
192 | } | |
455f800c | 193 | |
800172fb VS |
194 | let sfi = match i.handling { |
195 | H::Bin => { | |
455f800c AC |
196 | SourceFileInformation { |
197 | relative_path: pp, | |
198 | target_name: project_name.to_string(), | |
199 | bin: true | |
800172fb VS |
200 | } |
201 | } | |
202 | H::Lib => { | |
455f800c AC |
203 | SourceFileInformation { |
204 | relative_path: pp, | |
205 | target_name: project_name.to_string(), | |
800172fb VS |
206 | bin: false |
207 | } | |
208 | } | |
209 | H::Detect => { | |
82655b46 | 210 | let content = paths::read(&path.join(pp.clone()))?; |
800172fb | 211 | let isbin = content.contains("fn main"); |
455f800c AC |
212 | SourceFileInformation { |
213 | relative_path: pp, | |
214 | target_name: project_name.to_string(), | |
215 | bin: isbin | |
800172fb VS |
216 | } |
217 | } | |
218 | }; | |
219 | detected_files.push(sfi); | |
220 | } | |
455f800c | 221 | |
800172fb | 222 | // Check for duplicate lib attempt |
455f800c | 223 | |
800172fb VS |
224 | let mut previous_lib_relpath : Option<&str> = None; |
225 | let mut duplicates_checker : BTreeMap<&str, &SourceFileInformation> = BTreeMap::new(); | |
455f800c | 226 | |
800172fb VS |
227 | for i in detected_files { |
228 | if i.bin { | |
229 | if let Some(x) = BTreeMap::get::<str>(&duplicates_checker, i.target_name.as_ref()) { | |
230 | bail!("\ | |
231 | multiple possible binary sources found: | |
232 | {} | |
233 | {} | |
234 | cannot automatically generate Cargo.toml as the main target would be ambiguous", | |
235 | &x.relative_path, &i.relative_path); | |
236 | } | |
237 | duplicates_checker.insert(i.target_name.as_ref(), i); | |
238 | } else { | |
239 | if let Some(plp) = previous_lib_relpath { | |
c7de4859 | 240 | return Err(format!("cannot have a project with \ |
241 | multiple libraries, \ | |
242 | found both `{}` and `{}`", | |
243 | plp, i.relative_path).into()); | |
800172fb VS |
244 | } |
245 | previous_lib_relpath = Some(&i.relative_path); | |
246 | } | |
9a387087 | 247 | } |
455f800c | 248 | |
800172fb VS |
249 | Ok(()) |
250 | } | |
251 | ||
252 | fn plan_new_source_file(bin: bool, project_name: String) -> SourceFileInformation { | |
253 | if bin { | |
455f800c | 254 | SourceFileInformation { |
800172fb VS |
255 | relative_path: "src/main.rs".to_string(), |
256 | target_name: project_name, | |
257 | bin: true, | |
258 | } | |
259 | } else { | |
260 | SourceFileInformation { | |
261 | relative_path: "src/lib.rs".to_string(), | |
262 | target_name: project_name, | |
263 | bin: false, | |
264 | } | |
265 | } | |
266 | } | |
267 | ||
23591fe5 | 268 | pub fn new(opts: &NewOptions, config: &Config) -> CargoResult<()> { |
800172fb VS |
269 | let path = config.cwd().join(opts.path); |
270 | if fs::metadata(&path).is_ok() { | |
484a33af BE |
271 | bail!("destination `{}` already exists\n\n\ |
272 | Use `cargo init` to initialize the directory\ | |
273 | ", path.display() | |
274 | ) | |
800172fb | 275 | } |
455f800c | 276 | |
a882abe7 | 277 | if opts.lib && opts.bin { |
484a33af | 278 | bail!("can't specify both lib and binary outputs") |
a882abe7 JT |
279 | } |
280 | ||
23591fe5 | 281 | let name = get_name(&path, opts, config)?; |
6633e290 | 282 | check_name(name, opts.bin)?; |
800172fb VS |
283 | |
284 | let mkopts = MkOptions { | |
285 | version_control: opts.version_control, | |
286 | path: &path, | |
287 | name: name, | |
7e66058a | 288 | source_files: vec![plan_new_source_file(opts.bin, name.to_string())], |
0e4fa582 | 289 | bin: opts.bin, |
800172fb | 290 | }; |
455f800c | 291 | |
e95044e3 | 292 | mk(config, &mkopts).chain_err(|| { |
c7de4859 | 293 | format!("Failed to create project `{}` at `{}`", |
294 | name, path.display()) | |
800172fb VS |
295 | }) |
296 | } | |
297 | ||
23591fe5 | 298 | pub fn init(opts: &NewOptions, config: &Config) -> CargoResult<()> { |
800172fb | 299 | let path = config.cwd().join(opts.path); |
455f800c | 300 | |
800172fb VS |
301 | let cargotoml_path = path.join("Cargo.toml"); |
302 | if fs::metadata(&cargotoml_path).is_ok() { | |
303 | bail!("`cargo init` cannot be run on existing Cargo projects") | |
304 | } | |
455f800c | 305 | |
a882abe7 JT |
306 | if opts.lib && opts.bin { |
307 | bail!("can't specify both lib and binary outputs"); | |
308 | } | |
309 | ||
23591fe5 | 310 | let name = get_name(&path, opts, config)?; |
6633e290 | 311 | check_name(name, opts.bin)?; |
455f800c | 312 | |
800172fb | 313 | let mut src_paths_types = vec![]; |
455f800c | 314 | |
82655b46 | 315 | detect_source_paths_and_types(&path, name, &mut src_paths_types)?; |
455f800c | 316 | |
23591fe5 | 317 | if src_paths_types.is_empty() { |
800172fb VS |
318 | src_paths_types.push(plan_new_source_file(opts.bin, name.to_string())); |
319 | } else { | |
320 | // --bin option may be ignored if lib.rs or src/lib.rs present | |
321 | // Maybe when doing `cargo init --bin` inside a library project stub, | |
322 | // user may mean "initialize for library, but also add binary target" | |
323 | } | |
455f800c | 324 | |
800172fb | 325 | let mut version_control = opts.version_control; |
455f800c | 326 | |
800172fb VS |
327 | if version_control == None { |
328 | let mut num_detected_vsces = 0; | |
455f800c | 329 | |
800172fb VS |
330 | if fs::metadata(&path.join(".git")).is_ok() { |
331 | version_control = Some(VersionControl::Git); | |
332 | num_detected_vsces += 1; | |
333 | } | |
455f800c | 334 | |
800172fb VS |
335 | if fs::metadata(&path.join(".hg")).is_ok() { |
336 | version_control = Some(VersionControl::Hg); | |
337 | num_detected_vsces += 1; | |
338 | } | |
455f800c | 339 | |
2b31978c PW |
340 | if fs::metadata(&path.join(".pijul")).is_ok() { |
341 | version_control = Some(VersionControl::Pijul); | |
342 | num_detected_vsces += 1; | |
343 | } | |
344 | ||
7eca7f27 DK |
345 | if fs::metadata(&path.join(".fossil")).is_ok() { |
346 | version_control = Some(VersionControl::Fossil); | |
347 | num_detected_vsces += 1; | |
348 | } | |
349 | ||
800172fb | 350 | // if none exists, maybe create git, like in `cargo new` |
455f800c | 351 | |
800172fb | 352 | if num_detected_vsces > 1 { |
7eca7f27 DK |
353 | bail!("more than one of .hg, .git, .pijul, .fossil configurations \ |
354 | found and the ignore file can't be filled in as \ | |
355 | a result. specify --vcs to override detection"); | |
800172fb VS |
356 | } |
357 | } | |
455f800c | 358 | |
800172fb VS |
359 | let mkopts = MkOptions { |
360 | version_control: version_control, | |
361 | path: &path, | |
362 | name: name, | |
0e4fa582 | 363 | bin: src_paths_types.iter().any(|x|x.bin), |
7e66058a | 364 | source_files: src_paths_types, |
800172fb | 365 | }; |
455f800c | 366 | |
e95044e3 | 367 | mk(config, &mkopts).chain_err(|| { |
c7de4859 | 368 | format!("Failed to create project `{}` at `{}`", |
369 | name, path.display()) | |
a4272ef2 AC |
370 | }) |
371 | } | |
372 | ||
3f298bca CW |
373 | fn strip_rust_affixes(name: &str) -> &str { |
374 | for &prefix in &["rust-", "rust_", "rs-", "rs_"] { | |
375 | if name.starts_with(prefix) { | |
376 | return &name[prefix.len()..]; | |
377 | } | |
378 | } | |
379 | for &suffix in &["-rust", "_rust", "-rs", "_rs"] { | |
380 | if name.ends_with(suffix) { | |
381 | return &name[..name.len()-suffix.len()]; | |
382 | } | |
383 | } | |
384 | name | |
385 | } | |
386 | ||
09d62a65 ML |
387 | fn existing_vcs_repo(path: &Path, cwd: &Path) -> bool { |
388 | GitRepo::discover(path, cwd).is_ok() || HgRepo::discover(path, cwd).is_ok() | |
6c200854 PW |
389 | } |
390 | ||
800172fb VS |
391 | fn mk(config: &Config, opts: &MkOptions) -> CargoResult<()> { |
392 | let path = opts.path; | |
393 | let name = opts.name; | |
82655b46 | 394 | let cfg = global_config(config)?; |
bef1f477 | 395 | // Please ensure that ignore and hgignore are in sync. |
06267a77 | 396 | let ignore = ["/target/\n", "**/*.rs.bk\n", |
78dce2cc SM |
397 | if !opts.bin { "Cargo.lock\n" } else { "" }] |
398 | .concat(); | |
bef1f477 SA |
399 | // Mercurial glob ignores can't be rooted, so just sticking a 'syntax: glob' at the top of the |
400 | // file will exclude too much. Instead, use regexp-based ignores. See 'hg help ignore' for | |
401 | // more. | |
402 | let hgignore = ["^target/\n", "glob:*.rs.bk\n", | |
403 | if !opts.bin { "glob:Cargo.lock\n" } else { "" }] | |
404 | .concat(); | |
021f70af | 405 | |
50879f33 | 406 | let in_existing_vcs_repo = existing_vcs_repo(path.parent().unwrap_or(path), config.cwd()); |
16d3e72a WW |
407 | let vcs = match (opts.version_control, cfg.version_control, in_existing_vcs_repo) { |
408 | (None, None, false) => VersionControl::Git, | |
23591fe5 | 409 | (None, Some(option), false) | (Some(option), _, _) => option, |
16d3e72a WW |
410 | (_, _, true) => VersionControl::NoVcs, |
411 | }; | |
16d3e72a WW |
412 | match vcs { |
413 | VersionControl::Git => { | |
800172fb | 414 | if !fs::metadata(&path.join(".git")).is_ok() { |
82655b46 | 415 | GitRepo::init(path, config.cwd())?; |
800172fb | 416 | } |
82655b46 | 417 | paths::append(&path.join(".gitignore"), ignore.as_bytes())?; |
16d3e72a WW |
418 | }, |
419 | VersionControl::Hg => { | |
800172fb | 420 | if !fs::metadata(&path.join(".hg")).is_ok() { |
82655b46 | 421 | HgRepo::init(path, config.cwd())?; |
800172fb | 422 | } |
bef1f477 | 423 | paths::append(&path.join(".hgignore"), hgignore.as_bytes())?; |
16d3e72a | 424 | }, |
2b31978c PW |
425 | VersionControl::Pijul => { |
426 | if !fs::metadata(&path.join(".pijul")).is_ok() { | |
427 | PijulRepo::init(path, config.cwd())?; | |
428 | } | |
429 | }, | |
7eca7f27 DK |
430 | VersionControl::Fossil => { |
431 | if !fs::metadata(&path.join(".fossil")).is_ok() { | |
432 | FossilRepo::init(path, config.cwd())?; | |
433 | } | |
434 | }, | |
16d3e72a | 435 | VersionControl::NoVcs => { |
82655b46 | 436 | fs::create_dir_all(path)?; |
16d3e72a WW |
437 | }, |
438 | }; | |
a4272ef2 | 439 | |
82655b46 | 440 | let (author_name, email) = discover_author()?; |
18293f4a | 441 | // Hoo boy, sure glad we've got exhaustiveness checking behind us. |
7e66058a | 442 | let author = match (cfg.name, cfg.email, author_name, email) { |
de0f6041 AC |
443 | (Some(name), Some(email), _, _) | |
444 | (Some(name), None, _, Some(email)) | | |
445 | (None, Some(email), name, _) | | |
446 | (None, None, name, Some(email)) => format!("{} <{}>", name, email), | |
447 | (Some(name), None, _, None) | | |
448 | (None, None, name, None) => name, | |
449 | }; | |
455f800c | 450 | |
7e66058a EH |
451 | let mut cargotoml_path_specifier = String::new(); |
452 | ||
18293f4a | 453 | // Calculate what [lib] and [[bin]]s do we need to append to Cargo.toml |
7e66058a EH |
454 | |
455 | for i in &opts.source_files { | |
456 | if i.bin { | |
457 | if i.relative_path != "src/main.rs" { | |
458 | cargotoml_path_specifier.push_str(&format!(r#" | |
459 | [[bin]] | |
460 | name = "{}" | |
461 | path = {} | |
462 | "#, i.target_name, toml::Value::String(i.relative_path.clone()))); | |
463 | } | |
23591fe5 LL |
464 | } else if i.relative_path != "src/lib.rs" { |
465 | cargotoml_path_specifier.push_str(&format!(r#" | |
7e66058a EH |
466 | [lib] |
467 | name = "{}" | |
468 | path = {} | |
469 | "#, i.target_name, toml::Value::String(i.relative_path.clone()))); | |
7e66058a EH |
470 | } |
471 | } | |
472 | ||
473 | // Create Cargo.toml file with necessary [lib] and [[bin]] sections, if needed | |
474 | ||
475 | paths::write(&path.join("Cargo.toml"), format!( | |
476 | r#"[package] | |
477 | name = "{}" | |
478 | version = "0.1.0" | |
479 | authors = [{}] | |
480 | ||
481 | [dependencies] | |
482 | {}"#, name, toml::Value::String(author), cargotoml_path_specifier).as_bytes())?; | |
483 | ||
484 | ||
485 | // Create all specified source files | |
486 | // (with respective parent directories) | |
487 | // if they are don't exist | |
488 | ||
489 | for i in &opts.source_files { | |
490 | let path_of_source_file = path.join(i.relative_path.clone()); | |
491 | ||
492 | if let Some(src_dir) = path_of_source_file.parent() { | |
493 | fs::create_dir_all(src_dir)?; | |
800172fb | 494 | } |
455f800c | 495 | |
7e66058a EH |
496 | let default_file_content : &[u8] = if i.bin { |
497 | b"\ | |
498 | fn main() { | |
499 | println!(\"Hello, world!\"); | |
500 | } | |
501 | " | |
502 | } else { | |
503 | b"\ | |
504 | #[cfg(test)] | |
505 | mod tests { | |
506 | #[test] | |
7016687b GB |
507 | fn it_works() { |
508 | assert_eq!(2 + 2, 4); | |
509 | } | |
7e66058a EH |
510 | } |
511 | " | |
512 | }; | |
513 | ||
514 | if !fs::metadata(&path_of_source_file).map(|x| x.is_file()).unwrap_or(false) { | |
515 | paths::write(&path_of_source_file, default_file_content)?; | |
516 | } | |
a4272ef2 AC |
517 | } |
518 | ||
58ddb28a AC |
519 | if let Err(e) = Workspace::new(&path.join("Cargo.toml"), config) { |
520 | let msg = format!("compiling this new crate may not work due to invalid \ | |
521 | workspace configuration\n\n{}", e); | |
82655b46 | 522 | config.shell().warn(msg)?; |
58ddb28a | 523 | } |
875a8aba | 524 | |
7e66058a | 525 | Ok(()) |
875a8aba EH |
526 | } |
527 | ||
91e40657 SBI |
528 | fn get_environment_variable(variables: &[&str] ) -> Option<String>{ |
529 | variables.iter() | |
530 | .filter_map(|var| env::var(var).ok()) | |
531 | .next() | |
532 | } | |
533 | ||
de0f6041 | 534 | fn discover_author() -> CargoResult<(String, Option<String>)> { |
4ebeb1f7 HP |
535 | let cwd = env::current_dir()?; |
536 | let git_config = if let Ok(repo) = GitRepository::discover(&cwd) { | |
fe5a875e | 537 | repo.config().ok().or_else(|| GitConfig::open_default().ok()) |
4ebeb1f7 HP |
538 | } else { |
539 | GitConfig::open_default().ok() | |
540 | }; | |
c64fd71e | 541 | let git_config = git_config.as_ref(); |
c29be695 SBI |
542 | let name_variables = ["CARGO_NAME", "GIT_AUTHOR_NAME", "GIT_COMMITTER_NAME", |
543 | "USER", "USERNAME", "NAME"]; | |
544 | let name = get_environment_variable(&name_variables[0..3]) | |
545 | .or_else(|| git_config.and_then(|g| g.get_string("user.name").ok())) | |
546 | .or_else(|| get_environment_variable(&name_variables[3..])); | |
91e40657 | 547 | |
c64fd71e AC |
548 | let name = match name { |
549 | Some(name) => name, | |
333af136 AC |
550 | None => { |
551 | let username_var = if cfg!(windows) {"USERNAME"} else {"USER"}; | |
7ab18e3a AC |
552 | bail!("could not determine the current user, please set ${}", |
553 | username_var) | |
333af136 | 554 | } |
a4272ef2 | 555 | }; |
c29be695 SBI |
556 | let email_variables = ["CARGO_EMAIL", "GIT_AUTHOR_EMAIL", "GIT_COMMITTER_EMAIL", |
557 | "EMAIL"]; | |
558 | let email = get_environment_variable(&email_variables[0..3]) | |
559 | .or_else(|| git_config.and_then(|g| g.get_string("user.email").ok())) | |
560 | .or_else(|| get_environment_variable(&email_variables[3..])); | |
a4272ef2 | 561 | |
25e537aa AC |
562 | let name = name.trim().to_string(); |
563 | let email = email.map(|s| s.trim().to_string()); | |
a4272ef2 | 564 | |
de0f6041 AC |
565 | Ok((name, email)) |
566 | } | |
567 | ||
5d0cb3f2 | 568 | fn global_config(config: &Config) -> CargoResult<CargoNewConfig> { |
82655b46 SG |
569 | let name = config.get_string("cargo-new.name")?.map(|s| s.val); |
570 | let email = config.get_string("cargo-new.email")?.map(|s| s.val); | |
571 | let vcs = config.get_string("cargo-new.vcs")?; | |
a468236a | 572 | |
455f800c | 573 | let vcs = match vcs.as_ref().map(|p| (&p.val[..], &p.definition)) { |
a468236a AC |
574 | Some(("git", _)) => Some(VersionControl::Git), |
575 | Some(("hg", _)) => Some(VersionControl::Hg), | |
576 | Some(("none", _)) => Some(VersionControl::NoVcs), | |
577 | Some((s, p)) => { | |
578 | return Err(internal(format!("invalid configuration for key \ | |
579 | `cargo-new.vcs`, unknown vcs `{}` \ | |
455f800c | 580 | (found in {})", s, p))) |
de0f6041 | 581 | } |
a468236a | 582 | None => None |
de0f6041 | 583 | }; |
a468236a AC |
584 | Ok(CargoNewConfig { |
585 | name: name, | |
586 | email: email, | |
587 | version_control: vcs, | |
588 | }) | |
a4272ef2 | 589 | } |
3f298bca CW |
590 | |
591 | #[cfg(test)] | |
592 | mod tests { | |
593 | use super::strip_rust_affixes; | |
594 | ||
595 | #[test] | |
596 | fn affixes_stripped() { | |
597 | assert_eq!(strip_rust_affixes("rust-foo"), "foo"); | |
598 | assert_eq!(strip_rust_affixes("foo-rs"), "foo"); | |
599 | assert_eq!(strip_rust_affixes("rs_foo"), "foo"); | |
600 | // Only one affix is stripped | |
601 | assert_eq!(strip_rust_affixes("rs-foo-rs"), "foo-rs"); | |
602 | assert_eq!(strip_rust_affixes("foo-rs-rs"), "foo-rs"); | |
603 | // It shouldn't touch the middle | |
604 | assert_eq!(strip_rust_affixes("some-rust-crate"), "some-rust-crate"); | |
605 | } | |
606 | } |