File: nn/bytes.c 1 #include <stdlib.h> 2 #include "bytes.h" 3 4 // new_slice is the constructor for type slice 5 slice new_slice(size_t cap) { 6 slice res; 7 res.cap = cap; 8 res.len = 0; 9 res.ptr = malloc(res.cap); 10 return res; 11 } 12 13 // advance updates a slice so it starts after the number of bytes given 14 inline void advance(slice* src, size_t n) { 15 src->ptr += n; 16 src->len -= n; 17 } 18 19 // first creates a slice ending at the number of bytes given 20 slice first(slice src, size_t n) { 21 src.len = n; 22 return src; 23 } 24 25 // append_byte does as it says, potentially reallocating the memory area 26 // backing the slice given 27 void append_byte(slice* s, unsigned char b) { 28 if (s->len < s->cap) { 29 // under capacity, so it's ok to append directly 30 s->ptr[s->len] = b; 31 s->len++; 32 return; 33 } 34 35 // slice is full, so double it and reallocate 36 s->cap *= 2; 37 s->ptr = realloc(s->ptr, s->cap); 38 39 // now append directly to the larger array 40 s->ptr[s->len] = b; 41 s->len++; 42 } 43 44 // find_lf returns the index of the first line-feed found, or a negative value 45 // on failure 46 long long int find_lf(slice s) { 47 for (size_t i = 0; i < s.len; i++) { 48 if (s.ptr[i] == '\n') { 49 return i; 50 } 51 } 52 return -1; 53 } 54 55 // find_digit returns the index of the first digit found, or a negative value 56 // on failure 57 long long int find_digit(slice s) { 58 for (size_t i = 0; i < s.len; i++) { 59 const unsigned char b = s.ptr[i]; 60 if ('0' <= b && b <= '9') { 61 return i; 62 } 63 } 64 return -1; 65 } 66 67 // find_non_digit returns the index of the first non-digit found, or a negative 68 // value on failure 69 long long int find_non_digit(slice s) { 70 for (size_t i = 0; i < s.len; i++) { 71 const unsigned char b = s.ptr[i]; 72 if (b < '0' || b > '9') { 73 return i; 74 } 75 } 76 return -1; 77 } File: nn/bytes.h 1 #ifndef _NN_BYTES_H_ 2 #define _NN_BYTES_H_ 3 4 #include <stddef.h> 5 6 // slice is a growable region of bytes in memory 7 typedef struct slice { 8 // ptr is the starting place of the region 9 unsigned char* ptr; 10 11 // len is how many bytes are currently being used 12 size_t len; 13 14 // cap is how many bytes the memory region has available 15 size_t cap; 16 } slice; 17 18 // new_slice is the constructor for type slice 19 slice new_slice(size_t cap); 20 21 // advance updates a slice so it starts after the number of bytes given 22 void advance(slice* src, size_t n); 23 24 // first creates a slice ending at the number of bytes given 25 slice first(slice src, size_t n); 26 27 // append_byte does as it says, potentially reallocating the memory area 28 // backing the slice given 29 void append_byte(slice* s, unsigned char b); 30 31 // find_lf returns the index of the first line-feed found, or a negative value 32 // on failure 33 long long int find_lf(slice s); 34 35 // find_digit returns the index of the first digit found, or a negative value 36 // on failure 37 long long int find_digit(slice s); 38 39 // find_non_digit returns the index of the first non-digit found, or a negative 40 // value on failure 41 long long int find_non_digit(slice s); 42 43 #endif File: nn/info.txt 1 nn [filenames...] 2 3 Nice Numbers is an app which renders the plain text it's given to make long 4 numbers much easier to read, by alternating 3-digit groups which are colored 5 using ANSI-codes with unstyled ones. 6 7 Unlike the common practice of inserting commas between 3-digit groups, this 8 alternative doesn't widen the original text, keeping any alignments the same. 9 10 All input is assumed to be UTF-8. File: nn/io.c 1 #include <stdbool.h> 2 #include <stdlib.h> 3 #include "bytes.h" 4 #include "io.h" 5 6 const unsigned char digits_style[] = "\x1b[38;5;245m"; 7 const unsigned char reset_style[] = "\x1b[0m"; 8 9 // new_bufreader is the constructor for type bufreader 10 bufreader new_bufreader(FILE* src, size_t cap) { 11 bufreader res; 12 res.cap = cap; 13 res.len = 0; 14 res.pos = 0; 15 res.src = src; 16 res.buf = malloc(res.cap); 17 return res; 18 } 19 20 // close_bufwriter deallocates the buffer 21 void close_bufreader(bufreader* r) { 22 free(r->buf); 23 r->buf = NULL; 24 r->len = 0; 25 } 26 27 // read_byte does as it says: check its return for the value EOF, before 28 // using it as the next byte 29 int read_byte(bufreader* r) { 30 if (r->pos < r->len) { 31 // inside current chunk 32 const unsigned char b = r->buf[r->pos]; 33 r->pos++; 34 return b; 35 } 36 37 // need to read the next block 38 r->pos = 0; 39 r->len = fread(r->buf, sizeof(unsigned char), r->cap, r->src); 40 if (r->len > 0) { 41 const unsigned char b = r->buf[r->pos]; 42 r->pos++; 43 return b; 44 } 45 46 // reached the end of data 47 return EOF; 48 } 49 50 // new_bufwriter is the constructor for type bufwriter 51 bufwriter new_bufwriter(FILE* dst, size_t cap) { 52 bufwriter res; 53 res.cap = cap; 54 res.done = false; 55 res.len = 0; 56 res.out = dst; 57 res.buf = malloc(res.cap); 58 return res; 59 } 60 61 // close_bufwriter ensures all output is shown and deallocates the buffer 62 void close_bufwriter(bufwriter* w) { 63 flush(w); 64 free(w->buf); 65 w->buf = NULL; 66 } 67 68 // flush does as it says: it empties the buffer after ensuring its bytes end 69 // on their intended destination 70 void flush(bufwriter* w) { 71 if (w->len > 0 && fwrite(w->buf, w->len, 1, w->out) < 1) { 72 w->done = true; 73 } 74 w->len = 0; 75 } 76 77 // write_bytes does as it says, minimizing the number of calls to fwrite 78 void write_bytes(bufwriter* w, const unsigned char* src, size_t len) { 79 if (w->len + len < w->cap) { 80 // all bytes fit into buffer 81 memcpy(w->buf + w->len, src, len); 82 w->len += len; 83 return; 84 } 85 86 // ensure current buffer bytes go out, before crossing strides 87 flush(w); 88 89 // emit all chunks striding beyond/at the buffer's capacity 90 for (; len >= w->cap; src += w->cap, len -= w->cap) { 91 if (fwrite(src, w->cap, 1, w->out) < 1) { 92 w->done = true; 93 return; 94 } 95 } 96 97 // now all, if any, remaining bytes will fit into the buffer 98 memcpy(w->buf, src, len); 99 w->len += len; 100 } 101 102 // write_byte does as it says 103 void write_byte(bufwriter* w, unsigned char b) { 104 if (w->len >= w->cap) { 105 flush(w); 106 } 107 w->buf[w->len] = b; 108 w->len++; 109 } 110 111 // restyle_digits renders a run of digits as alternating styled/unstyled runs 112 // of 3 digits, which greatly improves readability, and is the only purpose 113 // of this app; string is assumed to be all decimal digits 114 void restyle_digits(bufwriter* w, slice digits) { 115 if (digits.len < 4) { 116 // digit sequence is short, so emit it as is 117 write_bytes(w, digits.ptr, digits.len); 118 return; 119 } 120 121 // separate leading 0..2 digits which don't align with the 3-digit groups 122 size_t lead = digits.len % 3; 123 // emit leading digits unstyled, if there are any 124 write_bytes(w, digits.ptr, lead); 125 // the rest is guaranteed to have a length which is a multiple of 3 126 advance(&digits, lead); 127 128 // start with the alternate style, unless there were no leading digits 129 bool style = lead != 0; 130 131 while (digits.len > 0) { 132 if (style) { 133 write_bytes(w, digits_style, sizeof(digits_style) - 1); 134 write_bytes(w, digits.ptr, 3); 135 write_bytes(w, reset_style, sizeof(reset_style) - 1); 136 } else { 137 write_bytes(w, digits.ptr, 3); 138 } 139 140 advance(&digits, 3); 141 // alternate between styled and unstyled 3-digit groups 142 style = !style; 143 } 144 } 145 146 // restyle_line renders the line given, using ANSI-styles to make any long 147 // numbers in it more legible 148 void restyle_line(bufwriter* w, slice line) { 149 while (!w->done && line.len > 0) { 150 // find where/if the next run of digits starts 151 long int i = find_digit(line); 152 if (i >= 0) { 153 // write non-digits which precede with run of digits 154 write_bytes(w, line.ptr, i); 155 advance(&line, i); 156 157 // find where/if this run of digits ends 158 long int j = find_non_digit(line); 159 if (j >= 0) { 160 // emit digits in groups of 3, alternating styles 161 restyle_digits(w, first(line, j)); 162 advance(&line, j); 163 continue; 164 } 165 166 // run of digits ends with the line 167 restyle_digits(w, line); 168 return; 169 } 170 171 // no more digits in the rest of the line 172 write_bytes(w, line.ptr, line.len); 173 return; 174 } 175 } File: nn/io.h 1 #ifndef _NN_IO_H 2 #define _NN_IO_H 3 4 #include <stdbool.h> 5 #include <stdio.h> 6 #include <string.h> 7 #include "bytes.h" 8 9 // bufreader is a way to speed up reading data by reducing the frequency of 10 // data reads from the a data source, while still allowing reading 1 byte at 11 // a time 12 typedef struct bufreader { 13 // buf is the buffer, (re)filled periodically as needed 14 unsigned char* buf; 15 16 // len is how many buffer bytes are being used, out of its max capacity 17 size_t len; 18 19 // cap is the buffer's capacity, or the most bytes it can hold at once 20 size_t cap; 21 22 // pos is the current position, up to the current buffer length 23 size_t pos; 24 25 // src is the data source used to fill the buffer 26 FILE* src; 27 } bufreader; 28 29 // new_bufreader is the constructor for type bufreader 30 bufreader new_bufreader(FILE* src, size_t cap); 31 32 // close_bufreader deallocates the buffer 33 void close_bufreader(bufreader* r); 34 35 // read_byte does as it says: check its return for the value EOF, before 36 // using it as the next byte 37 int read_byte(bufreader* r); 38 39 // bufwriter is, as the name implies, a buffered-writer: when it's aimed at 40 // stdout, it considerably speeds up this app, as intended 41 typedef struct bufwriter { 42 // buf is the buffer proper 43 unsigned char* buf; 44 45 // len is how many bytes of the buffer are currently being used 46 size_t len; 47 48 // cap is the capacity of the buffer, or the most bytes it can hold 49 size_t cap; 50 51 // out is the destination of all that's written into the buffer 52 FILE* out; 53 54 // done signals when/if no more output is accepted at the destination 55 bool done; 56 } bufwriter; 57 58 // new_bufwriter is the constructor for type bufwriter 59 bufwriter new_bufwriter(FILE* dst, size_t cap); 60 61 // close_bufwriter ensures all output is shown and deallocates the buffer 62 void close_bufwriter(bufwriter* w); 63 64 // flush does as it says: it empties the buffer after ensuring its bytes end 65 // on their intended destination 66 void flush(bufwriter* w); 67 68 // write_bytes does as it says, minimizing the number of calls to fwrite 69 void write_bytes(bufwriter* w, const unsigned char* src, size_t len); 70 71 // write_byte does as it says 72 void write_byte(bufwriter* w, unsigned char b); 73 74 // restyle_line renders the line given, using ANSI-styles to make any long 75 // numbers in it more legible 76 void restyle_line(bufwriter* w, slice line); 77 78 #endif File: nn/main.c 1 #include <fcntl.h> 2 #include <stdbool.h> 3 #include <stddef.h> 4 #include <stdlib.h> 5 #include "bytes.h" 6 #include "io.h" 7 8 // handle_reader loops over input lines, restyling all digit-runs as more 9 // readable `nice numbers`, fulfilling the app's purpose 10 void handle_reader(bufwriter* w, FILE* src) { 11 unsigned char prev = 0; 12 bufreader r = new_bufreader(src, 32 * 1024); 13 slice line = new_slice(32 * 1024); 14 15 while (!w->done) { 16 int v = read_byte(&r); 17 if (v != EOF) { 18 // still more bytes to go 19 unsigned char b = v; 20 prev = b; 21 22 if (b != '\n') { 23 // no end of line yet 24 append_byte(&line, b); 25 continue; 26 } 27 28 // end of line 29 append_byte(&line, b); 30 restyle_line(w, line); 31 line.len = 0; 32 continue; 33 } 34 35 // input is over 36 break; 37 } 38 39 // don't forget the last line 40 restyle_line(w, line); 41 42 // ensure last output line ends with a line-feed since, at least on 43 // msys/windows, `less` hangs when lines with millions of symbols 44 // don't end with a lf 45 if (prev != '\n') { 46 write_byte(w, '\n'); 47 } 48 49 close_bufreader(&r); 50 free(line.ptr); 51 } 52 53 // handle_file handles data from the filename given; returns false only when 54 // the file can't be opened 55 bool handle_file(bufwriter* w, char* fname) { 56 FILE* f = fopen(fname, "rb"); 57 if (f == NULL) { 58 // ensure currently-buffered/deferred output shows up right now: not 59 // doing so may scramble results in the common case where stdout and 60 // stderr are the same, thus confusing users 61 flush(w); 62 63 fprintf(stderr, "\x1b[31mcan't open file named %s\x1b[0m\n", fname); 64 return false; 65 } 66 67 handle_reader(w, f); 68 fclose(f); 69 return true; 70 } 71 72 // run returns the number of errors 73 size_t run(int argc, char** argv) { 74 bufwriter w = new_bufwriter(stdout, 32 * 1024); 75 76 if (argc < 2) { 77 handle_reader(&w, stdin); 78 close_bufwriter(&w); 79 return 0; 80 } 81 82 size_t errors = 0; 83 for (size_t i = 1; i < argc && !w.done; i++) { 84 if (i > 1) { 85 // put an extra empty line between adjacent outputs 86 write_byte(&w, '\n'); 87 } 88 89 if (!handle_file(&w, argv[i])) { 90 errors++; 91 } 92 } 93 94 close_bufwriter(&w); 95 return errors; 96 } 97 98 int main(int argc, char** argv) { 99 #ifdef _WIN32 100 setmode(fileno(stdin), O_BINARY); 101 // ensure output lines end in LF instead of CRLF on windows 102 setmode(fileno(stdout), O_BINARY); 103 setmode(fileno(stderr), O_BINARY); 104 #endif 105 106 // disable automatic stdio buffering, in favor of explicit buffering 107 setvbuf(stdin, NULL, _IONBF, 0); 108 setvbuf(stdout, NULL, _IONBF, 0); 109 setvbuf(stderr, NULL, _IONBF, 0); 110 111 return run(argc, argv) == 0 ? 0 : 1; 112 }