Refactor option-parser table printing code for reuse

Signed-off-by: Jim Ramsay <i.am@jimramsay.com>
pull/218/head
Jim Ramsay 2023-01-06 13:22:56 -05:00
parent aec9304885
commit d1e1f62d1e
6 changed files with 350 additions and 65 deletions

View File

@ -0,0 +1,46 @@
/*
* Copyright (c) 2023 Jim Ramsay
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include <stdio.h>
struct table_printer{
FILE* stream;
int max_width;
int left_indent;
int left_width;
int column_offset;
};
// Sets default values for every subsequent table_printer_new (Optional: defaults to 80/4/8)
void table_printer_set_defaults(int max_width, int left_indent,
int column_offset);
void table_printer_init(struct table_printer* self, FILE* stream,
int left_width);
void table_printer_print_line(struct table_printer* self, const char* left_text,
const char* right_text);
void table_printer_print_fmtline(struct table_printer* self,
const char* right_text,
const char* left_format, ...);
int table_printer_reflow_text(char* dst, int dst_size, const char* src,
int width);
void table_printer_indent_and_reflow_text(FILE* stream, const char* src,
int width, int first_line_indent, int subsequent_indent);

View File

@ -99,6 +99,7 @@ sources = [
'src/ctl-server.c', 'src/ctl-server.c',
'src/ctl-commands.c', 'src/ctl-commands.c',
'src/option-parser.c', 'src/option-parser.c',
'src/table-printer.c',
] ]
dependencies = [ dependencies = [
@ -123,6 +124,7 @@ ctlsources = [
'src/ctl-commands.c', 'src/ctl-commands.c',
'src/strlcpy.c', 'src/strlcpy.c',
'src/option-parser.c', 'src/option-parser.c',
'src/table-printer.c',
] ]
ctldependencies = [ ctldependencies = [

View File

@ -16,6 +16,7 @@
#include "option-parser.h" #include "option-parser.h"
#include "strlcpy.h" #include "strlcpy.h"
#include "table-printer.h"
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
@ -72,82 +73,35 @@ static int get_left_col_width(const struct wv_option* opts, int n)
return max_width; return max_width;
} }
static void reflow_text(char* dst, const char* src, int width) static void format_option(struct table_printer* printer, const struct wv_option* opt)
{
int line_len = 0;
int last_space_pos = 0;
int dst_len = 0;
int i = 0;
while (src[i]) {
char c = src[i];
if (line_len > width) {
assert(last_space_pos > 0);
dst_len -= i - last_space_pos;
dst[dst_len++] = '\n';
i = last_space_pos + 1;
line_len = 0;
continue;
}
if (c == ' ')
last_space_pos = i;
dst[dst_len++] = c;
++i;
++line_len;
}
dst[dst_len] = '\0';
}
static void format_option(const struct wv_option* opt, int left_col_width,
FILE* stream)
{ {
if (!opt->help) if (!opt->help)
return; return;
int n_chars = fprintf(stream, " "); int n_chars = 0;
char buf[64];
if (opt->short_opt) if (opt->short_opt)
n_chars += fprintf(stream, "-%c", opt->short_opt); n_chars += snprintf(buf + n_chars, sizeof(buf) - n_chars,
"-%c", opt->short_opt);
if (opt->long_opt) if (opt->long_opt)
n_chars += fprintf(stream, "%s--%s", n_chars += snprintf(buf + n_chars, sizeof(buf) - n_chars,
opt->short_opt ? "," : "", opt->long_opt); "%s--%s", opt->short_opt ? "," : "",
opt->long_opt);
if (opt->schema) if (opt->schema)
n_chars += fprintf(stream, "%s%s", n_chars += snprintf(buf + n_chars, sizeof(buf) - n_chars,
opt->long_opt ? "=" : "", opt->schema); "%s%s", opt->long_opt ? "=" : "", opt->schema);
n_chars += fprintf(stream, "%*s", left_col_width - n_chars + 8, ""); table_printer_print_line(printer, buf, opt->help);
int right_col_width = 80 - 8 - left_col_width;
assert(right_col_width >= 0);
char help[256];
reflow_text(help, opt->help, right_col_width);
char* line = strtok(help, "\n");
fprintf(stream, "%s\n", line);
while (true) {
line = strtok(NULL, "\n");
if (!line)
break;
fprintf(stream, "%*s%s\n", left_col_width + 8, "", line);
}
} }
void option_parser_print_options(struct option_parser* self, FILE* stream) void option_parser_print_options(struct option_parser* self, FILE* stream)
{ {
fprintf(stream, "%s:\n", self->name); fprintf(stream, "%s:\n", self->name);
int left_col_width = get_left_col_width(self->options, self->n_opts); int left_col_width = get_left_col_width(self->options, self->n_opts);
struct table_printer printer;
for (int i = 0; i < self->n_opts; ++i) { table_printer_init(&printer, stream, left_col_width);
format_option(&self->options[i], left_col_width, stream); for (int i = 0; i < self->n_opts; ++i)
} format_option(&printer, &self->options[i]);
} }
static const struct wv_option* find_long_option( static const struct wv_option* find_long_option(

130
src/table-printer.c 100644
View File

@ -0,0 +1,130 @@
/*
* Copyright (c) 2023 Andri Yngvason
* Copyright (c) 2023 Jim Ramsay
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
#include "table-printer.h"
#include <stdlib.h>
#include <stdarg.h>
#include <stdbool.h>
#include <string.h>
#include <assert.h>
static struct table_printer defaults = {
.max_width = 80,
.left_indent = 4,
.column_offset = 8,
.stream = NULL,
.left_width = 0,
};
void table_printer_set_defaults(int max_width, int left_indent,
int column_offset)
{
defaults.max_width = max_width;
defaults.left_indent = left_indent;
defaults.column_offset = column_offset;
}
void table_printer_init(struct table_printer* self, FILE* stream,
int left_width)
{
memcpy(self, &defaults, sizeof(*self));
self->stream = stream;
self->left_width = left_width;
}
int table_printer_reflow_text(char* dst, int dst_size, const char* src,
int width)
{
int line_len = 0;
int last_space_pos = 0;
int dst_len = 0;
int i = 0;
while (true) {
char c = src[i];
if (line_len > width) {
// first word > width
assert(last_space_pos > 0);
// subsequent word > width
assert(dst[last_space_pos] != '\n');
dst_len -= i - last_space_pos;
dst[dst_len++] = '\n';
i = last_space_pos + 1;
line_len = 0;
continue;
}
if (!c)
break;
if (c == ' ')
last_space_pos = i;
dst[dst_len++] = c;
assert(dst_len < dst_size);
++line_len;
++i;
}
dst[dst_len] = '\0';
return dst_len;
}
void table_printer_indent_and_reflow_text(FILE* stream, const char* src,
int width, int first_line_indent, int subsequent_indent)
{
char buffer[256];
table_printer_reflow_text(buffer, sizeof(buffer), src, width);
char* line = strtok(buffer, "\n");
fprintf(stream, "%*s%s\n", first_line_indent, "", line);
while (true) {
line = strtok(NULL, "\n");
if (!line)
break;
fprintf(stream, "%*s%s\n", subsequent_indent, "", line);
}
}
void table_printer_print_line(struct table_printer* self, const char* left_text,
const char* right_text)
{
fprintf(self->stream, "%*s", self->left_indent, "");
int field_len = fprintf(self->stream, "%s", left_text);
fprintf(self->stream, "%*s", self->left_width - field_len + self->column_offset, "");
int column_indent = self->left_indent + self->left_width + self->column_offset;
int column_width = self->max_width - column_indent;
table_printer_indent_and_reflow_text(self->stream,
right_text,
column_width, 0, column_indent);
}
void table_printer_print_fmtline(struct table_printer* self,
const char* right_text,
const char* left_format, ...)
{
char buf[64];
va_list args;
va_start(args, left_format);
vsnprintf(buf, sizeof(buf), left_format, args);
va_end(args);
table_printer_print_line(self, buf, right_text);
}

View File

@ -1,10 +1,18 @@
option_parser = executable('option-parser', test('table-printer', executable('table-printer',
[
'table-printer-test.c',
'../src/table-printer.c',
],
include_directories: inc,
dependencies: [ ],
))
test('option-parser', executable('option-parser',
[ [
'option-parser-test.c', 'option-parser-test.c',
'../src/option-parser.c', '../src/option-parser.c',
'../src/table-printer.c',
'../src/strlcpy.c', '../src/strlcpy.c',
], ],
include_directories: inc, include_directories: inc,
dependencies: [ ], dependencies: [ ],
) ))
test('option-parser', option_parser)

View File

@ -0,0 +1,145 @@
#include "tst.h"
#include "table-printer.h"
#include <stdlib.h>
static int test_reflow_text(void)
{
char buf[20];
const char* src = "one two three four";
int len;
len = table_printer_reflow_text(buf, sizeof(buf), src, 20);
ASSERT_INT_EQ(18, len);
ASSERT_STR_EQ("one two three four", buf);
len = table_printer_reflow_text(buf, sizeof(buf), src, 18);
ASSERT_INT_EQ(18, len);
ASSERT_STR_EQ("one two three four", buf);
len = table_printer_reflow_text(buf, sizeof(buf), src, 17);
ASSERT_INT_EQ(18, len);
ASSERT_STR_EQ("one two three\nfour", buf);
len = table_printer_reflow_text(buf, sizeof(buf), src, 10);
ASSERT_INT_EQ(18, len);
ASSERT_STR_EQ("one two\nthree four", buf);
len = table_printer_reflow_text(buf, sizeof(buf), src, 8);
ASSERT_INT_EQ(18, len);
ASSERT_STR_EQ("one two\nthree\nfour", buf);
len = table_printer_reflow_text(buf, sizeof(buf), src, 7);
ASSERT_INT_EQ(18, len);
ASSERT_STR_EQ("one two\nthree\nfour", buf);
len = table_printer_reflow_text(buf, sizeof(buf), src, 6);
ASSERT_INT_EQ(18, len);
ASSERT_STR_EQ("one\ntwo\nthree\nfour", buf);
len = table_printer_reflow_text(buf, sizeof(buf), src, 5);
ASSERT_INT_EQ(18, len);
ASSERT_STR_EQ("one\ntwo\nthree\nfour", buf);
// width <= 4 cause aborts (if any word length > width)
return 0;
}
static int test_indent_and_reflow(void)
{
size_t len;
char* buf;
FILE* stream;
stream = open_memstream(&buf, &len);
table_printer_indent_and_reflow_text(stream, "one two three four", 7, 2, 4);
fclose(stream);
// strlen(src)=18 + first=2 + subsequent=(2x4) + newline=1
ASSERT_INT_EQ(29, len);
ASSERT_STR_EQ(" one two\n three\n four\n", buf);
free(buf);
return 0;
}
static int test_defaults(void)
{
struct table_printer one;
table_printer_init(&one, stdout, 1);
table_printer_set_defaults(20, 2, 2);
struct table_printer two;
table_printer_init(&two, stderr, 2);
ASSERT_INT_EQ(80, one.max_width);
ASSERT_INT_EQ(4, one.left_indent);
ASSERT_INT_EQ(8, one.column_offset);
ASSERT_INT_EQ(1, one.left_width);
ASSERT_PTR_EQ(stdout, one.stream);
ASSERT_INT_EQ(20, two.max_width);
ASSERT_INT_EQ(2, two.left_indent);
ASSERT_INT_EQ(2, two.column_offset);
ASSERT_INT_EQ(2, two.left_width);
ASSERT_PTR_EQ(stderr, two.stream);
return 0;
}
static int test_print_line(void)
{
size_t len;
char* buf;
struct table_printer printer = {
.max_width = 20,
.left_indent = 2,
.left_width = 6,
.column_offset = 2,
};
printer.stream = open_memstream(&buf, &len);
table_printer_print_line(&printer, "left", "right");
fclose(printer.stream);
ASSERT_STR_EQ(" left right\n", buf);
free(buf);
printer.stream = open_memstream(&buf, &len);
table_printer_print_line(&printer, "left", "right side will wrap");
fclose(printer.stream);
ASSERT_STR_EQ(" left right side\n"
" will wrap\n", buf);
free(buf);
return 0;
}
static int test_print_fmtline(void)
{
size_t len;
char* buf;
struct table_printer printer = {
.max_width = 20,
.left_indent = 2,
.left_width = 6,
.column_offset = 2,
};
printer.stream = open_memstream(&buf, &len);
table_printer_print_fmtline(&printer, "right", "left");
fclose(printer.stream);
ASSERT_STR_EQ(" left right\n", buf);
free(buf);
printer.stream = open_memstream(&buf, &len);
table_printer_print_fmtline(&printer, "right side will wrap", "left%d", 2);
fclose(printer.stream);
ASSERT_STR_EQ(" left2 right side\n"
" will wrap\n", buf);
free(buf);
return 0;
}
int main()
{
int r = 0;
RUN_TEST(test_reflow_text);
RUN_TEST(test_indent_and_reflow);
RUN_TEST(test_defaults);
RUN_TEST(test_print_line);
RUN_TEST(test_print_fmtline);
return r;
}