Skip to content

Commit

Permalink
[Feature:System] Duplicate Enrollment of Class/Section(s) (#34)
Browse files Browse the repository at this point in the history
This PR creates a tool for duplicating enrollment for specific
class/section to another class/section with the intent to copy
registration to optional practice courses.
  • Loading branch information
pbailie authored Jan 31, 2024
1 parent 22d2d42 commit 0fdecb4
Show file tree
Hide file tree
Showing 4 changed files with 313 additions and 17 deletions.
5 changes: 5 additions & 0 deletions student_auto_feed/config.php
Original file line number Diff line number Diff line change
Expand Up @@ -193,5 +193,10 @@
// 'tmp' or the current semester code.
define('ADD_DROP_FILES_PATH', "path/to/reports/");

/* CRN Copymap ------------------------------------------------------------- */

// Where is the crn copymap CSV located. Set to NULL is this is not used.
define('CRN_COPYMAP_FILE', "path/to/csv");

//EOF
?>
158 changes: 158 additions & 0 deletions student_auto_feed/crn_copymap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
#!/usr/bin/env php
<?php
require __DIR__ . "/config.php";

if (php_sapi_name() !== "cli") {
die("This is a command line tool.\n");
}

if (is_null(CRN_COPYMAP_FILE)) {
die("CRN_COPYMAP_FILE is null. Check config.php.\n");
}

$proc = new crn_copy();
$proc->main();
exit;

class crn_copy {
public $err;

public function __construct() {
$this->err = "";
}

public function __destruct() {
if ($this->err !== "") fprintf(STDERR, $this->err);
}

public function main() {
// Reminder: cli::parse_args() returns an array captured by regex,
// so we need to always look at index [0] when reading $args data.
$args = cli::parse_args();
$args['source']['sections'][0] = $this->get_mappings($args['source']['sections'][0]);
$args['dest']['sections'][0] = $this->get_mappings($args['dest']['sections'][0]);
if (count($args['source']['sections'][0]) !== count($args['dest']['sections'][0])) {
$this->err = "One course has more sections than the other. Sections need to map 1:1.\n";
exit(1);
}

$this->write_mappings($args);
}

private function write_mappings($args) {
$term = $args['term'][0];
$source_course = $args['source']['course'][0];
$source_sections = $args['source']['sections'][0];
$dest_course = $args['dest']['course'][0];
$dest_sections = $args['dest']['sections'][0];

// Insert "_{$term}" right before file extension.
// e.g. "/path/to/crn_copymap.csv" for term f23 becomes "/path/to/crn_copymap_f23.csv"
$filename = preg_replace("/([^\/]+?)(\.[^\/\.]*)?$/", "$1_{$term}$2", CRN_COPYMAP_FILE, 1);

$fh = fopen($filename, "a");
if ($fh === false) {
$this->err = "Could not open crn copymap file for writing.\n";
exit(1);
}

$len = count($source_sections);
for ($i = 0; $i < $len; $i++) {
$row = array($source_course, $source_sections[$i], $dest_course, $dest_sections[$i]);
fputcsv($fh, $row, ",");
}

fclose($fh);
}

private function get_mappings($sections) {
if ($sections === "" || $sections === "all") return array($sections);

$arr = explode(",", $sections);
$expanded = array();
foreach($arr as $val) {
if (preg_match("/(\d+)\-(\d+)/", $val, $matches) === 1) {
$expanded = array_merge($expanded, range((int) $matches[1], (int) $matches[2]));
} else {
$expanded[] = $val;
}
}

return $expanded;
}
}

/** class to parse command line arguments */
class cli {
/** @var string usage help message */
private static $help_usage = "Usage: crn_copymap.php [-h | --help | help] (term) (course-a) (sections) (course-b) (sections)\n";
/** @var string short description help message */
private static $help_short_desc = "Create duplicate enrollment mapping of courses and semesters.\n";
/** @var string long description help message */
private static $help_long_desc = <<<LONG_DESC
Create a mapping of CRNs (course and sections) that are to be duplicated.
This is useful if a professor wishes to have a course enrollment,
by section, duplicated to another course. Particularly when the
duplicated course has no enrollment data provided by IT.\n
LONG_DESC;
/** @var string argument list help message */
private static $help_args_list = <<<ARGS_LIST
Arguments:
-h, --help, help Show this help message.
term Term code of courses and sections being mapped. Required.
course-a Original course
sections Section list, or "all" of preceding course
course-b Course being copied to
sections For course-b, this can be ommited when course-a sections is "all"
ARGS_LIST;

/**
* Parse command line arguments
*
* CLI arguments are captured from global $argv by regular expressions during validation.
*
* @return array cli arguments
*/
public static function parse_args() {
global $argc, $argv;
$matches = array();

switch(true) {
// Check for request for help
case $argc > 1 && ($argv[1] === "-h" || $argv[1] === "--help" || $argv[1] === "help"):
self::print_help();
exit;
// Validate CLI arguments. Something is wrong (invalid) when a case condition is true.
case $argc < 5 || $argc > 6:
case $argv[3] === "all" && (array_key_exists(5, $argv) && $argv[5] !== "all"):
case $argv[3] !== "all" && (!array_key_exists(5, $argv) || $argv[5] === "all"):
case preg_match("/^[a-z][\d]{2}$/", $argv[1], $matches['term']) !== 1:
case preg_match("/^[\w\d\-]+$/", $argv[2], $matches['source']['course']) !== 1:
case preg_match("/^\d+(?:(?:,|\-)\d+)*$|^all$/", $argv[3], $matches['source']['sections']) !== 1:
case preg_match("/^[\w\d\-]+$/", $argv[4], $matches['dest']['course']) !== 1:
case preg_match("/^\d+(?:(?:,|\-)\d+)*$|^(?:all)?$/", $argv[5], $matches['dest']['sections']) !== 1:
self::print_usage();
exit;
}

// $matches['dest']['sections'][0] must be "all" when ['source']['sections'][0] is "all".
if ($matches['source']['sections'][0] === "all") $matches['dest']['sections'][0] = "all";
return $matches;
}

/** Print complete help */
private static function print_help() {
$msg = self::$help_usage . PHP_EOL;
$msg .= self::$help_short_desc . PHP_EOL;
$msg .= self::$help_long_desc . PHP_EOL;
$msg .= self::$help_args_list . PHP_EOL;
print $msg;
}

/** Print CLI usage */
private static function print_usage() {
print self::$help_usage . PHP_EOL;
}
}
// EOF
?>
30 changes: 28 additions & 2 deletions student_auto_feed/readme.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Submitty Student Auto Feed Script
Readme last updated Nov 17, 2021
Readme last updated Sept 1, 2023

This is a code example for any University to use as a basis to have Submitty course's enrollment data added or updated on an automated schedule with a student enrollment CSV datasheet.

Expand Down Expand Up @@ -134,7 +134,7 @@ the first (prior to autofeed) or second (after auto feed) run.

Second cli parameter is the term code.

For example:
For example:
```
$ ./add_drop_report.php 1 f21
```
Expand All @@ -145,3 +145,29 @@ $ ./add_drop_report.php 2 f21
```
Will invoke the _second_ run to create the report of student enrollments for the
Fall 2021 term.

## crn_copymap.php

Create a mapping of CRNs (course, term) that are to be duplicated. This is
useful if a professor wishes to have a course enrollment, by section,
duplicated to another course. Particularly when the duplicated course has
no enrollment data provided by IT.

Sections can be a comma separated list, a range denoted by a hyphen, or the
word "all" for all sections. Note that "all" sections will copy sections
respectively. i.e. section 1 is copied as section 1, section 2 is copied as
section 2, etc.

### Usage
```bash
$ crn_copymap.php (term) (original_course) (original_sections) (copied_course) (copied_sections)
```
For example:
Copy enrollments of term "f23" (fall 2023) of course CSCI 1000,
sections 1, 3, and 5 through 9 to course CSCI 2000 as sections 2, 4, and 6 through 10
respectively.
```bash
$ crn_copymap.php f23 csci1000 1,3,5-9 csci2000 2,4,6-10
```

EOF
Loading

0 comments on commit 0fdecb4

Please sign in to comment.