change bug-reporting address
[platform/upstream/coreutils.git] / src / seq.c
1 /* seq - print sequence of numbers to standard output.
2    Copyright (C) 94, 1995, 96 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 (default 1) to LAST, moving by STEP (default 1).\n\
92 \n\
93   -f, --format FORMAT      use printf(3) style FORMAT (default: %%g)\n\
94   -s, --separator STRING   use STRING for separating 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 FIRST, INCREMENT, and LAST are interpreted as floating point values.\n\
100 INCREMENT should be positive if FIRST is smaller than LAST, and negative\n\
101 otherwise.  When given, the FORMAT argument must contain exactly one of\n\
102 the printf-style, floating point output formats %%e, %%f, or %%g.\n\
103 "));
104       puts (_("\nReport bugs to sh-utils-bugs@gnu.ai.mit.edu"));
105     }
106   exit (status);
107 }
108
109 int
110 main (int argc, char **argv)
111 {
112   int errs;
113   int optc;
114   int step_is_set;
115
116   program_name = argv[0];
117   setlocale (LC_ALL, "");
118   bindtextdomain (PACKAGE, LOCALEDIR);
119   textdomain (PACKAGE);
120
121   equal_width = 0;
122   format_str = NULL;
123   separator = "\n";
124   first = 1.0;
125   step_is_set = 0;
126
127   /* We have to handle negative numbers in the command line but this
128      conflicts with the command line arguments.  So the getopt mode is
129      REQUIRE_ORDER (the '+' in the format string) and it abort on the
130      first non-option or negative number.  */
131   while ((optc = getopt_long (argc, argv, "+0123456789f:s:w", long_options,
132                               (int *) 0)) != EOF)
133     {
134       if ('0' <= optc && optc <= '9')
135         {
136           /* means negative number */
137           break;
138         }
139
140       switch (optc)
141         {
142         case 0:
143           break;
144
145         case 'f':
146           format_str = optarg;
147           break;
148
149         case 's':
150           separator = optarg;
151           break;
152
153         case 'w':
154           equal_width = 1;
155           break;
156
157         default:
158           usage (1);
159           /* NOTREACHED */
160         }
161     }
162
163   if (show_version)
164     {
165       printf ("seq (%s) %s\n", GNU_PACKAGE, VERSION);
166       exit (0);
167     }
168
169   if (show_help)
170     {
171       usage (0);
172       /* NOTREACHED */
173     }
174
175   if (optind >= argc)
176     {
177       error (0, 0, _("too few arguments"));
178       usage (1);
179       /* NOTREACHED */
180     }
181   last = scan_double_arg (argv[optind++]);
182
183   if (optind < argc)
184     {
185       first = last;
186       last = scan_double_arg (argv[optind++]);
187
188       if (optind < argc)
189         {
190           step = last;
191           step_is_set = 1;
192           last = scan_double_arg (argv[optind++]);
193
194           if (optind < argc)
195             {
196               usage (1);
197               /* NOTREACHED */
198             }
199         }
200     }
201
202   if (format_str != NULL && equal_width)
203     {
204       error (0, 0, _("\
205 format string may not be specified when printing equal width strings"));
206       usage (1);
207     }
208
209   if (!step_is_set)
210     {
211       step = first <= last ? 1.0 : -1.0;
212     }
213
214   if (format_str != NULL)
215     {
216       if (!check_format (format_str))
217         {
218           error (0, 0, _("invalid format string: `%s'"), format_str);
219           usage (1);
220         }
221     }
222   else
223     {
224       if (equal_width)
225         format_str = get_width_format ();
226       else
227         format_str = "%g";
228     }
229
230   errs = print_numbers (format_str);
231
232   exit (errs);
233   /* NOTREACHED */
234 }
235
236 /* Read a double value from the command line.
237    Return if the string is correct else signal error.  */
238
239 static double
240 scan_double_arg (const char *arg)
241 {
242   double ret_val;
243
244   if (xstrtod (arg, NULL, &ret_val))
245     {
246       error (0, 0, _("invalid floating point argument: %s"), arg);
247       usage (1);
248       /* NOTREACHED */
249     }
250
251   return ret_val;
252 }
253
254 /* Check whether the format string is valid for a single double
255    argument.
256    Return 0 if not, 1 if correct.  */
257
258 static int
259 check_format (const char *format_string)
260 {
261   while (*format_string != '\0')
262     {
263       if (*format_string == '%')
264         {
265           format_string++;
266           if (*format_string != '%')
267             break;
268         }
269
270       format_string++;
271     }
272   if (*format_string == '\0')
273     return 0;
274
275   format_string += strspn (format_string, "-+#0");
276   if (isdigit (*format_string))
277     {
278       format_string += strspn (format_string, "012345789");
279
280       if (*format_string == '.')
281         format_string += strspn (++format_string, "0123456789");
282     }
283
284   if (*format_string != 'e' && *format_string != 'f' &&
285       *format_string != 'g')
286     return 0;
287
288   format_string++;
289   while (*format_string != '\0')
290     {
291       if (*format_string == '%')
292         {
293           format_string++;
294           if (*format_string != '%')
295             return 0;
296         }
297
298       format_string++;
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 *format_str)
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 (format_str, 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 (format_str, 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 (format_str, 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 }