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
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>
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);
55 /* Cut up str into pairs, filling gse as appropriate */
56 static int parse_cutoff_arg(char *str
, grading_standard_entry
**out
,
63 uint_fast8_t should_quit
= 0;
66 for (p1
= str
; *p1
; ++p1
) {
67 num_gses
+= (*p1
== ',');
70 if (!(*out
= malloc(num_gses
* sizeof(**out
)))) {
76 (*out
)[0] = (grading_standard_entry
) { 0 };
79 for (p2
= str
, p1
= str
; !should_quit
; ++p1
) {
82 should_quit
= (*p1
== '\0');
85 (*out
)[idx
++].name
= p2
;
90 for (idx
= 0; idx
< num_gses
; ++idx
) {
91 p2
= strchr((*out
)[idx
].name
, ':');
96 (*out
)[idx
].value
= p2
;
98 fprintf(stderr
, "invalid cutoff entry \"%s\"\n",
109 fprintf(stderr
, "invalid grade \"%s\"\n", p2
);
121 static int get_and_write_everything(const char *url_base
, const
122 char *auth_token
, char *course_id
)
127 const char *fn_course
[] = { "id", "grading_standard_id",
129 "apply_assignment_group_weights",
130 "start_at", "end_at", 0 };
131 const char *fn_gse
[] = { "name", "value", 0 };
136 map grading_standard
= { 0 };
137 grading_standard_entry
*sorted_gse
= 0;
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))) {
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,
158 c
= map_get(&course
, course_id
);
161 fprintf(stderr
, "no course %s\n", course_id
);
167 fprintf(stderr
, "no course %s\n", course_id
);
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");
180 printf(" No grading standard\n");
186 len
= snprintf(0, 0, "%s/api/v1/courses/%s/grading_standards", url_base
,
189 if (!(built_uri
= malloc(len
+ 1))) {
195 sprintf(built_uri
, "%s/api/v1/courses/%s/grading_standards", url_base
,
198 if ((ret
= key_value_extract(built_uri
, auth_token
, fn_gse
, c
[0],
199 &grading_standard
))) {
203 map_get_keys(&grading_standard
, &letters
, &letters_num
);
206 printf(" Empty (or Canvas-default?) grading standard\n");
211 if (!(sorted_gse
= malloc(letters_num
* sizeof(*sorted_gse
)))) {
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
]);
224 sorted_gse
[j
].value
= c
[0];
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))) {
243 while (j
< letters_num
) {
244 sorted_gse
[j
++].value
= 0;
250 sprintf(sorted_gse
[j
].value
, "%.6g%%", f
);
253 printf(" %s%*s [%s, \u221e)\n", sorted_gse
[0].name
, (int) (letter_len
-
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
);
271 for (j
= 0; j
< letters_num
; ++j
) {
272 if (sorted_gse
[j
].value
!= no_value
) {
273 free(sorted_gse
[j
].value
);
283 int main(int argc
, char **argv
)
287 char *auth_token
= 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;
294 char *cutoff_arg
= 0;
296 grading_standard_entry
*cutoffs
= 0;
297 size_t cutoffs_num
= 0;
300 struct curl_httppost
*post
= 0;
301 uint_fast8_t get_something
= 0;
302 uint_fast8_t set_something
= 0;
305 setlocale(LC_ALL
, "");
307 while ((opt
= getopt(argc
, argv
, "c:dhHwWLs:e:C:")) != -1) {
318 hfg_arg
= (opt
== 'h') ? "true" : "false";
323 aagw_arg
= (opt
== 'w') ? "true" : "false";
327 disable_grading_standard
= 1;
348 fprintf(stderr
, "course-id is mandatory\n");
352 if (!set_something
&&
355 fprintf(stderr
, "no action specified\n");
362 fprintf(stderr
, "-g is mutually exclusive with setting data\n");
367 (ret
= parse_cutoff_arg(cutoff_arg
, &cutoffs
, &cutoffs_num
))) {
368 /* Error should have already been printed */
372 if (disable_grading_standard
&&
375 fprintf(stderr
, "-L and -C are mutually exclusive\n");
379 curl_global_init(CURL_GLOBAL_DEFAULT
);
381 if (!(url_base
= get_url_base())) {
384 /* Error should have already been printed */
388 if (!(auth_token
= get_auth_token())) {
391 /* Error should have already been printed */
395 /* Reading is so complicated, it gets a separate function */
397 ret
= get_and_write_everything(url_base
, auth_token
, course_id
);
403 len
= snprintf(0, 0, "%s/api/v1/courses/%s/grading_standards",
404 url_base
, course_id
);
406 if (!(built_uri
= malloc(len
+ 1))) {
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
))) {
419 if ((ret
= send_and_id_scan(built_uri
, auth_token
, post
, "POST",
428 /* Everything else */
429 len
= snprintf(0, 0, "%s/api/v1/courses/%s", url_base
, course_id
);
432 if (!(built_uri
= malloc(len
+ 1))) {
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
))) {
445 if ((ret
= send_and_id_scan(built_uri
, auth_token
, post
, "PUT", 0))) {
456 curl_global_cleanup();