fix segfault on total grade calculation with no assignments
[nci.git] / nci-get-assignment-grades.c
blob23a8211bcd378d71ad3e4ef70bef92756496dd44
1 /*
2 * Copyright (c) 2016-2018 S. Gilles <sgilles@math.umd.edu>
4 * Permission to use, copy, modify, and/or distribute this software
5 * for any purpose with or without fee is hereby granted, provided
6 * that the above copyright notice and this permission notice appear
7 * in all copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
10 * WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
11 * WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
12 * AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
13 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
14 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
15 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
16 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 #include <errno.h>
19 #include <locale.h>
20 #include <stdint.h>
21 #include <stdint.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <strings.h>
25 #include <unistd.h>
27 #include <curl/curl.h>
28 #include <yajl_parse.h>
30 #include "macros.h"
31 #include "util.h"
33 /* Write output to stdout */
34 static void write_out_everything(char *course_id, map *students, map **grades,
35 map *totals, map *partials, size_t
36 assignments_num, char **assignment_ids,
37 map *assignments)
39 size_t j;
40 size_t k;
41 char **student_names = 0;
42 char *name;
43 char **id;
44 char **score;
45 char **res;
46 size_t students_num;
48 map_get_keys(students, &student_names, &students_num);
49 printf("\"Name\",\"ID\"");
51 for (j = 0; j < assignments_num; ++j) {
52 printf(",\"%s:%s (", course_id, assignment_ids[j]);
53 res = map_get(assignments, assignment_ids[j]);
55 if (res &&
56 res[0]) {
57 print_esc_0x22(res[0]);
60 printf(")\"");
63 if (totals) {
64 printf(",\"Total\",\"Letter Grade\"");
67 if (partials) {
68 printf(",\"Partial\",\"Partial Letter Grade\"");
71 printf("\n");
73 for (j = 0; j < students_num; ++j) {
74 name = student_names[j];
75 id = map_get(students, name);
77 if (!id ||
78 !id[0]) {
79 continue;
82 printf("\"");
83 print_esc_0x22(name);
84 printf("\",\"%s\"", id[0]);
86 for (k = 0; k < assignments_num; ++k) {
87 score = map_get(&((*grades)[k]), id[0]);
88 printf(",\"");
90 if (score &&
91 score[1] &&
92 !strcasecmp(score[1], "true")) {
93 print_esc_0x22("EX");
94 } else if (score &&
95 score[0]) {
96 print_esc_0x22(score[0]);
99 printf("\"");
102 if (totals) {
103 score = map_get(totals, id[0]);
104 printf(",\"");
106 if (score &&
107 score[0]) {
108 print_esc_0x22(score[0]);
111 printf(",\"");
113 if (score &&
114 score[1]) {
115 print_esc_0x22(score[1]);
118 printf("\"");
121 if (partials) {
122 score = map_get(partials, id[0]);
123 printf(",\"");
125 if (score &&
126 score[0] &&
127 score[2]) {
128 print_esc_0x22(score[2]);
131 printf("\",\"");
133 if (score &&
134 score[1] &&
135 score[3]) {
136 print_esc_0x22(score[3]);
139 printf("\"");
142 printf("\n");
145 free(student_names);
148 int main(int argc, char **argv)
150 int ret = EINVAL;
151 char *url_base = 0;
152 char *auth_token = 0;
153 char *course_id = 0;
154 const char *fn_students[] = { "sortable_name", "id", 0 };
155 const char *fn_scores[] = { "user_id", "score", "excused", 0 };
156 const char *fn_totals[] = { "user_id", "final_score", "final_grade",
157 "current_score", "current_grade", 0 };
158 const char *fn_assignments[] = { "id", "name", 0 };
159 uint_fast8_t fetch_all_arg = 0;
160 uint_fast8_t totals_arg = 0;
161 uint_fast8_t partials_arg = 0;
162 char **assignment_ids = 0;
163 map students = { 0 };
164 map *grades = 0;
165 map aggregates = { 0 };
166 map assignments = { 0 };
167 size_t j = 0;
168 size_t assignments_num = 0;
169 size_t len = 0;
170 char *built_uri = 0;
171 int opt = 0;
173 setlocale(LC_ALL, "");
175 if (!(assignment_ids = calloc((argc / 2), sizeof *assignment_ids))) {
176 ret = errno;
177 perror(L("calloc"));
178 goto cleanup;
181 while ((opt = getopt(argc, argv, "c:a:Atp")) != -1) {
182 switch (opt) {
183 case 'c':
184 course_id = optarg;
185 break;
186 case 'a':
187 assignment_ids[assignments_num++] = optarg;
188 break;
189 case 'A':
190 fetch_all_arg = 1;
191 break;
192 case 't':
193 totals_arg = 1;
194 break;
195 case 'p':
196 partials_arg = 1;
197 break;
198 default:
199 break;
203 if (fetch_all_arg &&
204 assignments_num) {
205 fprintf(stderr, "both -A and -a provided, ignoring -a\n");
208 if (!course_id) {
209 ret = EINVAL;
210 fprintf(stderr, "course-id is mandatory\n");
211 goto cleanup;
214 if (!fetch_all_arg &&
215 !assignments_num &&
216 !totals_arg &&
217 !partials_arg) {
218 ret = EINVAL;
219 fprintf(stderr, "one of -a, -A, -t, -p is mandatory\n");
220 goto cleanup;
223 if (!(grades = calloc(assignments_num, sizeof *grades))) {
224 ret = errno;
225 perror(L("calloc"));
226 goto cleanup;
229 curl_global_init(CURL_GLOBAL_DEFAULT);
231 if (!(url_base = get_url_base())) {
232 /* Error should have already been printed */
233 ret = ENOENT;
234 goto cleanup;
237 if (!(auth_token = get_auth_token())) {
238 /* Error should have already been printed */
239 ret = ENOENT;
240 goto cleanup;
243 /* Get a list of all students, so we can find out their names */
244 len = snprintf(0, 0, "%s/api/v1/courses/%s/students?per_page=9999",
245 url_base, course_id);
247 if (len + 1 < len) {
248 ret = errno = EOVERFLOW;
249 perror(L(""));
250 goto cleanup;
253 if (!(built_uri = malloc(len + 1))) {
254 ret = errno;
255 perror(L("malloc"));
256 goto cleanup;
259 sprintf(built_uri, "%s/api/v1/courses/%s/students?per_page=9999",
260 url_base, course_id);
262 if ((ret = key_value_extract(built_uri, auth_token, fn_students, 0,
263 &students))) {
264 goto cleanup;
267 /* Get a list of all assignments, so we can find out their names */
268 free(built_uri);
269 len = snprintf(0, 0, "%s/api/v1/courses/%s/assignments?per_page=9999",
270 url_base, course_id);
272 if (len + 1 < len) {
273 ret = errno = EOVERFLOW;
274 perror(L(""));
275 goto cleanup;
278 if (!(built_uri = malloc(len + 1))) {
279 ret = errno;
280 perror(L("malloc"));
281 goto cleanup;
284 sprintf(built_uri, "%s/api/v1/courses/%s/assignments?per_page=9999",
285 url_base, course_id);
287 if ((ret = key_value_extract(built_uri, auth_token, fn_assignments, 0,
288 &assignments))) {
289 goto cleanup;
292 if (fetch_all_arg) {
293 assignments_num = 0;
294 free(assignment_ids);
295 assignment_ids = 0;
296 free(grades);
297 grades = 0;
298 map_get_keys(&assignments, &assignment_ids, &assignments_num);
300 if (!assignments_num) {
301 ret = ENOMEM;
302 perror(L("map_get_keys"));
303 goto cleanup;
306 if (!(grades = calloc(assignments_num, sizeof *grades))) {
307 ret = errno;
308 perror(L("calloc"));
309 goto cleanup;
313 for (j = 0; j < assignments_num; ++j) {
314 free(built_uri);
315 len = snprintf(0, 0, "%s/api/v1/courses/%s/assignments/%s/"
316 "submissions?per_page=9999", url_base,
317 course_id,
318 assignment_ids[j]);
320 if (len + 1 < len) {
321 ret = errno = EOVERFLOW;
322 perror(L(""));
323 goto cleanup;
326 if (!(built_uri = malloc(len + 1))) {
327 ret = errno;
328 perror(L("malloc"));
329 goto cleanup;
332 sprintf(built_uri, "%s/api/v1/courses/%s/assignments/%s/"
333 "submissions?per_page=9999", url_base,
334 course_id,
335 assignment_ids[j]);
337 if ((ret = key_value_extract(built_uri, auth_token, fn_scores,
338 0, &(grades[j])))) {
339 goto cleanup;
343 if (totals_arg ||
344 partials_arg) {
345 free(built_uri);
346 len = snprintf(0, 0,
347 "%s/api/v1/courses/%s/enrollments?per_page=9999",
348 url_base,
349 course_id);
351 if (len + 1 < len) {
352 ret = errno = EOVERFLOW;
353 perror(L(""));
354 goto cleanup;
357 if (!(built_uri = malloc(len + 1))) {
358 ret = errno;
359 perror(L("malloc"));
360 goto cleanup;
363 sprintf(built_uri,
364 "%s/api/v1/courses/%s/enrollments?per_page=9999",
365 url_base,
366 course_id);
368 if ((ret = key_value_extract(built_uri, auth_token, fn_totals,
369 0, &aggregates))) {
370 goto cleanup;
374 write_out_everything(course_id, &students, &grades, (totals_arg ?
375 &aggregates : 0),
376 (partials_arg ? &aggregates : 0), assignments_num,
377 assignment_ids, &assignments);
378 ret = 0;
379 cleanup:
380 free(built_uri);
381 free(url_base);
382 free(auth_token);
383 curl_global_cleanup();
384 free(assignment_ids);
385 map_clean(&students);
386 map_clean(&assignments);
387 map_clean(&aggregates);
389 if (assignments_num) {
390 do {
391 map_clean(&grades[--assignments_num]);
392 } while (assignments_num);
395 free(grades);
397 return ret;