Skip to content

Commit 199a920

Browse files
committed
Implemented plugin-specific archive detection
1 parent ab09772 commit 199a920

File tree

26 files changed

+802
-351
lines changed

26 files changed

+802
-351
lines changed

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bindings/tmc-langs-node/jest/tmc.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ test("compresses project", async () => {
138138

139139
const dir = await mockExercise();
140140
expect(fs.existsSync([dir, "output.zip"].join("/"))).toBeFalsy();
141-
tmc.compressProject(dir, [dir, "output.zip"].join("/"));
141+
tmc.compressProject(dir, [dir, "output.zip"].join("/"), "zip");
142142
expect(fs.existsSync([dir, "output.zip"].join("/"))).toBeTruthy();
143143
});
144144

bindings/tmc-langs-node/ts/functions.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import types from "./generated";
1+
import types, { Compression } from "./generated";
22

33
export class Token {
44
access_token: string;
@@ -14,7 +14,7 @@ export function checkstyle(
1414
locale: string
1515
): types.StyleValidationResult | null;
1616
export function clean(exercisePath: string): void;
17-
export function compressProject(exercisePath: string, outputPath: string): void;
17+
export function compressProject(exercisePath: string, outputPath: string, compression: Compression): void;
1818
export function extractProject(archivePath: string, outputPath: string): void;
1919
export function fastAvailablePoints(exercisePath: string): Array<string>;
2020
export function findExercises(exercisePath: string): Array<string>;

bindings/tmc-langs-node/ts/tmc.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import tmc, { Token } from "./functions";
2-
import types from "./generated";
2+
import types, { Compression } from "./generated";
33

44
export { types, Token };
55

@@ -38,8 +38,8 @@ export class Tmc {
3838
return tmc.clean(exercisePath);
3939
}
4040

41-
compressProject(exercisePath: string, outputPath: string): void {
42-
return tmc.compressProject(exercisePath, outputPath);
41+
compressProject(exercisePath: string, outputPath: string, compression: Compression): void {
42+
return tmc.compressProject(exercisePath, outputPath, compression);
4343
}
4444

4545
extractProject(archivePath: string, outputPath: string): void {

plugins/csharp/src/plugin.rs

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -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
};
1213
use 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};
1819
use walkdir::WalkDir;
1920
use 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).
133136
impl 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

plugins/java/src/ant_plugin.rs

Lines changed: 79 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,17 @@ use j4rs::Jvm;
88
use std::{
99
env,
1010
ffi::OsStr,
11+
io::{Read, Seek},
12+
ops::ControlFlow::{Break, Continue},
1113
path::{Path, PathBuf},
1214
time::Duration,
1315
};
1416
use tmc_langs_framework::{
1517
nom::{error::VerboseError, IResult},
16-
ExerciseDesc, Language, LanguagePlugin, RunResult, StyleValidationResult, TmcCommand, TmcError,
18+
Archive, ExerciseDesc, Language, LanguagePlugin, RunResult, StyleValidationResult, TmcCommand,
19+
TmcError,
1720
};
18-
use tmc_langs_util::file_util;
21+
use tmc_langs_util::{file_util, path_util};
1922
use walkdir::WalkDir;
2023

2124
pub struct AntPlugin {
@@ -62,6 +65,10 @@ impl AntPlugin {
6265
}
6366
}
6467

