You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
394 lines
8.3 KiB
394 lines
8.3 KiB
#ifndef COSMOPOLITAN |
|
#include <stdio.h> |
|
#include <string.h> |
|
#include <stdbool.h> |
|
#include <stdlib.h> |
|
#endif |
|
|
|
#ifdef COSMOPOLITAN |
|
#include "cosmopolitan.h" |
|
#endif |
|
|
|
int replace_tabs(char **rtext); |
|
|
|
int read_file(FILE* in, char **out) { |
|
int size_of_buffer = 20; |
|
char *buffer = calloc(size_of_buffer, sizeof(char)); |
|
char character; |
|
int count = 0; |
|
|
|
while (true) { |
|
character = fgetc(in); |
|
if (character == EOF) { |
|
buffer[count] = '\0'; |
|
*out = buffer; |
|
return 0; |
|
} |
|
if (count == (size_of_buffer - 3)) { |
|
buffer = reallocarray(buffer, (size_of_buffer *= 2), sizeof(char)); |
|
|
|
if (!buffer) { |
|
return 1; |
|
} |
|
} |
|
buffer[count] = character; |
|
count++; |
|
} |
|
} |
|
|
|
char *read_argvs(int argc, char**argv) { |
|
int len = 1; |
|
char *string = calloc(1, sizeof(char)); |
|
|
|
int clen = 0; |
|
int now = 0; |
|
for (int i = 1; i < argc; i++) { |
|
int slen = strlen(argv[i]); |
|
clen += slen; |
|
|
|
if (clen == len) { |
|
clen += 1; |
|
} |
|
|
|
if (clen >= len - 1) { |
|
string = reallocarray(string, now + slen + 2, sizeof(char)); |
|
} |
|
|
|
strcpy(&string[now], argv[i]); |
|
strcpy(&(string[now + slen]), " "); |
|
|
|
now = now + slen + 1; |
|
} |
|
return string; |
|
} |
|
|
|
// Remove duplicate instances of <del> so that a maximum of 3 consecutive |
|
// remain |
|
void strip_excess_char(char **text, char del) { |
|
int len = strlen(*text); |
|
int count = 0; |
|
|
|
do { |
|
|
|
char last_char = 'a'; |
|
char llast_char = 'a'; |
|
char this_char; |
|
count = 0; |
|
|
|
for (int i = 0; (*text)[i] != '\0'; i++) { |
|
this_char = (*text)[i]; |
|
|
|
if (this_char == del && last_char == del && llast_char == del) { |
|
count++; |
|
|
|
if ((*text)[i+1] == '\0') { |
|
/*(*text)[i-1] = '\0';*/ |
|
(*text)[i] = '\0'; |
|
break; |
|
} |
|
|
|
for (int j = i; (*text)[j] != '\0'; j++) { |
|
/*(*text)[j-1] = (*text)[j];*/ |
|
/*(*text)[j] = (*text)[j+1];*/ |
|
(*text)[j] = (*text)[j+1]; |
|
} |
|
|
|
//*text = reallocarray(*text, --len, sizeof(char)); |
|
} |
|
llast_char = last_char; |
|
last_char = this_char; |
|
} |
|
} while (count > 0); |
|
} |
|
|
|
void strip_tailing_whitespace(char *text) { |
|
int len = strlen(text); |
|
|
|
char *match = "\n\t "; |
|
|
|
for (int i = len-1; i > 0; i ++) { |
|
bool m = false; |
|
for (int j = 0; j < strlen(match); j++) { |
|
if (text[i] == match[j]) { |
|
text[i] = '\0'; |
|
m = true; |
|
break; |
|
} |
|
} |
|
if (!m) { |
|
break; |
|
} |
|
} |
|
} |
|
|
|
char *read_stdin(void) { |
|
char *text; |
|
int err = read_file(stdin, &text); |
|
|
|
if (err) { |
|
return NULL; |
|
} else { |
|
strip_excess_char(&text, '\n'); |
|
replace_tabs(&text); |
|
//strip_excess_char(&text, '\t'); |
|
//strip_excess_char(&text, ' '); |
|
strip_tailing_whitespace(text); |
|
return text; |
|
} |
|
} |
|
|
|
char *get_text(int argc, char **argv) { |
|
if (argc == 1) { |
|
// stdin mode |
|
return read_stdin(); |
|
} else if (argc > 1) { |
|
// arg mode |
|
return read_argvs(argc, argv); |
|
} |
|
return NULL; |
|
} |
|
|
|
void insert_char(char **str, char c, int p) { |
|
int len = strlen(*str); |
|
|
|
*str = reallocarray(*str, len + 3, sizeof(char)); |
|
|
|
for (int i = len-1; i >= p; i--) { |
|
(*str)[i+1] = (*str)[i]; |
|
} |
|
|
|
(*str)[p] = c; |
|
} |
|
|
|
// hard-warps string on spaces and long-lines to match width. |
|
// @param width: the maximum width of the resulting string. |
|
// @param text: The text to be wrapped. It is modified in place. |
|
void wrap_text(char *text, int width) { |
|
|
|
char *last_break = NULL; |
|
int current_line = 1; |
|
int last_break_pos = 0; |
|
|
|
for (int i = 0; text[i] != '\0'; i++) { |
|
|
|
if (text[i] == '\n') { |
|
last_break = text + i; |
|
last_break_pos = current_line; |
|
current_line = 0; |
|
} |
|
|
|
// set whitespace to be break point |
|
if ((text[i] == ' ') && current_line <= width + 2) { |
|
last_break_pos = current_line; |
|
last_break = text + i; |
|
} |
|
|
|
// force break on long liness |
|
if ((current_line > width + 1) && last_break == NULL) { |
|
insert_char(&text, '\n', i-1); |
|
last_break = NULL; |
|
current_line = 0; |
|
} else if (current_line > width + 1) { |
|
// normal break |
|
*last_break = '\n'; |
|
last_break = NULL; |
|
current_line = current_line - last_break_pos; |
|
} |
|
|
|
current_line++; |
|
} |
|
} |
|
|
|
void print_bubble(char *text, int width, int line_count) { |
|
|
|
printf(" "); |
|
for (int i = 0; i < width + 3; i ++) { |
|
printf("_"); |
|
} |
|
printf("\n"); |
|
|
|
int lc = 0; |
|
int ll = 0; |
|
for (int i = 0; text[i] != '\0'; i++) { |
|
// printf("%d: %p = %c\n", i, text + i, text[i]); |
|
if (ll == 0) { |
|
if (lc == 0) { |
|
printf("/ "); |
|
} else if (lc == line_count) { |
|
printf("\\ "); |
|
} else { |
|
printf("| "); |
|
} |
|
} |
|
|
|
if (text[i] == '\n') { |
|
|
|
for (;ll <= width; ll++) { |
|
printf(" "); |
|
} |
|
|
|
if (lc == 0) { |
|
printf(" \\\n"); |
|
} else if (lc == line_count) { |
|
printf(" /\n"); |
|
} else { |
|
printf(" |\n"); |
|
} |
|
|
|
ll = -1; |
|
lc++; |
|
} else { |
|
printf("%c", text[i]); |
|
} |
|
ll++; |
|
} |
|
|
|
for (;ll <= width; ll++) { |
|
printf(" "); |
|
} |
|
|
|
if (lc == 0) { |
|
printf(" \\\n"); |
|
} else if (lc == line_count ) { |
|
printf(" /\n"); |
|
} else if (lc == line_count ) { |
|
; |
|
} else { |
|
printf(" |\n"); |
|
} |
|
|
|
printf(" "); |
|
for (int i = 0; i < width + 3; i ++) { |
|
printf("-"); |
|
} |
|
} |
|
|
|
void print_single_line_bubble(char *text, int len) { |
|
printf(" "); |
|
for (int i = 0; i < len + 1; i++) { |
|
printf("_"); |
|
} |
|
|
|
printf("\n<"); |
|
printf(" %s", text); |
|
printf(">\n"); |
|
|
|
printf(" "); |
|
|
|
for (int i = 0; i < len + 1; i++) { |
|
printf("-"); |
|
} |
|
printf("\n"); |
|
|
|
} |
|
|
|
int replace_tabs(char **rtext) { |
|
char *text = *rtext; |
|
size_t len = strlen(text); |
|
|
|
char *tab = strstr(text, "\t"); |
|
int numtabs = 0; |
|
|
|
while (tab != NULL) { |
|
numtabs++; |
|
tab = strstr(tab, "\t"); |
|
} |
|
|
|
if (numtabs == 0) { |
|
return 0; |
|
} |
|
|
|
len += 3 * numtabs; |
|
char * result = malloc(len); |
|
|
|
int resulti = 0; |
|
for (int i = 0; i < len; i++) { |
|
if (text[i] == '\t') { |
|
for (int j = 0; j < 4; j++) { |
|
result[resulti++] = ' '; |
|
} |
|
} else { |
|
result[resulti++] = text[i]; |
|
} |
|
} |
|
|
|
free(text); |
|
*rtext = result; |
|
|
|
return numtabs; |
|
|
|
}; |
|
|
|
void print_speech(char *text, int width) { |
|
|
|
int len = strlen(text); |
|
|
|
if (len <= width) { |
|
print_single_line_bubble(text, len); |
|
return; |
|
} |
|
|
|
width -= 2; // for margins |
|
|
|
wrap_text(text, width); |
|
|
|
int line_count = 0; |
|
for (int i = 0; text[i] != '\0'; i++) { |
|
if (text[i] == '\n') { |
|
line_count++; |
|
} |
|
} |
|
|
|
print_bubble(text, width, line_count); |
|
|
|
printf("\n"); |
|
|
|
} |
|
|
|
struct Cow { |
|
char *eyes; |
|
char *tongue; |
|
char *thoughts; |
|
char *cow; |
|
}; |
|
|
|
void print_cow(struct Cow *cow) { |
|
char default_cow[] = |
|
" \\ ^__^\n" \ |
|
" \\ (oo)\\_______\n" \ |
|
" (__)\\ )\\/\\\n" \ |
|
" ||----w |\n" \ |
|
" || ||\n" ; |
|
char default_eyes[] = "oo"; |
|
char default_thoughts[] = "\\"; |
|
|
|
if (!cow->eyes) { |
|
cow->eyes = default_eyes; |
|
} |
|
if (!cow->thoughts) { |
|
cow->thoughts = default_thoughts; |
|
} |
|
if (!cow->cow) { |
|
cow->cow = default_cow; |
|
} |
|
// TODO: write a replace string function to support cowfile-like |
|
// substitution of features for $eyes, $tongue, and $thoughts into |
|
// default_cow |
|
|
|
printf("%s", default_cow); |
|
} |
|
|
|
int main(int argc, char** argv) { |
|
|
|
char *text = get_text(argc, argv); |
|
|
|
if (!text) { |
|
return 1; |
|
} |
|
|
|
print_speech(text, 40); |
|
|
|
struct Cow *cow = calloc(1, sizeof(struct Cow)); |
|
print_cow(cow); |
|
|
|
}
|
|
|