/** * @file myexpand.c * @brief This module enables a user to expand tabs in different ways by choosing * a custom tabstop value. * @date 2018-03-10 */ #include #include #include #include #include #include #include #define BUF_SIZE 1024 /** * @details Variable which stores the program name. */ static char *pname; /** * @details Variable which stores the desired tabsize. Default is 8. */ static int tabstop = 8; /** * @details Variable which gets set when a SIGINT or SIGTERM is received. */ volatile sig_atomic_t quit = 0; /** * @details Signal handler. * @param signal The number of the received signal. * @return This function has no return value. */ static void handle_signal(int signal) { quit = 1; } /** * @details Prints the synopsis. * @param void This function takes no parameters. * @return Exit code of the program. Always non-zero. */ static void usage(void) { fprintf(stderr, "Usage: myexpand [-t tabstop] [file...]\n"); exit(EXIT_FAILURE); } /** * @details Safely parses the supplied -t parameter and converts the string * into a long int. * @param optarg The argument after -t. * @return On success returns the correctly parsed integer for a tabstop. * On error the exit code is non-zero. */ static long int strToInt(char *optarg) { long int result = 0; char *ptr = NULL; result = strtol(optarg, &ptr, 10); if (errno == EINVAL) { fprintf(stderr, "%s: Conversion error ocurred: %d\n", pname, errno); exit(EXIT_FAILURE); } if (errno == ERANGE) { fprintf(stderr, "%s: The value provided was out of range!\n", pname); exit(EXIT_FAILURE); } if (result == 0 || *ptr != '\0') { fprintf(stderr, "%s: Conversion error ocurred!\n", pname); exit(EXIT_FAILURE); } return result; } /** * @details Goes over the supplied text, converts tabs accordingly and prints * the outcome to stdout. * @param source A string containing the message to be converted. * @return This function has no return value. */ static void convert(char *source) { int x = 0; for (int i = 0; i < (int)strlen(source); i++) { if (source[i] == '\n') x = -1; if (source[i] == '\t') { int p = labs(x - tabstop); for (int s = 0; s < p; s++) { fprintf(stdout, " "); } x += p; } else { fprintf(stdout, "%c", source[i]); } x++; if (x >= (tabstop)) x = 0; } } /** * @details Reads filenames from an array of strings and calls the convert * function for each of them. * @param size The size of the array holding the filenames. * @param filenames The array containing the filenames. * @return Non-zero on failure. */ static void readFiles(int size, char *filenames[]) { for (int i = 0; i < size; i++) { char *source = NULL; FILE *fp = fopen(filenames[i], "rb"); if (fp == NULL) { perror(pname); exit(EXIT_FAILURE); } if (fseek(fp, 0L, SEEK_END) == 0) { long bufsize = ftell(fp); if (bufsize == -1) perror(pname); source = malloc(sizeof(char) * (bufsize + 1)); if (fseek(fp, 0L, SEEK_SET) != 0) perror(pname); size_t newLen = fread(source, sizeof(char), bufsize, fp); if (ferror(fp) != 0) { perror(pname); } else { source[newLen++] = '\0'; } } fclose(fp); convert(source); free(source); } } /** * The main entry point of the program. * @details Reads the supplied command-line arguments and calls the required * functions. * @param argc The number of command-line parameters in argv. * @param argv The array of command-line arguments, argc elements long. * @return 0 on success, non-zero on failure. */ int main(int argc, char *argv[]) { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = handle_signal; sigaction(SIGINT, &sa, NULL); while (quit != 1) { pname = argv[0]; int opt_ind = 0; int tflag = 0; while ((opt_ind = getopt(argc, argv, "t:")) != -1) { switch (opt_ind) { case 't': if (tflag != 0) { usage(); } else { tabstop = strToInt(optarg); tflag = 1; } break; case '?': usage(); default: assert(0); } } if ((argc - optind) != 0) { char *filenames[argc-optind]; for (int i = optind, c = 0; i < argc; i++, c++) { filenames[c] = argv[i]; } readFiles(argc-optind, filenames); exit(EXIT_SUCCESS); } else { char buffer[BUF_SIZE]; size_t contentSize = 1; char *content = malloc(sizeof(char) * BUF_SIZE); if (content == NULL) { perror(pname); exit(EXIT_FAILURE); } content[0] = '\0'; while (fgets(buffer, BUF_SIZE, stdin)) { char *old = content; contentSize += strlen(buffer); content = realloc(content, contentSize); if (content == NULL) { perror(pname); free(old); exit(EXIT_FAILURE); } strcat(content, buffer); } if (ferror(stdin)) { free(content); perror(pname); exit(EXIT_FAILURE); } convert(content); free(content); exit(EXIT_SUCCESS); } } exit(EXIT_FAILURE); }