/usr.bin/nl/nl.c

https://bitbucket.org/freebsd/freebsd-head/ · C · 409 lines · 298 code · 44 blank · 67 comment · 87 complexity · ba18b54d2a258bc9df6611c2c131fb60 MD5 · raw file

  1. /*-
  2. * Copyright (c) 1999 The NetBSD Foundation, Inc.
  3. * All rights reserved.
  4. *
  5. * This code is derived from software contributed to The NetBSD Foundation
  6. * by Klaus Klein.
  7. *
  8. * Redistribution and use in source and binary forms, with or without
  9. * modification, are permitted provided that the following conditions
  10. * are met:
  11. * 1. Redistributions of source code must retain the above copyright
  12. * notice, this list of conditions and the following disclaimer.
  13. * 2. Redistributions in binary form must reproduce the above copyright
  14. * notice, this list of conditions and the following disclaimer in the
  15. * documentation and/or other materials provided with the distribution.
  16. *
  17. * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
  18. * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
  19. * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  20. * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
  21. * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  22. * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  23. * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  24. * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  25. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  26. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  27. * POSSIBILITY OF SUCH DAMAGE.
  28. */
  29. #include <sys/cdefs.h>
  30. #ifndef lint
  31. __COPYRIGHT(
  32. "@(#) Copyright (c) 1999\
  33. The NetBSD Foundation, Inc. All rights reserved.");
  34. __RCSID("$FreeBSD$");
  35. #endif
  36. #define _WITH_GETLINE
  37. #include <sys/types.h>
  38. #include <err.h>
  39. #include <errno.h>
  40. #include <limits.h>
  41. #include <locale.h>
  42. #include <regex.h>
  43. #include <stdio.h>
  44. #include <stdlib.h>
  45. #include <string.h>
  46. #include <unistd.h>
  47. #include <wchar.h>
  48. typedef enum {
  49. number_all, /* number all lines */
  50. number_nonempty, /* number non-empty lines */
  51. number_none, /* no line numbering */
  52. number_regex /* number lines matching regular expression */
  53. } numbering_type;
  54. struct numbering_property {
  55. const char * const name; /* for diagnostics */
  56. numbering_type type; /* numbering type */
  57. regex_t expr; /* for type == number_regex */
  58. };
  59. /* line numbering formats */
  60. #define FORMAT_LN "%-*d" /* left justified, leading zeros suppressed */
  61. #define FORMAT_RN "%*d" /* right justified, leading zeros suppressed */
  62. #define FORMAT_RZ "%0*d" /* right justified, leading zeros kept */
  63. #define FOOTER 0
  64. #define BODY 1
  65. #define HEADER 2
  66. #define NP_LAST HEADER
  67. static struct numbering_property numbering_properties[NP_LAST + 1] = {
  68. { .name = "footer", .type = number_none },
  69. { .name = "body", .type = number_nonempty },
  70. { .name = "header", .type = number_none }
  71. };
  72. #define max(a, b) ((a) > (b) ? (a) : (b))
  73. /*
  74. * Maximum number of characters required for a decimal representation of a
  75. * (signed) int; courtesy of tzcode.
  76. */
  77. #define INT_STRLEN_MAXIMUM \
  78. ((sizeof (int) * CHAR_BIT - 1) * 302 / 1000 + 2)
  79. static void filter(void);
  80. static void parse_numbering(const char *, int);
  81. static void usage(void);
  82. /*
  83. * Dynamically allocated buffer suitable for string representation of ints.
  84. */
  85. static char *intbuffer;
  86. /* delimiter characters that indicate the start of a logical page section */
  87. static char delim[2 * MB_LEN_MAX];
  88. static int delimlen;
  89. /*
  90. * Configurable parameters.
  91. */
  92. /* line numbering format */
  93. static const char *format = FORMAT_RN;
  94. /* increment value used to number logical page lines */
  95. static int incr = 1;
  96. /* number of adjacent blank lines to be considered (and numbered) as one */
  97. static unsigned int nblank = 1;
  98. /* whether to restart numbering at logical page delimiters */
  99. static int restart = 1;
  100. /* characters used in separating the line number and the corrsp. text line */
  101. static const char *sep = "\t";
  102. /* initial value used to number logical page lines */
  103. static int startnum = 1;
  104. /* number of characters to be used for the line number */
  105. /* should be unsigned but required signed by `*' precision conversion */
  106. static int width = 6;
  107. int
  108. main(int argc, char *argv[])
  109. {
  110. int c;
  111. long val;
  112. unsigned long uval;
  113. char *ep;
  114. size_t intbuffersize, clen;
  115. char delim1[MB_LEN_MAX] = { '\\' }, delim2[MB_LEN_MAX] = { ':' };
  116. size_t delim1len = 1, delim2len = 1;
  117. (void)setlocale(LC_ALL, "");
  118. while ((c = getopt(argc, argv, "pb:d:f:h:i:l:n:s:v:w:")) != -1) {
  119. switch (c) {
  120. case 'p':
  121. restart = 0;
  122. break;
  123. case 'b':
  124. parse_numbering(optarg, BODY);
  125. break;
  126. case 'd':
  127. clen = mbrlen(optarg, MB_CUR_MAX, NULL);
  128. if (clen == (size_t)-1 || clen == (size_t)-2)
  129. errc(EXIT_FAILURE, EILSEQ, NULL);
  130. if (clen != 0) {
  131. memcpy(delim1, optarg, delim1len = clen);
  132. clen = mbrlen(optarg + delim1len,
  133. MB_CUR_MAX, NULL);
  134. if (clen == (size_t)-1 ||
  135. clen == (size_t)-2)
  136. errc(EXIT_FAILURE, EILSEQ, NULL);
  137. if (clen != 0) {
  138. memcpy(delim2, optarg + delim1len,
  139. delim2len = clen);
  140. if (optarg[delim1len + clen] != '\0')
  141. errx(EXIT_FAILURE,
  142. "invalid delim argument -- %s",
  143. optarg);
  144. }
  145. }
  146. break;
  147. case 'f':
  148. parse_numbering(optarg, FOOTER);
  149. break;
  150. case 'h':
  151. parse_numbering(optarg, HEADER);
  152. break;
  153. case 'i':
  154. errno = 0;
  155. val = strtol(optarg, &ep, 10);
  156. if ((ep != NULL && *ep != '\0') ||
  157. ((val == LONG_MIN || val == LONG_MAX) && errno != 0))
  158. errx(EXIT_FAILURE,
  159. "invalid incr argument -- %s", optarg);
  160. incr = (int)val;
  161. break;
  162. case 'l':
  163. errno = 0;
  164. uval = strtoul(optarg, &ep, 10);
  165. if ((ep != NULL && *ep != '\0') ||
  166. (uval == ULONG_MAX && errno != 0))
  167. errx(EXIT_FAILURE,
  168. "invalid num argument -- %s", optarg);
  169. nblank = (unsigned int)uval;
  170. break;
  171. case 'n':
  172. if (strcmp(optarg, "ln") == 0) {
  173. format = FORMAT_LN;
  174. } else if (strcmp(optarg, "rn") == 0) {
  175. format = FORMAT_RN;
  176. } else if (strcmp(optarg, "rz") == 0) {
  177. format = FORMAT_RZ;
  178. } else
  179. errx(EXIT_FAILURE,
  180. "illegal format -- %s", optarg);
  181. break;
  182. case 's':
  183. sep = optarg;
  184. break;
  185. case 'v':
  186. errno = 0;
  187. val = strtol(optarg, &ep, 10);
  188. if ((ep != NULL && *ep != '\0') ||
  189. ((val == LONG_MIN || val == LONG_MAX) && errno != 0))
  190. errx(EXIT_FAILURE,
  191. "invalid startnum value -- %s", optarg);
  192. startnum = (int)val;
  193. break;
  194. case 'w':
  195. errno = 0;
  196. val = strtol(optarg, &ep, 10);
  197. if ((ep != NULL && *ep != '\0') ||
  198. ((val == LONG_MIN || val == LONG_MAX) && errno != 0))
  199. errx(EXIT_FAILURE,
  200. "invalid width value -- %s", optarg);
  201. width = (int)val;
  202. if (!(width > 0))
  203. errx(EXIT_FAILURE,
  204. "width argument must be > 0 -- %d",
  205. width);
  206. break;
  207. case '?':
  208. default:
  209. usage();
  210. /* NOTREACHED */
  211. }
  212. }
  213. argc -= optind;
  214. argv += optind;
  215. switch (argc) {
  216. case 0:
  217. break;
  218. case 1:
  219. if (freopen(argv[0], "r", stdin) == NULL)
  220. err(EXIT_FAILURE, "%s", argv[0]);
  221. break;
  222. default:
  223. usage();
  224. /* NOTREACHED */
  225. }
  226. /* Generate the delimiter sequence */
  227. memcpy(delim, delim1, delim1len);
  228. memcpy(delim + delim1len, delim2, delim2len);
  229. delimlen = delim1len + delim2len;
  230. /* Allocate a buffer suitable for preformatting line number. */
  231. intbuffersize = max((int)INT_STRLEN_MAXIMUM, width) + 1; /* NUL */
  232. if ((intbuffer = malloc(intbuffersize)) == NULL)
  233. err(EXIT_FAILURE, "cannot allocate preformatting buffer");
  234. /* Do the work. */
  235. filter();
  236. exit(EXIT_SUCCESS);
  237. /* NOTREACHED */
  238. }
  239. static void
  240. filter(void)
  241. {
  242. char *buffer;
  243. size_t buffersize;
  244. ssize_t linelen;
  245. int line; /* logical line number */
  246. int section; /* logical page section */
  247. unsigned int adjblank; /* adjacent blank lines */
  248. int consumed; /* intbuffer measurement */
  249. int donumber = 0, idx;
  250. adjblank = 0;
  251. line = startnum;
  252. section = BODY;
  253. buffer = NULL;
  254. buffersize = 0;
  255. while ((linelen = getline(&buffer, &buffersize, stdin)) > 0) {
  256. for (idx = FOOTER; idx <= NP_LAST; idx++) {
  257. /* Does it look like a delimiter? */
  258. if (delimlen * (idx + 1) > linelen)
  259. break;
  260. if (memcmp(buffer + delimlen * idx, delim,
  261. delimlen) != 0)
  262. break;
  263. /* Was this the whole line? */
  264. if (buffer[delimlen * (idx + 1)] == '\n') {
  265. section = idx;
  266. adjblank = 0;
  267. if (restart)
  268. line = startnum;
  269. goto nextline;
  270. }
  271. }
  272. switch (numbering_properties[section].type) {
  273. case number_all:
  274. /*
  275. * Doing this for number_all only is disputable, but
  276. * the standard expresses an explicit dependency on
  277. * `-b a' etc.
  278. */
  279. if (buffer[0] == '\n' && ++adjblank < nblank)
  280. donumber = 0;
  281. else
  282. donumber = 1, adjblank = 0;
  283. break;
  284. case number_nonempty:
  285. donumber = (buffer[0] != '\n');
  286. break;
  287. case number_none:
  288. donumber = 0;
  289. break;
  290. case number_regex:
  291. donumber =
  292. (regexec(&numbering_properties[section].expr,
  293. buffer, 0, NULL, 0) == 0);
  294. break;
  295. }
  296. if (donumber) {
  297. /* Note: sprintf() is safe here. */
  298. consumed = sprintf(intbuffer, format, width, line);
  299. (void)printf("%s",
  300. intbuffer + max(0, consumed - width));
  301. line += incr;
  302. } else {
  303. (void)printf("%*s", width, "");
  304. }
  305. (void)fputs(sep, stdout);
  306. (void)fwrite(buffer, linelen, 1, stdout);
  307. if (ferror(stdout))
  308. err(EXIT_FAILURE, "output error");
  309. nextline:
  310. ;
  311. }
  312. if (ferror(stdin))
  313. err(EXIT_FAILURE, "input error");
  314. free(buffer);
  315. }
  316. /*
  317. * Various support functions.
  318. */
  319. static void
  320. parse_numbering(const char *argstr, int section)
  321. {
  322. int error;
  323. char errorbuf[NL_TEXTMAX];
  324. switch (argstr[0]) {
  325. case 'a':
  326. numbering_properties[section].type = number_all;
  327. break;
  328. case 'n':
  329. numbering_properties[section].type = number_none;
  330. break;
  331. case 't':
  332. numbering_properties[section].type = number_nonempty;
  333. break;
  334. case 'p':
  335. /* If there was a previous expression, throw it away. */
  336. if (numbering_properties[section].type == number_regex)
  337. regfree(&numbering_properties[section].expr);
  338. else
  339. numbering_properties[section].type = number_regex;
  340. /* Compile/validate the supplied regular expression. */
  341. if ((error = regcomp(&numbering_properties[section].expr,
  342. &argstr[1], REG_NEWLINE|REG_NOSUB)) != 0) {
  343. (void)regerror(error,
  344. &numbering_properties[section].expr,
  345. errorbuf, sizeof (errorbuf));
  346. errx(EXIT_FAILURE,
  347. "%s expr: %s -- %s",
  348. numbering_properties[section].name, errorbuf,
  349. &argstr[1]);
  350. }
  351. break;
  352. default:
  353. errx(EXIT_FAILURE,
  354. "illegal %s line numbering type -- %s",
  355. numbering_properties[section].name, argstr);
  356. }
  357. }
  358. static void
  359. usage(void)
  360. {
  361. (void)fprintf(stderr,
  362. "usage: nl [-p] [-b type] [-d delim] [-f type] [-h type] [-i incr] [-l num]\n"
  363. " [-n format] [-s sep] [-v startnum] [-w width] [file]\n");
  364. exit(EXIT_FAILURE);
  365. }