Don't print an error message for empty courses
[nci.git] / nci-course-settings.c
bloba1807e68df8fb9284f614222f57d734f60288bb8
1 /*
2 * Copyright (c) 2016-2019 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 <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
26 #include <curl/curl.h>
27 #include <yajl_parse.h>
29 #include "macros.h"
30 #include "util.h"
32 /* Generic max */
33 static size_t
34 max(size_t a, size_t b)
36 return (a > b) ? a : b;
39 /* Compare grading standard entries based on score */
40 static int
41 gse_cmp(const void *a, const void *b)
43 grading_standard_entry *ga = (grading_standard_entry *) a;
44 grading_standard_entry *gb = (grading_standard_entry *) b;
45 float fa = strtof(ga->value, 0);
46 float fb = strtof(gb->value, 0);
48 if (fa > fb) {
49 return -1;
50 } else if (fa < fb) {
51 return 1;
54 return 0;
57 /* Cut up str into pairs, filling gse as appropriate */
58 static int
59 parse_cutoff_arg(char *str, grading_standard_entry **out, size_t *out_num)
61 char *p1 = 0;
62 char *p2 = 0;
63 size_t num_gses = 1;
64 size_t idx = 0;
65 uint_fast8_t should_quit = 0;
66 int ret = 0;
68 for (p1 = str; *p1; ++p1) {
69 num_gses += (*p1 == ',');
72 if (!(*out = calloc(num_gses, sizeof(**out)))) {
73 ret = errno;
74 perror(L("calloc"));
75 goto cleanup;
78 (*out)[0] = (grading_standard_entry) { 0 };
79 should_quit = 0;
81 for (p2 = str, p1 = str; !should_quit; ++p1) {
82 if (*p1 == ',' ||
83 *p1 == '\0') {
84 should_quit = (*p1 == '\0');
85 *p1 = '\0';
86 p1++;
87 (*out)[idx++].name = p2;
88 p2 = p1;
92 for (idx = 0; idx < num_gses; ++idx) {
93 p2 = strchr((*out)[idx].name, ':');
95 if (p2) {
96 *p2 = '\0';
97 p2++;
98 (*out)[idx].value = p2;
99 } else {
100 fprintf(stderr, "invalid cutoff entry \"%s\"\n",
101 (*out)[idx].name);
102 ret = EINVAL;
103 goto cleanup;
106 p1 = 0;
107 strtof(p2, &p1);
109 if (p1 &&
110 *p1) {
111 fprintf(stderr, "invalid grade \"%s\"\n", p2);
112 ret = EINVAL;
113 goto cleanup;
117 cleanup:
118 *out_num = num_gses;
120 return ret;
123 static int
124 get_and_write_everything(const char *url_base, const char *auth_token,
125 char *course_id)
127 int ret = EINVAL;
128 size_t len = 0;
129 char *built_uri = 0;
130 const char *fn_course[] = { "id", "grading_standard_id",
131 "hide_final_grades",
132 "apply_assignment_group_weights",
133 "start_at", "end_at", 0 };
134 const char *fn_gse[] = { "name", "value", 0 };
135 char **letters = 0;
136 size_t letters_num;
137 char **c = 0;
138 map course = { 0 };
139 map grading_standard = { 0 };
140 grading_standard_entry *sorted_gse = 0;
141 size_t j = 0;
142 float f;
143 size_t letter_len = 0;
144 char no_value[] = { '0', '.', '0', 0 };
146 len = snprintf(0, 0, "%s/api/v1/courses/%s", url_base, course_id);
148 if (len + 1 < len) {
149 ret = errno = EOVERFLOW;
150 perror(L(""));
151 goto cleanup;
154 if (!(built_uri = malloc(len + 1))) {
155 ret = errno;
156 perror(L("malloc"));
157 goto cleanup;
160 sprintf(built_uri, "%s/api/v1/courses/%s", url_base, course_id);
162 if ((ret = key_value_extract(built_uri, auth_token, fn_course, 0,
163 &course))) {
164 goto cleanup;
167 c = map_get(&course, course_id);
169 if (!c) {
170 fprintf(stderr, "no course %s\n", course_id);
171 ret = EINVAL;
172 goto cleanup;
175 if (!c) {
176 fprintf(stderr, "no course %s\n", course_id);
177 ret = EINVAL;
178 goto cleanup;
181 printf("Hide final grades: %s\n", UBSAFES(c[1]));
182 printf("Apply assignment group weights: %s\n", UBSAFES(c[2]));
183 printf("Grading standard enabled: %s\n", (c[0] ? "true" : "false"));
184 printf("Start date: %s\n", UBSAFES(c[3]));
185 printf("End date: %s\n", UBSAFES(c[4]));
186 printf("Letter grades:\n");
188 if (!c[0]) {
189 printf(" No grading standard\n");
190 ret = 0;
191 goto cleanup;
194 free(built_uri);
195 len = snprintf(0, 0, "%s/api/v1/courses/%s/grading_standards", url_base,
196 course_id);
198 if (len + 1 < len) {
199 ret = errno = EOVERFLOW;
200 perror(L(""));
201 goto cleanup;
204 if (!(built_uri = malloc(len + 1))) {
205 ret = errno;
206 perror(L("malloc"));
207 goto cleanup;
210 sprintf(built_uri, "%s/api/v1/courses/%s/grading_standards", url_base,
211 course_id);
213 if ((ret = key_value_extract(built_uri, auth_token, fn_gse, c[0],
214 &grading_standard))) {
215 goto cleanup;
218 map_get_keys(&grading_standard, &letters, &letters_num);
220 if (!letters_num) {
221 printf(" Empty (or Canvas-default?) grading standard\n");
222 ret = 0;
223 goto cleanup;
226 if (!(sorted_gse = calloc(letters_num, sizeof *sorted_gse))) {
227 ret = errno;
228 perror(L("calloc"));
229 goto cleanup;
232 for (j = 0; j < letters_num; ++j) {
233 sorted_gse[j] = (grading_standard_entry) { 0 };
234 sorted_gse[j].name = letters[j];
235 c = map_get(&grading_standard, letters[j]);
237 if (c &&
238 c[0]) {
239 sorted_gse[j].value = c[0];
240 } else {
241 sorted_gse[j].value = no_value;
244 letter_len = max(letter_len, strlen(letters[j]));
247 qsort(sorted_gse, letters_num, sizeof(sorted_gse[0]), gse_cmp);
249 for (j = 0; j < letters_num; ++j) {
250 f = 100.0 * strtof(sorted_gse[j].value, 0);
251 len = snprintf(0, 0, "%.6g%%", f);
253 if (len + 1 < len) {
254 ret = errno = EOVERFLOW;
255 perror(L(""));
256 goto cleanup;
259 if (!(sorted_gse[j].value = malloc(len + 1))) {
260 ret = errno;
261 perror(L("malloc"));
262 j++;
264 while (j < letters_num) {
265 sorted_gse[j++].value = 0;
268 goto cleanup;
271 sprintf(sorted_gse[j].value, "%.6g%%", f);
274 printf(" %s%*s [%s, \u221e)\n", sorted_gse[0].name, (int) (letter_len -
275 strlen(
276 sorted_gse
277 [0].
278 name)),
279 "", sorted_gse[0].value);
281 for (j = 1; j < letters_num; ++j) {
282 printf(" %s%*s [%s, %s)\n", sorted_gse[j].name,
283 (int) (letter_len - strlen(sorted_gse[j].name)), "",
284 sorted_gse[j].value, sorted_gse[j - 1].value);
287 ret = 0;
288 cleanup:
289 map_clean(&course);
290 free(built_uri);
292 if (sorted_gse) {
293 for (j = 0; j < letters_num; ++j) {
294 if (sorted_gse[j].value != no_value) {
295 free(sorted_gse[j].value);
300 free(sorted_gse);
302 return ret;
306 main(int argc, char **argv)
308 int ret = EINVAL;
309 char *url_base = 0;
310 char *auth_token = 0;
311 char *course_id = 0;
312 const char *hfg_arg = 0;
313 const char *aagw_arg = 0;
314 uint_fast8_t disable_grading_standard = 0;
315 char *start_date = 0;
316 char *end_date = 0;
317 char *cutoff_arg = 0;
318 char *gse_id = 0;
319 grading_standard_entry *cutoffs = 0;
320 size_t cutoffs_num = 0;
321 size_t len = 0;
322 char *built_uri = 0;
323 struct curl_httppost *post = 0;
324 uint_fast8_t get_something = 0;
325 uint_fast8_t set_something = 0;
326 int opt = 0;
328 setlocale(LC_ALL, "");
330 while ((opt = getopt(argc, argv, "c:dhHwWLs:e:C:")) != -1) {
331 switch (opt) {
332 case 'c':
333 course_id = optarg;
334 break;
335 case 'd':
336 get_something = 1;
337 break;
338 case 'h':
339 case 'H':
340 set_something = 1;
341 hfg_arg = (opt == 'h') ? "true" : "false";
342 break;
343 case 'w':
344 case 'W':
345 set_something = 1;
346 aagw_arg = (opt == 'w') ? "true" : "false";
347 break;
348 case 'L':
349 set_something = 1;
350 disable_grading_standard = 1;
351 break;
352 case 's':
353 set_something = 1;
354 start_date = optarg;
355 break;
356 case 'e':
357 set_something = 1;
358 end_date = optarg;
359 break;
360 case 'C':
361 set_something = 1;
362 cutoff_arg = optarg;
363 break;
364 default:
365 break;
369 if (!course_id) {
370 ret = EINVAL;
371 fprintf(stderr, "course-id is mandatory\n");
372 goto cleanup;
375 if (!set_something &&
376 !get_something) {
377 ret = EINVAL;
378 fprintf(stderr, "no action specified\n");
379 goto cleanup;
382 if (set_something &&
383 get_something) {
384 ret = EINVAL;
385 fprintf(stderr, "-g is mutually exclusive with setting data\n");
386 goto cleanup;
389 if (cutoff_arg &&
390 (ret = parse_cutoff_arg(cutoff_arg, &cutoffs, &cutoffs_num))) {
391 /* Error should have already been printed */
392 goto cleanup;
395 if (disable_grading_standard &&
396 cutoff_arg) {
397 ret = EINVAL;
398 fprintf(stderr, "-L and -C are mutually exclusive\n");
399 goto cleanup;
402 curl_global_init(CURL_GLOBAL_DEFAULT);
404 if (!(url_base = get_url_base())) {
405 ret = ENOENT;
407 /* Error should have already been printed */
408 goto cleanup;
411 if (!(auth_token = get_auth_token())) {
412 ret = ENOENT;
414 /* Error should have already been printed */
415 goto cleanup;
418 /* Reading is so complicated, it gets a separate function */
419 if (get_something) {
420 ret = get_and_write_everything(url_base, auth_token, course_id);
421 goto cleanup;
424 /* Cutoffs */
425 if (cutoffs_num) {
426 len = snprintf(0, 0, "%s/api/v1/courses/%s/grading_standards",
427 url_base, course_id);
429 if (len + 1 < len) {
430 ret = errno = EOVERFLOW;
431 perror(L(""));
432 goto cleanup;
435 if (!(built_uri = malloc(len + 1))) {
436 ret = errno;
437 perror(L("malloc"));
438 goto cleanup;
441 sprintf(built_uri, "%s/api/v1/courses/%s/grading_standards",
442 url_base, course_id);
444 if ((ret = make_gse_post(cutoffs, cutoffs_num, &post))) {
445 goto cleanup;
448 if ((ret = send_and_id_scan(built_uri, auth_token, post, "POST",
449 &gse_id))) {
450 goto cleanup;
453 curl_formfree(post);
454 post = 0;
457 /* Everything else */
458 len = snprintf(0, 0, "%s/api/v1/courses/%s", url_base, course_id);
459 free(built_uri);
461 if (len + 1 < len) {
462 ret = errno = EOVERFLOW;
463 perror(L(""));
464 goto cleanup;
467 if (!(built_uri = malloc(len + 1))) {
468 ret = errno;
469 perror(L("malloc"));
470 goto cleanup;
473 sprintf(built_uri, "%s/api/v1/courses/%s", url_base, course_id);
475 if ((ret = make_course_post(hfg_arg, aagw_arg, disable_grading_standard,
476 start_date, end_date, gse_id, &post))) {
477 goto cleanup;
480 if ((ret = send_and_id_scan(built_uri, auth_token, post, "PUT", 0))) {
481 goto cleanup;
484 ret = 0;
485 cleanup:
486 curl_formfree(post);
487 free(built_uri);
488 free(cutoffs);
489 free(url_base);
490 free(auth_token);
491 curl_global_cleanup();
493 return ret;