refer: print usage to stderr
[neatrefer.git] / refer.c
blobda35105cf74548e5c609f056882173507ec9931a
1 /*
2 * NEATREFER - A REFER CLONE FOR NEATROFF
4 * Copyright (C) 2011-2017 Ali Gholami Rudi <ali at rudi dot ir>
6 * Permission to use, copy, modify, and/or distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 #include <ctype.h>
19 #include <fcntl.h>
20 #include <stdio.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <unistd.h>
25 #define NREFS (1 << 14)
26 #define LEN(a) (sizeof(a) / sizeof((a)[0]))
28 struct ref {
29 char *keys[128]; /* reference keys */
30 char *auth[128]; /* authors */
31 int id; /* allocated reference id */
32 int nauth;
35 static struct ref refs[NREFS]; /* all references in refer database */
36 static int refs_n;
37 static struct ref *cites[NREFS]; /* cited references */
38 static int cites_n;
39 static int inserted; /* number of inserted references */
40 static int multiref; /* allow specifying multiple references */
41 static int accumulate; /* accumulate all references */
42 static int initials; /* initials for authors' first name */
43 static int refauth; /* use author-year citations */
44 static int sortall; /* sort references */
45 static char *refmac; /* citation macro name */
46 static char *refmac_auth; /* author-year citation macro name */
47 static FILE *refdb; /* the database file */
49 #define ref_label(ref) ((ref)->keys['L'])
51 /* the next input line */
52 static char *lnget(void)
54 static char buf[1024];
55 return fgets(buf, sizeof(buf), stdin);
58 /* write an output line */
59 static void lnput(char *s, int n)
61 write(1, s, n >= 0 ? n : strlen(s));
64 /* the next refer database input line */
65 static char *dbget(void)
67 static char buf[1024];
68 return refdb ? fgets(buf, sizeof(buf), refdb) : NULL;
71 static char *sdup(char *s)
73 char *e = strchr(s, '\n') ? strchr(s, '\n') : strchr(s, '\0');
74 char *r;
75 int n = e - s;
76 r = malloc(n + 1);
77 memcpy(r, s, n);
78 r[n] = '\0';
79 return r;
82 /* format author names as J. Smith */
83 static char *ref_author(char *ref)
85 char *res;
86 char *out;
87 char *beg;
88 if (!initials)
89 return sdup(ref);
90 res = malloc(strlen(ref) + 32);
91 out = res;
92 while (1) {
93 while (*ref == ' ' || *ref == '.')
94 ref++;
95 if (*ref == '\0')
96 break;
97 beg = ref;
98 while (*ref && *ref != ' ' && *ref != '.')
99 ref++;
100 if (out != res)
101 *out++ = ' ';
102 if (islower((unsigned char) *beg) || *ref == '\0') {
103 while (beg < ref)
104 *out++ = *beg++;
105 } else { /* initials */
106 do {
107 *out++ = *beg++;
108 *out++ = '.';
109 while (beg < ref && *beg != '-')
110 beg++;
111 if (*beg == '-') /* handling J.-K. Smith */
112 *out++ = *beg++;
113 } while (beg < ref);
116 *out = '\0';
117 return res;
120 /* strip excess whitespace */
121 static void rstrip(char *s)
123 int i;
124 int last = -1;
125 for (i = 0; s[i]; i++)
126 if (s[i] != ' ' && s[i] != '\n')
127 last = i;
128 s[last + 1] = '\0';
131 /* read a single refer record */
132 static void db_ref(struct ref *ref, char *ln)
134 do {
135 if (ln[0] == '%' && ln[1] >= 'A' && ln[1] <= 'Z') {
136 char *r = ln + 2;
137 while (isspace((unsigned char) *r))
138 r++;
139 rstrip(r);
140 if (ln[1] == 'A')
141 ref->auth[ref->nauth++] = ref_author(r);
142 else
143 ref->keys[(unsigned char) ln[1]] = sdup(r);
144 ref->id = -1;
146 } while ((ln = dbget()) && ln[0] != '\n');
149 /* parse a refer-style bib file and fill refs[] */
150 static int db_parse(void)
152 char *ln;
153 while ((ln = dbget()))
154 if (ln[0] != '\n')
155 db_ref(&refs[refs_n++], ln);
156 return 0;
159 static char fields[] = "LTABERJDVNPITOH";
160 static char fields_flag[] = "OP";
161 static char *kinds[] = {"Other", "Article", "Book", "In book", "Report"};
163 static int ref_kind(struct ref *r)
165 if (r->keys['J'])
166 return 1;
167 if (r->keys['B'])
168 return 3;
169 if (r->keys['R'])
170 return 4;
171 if (r->keys['I'])
172 return 2;
173 return 0;
176 /* print the given reference */
177 static void ref_ins(struct ref *ref, int id)
179 char buf[1 << 12];
180 char *s = buf;
181 int kind = ref_kind(ref);
182 int j;
183 s += sprintf(s, ".ds [F %d\n", id);
184 s += sprintf(s, ".]-\n");
185 if (ref->nauth) {
186 s += sprintf(s, ".ds [A ");
187 for (j = 0; j < ref->nauth; j++)
188 s += sprintf(s, "%s%s", j ? ", " : "", ref->auth[j]);
189 s += sprintf(s, "\n");
191 for (j = 'B'; j <= 'Z'; j++) {
192 char *val = ref->keys[j];
193 if (!val || !strchr(fields, j))
194 continue;
195 s += sprintf(s, ".ds [%c %s\n", j, val ? val : "");
196 if (strchr(fields_flag, j))
197 s += sprintf(s, ".nr [%c 1\n", j);
199 s += sprintf(s, ".][ %d %s\n", kind, kinds[kind]);
200 lnput(buf, s - buf);
203 static char *lastname(char *name)
205 char *last = name;
206 while (*name) {
207 if (!islower((unsigned char) last[0]))
208 last = name;
209 while (*name && *name != ' ')
210 if (*name++ == '\\')
211 name++;
212 while (*name == ' ')
213 name++;
215 return last;
218 static int refcmp(struct ref *r1, struct ref *r2)
220 if (!r2->nauth || (r1->keys['H'] && !r2->keys['H']))
221 return -1;
222 if (!r1->nauth || (!r1->keys['H'] && r2->keys['H']))
223 return 1;
224 return strcmp(lastname(r1->auth[0]), lastname(r2->auth[0]));
227 /* print all references */
228 static void ref_all(void)
230 int i, j;
231 struct ref **sorted;
232 sorted = malloc(cites_n * sizeof(sorted[0]));
233 memcpy(sorted, cites, cites_n * sizeof(sorted[0]));
234 if (sortall == 'a') {
235 for (i = 1; i < cites_n; i++) {
236 for (j = i - 1; j >= 0 && refcmp(cites[i], sorted[j]) < 0; j--)
237 sorted[j + 1] = sorted[j];
238 sorted[j + 1] = cites[i];
241 lnput(".]<\n", -1);
242 for (i = 0; i < cites_n; i++)
243 ref_ins(sorted[i], sorted[i]->id + 1);
244 lnput(".]>", -1);
245 free(sorted);
248 static int intcmp(void *v1, void *v2)
250 return *(int *) v1 - *(int *) v2;
253 /* the given label was referenced; add it to cites[] */
254 static int refer_seen(char *label)
256 int i;
257 for (i = 0; i < refs_n; i++)
258 if (ref_label(&refs[i]) && !strcmp(label, ref_label(&refs[i])))
259 break;
260 if (i == refs_n)
261 return -1;
262 if (refs[i].id < 0) {
263 refs[i].id = cites_n++;
264 cites[refs[i].id] = &refs[i];
266 return refs[i].id;
269 static void refer_quote(char *d, char *s)
271 if (!strchr(s, ' ') && s[0] != '"') {
272 strcpy(d, s);
273 } else {
274 *d++ = '"';
275 while (*s) {
276 if (*s == '"')
277 *d++ = '"';
278 *d++ = *s++;
280 *d++ = '"';
281 *d = '\0';
285 /* replace .[ .] macros with reference numbers or author-year */
286 static int refer_cite(int *id, char *s, int auth)
288 char msg[256];
289 char label[256];
290 int nid = 0;
291 int i = 0;
292 msg[0] = '\0';
293 while (!nid || multiref) {
294 char *r = label;
295 while (*s && strchr(" \t\n,", (unsigned char) *s))
296 s++;
297 while (*s && !strchr(" \t\n,]", (unsigned char) *s))
298 *r++ = *s++;
299 *r = '\0';
300 if (!strcmp("$LIST$", label)) {
301 ref_all();
302 break;
304 id[nid] = refer_seen(label);
305 if (id[nid] < 0)
306 fprintf(stderr, "refer: <%s> not found\n", label);
307 else
308 nid++;
309 if (!*s || *s == '\n' || *s == ']')
310 break;
312 if (!auth) { /* numbered citations */
313 /* sort references for cleaner reference intervals */
314 qsort(id, nid, sizeof(id[0]), (void *) intcmp);
315 while (i < nid) {
316 int beg = i++;
317 /* reading reference intervals */
318 while (i < nid && id[i] == id[i - 1] + 1)
319 i++;
320 if (beg)
321 sprintf(msg + strlen(msg), ",");
322 if (beg == i - 1)
323 sprintf(msg + strlen(msg), "%d", id[beg] + 1);
324 else
325 sprintf(msg + strlen(msg), "%d%s%d",
326 id[beg] + 1, beg < i - 2 ? "\\-" : ",", id[i - 1] + 1);
328 } else if (nid) { /* year + authors citations */
329 struct ref *ref = cites[id[0]];
330 sprintf(msg, "%s %d", ref->keys['D'] ? ref->keys['D'] : "-", ref->nauth);
331 for (i = 0; i < ref->nauth; i++) {
332 sprintf(msg + strlen(msg), " ");
333 refer_quote(msg + strlen(msg), lastname(ref->auth[i]));
336 lnput(msg, -1);
337 return nid;
340 static int slen(char *s, int delim)
342 char *r = strchr(s, delim);
343 return r ? r - s : strchr(s, '\0') - s;
346 static int refer_reqname(char *mac, int maclen, char *s)
348 int i = 0;
349 if (*s++ != '.')
350 return 1;
351 for (i = 0; i < maclen && *s && *s != ' '; i++)
352 mac[i] = *s++;
353 mac[i] = '\0';
354 return *s != ' ';
357 static int refer_macname(char *mac, int maclen, char *s)
359 int i = 0;
360 if (*s++ != '\\')
361 return 1;
362 if (*s++ != '*')
363 return 1;
364 if (*s++ != '[')
365 return 1;
366 for (i = 0; i < maclen && *s && *s != ' '; i++)
367 mac[i] = *s++;
368 mac[i] = '\0';
369 return *s != ' ';
372 /* return 1 if mac is a citation macro */
373 static int refer_refmac(char *pat, char *mac)
375 char *s = pat ? strstr(pat, mac) : NULL;
376 if (!mac[0] || !s)
377 return 0;
378 return (s == pat || s[-1] == ',') &&
379 (!s[strlen(mac)] || s[strlen(mac)] == ',');
382 static void refer_insert(int *id, int id_n)
384 int i;
385 for (i = 0; i < id_n; i++)
386 ref_ins(cites[id[i]], ++inserted);
389 static void refer(void)
391 char mac[256];
392 int id[256];
393 char *s, *r, *ln;
394 while ((ln = lnget())) {
395 int id_n = 0;
396 /* multi-line citations: .[ rudi17 .] */
397 if (ln[0] == '.' && ln[1] == '[') {
398 lnput(ln + 2, slen(ln + 2, '\n'));
399 if ((ln = lnget())) {
400 id_n = refer_cite(id, ln, 0);
401 while (ln && (ln[0] != '.' || ln[1] != ']'))
402 ln = lnget();
403 if (ln)
404 lnput(ln + 2, -1);
406 if (!accumulate)
407 refer_insert(id, id_n);
408 continue;
410 /* single line citation .cite rudi17 */
411 if (ln[0] == '.' && !refer_reqname(mac, sizeof(mac), ln) &&
412 (refer_refmac(refmac, mac) || refer_refmac(refmac_auth, mac))) {
413 int i = 1;
414 while (ln[i] && ln[i] != ' ')
415 i++;
416 while (ln[i] && ln[i] == ' ')
417 i++;
418 lnput(ln, i);
419 id_n = refer_cite(id, ln + i, refer_refmac(refmac_auth, mac));
420 while (ln[i] && ln[i] != ' ' && ln[i] != '\n')
421 i++;
422 lnput(ln + i, -1);
423 if (!accumulate)
424 refer_insert(id, id_n);
425 continue;
427 s = ln;
428 r = s;
429 /* inline citations \*[cite rudi17] */
430 while ((r = strchr(r, '\\'))) {
431 r++;
432 if (refer_macname(mac, sizeof(mac), r - 1))
433 continue;
434 if (!refer_refmac(refmac, mac) && !refer_refmac(refmac_auth, mac))
435 continue;
436 if (!strchr(r, ']'))
437 continue;
438 r = strchr(r, ' ') + 1;
439 lnput(s, r - s);
440 id_n = refer_cite(id, r, refer_refmac(refmac_auth, mac));
441 while (*r && *r != ' ' && *r != ']')
442 r++;
443 s = r;
445 lnput(s, -1);
446 if (!accumulate)
447 refer_insert(id, id_n);
451 static char *usage =
452 "Usage neatrefer [options] <input >output\n"
453 "Options:\n"
454 "\t-p bib \tspecify the database file\n"
455 "\t-e \taccumulate references\n"
456 "\t-m \tmerge multiple references in a single .[/.] block\n"
457 "\t-i \tinitials for authors' first and middle names\n"
458 "\t-o xy \tcitation macro (\\*[xy label])\n"
459 "\t-a xy \tauthor-year citation macro (\\*[xy label])\n"
460 "\t-sa \tsort by author last names\n";
462 int main(int argc, char *argv[])
464 int i, j;
465 for (i = 1; i < argc; i++) {
466 switch (argv[i][0] == '-' ? argv[i][1] : 'h') {
467 case 'm':
468 multiref = 1;
469 break;
470 case 'e':
471 accumulate = 1;
472 break;
473 case 'p':
474 refdb = fopen(argv[i][2] ? argv[i] + 2 : argv[++i], "r");
475 if (refdb) {
476 db_parse();
477 fclose(refdb);
479 refdb = NULL;
480 break;
481 case 'o':
482 refmac = argv[i][2] ? argv[i] + 2 : argv[++i];
483 break;
484 case 'i':
485 initials = 1;
486 break;
487 case 'a':
488 refmac_auth = argv[i][2] ? argv[i] + 2 : argv[++i];
489 break;
490 case 's':
491 sortall = (unsigned char) (argv[i][2] ? argv[i][2] : argv[++i][0]);
492 break;
493 default:
494 fprintf(stderr, "%s", usage);
495 return 1;
498 if (refauth && multiref) {
499 fprintf(stderr, "refer: cannot use -m with -a\n");
500 return 1;
502 refer();
503 for (i = 0; i < refs_n; i++)
504 for (j = 0; j < LEN(refs[i].keys); j++)
505 if (refs[i].keys[j])
506 free(refs[i].keys[j]);
507 for (i = 0; i < refs_n; i++)
508 for (j = 0; j < LEN(refs[i].auth); j++)
509 if (refs[i].auth[j])
510 free(refs[i].auth[j]);
511 return 0;