Skip to content

Commit

Permalink
Support for mooc
Browse files Browse the repository at this point in the history
  • Loading branch information
Heliozoa committed Aug 9, 2023
1 parent f0eb2c4 commit bfc8140
Show file tree
Hide file tree
Showing 15 changed files with 808 additions and 91 deletions.
34 changes: 29 additions & 5 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 Down Expand Up @@ -45,11 +46,7 @@ pub enum Command {
course: Option<String>,
},
/// Login to TMC server.
Login {
/// Initiates the non-interactive mode.
#[arg(short, long)]
non_interactive: bool,
},
Login,
/// Logout from TMC server.
Logout,
/// Change organization.
Expand All @@ -71,6 +68,33 @@ pub enum Command {
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,
},
/// Updates local exercises for the selected course.
MoocUpdateExercises {
/// If set, the exercises of this course are downloaded. If not set, the selection is done from an interactive menu.
course: Option<String>,
},
/// 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)]
Expand Down
47 changes: 46 additions & 1 deletion src/client.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use crate::{commands::util, config::TmcCliConfig, PLUGIN, PLUGIN_VERSION};
use anyhow::Context;
use bytes::Bytes;
use reqwest::Url;
use std::path::Path;
use tmc_langs::{
mooc::{self, ExerciseTaskSubmissionResult, ExerciseTaskSubmissionStatus, MoocClient},
tmc::{
response::{
Course, CourseDetails, CourseExercise, NewSubmission, Organization, SubmissionFinished,
Expand All @@ -11,25 +13,30 @@ use tmc_langs::{
},
Credentials, DownloadOrUpdateCourseExercisesResult, DownloadResult, LangsError, Language,
};
use uuid::Uuid;

pub const SUCCESSFUL_LOGIN: &str = "Logged in successfully!";
pub const WRONG_LOGIN: &str = "Wrong username or password";

pub struct Client {
pub tmc_client: TestMyCodeClient,
pub mooc_client: MoocClient,
pub test_mode: bool,
}

impl Client {
pub fn new(tmc_root_url: Url, test_mode: bool) -> anyhow::Result<Self> {
pub fn new(tmc_root_url: Url, mooc_root_url: String, test_mode: bool) -> anyhow::Result<Self> {
let (tmc_client, _credentials) = tmc_langs::init_testmycode_client_with_credentials(
tmc_root_url,
PLUGIN,
PLUGIN_VERSION,
)?;
let (mooc_client, _credentials) =
tmc_langs::init_mooc_client_with_credentials(mooc_root_url, PLUGIN)?;

Ok(Client {
tmc_client,
mooc_client,
test_mode,
})
}
Expand Down Expand Up @@ -351,6 +358,44 @@ impl Client {
}
}

// mooc commands
pub fn mooc_courses(&self) -> anyhow::Result<Vec<mooc::CourseInstance>> {
let courses = self.mooc_client.course_instances()?;
Ok(courses)
}
pub fn mooc_course_exercises(
&self,
course_instance_id: Uuid,
) -> anyhow::Result<Vec<mooc::TmcExerciseSlide>> {
let exercises = self
.mooc_client
.course_instance_exercise_slides(course_instance_id)?;
Ok(exercises)
}
pub fn mooc_download_exercise(&self, url: String) -> anyhow::Result<Bytes> {
let bytes = self.mooc_client.download(url)?;
Ok(bytes)
}
pub fn mooc_submit_exercise(
&self,
exercise_id: Uuid,
slide_id: Uuid,
task_id: Uuid,
archive: &Path,
) -> anyhow::Result<ExerciseTaskSubmissionResult> {
let res = self
.mooc_client
.submit(exercise_id, slide_id, task_id, archive)?;
Ok(res)
}
pub fn mooc_get_submission_grading(
&self,
submission_id: Uuid,
) -> anyhow::Result<ExerciseTaskSubmissionStatus> {
let res = self.mooc_client.get_submission_grading(submission_id)?;
Ok(res)
}

#[cfg(test)]
pub fn set_tmc_token(&mut self, token: Token) {
self.tmc_client.set_token(token);
Expand Down
29 changes: 25 additions & 4 deletions 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 All @@ -27,7 +28,9 @@ pub fn handle(cli: Cli, io: &mut Io, mut config: TmcCliConfig) -> anyhow::Result
.with_context(|| format!("Failed to parse TMC_LANGS_TMC_ROOT_URL ({url}) as a URL"))?,
Err(_) => "https://tmc.mooc.fi".parse().expect("known to work"),
};
let mut client = Client::new(tmc_root_url, cli.testmode)?;
let mooc_root_url = env::var("TMC_LANGS_MOOC_ROOT_URL")
.unwrap_or_else(|_| "https://courses.mooc.fi".to_string());
let mut client = Client::new(tmc_root_url, mooc_root_url, cli.testmode)?;

let require_logged_out = |client: &mut Client| {
let exists = client.load_login(&config).is_ok();
Expand All @@ -49,10 +52,9 @@ pub fn handle(cli: Cli, io: &mut Io, mut config: TmcCliConfig) -> anyhow::Result

match cli.subcommand {
// tmc commands
Command::Login { non_interactive } => {
Command::Login => {
require_logged_out(&mut client)?;
let interactive_mode = !non_interactive;
login::login(io, &mut client, interactive_mode, &mut config)?;
login::login(io, &mut client, &mut config)?;
}
Command::Download { course, currentdir } => {
require_logged_in(&mut client)?;
Expand Down Expand Up @@ -101,6 +103,25 @@ pub fn handle(cli: Cli, io: &mut Io, mut config: TmcCliConfig) -> anyhow::Result
logout::logout(io, &mut client, &mut config)?;
}

// 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,
&mut config,
)?,
Command::MoocUpdateExercises { course } => {
mooc::update_exercises::run(io, &mut client, course.as_deref(), &mut config)?
}
Command::MoocSubmitExercise { path } => {
mooc::submit_exercise::run(io, &mut client, path.as_deref(), &config)?
}

// hidden commands
Command::Fetchupdate => {
#[cfg(target_os = "windows")]
Expand Down
2 changes: 1 addition & 1 deletion src/commands/courses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ mod tests {
let (mut input, mut output) = test_helper::input_output();
let TestSetup {
mut io, mut client, ..
} = test_helper::setup(&mut input, &mut output, &server);
} = test_helper::tmc_setup(&mut input, &mut output, &server);
client.set_tmc_token(test_helper::tmc_token());

list_courses(&mut io, &mut client, "test").unwrap();
Expand Down
2 changes: 1 addition & 1 deletion src/commands/exercises.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ mod tests {
let (mut input, mut output) = test_helper::input_output();
let TestSetup {
mut io, mut client, ..
} = test_helper::setup(&mut input, &mut output, &server);
} = test_helper::tmc_setup(&mut input, &mut output, &server);
client.set_tmc_token(test_helper::tmc_token());

list_exercises(&mut io, &mut client, Some("course_name"), "test").unwrap();
Expand Down
71 changes: 2 additions & 69 deletions src/commands/login.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,10 @@
use super::{download, organization, util};
use crate::{
client::Client,
config::TmcCliConfig,
io::{Io, PrintColor},
};
use anyhow::Context;

pub fn login(
io: &mut Io,
client: &mut Client,
interactive_mode: bool,
config: &mut TmcCliConfig,
) -> anyhow::Result<()> {

pub fn login(io: &mut Io, client: &mut Client, config: &mut TmcCliConfig) -> anyhow::Result<()> {
io.print("Email / username: ", PrintColor::Normal)?;
let mut username = io.read_line()?;
username = username.trim().to_string();
Expand All @@ -34,74 +27,14 @@ pub fn login(
let message = client.try_login(username, password, config)?;
io.println(&message, PrintColor::Success)?;

let org = if interactive_mode {
organization::set_organization(io, client, config)
} else {
organization::set_organization_old(io, client, config)
}
.context("Could not set organization")?;

if client.is_test_mode() {
return Ok(());
}

if interactive_mode {
download_after_login(client, io, config, &org)?;
}

io.println("Logged in", PrintColor::Success)?;
Ok(())
}

pub fn download_after_login(
client: &mut Client,
io: &mut Io,
config: &TmcCliConfig,
org: &str,
) -> anyhow::Result<()> {
io.println("Fetching courses...", PrintColor::Normal)?;
let courses = client.list_courses(org)?;

let mut courses = courses
.iter()
.map(|course| client.get_course_details(course.id))
.collect::<Result<Vec<_>, _>>()?;

courses.sort_by(|a, b| {
a.course
.title
.to_lowercase()
.cmp(&b.course.title.to_lowercase())
});

let mut courses_displayed = courses
.iter()
.map(|course| course.course.title.as_str())
.collect::<Vec<_>>();
let no_download = "Don't download anything".to_string();
courses_displayed.insert(0, no_download.as_str());

let course = util::get_course_name(&courses_displayed)?;
if course == no_download {
anyhow::bail!("No course downloaded.");
}
let name_select = &courses
.iter()
.find(|c| c.course.title == course)
.context("No course matching the selected name was found")?
.course
.name;

// Get course by name
let course = util::get_course_by_name(client, name_select, org)?
.ok_or_else(|| anyhow::anyhow!("Could not find course with that name"))?;
let path = config.get_projects_dir();

let msg = download::download_exercises(path, client, &course)?;
io.println(&msg, PrintColor::Success)?;
Ok(())
}

#[cfg(test)]
mod tests {
/*
Expand Down
Loading

0 comments on commit bfc8140

Please sign in to comment.