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