New format sequence "%nL" to display the most recently written line (#121).
This commit is contained in:
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user