-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhcat
executable file
·85 lines (76 loc) · 2.68 KB
/
hcat
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#!/usr/bin/env python3
"Horizontally concatenate files."
import argparse
from sys import stdin
from typing import Iterable, Optional
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
'file', nargs='+',
help="Accepts - for stdin (noninteractive)")
parser.add_argument(
'--no-color', dest='skip_color_codes', action='store_true',
help="Do not ignore invisible color codes, and count all characters."
" This may affect column length, and has negligible speedup.")
parser.add_argument(
'--sep', default=' ',
help="Column separator. Default is a single space.")
parser.add_argument(
'--widths', '-w', nargs='+', default=[],
help="Widths of each file (useful if you're using color styling etc)."
" Accepts - to automatically determine."
" Does not have to match nunber of files.")
def visible_char_count(string: str) -> int:
'''Number of visible characters in a string, Terminal color codes notwithstanding.'''
# This has been tested with `viu` and `lolcat`.
# Hopefully it catches other fanciful formatting
ESC = chr(27)
ESC_FINISH_COL_CODE = 'm'
n = 0
is_visible = 1
for c in string:
if c == ESC:
is_visible = 0
continue
elif not is_visible and c == ESC_FINISH_COL_CODE:
is_visible = 1
continue
n += is_visible
return n
def horizontal_concat(
strings: Iterable[str],
col_widths: Iterable[Optional[int]],
skip_color_codes: bool = True,
sep=' ',
) -> str:
length = len if skip_color_codes else visible_char_count
# split (and remove trailing newline)
col_rows = [s.removesuffix('\n').split('\n') for s in strings] # list[column]
# get lengths for filling blanks:
# zip(*x) is only good on rectangular transpose!
max_row_count = max(map(len, col_rows))
actual_col_widths = list(map(
lambda col: max(map(length, col)),
col_rows))
# (args override)
for i, w in enumerate(col_widths):
if w is not None:
actual_col_widths[i] = w
# fill with blanks
col_rows = [
[row.ljust(width) for row in rows]
+ [' '*width] * (max_row_count - len(rows))
for rows, width in zip(col_rows, actual_col_widths)
]
row_cols = zip(*col_rows) # transpile
return '\n'.join(
sep.join(row)
for row in row_cols
)
if __name__ == '__main__':
args = parser.parse_args()
print(horizontal_concat(
[(stdin if path == '-' else open(path)).read() for path in args.file],
col_widths=[None if width == '-' else int(width) for width in args.widths],
skip_color_codes=args.skip_color_codes,
sep=args.sep,
))