Include "closeout.h".
[platform/upstream/coreutils.git] / src / seq.c
1 /* seq - print sequence of numbers to standard output.
2    Copyright (C) 1994-2000 Free Software Foundation, Inc.
3
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2, or (at your option)
7    any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the Free Software Foundation,
16    Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
17
18 /* Written by Ulrich Drepper.  */
19
20 #include <config.h>
21 #include <getopt.h>
22 #include <math.h>
23 #include <stdio.h>
24 #include <sys/types.h>
25
26 #include "system.h"
27 #include "closeout.h"
28 #include "error.h"
29 #include "xstrtol.h"
30 #include "xstrtod.h"
31
32 /* The official name of this program (e.g., no `g' prefix).  */
33 #define PROGRAM_NAME "seq"
34
35 #define AUTHORS "Ulrich Drepper"
36
37 /* If nonzero print all number with equal width.  */
38 static int equal_width;
39
40 /* The name that this program was run with.  */
41 char *program_name;
42
43 /* The string used to separate two numbers.  */
44 static char *separator;
45
46 /* The string output after all numbers have been output.
47    Usually "\n" or "\0".  */
48 /* FIXME: make this an option.  */
49 static char *terminator = "\n";
50
51 /* The representation of the decimal point in the current locale.
52    Always "." if the localeconv function is not supported.  */
53 static char *decimal_point = ".";
54
55 /* The starting number.  */
56 static double first;
57
58 /* The increment.  */
59 static double step;
60
61 /* The last number.  */
62 static double last;
63
64 static struct option const long_options[] =
65 {
66   { "equal-width", no_argument, NULL, 'w'},
67   { "format", required_argument, NULL, 'f'},
68   { "separator", required_argument, NULL, 's'},
69   {GETOPT_HELP_OPTION_DECL},
70   {GETOPT_VERSION_OPTION_DECL},
71   { NULL, 0, NULL, 0}
72 };
73
74 void
75 usage (int status)
76 {
77   if (status != 0)
78     fprintf (stderr, _("Try `%s --help' for more information.\n"),
79              program_name);
80   else
81     {
82       printf (_("\
83 Usage: %s [OPTION]... LAST\n\
84   or:  %s [OPTION]... FIRST LAST\n\
85   or:  %s [OPTION]... FIRST INCREMENT LAST\n\
86 "), program_name, program_name, program_name);
87       printf (_("\
88 Print numbers from FIRST to LAST, in steps of INCREMENT.\n\
89 \n\
90   -f, --format FORMAT      use printf(3) style FORMAT (default: %%g)\n\
91   -s, --separator STRING   use STRING to separate numbers (default: \\n)\n\
92   -w, --equal-width        equalize width by padding with leading zeroes\n\
93       --help               display this help and exit\n\
94       --version            output version information and exit\n\
95 \n\
96 If FIRST or INCREMENT is omitted, it defaults to 1.\n\
97 FIRST, INCREMENT, and LAST are interpreted as floating point values.\n\
98 INCREMENT should be positive if FIRST is smaller than LAST, and negative\n\
99 otherwise.  When given, the FORMAT argument must contain exactly one of\n\
100 the printf-style, floating point output formats %%e, %%f, %%g\n\
101 "));
102       puts (_("\nReport bugs to <bug-sh-utils@gnu.org>."));
103     }
104   exit (status);
105 }
106
107 /* Read a double value from the command line.
108    Return if the string is correct else signal error.  */
109
110 static double
111 scan_double_arg (const char *arg)
112 {
113   double ret_val;
114
115   if (xstrtod (arg, NULL, &ret_val))
116     {
117       error (0, 0, _("invalid floating point argument: %s"), arg);
118       usage (1);
119     }
120
121   return ret_val;
122 }
123
124 /* Check whether the format string is valid for a single `double'
125    argument.  Return 0 if not, 1 if correct. */
126
127 static int
128 valid_format (const char *fmt)
129 {
130   while (*fmt != '\0')
131     {
132       if (*fmt == '%')
133         {
134           fmt++;
135           if (*fmt != '%')
136             break;
137         }
138
139       fmt++;
140     }
141   if (*fmt == '\0')
142     return 0;
143
144   fmt += strspn (fmt, "-+#0");
145   if (ISDIGIT (*fmt))
146     {
147       fmt += strspn (fmt, "0123456789");
148
149       if (*fmt == '.')
150         fmt += strspn (++fmt, "0123456789");
151     }
152
153   if (!(*fmt == 'e' || *fmt == 'f' || *fmt == 'g'))
154     return 0;
155
156   fmt++;
157   while (*fmt != '\0')
158     {
159       if (*fmt == '%')
160         {
161           fmt++;
162           if (*fmt != '%')
163             return 0;
164         }
165
166       fmt++;
167     }
168
169   return 1;
170 }
171
172 /* Actually print the sequence of numbers in the specified range, with the
173    given or default stepping and format.  */
174 static int
175 print_numbers (const char *fmt)
176 {
177   if (first > last)
178     {
179       int i;
180
181       if (step >= 0)
182         {
183           error (0, 0,
184                  _("when the starting value is larger than the limit,\n\
185 the increment must be negative"));
186           usage (1);
187         }
188
189       printf (fmt, first);
190       for (i = 1; /* empty */; i++)
191         {
192           double x = first + i * step;
193
194           if (x < last)
195             break;
196
197           fputs (separator, stdout);
198           printf (fmt, x);
199         }
200     }
201   else
202     {
203       int i;
204
205       if (step <= 0)
206         {
207           error (0, 0,
208                  _("when the starting value is smaller than the limit,\n\
209 the increment must be positive"));
210           usage (1);
211         }
212
213       printf (fmt, first);
214       for (i = 1; /* empty */; i++)
215         {
216           double x = first + i * step;
217
218           if (x > last)
219             break;
220
221           fputs (separator, stdout);
222           printf (fmt, x);
223         }
224     }
225   fputs (terminator, stdout);
226
227   return 0;
228 }
229
230 #if HAVE_RINT && HAVE_MODF && HAVE_FLOOR
231
232 /* Return a printf-style format string with which all selected numbers
233    will format to strings of the same width.  */
234
235 static char *
236 get_width_format ()
237 {
238   static char buffer[256];
239   int full_width;
240   int frac_width;
241   int width1, width2;
242   double max_val;
243   double min_val;
244   double temp;
245
246   if (first > last)
247     {
248       min_val = first - step * floor ((first - last) / step);
249       max_val = first;
250     }
251   else
252     {
253       min_val = first;
254       max_val = first + step * floor ((last - first) / step);
255     }
256
257   sprintf (buffer, "%g", rint (max_val));
258   if (buffer[strspn (buffer, "0123456789")] != '\0')
259     return "%g";
260   width1 = strlen (buffer);
261
262   if (min_val < 0.0)
263     {
264       double int_min_val = rint (min_val);
265       sprintf (buffer, "%g", int_min_val);
266       if (buffer[strspn (buffer, "-0123456789")] != '\0')
267         return "%g";
268       /* On some systems, `seq -w -.1 .1 .1' results in buffer being `-0'.
269          On others, it is just `0'.  The former results in better output.  */
270       width2 = (int_min_val == 0 ? 2 : strlen (buffer));
271
272       width1 = width1 > width2 ? width1 : width2;
273     }
274   full_width = width1;
275
276   sprintf (buffer, "%g", 1.0 + modf (fabs (min_val), &temp));
277   width1 = strlen (buffer);
278   if (width1 == 1)
279     width1 = 0;
280   else
281     {
282       if (buffer[0] != '1'
283           /* FIXME: assumes that decimal_point is a single character
284              string.  */
285           || buffer[1] != decimal_point[0]
286           || buffer[2 + strspn (&buffer[2], "0123456789")] != '\0')
287         return "%g";
288       width1 -= 2;
289     }
290
291   sprintf (buffer, "%g", 1.0 + modf (fabs (step), &temp));
292   width2 = strlen (buffer);
293   if (width2 == 1)
294     width2 = 0;
295   else
296     {
297       if (buffer[0] != '1'
298           /* FIXME: assumes that decimal_point is a single character
299              string.  */
300           || buffer[1] != decimal_point[0]
301           || buffer[2 + strspn (&buffer[2], "0123456789")] != '\0')
302         return "%g";
303       width2 -= 2;
304     }
305   frac_width = width1 > width2 ? width1 : width2;
306
307   if (frac_width)
308     sprintf (buffer, "%%0%d.%df", full_width + 1 + frac_width, frac_width);
309   else
310     sprintf (buffer, "%%0%dg", full_width);
311
312   return buffer;
313 }
314
315 #else   /* one of the math functions rint, modf, floor is missing.  */
316
317 static char *
318 get_width_format (void)
319 {
320   /* We cannot compute the needed information to determine the correct
321      answer.  So we simply return a value that works for all cases.  */
322   return "%g";
323 }
324
325 #endif
326
327 int
328 main (int argc, char **argv)
329 {
330   int errs;
331   int optc;
332   int step_is_set;
333
334   /* The printf(3) format used for output.  */
335   char *format_str = NULL;
336
337   program_name = argv[0];
338   setlocale (LC_ALL, "");
339   bindtextdomain (PACKAGE, LOCALEDIR);
340   textdomain (PACKAGE);
341
342   equal_width = 0;
343   separator = "\n";
344   first = 1.0;
345   step_is_set = 0;
346
347   /* Figure out the locale's idea of a decimal point.  */
348 #if HAVE_LOCALECONV
349   {
350     struct lconv *locale;
351
352     locale = localeconv ();
353     /* Paranoia.  */
354     if (locale && locale->decimal_point && locale->decimal_point[0] != '\0')
355       decimal_point = locale->decimal_point;
356   }
357 #endif
358
359   /* We have to handle negative numbers in the command line but this
360      conflicts with the command line arguments.  So explicitly check first
361      whether the next argument looks like a negative number.  */
362   while (optind < argc)
363     {
364       if (argv[optind][0] == '-'
365           && ((optc = argv[optind][1]) == decimal_point[0]
366               || ISDIGIT (optc)))
367         {
368           /* means negative number */
369           break;
370         }
371
372       optc = getopt_long (argc, argv, "+f:s:w", long_options, NULL);
373       if (optc == -1)
374         break;
375
376       switch (optc)
377         {
378         case 0:
379           break;
380
381         case 'f':
382           format_str = optarg;
383           break;
384
385         case 's':
386           separator = optarg;
387           break;
388
389         case 'w':
390           equal_width = 1;
391           break;
392
393         case_GETOPT_HELP_CHAR;
394
395         case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
396
397         default:
398           usage (1);
399         }
400     }
401
402   if (argc - optind < 1)
403     {
404       error (0, 0, _("too few arguments"));
405       usage (1);
406     }
407
408   if (3 < argc - optind)
409     {
410       error (0, 0, _("too many arguments"));
411       usage (1);
412     }
413
414   if (format_str && !valid_format (format_str))
415     {
416       error (0, 0, _("invalid format string: `%s'"), format_str);
417       usage (1);
418     }
419
420   last = scan_double_arg (argv[optind++]);
421
422   if (optind < argc)
423     {
424       first = last;
425       last = scan_double_arg (argv[optind++]);
426
427       if (optind < argc)
428         {
429           step = last;
430           step_is_set = 1;
431           last = scan_double_arg (argv[optind++]);
432
433         }
434     }
435
436   if (format_str != NULL && equal_width)
437     {
438       error (0, 0, _("\
439 format string may not be specified when printing equal width strings"));
440       usage (1);
441     }
442
443   if (!step_is_set)
444     {
445       step = first <= last ? 1.0 : -1.0;
446     }
447
448   if (format_str == NULL)
449     {
450       if (equal_width)
451         format_str = get_width_format ();
452       else
453         format_str = "%g";
454     }
455
456   errs = print_numbers (format_str);
457
458   close_stdout ();
459
460   exit (errs);
461 }