#include #include #include #include #include #include #include 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; }