table

Помоћни програм из командне линије за форматирање и приказ CSV-а
Дневник | Датотеке | Референце | ПРОЧИТАЈМЕ | ЛИЦЕНЦА

чување e9844454182c4ed5dd1957c859c7195a199b6177
родитељ 73a02e71835ca6cefc507e4d916e83d8f9445d96
Аутор: Страхиња Радић <contact@strahinja.org>
Датум:   Sat, 24 Oct 2020 20:51:49 +0200

Merge branch 'wip-2020-10-2'

Diffstat:
MCHANGES | 17+++++++++++++++--
MREADME | 16++++++++++------
MTODO | 13+------------
Mdefs.h | 7+++++++
Mindex.html | 53+++++++++++++++++++++++++++++++++++++++++++++++++++--
Mtable.c | 263++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Mtest/file.sh | 2+-
Atest/ls-2col.sh | 2++
Atest/ls-multicolumn.sh | 2++
Mtest/neofetch.sh | 2+-
Mtest/sales.sc | 2+-
измењених датотека: 11, додавања: 325(+), брисања: 54(-)

diff --git a/CHANGES b/CHANGES @@ -1,12 +1,25 @@ Changelog ========= -* v0.1-alpha +* v0.1.2-beta - - Read and display columns + - Add --border-mode + + - Fix last row in ls -mw30 | table when last input line has only one column + + +* v0.1.1-beta + + - Ignore malformed UTF-8 sequences. Segmentation faulted (test/who-Huw.sh + with LC_ALL=sr_RS.UTF8 and cyrillic user names) - Use "T" connector chars; connector selection similar to -s + +* v0.1-alpha + + - Read and display columns + - Handle tabs - unwrap to spaces at tab stops (test/lsb_release.sh) - Better UTF-8 substring code based on screen columns diff --git a/README b/README @@ -14,21 +14,25 @@ $ make && sudo make install Examples -------- +<<<<<<< HEAD $ watch "table /proc/loadavg -d \" \" -c 50 -s ss" +======= +$ watch "table /proc/loadavg -d ' ' -c 50 -s ss" +>>>>>>> wip-2020-10-2 Periodically display CPU load information in a single-line table -$ ls -l | tail -n +2 | sed -e 's/ \+/ /g' | table -d " " +$ ls -l | tail -n +2 | sed -e 's/ \+/ /g' | table -d ' ' - Format a long list of files in the current directory in a double line table - with single-line column divisors + Format a long list of files in the current directory in a double line + table with single-line column divisors -$ ls -l | tail -n +2 | awk '{ print $1 " " $9 }' | table -d " " -c 30 +$ ls -l | tail -n +2 | awk '{ print $1 " " $9 }' | table -d ' ' -c 30 - Like the above, but only permissions and file names are printed in a narrow - table + Like the above, but only permissions and file names are printed in a + narrow table License diff --git a/TODO b/TODO @@ -1,18 +1,7 @@ Todo ==== -* v0.1-alpha +* Current WIP [ ] Handle tty ANSI codes (test/neofetch.sh) - [x] Ignore malformed UTF-8 sequences. Currently segmentation faults - (test/who-Huw.sh with LC_ALL=sr_RS.UTF8 and cyrillic user names) - - [x] Handle tabs - unwrap to spaces at tab stops (test/lsb_release.sh) - - [x] Read and display columns - - [x] Use "T" connector chars; connector selection similar to -s - - [x] Better UTF-8 substring code based on screen columns - diff --git a/defs.h b/defs.h @@ -41,6 +41,13 @@ // [Max table columns] = ------------------------------------------ // MIN_TABLE_COL_WIDTH #define MIN_TABLE_COL_WIDTH 3 +#define ANSI_SGR_RESET "\e[0m\e[?25h" + +typedef enum +{ + FALSE = 0, + TRUE = 1 +} BOOL; typedef enum { diff --git a/index.html b/index.html @@ -42,6 +42,19 @@ { overflow-x: auto; } + + .example + { + margin-top: 2em; + margin-bottom: 0; + } + + .explanation + { + margin-top: 0; + margin-bottom: 2em; + margin-left: 2em; + } </style> </head> <body> @@ -58,13 +71,49 @@ $ cd table $ make &amp;&amp; sudo make install </pre> + <h2>Examples</h2> + + <pre class="example"> +$ watch "table /proc/loadavg -d ' ' -c 50 -s ss"</pre> + + <p class="explanation">Periodically display CPU load information in a + single-line table</p> + + <pre class="example"> +$ ls -l | tail -n +2 | sed -e 's/ \+/ /g' | table -d ' '</pre> + + <p class="explanation">Format a long list of files in the current + directory in a double line table with single-line column divisors</p> + + <pre class="example"> +$ ls -l | tail -n +2 | awk '{ print $1 " " $9 }' | table -d ' ' -c 30</pre> + + <p class="explanation">Like the above, but only permissions and file + names are printed in a narrow table</p> + + <h2>Changelog</h2> + <h3>v0.1.2-beta</h3> + <ul> + <li>Add --border-mode</li> + <li>Fix last row in ls -mw30 | table when last input line has only + one column</li> + </ul> + + <h3>v0.1.1-beta</h3> + <ul> + <li>Ignore malformed UTF-8 sequences. Segmentation faulted + (test/who-Huw.sh with LC_ALL=sr_RS.UTF8 and cyrillic user + names)</li> + <li>Use "T" connector chars; connector selection similar to -s</li> + </ul> + <h3>v0.1-alpha</h3> <ul> <li>Read and display columns</li> - <li>Use "T" connector chars; connector selection similar to -s</li> - <li>Handle tabs - unwrap to spaces at tab stops (test/lsb_release.sh)</li> + <li>Handle tabs - unwrap to spaces at tab stops + (test/lsb_release.sh)</li> <li>Better UTF-8 substring code based on screen columns</li> </ul> diff --git a/table.c b/table.c @@ -31,6 +31,16 @@ usage() { printf("Usage: %s [options] [filename]\n", PROGRAMNAME); printf(" Where [options] is one or more of the following:\n"); + printf(" -a\n"); + printf(" --ansi\n"); + printf(" ANSI SGR processing in the input: add\n"); + printf(" \\e[0m\\e[?25h\n"); + printf(" to the end of each cell\n"); + printf("\n"); + printf(" -b\n"); + printf(" --border-mode\n"); + printf(" Border mode: force single column\n"); + printf("\n"); printf(" -c <cols>\n"); printf(" --columns=<cols>\n"); printf(" Set maximum table width in columns (default 80)\n"); @@ -141,7 +151,67 @@ u32_substr(uint32_t* src, size_t src_len, size_t start, size_t finish, size_t len = normalized_finish - normalized_start; dest = (uint32_t*) calloc(len+1, sizeof(uint32_t)); + + /* + * if (handle_ansi) + * { + * uint32_t *psrc = src + normalized_start; + * size_t i = 0; + * size_t chars_in_sgr = 0; + * size_t current_sgr_len = 0; + * BOOL in_sgr = FALSE; + * + * while (*psrc && i < len) + * { + * if (*psrc == (uint32_t)'\e' + * && i+1 < len + * && *(psrc+1) == (uint32_t)'[') + * { + * in_sgr = TRUE; + * current_sgr_len = 0; + * psrc++; + * i++; + * } + * else if (in_sgr && ( + * *psrc == (uint32_t)'0' + * || *psrc == (uint32_t)'0' + * || *psrc == (uint32_t)'1' + * || *psrc == (uint32_t)'2' + * || *psrc == (uint32_t)'3' + * || *psrc == (uint32_t)'4' + * || *psrc == (uint32_t)'5' + * || *psrc == (uint32_t)'6' + * || *psrc == (uint32_t)'7' + * || *psrc == (uint32_t)'8' + * || *psrc == (uint32_t)'9' + * || *psrc == (uint32_t)';')) + * { + * current_sgr_len++; + * chars_in_sgr++; + * } + * else if (in_sgr && current_sgr_len > 0 && *psrc == (uint32_t)'m') + * { + * in_sgr = FALSE; + * chars_in_sgr++; + * } + * else if (in_sgr) + * { + * in_sgr = FALSE; + * chars_in_sgr -= current_sgr_len; + * current_sgr_len = 0; + * } + * else + * { + * *dest++ = *psrc; + * i++; + * psrc++; + * } + * } + * } + * else + */ memcpy(dest, src + normalized_start, sizeof(uint32_t) * len); + dest[len] = (uint32_t)'\0'; *dest_len = len; @@ -267,6 +337,9 @@ u32_tabs_to_spaces(const uint32_t* src, const size_t src_len, size_t get_max_table_column_width(size_t rune_columns, size_t table_columns) { + if (rune_columns < table_columns+1) + return MIN_TABLE_COL_WIDTH; + size_t result = (rune_columns-table_columns-1)/table_columns; return result < MIN_TABLE_COL_WIDTH @@ -278,22 +351,24 @@ size_t get_max_last_table_column_width(size_t rune_columns, size_t table_columns) { size_t result = get_max_table_column_width(rune_columns, table_columns); - return rune_columns - (table_columns-1) * (result+1) - 2; + if (rune_columns > (table_columns-1) * (result+1) + 2) + return rune_columns - (table_columns-1) * (result+1) - 2; + return MIN_TABLE_COL_WIDTH; } void u32_print(char* name, uint32_t* src, size_t src_len) { - printf("%s [%lu] = \"", name, src_len); + fprintf(stdout, "%s [%lu] = \"", name, src_len); for (size_t k = 0; k < src_len; k++) { uint8_t charbuf[7]; size_t charbuf_len = 7; u32_to_u8(src+k, 1, charbuf, &charbuf_len); charbuf[charbuf_len] = (uint8_t)'\0'; - printf("%X='%s' ", src[k], charbuf); + fprintf(stdout, "%X='%s' ", src[k], charbuf); } - printf("\"\n"); + fprintf(stdout, "\"\n"); } /* TODO: Column alignment? @@ -324,6 +399,59 @@ u32_print(char* name, uint32_t* src, size_t src_len) *} */ +// Returns the number of uint32_t characters that make up ANSI SGR codes and +// thus don't count towards the string rune column length +size_t +ansi_lookahead(uint32_t* src, size_t src_len) +{ + size_t i = 0; + size_t chars_in_sgr = 0; + size_t current_sgr_len = 0; + BOOL in_sgr = FALSE; + + while (i < src_len) + { + if (src[i] == (uint32_t)'\e' + && i+1 < src_len + && src[i+1] == (uint32_t)'[') + { + in_sgr = TRUE; + current_sgr_len = 0; + i++; + } + else if (in_sgr && (src[i] == (uint32_t)'0' + || src[i] == (uint32_t)'1' + || src[i] == (uint32_t)'2' + || src[i] == (uint32_t)'3' + || src[i] == (uint32_t)'4' + || src[i] == (uint32_t)'5' + || src[i] == (uint32_t)'6' + || src[i] == (uint32_t)'7' + || src[i] == (uint32_t)'8' + || src[i] == (uint32_t)'9' + || src[i] == (uint32_t)';')) + { + current_sgr_len++; + chars_in_sgr++; + } + else if (in_sgr && current_sgr_len > 0 && src[i] == (uint32_t)'m') + { + in_sgr = FALSE; + chars_in_sgr++; + } + else if (in_sgr) + { + // Only count full SGRs, \e[ n [; n...] m + in_sgr = FALSE; + chars_in_sgr -= current_sgr_len; + current_sgr_len = 0; + } + //else... + i++; + } + return chars_in_sgr; +} + int main(int argc, char** argv) { @@ -338,6 +466,8 @@ main(int argc, char** argv) size_t rune_columns = 80; size_t tab_length = 8; uint32_t delimiter = ','; + BOOL handle_ansi = FALSE; + BOOL border_mode = FALSE; while ((arg = *++argv)) { @@ -352,6 +482,17 @@ main(int argc, char** argv) { cmd = CMD_VERSION; } + else if (!strcmp(substr(arg, 0, strlen("ansi")), "ansi")) + { + arg += strlen("ansi"); + handle_ansi = TRUE; + } + else if (!strcmp(substr(arg, 0, strlen("border-mode")), + "border-mode")) + { + arg += strlen("border-mode"); + border_mode = TRUE; + } else if (!strcmp(substr(arg, 0, strlen("columns=")), "columns=")) { arg += strlen("columns="); @@ -389,6 +530,12 @@ main(int argc, char** argv) { switch (c) { + case 'a': + handle_ansi = TRUE; + break; + case 'b': + border_mode = TRUE; + break; case 'c': cmd = CMD_COLUMNS; break; @@ -469,6 +616,8 @@ main(int argc, char** argv) uint32_t* unicode_buffer = NULL; size_t unicode_buffer_len = 0; + size_t sgr_chars = 0; + size_t col; do @@ -498,19 +647,39 @@ main(int argc, char** argv) { fprintf(stdout, " "); } + if (handle_ansi) + fprintf(stdout, "%s", ANSI_SGR_RESET); fprintf(stdout, "%s\n", table_symbols[current_symbol_set][5]); continue; } if (!table_columns) { - table_columns = number_of_columns(unicode_line, - unicode_line_len, delimiter); + // Sanity check + if (rune_columns < MIN_TABLE_COL_WIDTH + 2) + rune_columns = MIN_TABLE_COL_WIDTH + 2; + + table_columns = border_mode + ? 1 + : number_of_columns(unicode_line, + unicode_line_len, delimiter); max_table_column_width = get_max_table_column_width(rune_columns, table_columns); max_last_table_column_width = get_max_last_table_column_width(rune_columns, table_columns); + // Sanity check + if (rune_columns < (table_columns-1) * max_table_column_width + + max_last_table_column_width + table_columns + 1) + { + rune_columns = (table_columns-1) * max_table_column_width + + max_last_table_column_width + table_columns + 1; + max_table_column_width = + get_max_table_column_width(rune_columns, table_columns); + max_last_table_column_width = + get_max_last_table_column_width(rune_columns, table_columns); + } + fprintf(stdout, "%s", table_symbols[current_symbol_set][0]); col = 0; // |---|---|---|---|---| @@ -564,7 +733,6 @@ main(int argc, char** argv) && current_table_column < table_columns-1 && unicode_line[rune_column] == delimiter) { - char temp[255]; column_width = max_table_column_width; fprintf(stdout, "%s", @@ -595,14 +763,25 @@ main(int argc, char** argv) if (buffer && buffer_len>0) { fprintf(stdout, "%s", buffer); - written_columns += column_width; } - for (size_t i = 0; i < max_table_column_width-column_width; i++) + if (handle_ansi) + sgr_chars = ansi_lookahead(unicode_buffer, column_width); + for (size_t i = 0; i + column_width < + max_table_column_width + sgr_chars; i++) { fprintf(stdout, " "); } - written_columns += max_table_column_width-column_width; + if (handle_ansi) + fprintf(stdout, "%s", ANSI_SGR_RESET); + + written_columns += max_table_column_width; + /* + *printf("wc = %lu, sc = %lu, bl = %lu\n", + * written_columns, + * sgr_chars, + * buffer_len); + */ substr_start = rune_column+1; current_table_column++; @@ -650,12 +829,17 @@ main(int argc, char** argv) } // Not the last column - if (current_table_column < table_columns-1) + if (current_table_column+1 < table_columns) { - for (size_t i = 0; i < max_table_column_width-column_width; i++) + if (handle_ansi) + sgr_chars = ansi_lookahead(unicode_buffer, column_width); + for (size_t i = 0; i < + max_table_column_width-column_width+sgr_chars; i++) { fprintf(stdout, " "); } + if (handle_ansi) + fprintf(stdout, "%s", ANSI_SGR_RESET); written_columns += max_table_column_width - column_width; for (size_t leftover_column = 0; @@ -667,7 +851,7 @@ main(int argc, char** argv) size_t table_column_width = max_table_column_width; if (leftover_column == table_columns-current_table_column-2) - table_column_width = rune_columns - written_columns - 2; + table_column_width = max_last_table_column_width; for (size_t i = 0; i < table_column_width; i++) fprintf(stdout, " "); } @@ -679,6 +863,8 @@ main(int argc, char** argv) { fprintf(stdout, " "); } + if (handle_ansi) + fprintf(stdout, "%s", ANSI_SGR_RESET); } written_columns = rune_columns; @@ -698,7 +884,7 @@ main(int argc, char** argv) substr_start, substr_finish, &unicode_buffer_len); - if (unicode_buffer_len-1 < column_width) + if (unicode_buffer_len < column_width + 1) column_width = unicode_buffer_len-1; if (buffer) @@ -707,36 +893,55 @@ main(int argc, char** argv) buffer_len = BUFSIZE; u32_to_u8(unicode_buffer, column_width, buffer, &buffer_len); + /* + *printf("\n"); + *u32_print("unicode_buffer", unicode_buffer, column_width); + *printf("\n"); + */ if (buffer && buffer_len>0) { fprintf(stdout, "%s", buffer); } - for (size_t i = 0; i < rune_columns-column_width-table_columns-1; i++) + if (handle_ansi) + sgr_chars = ansi_lookahead(unicode_buffer, column_width); + for (size_t i = 0; i + column_width + table_columns + 1 < + rune_columns + sgr_chars; i++) { fprintf(stdout, " "); } + /* + *printf("cw = %lu, sc = %lu, bl = %lu\n", + * column_width, + * sgr_chars, + * buffer_len); + */ } + if (handle_ansi) + fprintf(stdout, "%s", ANSI_SGR_RESET); fprintf(stdout, "%s\n", table_symbols[current_symbol_set][5]); } } while (!feof(input)); - fprintf(stdout, "%s", table_symbols[current_symbol_set][6]); - size_t table_column = 0; - col = 0; - while (col++ < rune_columns-2) - if (table_column < table_columns-1 - && col % (max_table_column_width+1) == 0) - { - fprintf(stdout, "%s", - table_inner_symbols[current_inner_symbol_set][2]); - table_column++; - } - else - fprintf(stdout, "%s", table_symbols[current_symbol_set][7]); - fprintf(stdout, "%s\n", table_symbols[current_symbol_set][8]); + if (table_columns) + { + fprintf(stdout, "%s", table_symbols[current_symbol_set][6]); + size_t table_column = 0; + col = 0; + while (col++ < rune_columns-2) + if (table_column < table_columns-1 + && col % (max_table_column_width+1) == 0) + { + fprintf(stdout, "%s", + table_inner_symbols[current_inner_symbol_set][2]); + table_column++; + } + else + fprintf(stdout, "%s", table_symbols[current_symbol_set][7]); + fprintf(stdout, "%s\n", table_symbols[current_symbol_set][8]); + } return 0; } diff --git a/test/file.sh b/test/file.sh @@ -1,2 +1,2 @@ #!/bin/sh -file ~/Downloads/* | sed -e's/ \+/ /g' | table -d : -c 110 +file * | sed -e's/ \+/ /g' | table -d : -c 110 diff --git a/test/ls-2col.sh b/test/ls-2col.sh @@ -0,0 +1,2 @@ +#!/bin/sh +ls -l | tail -n +2 | sed -e's/ \+/ /g' | cut -d' ' -f1,9 | table -d " " -c 30 diff --git a/test/ls-multicolumn.sh b/test/ls-multicolumn.sh @@ -0,0 +1,2 @@ +#!/bin/sh +ls -m /bin | sed -e's/,$//g' | table -c 160 diff --git a/test/neofetch.sh b/test/neofetch.sh @@ -2,4 +2,4 @@ SRCDIR=. -neofetch | $SRCDIR/table +neofetch | $SRCDIR/table -a -b diff --git a/test/sales.sc b/test/sales.sc @@ -39,4 +39,4 @@ leftstring A10 = "Просек" let B10 = @avg(B7:B9) let C10 = @avg(C7:C9) let D10 = @avg(D7:D9) -goto A10 +goto A0