[go: up one dir, main page]

Menu

[r5]: / tabler.c  Maximize  Restore  History

Download this file

499 lines (432 with data), 14.8 kB

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
/* tabler.c: Main program for tabler.
*
* $Id$
*
* This file is part of tabler.
* Copyright (C) 2007 Heath Caldwell
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License , or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* The Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111 USA
*
* You may contact the author by:
* e-mail: Heath Caldwell <hncaldwell@csupomona.edu>
*/
#ifndef PACKAGE_BUGREPORT
#define PACKAGE_BUGREPORT ""
#endif /* PACKAGE_BUGREPORT */
#ifndef VERSION
#define VERSION ""
#endif /* VERSION */
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <regex.h>
#include "conf.h"
#include "util.h"
#define DTOP (*(decorations))
#define DTOP_SEP (*(decorations+1))
#define DTOP_LEFT (*(decorations+2))
#define DTOP_RIGHT (*(decorations+3))
#define DBOTTOM (*(decorations+4))
#define DBOTTOM_SEP (*(decorations+5))
#define DBOTTOM_LEFT (*(decorations+6))
#define DBOTTOM_RIGHT (*(decorations+7))
#define DMIDDLE (*(decorations+8))
#define DMIDDLE_SEP (*(decorations+9))
#define DMIDDLE_LEFT (*(decorations+10))
#define DMIDDLE_RIGHT (*(decorations+11))
#define DLEFT (*(decorations+12))
#define DRIGHT (*(decorations+13))
#define DSEP (*(decorations+14))
#define DEFAULT_FIELD_SEP "[ \t]+"
#define DEFAULT_RECORD_SEP "\n"
void
usage(void)
{
printf("tabler %s - %s\n", VERSION, PACKAGE_BUGREPORT);
printf("Usage: tabler [-v] [-h] [-i] [-n] [-F regex] [-R regex] [-b decorations] [filename ... ]\n");
printf(" -v: Print version information.\n");
printf(" -h: Print help.\n");
printf(" -i: Treat first record as a header.\n");
printf(" -n: No decorations.\n");
printf(" -F regex: Specify regex to use as field seperator. Default: `[ \\t]+'\n");
printf(" -R regex: Specify regex to use as record seperator. Default: `\\n'\n");
printf(" -b decorations: Table decorations, as a string of characters in the following order:\n");
printf(" top, top separator, top left, top right, bottom, bottom seperator,\n");
printf(" bottom left, bottom right, middle, middle seperator, middle left,\n");
printf(" middle right, left, right, separator.\n");
printf(" default: \"-+++-+++-+++|||\"\n");
printf(" The list of filenames will be used, in order, for input.\n");
printf(" If no filenames are mentioned, stdin will be used for input.\n");
}
/* Data structure to hold data for each record of input.
* t: Full text of record.
* p: Portions of record, after being split by the field seperator.
* n: Number of portions.
*
* Use free_record() to clean up.
*/
typedef struct {
char *t;
char **p;
int n;
} record;
void make_portions(record *record, const regex_t *seperator);
void make_records(char *string, char **leftovers, record **records, int *records_size, int *num_records, const regex_t *field_sep, const regex_t *record_sep);
void read_file(FILE *file, record **records, int *records_size, int *num_records, const regex_t *field_seperator, const regex_t *record_seperator);
void print_bar(int *column_widths, int num_columns, char bar, char left, char right, char sep);
void free_record(record *record);
int
main(int argc, char **argv)
{
char decorations[15] = "-+++-+++-+++|||";
short int first_record_is_header = 0;
short int field_sep_provided = 0;
short int record_sep_provided = 0;
short int decorations_enabled = 1;
int error_code;
char error_string[ERROR_STRING_LENGTH];
int opt;
regex_t field_sep;
regex_t record_sep;
int i, j;
FILE *file;
record *records = NULL;
int records_size = 0;
int num_records = 0;
int *column_widths = NULL;
int num_columns = 0;
char *scratch; /* Scratch string pointer. */
while ((opt = getopt(argc, argv, "vhinF:R:-b:")) != -1) {
switch (opt) {
case 'v':
printf("tabler %s\n", VERSION);
exit(EXIT_SUCCESS);
break;
case 'h':
usage();
exit(EXIT_SUCCESS);
break;
case 'i':
first_record_is_header = 1;
break;
case 'n':
decorations_enabled = 0;
break;
case 'F':
if(field_sep_provided)
regfree(&field_sep);
error_code = regcomp(&field_sep, replace_backslashed(optarg), REG_EXTENDED);
if(error_code) {
regerror(error_code, &field_sep, error_string, ERROR_STRING_LENGTH);
fprintf(stderr, "Bad field seperator: %s\n", error_string);
exit(EXIT_FAILURE);
}
field_sep_provided = 1;
break;
case 'R':
if(record_sep_provided)
regfree(&record_sep);
error_code = regcomp(&record_sep, replace_backslashed(optarg), REG_EXTENDED);
if(error_code) {
regerror(error_code, &record_sep, error_string, ERROR_STRING_LENGTH);
fprintf(stderr, "Bad record seperator: %s\n", error_string);
exit(EXIT_FAILURE);
}
record_sep_provided = 1;
break;
case 'b':
memcpy(decorations, optarg, strlen(optarg) < 15 ? strlen(optarg) : 15);
break;
default: /* '?' */
usage();
exit(EXIT_FAILURE);
}
}
/* Use default seperator if one was not provided. */
if(!field_sep_provided) {
error_code = regcomp(&field_sep, DEFAULT_FIELD_SEP, REG_EXTENDED);
if(error_code) {
regerror(error_code, &field_sep, error_string, ERROR_STRING_LENGTH);
fprintf(stderr, "Bad field seperator: %s\n", error_string);
exit(EXIT_FAILURE);
}
} else {
/* Check to make sure that the seperator does not match the empty string,
* which causes an infinite loop to occur. */
if(!(i = regexec(&field_sep, "", 0, NULL, 0))) {
fprintf(stderr, "Bad field seperator: Matches empty string.\n");
exit(EXIT_FAILURE);
} else if(i != REG_NOMATCH) {
regerror(i, &field_sep, error_string, ERROR_STRING_LENGTH);
fprintf(stderr, "Error: %s\n", error_string);
}
}
if(!record_sep_provided) {
error_code = regcomp(&record_sep, DEFAULT_RECORD_SEP, REG_EXTENDED);
if(error_code) {
regerror(error_code, &record_sep, error_string, ERROR_STRING_LENGTH);
fprintf(stderr, "Bad record seperator: %s\n", error_string);
exit(EXIT_FAILURE);
}
} else {
if(!(i = regexec(&record_sep, "", 0, NULL, 0))) {
fprintf(stderr, "Bad record seperator: Matches empty string.\n");
exit(EXIT_FAILURE);
} else if(i != REG_NOMATCH) {
regerror(i, &record_sep, error_string, ERROR_STRING_LENGTH);
fprintf(stderr, "Error: %s\n", error_string);
}
}
/* *** */
if(optind >= argc) {
read_file(stdin, &records, &records_size, &num_records, &field_sep, &record_sep);
} else {
for(i=optind; i < argc; i++) {
if(!(file = fopen(argv[i], "r"))) {
printf("Error opening file: %s\n", argv[i]);
exit(EXIT_FAILURE);
}
read_file(file, &records, &records_size, &num_records, &field_sep, &record_sep);
fclose(file);
}
}
/* Get column widths. */
for(i=0; i < num_records; i++) {
if(records[i].n > num_columns) {
column_widths = realloc(column_widths, records[i].n * sizeof(int));
for(j=num_columns; j < records[i].n; j++)
column_widths[j] = 0;
num_columns = records[i].n;
}
for(j=0; j < records[i].n; j++) {
if(strlen(records[i].p[j]) > column_widths[j])
column_widths[j] = strlen(records[i].p[j]);
}
}
/* Print table. */
if(decorations_enabled) print_bar(column_widths, num_columns, DTOP, DTOP_LEFT, DTOP_RIGHT, DTOP_SEP);
for(i=0; i<num_records; i++) {
if(decorations_enabled)
printf("%c ", DLEFT);
else
printf(" ");
for(j=0; j < num_columns; j++) {
scratch = calloc(dec_digits(column_widths[j]) + 4, sizeof(char)); /* 4 = %, -, s, \0 */
sprintf(scratch, "%%-%ds", column_widths[j]);
if(records[i].n > j)
printf(scratch, records[i].p[j]);
else
printf(scratch, "");
if(j < num_columns-1) { /* Not the last column. */
if(decorations_enabled)
printf(" %c ", DSEP);
else
printf(" ");
}
free(scratch);
}
if(decorations_enabled)
printf(" %c", DRIGHT);
printf("\n");
if(!i && first_record_is_header) {
if(decorations_enabled) print_bar(column_widths, num_columns, DMIDDLE, DMIDDLE_LEFT, DMIDDLE_RIGHT, DMIDDLE_SEP);
}
free_record(&records[i]);
}
if(decorations_enabled) print_bar(column_widths, num_columns, DBOTTOM, DBOTTOM_LEFT, DBOTTOM_RIGHT, DBOTTOM_SEP);
free(column_widths);
free(records);
regfree(&field_sep);
regfree(&record_sep);
exit(EXIT_SUCCESS);
}
/* print_bar
*
* Prints a full bar across the width of the table, with seperators at
* appropriate intervals for each column.
*
* Takes: column_widths: Array holding the width of each column, in
* order from left to right.
* num_columns: The number of columns.
* bar: Character to use for the middle parts of
* the bar.
* left: Leftmost character of the bar.
* right: Rightmost character of the bar.
* sep: Character to use for the separator.
*/
void
print_bar(int *column_widths, int num_columns, char bar, char left, char right, char sep)
{
int i, j;
for(i=0; i < num_columns; i++) {
printf("%c", i ? sep : left);
for(j=0; j < column_widths[i]+2; j++)
printf("%c", bar);
}
printf("%c\n", right);
}
/* free_record
*
* Cleans up (destroys) a record structure.
*
* Takes: record: Pointer to record to clean up.
*/
void
free_record(record *record)
{
int i;
free(record->t);
for(i=0; i < record->n; i++)
free(record->p[i]);
}
/* make_portions
*
* Splits up a record into the portions that are separated by a given regex.
* The given record's t field is expected to be pointing to a string.
*
* Takes: record: Line structure to operate on.
* seperator: Compiled regular expression to use as the field seperator.
*
* record's p field will be populated with the portions found.
* record's n field will contain the number of portions.
*/
void
make_portions(record *record, const regex_t *seperator)
{
int result = 0;
char *t_copy;
char *token = 0;
char *prev_token = 0; /* Previous token. used to take care of dangling seperator at end of record. */
t_copy = strdup(record->t);
while(token = strregtok(token ? NULL : t_copy, seperator)) {
//if(strlen(token) < 1) {
// fprintf(stderr, "Error: Field seperator match yeilds no progress.\n");
// exit(EXIT_FAILURE);
//}
if(prev_token) {
record->p = realloc(record->p, (record->n + 1) * sizeof(char *));
record->p[record->n] = strdup(prev_token);
record->n++;
}
prev_token = token;
}
if(strlen(prev_token) > 0) {
record->p = realloc(record->p, (record->n + 1) * sizeof(char *));
record->p[record->n] = strdup(prev_token);
record->n++;
}
free(t_copy);
}
/* make_records
*
* Makes records from string.
*
* Takes: string: String to split into records.
* leftovers: Pointer to a string pointer that will point to newly
* allocated space that contains the leftovers.
* records: Pointer to array of record structures to store records in.
* records_size: Pointer to place to store how many records have been
* allocated.
* num_records: Pointer to place to store how many records have been
* stored.
* field_sep: Compiled regex_t to use as the field seperator.
* record_sep: Compiled regex_t to use as the record seperator.
*/
void
make_records(char *string, char **leftovers, record **records, int *records_size, int *num_records, const regex_t *field_sep, const regex_t *record_sep)
{
char *string_copy;
char *token = NULL;
char *prev_token = NULL;
record *cr; /* Current record. */
string_copy = strdup(string);
//printf("\n[%s]", string);
while(token = strregtok(token ? NULL : string_copy, record_sep)) {
if(*num_records >= *records_size) {
if(*records_size) {
*records_size *= 2;
*records = realloc(*records, *records_size * sizeof(record));
} else {
*records_size = STARTING_LINES_SIZE;
*records = calloc(*records_size, sizeof(record));
}
}
if(prev_token) {
cr = &(*records)[*num_records];
cr->t = strdup(prev_token);
cr->n = 0;
make_portions(cr, field_sep);
(*num_records)++;
}
prev_token = token;
}
*leftovers = strdup(prev_token);
free(string_copy);
}
/* read_file
*
* Reads a file and stores its records in record structures.
*
* Takes: file: Pointer to file to read.
* records: Pointer to array of record structures to store records in.
* records_size: Pointer to place to store how many records have been
* allocated.
* num_records: Pointer to place to store how many records have been
* stored.
* field_sep: Compiled regex_t to use as the field seperator.
* record_sep: Compiled regex_t to use as the record seperator.
*/
void
read_file(FILE *file, record **records, int *records_size, int *num_records, const regex_t *field_sep, const regex_t *record_sep)
{
char buffer[READ_CHUNK_SIZE];
char *leftovers = NULL;
char *string;
unsigned int string_size = 0;
record *lr; /* Last record. */
string = calloc(READ_CHUNK_SIZE + 1, sizeof(char));
string[0] = '\0'; /* Start out with zero length string. */
while(fgets(buffer, READ_CHUNK_SIZE, file)) {
if(string_size < strlen(string) + READ_CHUNK_SIZE + 1) {
string = realloc(string, (strlen(string) + READ_CHUNK_SIZE + 1) * sizeof(char));
}
strcpy(&string[strlen(string)], buffer);
free(leftovers);
make_records(string, &leftovers, records, records_size, num_records, field_sep, record_sep);
strcpy(string, leftovers);
}
/* Add last record. */
if(strlen(leftovers) > 0) {
string = strregtok(string, record_sep);
if(*num_records >= *records_size) {
if(*records_size) {
*records_size *= 2;
*records = realloc(*records, *records_size * sizeof(record));
} else {
*records_size = STARTING_LINES_SIZE;
*records = calloc(*records_size, sizeof(record));
}
}
lr = &(*records)[*num_records];
lr->t = strdup(string);
lr->n = 0;
make_portions(lr, field_sep);
(*num_records)++;
}
free(leftovers);
}