Refactor option-parser table printing code for reuse
Signed-off-by: Jim Ramsay <i.am@jimramsay.com>pull/218/head
parent
aec9304885
commit
d1e1f62d1e
|
@ -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);
|
||||
|
|
@ -99,6 +99,7 @@ sources = [
|
|||
'src/ctl-server.c',
|
||||
'src/ctl-commands.c',
|
||||
'src/option-parser.c',
|
||||
'src/table-printer.c',
|
||||
]
|
||||
|
||||
dependencies = [
|
||||
|
@ -123,6 +124,7 @@ ctlsources = [
|
|||
'src/ctl-commands.c',
|
||||
'src/strlcpy.c',
|
||||
'src/option-parser.c',
|
||||
'src/table-printer.c',
|
||||
]
|
||||
|
||||
ctldependencies = [
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
#include "option-parser.h"
|
||||
#include "strlcpy.h"
|
||||
#include "table-printer.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
@ -72,82 +73,35 @@ static int get_left_col_width(const struct wv_option* opts, int n)
|
|||
return max_width;
|
||||
}
|
||||
|
||||
static void reflow_text(char* dst, const char* src, int width)
|
||||
{
|
||||
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)
|
||||
static void format_option(struct table_printer* printer, const struct wv_option* opt)
|
||||
{
|
||||
if (!opt->help)
|
||||
return;
|
||||
|
||||
int n_chars = fprintf(stream, " ");
|
||||
int n_chars = 0;
|
||||
char buf[64];
|
||||
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)
|
||||
n_chars += fprintf(stream, "%s--%s",
|
||||
opt->short_opt ? "," : "", opt->long_opt);
|
||||
n_chars += snprintf(buf + n_chars, sizeof(buf) - n_chars,
|
||||
"%s--%s", opt->short_opt ? "," : "",
|
||||
opt->long_opt);
|
||||
if (opt->schema)
|
||||
n_chars += fprintf(stream, "%s%s",
|
||||
opt->long_opt ? "=" : "", opt->schema);
|
||||
n_chars += snprintf(buf + n_chars, sizeof(buf) - n_chars,
|
||||
"%s%s", opt->long_opt ? "=" : "", opt->schema);
|
||||
|
||||
n_chars += fprintf(stream, "%*s", left_col_width - n_chars + 8, "");
|
||||
|
||||
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);
|
||||
}
|
||||
table_printer_print_line(printer, buf, opt->help);
|
||||
}
|
||||
|
||||
void option_parser_print_options(struct option_parser* self, FILE* stream)
|
||||
{
|
||||
fprintf(stream, "%s:\n", self->name);
|
||||
int left_col_width = get_left_col_width(self->options, self->n_opts);
|
||||
|
||||
for (int i = 0; i < self->n_opts; ++i) {
|
||||
format_option(&self->options[i], left_col_width, stream);
|
||||
}
|
||||
struct table_printer printer;
|
||||
table_printer_init(&printer, stream, left_col_width);
|
||||
for (int i = 0; i < self->n_opts; ++i)
|
||||
format_option(&printer, &self->options[i]);
|
||||
}
|
||||
|
||||
static const struct wv_option* find_long_option(
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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',
|
||||
'../src/option-parser.c',
|
||||
'../src/table-printer.c',
|
||||
'../src/strlcpy.c',
|
||||
],
|
||||
include_directories: inc,
|
||||
dependencies: [ ],
|
||||
)
|
||||
test('option-parser', option_parser)
|
||||
))
|
||||
|
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue