2 kjv: Read the Word of God from your terminal
13 #include <readline/readline.h>
14 #include <readline/history.h>
16 #include <sys/types.h>
19 #include <sys/ioctl.h>
24 #define KJV_REF_SEARCH 1
25 #define KJV_REF_EXACT 2
26 #define KJV_REF_EXACT_SET 3
27 #define KJV_REF_RANGE 4
28 #define KJV_REF_RANGE_EXT 5
30 typedef struct kjv_ref
{
34 unsigned int chapter_end
;
36 unsigned int verse_end
;
44 return calloc(1, sizeof(kjv_ref
));
48 kjv_freeref(kjv_ref
*ref
)
51 regfree(&ref
->search
);
57 kjv_parseref(kjv_ref
*ref
, const char *ref_str
)
60 // 2. <book>:?<chapter>
61 // 3. <book>:?<chapter>:<verse>
62 // 3a. <book>:?<chapter>:<verse>[,<verse>]...
63 // 4. <book>:?<chapter>-<chapter>
64 // 5. <book>:?<chapter>:<verse>-<verse>
65 // 6. <book>:?<chapter>:<verse>-<chapter>:<verse>
68 // 9. <book>:?<chapter>/search
76 intset_free(ref
->verse_set
);
77 ref
->verse_set
= NULL
;
78 regfree(&ref
->search
);
82 sscanf(ref_str
, "%*1[1-3]%n", &n
);
83 bool has_booknum
= n
> 0;
84 if (has_booknum
&& sscanf(ref_str
, "%1[1-3]%62[a-zA-Z ]%n", &ref
->book
[0], &ref
->book
[1], &n
) == 2) {
85 ref_str
= &ref_str
[n
];
86 } else if (!has_booknum
&& sscanf(ref_str
, "%63[a-zA-Z ]%n", &ref
->book
[0], &n
) == 1) {
87 // 1, 2, 3, 3a, 4, 5, 6, 8, 9
88 ref_str
= &ref_str
[n
];
89 } else if (ref_str
[0] == '/') {
96 if (sscanf(ref_str
, ":%u%n", &ref
->chapter
, &n
) == 1 || sscanf(ref_str
, "%u%n", &ref
->chapter
, &n
) == 1) {
97 // 2, 3, 3a, 4, 5, 6, 9
98 ref_str
= &ref_str
[n
];
99 } else if (ref_str
[0] == '/') {
102 } else if (ref_str
[0] == '\0') {
104 ref
->type
= KJV_REF_EXACT
;
110 if (sscanf(ref_str
, ":%u%n", &ref
->verse
, &n
) == 1) {
112 ref_str
= &ref_str
[n
];
113 } else if (sscanf(ref_str
, "-%u%n", &ref
->chapter_end
, &n
) == 1) {
115 if (ref_str
[n
] != '\0') {
118 ref
->type
= KJV_REF_RANGE
;
120 } else if (ref_str
[0] == '/') {
123 } else if (ref_str
[0] == '\0') {
125 ref
->type
= KJV_REF_EXACT
;
132 int ret
= sscanf(ref_str
, "-%u%n", &value
, &n
);
133 if (ret
== 1 && ref_str
[n
] == '\0') {
135 ref
->verse_end
= value
;
136 ref
->type
= KJV_REF_RANGE
;
138 } else if (ret
== 1) {
140 ref
->chapter_end
= value
;
141 ref_str
= &ref_str
[n
];
142 } else if (ref_str
[0] == '\0') {
144 ref
->type
= KJV_REF_EXACT
;
146 } else if (sscanf(ref_str
, ",%u%n", &value
, &n
) == 1) {
148 ref
->verse_set
= intset_new();
149 intset_add(ref
->verse_set
, ref
->verse
);
150 intset_add(ref
->verse_set
, value
);
151 ref_str
= &ref_str
[n
];
153 if (sscanf(ref_str
, ",%u%n", &value
, &n
) != 1) {
156 intset_add(ref
->verse_set
, value
);
157 ref_str
= &ref_str
[n
];
159 if (ref_str
[0] != '\0') {
162 ref
->type
= KJV_REF_EXACT_SET
;
168 if (sscanf(ref_str
, ":%u%n", &ref
->verse_end
, &n
) == 1 && ref_str
[n
] == '\0') {
170 ref
->type
= KJV_REF_RANGE_EXT
;
177 ref
->type
= KJV_REF_SEARCH
;
178 if (regcomp(&ref
->search
, &ref_str
[1], REG_EXTENDED
|REG_ICASE
|REG_NOSUB
) != 0) {
185 str_join(size_t n
, char *strs
[])
188 for (size_t i
= 0; i
< n
; i
++) {
192 length
+= strlen(strs
[i
]);
194 char *str
= malloc(length
+ 1);
196 for (size_t i
= 0; i
< n
; i
++) {
200 strcat(str
, strs
[i
]);
206 kjv_bookequal(const char *a
, const char *b
, bool short_match
)
208 for (size_t i
= 0, j
= 0; ; ) {
209 if ((!a
[i
] && !b
[j
]) || (short_match
&& !b
[j
])) {
211 } else if (a
[i
] == ' ') {
213 } else if (b
[j
] == ' ') {
215 } else if (tolower(a
[i
]) != tolower(b
[j
])) {
225 kjv_book_matches(const char *book
, const kjv_verse
*verse
)
227 return kjv_bookequal(verse
->book_name
, book
, false) ||
228 kjv_bookequal(verse
->book_abbr
, book
, false) ||
229 kjv_bookequal(verse
->book_name
, book
, true);
233 kjv_should_output(const kjv_ref
*ref
, const kjv_verse
*verse
)
237 return (ref
->book
[0] == '\0' || kjv_book_matches(ref
->book
, verse
)) &&
238 (ref
->chapter
== 0 || verse
->chapter
== ref
->chapter
) &&
239 regexec(&ref
->search
, verse
->text
, 0, NULL
, 0) == 0;
242 return kjv_book_matches(ref
->book
, verse
) &&
243 (ref
->chapter
== 0 || ref
->chapter
== verse
->chapter
) &&
244 (ref
->verse
== 0 || ref
->verse
== verse
->verse
);
246 case KJV_REF_EXACT_SET
:
247 return kjv_book_matches(ref
->book
, verse
) &&
248 (ref
->chapter
== 0 || verse
->chapter
== ref
->chapter
) &&
249 intset_contains(ref
->verse_set
, verse
->verse
);
252 return kjv_book_matches(ref
->book
, verse
) &&
253 ((ref
->chapter_end
== 0 && ref
->chapter
== verse
->chapter
) ||
254 (verse
->chapter
>= ref
->chapter
&& verse
->chapter
<= ref
->chapter_end
)) &&
255 (ref
->verse
== 0 || verse
->verse
>= ref
->verse
) &&
256 (ref
->verse_end
== 0 || verse
->verse
<= ref
->verse_end
);
258 case KJV_REF_RANGE_EXT
:
259 return kjv_book_matches(ref
->book
, verse
) &&
261 (verse
->chapter
== ref
->chapter
&& verse
->verse
>= ref
->verse
&& ref
->chapter
!= ref
->chapter_end
) ||
262 (verse
->chapter
> ref
->chapter
&& verse
->chapter
< ref
->chapter_end
) ||
263 (verse
->chapter
== ref
->chapter_end
&& verse
->verse
<= ref
->verse_end
&& ref
->chapter
!= ref
->chapter_end
) ||
264 (ref
->chapter
== ref
->chapter_end
&& verse
->chapter
== ref
->chapter
&& verse
->verse
>= ref
->verse
&& verse
->verse
<= ref
->verse_end
)
273 kjv_output(const kjv_ref
*ref
, FILE *f
, bool no_linewrap
, int maximum_width
)
275 bool printed
= false;
276 int last_book_printed
= 0;
277 for (size_t i
= 0; i
< kjv_verses_length
; i
++) {
278 kjv_verse
*verse
= &kjv_verses
[i
];
279 if (kjv_should_output(ref
, verse
)) {
280 if (verse
->book
!= last_book_printed
) {
281 fprintf(f
, "%s\n", verse
->book_name
);
282 last_book_printed
= verse
->book
;
284 fprintf(f
, "%d:%d\t", verse
->chapter
, verse
->verse
);
286 fprintf(f
, "%s\n", verse
->text
);
288 char verse_text
[1024];
289 strcpy(verse_text
, verse
->text
);
290 size_t characters_printed
= 0;
291 char *word
= strtok(verse_text
, " ");
292 while (word
!= NULL
) {
293 size_t word_length
= strlen(word
);
294 if (characters_printed
+ word_length
+ (characters_printed
> 0 ? 1 : 0) > maximum_width
- 8) {
296 characters_printed
= 0;
298 if (characters_printed
> 0) {
300 characters_printed
++;
302 fprintf(f
, "%s", word
);
303 characters_printed
+= word_length
;
304 word
= strtok(NULL
, " ");
315 kjv_render(const kjv_ref
*ref
, bool no_linewrap
, int maximum_width
)
318 if (pipe(fds
) == -1) {
325 dup2(fds
[0], STDIN_FILENO
);
326 char *args
[] = { "less", "-f", "-", NULL
};
327 execvp("less", args
);
328 printf("unable not exec less\n");
330 } else if (pid
== -1) {
331 printf("unable to fork\n");
335 FILE *output
= fdopen(fds
[1], "w");
336 bool printed
= kjv_output(ref
, output
, no_linewrap
, maximum_width
);
341 waitpid(pid
, NULL
, 0);
343 printf("unknown reference\n");
349 usage
= "usage: kjv [flags] [reference...]\n"
358 " <Book>:<Chapter>\n"
359 " Individual chapter of a book\n"
360 " <Book>:<Chapter>:<Verse>[,<Verse>]...\n"
361 " Individual verse(s) of a specific chapter of a book\n"
362 " <Book>:<Chapter>-<Chapter>\n"
363 " Range of chapters in a book\n"
364 " <Book>:<Chapter>:<Verse>-<Verse>\n"
365 " Range of verses in a book chapter\n"
366 " <Book>:<Chapter>:<Verse>-<Chapter>:<Verse>\n"
367 " Range of chapters and verses in a book\n"
370 " All verses that match a pattern\n"
372 " All verses in a book that match a pattern\n"
373 " <Book>:<Chapter>/<Search>\n"
374 " All verses in a chapter of a book that match a pattern\n";
377 main(int argc
, char *argv
[])
379 bool list_books
= false;
380 bool no_linewrap
= false;
383 while ((opt
= getopt(argc
, argv
, "lWh")) != -1) {
402 char *last_book_printed
= NULL
;
403 for (i
= 0; i
< kjv_verses_length
; i
++) {
404 if (last_book_printed
== NULL
|| strcmp(kjv_verses
[i
].book_name
, last_book_printed
) != 0) {
405 printf("%s (%s)\n", kjv_verses
[i
].book_name
, kjv_verses
[i
].book_abbr
);
406 last_book_printed
= kjv_verses
[i
].book_name
;
412 int maximum_width
= 80;
413 struct winsize ttysize
;
414 memset(&ttysize
, 0, sizeof(struct winsize
));
415 if (ioctl(STDOUT_FILENO
, TIOCGWINSZ
, &ttysize
) == 0 && ttysize
.ws_col
> 0) {
416 maximum_width
= ttysize
.ws_col
;
419 signal(SIGPIPE
, SIG_IGN
);
421 if (argc
== optind
) {
423 char *input
= readline("kjv> ");
427 kjv_ref
*ref
= kjv_newref();
428 int success
= kjv_parseref(ref
, input
);
431 kjv_render(ref
, no_linewrap
, maximum_width
);
436 char *ref_str
= str_join(argc
-optind
, &argv
[optind
]);
437 kjv_ref
*ref
= kjv_newref();
438 int success
= kjv_parseref(ref
, ref_str
);
441 kjv_render(ref
, no_linewrap
, maximum_width
);