Skip to content

Commit

Permalink
Progress on implementing mooc support
Browse files Browse the repository at this point in the history
  • Loading branch information
Heliozoa committed Jul 5, 2023
1 parent 9a7e25e commit c66fdac
Show file tree
Hide file tree
Showing 21 changed files with 1,143 additions and 533 deletions.
962 changes: 541 additions & 421 deletions Cargo.lock

Large diffs are not rendered by default.

31 changes: 18 additions & 13 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,41 +2,43 @@
name = "tmc"
version = "1.1.2"
authors = [
"University of Helsinki <mooc@cs.helsinki.fi>",
"HoolaBoola <jaime.heikkiladias@helsinki.fi>",
"Robustic <juha.malinen@helsinki.fi>",
"ShootingStar91 <arttu.kangas@helsinki.fi>",
"Nooblue <joni.sikio@helsinki.fi>",
"Daniel Martinez <daniel.x.martinez@helsinki.fi>",
"University of Helsinki <mooc@cs.helsinki.fi>",
"HoolaBoola <jaime.heikkiladias@helsinki.fi>",
"Robustic <juha.malinen@helsinki.fi>",
"ShootingStar91 <arttu.kangas@helsinki.fi>",
"Nooblue <joni.sikio@helsinki.fi>",
"Daniel Martinez <daniel.x.martinez@helsinki.fi>",
]
edition = "2021"
description = "Client for downloading, testing and submitting exercises through the TestMyCode system."
description = "Client for downloading, testing and submitting exercises through the TestMyCode and MOOC.fi systems."
license = "Apache-2.0"
rust-version = "1.66.1"
rust-version = "1.70.0"

[dependencies]
anyhow = { version = "1.0.56", features = ["backtrace"] }
bytes = "1.4.0"
clap = { version = "4.0.7", features = ["derive"] }
clap_complete = "4.0.2"
crossterm = "0.26.0"
flexi_logger = "0.25.3"
indicatif = "0.17.1"
log = "0.4.17"
reqwest = { version = "0.11.9", default-features = false, features = [
"blocking",
"json",
"rustls-tls",
"multipart",
"blocking",
"json",
"rustls-tls",
"multipart",
] }
rpassword = "7.0.0"
serde = "1.0.136"
serde_json = "1.0.79"
termcolor = "1.1.3"
terminal_size = "0.2.1"
tmc-langs = { git = "https://github.com/rage/tmc-langs-rust/", ref = "0.31.2" }
tmc-langs = { git = "https://github.com/rage/tmc-langs-rust/", tag = "0.31.2" }
toml = "0.7.2"
tui = { version = "0.19.0", default-features = false, features = ['crossterm'] }
url = "2.2.2"
uuid = { version = "1.4.0", features = ["v4"] }

[dev-dependencies]
assert_cmd = "2.0.4"
Expand All @@ -45,3 +47,6 @@ predicates = "3.0.3"
[build-dependencies]
clap = { version = "4.0.7", features = ["derive"] }
clap_complete = "4.0.2"

[patch."https://github.com/rage/tmc-langs-rust/"]
tmc-langs = { path = "../tmc-langs-rust/crates/tmc-langs" }
4 changes: 2 additions & 2 deletions rustfmt.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
imports_granularity="Crate"
group_imports="One"
group_imports = "One"
imports_granularity = "Crate"
64 changes: 49 additions & 15 deletions src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use clap::{Parser, Subcommand, ValueEnum};
use std::path::PathBuf;

#[derive(Parser, Debug)]
#[command(
Expand All @@ -10,21 +11,24 @@ use clap::{Parser, Subcommand, ValueEnum};
arg_required_else_help(true)
)]
pub struct Cli {
#[command(subcommand)]
pub subcommand: Command,

/// Disable auto-update temporarily.
#[arg(short = 'd', long, hide = !cfg!(windows))]
pub no_update: bool,
/// Force auto-update to run.
#[arg(short = 'u', long, hide = !cfg!(windows))]
pub force_update: bool,
/// Only for internal testing, disables server connection

/// Only for internal testing, disables server connection.
#[arg(long, hide = true)]
pub testmode: bool,
#[command(subcommand)]
pub subcommand: Command,
}

