1use std::fs;
7use std::path::PathBuf;
8use typr_core::{OutputError, OutputHandler, PackageChecker, PackageError, SourceProvider};
9
10#[derive(Debug, Clone)]
14pub struct FileSystemSourceProvider {
15 base_path: PathBuf,
16}
17
18impl FileSystemSourceProvider {
19 pub fn new(base_path: PathBuf) -> Self {
21 Self { base_path }
22 }
23
24 pub fn current_dir() -> std::io::Result<Self> {
26 Ok(Self {
27 base_path: std::env::current_dir()?,
28 })
29 }
30
31 fn resolve_path(&self, path: &str) -> PathBuf {
33 if PathBuf::from(path).is_absolute() {
34 PathBuf::from(path)
35 } else {
36 self.base_path.join(path)
37 }
38 }
39}
40
41impl SourceProvider for FileSystemSourceProvider {
42 fn get_source(&self, path: &str) -> Option<String> {
43 let full_path = self.resolve_path(path);
44 fs::read_to_string(&full_path).ok()
45 }
46
47 fn exists(&self, path: &str) -> bool {
48 let full_path = self.resolve_path(path);
49 full_path.exists() && full_path.is_file()
50 }
51
52 fn list_sources(&self) -> Vec<String> {
53 self.list_sources_recursive(&self.base_path)
54 }
55}
56
57impl FileSystemSourceProvider {
58 fn list_sources_recursive(&self, dir: &PathBuf) -> Vec<String> {
60 let mut sources = Vec::new();
61
62 if let Ok(entries) = fs::read_dir(dir) {
63 for entry in entries.flatten() {
64 let path = entry.path();
65 if path.is_dir() {
66 sources.extend(self.list_sources_recursive(&path));
67 } else if path.extension().map_or(false, |ext| ext == "ty") {
68 if let Ok(relative) = path.strip_prefix(&self.base_path) {
69 sources.push(relative.to_string_lossy().to_string());
70 }
71 }
72 }
73 }
74
75 sources
76 }
77}
78
79#[derive(Debug, Clone)]
83pub struct FileSystemOutputHandler {
84 output_dir: PathBuf,
85}
86
87impl FileSystemOutputHandler {
88 pub fn new(output_dir: PathBuf) -> Self {
90 Self { output_dir }
91 }
92
93 pub fn current_dir() -> std::io::Result<Self> {
95 Ok(Self {
96 output_dir: std::env::current_dir()?,
97 })
98 }
99
100 fn ensure_dir(&self) -> Result<(), OutputError> {
102 fs::create_dir_all(&self.output_dir).map_err(|e| OutputError {
103 message: format!("Failed to create output directory: {}", e),
104 })
105 }
106
107 fn write_file(&self, filename: &str, content: &str) -> Result<(), OutputError> {
109 self.ensure_dir()?;
110 let path = self.output_dir.join(filename);
111 fs::write(&path, content).map_err(|e| OutputError {
112 message: format!("Failed to write {}: {}", path.display(), e),
113 })
114 }
115}
116
117impl OutputHandler for FileSystemOutputHandler {
118 fn write_r_code(&mut self, filename: &str, content: &str) -> Result<(), OutputError> {
119 self.write_file(filename, content)
120 }
121
122 fn write_type_annotations(&mut self, filename: &str, content: &str) -> Result<(), OutputError> {
123 self.write_file(
124 &format!("{}_types.R", filename.trim_end_matches(".R")),
125 content,
126 )
127 }
128
129 fn write_generic_functions(
130 &mut self,
131 filename: &str,
132 content: &str,
133 ) -> Result<(), OutputError> {
134 self.write_file(
135 &format!("{}_generics.R", filename.trim_end_matches(".R")),
136 content,
137 )
138 }
139}
140
141#[derive(Debug, Clone)]
145pub struct NativePackageChecker {
146 package_types: std::collections::HashMap<String, String>,
148}
149
150impl NativePackageChecker {
151 pub fn new() -> Self {
152 Self {
153 package_types: std::collections::HashMap::new(),
154 }
155 }
156}
157
158impl Default for NativePackageChecker {
159 fn default() -> Self {
160 Self::new()
161 }
162}
163
164impl PackageChecker for NativePackageChecker {
165 fn is_package_available(&self, name: &str) -> bool {
166 use std::process::Command;
167
168 let result = Command::new("Rscript")
170 .arg("-e")
171 .arg(format!("cat(requireNamespace('{}', quietly = TRUE))", name))
172 .output();
173
174 match result {
175 Ok(output) => {
176 let stdout = String::from_utf8_lossy(&output.stdout);
177 stdout.trim() == "TRUE"
178 }
179 Err(_) => false,
180 }
181 }
182
183 fn install_package(&mut self, name: &str) -> Result<(), PackageError> {
184 use std::process::Command;
185
186 let result = Command::new("Rscript")
187 .arg("-e")
188 .arg(format!(
189 "install.packages('{}', repos='https://cloud.r-project.org')",
190 name
191 ))
192 .output();
193
194 match result {
195 Ok(output) => {
196 if output.status.success() {
197 Ok(())
198 } else {
199 let stderr = String::from_utf8_lossy(&output.stderr);
200 Err(PackageError {
201 message: format!("Failed to install package '{}': {}", name, stderr),
202 })
203 }
204 }
205 Err(e) => Err(PackageError {
206 message: format!("Failed to run Rscript: {}", e),
207 }),
208 }
209 }
210
211 fn get_package_types(&self, name: &str) -> Option<String> {
212 self.package_types.get(name).cloned()
213 }
214}