|
| 1 | +#!/usr/bin/env php |
| 2 | +<?php |
| 3 | +require __DIR__ . "/config.php"; |
| 4 | + |
| 5 | +if (php_sapi_name() !== "cli") { |
| 6 | + die("This is a command line tool.\n"); |
| 7 | +} |
| 8 | + |
| 9 | +if (is_null(CRN_COPYMAP_FILE)) { |
| 10 | + die("CRN_COPYMAP_FILE is null. Check config.php.\n"); |
| 11 | +} |
| 12 | + |
| 13 | +$proc = new crn_copy(); |
| 14 | +$proc->main(); |
| 15 | +exit; |
| 16 | + |
| 17 | +class crn_copy { |
| 18 | + public $err; |
| 19 | + |
| 20 | + public function __construct() { |
| 21 | + $this->err = ""; |
| 22 | + } |
| 23 | + |
| 24 | + public function __destruct() { |
| 25 | + if ($this->err !== "") fprintf(STDERR, $this->err); |
| 26 | + } |
| 27 | + |
| 28 | + public function main() { |
| 29 | + // Reminder: cli::parse_args() returns an array captured by regex, |
| 30 | + // so we need to always look at index [0] when reading $args data. |
| 31 | + $args = cli::parse_args(); |
| 32 | + $args['source']['sections'][0] = $this->get_mappings($args['source']['sections'][0]); |
| 33 | + $args['dest']['sections'][0] = $this->get_mappings($args['dest']['sections'][0]); |
| 34 | + if (count($args['source']['sections'][0]) !== count($args['dest']['sections'][0])) { |
| 35 | + $this->err = "One course has more sections than the other. Sections need to map 1:1.\n"; |
| 36 | + exit(1); |
| 37 | + } |
| 38 | + |
| 39 | + $this->write_mappings($args); |
| 40 | + } |
| 41 | + |
| 42 | + private function write_mappings($args) { |
| 43 | + $term = $args['term'][0]; |
| 44 | + $source_course = $args['source']['course'][0]; |
| 45 | + $source_sections = $args['source']['sections'][0]; |
| 46 | + $dest_course = $args['dest']['course'][0]; |
| 47 | + $dest_sections = $args['dest']['sections'][0]; |
| 48 | + |
| 49 | + // Insert "_{$term}" right before file extension. |
| 50 | + // e.g. "/path/to/crn_copymap.csv" for term f23 becomes "/path/to/crn_copymap_f23.csv" |
| 51 | + $filename = preg_replace("/([^\/]+?)(\.[^\/\.]*)?$/", "$1_{$term}$2", CRN_COPYMAP_FILE, 1); |
| 52 | + |
| 53 | + $fh = fopen($filename, "a"); |
| 54 | + if ($fh === false) { |
| 55 | + $this->err = "Could not open crn copymap file for writing.\n"; |
| 56 | + exit(1); |
| 57 | + } |
| 58 | + |
| 59 | + $len = count($source_sections); |
| 60 | + for ($i = 0; $i < $len; $i++) { |
| 61 | + $row = array($source_course, $source_sections[$i], $dest_course, $dest_sections[$i]); |
| 62 | + fputcsv($fh, $row, ","); |
| 63 | + } |
| 64 | + |
| 65 | + fclose($fh); |
| 66 | + } |
| 67 | + |
| 68 | + private function get_mappings($sections) { |
| 69 | + if ($sections === "" || $sections === "all") return array($sections); |
| 70 | + |
| 71 | + $arr = explode(",", $sections); |
| 72 | + $expanded = array(); |
| 73 | + foreach($arr as $val) { |
| 74 | + if (preg_match("/(\d+)\-(\d+)/", $val, $matches) === 1) { |
| 75 | + $expanded = array_merge($expanded, range((int) $matches[1], (int) $matches[2])); |
| 76 | + } else { |
| 77 | + $expanded[] = $val; |
| 78 | + } |
| 79 | + } |
| 80 | + |
| 81 | + return $expanded; |
| 82 | + } |
| 83 | +} |
| 84 | + |
| 85 | +/** class to parse command line arguments */ |
| 86 | +class cli { |
| 87 | + /** @var string usage help message */ |
| 88 | + private static $help_usage = "Usage: crn_copymap.php [-h | --help | help] (term) (course-a) (sections) (course-b) (sections)\n"; |
| 89 | + /** @var string short description help message */ |
| 90 | + private static $help_short_desc = "Create duplicate enrollment mapping of courses and semesters.\n"; |
| 91 | + /** @var string long description help message */ |
| 92 | + private static $help_long_desc = <<<LONG_DESC |
| 93 | + Create a mapping of CRNs (course and sections) that are to be duplicated. |
| 94 | + This is useful if a professor wishes to have a course enrollment, |
| 95 | + by section, duplicated to another course. Particularly when the |
| 96 | + duplicated course has no enrollment data provided by IT.\n |
| 97 | + LONG_DESC; |
| 98 | + /** @var string argument list help message */ |
| 99 | + private static $help_args_list = <<<ARGS_LIST |
| 100 | + Arguments: |
| 101 | + -h, --help, help Show this help message. |
| 102 | + term Term code of courses and sections being mapped. Required. |
| 103 | + course-a Original course |
| 104 | + sections Section list, or "all" of preceding course |
| 105 | + course-b Course being copied to |
| 106 | + sections For course-b, this can be ommited when course-a sections is "all" |
| 107 | + ARGS_LIST; |
| 108 | + |
| 109 | + /** |
| 110 | + * Parse command line arguments |
| 111 | + * |
| 112 | + * CLI arguments are captured from global $argv by regular expressions during validation. |
| 113 | + * |
| 114 | + * @return array cli arguments |
| 115 | + */ |
| 116 | + public static function parse_args() { |
| 117 | + global $argc, $argv; |
| 118 | + $matches = array(); |
| 119 | + |
| 120 | + switch(true) { |
| 121 | + // Check for request for help |
| 122 | + case $argc > 1 && ($argv[1] === "-h" || $argv[1] === "--help" || $argv[1] === "help"): |
| 123 | + self::print_help(); |
| 124 | + exit; |
| 125 | + // Validate CLI arguments. Something is wrong (invalid) when a case condition is true. |
| 126 | + case $argc < 5 || $argc > 6: |
| 127 | + case $argv[3] === "all" && (array_key_exists(5, $argv) && $argv[5] !== "all"): |
| 128 | + case $argv[3] !== "all" && (!array_key_exists(5, $argv) || $argv[5] === "all"): |
| 129 | + case preg_match("/^[a-z][\d]{2}$/", $argv[1], $matches['term']) !== 1: |
| 130 | + case preg_match("/^[\w\d\-]+$/", $argv[2], $matches['source']['course']) !== 1: |
| 131 | + case preg_match("/^\d+(?:(?:,|\-)\d+)*$|^all$/", $argv[3], $matches['source']['sections']) !== 1: |
| 132 | + case preg_match("/^[\w\d\-]+$/", $argv[4], $matches['dest']['course']) !== 1: |
| 133 | + case preg_match("/^\d+(?:(?:,|\-)\d+)*$|^(?:all)?$/", $argv[5], $matches['dest']['sections']) !== 1: |
| 134 | + self::print_usage(); |
| 135 | + exit; |
| 136 | + } |
| 137 | + |
| 138 | + // $matches['dest']['sections'][0] must be "all" when ['source']['sections'][0] is "all". |
| 139 | + if ($matches['source']['sections'][0] === "all") $matches['dest']['sections'][0] = "all"; |
| 140 | + return $matches; |
| 141 | + } |
| 142 | + |
| 143 | + /** Print complete help */ |
| 144 | + private static function print_help() { |
| 145 | + $msg = self::$help_usage . PHP_EOL; |
| 146 | + $msg .= self::$help_short_desc . PHP_EOL; |
| 147 | + $msg .= self::$help_long_desc . PHP_EOL; |
| 148 | + $msg .= self::$help_args_list . PHP_EOL; |
| 149 | + print $msg; |
| 150 | + } |
| 151 | + |
| 152 | + /** Print CLI usage */ |
| 153 | + private static function print_usage() { |
| 154 | + print self::$help_usage . PHP_EOL; |
| 155 | + } |
| 156 | +} |
| 157 | +// EOF |
| 158 | +?> |
0 commit comments