Don't print an error message for empty courses
[nci.git] / nci-list-assignments.c
blobe98779f88e2ebf132d175d5c86e8b6fe01ea49ec
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 enum {
33 FN_AG_POSITION = 0,
34 FN_AG_NAME = 1,
35 FN_AG_ID = 2,
36 FN_AG_WEIGHT = 3,
37 FN_AG_DROP_LOWEST = 4,
38 FN_AG_DROP_HIGHEST = 5,
39 FN_AG_END = 6,
42 enum {
43 FN_A_ID = 0,
44 FN_A_GROUP_ID = 1,
45 FN_A_POSITION = 2,
46 FN_A_NAME = 3,
47 FN_A_MAX_POINTS = 4,
48 FN_A_DUE_AT = 5,
49 FN_A_END = 6
52 static int
53 cmp_entry_by_pos(const void *a, const void *b)
55 const entry *ea = (const entry *) a;
56 const entry *eb = (const entry *) b;
57 const char *sa = ea->vs[FN_A_POSITION - 1];
58 const char *sb = eb->vs[FN_A_POSITION - 1];
59 long long la = strtoll(sa, 0, 0);
60 long long lb = strtoll(sb, 0, 0);
62 return (la < lb) ? -1 : ((la > lb) ? 1 : 0);
65 static int
66 write_out_everything(map *assigns, map *agroups)
68 int ret = EINVAL;
69 size_t j = 0;
70 size_t k = 0;
71 char **agroups_positions = 0;
72 size_t agroups_num = 0;
73 char **ag = 0;
74 char **a_ids = 0;
75 char **unprinted_a_ids = 0;
76 size_t as_num = 0;
77 char **a = 0;
78 entry *ca = 0;
79 size_t ca_num = 0;
80 uint_fast8_t some_categorized = 0;
81 uint_fast8_t printed_uncategorized = 0;
83 map_get_keys(agroups, &agroups_positions, &agroups_num);
84 map_get_keys(assigns, &a_ids, &as_num);
86 if (!(unprinted_a_ids = calloc(as_num, sizeof *unprinted_a_ids))) {
87 ret = errno;
88 perror(L("calloc"));
89 goto cleanup;
92 if (!(ca = calloc(as_num, sizeof *ca))) {
93 ret = errno;
94 perror(L("calloc"));
95 goto cleanup;
98 memcpy(unprinted_a_ids, a_ids, as_num * sizeof unprinted_a_ids);
100 for (j = 0; j < agroups_num; ++j) {
101 ag = map_get(agroups, agroups_positions[j]);
103 if (!ag) {
104 continue;
107 printf("%s %s", UBSAFES(ag[FN_AG_ID - 1]), UBSAFES(
108 ag[FN_AG_NAME - 1]));
110 if (ag[FN_AG_WEIGHT - 1]) {
111 printf(" (%s%%)", ag[FN_AG_WEIGHT - 1]);
114 if (ag[FN_AG_DROP_LOWEST - 1] &&
115 strcmp(ag[FN_AG_DROP_LOWEST - 1], "0")) {
116 printf(" (Drop %s lowest)", ag[FN_AG_DROP_LOWEST - 1]);
119 if (ag[FN_AG_DROP_HIGHEST - 1] &&
120 strcmp(ag[FN_AG_DROP_HIGHEST - 1], "0")) {
121 printf(" (Drop %s highest)\n", ag[FN_AG_DROP_HIGHEST -
122 1]);
123 } else {
124 printf("\n");
127 ca_num = 0;
129 for (k = 0; k < as_num; ++k) {
130 a = map_get(assigns, a_ids[k]);
132 if (!strcmp(ag[FN_AG_ID - 1], a[FN_A_GROUP_ID - 1])) {
133 unprinted_a_ids[k] = 0;
134 ca[ca_num] = (entry) { .k = a_ids[k], .vs = a };
135 ca_num++;
139 qsort(ca, ca_num, sizeof *ca, cmp_entry_by_pos);
141 for (k = 0; k < ca_num; ++k) {
142 some_categorized = 1;
143 printf(" %s %s", UBSAFES(ca[k].k), UBSAFES(
144 ca[k].vs[FN_A_NAME - 1]));
146 if (ca[k].vs[FN_A_MAX_POINTS - 1]) {
147 printf(" (%s)", ca[k].vs[FN_A_MAX_POINTS - 1]);
150 if (ca[k].vs[FN_A_DUE_AT - 1]) {
151 printf(" due at %s\n", ca[k].vs[FN_A_DUE_AT -
152 1]);
153 } else {
154 printf("\n");
159 for (k = 0; k < as_num; ++k) {
160 if (!unprinted_a_ids[k]) {
161 continue;
164 if (some_categorized &&
165 !printed_uncategorized) {
166 printf("UNCATEGORIZED\n");
167 printed_uncategorized = 1;
170 if (some_categorized) {
171 printf(" ");
174 a = map_get(assigns, unprinted_a_ids[k]);
175 printf("%s %s", unprinted_a_ids[k], a[FN_A_NAME - 1]);
177 if (a[FN_A_MAX_POINTS - 1]) {
178 printf(" (%s)\n", a[FN_A_MAX_POINTS - 1]);
179 } else {
180 printf("\n");
184 ret = 0;
185 cleanup:
186 free(agroups_positions);
187 free(a_ids);
188 free(unprinted_a_ids);
189 free(ca);
191 return ret;
195 main(int argc, char **argv)
197 int ret = EINVAL;
198 char *url_base = 0;
199 char *auth_token = 0;
200 char *course_id = 0;
201 const char *fn_agroups[] = {
202 /* */
203 [FN_AG_POSITION] = "position", /* */
204 [FN_AG_NAME] = "name", /* */
205 [FN_AG_ID] = "id", /* */
206 [FN_AG_WEIGHT] = "group_weight", /* */
207 [FN_AG_DROP_LOWEST] = "drop_lowest", /* */
208 [FN_AG_DROP_HIGHEST] = "drop_highest", /* */
209 [FN_AG_END] = 0 /* */
211 const char *fn_assigns[] = {
212 /* */
213 [FN_A_ID] = "id", /* */
214 [FN_A_GROUP_ID] = "assignment_group_id", /* */
215 [FN_A_POSITION] = "position", /* */
216 [FN_A_NAME] = "name", /* */
217 [FN_A_MAX_POINTS] = "points_possible", /* */
218 [FN_A_DUE_AT] = "due_at", /* */
219 [FN_A_END] = 0 /* */
221 map agroups = { 0 };
222 map assigns = { 0 };
223 size_t len = 0;
224 char *built_uri = 0;
225 int opt;
227 setlocale(LC_ALL, "");
229 while ((opt = getopt(argc, argv, "c:")) != -1) {
230 switch (opt) {
231 case 'c':
232 course_id = optarg;
233 break;
234 default:
235 break;
239 if (!course_id) {
240 ret = EINVAL;
241 fprintf(stderr, "course-id is mandatory\n");
242 goto cleanup;
245 curl_global_init(CURL_GLOBAL_DEFAULT);
247 if (!(url_base = get_url_base())) {
248 ret = ENOENT;
250 /* Error should have already been printed */
251 goto cleanup;
254 if (!(auth_token = get_auth_token())) {
255 ret = ENOENT;
257 /* Error should have already been printed */
258 goto cleanup;
261 len = snprintf(0, 0,
262 "%s/api/v1/courses/%s/assignment_groups?per_page=9999",
263 url_base,
264 course_id);
266 if (len + 1 < len) {
267 ret = errno = EOVERFLOW;
268 perror(L(""));
269 goto cleanup;
272 if (!(built_uri = malloc(len + 1))) {
273 ret = errno;
274 perror(L("malloc"));
275 goto cleanup;
278 sprintf(built_uri,
279 "%s/api/v1/courses/%s/assignment_groups?per_page=9999",
280 url_base,
281 course_id);
283 if ((ret = key_value_extract(built_uri, auth_token, fn_agroups, 0,
284 &agroups))) {
285 goto cleanup;
288 free(built_uri);
289 built_uri = 0;
290 len = snprintf(0, 0, "%s/api/v1/courses/%s/assignments?per_page=9999",
291 url_base, course_id);
293 if (len + 1 < len) {
294 ret = errno = EOVERFLOW;
295 perror(L(""));
296 goto cleanup;
299 if (!(built_uri = malloc(len + 1))) {
300 ret = errno;
301 perror(L("malloc"));
302 goto cleanup;
305 sprintf(built_uri, "%s/api/v1/courses/%s/assignments?per_page=9999",
306 url_base, course_id);
308 if ((ret = key_value_extract(built_uri, auth_token, fn_assigns, 0,
309 &assigns))) {
310 goto cleanup;
313 if ((ret = write_out_everything(&assigns, &agroups))) {
314 goto cleanup;
317 ret = 0;
318 cleanup:
319 free(built_uri);
320 map_clean(&agroups);
321 map_clean(&assigns);
322 free(url_base);
323 free(auth_token);
324 curl_global_cleanup();
326 return ret;