Skip to content

Commit

Permalink
Implemented the feature fill to generate a table
Browse files Browse the repository at this point in the history
* allows to fill out a table to upload directly into moodle
* unzip has been altered/extended!

todo:
* `zipit` command to zip feedback files automatically
* write better `--help` messages
* write README.md (generate it?)
  • Loading branch information
BioGustav committed Mar 18, 2024
2 parents 7a6dd51 + 086f7f1 commit de784b0
Show file tree
Hide file tree
Showing 6 changed files with 544 additions and 135 deletions.
55 changes: 45 additions & 10 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 2 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,15 @@ description = "Small cli tool to make a tutors life easier"
name = "tutors"
path = "src/tuto.rs"


# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = "1.0.75"
clap = { version = "4.4.11", features = ["derive"] }
regex = "1.10.2"
walkdir = "2.4.0"
zip = "0.6.6"
zip-extensions = "0.6.2"

csv = "1.3.0"
serde = { version = "1.0.197", features = ["derive"] }

[profile.release]
debug = false
Expand Down
36 changes: 30 additions & 6 deletions src/tuto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ use std::path::PathBuf;
use anyhow::Result;
use clap::{Parser, Subcommand};

#[macro_use]
mod tutorsmacros;
mod tutors_csv;
mod tutorslib;

#[derive(Parser)]
Expand All @@ -29,10 +32,13 @@ enum Commands {
/// Unzip outer and inner containers
Unzip {
path: PathBuf,
/// unzip only outermost zip
/// Unzip only outermost zip
#[arg(short, long, action = clap::ArgAction::SetTrue)]
single: bool,
/// Specify the target directory [default: ./<FILE_NAME>]
/// Flatten the directory structure
#[arg(short, long, action = clap::ArgAction::SetTrue)]
flatten: bool,
/// Specify the target directory to unzip to [default: ./<FILE_NAME>]
#[arg(short, long)]
target: Option<PathBuf>,
},
Expand All @@ -45,29 +51,47 @@ enum Commands {
#[arg(short, long)]
max_points: Option<u8>,
},
Fill {
/// Path to the table file
table_path: PathBuf,
/// Path to the directory containing the student submissions
#[arg(short, long, default_value = ".")]
dir_path: PathBuf,
#[arg(short, long, default_value = "result.csv")]
result_path: PathBuf,
},
Stats,
}

#[allow(unused)]
fn main() -> Result<()> {
let cli = Cli::parse();

if cli.debug {
println!("{:?}", &cli.command);
}
dbglog!(cli.debug, "Command", &cli.command);

match cli.command {
Commands::Zip { name, paths } => tutorslib::zipit(name, paths),
Commands::Unzip {
path,
single,
flatten,
target,
} => tutorslib::unzip(&path, single, target.as_ref(), cli.debug),
} => tutorslib::unzip(&path, single, flatten, target.as_ref(), cli.debug),
Commands::Count {
path,
target_dir,
max_points,
} => tutorslib::count(&path, &target_dir, &max_points, cli.debug),
Commands::Fill {
table_path,
dir_path,
result_path,
} => tutorslib::fill_table(
table_path.as_path(),
dir_path.as_path(),
result_path.as_path(),
cli.debug,
),
Commands::Stats => tutorslib::stats(),
}
}
Expand Down
178 changes: 178 additions & 0 deletions src/tutors_csv.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
use regex::Regex;
use serde::{Deserialize, Deserializer, Serialize};

#[derive(Debug, Deserialize, Serialize, PartialEq, Clone)]
#[allow(unused)]
pub struct Record {
#[serde(rename = "ID")]
#[serde(deserialize_with = "deserialize_id")]
#[serde(serialize_with = "serialize_id")]
pub id: String,
#[serde(rename = "Vollständiger Name")]
name: String,
#[serde(rename = "ID-Nummer")]
id_number: String,
#[serde(rename = "E-Mail-Adresse")]
email: String,
#[serde(rename = "Status")]
status: String,
#[serde(rename = "Bewertung")]
#[serde(deserialize_with = "deserialize_points")]
#[serde(serialize_with = "serialize_points")]
pub points: Option<f32>,
#[serde(rename = "Bestwertung")]
#[serde(deserialize_with = "deserialize_max_points")]
#[serde(serialize_with = "serialize_max_points")]
pub max_points: f32,
#[serde(rename = "Bewertung kann geändert werden")]
rating_changeable: String,
#[serde(rename = "Zuletzt geändert (Abgabe)")]
last_change_submission: String,
#[serde(rename = "Zuletzt geändert (Bewertung)")]
last_change_rating: String,
#[serde(rename = "Feedback als Kommentar")]
pub feedback: String,
}

