@@ -5,8 +5,10 @@ use regex::Regex;
55use std:: collections:: HashMap ;
66use std:: ffi:: OsStr ;
77use std:: fs;
8+ use std:: fs:: File ;
9+ use std:: io:: prelude:: * ;
810use std:: path:: { Path , PathBuf } ;
9- use std:: sync:: LazyLock ;
11+ use std:: sync:: { Arc , LazyLock , Mutex } ;
1012use unindent:: unindent;
1113
1214static ENCODING_RE : LazyLock < Regex > =
@@ -22,17 +24,19 @@ pub trait FileSystem: Send + Sync {
2224 fn exists ( & self , file_name : & str ) -> bool ;
2325
2426 fn read ( & self , file_name : & str ) -> PyResult < String > ;
27+
28+ fn write ( & mut self , file_name : & str , contents : & str ) -> PyResult < ( ) > ;
2529}
2630
2731#[ derive( Clone ) ]
2832#[ pyclass]
29- pub struct RealBasicFileSystem { }
33+ struct RealBasicFileSystem { }
3034
3135// Implements a BasicFileSystem (defined in grimp.application.ports.filesystem.BasicFileSystem)
3236// that actually reads files.
3337#[ pyclass( name = "RealBasicFileSystem" ) ]
3438pub struct PyRealBasicFileSystem {
35- pub inner : RealBasicFileSystem ,
39+ inner : RealBasicFileSystem ,
3640}
3741
3842impl FileSystem for RealBasicFileSystem {
@@ -129,6 +133,16 @@ impl FileSystem for RealBasicFileSystem {
129133 } )
130134 }
131135 }
136+
137+ fn write ( & mut self , file_name : & str , contents : & str ) -> PyResult < ( ) > {
138+ let file_path: PathBuf = file_name. into ( ) ;
139+ if let Some ( patent_dir) = file_path. parent ( ) {
140+ fs:: create_dir_all ( patent_dir) ?;
141+ }
142+ File :: create ( file_path) ?
143+ . write_all ( contents. as_bytes ( ) )
144+ . map_err ( Into :: into)
145+ }
132146}
133147
134148#[ pymethods]
@@ -161,19 +175,23 @@ impl PyRealBasicFileSystem {
161175 fn read ( & self , file_name : & str ) -> PyResult < String > {
162176 self . inner . read ( file_name)
163177 }
178+
179+ fn write ( & mut self , file_name : & str , contents : & str ) -> PyResult < ( ) > {
180+ self . inner . write ( file_name, contents)
181+ }
164182}
165183
166184type FileSystemContents = HashMap < String , String > ;
167185
168186#[ derive( Clone ) ]
169- pub struct FakeBasicFileSystem {
170- contents : Box < FileSystemContents > ,
187+ struct FakeBasicFileSystem {
188+ contents : Arc < Mutex < FileSystemContents > > ,
171189}
172190
173191// Implements BasicFileSystem (defined in grimp.application.ports.filesystem.BasicFileSystem).
174192#[ pyclass( name = "FakeBasicFileSystem" ) ]
175193pub struct PyFakeBasicFileSystem {
176- pub inner : FakeBasicFileSystem ,
194+ inner : FakeBasicFileSystem ,
177195}
178196
179197impl FakeBasicFileSystem {
@@ -190,7 +208,7 @@ impl FakeBasicFileSystem {
190208 parsed_contents. extend ( unindented_map) ;
191209 } ;
192210 Ok ( FakeBasicFileSystem {
193- contents : Box :: new ( parsed_contents) ,
211+ contents : Arc :: new ( Mutex :: new ( parsed_contents) ) ,
194212 } )
195213 }
196214}
@@ -232,17 +250,25 @@ impl FileSystem for FakeBasicFileSystem {
232250
233251 /// Checks if a file or directory exists within the file system.
234252 fn exists ( & self , file_name : & str ) -> bool {
235- self . contents . contains_key ( file_name)
253+ self . contents . lock ( ) . unwrap ( ) . contains_key ( file_name)
236254 }
237255
238256 fn read ( & self , file_name : & str ) -> PyResult < String > {
239- match self . contents . get ( file_name) {
240- Some ( file_name) => Ok ( file_name. clone ( ) ) ,
257+ let contents = self . contents . lock ( ) . unwrap ( ) ;
258+ match contents. get ( file_name) {
259+ Some ( file_contents) => Ok ( file_contents. clone ( ) ) ,
241260 None => Err ( PyFileNotFoundError :: new_err ( format ! (
242261 "No such file: {file_name}"
243262 ) ) ) ,
244263 }
245264 }
265+
266+ #[ allow( unused_variables) ]
267+ fn write ( & mut self , file_name : & str , contents : & str ) -> PyResult < ( ) > {
268+ let mut contents_mut = self . contents . lock ( ) . unwrap ( ) ;
269+ contents_mut. insert ( file_name. to_string ( ) , contents. to_string ( ) ) ;
270+ Ok ( ( ) )
271+ }
246272}
247273
248274#[ pymethods]
@@ -278,6 +304,10 @@ impl PyFakeBasicFileSystem {
278304 self . inner . read ( file_name)
279305 }
280306
307+ fn write ( & mut self , file_name : & str , contents : & str ) -> PyResult < ( ) > {
308+ self . inner . write ( file_name, contents)
309+ }
310+
281311 // Temporary workaround method for Python tests.
282312 fn convert_to_basic ( & self ) -> PyResult < Self > {
283313 Ok ( PyFakeBasicFileSystem {
@@ -289,7 +319,7 @@ impl PyFakeBasicFileSystem {
289319/// Parses an indented string representing a file system structure
290320/// into a HashMap where keys are full file paths.
291321/// See tests.adaptors.filesystem.FakeFileSystem for the API.
292- pub fn parse_indented_file_system_string ( file_system_string : & str ) -> HashMap < String , String > {
322+ fn parse_indented_file_system_string ( file_system_string : & str ) -> HashMap < String , String > {
293323 let mut file_paths_map: HashMap < String , String > = HashMap :: new ( ) ;
294324 let mut path_stack: Vec < String > = Vec :: new ( ) ; // Stores current directory path components
295325 let mut first_line = true ; // Flag to handle the very first path component
@@ -381,7 +411,6 @@ pub fn get_file_system_boxed<'py>(
381411 file_system : & Bound < ' py , PyAny > ,
382412) -> PyResult < Box < dyn FileSystem + Send + Sync > > {
383413 let file_system_boxed: Box < dyn FileSystem + Send + Sync > ;
384-
385414 if let Ok ( py_real) = file_system. extract :: < PyRef < PyRealBasicFileSystem > > ( ) {
386415 file_system_boxed = Box :: new ( py_real. inner . clone ( ) ) ;
387416 } else if let Ok ( py_fake) = file_system. extract :: < PyRef < PyFakeBasicFileSystem > > ( ) {
@@ -391,5 +420,6 @@ pub fn get_file_system_boxed<'py>(
391420 "file_system must be an instance of RealBasicFileSystem or FakeBasicFileSystem" ,
392421 ) ) ;
393422 }
423+
394424 Ok ( file_system_boxed)
395425}
0 commit comments