1use crate::{config::Config, Cache, Location, Remote, Tag};
2use anyhow::{anyhow, Context, Result};
3use serde::{Deserialize, Serialize};
4use std::{
5 collections::BTreeSet,
6 path::{Path, PathBuf},
7};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct Repository {
11 pub name: String,
12 pub path: Option<PathBuf>,
13 pub work: Option<String>,
14 pub clone: Option<String>,
15 pub use_cli: Option<bool>,
16
17 pub tags: BTreeSet<String>,
18 pub remotes: Vec<Remote>,
19
20 #[serde(skip)]
21 pub config: PathBuf,
22
23 #[serde(skip)]
24 pub location: Location,
25}
26
27pub struct RepositoryBuilder {
28 name: String,
29 remotes: Vec<Remote>,
30 tags: BTreeSet<String>,
31 location: Location,
32 path: Option<PathBuf>,
33 work: Option<String>,
34 clone: Option<String>,
35 use_cli: Option<bool>,
36}
37
38impl Repository {
39 pub fn resolve_workspace_path(&self, cache: &Cache) -> PathBuf {
40 self.path
41 .as_ref()
42 .map(|s| s.join(&self.name))
43 .or_else(|| {
44 self.resolve_from_tags(cache, |tag| tag.path.clone())
45 .pop()
46 .map(|p| p.join(&self.name))
47 })
48 .unwrap_or_else(|| PathBuf::from(&self.name))
49 }
50
51 pub fn path_from_location(location: Location) -> PathBuf {
52 match location {
53 Location::Global => Config::global_path().join("repository"),
54 Location::Local => Config::local_path().join("repository"),
55 }
56 }
57
58 pub fn resolve_from_tags<F, T>(&self, cache: &Cache, resolver: F) -> Vec<T>
59 where
60 F: Fn(&Tag) -> Option<T>,
61 {
62 let tags = cache.tags();
63 let mut priority: Vec<(T, i32)> = tags
64 .iter()
65 .filter(|t| self.tags.contains(&t.name))
66 .flat_map(|t| resolver(t).map(|value| (value, t.priority.unwrap_or(50))))
67 .collect();
68
69 priority.sort_by_key(|v| v.1);
70 priority.into_iter().map(|v| v.0).collect()
71 }
72
73 pub fn set_location(&mut self, location: Location) {
74 if self.location == location {
75 return;
76 }
77
78 self.location = location;
79 self.config = Repository::path_from_location(location).join(format!("{}.toml", self.name));
80 }
81
82 pub fn del_cache_file(&self) -> Result<()> {
83 std::fs::remove_file(&self.config)
84 .context(format!(
85 "failed to remove repository config file: {}",
86 &self.config.display()
87 ))
88 .map_err(Into::into)
89 }
90}
91
92impl RepositoryBuilder {
93 pub fn new(name: &str) -> Self {
94 Self {
95 name: name.to_owned(),
96 remotes: Vec::new(),
97 tags: BTreeSet::new(),
98 location: Location::default(),
99 use_cli: None,
100 path: None,
101 work: None,
102 clone: None,
103 }
104 }
105
106 pub fn remote(mut self, remote: Remote) -> Self {
107 self.remotes.push(remote);
108 self
109 }
110
111 pub fn tag(mut self, tag: String) -> Self {
112 self.tags.insert(tag);
113 self
114 }
115
116 pub fn location(mut self, location: Location) -> Self {
117 self.location = location;
118 self
119 }
120
121 pub fn path<P: AsRef<Path>>(mut self, path: P) -> Self {
122 self.path = Some(path.as_ref().to_path_buf());
123 self
124 }
125
126 pub fn cli(mut self, use_cli: bool) -> Self {
127 self.use_cli = Some(use_cli);
128 self
129 }
130
131 pub fn clone(mut self, command: String) -> Self {
132 self.clone = Some(command);
133 self
134 }
135
136 pub fn work(mut self, command: String) -> Self {
137 self.work = Some(command);
138 self
139 }
140
141 pub fn build(self) -> Repository {
142 let config =
143 Repository::path_from_location(self.location).join(format!("{}.toml", self.name));
144
145 Repository {
146 name: self.name,
147 remotes: self.remotes,
148 tags: self.tags,
149 path: self.path,
150 clone: self.clone,
151 work: self.work,
152 use_cli: self.use_cli,
153 location: self.location,
154 config,
155 }
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162 use crate::Query;
163 use url::Url;
164
165 #[test]
166 fn build() -> Result<()> {
167 let url: Url = "https://github.com/edeneast/repo".parse()?;
168 let remote = Remote::new(url);
169 let repo = RepositoryBuilder::new("repo")
170 .remote(remote.clone())
171 .build();
172
173 assert_eq!(repo.name, "repo");
174 assert_eq!(repo.remotes.len(), 1);
175 Ok(())
177 }
178}