const ID_PATTERN: &str = r"([\d]+)";
const PREFIX_ID: &str = "Teilnehmer/in";

fn deserialize_id<'de, D>(deserializer: D) -> Result<String, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
let re = Regex::new(ID_PATTERN).unwrap();
if re.is_match(&s) {
let caps = re.captures(&s).unwrap();
let id = caps.get(1).unwrap().as_str().to_string();
Ok(id)
} else {
Err(serde::de::Error::custom("Invalid ID"))
}
}
fn deserialize_max_points<'de, D>(deserializer: D) -> Result<f32, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
let s = s.replace(',', ".");
match s.parse() {
Ok(f) => Ok(f),
Err(_) => Err(serde::de::Error::custom("Invalid rating")),
}
}

fn deserialize_points<'de, D>(deserializer: D) -> Result<Option<f32>, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
if s.is_empty() {
Ok(None)
} else {
s.replace(',', ".")
.parse()
.map(Some)
.map_err(serde::de::Error::custom)
}
}

fn serialize_id<S>(id: &str, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let id = format!("{}{}", PREFIX_ID, id);
serializer.serialize_str(&id.to_string())
}

fn serialize_max_points<S>(rating: &f32, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&format!("{:.2}", rating).replace('.', ","))
}

fn serialize_points<S>(rating: &Option<f32>, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match rating {
Some(rating) => serialize_max_points(rating, serializer),
None => serializer.serialize_str(""),
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_deserialize() {
let records = get_records();

let mut reader = csv::ReaderBuilder::new()
.delimiter(b',')
.has_headers(true)
.from_reader(DATA.as_bytes());

for (i, record) in reader
.deserialize::<Record>()
.filter_map(Result::ok)
.take(2)
.enumerate()
{
assert_eq!(record, records[i]);
}
}

#[test]
fn test_serialize() {
let records = get_records();

let mut wtr = csv::WriterBuilder::new()
.delimiter(b',')
.has_headers(true)
.from_writer(vec![]);
wtr.serialize(&records[0]).unwrap();
wtr.serialize(&records[1]).unwrap();

assert_eq!(DATA, String::from_utf8(wtr.into_inner().unwrap()).unwrap());
}

fn get_records() -> [Record; 2] {
[
Record {
id: 1234567.to_string(),
name: "asdf ghjklö".to_string(),
id_number: 12345678.to_string(),
email: "K12345678@students.jku.at".to_string(),
status: "Zur Bewertung abgegeben".to_string(),
points: Some(13.5),
max_points: 24.0,
rating_changeable: "Ja".to_string(),
last_change_submission: "Mittwoch, 13. März 2024, 10:15".to_string(),
last_change_rating: "Mittwoch, 13. März 2024, 19:59".to_string(),
feedback: "".to_string(),
},
Record {
id: 7654321.to_string(),
name: "fdsa ölkjh,g".to_string(),
id_number: 87654321.to_string(),
email: "K87654321@students.jku.at".to_string(),
status: "Zur Bewertung abgegeben".to_string(),
points: None,
max_points: 24.0,
rating_changeable: "Ja".to_string(),
last_change_submission: "Mittwoch, 13. März 2024, 15:10".to_string(),
last_change_rating: "Mittwoch, 13. März 2024, 19:59".to_string(),
feedback: "".to_string(),
},
]
}

const DATA: &str = r#"ID,Vollständiger Name,ID-Nummer,E-Mail-Adresse,Status,Bewertung,Bestwertung,Bewertung kann geändert werden,Zuletzt geändert (Abgabe),Zuletzt geändert (Bewertung),Feedback als Kommentar
Teilnehmer/in1234567,asdf ghjklö,12345678,K12345678@students.jku.at,Zur Bewertung abgegeben,"13,50","24,00",Ja,"Mittwoch, 13. März 2024, 10:15","Mittwoch, 13. März 2024, 19:59",
Teilnehmer/in7654321,"fdsa ölkjh,g",87654321,K87654321@students.jku.at,Zur Bewertung abgegeben,,"24,00",Ja,"Mittwoch, 13. März 2024, 15:10","Mittwoch, 13. März 2024, 19:59",
"#;
}
Loading

0 comments on commit de784b0

Please sign in to comment.