File: tcatl.c 1 /* 2 The MIT License (MIT) 3 4 Copyright © 2020-2025 pacman64 5 6 Permission is hereby granted, free of charge, to any person obtaining a copy of 7 this software and associated documentation files (the “Software”), to deal 8 in the Software without restriction, including without limitation the rights to 9 use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 10 of the Software, and to permit persons to whom the Software is furnished to do 11 so, subject to the following conditions: 12 13 The above copyright notice and this permission notice shall be included in all 14 copies or substantial portions of the Software. 15 16 THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 SOFTWARE. 23 */ 24 25 /* 26 You can build this command-line app by running 27 28 cc -Wall -s -O3 -march=native -mtune=native -flto -o ./tcatl ./tcatl.c 29 */ 30 31 #include <stdbool.h> 32 #include <stdio.h> 33 #include <stdlib.h> 34 #include <string.h> 35 #include <unistd.h> 36 37 #ifdef _WIN32 38 #include <fcntl.h> 39 #include <windows.h> 40 #endif 41 42 #ifdef RED_ERRORS 43 #define ERROR_STYLE "\x1b[38;2;204;0;0m" 44 #ifdef __APPLE__ 45 #define ERROR_STYLE "\x1b[31m" 46 #endif 47 #define RESET_STYLE "\x1b[0m" 48 #else 49 #define ERROR_STYLE 50 #define RESET_STYLE 51 #endif 52 53 #define ERROR_LINE(MSG) (ERROR_STYLE MSG RESET_STYLE "\n") 54 55 const char* info = "" 56 "tcatl [filenames...]\n" 57 "\n" 58 "Title and Concatenate lines emits lines from all the named sources given,\n" 59 "preceding each file's contents with its name, using an ANSI reverse style.\n" 60 "\n" 61 "The name `-` stands for the standard input. When no names are given, the\n" 62 "standard input is used by default.\n" 63 ""; 64 65 // slice is a growable region of bytes in memory 66 typedef struct slice { 67 // ptr is the starting place of the region 68 unsigned char* ptr; 69 70 // len is how many bytes are currently being used 71 size_t len; 72 73 // cap is how many bytes the memory region has available 74 size_t cap; 75 } slice; 76 77 bool handle_reader_faster(FILE* w, FILE* r) { 78 unsigned char buf[32 * 1024]; 79 unsigned char last = '\n'; 80 81 while (!feof(w)) { 82 size_t len = fread(buf, sizeof(buf[0]), sizeof(buf), r); 83 if (len < 1) { 84 break; 85 } 86 87 fwrite(buf, 1, len, w); 88 last = buf[len - 1]; 89 } 90 91 if (last != '\n') { 92 fputc('\n', w); 93 } 94 return true; 95 } 96 97 // handle_reader skips leading UTF-8 BOMs (byte-order marks), and turns all 98 // CR-LF pairs into single LF bytes 99 bool handle_reader(FILE* w, FILE* r, slice* line, bool go_faster) { 100 if (go_faster) { 101 return handle_reader_faster(w, r); 102 } 103 104 while (!feof(w)) { 105 ssize_t len = getline((char**)&line->ptr, &line->cap, r); 106 if (line->ptr == NULL) { 107 fprintf(stderr, ERROR_LINE("out of memory")); 108 return false; 109 } 110 111 if (len < 0) { 112 break; 113 } 114 115 const bool has_lf = len >= 1 && line->ptr[len - 1] == '\n'; 116 fwrite(line->ptr, len, 1, w); 117 if (!has_lf) { 118 fputc('\n', w); 119 } 120 fflush(w); 121 } 122 123 return true; 124 } 125 126 // handle_file handles data from the filename given; returns false only when 127 // the file can't be opened 128 bool handle_file(FILE* w, const char* path, slice* line, bool go_faster) { 129 fprintf(w, "\x1b[7m%s\x1b[0m\n", path); 130 131 FILE* f = fopen(path, "rb"); 132 if (f == NULL) { 133 fprintf(stderr, ERROR_LINE("can't open file named '%s'"), path); 134 return false; 135 } 136 137 const bool ok = handle_reader(w, f, line, go_faster); 138 fclose(f); 139 return ok; 140 } 141 142 // run returns the number of errors 143 int run(char** args, size_t nargs, FILE* w, bool go_faster) { 144 size_t dashes = 0; 145 for (size_t i = 0; i < nargs; i++) { 146 if (args[i][0] == '-' && args[i][1] == 0) { 147 dashes++; 148 } 149 } 150 151 if (dashes > 1) { 152 const char* m = "can't use the standard input (dash) more than once"; 153 fprintf(stderr, ERROR_LINE("%s"), m); 154 return 1; 155 } 156 157 slice line; 158 line.ptr = NULL; 159 line.len = 0; 160 line.cap = 0; 161 162 if (!go_faster) { 163 line.cap = 32 * 1024; 164 line.ptr = malloc(line.cap); 165 if (line.ptr == NULL) { 166 fprintf(stderr, ERROR_LINE("out of memory")); 167 return 1; 168 } 169 } 170 171 size_t errors = 0; 172 for (size_t i = 0; i < nargs && !feof(w); i++) { 173 if (!go_faster && line.ptr == NULL) { 174 break; 175 } 176 177 if (args[i][0] == '-' && args[i][1] == 0) { 178 fputs("\x1b[7m-\x1b[0m\n", w); 179 if (!handle_reader(w, stdin, &line, go_faster)) { 180 errors++; 181 } 182 continue; 183 } 184 185 if (!handle_file(w, args[i], &line, go_faster)) { 186 errors++; 187 } 188 } 189 190 // use stdin when not given any filepaths 191 if (nargs < 1) { 192 fputs("\x1b[7m-\x1b[0m\n", w); 193 if (!handle_reader(w, stdin, &line, go_faster)) { 194 errors++; 195 } 196 } 197 198 free(line.ptr); 199 return errors; 200 } 201 202 int main(int argc, char** argv) { 203 #ifdef _WIN32 204 setmode(fileno(stdin), O_BINARY); 205 // ensure output lines end in LF instead of CRLF on windows 206 setmode(fileno(stdout), O_BINARY); 207 setmode(fileno(stderr), O_BINARY); 208 #endif 209 210 if (argc > 1) { 211 if ( 212 strcmp(argv[1], "-h") == 0 || 213 strcmp(argv[1], "-help") == 0 || 214 strcmp(argv[1], "--h") == 0 || 215 strcmp(argv[1], "--help") == 0 216 ) { 217 fprintf(stdout, "%s", info); 218 return 0; 219 } 220 } 221 222 size_t nargs = argc - 1; 223 char** args = argv + 1; 224 if (nargs > 0 && strcmp(args[0], "--") == 0) { 225 nargs--; 226 args++; 227 } 228 229 const bool go_faster = lseek(fileno(stdout), 0, SEEK_CUR) == 0; 230 if (go_faster) { 231 setvbuf(stdout, NULL, _IOFBF, 0); 232 } 233 return run(args, nargs, stdout, go_faster) == 0 ? 0 : 1; 234 }