/*++ Copyright (c) 2016 Minoca Corp. All Rights Reserved Module Name: seq.c Abstract: This module implements the seq (sequence) utility, which simply prints out a sequence of numbers. Author: Evan Green 9-May-2016 Environment: POSIX --*/ // // ------------------------------------------------------------------- Includes // #include #include #include #include #include #include #include #include "swlib.h" // // ---------------------------------------------------------------- Definitions // #define SEQ_VERSION_MAJOR 1 #define SEQ_VERSION_MINOR 0 #define SEQ_USAGE \ "usage: seq [options] [[first] increment] last\n" \ "The seq utility prints a sequence of numbers between the given range.\n" \ "Options are:\n" \ " -f, --format=format -- Specify the printf-style format to print " \ "values in.\n" \ " -s, --separator=string -- Specify the separator (default newline).\n" \ " -w, --equal-width -- Pad with leading zeros so all values have the \n" \ " same width.\n" \ " --help -- Show this help text and exit.\n" \ " --version -- Print the application version information and exit.\n" // // No permutations of arguments are allowed because something like -1 might be // specified as an argument. // #define SEQ_OPTIONS_STRING "+f:s:whV" // // ------------------------------------------------------ Data Type Definitions // // // ----------------------------------------------- Internal Function Prototypes // BOOL SeqCheckFormat ( PSTR Format ); // // -------------------------------------------------------------------- Globals // struct option SeqLongOptions[] = { {"format", no_argument, 0, 'f'}, {"separator", no_argument, 0, 's'}, {"equal-width", no_argument, 0, 'w'}, {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'V'}, {NULL, 0, 0, 0}, }; // // ------------------------------------------------------------------ Functions // INT SeqMain ( INT ArgumentCount, CHAR **Arguments ) /*++ Routine Description: This routine is the main entry point for the seq utility. Arguments: ArgumentCount - Supplies the number of command line arguments the program was invoked with. Arguments - Supplies a tokenized array of command line arguments. Return Value: Returns an integer exit code. 0 for success, nonzero otherwise. --*/ { PSTR AfterScan; PSTR Argument; INT ArgumentIndex; double Current; INT CurrentFractionDigits; PSTR CurrentSeparator; INT CurrentWidth; PSTR Decimal; double End; BOOL EqualWidth; PSTR Format; INT FractionDigits; double Increment; INT Index; INT LastCheck; INT Option; PSTR Separator; double Start; int Status; INT Width; EqualWidth = FALSE; Format = NULL; Separator = "\n"; Start = 1; Increment = 1; // // Process the control arguments. // while (TRUE) { Option = getopt_long(ArgumentCount, Arguments, SEQ_OPTIONS_STRING, SeqLongOptions, NULL); if (Option == -1) { break; } if ((Option == '?') || (Option == ':')) { // // Watch out for the possibility of a negative number as the first // argument. // if ((optind > 1) && (Arguments[optind - 1][0] == '-') && (isdigit(Arguments[optind - 1][1]))) { optind -= 1; break; } Status = 1; goto MainEnd; } switch (Option) { case 'f': Format = optarg; if (SeqCheckFormat(Format) == FALSE) { SwPrintError(0, Format, "Invalid format string"); Status = 1; goto MainEnd; } break; case 's': Separator = optarg; break; case 'w': EqualWidth = TRUE; break; case 'V': SwPrintVersion(SEQ_VERSION_MAJOR, SEQ_VERSION_MINOR); return 1; case 'h': printf(SEQ_USAGE); return 1; default: assert(FALSE); Status = 1; goto MainEnd; } } Status = 1; if ((Format != NULL) && (EqualWidth != FALSE)) { SwPrintError(0, NULL, "Cannot have -f and -w together"); goto MainEnd; } ArgumentIndex = optind; if (ArgumentIndex >= ArgumentCount) { SwPrintError(0, NULL, "Argument expected"); goto MainEnd; } LastCheck = ArgumentCount - 1; // // Get the end. // Argument = Arguments[ArgumentCount - 1]; End = strtod(Argument, &AfterScan); if (*AfterScan != '\0') { SwPrintError(0, Argument, "Invalid value"); goto MainEnd; } // // If there are more arguments, then there's a start. If there's a third // argument, there's an increment. Any more is an error. // if (ArgumentIndex + 1 < ArgumentCount) { // // For some reason the ending value isn't checked for width if there's // a start and/or increment. // LastCheck = ArgumentCount - 2; Argument = Arguments[ArgumentIndex]; Start = strtod(Argument, &AfterScan); if (*AfterScan != '\0') { SwPrintError(0, Argument, "Invalid value"); goto MainEnd; } if (ArgumentIndex + 2 < ArgumentCount) { if (ArgumentIndex + 3 < ArgumentCount) { SwPrintError(0, NULL, "Too many arguments"); goto MainEnd; } Argument = Arguments[ArgumentIndex + 1]; Increment = strtod(Argument, &AfterScan); if (*AfterScan != '\0') { SwPrintError(0, Argument, "Invalid value"); goto MainEnd; } } } // // Figure out the proper width to print. // Width = 0; FractionDigits = 0; while (ArgumentIndex <= LastCheck) { Argument = Arguments[ArgumentIndex]; Decimal = strchr(Argument, '.'); if (Decimal == NULL) { CurrentWidth = strlen(Argument); CurrentFractionDigits = 0; } else { CurrentWidth = Decimal - Argument; CurrentFractionDigits = strlen(Decimal); } if (Width < CurrentWidth) { Width = CurrentWidth; } if (FractionDigits < CurrentFractionDigits) { FractionDigits = CurrentFractionDigits; } ArgumentIndex += 1; } if (FractionDigits != 0) { FractionDigits -= 1; if (FractionDigits != 0) { Width += FractionDigits + 1; } } if (EqualWidth == FALSE) { Width = 0; } Status = 0; Index = 0; Current = Start; CurrentSeparator = ""; while (TRUE) { if (Increment >= 0) { if (Current > End) { break; } } else { if (Current < End) { break; } } // // Print with the given format if supplied, or the default format // otherwise. // if (Format != NULL) { if (printf("%s", CurrentSeparator) < 0) { Status = 1; break; } if (printf(Format, Current) < 0) { Status = 1; break; } } else { Status = printf("%s%0*.*f", CurrentSeparator, Width, FractionDigits, Current); if (Status < 0) { Status = 1; break; } } CurrentSeparator = Separator; Index += 1; Current = Start + (Index * Increment); } if (Index != 0) { printf("\n"); } MainEnd: return Status; } // // --------------------------------------------------------- Internal Functions // BOOL SeqCheckFormat ( PSTR Format ) /*++ Routine Description: This routine ensures the given format string is correct for a double argument. Arguments: Format - Supplies a pointer to the potential format string. Return Value: TRUE if the format is okay. FALSE if the format string is invalid. --*/ { INT Specifiers; Specifiers = 0; while (*Format != '\0') { // // Skip anything that's not a format specifier. // if (*Format != '%') { Format += 1; continue; } Format += 1; // // Skip a literal percent. // if (*Format == '%') { Format += 1; continue; } // // Only one specifier is allowed. // Specifiers += 1; if (Specifiers > 1) { return FALSE; } // // Swallow the flags. // while ((*Format == '\'') || (*Format == '-') || (*Format == '+') || (*Format == ' ') || (*Format == '#') || (*Format == '0')) { Format += 1; } // // Swallow the field width. // while (isdigit(*Format)) { Format += 1; } // // Swallow the precision. // if (*Format == '.') { Format += 1; while (isdigit(*Format)) { Format += 1; } } // // Now it had better be a specifier. // if ((*Format != 'f') && (*Format != 'F') && (*Format != 'e') && (*Format != 'E') && (*Format != 'g') && (*Format != 'G') && (*Format != 'A') && (*Format != 'a')) { return FALSE; } Format += 1; } return TRUE; }