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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
|
# -*- coding: UTF-8 -*-
# Copyright 2015 Red Hat, Inc.
# Part of clufter project
# Licensed under GPLv2+ (a copy included | http://gnu.org/licenses/gpl-2.0.txt)
"""cmd-wrap filter"""
__author__ = "Jan Pokorný <jpokorny @at@ Red Hat .dot. com>"
from ..filter import Filter
from ..formats.command import command
from logging import getLogger
from os import getenv
from sys import maxint
from textwrap import TextWrapper
log = getLogger(__name__)
def cmd_args_cutter(itemgroups):
if not itemgroups:
return itemgroups
ret, acc = [], []
cmd = itemgroups[0][0] if itemgroups[0] else ""
for i in itemgroups:
if len(i) > 1 and (not(i[0].startswith('-')) or i[0] == '-'):
if cmd.endswith('pcs'):
pos = -1
end = len(i)
while pos + 1 < end:
pos += 1
# try to cut into "firm groups"
if pos <= end - 4:
if i[pos:pos + 2] in (("resource", "create"),
("stonith", "create")):
# "resource/stonith create X Y" firm group
ret.extend(
filter(bool, (tuple(acc), tuple(i[pos:pos + 4])))
)
pos += 4
acc = list(i[pos:pos])
pos += len(acc) - 1
continue
if pos <= end - 3:
if i[pos:pos + 2] in (('property', 'set'),
('property', 'unset')):
# "property set/unset non-option [non-option...]"
ret.extend(filter(bool, (tuple(acc), )))
acc = list(i[pos:pos + 2])
pos += len(acc) - 1
continue
if pos <= end - 2:
if i[pos] in ("op", "meta"):
# "op/meta non-option [non-option...]"
ret.extend(filter(bool, (tuple(acc), )))
acc = list(i[pos:pos + 1])
pos += len(acc) - 1
continue
# TBD
acc.append(i[pos])
ret.append(tuple(acc))
acc = []
else:
ret.extend((ii, ) for ii in i)
else:
ret.append(i)
return ret
@Filter.deco('string-iter', 'string-iter')
def cmd_wrap(flt_ctxt, in_obj):
"""Try to apply a bit smarter wrapping on lines carrying shell commands
Smarter means:
- standard textwrap module logic applied on comments splitting; otherwise:
- do not delimit option from its argument
- when line is broken vertically, append backslash for the continuation
and indent subsequent lines for visual clarity
- and as a side-effect: normalize whitespace occurrences
Width used for rewrapping is based on three factors in precedence order:
- text_width value inside the filter context (`flt_ctxt`)
- 0 ~ fall-through to the value per the next item
- -1 ~ apply no wrapping at all (mimicked by implying huge text_width)
- positive ~ hard-limit the width (no smartness involved)
- negative ~ high-limit the width, apply the inverse value only when not
exceeding the value per the next item
- COLUMNS environmental variable, if defined and possesses integer value
- hard-coded default of 72
If absolute value at this point is lower than 20, fallback to 20.
"""
try:
tw_system = int(getenv('COLUMNS'))
except TypeError:
tw_system = 0
try:
tw = int(flt_ctxt.get('text_width'))
if not tw:
raise TypeError
elif tw < -1:
tw = -tw
tw = tw if not tw_system or tw < tw_system else tw_system
elif tw == -1:
tw = maxint >> 1 # one order of magnitude less to avoid overflows
except TypeError:
tw = tw_system
if tw < 20: # watch out for deliberate lower limit
tw = 20 if tw else 72
log.info('Text width fallback: {0}'.format(tw))
cw = TextWrapper(width=tw, subsequent_indent='# ') # wrapper for comments
ret, continuation = [], []
for line in in_obj('stringiter', protect_safe=True):
if line.lstrip().startswith('#'):
ret.extend(cw.wrap(line))
continue
# rough overapproximation of what is indeed a line continuation
if line.endswith('\\') and not line.endswith('\\\\'):
if '#' not in line:
continuation.append(line[:-1])
continue
line += '\\' # XXX
line = ' '.join(continuation) + line
continuation = []
linecnt, rline, remains = -1, [], tw - 2 # ' \'
itemgroups = cmd_args_cutter(command('bytestring', line)('separated'))
itemgroups.reverse()
while itemgroups:
itemgroup = list(itemgroups.pop())
itemgroup.reverse()
while itemgroup:
curlen = 0
line = [itemgroup.pop()]
curlen += len(line[-1])
# best match fill
while itemgroup \
and remains - (curlen + 1 + len(itemgroup[-1])) >= 0:
line.append(itemgroup.pop())
curlen += 1 + len(line[-1])
# compensate for ' \' tail not necessary if very last item fits
if not itemgroups and len(itemgroup) == 1 \
and len(itemgroup[-1]) == 1:
line.append(itemgroup.pop())
curlen += 1 + len(line[-1])
# merge previous group to the current one if it fits the length
if rline and not itemgroup \
and remains - (curlen + 1 + len(' '.join(rline))) >= 0:
line = rline + line
rline = []
linecnt -= 1
# second pass optionally handles the terminal propagation
for i in xrange(2):
if rline:
tail = ' \\' if rline is not line else ''
rline = ' '.join(rline)
if not linecnt:
ret.append(rline + tail)
remains -= 2 # initial indent
else:
ret.append(' ' + rline + tail)
linecnt += 1
rline = line
if itemgroups or itemgroup:
break
return ('stringiter', ret)
|