#include "set.h"
#include "strv.h"
#include "terminal-util.h"
+#include "utf8.h"
#include "varlink-idl.h"
#define DEPTH_MAX 64U
COLOR_IDENTIFIER,
COLOR_MARKS, /* [], ->, ?, … */
COLOR_RESET,
+ COLOR_COMMENT,
_COLOR_MAX,
};
#define varlink_idl_log(error, format, ...) log_debug_errno(error, "Varlink-IDL: " format, ##__VA_ARGS__)
#define varlink_idl_log_full(level, error, format, ...) log_full_errno(level, error, "Varlink-IDL: " format, ##__VA_ARGS__)
-static int varlink_idl_format_all_fields(FILE *f, const VarlinkSymbol *symbol, VarlinkFieldDirection direction, const char *indent, const char *const colors[static _COLOR_MAX]);
+static int varlink_idl_format_all_fields(FILE *f, const VarlinkSymbol *symbol, VarlinkFieldDirection direction, const char *indent, const char *const colors[static _COLOR_MAX], size_t cols);
+
+static int varlink_idl_format_comment(
+ FILE *f,
+ const char *text,
+ const char *indent,
+ const char *const colors[static _COLOR_MAX],
+ size_t cols) {
+
+ int r;
+
+ assert(f);
+ assert(colors);
+
+ if (!text) {
+ /* If text is NULL, output an empty but commented line */
+ fputs(strempty(indent), f);
+ fputs(colors[COLOR_COMMENT], f);
+ fputs("#", f);
+ fputs(colors[COLOR_RESET], f);
+ fputs("\n", f);
+ return 0;
+ }
+
+ _cleanup_strv_free_ char **l = NULL;
+ r = strv_split_full(&l, text, NEWLINE, 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to split comment string: %m");
+
+ size_t indent_width = utf8_console_width(indent);
+ size_t max_width = indent_width < cols ? cols - indent_width : 0;
+ if (max_width < 10)
+ max_width = 10;
+
+ _cleanup_strv_free_ char **broken = NULL;
+ r = strv_rebreak_lines(l, max_width, &broken);
+ if (r < 0)
+ return log_error_errno(r, "Failed to rebreak lines in comment: %m");
+
+ STRV_FOREACH(i, broken) {
+ fputs(strempty(indent), f);
+ fputs(colors[COLOR_COMMENT], f);
+ fputs("# ", f);
+ fputs(*i, f);
+ fputs(colors[COLOR_RESET], f);
+ fputs("\n", f);
+ }
+
+ return 0;
+}
static int varlink_idl_format_enum_values(
FILE *f,
FILE *f,
const VarlinkField *field,
const char *indent,
- const char *const colors[static _COLOR_MAX]) {
+ const char *const colors[static _COLOR_MAX],
+ size_t cols) {
assert(f);
assert(field);
+ assert(field->field_type != _VARLINK_FIELD_COMMENT);
fputs(strempty(indent), f);
fputs(colors[COLOR_IDENTIFIER], f);
break;
case VARLINK_STRUCT:
- return varlink_idl_format_all_fields(f, ASSERT_PTR(field->symbol), VARLINK_REGULAR, indent, colors);
+ return varlink_idl_format_all_fields(f, ASSERT_PTR(field->symbol), VARLINK_REGULAR, indent, colors, cols);
case VARLINK_ENUM:
return varlink_idl_format_enum_values(f, ASSERT_PTR(field->symbol), indent, colors);
const VarlinkSymbol *symbol,
VarlinkFieldDirection filter_direction,
const char *indent,
- const char *const colors[static _COLOR_MAX]) {
+ const char *const colors[static _COLOR_MAX],
+ size_t cols) {
_cleanup_free_ char *indent2 = NULL;
bool first = true;
for (const VarlinkField *field = symbol->fields; field->field_type != _VARLINK_FIELD_TYPE_END_MARKER; field++) {
+ if (field->field_type == _VARLINK_FIELD_COMMENT) /* skip comments at first */
+ continue;
+
if (field->field_direction != filter_direction)
continue;
} else
fputs(",\n", f);
- r = varlink_idl_format_field(f, field, indent2, colors);
+ /* We found a field we want to output. In this case, output all immediately preceeding
+ * comments first. First, find the first comment in the series before. */
+ const VarlinkField *start_comment = NULL;
+ for (const VarlinkField *c1 = field; c1 > symbol->fields; c1--) {
+ const VarlinkField *c0 = c1 - 1;
+
+ if (c0->field_type != _VARLINK_FIELD_COMMENT)
+ break;
+
+ start_comment = c0;
+ }
+
+ if (start_comment) {
+ for (const VarlinkField *c = start_comment; c < field; c++) {
+ r = varlink_idl_format_comment(f, ASSERT_PTR(c->name), indent2, colors, cols);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ r = varlink_idl_format_field(f, field, indent2, colors, cols);
if (r < 0)
return r;
}
static int varlink_idl_format_symbol(
FILE *f,
const VarlinkSymbol *symbol,
- const char *const colors[static _COLOR_MAX]) {
+ const char *const colors[static _COLOR_MAX],
+ size_t cols) {
int r;
assert(f);
fputs(symbol->name, f);
fputs(colors[COLOR_RESET], f);
- r = varlink_idl_format_all_fields(f, symbol, VARLINK_REGULAR, /* indent= */ NULL, colors);
+ r = varlink_idl_format_all_fields(f, symbol, VARLINK_REGULAR, /* indent= */ NULL, colors, cols);
break;
case VARLINK_METHOD:
fputs(symbol->name, f);
fputs(colors[COLOR_RESET], f);
- r = varlink_idl_format_all_fields(f, symbol, VARLINK_INPUT, /* indent= */ NULL, colors);
+ r = varlink_idl_format_all_fields(f, symbol, VARLINK_INPUT, /* indent= */ NULL, colors, cols);
if (r < 0)
return r;
fputs(" -> ", f);
fputs(colors[COLOR_RESET], f);
- r = varlink_idl_format_all_fields(f, symbol, VARLINK_OUTPUT, /* indent= */ NULL, colors);
+ r = varlink_idl_format_all_fields(f, symbol, VARLINK_OUTPUT, /* indent= */ NULL, colors, cols);
break;
case VARLINK_ERROR:
fputs(symbol->name, f);
fputs(colors[COLOR_RESET], f);
- r = varlink_idl_format_all_fields(f, symbol, VARLINK_REGULAR, /* indent= */ NULL, colors);
+ r = varlink_idl_format_all_fields(f, symbol, VARLINK_REGULAR, /* indent= */ NULL, colors, cols);
break;
default:
FILE *f,
const VarlinkInterface *interface,
VarlinkSymbolType filter_type,
- const char *const colors[static _COLOR_MAX]) {
+ const char *const colors[static _COLOR_MAX],
+ size_t cols) {
int r;
if ((*symbol)->symbol_type != filter_type)
continue;
+ if ((*symbol)->symbol_type == _VARLINK_INTERFACE_COMMENT) {
+ /* Interface comments we'll output directly. */
+ r = varlink_idl_format_comment(f, ASSERT_PTR((*symbol)->name), /* indent= */ NULL, colors, cols);
+ if (r < 0)
+ return r;
+
+ continue;
+ }
+
fputs("\n", f);
- r = varlink_idl_format_symbol(f, *symbol, colors);
+ /* Symbol comments we'll only output if we are outputing the symbol they belong to. Scan
+ * backwards for symbol comments. */
+ const VarlinkSymbol *const*start_comment = NULL;
+ for (const VarlinkSymbol *const*c1 = symbol; c1 > interface->symbols; c1--) {
+ const VarlinkSymbol *const *c0 = c1 - 1;
+
+ if ((*c0)->symbol_type != _VARLINK_SYMBOL_COMMENT)
+ break;
+
+ start_comment = c0;
+ }
+
+ /* Found one or more comments, output them now */
+ if (start_comment)
+ for (const VarlinkSymbol *const*c = start_comment; c < symbol; c++) {
+ r = varlink_idl_format_comment(f, ASSERT_PTR((*c)->name), /* indent= */ NULL, colors, cols);
+ if (r < 0)
+ return r;
+ }
+
+ r = varlink_idl_format_symbol(f, *symbol, colors, cols);
if (r < 0)
return r;
}
return 0;
}
-int varlink_idl_dump(FILE *f, int use_colors, const VarlinkInterface *interface) {
+int varlink_idl_dump(FILE *f, int use_colors, size_t cols, const VarlinkInterface *interface) {
static const char* const color_table[_COLOR_MAX] = {
[COLOR_SYMBOL_TYPE] = ANSI_HIGHLIGHT_GREEN,
[COLOR_FIELD_TYPE] = ANSI_HIGHLIGHT_BLUE,
[COLOR_IDENTIFIER] = ANSI_NORMAL,
[COLOR_MARKS] = ANSI_HIGHLIGHT_MAGENTA,
[COLOR_RESET] = ANSI_NORMAL,
+ [COLOR_COMMENT] = ANSI_GREY,
};
static const char* const color_off[_COLOR_MAX] = {
- "", "", "", "", "",
+ "", "", "", "", "", "",
};
int r;
const char *const *colors = use_colors ? color_table : color_off;
+ /* First output interface comments */
+ r = varlink_idl_format_all_symbols(f, interface, _VARLINK_INTERFACE_COMMENT, colors, cols);
+ if (r < 0)
+ return r;
+
fputs(colors[COLOR_SYMBOL_TYPE], f);
fputs("interface ", f);
fputs(colors[COLOR_IDENTIFIER], f);
fputs(colors[COLOR_RESET], f);
fputs("\n", f);
+ /* Then output all symbols, ordered by symbol type */
for (VarlinkSymbolType t = 0; t < _VARLINK_SYMBOL_TYPE_MAX; t++) {
- r = varlink_idl_format_all_symbols(f, interface, t, colors);
+
+ /* Interface comments we already have output above. Symbol comments are output when the
+ * symbol they belong to are output, hence filter both here. */
+ if (IN_SET(t, _VARLINK_SYMBOL_COMMENT, _VARLINK_INTERFACE_COMMENT))
+ continue;
+
+ r = varlink_idl_format_all_symbols(f, interface, t, colors, cols);
if (r < 0)
return r;
}
return 0;
}
-int varlink_idl_format(const VarlinkInterface *interface, char **ret) {
+int varlink_idl_format_full(const VarlinkInterface *interface, size_t cols, char **ret) {
_cleanup_(memstream_done) MemStream memstream = {};
int r;
if (!memstream_init(&memstream))
return -errno;
- r = varlink_idl_dump(memstream.f, /* use_colors= */ false, interface);
+ r = varlink_idl_dump(memstream.f, /* use_colors= */ false, cols, interface);
if (r < 0)
return r;
return true;
}
+static bool varlink_idl_comment_is_valid(const char *comment) {
+ return utf8_is_valid(comment);
+}
+
static int varlink_idl_symbol_consistent(const VarlinkInterface *interface, const VarlinkSymbol *symbol, int level);
static int varlink_idl_field_consistent(
static bool varlink_symbol_is_empty(const VarlinkSymbol *symbol) {
assert(symbol);
+ if (IN_SET(symbol->symbol_type, _VARLINK_SYMBOL_COMMENT, _VARLINK_INTERFACE_COMMENT))
+ return true;
+
return symbol->fields[0].field_type == _VARLINK_FIELD_TYPE_END_MARKER;
}
if (IN_SET(symbol->symbol_type, VARLINK_STRUCT_TYPE, VARLINK_ENUM_TYPE) && varlink_symbol_is_empty(symbol))
return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol '%s' is empty, refusing.", symbol_name);
+ if (IN_SET(symbol->symbol_type, _VARLINK_SYMBOL_COMMENT, _VARLINK_INTERFACE_COMMENT))
+ return 0;
+
for (const VarlinkField *field = symbol->fields; field->field_type != _VARLINK_FIELD_TYPE_END_MARKER; field++) {
+
+ if (field->field_type == _VARLINK_FIELD_COMMENT) {
+ if (!varlink_idl_comment_is_valid(field->name))
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Comment in symbol '%s' not valid, refusing.", symbol_name);
+
+ continue;
+ }
+
Set **name_set = field->field_direction == VARLINK_OUTPUT ? &output_set : &input_set; /* for the method case we need two separate sets, otherwise we use the same */
if (!varlink_idl_field_name_is_valid(field->name))
for (const VarlinkSymbol *const *symbol = interface->symbols; *symbol; symbol++) {
+ if (IN_SET((*symbol)->symbol_type, _VARLINK_SYMBOL_COMMENT, _VARLINK_INTERFACE_COMMENT)) {
+ if (!varlink_idl_comment_is_valid((*symbol)->name))
+ return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Comment in interface '%s' not valid, refusing.", interface->name);
+ continue;
+ }
+
if (!varlink_idl_symbol_name_is_valid((*symbol)->name))
return varlink_idl_log_full(level, SYNTHETIC_ERRNO(EUCLEAN), "Symbol name '%s' is not valid, refusing.", strempty((*symbol)->name));
break;
+ case _VARLINK_FIELD_COMMENT:
+ break;
+
default:
assert_not_reached();
}
int r;
assert(field);
+ assert(field->field_type != _VARLINK_FIELD_COMMENT);
if (!v || sd_json_variant_is_null(v)) {
return r;
}
} else {
-
r = varlink_idl_validate_field_element_type(field, v);
if (r < 0)
return r;
int r;
assert(symbol);
+ assert(!IN_SET(symbol->symbol_type, _VARLINK_SYMBOL_COMMENT, _VARLINK_INTERFACE_COMMENT));
if (!v) {
if (bad_field)
break;
}
+ case _VARLINK_SYMBOL_COMMENT:
+ case _VARLINK_INTERFACE_COMMENT:
+ break;
+
default:
assert_not_reached();
}
assert(interface);
assert(type < _VARLINK_SYMBOL_TYPE_MAX);
+ assert(!IN_SET(type, _VARLINK_SYMBOL_COMMENT, _VARLINK_INTERFACE_COMMENT));
if (isempty(name))
return NULL;
if (isempty(name))
return NULL;
- for (const VarlinkField *field = symbol->fields; field->field_type != _VARLINK_FIELD_TYPE_END_MARKER; field++)
+ for (const VarlinkField *field = symbol->fields; field->field_type != _VARLINK_FIELD_TYPE_END_MARKER; field++) {
+ if (field->field_type == _VARLINK_FIELD_COMMENT)
+ continue;
+
if (streq_ptr(field->name, name))
return field;
+ }
return NULL;
}