Skip to content

Commit 95d305a

Browse files
committed
Work
1 parent 70a4345 commit 95d305a

File tree

16 files changed

+621
-203
lines changed

16 files changed

+621
-203
lines changed

.cargo/config.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
#[build]
2+
#target = "x86_64-pc-windows-gnu"
3+
14
[target.armv7-unknown-linux-gnueabihf]
25
linker = "arm-linux-gnueabihf-gcc"
36

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.

crates/bindings/tmc-langs-node/src/bin/generate-node-bindings.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ fn main() {
1515
// getExercisePackagingConfiguration
1616
tmc_langs::ExercisePackagingConfiguration,
1717
// listLocalCourseExercises
18-
tmc_langs::LocalExercise,
18+
tmc_langs::LocalTmcExercise,
1919
// prepareSubmission
2020
tmc_langs::Compression,
2121
// refreshCourse

crates/bindings/tmc-langs-node/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ fn get_exercise_packaging_configuration(mut cx: FunctionContext) -> JsResult<JsV
168168
fn list_local_course_exercises(mut cx: FunctionContext) -> JsResult<JsValue> {
169169
parse_args!(cx, client_name: String, course_slug: String);
170170

171-
let res = tmc_langs::list_local_course_exercises(&client_name, &course_slug);
171+
let res = tmc_langs::list_local_tmc_course_exercises(&client_name, &course_slug);
172172
convert_res(&mut cx, res)
173173
}
174174

crates/bindings/tmc-langs-node/ts/generated.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ student_file_paths: Array<string>,
1414
*/
1515
exercise_file_paths: Array<string>, }
1616

17-
export type LocalExercise = { "exercise-slug": string, "exercise-path": string, }
17+
export type LocalTmcExercise = { "exercise-slug": string, "exercise-path": string, }
1818

1919
export type Compression = "tar" | "zip" | "zstd";
2020

crates/tmc-langs-cli/bindings.d.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ export type Locale = string;
22

33
export type CliOutput = { "output-kind": "output-data" } & OutputData | { "output-kind": "status-update" } & StatusUpdateData | { "output-kind": "notification" } & Notification;
44

