comparison frontends/src/jp/cmd_input.py @ 2282:d8e48c850ad2

jp (input): log improvments + empty filter: - more colors in debug logs - read values are printed if verbosity is >= 2 - stdout and stderr of each command can be written in files using --log and --log-err - --empty option allow to ignore arguments if its value is empty, or skip the whole row
author Goffi <goffi@goffi.org>
date Wed, 28 Jun 2017 23:49:55 +0200
parents 489efbda377c
children cbc989508474
comparison
equal deleted inserted replaced
2281:4af1805cc6df 2282:d8e48c850ad2
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 19
20 20
21 import base 21 import base
22 from sat.core.i18n import _ 22 from sat.core.i18n import _
23 from sat.core import exceptions
23 from sat_frontends.jp.constants import Const as C 24 from sat_frontends.jp.constants import Const as C
24 from sat.tools.common.ansi import ANSI as A 25 from sat.tools.common.ansi import ANSI as A
25 import subprocess 26 import subprocess
26 import argparse 27 import argparse
27 import os
28 import sys 28 import sys
29 29
30 __commands__ = ["Input"] 30 __commands__ = ["Input"]
31 OPT_STDIN = 'stdin' 31 OPT_STDIN = 'stdin'
32 OPT_SHORT = 'short' 32 OPT_SHORT = 'short'
33 OPT_LONG = 'long' 33 OPT_LONG = 'long'
34 OPT_POS = 'positional' 34 OPT_POS = 'positional'
35 OPT_IGNORE = 'ignore' 35 OPT_IGNORE = 'ignore'
36 OPT_TYPES = (OPT_STDIN, OPT_SHORT, OPT_LONG, OPT_POS, OPT_IGNORE) 36 OPT_TYPES = (OPT_STDIN, OPT_SHORT, OPT_LONG, OPT_POS, OPT_IGNORE)
37 OPT_EMPTY_SKIP = 'skip'
38 OPT_EMPTY_IGNORE = 'ignore'
39 OPT_EMPTY_CHOICES = (OPT_EMPTY_SKIP, OPT_EMPTY_IGNORE)
37 40
38 41
39 class InputCommon(base.CommandBase): 42 class InputCommon(base.CommandBase):
40 43
41 def __init__(self, host, name, help): 44 def __init__(self, host, name, help):
46 def reset(self): 49 def reset(self):
47 self.args_idx = 0 50 self.args_idx = 0
48 self._stdin = [] 51 self._stdin = []
49 self._opts = [] 52 self._opts = []
50 self._pos = [] 53 self._pos = []
54 self._values_ori = []
51 55
52 def add_parser_options(self): 56 def add_parser_options(self):
53 self.parser.add_argument("--encoding", default='utf-8', help=_(u"encoding of the input data")) 57 self.parser.add_argument("--encoding", default='utf-8', help=_(u"encoding of the input data"))
54 self.parser.add_argument("-i", "--stdin", action='append_const', const=(OPT_STDIN, None), dest='arguments', help=_(u"standard input")) 58 self.parser.add_argument("-i", "--stdin", action='append_const', const=(OPT_STDIN, None), dest='arguments', help=_(u"standard input"))
55 self.parser.add_argument("-s", "--short", type=self.opt(OPT_SHORT), action='append', dest='arguments', help=_(u"short option")) 59 self.parser.add_argument("-s", "--short", type=self.opt(OPT_SHORT), action='append', dest='arguments', help=_(u"short option"))
56 self.parser.add_argument("-l", "--long", type=self.opt(OPT_LONG), action='append', dest='arguments', help=_(u"long option")) 60 self.parser.add_argument("-l", "--long", type=self.opt(OPT_LONG), action='append', dest='arguments', help=_(u"long option"))
57 self.parser.add_argument("-p", "--positional", type=self.opt(OPT_POS), action='append', dest='arguments', help=_(u"positional argument")) 61 self.parser.add_argument("-p", "--positional", type=self.opt(OPT_POS), action='append', dest='arguments', help=_(u"positional argument"))
58 self.parser.add_argument("-x", "--ignore", action='append_const', const=(OPT_IGNORE, None), dest='arguments', help=_(u"ignore value")) 62 self.parser.add_argument("-x", "--ignore", action='append_const', const=(OPT_IGNORE, None), dest='arguments', help=_(u"ignore value"))
59 self.parser.add_argument("-D", "--debug", action='store_true', help=_(u"don't actually run commands but echo what would be launched")) 63 self.parser.add_argument("-D", "--debug", action='store_true', help=_(u"don't actually run commands but echo what would be launched"))
64 self.parser.add_argument("--log", type=argparse.FileType('wb'), help=_(u"log stdout to FILE"))
65 self.parser.add_argument("--log-err", type=argparse.FileType('wb'), help=_(u"log stderr to FILE"))
60 self.parser.add_argument("command", nargs=argparse.REMAINDER) 66 self.parser.add_argument("command", nargs=argparse.REMAINDER)
61 67
62 def opt(self, type_): 68 def opt(self, type_):
63 return lambda s: (type_, s) 69 return lambda s: (type_, s)
64 70
65 def addValue(self, value): 71 def addValue(self, value):
66 """add a parsed value according to arguments sequence""" 72 """add a parsed value according to arguments sequence"""
73 self._values_ori.append(value)
67 arguments = self.args.arguments 74 arguments = self.args.arguments
68 try: 75 try:
69 arg_type, arg_name = arguments[self.args_idx] 76 arg_type, arg_name = arguments[self.args_idx]
70 except IndexError: 77 except IndexError:
71 self.disp(_(u"arguments in input data and in arguments sequence don't match"), error=True) 78 self.disp(_(u"arguments in input data and in arguments sequence don't match"), error=True)
72 self.host.quit(C.EXIT_DATA_ERROR) 79 self.host.quit(C.EXIT_DATA_ERROR)
73 self.args_idx += 1 80 self.args_idx += 1
74 while self.args_idx < len(arguments): 81 while self.args_idx < len(arguments):
75 next_arg = arguments[self.args_idx] 82 next_arg = arguments[self.args_idx]
76 if next_arg[0] not in OPT_TYPES: 83 if next_arg[0] not in OPT_TYPES:
77 # we have a filter 84 # value will not be used if False or None, so we skip filter
78 filter_type, filter_arg = arguments[self.args_idx] 85 if value not in (False, None):
79 value = self.filter(filter_type, filter_arg, value) 86 # we have a filter
87 filter_type, filter_arg = arguments[self.args_idx]
88 value = self.filter(filter_type, filter_arg, value)
80 else: 89 else:
81 break 90 break
82 self.args_idx += 1 91 self.args_idx += 1
92
93 if value is None:
94 # we ignore this argument
95 return
96
97 if value is False:
98 # we skip the whole row
99 if self.args.debug:
100 self.disp(A.color(C.A_SUBHEADER, _(u'values: '), A.RESET, u', '.join(self._values_ori)), 2)
101 self.disp(A.color(A.BOLD, _(u'**SKIPPING**\n')))
102 self.reset()
103 self.idx += 1
104 raise exceptions.CancelError
83 105
84 if not isinstance(value, list): 106 if not isinstance(value, list):
85 value = [value] 107 value = [value]
86 108
87 for v in value: 109 for v in value:
104 def runCommand(self): 126 def runCommand(self):
105 """run requested command with parsed arguments""" 127 """run requested command with parsed arguments"""
106 if self.args_idx != len(self.args.arguments): 128 if self.args_idx != len(self.args.arguments):
107 self.disp(_(u"arguments in input data and in arguments sequence don't match"), error=True) 129 self.disp(_(u"arguments in input data and in arguments sequence don't match"), error=True)
108 self.host.quit(C.EXIT_DATA_ERROR) 130 self.host.quit(C.EXIT_DATA_ERROR)
131 self.disp(A.color(C.A_HEADER, _(u'command {idx}').format(idx=self.idx)), no_lf=not self.args.debug)
109 stdin = ''.join(self._stdin) 132 stdin = ''.join(self._stdin)
110 if self.args.debug: 133 if self.args.debug:
111 self.disp(_(u'command {idx}').format(idx=self.idx)) 134 self.disp(A.color(C.A_SUBHEADER, _(u'values: '), A.RESET, u', '.join(self._values_ori)), 2)
135
112 if stdin: 136 if stdin:
113 self.disp(u'--- STDIN ---') 137 self.disp(A.color(C.A_SUBHEADER, u'--- STDIN ---'))
114 self.disp(stdin.decode('utf-8')) 138 self.disp(stdin.decode('utf-8'))
115 self.disp(u'-------------') 139 self.disp(A.color(C.A_SUBHEADER, u'-------------'))
116 self.disp(u'{indent}{prog} {static} {options} {positionals}'.format( 140 self.disp(u'{indent}{prog} {static} {options} {positionals}'.format(
117 indent = 4*u' ', 141 indent = 4*u' ',
118 prog=sys.argv[0], 142 prog=sys.argv[0],
119 static = ' '.join(self.args.command).encode('utf-8'), 143 static = ' '.join(self.args.command).encode('utf-8'),
120 options = u' '.join([o.encode('utf-8') for o in self._opts]), 144 options = u' '.join([o.encode('utf-8') for o in self._opts]),
121 positionals = u' '.join([p.encode('utf-8') for p in self._pos]) 145 positionals = u' '.join([p.encode('utf-8') for p in self._pos])
122 )) 146 ))
123 self.disp(u'') 147 self.disp(u'\n')
124 else: 148 else:
125 self.disp(_(u'command {idx}').format(idx=self.idx), no_lf=True) 149 self.disp(u' (' + u', '.join(self._values_ori) + u')', 2, no_lf=True)
126 with open(os.devnull, 'wb') as DEVNULL: 150 args = [sys.argv[0]] + self.args.command + self._opts + self._pos
127 p = subprocess.Popen([sys.argv[0]] + self.args.command + self._opts + self._pos, 151 p = subprocess.Popen(args,
128 stdin=subprocess.PIPE, stdout=DEVNULL, stderr=DEVNULL) 152 stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
129 p.communicate(stdin) 153 (stdout, stderr) = p.communicate(stdin)
130 ret = p.wait() 154 log = self.args.log
155 log_err = self.args.log_err
156 log_tpl = '{command}\n{buff}\n\n'
157 if log:
158 log.write(log_tpl.format(command=' '.join(args), buff=stdout))
159 if log_err:
160 log_err.write(log_tpl.format(command=' '.join(args), buff=stderr))
161 ret = p.wait()
131 if ret == 0: 162 if ret == 0:
132 self.disp(A.color(C.A_SUCCESS, _(u'OK'))) 163 self.disp(A.color(C.A_SUCCESS, _(u'OK')))
133 else: 164 else:
134 self.disp(A.color(C.A_FAILURE, _(u'FAILED'))) 165 self.disp(A.color(C.A_FAILURE, _(u'FAILED')))
135 166
136 self.reset() 167 self.reset()
137 self.idx += 1 168 self.idx += 1
138 169
139 def filter(self, filter_type, filter_arg, value): 170 def filter(self, filter_type, filter_arg, value):
171 """change input value
172
173 @param filter_type(unicode): name of the filter
174 @param filter_arg(unicode, None): argument of the filter
175 @param value(unicode): value to filter
176 @return (unicode, False, None): modified value
177 False to skip the whole row
178 None to ignore this argument (but continue row with other ones)
179 """
140 raise NotImplementedError 180 raise NotImplementedError
141 181
142 182
143 class Csv(InputCommon): 183 class Csv(InputCommon):
144 184
147 187
148 def add_parser_options(self): 188 def add_parser_options(self):
149 InputCommon.add_parser_options(self) 189 InputCommon.add_parser_options(self)
150 self.parser.add_argument("-r", "--row", type=int, default=0, help=_(u"starting row (previous ones will be ignored)")) 190 self.parser.add_argument("-r", "--row", type=int, default=0, help=_(u"starting row (previous ones will be ignored)"))
151 self.parser.add_argument("-S", "--split", action='append_const', const=('split', None), dest='arguments', help=_(u"split value in several options")) 191 self.parser.add_argument("-S", "--split", action='append_const', const=('split', None), dest='arguments', help=_(u"split value in several options"))
152 192 self.parser.add_argument("-E", "--empty", action='append', type=self.opt('empty'), dest='arguments',
153 def filter(self, arg_type, filter_type, filter_arg, value): 193 help=_(u"action to do on empty value ({choices})").format(choices=u', '.join(OPT_EMPTY_CHOICES)))
194
195 def filter(self, filter_type, filter_arg, value):
154 if filter_type == 'split': 196 if filter_type == 'split':
155 return value.split() 197 return value.split()
198 elif filter_type == 'empty':
199 if filter_arg == OPT_EMPTY_IGNORE:
200 return value if value else None
201 elif filter_arg == OPT_EMPTY_SKIP:
202 return value if value else False
203 else:
204 self.parser.error(_(u"--empty value must be one of {choices}").format(choices=u', '.join(OPT_EMPTY_CHOICES)))
156 205
157 super(Csv, self).filter(filter_type, filter_arg, value) 206 super(Csv, self).filter(filter_type, filter_arg, value)
158 207
159 def start(self): 208 def start(self):
160 import csv 209 import csv
161 reader = csv.reader(sys.stdin) 210 reader = csv.reader(sys.stdin)
162 for idx, row in enumerate(reader): 211 for idx, row in enumerate(reader):
163 if idx < self.args.row: 212 try:
213 if idx < self.args.row:
214 continue
215 for value in row:
216 self.addValue(value.decode(self.args.encoding))
217 self.runCommand()
218 except exceptions.CancelError:
219 # this row has been cancelled, we skip it
164 continue 220 continue
165 for value in row:
166 self.addValue(value.decode(self.args.encoding))
167 self.runCommand()
168 221
169 222
170 class Input(base.CommandBase): 223 class Input(base.CommandBase):
171 subcommands = (Csv,) 224 subcommands = (Csv,)
172 225