The previous iteration at A trivial command line utility for trimming whitespace from lines in C - follow-up 2
Now my code looks like:
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define HELP_FLAG "-h"
#define VERSION_FLAG "-v"
#define FLAG_DESC "%-5s"
#define INITIAL_BUFFER_SIZE 64
/*******************************************************************************
* This routine removes all leading and trailing whitespace from a string, *
* doing that in-place and using only one pass over the string. *
*******************************************************************************/
static char* trim_inplace(char* start)
{
while (isspace(*start))
{
++start;
}
int whitespace_begin_index = -1;
// At this point, we have dealt with leading whitespace.
for (int index = 0; start[index]; ++index)
{
if (!isspace(start[index]))
{
whitespace_begin_index = -1;
}
else if (whitespace_begin_index == -1)
{
whitespace_begin_index = index;
}
}
if (whitespace_begin_index != -1)
{
// Cut the trailing whitespace off.
start[whitespace_begin_index] = '\0';
}
return start;
}
/*******************************************************************************
* Attempts to expand the line buffer. If succeeded, returns the pointer to the *
* line buffer. Otherwise NULL is returned. *
*******************************************************************************/
static char* try_expand(char* buffer, int* p_buffer_length)
{
*p_buffer_length *= 2;
return realloc(buffer, *p_buffer_length);
}
/*******************************************************************************
* Processes a single line and handles everything needed for dealing with lines *
* of arbitrary length. *
*******************************************************************************/
static void process_line(char** p_buffer, int* p_buffer_length, FILE* file)
{
int chars_read = 0;
for (;;)
{
// The delta is for appending the next text chunk at correct position.
int delta = chars_read > 0;
char* ret = fgets(*p_buffer + chars_read - delta,
*p_buffer_length - chars_read + delta,
file);
if (!ret)
{
return;
}
// Find out whether we have a newline character, which would imply that
// we have an entire line read.
for (int i = 0; i < *p_buffer_length; ++i)
{
if ((*p_buffer)[i] == '\n')
{
(*p_buffer)[i] = '\0';
puts(trim_inplace(*p_buffer));
return;
}
}
chars_read = *p_buffer_length;
char* new_buffer;
// Once here, the current line does not fit in 'p_buffer'. Expand the
// array by doubling its capacity.
if (!(new_buffer = try_expand(*p_buffer, p_buffer_length)))
{
perror("Could not expand the line buffer");
free(*p_buffer);
exit(EXIT_FAILURE);
}
else
{
*p_buffer = new_buffer;
}
}
}
/*******************************************************************************
* Processes a file. *
*******************************************************************************/
static void process_file(char** p_buffer, int* p_buffer_length, FILE* file)
{
while (!feof(file))
{
process_line(p_buffer, p_buffer_length, file);
}
}
/*******************************************************************************
* If name contains directories, gets rid of them and returns a sole name of *
* the executable file. *
*******************************************************************************/
static const char* get_short_program_name(const char* name)
{
size_t len = strlen(name);
const char* ret = name;
for (size_t i = 0; i < len; ++i)
{
if (name[i] == '/')
{
ret = &name[i + 1];
}
}
return ret;
}
/*******************************************************************************
* Prints the help message and exits. *
*******************************************************************************/
static void print_help(const char* program_name)
{
printf("Usage: %s [" HELP_FLAG "] [" VERSION_FLAG "] " \
"[FILE1, [FILE2, [...]]]\n" \
" " FLAG_DESC " Print this help message and exit.\n" \
" " FLAG_DESC " Print the version message and exit.\n" \
" If no files specified, reads from standard input.\n",
get_short_program_name(program_name),
HELP_FLAG,
VERSION_FLAG);
}
/*******************************************************************************
* Prints the version string. *
*******************************************************************************/
static void print_version()
{
printf("trim 1.6180\n" \
"By Rodion \"rodde\" Efremov 10.04.2015 Helsinki\n");
}
/*******************************************************************************
* Prints the erroneous flag. *
*******************************************************************************/
static void print_bad_flag(const char* flag)
{
printf("Unknown flag \"%s\"\n", flag);
}
/*******************************************************************************
* Checks the flags. *
*******************************************************************************/
static void check_flags(int argc, char** argv)
{
int c;
while ((c = getopt(argc, argv, "vh")) != -1)
{
switch (c)
{
case 'v':
print_version();
exit(EXIT_SUCCESS);
break;
case 'h':
print_help(argv[0]);
exit(EXIT_SUCCESS);
break;
case '?':
print_bad_flag((char*) &optopt);
exit(EXIT_FAILURE);
break;
default:
abort();
}
}
}
/*******************************************************************************
* The entry point for a trivial line trimmer. *
*******************************************************************************/
int main(int argc, char** argv)
{
check_flags(argc, argv);
int buffer_length = INITIAL_BUFFER_SIZE;
char* buffer = malloc(buffer_length);
if (argc < 2)
{
// If realloc changes the location of memory, we need to know this.
process_file(&buffer, &buffer_length, stdin);
fclose(stdin);
free(buffer);
return EXIT_SUCCESS;
}
for (int i = 1; i < argc; ++i)
{
FILE* file = fopen(argv[i], "r");
if (!file)
{
perror("Error opening a file");
return (EXIT_FAILURE);
}
process_file(&buffer, &buffer_length, file);
fclose(file);
}
free(buffer);
}
I have done the following:
- Boolean stuff removed as it is no longer used.
- Whenever printing the help message, the actual name of the binary is used.
- Used
getopt
for parsing command line arguments. - Fixed
size_t
versusint
warnings.
sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//;'
\$\endgroup\$get_short_program_name
seems to do the same asbasename (3)
\$\endgroup\$