cargo_wsinit/
workspace.rs1use std::fmt::Debug;
2use std::fs::{self, File, OpenOptions};
3use std::io::prelude::*;
4use std::io::{Error as IoError, ErrorKind, SeekFrom};
5use std::ops::Deref;
6use std::path::{Path, PathBuf};
7
8use crate::options::FileExistsBehaviour;
9use crate::options::Options;
10use crate::toml_editor;
11use crate::toml_file::TomlFile;
12
13#[derive(Debug)]
14pub enum Error {
15 FileAlreadyExists,
16 GenericCreationError(IoError),
17 ReadError(IoError),
18 ParseError,
19 WriteError(IoError),
20}
21
22pub struct Workspace {
23 toml: TomlFile,
24 options: Options,
25}
26
27impl Workspace {
28 pub fn new(options: Options) -> Workspace {
29 let path = &options.path;
30 let toml = TomlFile::new(path.join(Path::new("Cargo.toml")));
31
32 Workspace { toml, options }
33 }
34
35 pub fn toml(&self) -> &TomlFile {
36 &self.toml
37 }
38
39 pub fn update_toml(&self) -> Result<TomlFile, Error> {
40 self.create_path()
41 .map_err(|err| Error::GenericCreationError(err))?;
42
43 let mut sub_crates = self
44 .find_sub_crates()
45 .map_err(|err| Error::GenericCreationError(err))?;
46
47 sub_crates.sort();
48
49 let mut file = self.open_file()?;
50
51 self.write_toml_to_file(&mut file, &sub_crates)
52 .map(|_| self.toml.clone())
53 }
54
55 fn path(&self) -> &PathBuf {
56 &self.options.path
57 }
58
59 fn create_path(&self) -> Result<(), IoError> {
60 fs::create_dir_all(self.path())
61 }
62
63 fn find_sub_crates(&self) -> Result<Vec<String>, IoError> {
64 let root = &self.options.path;
65 let sub_toml_files = Workspace::search_for_cargo_files(root, 0)?;
66
67 Ok(sub_toml_files
68 .iter()
69 .map(|p| {
70 p.parent()
71 .unwrap()
72 .strip_prefix(root)
73 .unwrap()
74 .to_str()
75 .unwrap()
76 .to_string()
77 })
78 .collect())
79 }
80
81 fn search_for_cargo_files(dir: &PathBuf, depth: i32) -> Result<Vec<PathBuf>, IoError> {
82 let mut results: Vec<PathBuf> = vec![];
83
84 if depth > 0 {
86 for path in fs::read_dir(dir)? {
87 let path = path.unwrap().path();
88
89 if path.is_file() {
90 match path.file_name() {
91 Some(p) if p == "Cargo.toml" => {
92 results.push(path);
93 break;
94 }
95 _ => (),
96 }
97 }
98 }
99 }
100
101 if results.is_empty() {
103 for path in fs::read_dir(dir)? {
104 let path = path.unwrap().path();
105
106 if path.is_dir() {
107 match path.file_name() {
108 Some(p) if p != "target" => {
109 results.extend(Workspace::search_for_cargo_files(&path, depth + 1)?);
110 }
111 _ => (),
112 }
113 }
114 }
115 }
116
117 Ok(results)
118 }
119
120 fn open_file(&self) -> Result<File, Error> {
121 OpenOptions::new()
122 .write(true)
123 .read(self.options.existing_file_behaviour == FileExistsBehaviour::Update)
124 .create_new(self.options.existing_file_behaviour.create_new())
125 .create(self.options.existing_file_behaviour != FileExistsBehaviour::Update)
126 .open(self.toml.deref())
127 .map_err(|err| match err.kind() {
128 ErrorKind::AlreadyExists => Error::FileAlreadyExists,
129 _ => Error::GenericCreationError(err),
130 })
131 }
132
133 fn write_toml_to_file(&self, file: &mut File, sub_crates: &[String]) -> Result<(), Error> {
134 let toml_content = match self.options.existing_file_behaviour {
135 FileExistsBehaviour::Update => {
136 Workspace::read_toml(file).map_err(|err| Error::ReadError(err))?
137 }
138 _ => "".to_string(),
139 };
140
141 let new_file_content = toml_editor::toml_update(toml_content.as_str(), &sub_crates[..])
142 .map_err(|_| Error::ParseError)?;
143
144 Workspace::write_toml(file, new_file_content).map_err(|err| Error::WriteError(err))
145 }
146
147 fn write_toml(file: &mut File, toml: String) -> Result<(), IoError> {
148 file.set_len(0)?; file.seek(SeekFrom::Start(0))?;
150 file.write_all(toml.as_bytes())
151 }
152
153 fn read_toml(file: &mut File) -> Result<String, IoError> {
154 let mut toml = String::new();
155 file.read_to_string(&mut toml)?;
156 Ok(toml)
157 }
158}