5-
export type DataKind = { "output-data-kind": "error", "output-data": { kind: Kind, trace: Array<string>, } } | { "output-data-kind": "validation", "output-data": StyleValidationResult | null } | { "output-data-kind": "available-points", "output-data": Array<string> } | { "output-data-kind": "exercises", "output-data": Array<string> } | { "output-data-kind": "exercise-packaging-configuration", "output-data": ExercisePackagingConfiguration } | { "output-data-kind": "local-exercises", "output-data": Array<LocalExercise> } | { "output-data-kind": "refresh-result", "output-data": RefreshData } | { "output-data-kind": "test-result", "output-data": RunResult } | { "output-data-kind": "exercise-desc", "output-data": ExerciseDesc } | { "output-data-kind": "updated-exercises", "output-data": Array<UpdatedExercise> } | { "output-data-kind": "exercise-download", "output-data": DownloadOrUpdateCourseExercisesResult } | { "output-data-kind": "combined-course-data", "output-data": CombinedCourseData } | { "output-data-kind": "course-details", "output-data": CourseDetails } | { "output-data-kind": "course-exercises", "output-data": Array<CourseExercise> } | { "output-data-kind": "course-data", "output-data": CourseData } | { "output-data-kind": "courses", "output-data": Array<Course> } | { "output-data-kind": "exercise-details", "output-data": ExerciseDetails } | { "output-data-kind": "submissions", "output-data": Array<Submission> } | { "output-data-kind": "update-result", "output-data": UpdateResult } | { "output-data-kind": "organization", "output-data": Organization } | { "output-data-kind": "organizations", "output-data": Array<Organization> } | { "output-data-kind": "reviews", "output-data": Array<Review> } | { "output-data-kind": "token", "output-data": unknown } | { "output-data-kind": "new-submission", "output-data": NewSubmission } | { "output-data-kind": "submission-feedback-response", "output-data": SubmissionFeedbackResponse } | { "output-data-kind": "submission-finished", "output-data": SubmissionFinished } | { "output-data-kind": "config-value", "output-data": ConfigValue } | { "output-data-kind": "tmc-config", "output-data": TmcConfig } | { "output-data-kind": "compressed-project-hash", "output-data": string } | { "output-data-kind": "submission-sandbox", "output-data": string } | { "output-data-kind": "mooc-course-instances", "output-data": Array<CourseInstance> } | { "output-data-kind": "mooc-exercise-slides", "output-data": Array<TmcExerciseSlide> } | { "output-data-kind": "mooc-exercise-slide", "output-data": TmcExerciseSlide } | { "output-data-kind": "mooc-submission-finished", "output-data": ExerciseTaskSubmissionResult };
5+
export type DataKind = { "output-data-kind": "error", "output-data": { kind: Kind, trace: Array<string>, } } | { "output-data-kind": "validation", "output-data": StyleValidationResult | null } | { "output-data-kind": "available-points", "output-data": Array<string> } | { "output-data-kind": "exercises", "output-data": Array<string> } | { "output-data-kind": "exercise-packaging-configuration", "output-data": ExercisePackagingConfiguration } | { "output-data-kind": "local-tmc-exercises", "output-data": Array<LocalTmcExercise> } | { "output-data-kind": "local-mooc-exercises", "output-data": Array<LocalMoocExercise> } | { "output-data-kind": "refresh-result", "output-data": RefreshData } | { "output-data-kind": "test-result", "output-data": RunResult } | { "output-data-kind": "exercise-desc", "output-data": ExerciseDesc } | { "output-data-kind": "updated-exercises", "output-data": Array<UpdatedExercise> } | { "output-data-kind": "exercise-download", "output-data": DownloadOrUpdateCourseExercisesResult } | { "output-data-kind": "combined-course-data", "output-data": CombinedCourseData } | { "output-data-kind": "course-details", "output-data": CourseDetails } | { "output-data-kind": "course-exercises", "output-data": Array<CourseExercise> } | { "output-data-kind": "course-data", "output-data": CourseData } | { "output-data-kind": "courses", "output-data": Array<Course> } | { "output-data-kind": "exercise-details", "output-data": ExerciseDetails } | { "output-data-kind": "submissions", "output-data": Array<Submission> } | { "output-data-kind": "update-result", "output-data": UpdateResult } | { "output-data-kind": "organization", "output-data": Organization } | { "output-data-kind": "organizations", "output-data": Array<Organization> } | { "output-data-kind": "reviews", "output-data": Array<Review> } | { "output-data-kind": "token", "output-data": unknown } | { "output-data-kind": "new-submission", "output-data": NewSubmission } | { "output-data-kind": "submission-feedback-response", "output-data": SubmissionFeedbackResponse } | { "output-data-kind": "submission-finished", "output-data": SubmissionFinished } | { "output-data-kind": "config-value", "output-data": ConfigValue } | { "output-data-kind": "tmc-config", "output-data": TmcConfig } | { "output-data-kind": "compressed-project-hash", "output-data": string } | { "output-data-kind": "submission-sandbox", "output-data": string } | { "output-data-kind": "mooc-course-instances", "output-data": Array<CourseInstance> } | { "output-data-kind": "mooc-exercise-slides", "output-data": Array<TmcExerciseSlide> } | { "output-data-kind": "mooc-exercise-slide", "output-data": TmcExerciseSlide } | { "output-data-kind": "mooc-submission-finished", "output-data": ExerciseTaskSubmissionResult };
66