68+
/// Project directory:
69+
/// Contains build.xml file.
70+
/// OR
71+
/// Contains src and test directories.
6572
impl LanguagePlugin for AntPlugin {
6673
const PLUGIN_NAME: &'static str = "apache-ant";
6774
const LINE_COMMENT: &'static str = "//";
@@ -94,6 +101,57 @@ impl LanguagePlugin for AntPlugin {
94101
Ok(self.run_java_tests(project_root_path, timeout)?)
95102
}
96103

104+
fn find_project_dir_in_archive<R: Read + Seek>(
105+
archive: &mut Archive<R>,
106+
) -> Result<PathBuf, TmcError> {
107+
let mut iter = archive.iter()?;
108+
let mut src_parents = vec![];
109+
let mut test_parents = vec![];
110+
let project_dir = loop {
111+
let next = iter.with_next(|file| {
112+
let file_path = file.path()?;
113+
114+
if file.is_file() {
115+
// check for build.xml
116+
if let Some(parent) = path_util::get_parent_of(&file_path, "build.xml") {
117+
return Ok(Break(Some(parent)));
118+
}
119+
} else if file.is_dir() {
120+
// check for src
121+
if let Some(src_parent) = path_util::get_parent_of_dir(&file_path, "src") {
122+
if test_parents.contains(&src_parent) {
123+
// found a test in the same directory before, return
124+
return Ok(Break(Some(src_parent)));
125+
} else {
126+
src_parents.push(src_parent)
127+
}
128+
}
129+
130+
// check for test
131+
if let Some(test_parent) = path_util::get_parent_of_dir(&file_path, "test") {
132+
if src_parents.contains(&test_parent) {
133+
// found a test in the same directory before, return
134+
return Ok(Break(Some(test_parent)));
135+
} else {
136+
test_parents.push(test_parent)
137+
}
138+
}
139+
}
140+
141+
Ok(Continue(()))
142+
});
143+
match next? {
144+
Continue(_) => continue,
145+
Break(project_dir) => break project_dir,
146+
}
147+
};
148+
149+
match project_dir {
150+
Some(project_dir) => Ok(project_dir),
151+
None => Err(TmcError::NoProjectDirInArchive),
152+
}
153+
}
154+
97155
/// Checks if the directory contains a build.xml file, or a src and a test directory.
98156
fn is_exercise_type_correct(path: &Path) -> bool {
99157
path.join("build.xml").is_file() || path.join("test").is_dir() && path.join("src").is_dir()
@@ -273,9 +331,8 @@ impl JavaPlugin for AntPlugin {
273331
mod test {
274332
use super::*;
275333
use std::fs;
276-
use tmc_langs_framework::StyleValidationStrategy;
334+
use tmc_langs_framework::{Archive, StyleValidationStrategy};
277335
use tmc_langs_util::deserialize;
278-
use zip::ZipArchive;
279336

280337
fn init() {
281338
use log::*;
@@ -601,10 +658,24 @@ mod test {
601658

602659
let temp_dir = tempfile::tempdir().unwrap();
603660
dir_to(&temp_dir, "Outer/Inner/ant-exercise/src");
661+
dir_to(&temp_dir, "Outer/Inner/ant-exercise/test");
662+
663+
let zip_contents = dir_to_zip(&temp_dir);
664+
let mut zip = Archive::zip(std::io::Cursor::new(zip_contents)).unwrap();
665+
let dir = AntPlugin::find_project_dir_in_archive(&mut zip).unwrap();
666+
assert_eq!(dir, Path::new("Outer/Inner/ant-exercise"));
667+
}
668+
669+
#[test]
670+
fn finds_project_dir_in_zip_build() {
671+
init();
672+
673+
let temp_dir = tempfile::tempdir().unwrap();
674+
file_to(&temp_dir, "Outer/Inner/ant-exercise/build.xml", "build!");
604675

605676
let zip_contents = dir_to_zip(&temp_dir);
606-
let mut zip = ZipArchive::new(std::io::Cursor::new(zip_contents)).unwrap();
607-
let dir = AntPlugin::find_project_dir_in_zip(&mut zip).unwrap();
677+
let mut zip = Archive::zip(std::io::Cursor::new(zip_contents)).unwrap();
678+
let dir = AntPlugin::find_project_dir_in_archive(&mut zip).unwrap();
608679
assert_eq!(dir, Path::new("Outer/Inner/ant-exercise"));
609680
}
610681

@@ -616,8 +687,8 @@ mod test {
616687
dir_to(&temp_dir, "Outer/Inner/ant-exercise/srcb");
617688

618689
let zip_contents = dir_to_zip(&temp_dir);
619-
let mut zip = ZipArchive::new(std::io::Cursor::new(zip_contents)).unwrap();
620-
let dir = AntPlugin::find_project_dir_in_zip(&mut zip);
690+
let mut zip = Archive::zip(std::io::Cursor::new(zip_contents)).unwrap();
691+
let dir = AntPlugin::find_project_dir_in_archive(&mut zip);
621692
assert!(dir.is_err());
622693
}
623694
}

plugins/java/src/java_plugin.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,9 +286,11 @@ pub(crate) trait JavaPlugin: LanguagePlugin {
286286
#[cfg(test)]
287287
#[allow(clippy::unwrap_used)]
288288
mod test {
289+
use std::io::{Read, Seek};
290+
289291
use super::*;
290292
use crate::SEPARATOR;
291-
use tmc_langs_framework::{nom, TmcError};
293+
use tmc_langs_framework::{nom, Archive, TmcError};
292294

293295
fn init() {
294296
use log::*;
@@ -348,6 +350,12 @@ mod test {
348350
unimplemented!()
349351
}
350352

353+
fn find_project_dir_in_archive<R: Read + Seek>(
354+
_archive: &mut Archive<R>,
355+
) -> Result<PathBuf, TmcError> {
356+
unimplemented!()
357+
}
358+
351359
fn is_exercise_type_correct(_path: &Path) -> bool {
352360
true
353361
}

0 commit comments

Comments
 (0)