File: timestamp.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 -O2 -o ./timestamp ./timestamp.c 29 */ 30 31 #include <fcntl.h> 32 #include <stdbool.h> 33 #include <stddef.h> 34 #include <stdio.h> 35 #include <stdlib.h> 36 #include <string.h> 37 #include <time.h> 38 39 #ifdef _WIN32 40 #include <windows.h> 41 #endif 42 43 #define STYLE_TIMESTAMP 44 45 // info is the message shown when this app is given any of its help options 46 const char* info = "" 47 "timestamp [options...]\n" 48 "\n" 49 "\n" 50 "Timestamp each line read from stdin with the time it was received.\n" 51 "\n" 52 "\n" 53 "Options, all of which can start with either 1 or 2 dashes:\n" 54 "\n" 55 "\n" 56 " -h show this help message\n" 57 " -help show this help message\n" 58 ""; 59 60 const char* line_memory_error_msg = 61 "" 62 "\x1b[31mcan't get memory for the line-scanner\x1b[0m\n"; 63 64 // slice is a growable region of bytes in memory 65 typedef struct slice { 66 // ptr is the starting place of the region 67 unsigned char* ptr; 68 69 // len is how many bytes are currently being used 70 size_t len; 71 72 // cap is how many bytes the memory region has available 73 size_t cap; 74 } slice; 75 76 // init_slice is the constructor for type slice 77 void init_slice(slice* s, size_t cap) { 78 s->ptr = malloc(cap); 79 s->len = 0; 80 s->cap = cap; 81 } 82 83 // EMIT_CONST abstracts emitting string constants without their final null byte 84 #define EMIT_CONST(w, x) fwrite(x, sizeof(x) - 1, 1, w) 85 86 void timestamp_line(FILE* w, slice line) { 87 char buf[32]; 88 time_t now = time(NULL); 89 struct tm* ymdhms = localtime(&now); 90 91 #ifdef STYLE_TIMESTAMP 92 EMIT_CONST(w, "\x1b[48;2;218;218;218m\x1b[38;2;0;95;153m"); 93 size_t len = strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", ymdhms); 94 fwrite(buf, len, 1, w); 95 EMIT_CONST(w, "\x1b[0m\t"); 96 #else 97 size_t len = strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", ymdhms); 98 fwrite(buf, len, 1, w); 99 putc('\t', w); 100 #endif 101 fwrite(line.ptr, line.len, 1, w); 102 putc('\n', w); 103 } 104 105 bool bom_start(slice s) { 106 const unsigned char* p = s.ptr; 107 return s.len >= 3 && p[0] == 0xef && p[1] == 0xbb && p[2] == 0xbf; 108 } 109 110 // handle_lines loops over input lines, restyling all digit-runs as more 111 // readable `nice numbers`, fulfilling the app's purpose 112 bool handle_lines(FILE* w, slice* line, FILE* src) { 113 slice trimmed; 114 trimmed.cap = 0; 115 116 for (size_t i = 0; !feof(w); i++) { 117 int len = getline((char**)&line->ptr, &line->cap, src); 118 if (len < 0) { 119 break; 120 } 121 if (line->ptr == NULL) { 122 fprintf(stderr, line_memory_error_msg); 123 exit(1); 124 } 125 126 line->len = len; 127 trimmed.ptr = line->ptr; 128 trimmed.len = line->len; 129 130 // get rid of leading UTF-8 BOM (byte-order mark) if 1st line has it 131 if (i == 0 && bom_start(trimmed)) { 132 trimmed.ptr += 3; 133 trimmed.len -= 3; 134 len = trimmed.len; 135 } 136 137 const unsigned char* p = trimmed.ptr; 138 // get rid of trailing line-feeds and CRLF end-of-line byte-pairs 139 if (len >= 2 && p[len - 2] == '\r' && p[len - 1] == '\n') { 140 trimmed.len -= 2; 141 } else if (len >= 1 && p[len - 1] == '\n') { 142 trimmed.len--; 143 } 144 145 timestamp_line(w, trimmed); 146 fflush(w); 147 } 148 149 return true; 150 } 151 152 // handle_file handles data from the filename given; returns false only when 153 // the file can't be opened 154 bool handle_file(FILE* w, slice* line, char* path) { 155 FILE* f = fopen(path, "rb"); 156 if (f == NULL) { 157 fprintf(stderr, "\x1b[31mcan't open file named %s\x1b[0m\n", path); 158 return false; 159 } 160 161 const bool ok = handle_lines(w, line, f); 162 fclose(f); 163 return ok; 164 } 165 166 // run returns the number of errors 167 int run(int argc, char** argv, FILE* w, slice* line) { 168 size_t errors = 0; 169 170 // use stdin when not given any filepaths 171 if (argc < 2) { 172 if (!handle_lines(w, line, stdin)) { 173 errors++; 174 } 175 return errors; 176 } 177 178 for (size_t i = 1; i < (size_t)argc && !feof(w); i++) { 179 if (argv[i][0] == '-' && argv[i][1] == 0) { 180 if (!handle_lines(w, line, stdin)) { 181 errors++; 182 } 183 continue; 184 } 185 186 if (!handle_file(w, line, argv[i])) { 187 errors++; 188 } 189 } 190 191 return errors; 192 } 193 194 // is_help_option simplifies control-flow for func main 195 bool is_help_option(char* s) { 196 return (s[0] == '-') && ( 197 strcmp(s, "-h") == 0 || strcmp(s, "-help") == 0 || 198 strcmp(s, "--h") == 0 || strcmp(s, "--help") == 0 199 ); 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 // handle any of the help options, if given 211 if (argc > 1 && is_help_option(argv[1])) { 212 puts(info); 213 return 0; 214 } 215 216 slice line; 217 init_slice(&line, 32 * 1024); 218 if (line.ptr == NULL) { 219 fprintf(stderr, line_memory_error_msg); 220 return 1; 221 } 222 223 const int res = run(argc, argv, stdout, &line) == 0 ? 0 : 1; 224 free(line.ptr); 225 return res; 226 }