#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/wait.h>
struct chunk {
char type;
char * contents;
};
int main(void);
struct chunk * get_input_chunks(void);
char * get_line(void);
void destroy_input_chunks(struct chunk *);
void parse_chunk(const struct chunk *);
unsigned execute_query(const char *, char ****, unsigned *);
char * next_output_line(char **);
void destroy_query_results(char ***);
unsigned execute_program(const char *, char **, const void *, unsigned, void **);
void execute_subprocess(const char *, char **, int, int);
void display_percentage_table(const char *);
char * convert_number_to_string(unsigned long long);
char * convert_percentage_to_string(unsigned short);
char * database = NULL;
char last_number[40];
char last_percentage[8];
int main (void) {
struct chunk * chunks = get_input_chunks();
if (!chunks) return 0;
unsigned chunkID;
for (chunkID = 0; chunks[chunkID].type; chunkID ++) parse_chunk(chunks + chunkID);
destroy_input_chunks(chunks);
return 0;
}
struct chunk * get_input_chunks (void) {
struct chunk * chunks = NULL;
unsigned chunk_count = 0;
char * line = get_line();
struct chunk current_chunk = {.type = 0, .contents = NULL};
unsigned c4 = 0; // current chunk contents count
unsigned line_length;
while (line) {
line_length = strlen(line);
if (*line == current_chunk.type) {
if (!current_chunk.type) {
line = get_line();
continue;
}
*line = '\n';
if (c4) {
current_chunk.contents = realloc(current_chunk.contents, c4 + line_length);
memcpy(current_chunk.contents + c4, line, line_length);
c4 += line_length;
} else {
current_chunk.contents = malloc(c4 = line_length - 1);
memcpy(current_chunk.contents, line + 1, c4);
}
free(line);
line = get_line();
continue;
}
if (current_chunk.type) {
c4[current_chunk.contents = realloc(current_chunk.contents, c4 + 1)] = 0;
chunks = realloc(chunks, sizeof(struct chunk) * (chunk_count + 1));
chunks[chunk_count ++] = current_chunk;
}
current_chunk.type = *line;
current_chunk.contents = NULL;
c4 = 0;
}
if (current_chunk.type) {
chunks = realloc(chunks, sizeof(struct chunk) * (chunk_count + 1));
chunks[chunk_count ++] = current_chunk;
}
chunk_count[chunks = realloc(chunks, sizeof(struct chunk) * (chunk_count + 1))] = (struct chunk) {.type = 0, .contents = NULL};
return chunks;
}
char * get_line (void) {
char * line = NULL;
unsigned length = 0;
int next_char = getchar();
if (next_char == EOF) return NULL;
while ((next_char != EOF) && (next_char != '\n')) {
line = realloc(line, length + 1);
line[length ++] = next_char;
next_char = getchar();
}
line = realloc(line, length + 1);
line[length] = 0;
return line;
}
void destroy_input_chunks (struct chunk * chunks) {
struct chunk * chunk;
for (chunk = chunks; chunk -> type; chunk ++) free(chunk -> contents);
free(chunks);
}
void parse_chunk (const struct chunk * chunk) {
if (!chunk) return;
switch (chunk -> type) {
case ' ':
printf("%s\n", chunk -> contents);
return;
case '%':
display_percentage_table(chunk -> contents);
return;
case '!':
if (database) free(database);
database = strcpy(malloc(strlen(chunk -> contents) + 1), chunk -> contents);
return;
case 0:
return;
default:
fprintf(stderr, "unknown chunk type: %c\n", chunk -> type);
}
}
unsigned execute_query (const char * query, char **** results, unsigned * column_count) {
if (!(query && database)) goto argument_error;
char * arguments[4];
*arguments = "mysql";
arguments[1] = "-sr";
arguments[2] = database;
arguments[3] = NULL;
char * output;
unsigned output_size = execute_program(*arguments, arguments, query, strlen(query), (void **) &output);
if (!(output && output_size)) goto output_error;
output = realloc(output, 1 + output_size);
output[output_size] = 0;
char * buffer = output;
char * line = next_output_line(&buffer);
char * first_line = line;
if (!(line && *line)) goto output_error;
unsigned col_count = 1;
while (first_line = strchr(first_line, '\t')) first_line ++, col_count ++;
if (column_count) *column_count = col_count;
unsigned row_count = 0;
if (!results) {
while (buffer = strchr(buffer, '\n')) buffer ++, row_count ++;
free(output);
return row_count;
}
*results = NULL;
char *** row;
unsigned col;
unsigned len;
char * tabpos;
while (line) {
row = (*results = realloc(*results, sizeof(char **) * (1 + row_count))) + row_count;
row_count ++;
*row = calloc(col_count + 1, sizeof(char *));
for (col = 0; col < col_count; col ++) {
if (!line) break;
len = (tabpos = strchr(line, '\t')) ? tabpos - line : strlen(line);
memcpy(col[*row] = malloc(len + 1), line, len);
(*row)[col][len] = 0;
line = tabpos;
if (tabpos) line ++;
}
line = next_output_line(&buffer);
}
row_count[*results = realloc(*results, sizeof(char **) * (1 + row_count))] = NULL;
free(output);
return row_count;
output_error:
free(output);
argument_error:
if (results) *results = NULL;
if (column_count) *column_count = 0;
return 0;
}
char * next_output_line (char ** string) {
if (!(string && *string && **string)) return NULL;
unsigned line_length = strcspn(*string, "\r\n");
char * result = *string;
*string += line_length + 1 + !strncmp(*string + line_length, "\r\n", 2);
result[line_length] = 0;
return result;
}
void destroy_query_results (char *** results) {
char *** row;
char ** cell;
for (row = results; *row; row ++) {
for (cell = *row; *cell; cell ++) free(*cell);
free(*row);
}
free(results);
}
unsigned execute_program (const char * program, char ** arguments, const void * input, unsigned input_size, void ** output) {
int file_descriptors[4];
pipe(file_descriptors);
pipe(file_descriptors + 2);
fcntl(file_descriptors[1], F_SETFD, FD_CLOEXEC);
fcntl(file_descriptors[2], F_SETFD, FD_CLOEXEC);
int pid = fork();
if (!pid) execute_subprocess(program, arguments, *file_descriptors, file_descriptors[3]);
close(*file_descriptors);
close(file_descriptors[3]);
if (input) write(file_descriptors[1], input, input_size);
close(file_descriptors[1]);
char buffer[4096];
unsigned output_size = 0;
int reception;
if (output) *output = NULL;
while (reception = read(file_descriptors[2], buffer, 4096)) {
if (reception < 0) {
if ((errno == EAGAIN) || (errno == EINTR)) continue;
break;
}
if (output) {
*output = realloc(*output, output_size + reception);
memcpy(((char *) *output) + output_size, buffer, reception);
}
output_size += reception;
}
close(file_descriptors[2]);
waitpid(pid, NULL, 0);
return output_size;
}
void execute_subprocess (const char * program, char ** arguments, int read_fd, int write_fd) {
// must not return to caller
while (dup2(read_fd, 0) < 0);
close(read_fd);
while (dup2(write_fd, 1) < 0);
close(write_fd);
execvp(program, arguments);
abort();
}
#define PERCENTAGE_TABLE_ROW "[tr][td=width: 270px; border-bottom: 1px solid #ccc; padding-left: 6px]%s[/td]\
[td=width: 80px; text-align: right; border-bottom: 1px solid #ccc]%s[/td]\
[td=width: 50px; text-align: right; border-bottom: 1px solid #ccc; padding-right: 6px]%s[/td][/tr]"
#define PERCENTAGE_TABLE_TOTAL_ROW "[tr][td=width: 270px; text-align: right; font-weight: bold]Total[/td]\
[td=width: 80px; text-align: right]%s[/td]\
[td=width: 50px; text-align: right; padding-right: 6px; font-weight: bold]100[/td][/tr]"
void display_percentage_table (const char * query) {
char *** results;
unsigned column_count;
unsigned row_count = execute_query(query, &results, &column_count);
if (column_count != 2) fputs("warning: percentage query did not return two columns\n", stderr);
if (!row_count) {
destroy_query_results(results);
return;
}
unsigned long long * item_counts = malloc(row_count * sizeof(unsigned long long));
unsigned row;
char * t;
unsigned long long total = 0;
for (row = 0; row < row_count; row ++) {
item_counts[row] = strtoull(results[row][1], &t, 10);
if (*t) fprintf(stderr, "warning: illegal numeric value at row %u: %s\n", row + 1, results[row][1]);
total += item_counts[row];
}
unsigned short * percentages = malloc(row_count * sizeof(unsigned short));
unsigned short total_percentages = 0;
for (row = 0; row < row_count; row ++) {
percentages[row] = (item_counts[row] * 20000 / total + 1) >> 1;
total_percentages += percentages[row];
}
unsigned short max = *percentages;
unsigned max_row = 0;
for (row = 1; row < row_count; row ++) if (percentages[row] > max) {
max_row = row;
max = percentages[row];
}
percentages[max_row] = percentages[max_row] + 10000 - total_percentages;
puts("[table]");
for (row = 0; row < row_count; row ++)
printf(PERCENTAGE_TABLE_ROW, results[row][0], convert_number_to_string(item_counts[row]), convert_percentage_to_string(percentages[row]));
printf(PERCENTAGE_TABLE_TOTAL_ROW, convert_number_to_string(total));
puts("[/table]\n");
destroy_query_results(results);
free(item_counts);
free(percentages);
}
char * convert_number_to_string (unsigned long long number) {
if (number < 1000) {
sprintf(last_number, "%llu", number);
return last_number;
}
convert_number_to_string(number / 1000);
char remainder[5];
sprintf(remainder, ",%03llu", number % 1000);
strcat(last_number, remainder);
return last_number;
}
char * convert_percentage_to_string (unsigned short percentage) {
if (percentage > 9999) {
strcpy(last_percentage, "100");
return last_percentage;
}
sprintf(last_percentage, "%2hu.%02hu", percentage / 100, percentage % 100);
return last_percentage;
}
Comments