Be more verbose on HTTP error messages
[nci.git] / nci-course-settings.c
blob54756b3a64d898926248703373b0da1686fbf6b8
1 /*
2 * Copyright (c) 2016, 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 max(size_t a, size_t b)
35 return (a > b) ? a : b;
38 /* Compare grading standard entries based on score */
39 static int gse_cmp(const void *a, const void *b)
41 grading_standard_entry *ga = (grading_standard_entry *) a;
42 grading_standard_entry *gb = (grading_standard_entry *) b;
43 float fa = strtof(ga->value, 0);
44 float fb = strtof(gb->value, 0);
46 if (fa > fb) {
47 return -1;
48 } else if (fa < fb) {
49 return 1;
52 return 0;
55 /* Cut up str into pairs, filling gse as appropriate */
56 static int parse_cutoff_arg(char *str, grading_standard_entry **out,
57 size_t *out_num)
59 char *p1 = 0;
60 char *p2 = 0;
61 size_t num_gses = 1;
62 size_t idx = 0;
63 uint_fast8_t should_quit = 0;
64 int ret = 0;
66 for (p1 = str; *p1; ++p1) {
67 num_gses += (*p1 == ',');
70 if (!(*out = malloc(num_gses * sizeof(**out)))) {
71 ret = errno;
72 perror(L("malloc"));
73 goto cleanup;
76 (*out)[0] = (grading_standard_entry) { 0 };
77 should_quit = 0;
79 for (p2 = str, p1 = str; !should_quit; ++p1) {
80 if (*p1 == ',' ||
81 *p1 == '\0') {
82 should_quit = (*p1 == '\0');
83 *p1 = '\0';
84 p1++;
85 (*out)[idx++].name = p2;
86 p2 = p1;
90 for (idx = 0; idx < num_gses; ++idx) {
91 p2 = strchr((*out)[idx].name, ':');
93 if (p2) {
94 *p2 = '\0';
95 p2++;
96 (*out)[idx].value = p2;
97 } else {
98 fprintf(stderr, "invalid cutoff entry \"%s\"\n",
99 (*out)[idx].name);
100 ret = EINVAL;
101 goto cleanup;
104 p1 = 0;
105 strtof(p2, &p1);
107 if (p1 &&
108 *p1) {
109 fprintf(stderr, "invalid grade \"%s\"\n", p2);
110 ret = EINVAL;
111 goto cleanup;
115 cleanup:
116 *out_num = num_gses;
118 return ret;
121 static int get_and_write_everything(const char *url_base, const
122 char *auth_token, char *course_id)
124 int ret = EINVAL;
125 size_t len = 0;
126 char *built_uri = 0;
127 const char *fn_course[] = { "id", "grading_standard_id",
128 "hide_final_grades",
129 "apply_assignment_group_weights",
130 "start_at", "end_at", 0 };
131 const char *fn_gse[] = { "name", "value", 0 };
132 char **letters = 0;
133 size_t letters_num;
134 char **c = 0;
135 map course = { 0 };
136 map grading_standard = { 0 };
137 grading_standard_entry *sorted_gse = 0;
138 size_t j = 0;
139 float f;
140 size_t letter_len = 0;
141 char no_value[] = { '0', '.', '0', 0 };
143 len = snprintf(0, 0, "%s/api/v1/courses/%s", url_base, course_id);
145 if (!(built_uri = malloc(len + 1))) {
146 ret = errno;
147 perror(L("malloc"));
148 goto cleanup;
151 sprintf(built_uri, "%s/api/v1/courses/%s", url_base, course_id);
153 if ((ret = key_value_extract(built_uri, auth_token, fn_course, 0,
154 &course))) {
155 goto cleanup;
158 c = map_get(&course, course_id);
160 if (!c) {
161 fprintf(stderr, "no course %s\n", course_id);
162 ret = EINVAL;
163 goto cleanup;
166 if (!c) {
167 fprintf(stderr, "no course %s\n", course_id);
168 ret = EINVAL;
169 goto cleanup;
172 printf("Hide final grades: %s\n", UBSAFES(c[1]));
173 printf("Apply assignment group weights: %s\n", UBSAFES(c[2]));
174 printf("Grading standard enabled: %s\n", (c[0] ? "true" : "false"));
175 printf("Start date: %s\n", UBSAFES(c[3]));
176 printf("End date: %s\n", UBSAFES(c[4]));
177 printf("Letter grades:\n");
179 if (!c[0]) {
180 printf(" No grading standard\n");
181 ret = 0;
182 goto cleanup;
185 free(built_uri);
186 len = snprintf(0, 0, "%s/api/v1/courses/%s/grading_standards", url_base,
187 course_id);
189 if (!(built_uri = malloc(len + 1))) {
190 ret = errno;
191 perror(L("malloc"));
192 goto cleanup;
195 sprintf(built_uri, "%s/api/v1/courses/%s/grading_standards", url_base,
196 course_id);
198 if ((ret = key_value_extract(built_uri, auth_token, fn_gse, c[0],
199 &grading_standard))) {
200 goto cleanup;
203 map_get_keys(&grading_standard, &letters, &letters_num);
205 if (!letters_num) {
206 printf(" Empty (or Canvas-default?) grading standard\n");
207 ret = 0;
208 goto cleanup;
211 if (!(sorted_gse = malloc(letters_num * sizeof(*sorted_gse)))) {
212 ret = errno;
213 perror(L("malloc"));
214 goto cleanup;
217 for (j = 0; j < letters_num; ++j) {
218 sorted_gse[j] = (grading_standard_entry) { 0 };
219 sorted_gse[j].name = letters[j];
220 c = map_get(&grading_standard, letters[j]);
222 if (c &&
223 c[0]) {
224 sorted_gse[j].value = c[0];
225 } else {
226 sorted_gse[j].value = no_value;
229 letter_len = max(letter_len, strlen(letters[j]));
232 qsort(sorted_gse, letters_num, sizeof(sorted_gse[0]), gse_cmp);
234 for (j = 0; j < letters_num; ++j) {
235 f = 100.0 * strtof(sorted_gse[j].value, 0);
236 len = snprintf(0, 0, "%.6g%%", f);
238 if (!(sorted_gse[j].value = malloc(len + 1))) {
239 ret = errno;
240 perror(L("malloc"));
241 j++;
243 while (j < letters_num) {
244 sorted_gse[j++].value = 0;
247 goto cleanup;
250 sprintf(sorted_gse[j].value, "%.6g%%", f);
253 printf(" %s%*s [%s, \u221e)\n", sorted_gse[0].name, (int) (letter_len -
254 strlen(
255 sorted_gse
256 [0].
257 name)), "", sorted_gse[0].value);
259 for (j = 1; j < letters_num; ++j) {
260 printf(" %s%*s [%s, %s)\n", sorted_gse[j].name,
261 (int) (letter_len - strlen(sorted_gse[j].name)), "",
262 sorted_gse[j].value, sorted_gse[j - 1].value);
265 ret = 0;
266 cleanup:
267 map_clean(&course);
268 free(built_uri);
270 if (sorted_gse) {
271 for (j = 0; j < letters_num; ++j) {
272 if (sorted_gse[j].value != no_value) {
273 free(sorted_gse[j].value);
278 free(sorted_gse);
280 return ret;
283 int main(int argc, char **argv)
285 int ret = EINVAL;
286 char *url_base = 0;
287 char *auth_token = 0;
288 char *course_id = 0;
289 const char *hfg_arg = 0;
290 const char *aagw_arg = 0;
291 uint_fast8_t disable_grading_standard = 0;
292 char *start_date = 0;
293 char *end_date = 0;
294 char *cutoff_arg = 0;
295 char *gse_id = 0;
296 grading_standard_entry *cutoffs = 0;
297 size_t cutoffs_num = 0;
298 size_t len = 0;
299 char *built_uri = 0;
300 struct curl_httppost *post = 0;
301 uint_fast8_t get_something = 0;
302 uint_fast8_t set_something = 0;
303 int opt = 0;
305 setlocale(LC_ALL, "");
307 while ((opt = getopt(argc, argv, "c:dhHwWLs:e:C:")) != -1) {
308 switch (opt) {
309 case 'c':
310 course_id = optarg;
311 break;
312 case 'd':
313 get_something = 1;
314 break;
315 case 'h':
316 case 'H':
317 set_something = 1;
318 hfg_arg = (opt == 'h') ? "true" : "false";
319 break;
320 case 'w':
321 case 'W':
322 set_something = 1;
323 aagw_arg = (opt == 'w') ? "true" : "false";
324 break;
325 case 'L':
326 set_something = 1;
327 disable_grading_standard = 1;
328 break;
329 case 's':
330 set_something = 1;
331 start_date = optarg;
332 break;
333 case 'e':
334 set_something = 1;
335 end_date = optarg;
336 break;
337 case 'C':
338 set_something = 1;
339 cutoff_arg = optarg;
340 break;
341 default:
342 break;
346 if (!course_id) {
347 ret = EINVAL;
348 fprintf(stderr, "course-id is mandatory\n");
349 goto cleanup;
352 if (!set_something &&
353 !get_something) {
354 ret = EINVAL;
355 fprintf(stderr, "no action specified\n");
356 goto cleanup;
359 if (set_something &&
360 get_something) {
361 ret = EINVAL;
362 fprintf(stderr, "-g is mutually exclusive with setting data\n");
363 goto cleanup;
366 if (cutoff_arg &&
367 (ret = parse_cutoff_arg(cutoff_arg, &cutoffs, &cutoffs_num))) {
368 /* Error should have already been printed */
369 goto cleanup;
372 if (disable_grading_standard &&
373 cutoff_arg) {
374 ret = EINVAL;
375 fprintf(stderr, "-L and -C are mutually exclusive\n");
376 goto cleanup;
379 curl_global_init(CURL_GLOBAL_DEFAULT);
381 if (!(url_base = get_url_base())) {
382 ret = ENOENT;
384 /* Error should have already been printed */
385 goto cleanup;
388 if (!(auth_token = get_auth_token())) {
389 ret = ENOENT;
391 /* Error should have already been printed */
392 goto cleanup;
395 /* Reading is so complicated, it gets a separate function */
396 if (get_something) {
397 ret = get_and_write_everything(url_base, auth_token, course_id);
398 goto cleanup;
401 /* Cutoffs */
402 if (cutoffs_num) {
403 len = snprintf(0, 0, "%s/api/v1/courses/%s/grading_standards",
404 url_base, course_id);
406 if (!(built_uri = malloc(len + 1))) {
407 ret = errno;
408 perror(L("malloc"));
409 goto cleanup;
412 sprintf(built_uri, "%s/api/v1/courses/%s/grading_standards",
413 url_base, course_id);
415 if ((ret = make_gse_post(cutoffs, cutoffs_num, &post))) {
416 goto cleanup;
419 if ((ret = send_and_id_scan(built_uri, auth_token, post, "POST",
420 &gse_id))) {
421 goto cleanup;
424 curl_formfree(post);
425 post = 0;
428 /* Everything else */
429 len = snprintf(0, 0, "%s/api/v1/courses/%s", url_base, course_id);
430 free(built_uri);
432 if (!(built_uri = malloc(len + 1))) {
433 ret = errno;
434 perror(L("malloc"));
435 goto cleanup;
438 sprintf(built_uri, "%s/api/v1/courses/%s", url_base, course_id);
440 if ((ret = make_course_post(hfg_arg, aagw_arg, disable_grading_standard,
441 start_date, end_date, gse_id, &post))) {
442 goto cleanup;
445 if ((ret = send_and_id_scan(built_uri, auth_token, post, "PUT", 0))) {
446 goto cleanup;
449 ret = 0;
450 cleanup:
451 curl_formfree(post);
452 free(built_uri);
453 free(cutoffs);
454 free(url_base);
455 free(auth_token);
456 curl_global_cleanup();
458 return ret;