gemfile_downloader/
lib.rs1use std::error::Error;
2use std::path::{Path, PathBuf};
3use std::sync::Arc;
4use futures::future::join_all;
5use serde::{Deserialize, Serialize};
6use tokio::fs::read_to_string;
7use tokio::sync::Mutex;
8use crate::parser::GemfileData;
9
10pub mod parser;
11pub mod download;
12pub mod unpack_gem;
13pub mod unpack_tar_gz;
14pub mod gem_version;
15
16#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
20pub struct InstallInfo {
21 pub install_gems: Vec<String>,
23 pub find_gemfiles: Vec<FindGemFileInfo>,
25}
26
27#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
31pub struct FindGemFileInfo {
32 pub gem_name: String,
34 pub gemfile_path: PathBuf,
36}
37
38pub async fn install_from_gemfile_file(gemfile: &Path, install_dictionary: &Path, cache_directory: &Path) -> Result<InstallInfo, Box<dyn Error>> {
48 let gemfile_context = read_to_string(gemfile).await?;
50
51 install_from_gemfile_literal(&gemfile_context, install_dictionary, cache_directory).await
53}
54
55pub async fn install_from_gemfile_literal(gemfile_context: &str, install_dictionary: &Path, cache_directory: &Path) -> Result<InstallInfo, Box<dyn Error>> {
65 let gemfile_data = parser::GemfileData::parse(gemfile_context).await?;
67
68 install_gems(gemfile_data, install_dictionary, cache_directory).await
69}
70
71pub async fn install_gems(gemfile_data: GemfileData, install_dictionary: &Path, cache_directory: &Path) -> Result<InstallInfo, Box<dyn Error>>{
81
82 let installed_gems: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
84 let gemfiles: Arc<Mutex<Vec<FindGemFileInfo>>> = Arc::new(Mutex::new(Vec::new()));
86
87 let tasks: Vec<_> = gemfile_data.gems.into_iter().map(|gem| {
89 let installed_gems = Arc::clone(&installed_gems);
90 let gemfiles = Arc::clone(&gemfiles);
91 let source = gemfile_data.source.clone();
92
93 async move {
94 let download_result = download::download_gem(cache_directory, &source, &gem).await;
96 let Ok(download_result) = download_result else {
97 return;
98 };
99 let gem_name = download_result.file_stem();
100 let Some(gem_name) = gem_name else {
101 return;
102 };
103
104 let cache_directory = &cache_directory.join(gem_name);
106 let gems_directory = &install_dictionary.join(gem_name);
108
109 let gz_result = unpack_gem::unpack_gem(&download_result, cache_directory);
111 let Ok(gz_result) = gz_result else {
112 return;
113 };
114
115 let tar_gz_result = unpack_tar_gz::unpack_tar_gz(&gz_result, cache_directory, gems_directory);
117 let Ok(tar_gz_result) = tar_gz_result else {
118 return;
119 };
120
121 let gem_name = gem_name.to_string_lossy().to_string();
122 installed_gems.lock().await.push(gem_name.clone());
124
125 if let Some(gemfile) = tar_gz_result {
127 gemfiles.lock().await.push(FindGemFileInfo{
128 gem_name,
129 gemfile_path: gemfile,
130 });
131 }
132 }
133 }).collect();
134 join_all(tasks).await;
135
136 let Ok(installed_gems) = Arc::try_unwrap(installed_gems) else {
138 return Err("installed_gems unwrap error".into());
139 };
140 let Ok(gemfiles) = Arc::try_unwrap(gemfiles) else {
141 return Err("gemfiles unwrap error".into());
142 };
143
144 Ok(InstallInfo{
145 install_gems: installed_gems.into_inner(),
146 find_gemfiles: gemfiles.into_inner(),
147 })
148}
149
150#[cfg(test)]
151mod tests {
152 use std::path::Path;
153 use crate::install_from_gemfile_literal;
154
155 #[tokio::test]
159 pub async fn gems_download_test() {
160 let gems_cache_directory = Path::new("./target/gems_cache");
162 let gems_directory = Path::new("./target/gems");
164
165 let gemfile = "
167source \"https://rubygems.org\"
168
169gemspec
170
171group :development, :test do
172 gem \"docile\", \"~> 1.4.0\"
173 gem \"simplecov-html\", \"~> 0.12.3\"
174 gem \"i18n\", \"~> 1.8.5\"
175 gem \"concurrent-ruby\", \"~> 1.3.4\"
176end";
177 let result = install_from_gemfile_literal(gemfile, gems_directory, gems_cache_directory).await;
178 assert!(result.is_ok());
179
180 result.unwrap().find_gemfiles.iter().for_each(|find_gemfile| {
181 println!("gemfile: {:?}", find_gemfile.gemfile_path);
182 });
183 }
184}