(check_format): Rename local, FORMAT_STRING, to avoid shadowing global.
[platform/upstream/coreutils.git] / src / seq.c
1 /* seq - print sequence of numbers to standard output.
2    Copyright (C) 94, 95, 96, 1997 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 "error.h"
28 #include "xstrtod.h"
29
30 static double scan_double_arg __P ((const char *arg));
31 static int check_format __P ((const char *format_string));
32 static char *get_width_format __P ((void));
33 static int print_numbers __P ((const char *format_str));
34
35 /* If nonzero print all number with equal width.  */
36 static int equal_width;
37
38 /* The printf(3) format used for output.  */
39 static char *format_str;
40
41 /* The starting number.  */
42 static double first;
43
44 /* The name that this program was run with.  */
45 char *program_name;
46
47 /* The string used to separate two numbers.  */
48 static char *separator;
49
50 /* The string output after all numbers have been output.
51    Usually "\n" or "\0".  */
52 /* FIXME: make this an option.  */
53 static char *terminator = "\n";
54
55 /* If nonzero, display usage information and exit.  */
56 static int show_help;
57
58 /* If nonzero, print the version on standard output and exit.  */
59 static int show_version;
60
61 /* The increment.  */
62 static double step;
63
64 /* The last number.  */
65 static double last;
66
67 static struct option const long_options[] =
68 {
69   { "equal-width", no_argument, NULL, 'w'},
70   { "format", required_argument, NULL, 'f'},
71   { "help", no_argument, &show_help, 1},
72   { "separator", required_argument, NULL, 's'},
73   { "version", no_argument, &show_version, 1},
74   { NULL, 0, NULL, 0}
75 };
76
77 static void
78 usage (int status)
79 {
80   if (status != 0)
81     fprintf (stderr, _("Try `%s --help' for more information.\n"),
82                     program_name);
83   else
84     {
85       printf (_("\
86 Usage: %s [OPTION]... LAST\n\
87   or:  %s [OPTION]... FIRST LAST\n\
88   or:  %s [OPTION]... FIRST INCREMENT LAST\n\
89 "), program_name, program_name, program_name);
90       printf (_("\
91 Print numbers from FIRST to LAST, in steps of INCREMENT.\n\
92 \n\
93   -f, --format FORMAT      use printf(3) style FORMAT (default: %%g)\n\
94   -s, --separator STRING   use STRING to separate numbers (default: \\n)\n\
95   -w, --equal-width        equalize width by padding with leading zeroes\n\
96       --help               display this help and exit\n\
97       --version            output version information and exit\n\
98 \n\
99 If FIRST or INCREMENT is omitted, it defaults to 1.\n\
100 FIRST, INCREMENT, and LAST are interpreted as floating point values.\n\
101 INCREMENT should be positive if FIRST is smaller than LAST, and negative\n\
102 otherwise.  When given, the FORMAT argument must contain exactly one of\n\
103 the printf-style, floating point output formats %%e, %%f, or %%g.\n\
104 "));
105       puts (_("\nReport bugs to <sh-utils-bugs@gnu.ai.mit.edu>."));
106     }
107   exit (status);
108 }
109
110 int
111 main (int argc, char **argv)
112 {
113   int errs;
114   int optc;
115   int step_is_set;
116
117   program_name = argv[0];
118   setlocale (LC_ALL, "");
119   bindtextdomain (PACKAGE, LOCALEDIR);
120   textdomain (PACKAGE);
121
122   equal_width = 0;
123   format_str = NULL;
124   separator = "\n";
125   first = 1.0;
126   step_is_set = 0;
127
128   /* We have to handle negative numbers in the command line but this
129      conflicts with the command line arguments.  So the getopt mode is
130      REQUIRE_ORDER (the '+' in the format string) and it abort on the
131      first non-option or negative number.  */
132   while ((optc = getopt_long (argc, argv, "+0123456789f:s:w", long_options,
133                               NULL)) != -1)
134     {
135       if ('0' <= optc && optc <= '9')
136         {
137           /* means negative number */
138           break;
139         }
140
141       switch (optc)
142         {
143         case 0:
144           break;
145
146         case 'f':
147           format_str = optarg;
148           break;
149
150         case 's':
151           separator = optarg;
152           break;
153
154         case 'w':
155           equal_width = 1;
156           break;
157
158         default:
159           usage (1);
160           /* NOTREACHED */
161         }
162     }
163
164   if (show_version)
165     {
166       printf ("seq (%s) %s\n", GNU_PACKAGE, VERSION);
167       exit (0);
168     }
169
170   if (show_help)
171     {
172       usage (0);
173       /* NOTREACHED */
174     }
175
176   if (optind >= argc)
177     {
178       error (0, 0, _("too few arguments"));
179       usage (1);
180       /* NOTREACHED */
181     }
182   last = scan_double_arg (argv[optind++]);
183
184   if (optind < argc)
185     {
186       first = last;
187       last = scan_double_arg (argv[optind++]);
188
189       if (optind < argc)
190         {
191           step = last;
192           step_is_set = 1;
193           last = scan_double_arg (argv[optind++]);
194
195           if (optind < argc)
196             {
197               usage (1);
198               /* NOTREACHED */
199             }
200         }
201     }
202
203   if (format_str != NULL && equal_width)
204     {
205       error (0, 0, _("\
206 format string may not be specified when printing equal width strings"));
207       usage (1);
208     }
209
210   if (!step_is_set)
211     {
212       step = first <= last ? 1.0 : -1.0;
213     }
214
215   if (format_str != NULL)
216     {
217       if (!check_format (format_str))
218         {
219           error (0, 0, _("invalid format string: `%s'"), format_str);
220           usage (1);
221         }
222     }
223   else
224     {
225       if (equal_width)
226         format_str = get_width_format ();
227       else
228         format_str = "%g";
229     }
230
231   errs = print_numbers (format_str);
232
233   exit (errs);
234   /* NOTREACHED */
235 }
236
237 /* Read a double value from the command line.
238    Return if the string is correct else signal error.  */
239
240 static double
241 scan_double_arg (const char *arg)
242 {
243   double ret_val;
244
245   if (xstrtod (arg, NULL, &ret_val))
246     {
247       error (0, 0, _("invalid floating point argument: %s"), arg);
248       usage (1);
249       /* NOTREACHED */
250     }
251
252   return ret_val;
253 }
254
255 /* Check whether the format string is valid for a single double
256    argument.
257    Return 0 if not, 1 if correct.  */
258
259 static int
260 check_format (const char *fmt)
261 {
262   while (*fmt != '\0')
263     {
264       if (*fmt == '%')
265         {
266           fmt++;
267           if (*fmt != '%')
268             break;
269         }
270
271       fmt++;
272     }
273   if (*fmt == '\0')
274     return 0;
275
276   fmt += strspn (fmt, "-+#0");
277   if (isdigit (*fmt))
278     {
279       fmt += strspn (fmt, "012345789");
280
281       if (*fmt == '.')
282         fmt += strspn (++fmt, "0123456789");
283     }
284
285   if (*fmt != 'e' && *fmt != 'f' && *fmt != 'g')
286     return 0;
287
288   fmt++;
289   while (*fmt != '\0')
290     {
291       if (*fmt == '%')
292         {
293           fmt++;
294           if (*fmt != '%')
295             return 0;
296         }
297
298       fmt++;
299     }
300
301   return 1;
302 }
303
304 #if defined (HAVE_RINT) && defined (HAVE_MODF) && defined (HAVE_FLOOR)
305
306 /* Return a printf-style format string with which all selected numbers
307    will format to strings of the same width.  */
308
309 static char *
310 get_width_format ()
311 {
312   static char buffer[256];
313   int full_width;
314   int frac_width;
315   int width1, width2;
316   double max_val;
317   double min_val;
318   double temp;
319
320   if (first > last)
321     {
322       min_val = first - step * floor ((first - last) / step);
323       max_val = first;
324     }
325   else
326     {
327       min_val = first;
328       max_val = first + step * floor ((last - first) / step);
329     }
330
331   sprintf (buffer, "%g", rint (max_val));
332   if (buffer[strspn (buffer, "0123456789")] != '\0')
333     return "%g";
334   width1 = strlen (buffer);
335
336   if (min_val < 0.0)
337     {
338       sprintf (buffer, "%g", rint (min_val));
339       if (buffer[strspn (buffer, "-0123456789")] != '\0')
340         return "%g";
341       width2 = strlen (buffer);
342
343       width1 = width1 > width2 ? width1 : width2;
344     }
345   full_width = width1;
346
347   sprintf (buffer, "%g", 1.0 + modf (min_val, &temp));
348   width1 = strlen (buffer);
349   if (width1 == 1)
350     width1 = 0;
351   else
352     {
353       if (buffer[0] != '1' || buffer[1] != '.' ||
354           buffer[2 + strspn (&buffer[2], "0123456789")] != '\0')
355         return "%g";
356       width1 -= 2;
357     }
358
359   sprintf (buffer, "%g", 1.0 + modf (step, &temp));
360   width2 = strlen (buffer);
361   if (width2 == 1)
362     width2 = 0;
363   else
364     {
365       if (buffer[0] != '1' || buffer[1] != '.' ||
366           buffer[2 + strspn (&buffer[2], "0123456789")] != '\0')
367         return "%g";
368       width2 -= 2;
369     }
370   frac_width = width1 > width2 ? width1 : width2;
371
372   if (frac_width)
373     sprintf (buffer, "%%0%d.%df", full_width + 1 + frac_width, frac_width);
374   else
375     sprintf (buffer, "%%0%dg", full_width);
376
377   return buffer;
378 }
379
380 #else   /* one of the math functions rint, modf, floor is missing.  */
381
382 static char *
383 get_width_format (void)
384 {
385   /* We cannot compute the needed information to determine the correct
386      answer.  So we simply return a value that works for all cases.  */
387   return "%g";
388 }
389
390 #endif
391
392 /* Actually print the sequence of numbers in the specified range, with the
393    given or default stepping and format.  */
394 static int
395 print_numbers (const char *fmt)
396 {
397   if (first > last)
398     {
399       int i;
400
401       if (step >= 0)
402         {
403           error (0, 0,
404                  _("when the starting value is larger than the limit,\n\
405 the increment must be negative"));
406           usage (1);
407           /* NOTREACHED */
408         }
409
410       printf (fmt, first);
411       for (i = 1; /* empty */; i++)
412         {
413           double x = first + i * step;
414
415           if (x < last)
416             break;
417
418           fputs (separator, stdout);
419           printf (fmt, x);
420         }
421     }
422   else
423     {
424       int i;
425
426       if (step <= 0)
427         {
428           error (0, 0,
429                  _("when the starting value is smaller than the limit,\n\
430 the increment must be positive"));
431           usage (1);
432           /* NOTREACHED */
433         }
434
435       printf (fmt, first);
436       for (i = 1; /* empty */; i++)
437         {
438           double x = first + i * step;
439
440           if (x > last)
441             break;
442
443           fputs (separator, stdout);
444           printf (format_str, x);
445         }
446     }
447   fputs (terminator, stdout);
448
449   return 0;
450 }