ufold.c (6998B)
1 /* This program is licensed under the terms of GNU GPL v3 or (at your option) 2 * any later version. Copyright (C) 2021-2025 Страхиња Радић. 3 * See the file LICENSE for exact copyright and license details. */ 4 5 #include <errno.h> 6 #include <stdarg.h> 7 #include <stdio.h> 8 #include <stdint.h> 9 #include <stdlib.h> 10 #include <string.h> 11 12 #include "defs.h" 13 #include "utf8.h" 14 #include "version.h" 15 16 #define COPYRIGHT \ 17 (" This program is licensed under the terms of GNU GPL v3" \ 18 " or (at your option)\n" \ 19 " any later version. Copyright (C) 2021-2025 Strahinya Radich.\n" \ 20 " See the file LICENSE for exact copyright and license " \ 21 "details.") 22 23 const u32 U32_SPACE = (const u32)0x00000020; 24 const u32 U32_TAB = (const u32)0x00000009; 25 26 char filename[MAXPATH]; 27 28 int 29 usage(void) 30 { 31 printf("Usage:\t%s -h | --help | -V | --full-version | -v | --version\n" 32 "\t%s [-s | --spaces] [-w | --width=<cols>] [file]\n", 33 PROGRAMNAME, PROGRAMNAME); 34 return 0; 35 } 36 37 int 38 error(const int code, const char* fmt, ...) 39 { 40 char buf[MAXBUF]; 41 va_list args; 42 va_start(args, fmt); 43 vsnprintf(buf, sizeof(buf), fmt, args); 44 va_end(args); 45 fprintf(stderr, "%s:%s: %s\n", PROGRAMNAME, 46 *filename ? filename : "(stdin)", buf); 47 return code; 48 } 49 50 int 51 version(const int full) 52 { 53 printf("%s %s, built on %s\n", PROGRAMNAME, VERSION, DATE); 54 if (full) 55 puts(COPYRIGHT); 56 return 0; 57 } 58 59 int 60 starts_with(const char* s, const char* with) 61 { 62 while (*s && *s == *with) 63 { 64 if (*s != *with) 65 return 0; 66 s++; 67 with++; 68 } 69 return *with == 0; 70 } 71 72 void 73 fold_line_spaces(const u8* line, const int width) 74 { 75 u32* ptoken = NULL; 76 const u8* pline = line; 77 u32 uch = 0; 78 u32 token[MAXINPUTBUF]; 79 u8 u8ch[7]; 80 size_t u8ch_len = 0; 81 size_t delta = 0; 82 size_t len = 0; 83 size_t col = 0; 84 85 if (!line) 86 return; 87 88 /* 89 * Idea: 90 * 1. store characters in token until line is exhausted 91 * 2. when ' ' or end of line is encountered: 92 * 2,1, if token length > width, see if current column is 0: 93 * 2.1.1. if it isn't, output newline and go to 2.1.2. 94 * 2.1.2. (if it is) output chunks of length "width" until the 95 * end of token, then reset token 96 * 2.2. if token length <= width, see if token length + current 97 * column exceed width 98 * 2.2.1. if they do, output newline and go to 2.2.2. 99 * 2.2.2. (if they don't) output token 100 * 3. go to 1. unless line is exhausted 101 */ 102 103 *token = 0; 104 ptoken = token; 105 /* 1. */ 106 while (1) 107 { 108 u8_char_to_u32(&uch, pline, &len); 109 if (!len) 110 return; 111 *ptoken++ = uch; 112 pline += len; 113 114 /* 2. */ 115 if (uch == U32_SPACE || !uch) 116 { 117 if (ptoken - token > width) 118 { 119 /* 2.1. */ 120 if (col > 0) 121 { 122 /* 2.1.1. */ 123 printf("\n"); 124 col = 0; 125 } 126 127 /* 2.1.2. */ 128 *ptoken = 0; 129 ptoken = token; 130 while (*ptoken) 131 { 132 u8ch_len 133 = u32_char_to_u8(u8ch, *ptoken); 134 if (!u8ch_len) 135 return; 136 u8ch[u8ch_len] = 0; 137 138 if (*ptoken == U32_TAB) 139 delta = 8 - (col % 8); 140 else 141 delta = 1; 142 143 if (col + delta > width) 144 { 145 printf("\n"); 146 col = 0; 147 if (*ptoken == U32_TAB) 148 delta = 8; 149 else 150 delta = 1; 151 } 152 printf("%s", u8ch); 153 col += delta; 154 ptoken += u8ch_len; 155 } 156 *token = 0; 157 ptoken = token; 158 } 159 else 160 { 161 /* 2.2. */ 162 if (ptoken - token + col > width) 163 { 164 /* 2.2.1. */ 165 printf("\n"); 166 col = 0; 167 } 168 169 /* 2.2.2. */ 170 *ptoken = 0; 171 ptoken = token; 172 while (*ptoken) 173 { 174 u8ch_len 175 = u32_char_to_u8(u8ch, *ptoken); 176 if (!u8ch_len) 177 return; 178 u8ch[u8ch_len] = 0; 179 180 if (*ptoken == U32_TAB) 181 delta = 8 - (col % 8); 182 else 183 delta = 1; 184 185 printf("%s", u8ch); 186 col += delta; 187 ptoken++; 188 } 189 *token = 0; 190 ptoken = token; 191 } 192 } 193 194 /* 3. */ 195 if (!uch) 196 break; 197 } 198 } 199 200 void 201 fold_line_normal(const u8* line, const int width) 202 { 203 if (!line) 204 return; 205 206 const u8* pline = line; 207 const u8* pline_inner = NULL; 208 u32 uch; 209 u8 u8_char[7]; 210 u8* pu8_char = NULL; 211 size_t uch_len; 212 size_t delta = 0; 213 size_t col = 0; 214 215 while (*pline) 216 { 217 /* convert only to get byte length */ 218 u8_char_to_u32(&uch, pline, &uch_len); 219 if (!uch_len) 220 return; 221 222 pline_inner = pline; 223 pu8_char = u8_char; 224 while (*pline_inner && pline_inner < pline + uch_len) 225 *pu8_char++ = *pline_inner++; 226 *pu8_char = 0; 227 228 if (uch == U32_TAB) 229 delta = 8 - (col % 8); 230 else 231 delta = 1; 232 233 if (col + delta > width) 234 { 235 printf("\n"); 236 col = 0; 237 if (uch == U32_TAB) 238 delta = 8; 239 else 240 delta = 1; 241 } 242 243 printf("%s", u8_char); 244 col += delta; 245 pline += uch_len; 246 } 247 } 248 249 int 250 main(int argc, char** argv) 251 { 252 int argn = 1; 253 char* carg = NULL; 254 char** earg = NULL; 255 int spaces = 0; 256 int in_width = 0; 257 long width = 80; 258 char width_buf[MAXBUF]; 259 260 *filename = 0; 261 while (argn < argc) 262 { 263 carg = *(argv + argn); 264 if (!strcmp(carg, "-s") || !strcmp(carg, "--spaces")) 265 spaces = 1; 266 else if (!strcmp(carg, "-h") || !strcmp(carg, "--help")) 267 return usage(); 268 else if (!strcmp(carg, "-V") || !strcmp(carg, "--full-version")) 269 return version(1); 270 else if (!strcmp(carg, "-v") || !strcmp(carg, "--version")) 271 return version(0); 272 else if (!strcmp(carg, "-w")) 273 in_width = 1; 274 else if (starts_with(carg, "--width=")) 275 { 276 char* pcarg = carg + strlen("--width="); 277 strncpy(width_buf, pcarg, MAXBUF); 278 errno = 0; 279 earg = &pcarg; 280 width = strtol(width_buf, earg, 10); 281 if (errno == ERANGE || errno == EINVAL 282 || (**earg && *pcarg)) 283 return error(EINVAL, 284 "Invalid numerical value " 285 "for --width: %s", 286 pcarg); 287 in_width = 0; 288 } 289 else if (in_width) 290 { 291 errno = 0; 292 earg = &carg; 293 width = strtol(carg, earg, 10); 294 if (errno == ERANGE || errno == EINVAL 295 || (**earg && *carg)) 296 return error(EINVAL, 297 "Invalid numerical value " 298 "for -w: %s", 299 carg); 300 in_width = 0; 301 } 302 else if (starts_with(carg, "--width")) 303 return error(EINVAL, 304 "Parameter --width requires an " 305 "argument"); 306 else if (*carg == '-' && *(carg + 1) != 0) 307 return usage(); 308 else 309 strncpy(filename, carg, MAXPATH); 310 argn++; 311 } 312 313 if (width < 1) 314 return error(EINVAL, "Width too small"); 315 316 if (!*filename) 317 { 318 filename[0] = '-'; 319 filename[1] = 0; 320 } 321 322 FILE* input = NULL; 323 if (!*filename || (*filename == '-' && !*(filename + 1))) 324 input = stdin; 325 else 326 input = fopen(filename, "rt"); 327 if (!input) 328 return error(errno, "Cannot open file"); 329 330 char line[MAXINPUTBUF]; 331 while (!feof(input)) 332 { 333 if (!fgets(line, MAXINPUTBUF, input)) 334 { 335 if (!feof(input)) 336 return error(errno, "Error reading file"); 337 else 338 break; 339 } 340 char* eol = strchr(line, '\n'); 341 if (eol) 342 *eol = 0; 343 if (spaces) 344 fold_line_spaces((const u8*)line, width); 345 else 346 fold_line_normal((const u8*)line, width); 347 printf("\n"); 348 } 349 350 fclose(input); 351 return 0; 352 }