#[derive(Subcommand, Debug)]
pub enum Command {
// tmc commands
/// List the available courses.
Courses,
/// Download exercises for a course.
Expand All @@ -41,26 +45,56 @@ pub enum Command {
/// If set, the exercises of this course are listed. If not set, the selection is done from an interactive menu.
course: Option<String>,
},
/// Login to TMC server
/// Login to TMC server.
Login {
/// Initiates the non-interactive mode.
#[arg(short, long)]
non_interactive: bool,
},
/// Logout from TMC server
/// Logout from TMC server.
Logout,
/// Change organization
/// Change organization.
Organization {
/// Initiates the non-interactive mode.
#[arg(short, long)]
non_interactive: bool,
},
/// Submit exercise to TMC pastebin
/// Submit exercise to TMC pastebin.
Paste { exercise: Option<String> },
/// Submit exercises to TMC server
/// Submit exercises to TMC server.
Submit { exercise: Option<String> },
/// Run local exercise tests
/// Run local exercise tests.
Test { exercise: Option<String> },
/// Updates course exercises.
Update {
/// If set, exercises in the current working directory are updated.
#[arg(short = 'd', long)]
currentdir: bool,
},

// MOOC commands
/// Currently enrolled courses.mooc.fi courses.
MoocCourses,
/// Active exercises of the selected course.
MoocCourseExercises {
/// If set, the exercises of this course are listed. If not set, the selection is done from an interactive menu.
course: Option<String>,
},
/// Downloads active exercises for the selected course.
MoocDownloadExercises {
/// If set, the exercises of this course are downloaded. If not set, the selection is done from an interactive menu.
course: Option<String>,
/// If set, exercises are downloaded to the current working directory.
#[arg(short = 'd', long)]
currentdir: bool,
},
/// Submits an exercise.
MoocSubmitExercise {
/// If set, the exercise at this path is submitted. If not set, the selection is done from an interactive menu.
path: Option<PathBuf>,
},

// hidden commands
/// Finishes the autoupdater. Administator rights needed.
#[clap(hide = true)]
Fetchupdate,
Expand All @@ -73,12 +107,6 @@ pub enum Command {
/// updates course from the tempfile. Administator rights needed.
#[clap(hide = true)]
Elevatedupdate,
/// Updates course exercises
Update {
/// If set, exercises in the current working directory are updated.
#[arg(short = 'd', long)]
currentdir: bool,
},
/// Generate completion scripts for command line usage.
#[clap(
hide = true,
Expand All @@ -88,6 +116,12 @@ pub enum Command {
GenerateCompletions { shell: ShellArg },
}

impl Command {
pub fn requires_organization_set(&self) -> bool {
matches!(self, Command::Download { .. } | Command::Courses { .. })
}
}

#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum ShellArg {
Bash,
Expand Down
18 changes: 17 additions & 1 deletion src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod exercises;
mod generate_completions;
mod login;
mod logout;
mod mooc;
mod organization;
mod paste;
mod submit;
Expand Down Expand Up @@ -37,11 +38,12 @@ pub fn handle(cli: Cli, io: &mut dyn Io) -> anyhow::Result<()> {
};

// Check that organization is set
if let Command::Download { .. } | Command::Courses { .. } = cli.subcommand {
if cli.subcommand.requires_organization_set() {
util::get_organization().context("No organization found. Run 'tmc organization' first.")?;
}

match cli.subcommand {
// tmc commands
Command::Login { non_interactive } => {
let interactive_mode = !non_interactive;
login::login(io, &mut client, interactive_mode)?;
Expand Down Expand Up @@ -70,6 +72,20 @@ pub fn handle(cli: Cli, io: &mut dyn Io) -> anyhow::Result<()> {
paste::paste(io, &mut client, exercise.as_deref())?;
}
Command::Logout => logout::logout(io, &mut client)?,

// mooc commands
Command::MoocCourses => mooc::courses::run(io, &mut client)?,
Command::MoocCourseExercises { course } => {
mooc::course_exercises::run(io, &mut client, course.as_deref())?
}
Command::MoocDownloadExercises { course, currentdir } => {
mooc::download_exercises::run(io, &mut client, course.as_deref(), currentdir)?
}
Command::MoocSubmitExercise { path } => {
mooc::submit_exercise::run(io, &mut client, path.as_deref())?
}

// hidden commands
Command::Fetchupdate => {
#[cfg(target_os = "windows")]
crate::updater::process_update()?;
Expand Down
25 changes: 18 additions & 7 deletions src/commands/courses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,14 @@ mod tests {
use reqwest::Url;
use std::{path::Path, slice::Iter};
use tmc_langs::{
ClientError, Course, CourseDetails, CourseExercise, DownloadOrUpdateCourseExercisesResult,
DownloadResult, ExercisesDetails, LangsError, Language, NewSubmission, Organization,
SubmissionFinished, SubmissionStatus,
tmc::{
response::{
Course, CourseDetails, CourseExercise, ExercisesDetails, NewSubmission,
Organization, SubmissionFinished, SubmissionStatus,
},
TestMyCodeClientError,
},
DownloadOrUpdateCourseExercisesResult, DownloadResult, LangsError, Language,
};
pub struct IoTest<'a> {
list: &'a mut Vec<String>,
Expand Down Expand Up @@ -129,7 +134,7 @@ mod tests {
fn wait_for_submission(
&self,
_submission_url: Url,
) -> Result<SubmissionFinished, ClientError> {
) -> Result<SubmissionFinished, TestMyCodeClientError> {
Ok(SubmissionFinished {
api_version: 0,
all_tests_passed: Some(true),
Expand Down Expand Up @@ -163,7 +168,7 @@ mod tests {
fn get_exercise_details(
&mut self,
_exercise_ids: Vec<u32>,
) -> Result<Vec<ExercisesDetails>, ClientError> {
) -> Result<Vec<ExercisesDetails>, TestMyCodeClientError> {
unimplemented!()
}

Expand All @@ -178,10 +183,16 @@ mod tests {
})
}

fn get_course_details(&self, _: u32) -> std::result::Result<CourseDetails, ClientError> {
fn get_course_details(
&self,
_: u32,
) -> std::result::Result<CourseDetails, TestMyCodeClientError> {
unimplemented!()
}
fn get_organization(&self, _: &str) -> std::result::Result<Organization, ClientError> {
fn get_organization(
&self,
_: &str,
) -> std::result::Result<Organization, TestMyCodeClientError> {
unimplemented!()
}
}
Expand Down
5 changes: 4 additions & 1 deletion src/commands/download.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ use crate::{
};
use anyhow::Context;
use std::{path::Path, process::Command};
use tmc_langs::{ClientUpdateData, Course, DownloadResult};
use tmc_langs::{
tmc::{response::Course, ClientUpdateData},
DownloadResult,
};

// Downloads course exercises
// course_name as None will trigger interactive menu for selecting a course
Expand Down
21 changes: 13 additions & 8 deletions src/commands/exercises.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::util::{self, choose_course, Client};
use crate::io::{Io, PrintColor};
use tmc_langs::CourseExercise;
use tmc_langs::tmc::response::CourseExercise;

/// Lists exercises for a given course
pub fn list_exercises(
Expand Down Expand Up @@ -97,9 +97,14 @@ mod tests {
use reqwest::Url;
use std::{path::Path, slice::Iter};
use tmc_langs::{
ClientError, Course, CourseDetails, CourseExercise, DownloadOrUpdateCourseExercisesResult,
DownloadResult, ExercisesDetails, LangsError, Language, NewSubmission, Organization,
SubmissionFinished, SubmissionStatus,
tmc::{
response::{
Course, CourseDetails, CourseExercise, ExercisesDetails, NewSubmission,
Organization, SubmissionFinished, SubmissionStatus,
},
TestMyCodeClientError,
},
DownloadOrUpdateCourseExercisesResult, DownloadResult, LangsError, Language,
};

pub struct IoTest<'a> {
Expand Down Expand Up @@ -203,7 +208,7 @@ mod tests {
fn wait_for_submission(
&self,
_submission_url: Url,
) -> Result<SubmissionFinished, ClientError> {
) -> Result<SubmissionFinished, TestMyCodeClientError> {
Ok(SubmissionFinished {
api_version: 0,
all_tests_passed: Some(true),
Expand Down Expand Up @@ -305,7 +310,7 @@ mod tests {
fn get_exercise_details(
&mut self,
_exercise_ids: Vec<u32>,
) -> Result<Vec<ExercisesDetails>, ClientError> {
) -> Result<Vec<ExercisesDetails>, TestMyCodeClientError> {
unimplemented!()
}
fn update_exercises(
Expand All @@ -325,10 +330,10 @@ mod tests {
})
}

fn get_course_details(&self, _: u32) -> Result<CourseDetails, ClientError> {
fn get_course_details(&self, _: u32) -> Result<CourseDetails, TestMyCodeClientError> {
unimplemented!()
}
fn get_organization(&self, _: &str) -> Result<Organization, ClientError> {
fn get_organization(&self, _: &str) -> Result<Organization, TestMyCodeClientError> {
unimplemented!()
}
}
Expand Down
Loading

0 comments on commit c66fdac

Please sign in to comment.