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-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 = [
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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',
|
'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)
|
|
||||||
|
|
|
@ -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