cargo_wsinit/
workspace.rs

1use 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        // do not look in the workspace root
85        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        // do not look in sub directories after found a Cargo.toml
102        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)?; // in lieu of OpenOptions::truncate which would prevent reading
149        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}