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
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.
26 #include <curl/curl.h>
27 #include <yajl_parse.h>
34 max(size_t a
, size_t b
)
36 return (a
> b
) ? a
: b
;
39 /* Compare grading standard entries based on score */
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);
57 /* Cut up str into pairs, filling gse as appropriate */
59 parse_cutoff_arg(char *str
, grading_standard_entry
**out
, size_t *out_num
)
65 uint_fast8_t should_quit
= 0;
68 for (p1
= str
; *p1
; ++p1
) {
69 num_gses
+= (*p1
== ',');
72 if (!(*out
= calloc(num_gses
, sizeof(**out
)))) {
78 (*out
)[0] = (grading_standard_entry
) { 0 };
81 for (p2
= str
, p1
= str
; !should_quit
; ++p1
) {
84 should_quit
= (*p1
== '\0');
87 (*out
)[idx
++].name
= p2
;
92 for (idx
= 0; idx
< num_gses
; ++idx
) {
93 p2
= strchr((*out
)[idx
].name
, ':');
98 (*out
)[idx
].value
= p2
;
100 fprintf(stderr
, "invalid cutoff entry \"%s\"\n",
111 fprintf(stderr
, "invalid grade \"%s\"\n", p2
);
124 get_and_write_everything(const char *url_base
, const char *auth_token
,
130 const char *fn_course
[] = { "id", "grading_standard_id",
132 "apply_assignment_group_weights",
133 "start_at", "end_at", 0 };
134 const char *fn_gse
[] = { "name", "value", 0 };
139 map grading_standard
= { 0 };
140 grading_standard_entry
*sorted_gse
= 0;
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
);
149 ret
= errno
= EOVERFLOW
;
154 if (!(built_uri
= malloc(len
+ 1))) {
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,
167 c
= map_get(&course
, course_id
);
170 fprintf(stderr
, "no course %s\n", course_id
);
176 fprintf(stderr
, "no course %s\n", course_id
);
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");
189 printf(" No grading standard\n");
195 len
= snprintf(0, 0, "%s/api/v1/courses/%s/grading_standards", url_base
,
199 ret
= errno
= EOVERFLOW
;
204 if (!(built_uri
= malloc(len
+ 1))) {
210 sprintf(built_uri
, "%s/api/v1/courses/%s/grading_standards", url_base
,
213 if ((ret
= key_value_extract(built_uri
, auth_token
, fn_gse
, c
[0],
214 &grading_standard
))) {
218 map_get_keys(&grading_standard
, &letters
, &letters_num
);
221 printf(" Empty (or Canvas-default?) grading standard\n");
226 if (!(sorted_gse
= calloc(letters_num
, sizeof *sorted_gse
))) {
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
]);
239 sorted_gse
[j
].value
= c
[0];
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
);
254 ret
= errno
= EOVERFLOW
;
259 if (!(sorted_gse
[j
].value
= malloc(len
+ 1))) {
264 while (j
< letters_num
) {
265 sorted_gse
[j
++].value
= 0;
271 sprintf(sorted_gse
[j
].value
, "%.6g%%", f
);
274 printf(" %s%*s [%s, \u221e)\n", sorted_gse
[0].name
, (int) (letter_len
-
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
);
293 for (j
= 0; j
< letters_num
; ++j
) {
294 if (sorted_gse
[j
].value
!= no_value
) {
295 free(sorted_gse
[j
].value
);
306 main(int argc
, char **argv
)
310 char *auth_token
= 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;
317 char *cutoff_arg
= 0;
319 grading_standard_entry
*cutoffs
= 0;
320 size_t cutoffs_num
= 0;
323 struct curl_httppost
*post
= 0;
324 uint_fast8_t get_something
= 0;
325 uint_fast8_t set_something
= 0;
328 setlocale(LC_ALL
, "");
330 while ((opt
= getopt(argc
, argv
, "c:dhHwWLs:e:C:")) != -1) {
341 hfg_arg
= (opt
== 'h') ? "true" : "false";
346 aagw_arg
= (opt
== 'w') ? "true" : "false";
350 disable_grading_standard
= 1;
371 fprintf(stderr
, "course-id is mandatory\n");
375 if (!set_something
&&
378 fprintf(stderr
, "no action specified\n");
385 fprintf(stderr
, "-g is mutually exclusive with setting data\n");
390 (ret
= parse_cutoff_arg(cutoff_arg
, &cutoffs
, &cutoffs_num
))) {
391 /* Error should have already been printed */
395 if (disable_grading_standard
&&
398 fprintf(stderr
, "-L and -C are mutually exclusive\n");
402 curl_global_init(CURL_GLOBAL_DEFAULT
);
404 if (!(url_base
= get_url_base())) {
407 /* Error should have already been printed */
411 if (!(auth_token
= get_auth_token())) {
414 /* Error should have already been printed */
418 /* Reading is so complicated, it gets a separate function */
420 ret
= get_and_write_everything(url_base
, auth_token
, course_id
);
426 len
= snprintf(0, 0, "%s/api/v1/courses/%s/grading_standards",
427 url_base
, course_id
);
430 ret
= errno
= EOVERFLOW
;
435 if (!(built_uri
= malloc(len
+ 1))) {
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
))) {
448 if ((ret
= send_and_id_scan(built_uri
, auth_token
, post
, "POST",
457 /* Everything else */
458 len
= snprintf(0, 0, "%s/api/v1/courses/%s", url_base
, course_id
);
462 ret
= errno
= EOVERFLOW
;
467 if (!(built_uri
= malloc(len
+ 1))) {
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
))) {
480 if ((ret
= send_and_id_scan(built_uri
, auth_token
, post
, "PUT", 0))) {
491 curl_global_cleanup();