New format sequence "%nL" to display the most recently written line (#121).

This commit is contained in:
Andrew Wood
2024-12-07 23:12:41 +00:00
parent c3648af57f
commit 619a84949f
14 changed files with 252 additions and 120 deletions
+10 -1
View File
@@ -35,6 +35,7 @@ typedef enum {
PV_COMPONENT_NAME, /* name prefix */
PV_COMPONENT_BUFPERCENT, /* percentage of buffer used */
PV_COMPONENT_OUTPUTBUF, /* recent bytes in output buffer */
PV_COMPONENT_PREVLINE, /* most recent complete line */
PV_COMPONENT__MAX
} pv_display_component;
@@ -56,6 +57,7 @@ typedef enum {
#define PV_SIZEOF_DEFAULT_FORMAT 512
#define PV_SIZEOF_CWD 4096
#define PV_SIZEOF_LASTOUTPUT_BUFFER 256
#define PV_SIZEOF_PREVLINE_BUFFER 1024
#define PV_FORMAT_ARRAY_MAX 100
#define PV_SIZEOF_CRS_LOCK_FILE 1024
@@ -202,13 +204,20 @@ struct pvstate_s {
bool required; /* true if included in format */
} component[PV_COMPONENT__MAX];
/* The last-output "n" bytes. */
char lastoutput_buffer[PV_SIZEOF_LASTOUTPUT_BUFFER];
/* The most recently output complete line. */
char previous_line[PV_SIZEOF_PREVLINE_BUFFER];
/* The line being received now. */
char next_line[PV_SIZEOF_PREVLINE_BUFFER];
/*@only@*/ /*@null@*/ char *display_buffer; /* buffer for display string */
size_t display_buffer_size; /* size allocated to display buffer */
size_t display_string_len; /* length of string in display buffer */
off_t initial_offset; /* offset when first opened (when watching fds) */
size_t lastoutput_bytes; /* largest number of last-output bytes to show */
size_t next_line_len; /* length of currently receiving line so far */
size_t format_segment_count; /* number of format string segments */
@@ -307,7 +316,7 @@ struct pvstate_s {
off_t transferred; /* amount transferred (written - unconsumed) */
/* Keep track of line positions to backtrack written_but_not_consumed. */
/*@null@*/ off_t *line_positions; /* line separator write positions (circular buffer) */
/*@only@*/ /*@null@*/ off_t *line_positions; /* line separator write positions (circular buffer) */
size_t line_positions_capacity; /* total size of line position array */
size_t line_positions_length; /* number of positions stored in array */
size_t line_positions_head; /* index to use for next position */
+49 -15
View File
@@ -491,7 +491,8 @@ static void pv__format_init(pvstate_t state, /*@null@ */ const char *format_supp
/*
* Check for a numeric prefix between the % and the
* format character - currently only used with "%A".
* format character - currently only used with "%A"
* and "%L".
*/
#if HAVE_STRTOUL
number_end_ptr = NULL;
@@ -535,6 +536,12 @@ static void pv__format_init(pvstate_t state, /*@null@ */ const char *format_supp
if (display->lastoutput_bytes < chosen_size)
display->lastoutput_bytes = chosen_size;
break;
case 'L':
seg_type = PV_COMPONENT_PREVLINE;
if (number_prefix > PV_SIZEOF_PREVLINE_BUFFER)
number_prefix = PV_SIZEOF_PREVLINE_BUFFER;
chosen_size = (size_t) number_prefix;
break;
case 'r':
seg_type = PV_COMPONENT_RATE;
break;
@@ -631,7 +638,7 @@ bool pv_format(pvstate_t state, /*@null@ */ const char *format_supplied, pvdispl
bool final)
{
long eta;
int static_portion_size, dynamic_segment_count;
int static_portion_size, dynamic_segment_count, dynamic_segment_width;
pv_display_component component_type;
size_t segment;
size_t new_display_string_len;
@@ -1028,6 +1035,18 @@ bool pv_format(pvstate_t state, /*@null@ */ const char *format_supplied, pvdispl
component_content[buf_idx] = '\0';
break;
case PV_COMPONENT_PREVLINE:
/* Most recent line. */
for (buf_idx = 0;
buf_idx < PV_SIZEOF_PREVLINE_BUFFER - 1 && buf_idx < PV_SIZEOF_COMPONENT_STR - 1;
buf_idx++) {
int display_char;
display_char = (int) (display->previous_line[buf_idx]);
component_content[buf_idx] = isprint(display_char) ? (char) display_char : ' ';
}
component_content[buf_idx] = '\0';
break;
default:
break;
}
@@ -1054,6 +1073,10 @@ bool pv_format(pvstate_t state, /*@null@ */ const char *format_supplied, pvdispl
} else if (display->format[segment].type == PV_COMPONENT_PROGRESS) {
dynamic_segment_count++;
debug("segment[%d] type:%d dynamic", segment, display->format[segment].type);
} else if (display->format[segment].type == PV_COMPONENT_PREVLINE
&& 0 == display->format[segment].chosen_size) {
dynamic_segment_count++;
debug("segment[%d] type:%d dynamic", segment, display->format[segment].type);
} else {
size_t segment_width = display->format[segment].chosen_size;
if (0 == segment_width)
@@ -1066,6 +1089,14 @@ bool pv_format(pvstate_t state, /*@null@ */ const char *format_supplied, pvdispl
debug("static_portion_size: %d", static_portion_size);
debug("dynamic_segment_count: %d", dynamic_segment_count);
dynamic_segment_width = (int) (state->control.width) - static_portion_size;
/*
* Divide the total remaining screen space by the number of dynamic
* segments, so that multiple dynamic segments will share the space.
*/
if (dynamic_segment_count > 1)
dynamic_segment_width /= dynamic_segment_count;
/*
* Assemble the progress bar now we know how big it should be.
*/
@@ -1073,7 +1104,7 @@ bool pv_format(pvstate_t state, /*@null@ */ const char *format_supplied, pvdispl
char *component_content;
size_t component_buf_size;
char after_bar[32]; /* flawfinder: ignore - only populated by pv_snprintf(). */
int component_width, bar_area_width, filled_bar_width, pad_count;
int bar_area_width, filled_bar_width, pad_count;
component_content = display->component[PV_COMPONENT_PROGRESS].content;
component_content[0] = '\0';
@@ -1081,13 +1112,6 @@ bool pv_format(pvstate_t state, /*@null@ */ const char *format_supplied, pvdispl
memset(after_bar, 0, sizeof(after_bar));
component_width = (int) (state->control.width) - static_portion_size;
/*
* Divide the total remaining screen space by the number of dynamic segments.
*/
if (dynamic_segment_count > 1)
component_width /= dynamic_segment_count;
if (state->control.size > 0 || state->control.rate_gauge) {
/*
* Known size, or rate gauge mode. Show a bar, and
@@ -1125,7 +1149,7 @@ bool pv_format(pvstate_t state, /*@null@ */ const char *format_supplied, pvdispl
after_bar_width = strlen(after_bar); /* flawfinder: ignore */
/* flawfinder: always \0-terminated by pv_snprintf() and the earlier memset(). */
bar_area_width = component_width - (int) (after_bar_width) - 2;
bar_area_width = dynamic_segment_width - (int) (after_bar_width) - 2;
if (bar_area_width < 0)
bar_area_width = 0;
@@ -1163,7 +1187,7 @@ bool pv_format(pvstate_t state, /*@null@ */ const char *format_supplied, pvdispl
int indicator_position = state->calc.percentage;
bar_area_width = component_width - 5;
bar_area_width = dynamic_segment_width - 5;
if (bar_area_width < 0)
bar_area_width = 0;
@@ -1237,16 +1261,26 @@ bool pv_format(pvstate_t state, /*@null@ */ const char *format_supplied, pvdispl
segment_content = display->component[display->format[segment].type].content;
segment_bytes = display->component[display->format[segment].type].bytes;
/* A chosen_size of 0 for PV_COMPONENT_PREVLINE means dynamic. */
if (0 == chosen_size && PV_COMPONENT_PREVLINE == display->format[segment].type)
chosen_size = (size_t) dynamic_segment_width;
/*
* If the segment's chosen size is smaller than the
* component, show only the last part of it. For
* component, show only the first or last part of
* it, depending on the component type. For
* instance with "show N last output bytes" (%nA),
* if one "n" was 16 and the other 8, then for the 8
* one, we show the last 8 bytes of the 16-byte
* buffer (issue #122).
* buffer (issue #122). With "show previous line",
* we show the first bytes.
*/
if (0 != chosen_size && chosen_size < segment_bytes) {
segment_content += (segment_bytes - chosen_size);
if (PV_COMPONENT_PREVLINE == display->format[segment].type) {
/* No need to move the segment content. */
} else {
segment_content += (segment_bytes - chosen_size);
}
segment_bytes = chosen_size;
}
}
+59 -6
View File
@@ -718,28 +718,42 @@ static int pv__transfer_write(pvstate_t state, bool *eof_in, bool *eof_out, long
}
if (nwritten > 0) {
bool tracking_lines = false;
if ((state->control.linemode) && (lineswritten != NULL))
tracking_lines = true;
else if (state->display.component[PV_COMPONENT_PREVLINE].required)
tracking_lines = true;
/*
* Write returned >0 - data successfully written.
*/
if ((state->control.linemode) && (lineswritten != NULL)) {
if (tracking_lines) {
char separator;
char *ptr;
long lines = 0;
/*
* Line mode - look through what we've just written
* to count how many lines there were.
* Tracking lines - either line mode, or we're
* showing the last line in the display, or both.
* So we need to look through what we've just
* written to either count how many lines there
* were, or get the content of the most recent
* complete line, or both.
*/
/* Allocate buffer to remember line positions. */
if (NULL == state->transfer.line_positions) {
if (NULL == state->transfer.line_positions && NULL != lineswritten) {
state->transfer.line_positions_capacity = MAX_LINE_POSITIONS;
/*@-mustfreeonly@ */
state->transfer.line_positions =
calloc((size_t) (state->transfer.line_positions_capacity), sizeof(off_t));
if (NULL == state->transfer.line_positions) {
pv_error(state, "%s: %s", _("line position buffer allocation failed"),
strerror(errno));
}
/*@+mustfreeonly@ */
/* splint doesn't see we only call calloc() when line_positions is NULL. */
}
if (state->control.null_terminated_lines) {
@@ -752,12 +766,50 @@ static int pv__transfer_write(pvstate_t state, bool *eof_in, bool *eof_out, long
for (ptr++;
ptr - (char *) state->transfer.transfer_buffer - state->transfer.write_position <
(size_t) nwritten; ptr++, state->transfer.last_output_position++) {
if (*ptr != separator)
if (*ptr != separator) {
/*
* If we're displaying the previous
* line ("%L"), add to our line
* buffer.
*/
if (state->display.component[PV_COMPONENT_PREVLINE].required
&& state->display.next_line_len < PV_SIZEOF_PREVLINE_BUFFER - 1) {
state->display.next_line[state->display.next_line_len] = *ptr;
state->display.next_line_len++;
}
continue;
}
/* Separator found - increment line count. */
++lines;
/*
* If we're displaying the previous line
* ("%L"), update the previous-line buffer
* with the line we just completed, and
* start a new one.
*/
if (state->display.component[PV_COMPONENT_PREVLINE].required) {
memset(state->display.previous_line, 0, PV_SIZEOF_PREVLINE_BUFFER);
if (state->display.next_line_len > PV_SIZEOF_PREVLINE_BUFFER - 1)
state->display.next_line_len = PV_SIZEOF_PREVLINE_BUFFER - 1;
if (state->display.next_line_len > 0) {
memcpy(state->display.previous_line, state->display.next_line, /* flawfinder: ignore */
state->display.next_line_len);
debug("%s: [%s]", "updated previous_line",
state->display.previous_line);
}
state->display.next_line_len = 0;
/*
* flawfinder - next_line_len is
* guaranteed to be less than the
* size of the previous_line buffer
* since we check it just before
* memcpy(), and we ensure that the
* last byte in the buffer is \0.
*/
}
if (NULL == state->transfer.line_positions)
continue;
@@ -777,7 +829,8 @@ static int pv__transfer_write(pvstate_t state, bool *eof_in, bool *eof_out, long
}
}
*lineswritten += lines;
if (NULL != lineswritten)
*lineswritten += lines;
}
state->transfer.write_position += nwritten;