@@ -6,15 +6,16 @@ use std::{
66 env,
77 ffi:: { OsStr , OsString } ,
88 io:: { BufReader , Cursor , Read , Seek } ,
9+ ops:: ControlFlow :: { Break , Continue } ,
910 path:: { Path , PathBuf } ,
1011 time:: Duration ,
1112} ;
1213use tmc_langs_framework:: {
1314 nom:: { bytes, character, combinator, error:: VerboseError , sequence, IResult } ,
14- CommandError , ExerciseDesc , Language , LanguagePlugin , RunResult , RunStatus ,
15+ Archive , CommandError , ExerciseDesc , Language , LanguagePlugin , RunResult , RunStatus ,
1516 StyleValidationResult , StyleValidationStrategy , TestDesc , TestResult , TmcCommand , TmcError ,
1617} ;
17- use tmc_langs_util:: { deserialize, file_util, parse_util, FileError } ;
18+ use tmc_langs_util:: { deserialize, file_util, parse_util, path_util , FileError } ;
1819use walkdir:: WalkDir ;
1920use zip:: ZipArchive ;
2021
@@ -130,13 +131,14 @@ impl CSharpPlugin {
130131 }
131132}
132133
134+ /// Project directory:
135+ /// Contains a src directory which contains a .csproj file (which may be inside a subdirectory).
133136impl LanguagePlugin for CSharpPlugin {
134137 const PLUGIN_NAME : & ' static str = "csharp" ;
135138 const LINE_COMMENT : & ' static str = "//" ;
136139 const BLOCK_COMMENT : Option < ( & ' static str , & ' static str ) > = Some ( ( "/*" , "*/" ) ) ;
137140 type StudentFilePolicy = CSharpStudentFilePolicy ;
138141
139- /// Checks the directories in src for csproj files, up to 2 subdirectories deep.
140142 fn is_exercise_type_correct ( path : & Path ) -> bool {
141143 WalkDir :: new ( path. join ( "src" ) )
142144 . max_depth ( 2 )
@@ -145,32 +147,40 @@ impl LanguagePlugin for CSharpPlugin {
145147 . any ( |e| e. path ( ) . extension ( ) == Some ( & OsString :: from ( "csproj" ) ) )
146148 }
147149
148- /// Finds any directory X which contains a X/src/*/*.csproj file.
149- /// Ignores everything in a __MACOSX directory.
150- fn find_project_dir_in_zip < R : Read + Seek > (
151- zip_archive : & mut ZipArchive < R > ,
150+ fn find_project_dir_in_archive < R : Read + Seek > (
151+ archive : & mut Archive < R > ,
152152 ) -> Result < PathBuf , TmcError > {
153- for i in 0 ..zip_archive. len ( ) {
154- let file = zip_archive. by_index ( i) ?;
155- let file_path = Path :: new ( file. name ( ) ) ;
156-
157- if file_path. extension ( ) == Some ( OsStr :: new ( "csproj" ) ) {
158- // check parent of parent of the csproj file for src
159- if let Some ( csproj_parent) = file_path. parent ( ) . and_then ( Path :: parent) {
160- if csproj_parent. file_name ( ) == Some ( OsStr :: new ( "src" ) ) {
161- // get parent of src
162- if let Some ( src_parent) = csproj_parent. parent ( ) {
163- // skip if any part of the path is __MACOSX
164- if file_path. components ( ) . any ( |p| p. as_os_str ( ) == "__MACOSX" ) {
165- continue ;
153+ let mut iter = archive. iter ( ) ?;
154+ let project_dir = loop {
155+ let next = iter. with_next ( |entry| {
156+ let file_path = entry. path ( ) ?;
157+
158+ if entry. is_file ( )
159+ && file_path. extension ( ) == Some ( OsStr :: new ( "csproj" ) )
160+ && !file_path. components ( ) . any ( |c| c. as_os_str ( ) == "__MACOSX" )
161+ {
162+ if let Some ( parent) = file_path. parent ( ) {
163+ if let Some ( src_parent) = path_util:: get_parent_of ( parent, "src" ) {
164+ return Ok ( Break ( Some ( src_parent) ) ) ;
165+ }
166+ if let Some ( parent) = parent. parent ( ) {
167+ if let Some ( src_parent) = path_util:: get_parent_of ( parent, "src" ) {
168+ return Ok ( Break ( Some ( src_parent) ) ) ;
166169 }
167- return Ok ( src_parent. to_path_buf ( ) ) ;
168170 }
169171 }
170172 }
173+ Ok ( Continue ( ( ) ) )
174+ } ) ;
175+ match next? {
176+ Continue ( _) => continue ,
177+ Break ( project_dir) => break project_dir,
171178 }
179+ } ;
180+ match project_dir {
181+ Some ( project_dir) => Ok ( project_dir) ,
182+ None => Err ( TmcError :: NoProjectDirInArchive ) ,
172183 }
173- Err ( TmcError :: NoProjectDirInZip )
174184 }
175185
176186 /// Runs --generate-points-file and parses the generated .tmc_available_points.json.
@@ -524,8 +534,8 @@ mod test {
524534 let temp = TempDir :: new ( ) . unwrap ( ) ;
525535 file_to ( & temp, "dir1/dir2/dir3/src/dir4/sample.csproj" , "" ) ;
526536 let bytes = dir_to_zip ( & temp) ;
527- let mut zip = ZipArchive :: new ( std:: io:: Cursor :: new ( bytes) ) . unwrap ( ) ;
528- let dir = CSharpPlugin :: find_project_dir_in_zip ( & mut zip) . unwrap ( ) ;
537+ let mut zip = Archive :: zip ( std:: io:: Cursor :: new ( bytes) ) . unwrap ( ) ;
538+ let dir = CSharpPlugin :: find_project_dir_in_archive ( & mut zip) . unwrap ( ) ;
529539 assert_eq ! ( dir, Path :: new( "dir1/dir2/dir3" ) )
530540 }
531541
@@ -534,12 +544,12 @@ mod test {
534544 init ( ) ;
535545
536546 let temp = TempDir :: new ( ) . unwrap ( ) ;
537- file_to ( & temp, "dir1/dir2/dir3/src/directly in src.csproj" , "" ) ;
547+ file_to ( & temp, "dir1/dir2/dir3/not src/directly in src.csproj" , "" ) ;
538548 file_to ( & temp, "dir1/dir2/dir3/src/__MACOSX/under macosx.csproj" , "" ) ;
539549 file_to ( & temp, "dir1/__MACOSX/dir3/src/dir/under macosx.csproj" , "" ) ;
540550 let bytes = dir_to_zip ( & temp) ;
541- let mut zip = ZipArchive :: new ( std:: io:: Cursor :: new ( bytes) ) . unwrap ( ) ;
542- let dir = CSharpPlugin :: find_project_dir_in_zip ( & mut zip) ;
551+ let mut zip = Archive :: zip ( std:: io:: Cursor :: new ( bytes) ) . unwrap ( ) ;
552+ let dir = CSharpPlugin :: find_project_dir_in_archive ( & mut zip) ;
543553 assert ! ( dir. is_err( ) )
544554 }
545555
0 commit comments