77
export type Kind = "generic" | "forbidden" | "not-logged-in" | "connection-error" | "obsolete-client" | "invalid-token" | { "failed-exercise-download": { completed: Array<ExerciseDownload>, skipped: Array<ExerciseDownload>, failed: Array<[ExerciseDownload, Array<string>]>, } };
88

@@ -38,7 +38,11 @@ student_file_paths: Array<string>,
3838
*/
3939
exercise_file_paths: Array<string>, }
4040

41-
export type LocalExercise = { "exercise-slug": string, "exercise-path": string, }
41+
export type LocalExercise = { "Tmc": LocalTmcExercise } | { "Mooc": LocalMoocExercise };
42+
43+
export type LocalTmcExercise = { "exercise-slug": string, "exercise-path": string, }
44+
45+
export type LocalMoocExercise = { "exercise-id": string, "exercise-path": string, }
4246

4347
export type Compression = "tar" | "zip" | "zstd";
4448

@@ -290,9 +294,9 @@ export type TmcExerciseSlide = { slide_id: string, exercise_id: string, exercise
290294

291295
export type TmcExerciseTask = { task_id: string, order_number: number, assignment: unknown, public_spec: PublicSpec | null, model_solution_spec: ModelSolutionSpec | null, }
292296

293-
export type PublicSpec = { "type": "browser", files: Array<ExerciseFile>, } | { "type": "editor", archiveName: string, archiveDownloadUrl: string, checksum: string, };
297+
export type PublicSpec = { "type": "Browser", files: Array<ExerciseFile>, } | { "type": "Editor", archive_name: string, archive_download_url: string, checksum: string, };
294298

295-
export type ModelSolutionSpec = { "type": "browser", solutionFiles: Array<ExerciseFile>, } | { "type": "editor", archiveDownloadUrl: string, };
299+
export type ModelSolutionSpec = { "type": "Browser", solution_files: Array<ExerciseFile>, } | { "type": "Editor", download_url: string, };
296300

297301
export type ExerciseFile = { filepath: string, contents: string, }
298302

crates/tmc-langs-cli/src/app.rs

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,10 @@ pub enum Command {
8080
naive: bool,
8181
},
8282

83+
/// Commands that communicate with the TMC server.
8384
Tmc(TestMyCode),
8485

86+
/// Commands that communicate with the Mooc server.
8587
Mooc(Mooc),
8688

8789
/// Extracts an exercise from an archive. If the output-path is a project root, the plugin's student file policy will be used to avoid overwriting student files
@@ -133,7 +135,7 @@ pub enum Command {
133135

134136
/// Returns a list of local exercises for the given course
135137
#[clap(long_about = schema_leaked::<Vec<LocalExercise>>())]
136-
ListLocalCourseExercises {
138+
ListLocalTmcCourseExercises {
137139
/// The client name of which the exercises should be listed.
138140
#[clap(long)]
139141
client_name: String,
@@ -542,23 +544,32 @@ pub struct Mooc {
542544

543545
#[derive(Parser)]
544546
pub enum MoocCommand {
547+
/// Fetches information about a course instance.
548+
CourseInstance {
549+
#[clap(long)]
550+
course_instance_id: Uuid,
551+
},
545552
/// Fetches the user's enrolled courses.
546553
#[clap(long_about = schema_leaked::<Vec<CourseInstance>>())]
547554
CourseInstances,
555+
/// Fetches the available exercises for a course instance.
548556
CourseInstanceExercises {
549557
#[clap(long)]
550558
course_instance_id: Uuid,
551559
},
560+
/// Fetches information about an exercise.
552561
Exercise {
553562
#[clap(long)]
554563
exercise_id: Uuid,
555564
},
565+
/// Downloads an exercise.
556566
DownloadExercise {
557567
#[clap(long)]
558568
exercise_id: Uuid,
559569
#[clap(long)]
560570
target: PathBuf,
561571
},
572+
/// Submits an exercise.
562573
Submit {
563574
#[clap(long)]
564575
exercise_id: Uuid,
@@ -644,7 +655,27 @@ impl FromStr for Locale {
644655
.or_else(|| Language::from_639_1(s))
645656
.or_else(|| Language::from_639_3(s))
646657
.with_context(|| format!("Invalid locale: {s}"))?;
647-
Ok(Locale(locale))
658+
Ok(Self(locale))
659+
}
660+
}
661+
662+
#[derive(Clone, Copy)]
663+
#[cfg_attr(feature = "ts-rs", derive(ts_rs::TS))]
664+
pub enum CourseType {
665+
Tmc,
666+
Mooc,
667+
}
668+
669+
impl FromStr for CourseType {
670+
type Err = anyhow::Error;
671+
672+
fn from_str(s: &str) -> Result<Self, Self::Err> {
673+
let ct = match s.to_lowercase().as_str() {
674+
"tmc" => Self::Tmc,
675+
"mooc" => Self::Mooc,
676+
other => return Err(anyhow::anyhow!("Invalid course type {other}")),
677+
};
678+
Ok(ct)
648679
}
649680
}
650681

@@ -762,7 +793,7 @@ mod base_test {
762793
#[test]
763794
fn list_local_course_exercises() {
764795
get_matches(&[
765-
"list-local-course-exercises",
796+
"list-local-tmc-course-exercises",
766797
"--client-name",
767798
"client",
768799
"--course-slug",
@@ -1196,6 +1227,8 @@ mod test {
11961227
tmc_langs::ExercisePackagingConfiguration,
11971228
// listLocalCourseExercises
11981229
tmc_langs::LocalExercise,
1230+
tmc_langs::LocalTmcExercise,
1231+
tmc_langs::LocalMoocExercise,
11991232
// prepareSubmission
12001233
tmc_langs::Compression,
12011234
// refreshCourse

crates/tmc-langs-cli/src/lib.rs

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,11 @@ use std::{
2626
use tmc_langs::{
2727
CommandError, Compression, Credentials, DownloadOrUpdateCourseExercisesResult, DownloadResult,
2828
Language, StyleValidationResult, TmcConfig, UpdatedExercise,
29-
mooc::MoocClient,
30-
tmc::{TestMyCodeClient, TestMyCodeClientError, request::FeedbackAnswer},
31-
};
32-
use tmc_langs_util::{
33-
deserialize,
3429
file_util::{self, Lock, LockOptions},
30+
mooc::{MoocClient, MoocClientError},
31+
tmc::{TestMyCodeClient, TestMyCodeClientError, request::FeedbackAnswer},
3532
};
33+
use tmc_langs_util::deserialize;
3634

3735
pub enum ParsingResult {
3836
Ok(Cli),
@@ -104,7 +102,7 @@ fn solve_error_kind(e: &anyhow::Error) -> Kind {
104102
return Kind::InvalidToken;
105103
}
106104

107-
// check for client errors
105+
// check for tmc client errors
108106
match cause.downcast_ref::<TestMyCodeClientError>() {
109107
Some(TestMyCodeClientError::HttpError {
110108
url: _,
@@ -131,6 +129,33 @@ fn solve_error_kind(e: &anyhow::Error) -> Kind {
131129
_ => {}
132130
}
133131

132+
// check for tmc client errors
133+
match cause.downcast_ref::<MoocClientError>() {
134+
Some(MoocClientError::HttpError {
135+
url: _,
136+
status,
137+
error: _,
138+
obsolete_client,
139+
}) => {
140+
if *obsolete_client {
141+
return Kind::ObsoleteClient;
142+
}
143+
if status.as_u16() == 403 {
144+
return Kind::Forbidden;
145+
}
146+
if status.as_u16() == 401 {
147+
return Kind::NotLoggedIn;
148+
}
149+
}
150+
Some(MoocClientError::NotAuthenticated) => {
151+
return Kind::NotLoggedIn;
152+
}
153+
Some(MoocClientError::ConnectionError { .. }) => {
154+
return Kind::ConnectionError;
155+
}
156+
_ => {}
157+
}
158+
134159
// check for download failed error
135160
if let Some(DownloadsFailedError {
136161
downloaded: completed,
@@ -308,16 +333,16 @@ fn run_app(cli: Cli) -> Result<CliOutput> {
308333
)
309334
}
310335

311-
Command::ListLocalCourseExercises {
336+
Command::ListLocalTmcCourseExercises {
312337
client_name,
313338
course_slug,
314339
} => {
315340
let local_exercises =
316-
tmc_langs::list_local_course_exercises(&client_name, &course_slug)?;
341+
tmc_langs::list_local_tmc_course_exercises(&client_name, &course_slug)?;
317342

318343
CliOutput::finished_with_data(
319344
format!("listed local exercises for {course_slug}"),
320-
DataKind::LocalExercises(local_exercises),
345+
DataKind::LocalTmcExercises(local_exercises),
321346
)
322347
}
323348

@@ -1008,7 +1033,9 @@ fn run_tmc_inner(
10081033

10091034
fn run_mooc(mooc: Mooc) -> Result<CliOutput> {
10101035
let root_url = env::var("TMC_LANGS_MOOC_ROOT_URL")
1011-
.unwrap_or_else(|_| "https://courses.mooc.fi".to_string());
1036+
.unwrap_or_else(|_| "https://courses.mooc.fi/".to_string())
1037+
.parse()
1038+
.context("Invalid TMC root url")?;
10121039

10131040
let (mut client, credentials) =
10141041
tmc_langs::init_mooc_client_with_credentials(root_url, &mooc.client_name)?;
@@ -1038,6 +1065,11 @@ fn run_mooc(mooc: Mooc) -> Result<CliOutput> {
10381065

10391066
fn run_mooc_inner(mooc: Mooc, client: &mut MoocClient) -> Result<CliOutput> {
10401067
let output = match mooc.command {
1068+
MoocCommand::CourseInstance {
1069+
course_instance_id: _,
1070+
} => {
1071+
todo!()
1072+
}
10411073
MoocCommand::CourseInstances => {
10421074
let course_instances = client.course_instances()?;
10431075
CliOutput::finished_with_data(
@@ -1046,8 +1078,7 @@ fn run_mooc_inner(mooc: Mooc, client: &mut MoocClient) -> Result<CliOutput> {
10461078
)
10471079
}
10481080
MoocCommand::CourseInstanceExercises { course_instance_id } => {
1049-
let course_instance_exercises =
1050-
client.course_instance_exercise_slides(course_instance_id)?;
1081+
let course_instance_exercises = client.course_instance_exercises(course_instance_id)?;
10511082
CliOutput::finished_with_data(
10521083
"fetched course instance exercises",
10531084
DataKind::MoocExerciseSlides(course_instance_exercises),

crates/tmc-langs-cli/src/output.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ use serde::{Deserialize, Serialize};
44
use std::path::PathBuf;
55
use tmc_langs::{
66
CombinedCourseData, ConfigValue, DownloadOrUpdateCourseExercisesResult, ExerciseDesc,
7-
ExerciseDownload, ExercisePackagingConfiguration, LocalExercise, RunResult,
8-
StyleValidationResult, TmcConfig, UpdatedExercise, mooc,
7+
ExerciseDownload, ExercisePackagingConfiguration, LocalExercise, LocalMoocExercise,
8+
LocalTmcExercise, RunResult, StyleValidationResult, TmcConfig, UpdatedExercise, mooc,
99
notification_reporter::Notification,
1010
tmc::{
1111
ClientUpdateData, Token, UpdateResult,
@@ -76,7 +76,8 @@ pub enum DataKind {
7676
AvailablePoints(Vec<String>),
7777
Exercises(Vec<PathBuf>),
7878
ExercisePackagingConfiguration(ExercisePackagingConfiguration),
79-
LocalExercises(Vec<LocalExercise>),
79+
LocalTmcExercises(Vec<LocalTmcExercise>),
80+
LocalMoocExercises(Vec<LocalMoocExercise>),
8081
RefreshResult(tmc_langs::RefreshData),
8182
TestResult(RunResult),
8283
ExerciseDesc(ExerciseDesc),
@@ -161,7 +162,7 @@ pub enum Kind {
161162
},
162163
}
163164

164-
pub use tmc_langs::ProjectsDirExercise;
165+
pub use tmc_langs::ProjectsDirTmcExercise;
165166

166167
#[derive(Debug, Serialize, Deserialize)]
167168
pub struct DownloadTarget {

crates/tmc-langs/src/config.rs

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ mod tmc_config;
66

77
pub use self::{
88
credentials::Credentials,
9-
projects_config::{CourseConfig, ProjectsConfig, ProjectsDirExercise},
9+
projects_config::{ProjectsConfig, ProjectsDirTmcExercise, TmcCourseConfig},
1010
tmc_config::TmcConfig,
1111
};
12-
use crate::{TMC_LANGS_CONFIG_DIR_VAR, data::LocalExercise, error::LangsError};
12+
use crate::{TMC_LANGS_CONFIG_DIR_VAR, data::LocalTmcExercise, error::LangsError};
1313
use std::{
1414
collections::BTreeMap,
1515
env,
@@ -30,25 +30,23 @@ fn get_tmc_dir(client_name: &str) -> Result<PathBuf, LangsError> {
3030
}
3131

3232
/// Returns all of the exercises for the given course.
33-
pub fn list_local_course_exercises(
33+
pub fn list_local_tmc_course_exercises(
3434
client_name: &str,
3535
course_slug: &str,
36-
) -> Result<Vec<LocalExercise>, LangsError> {
37-
log::debug!(
38-
"listing local course exercises of {course_slug} for {client_name}"
39-
);
36+
) -> Result<Vec<LocalTmcExercise>, LangsError> {
37+
log::debug!("listing local course exercises of {course_slug} for {client_name}");
4038

4139
let projects_dir = TmcConfig::load(client_name)?.projects_dir;
4240
let mut projects_config = ProjectsConfig::load(&projects_dir)?;
4341

4442
let exercises = projects_config
45-
.courses
43+
.tmc_courses
4644
.remove(course_slug)
4745
.map(|cc| cc.exercises)
4846
.unwrap_or_default();
49-
let mut local_exercises: Vec<LocalExercise> = vec![];
47+
let mut local_exercises: Vec<LocalTmcExercise> = vec![];
5048
for (exercise_slug, _) in exercises {
51-
local_exercises.push(LocalExercise {
49+
local_exercises.push(LocalTmcExercise {
5250
exercise_path: projects_dir.join(course_slug).join(&exercise_slug),
5351
exercise_slug,
5452
})
@@ -75,9 +73,9 @@ pub fn migrate_exercise(
7573
let _guard = lock.lock()?;
7674
let mut projects_config = ProjectsConfig::load(&tmc_config.projects_dir)?;
7775
let course_config = projects_config
78-
.courses
76+
.tmc_courses
7977
.entry(course_slug.to_string())
80-
.or_insert(CourseConfig {
78+
.or_insert(TmcCourseConfig {
8179
course: course_slug.to_string(),
8280
exercises: BTreeMap::new(),
8381
});
@@ -93,7 +91,7 @@ pub fn migrate_exercise(
9391

9492
course_config.exercises.insert(
9593
exercise_slug.to_string(),
96-
ProjectsDirExercise {
94+
ProjectsDirTmcExercise {
9795
id: exercise_id,
9896
checksum: exercise_checksum.to_string(),
9997
},

0 commit comments

Comments
 (0)