comparison sat_frontends/jp/cmd_input.py @ 2562:26edcf3a30eb

core, setup: huge cleaning: - moved directories from src and frontends/src to sat and sat_frontends, which is the recommanded naming convention - move twisted directory to root - removed all hacks from setup.py, and added missing dependencies, it is now clean - use https URL for website in setup.py - removed "Environment :: X11 Applications :: GTK", as wix is deprecated and removed - renamed sat.sh to sat and fixed its installation - added python_requires to specify Python version needed - replaced glib2reactor which use deprecated code by gtk3reactor sat can now be installed directly from virtualenv without using --system-site-packages anymore \o/
author Goffi <goffi@goffi.org>
date Mon, 02 Apr 2018 19:44:50 +0200
parents frontends/src/jp/cmd_input.py@0046283a285d
children 56f94936df1e
comparison
equal deleted inserted replaced
2561:bd30dc3ffe5a 2562:26edcf3a30eb
1 #!/usr/bin/env python2
2 # -*- coding: utf-8 -*-
3
4 # jp: a SàT command line tool
5 # Copyright (C) 2009-2018 Jérôme Poisson (goffi@goffi.org)
6
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
16
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20
21 import base
22 from sat.core.i18n import _
23 from sat.core import exceptions
24 from sat_frontends.jp.constants import Const as C
25 from sat.tools.common.ansi import ANSI as A
26 import subprocess
27 import argparse
28 import sys
29
30 __commands__ = ["Input"]
31 OPT_STDIN = 'stdin'
32 OPT_SHORT = 'short'
33 OPT_LONG = 'long'
34 OPT_POS = 'positional'
35 OPT_IGNORE = '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)
40
41
42 class InputCommon(base.CommandBase):
43
44 def __init__(self, host, name, help):
45 base.CommandBase.__init__(self, host, name, use_verbose=True, use_profile=False, help=help)
46 self.idx = 0
47 self.reset()
48
49 def reset(self):
50 self.args_idx = 0
51 self._stdin = []
52 self._opts = []
53 self._pos = []
54 self._values_ori = []
55
56 def add_parser_options(self):
57 self.parser.add_argument("--encoding", default='utf-8', help=_(u"encoding of the input data"))
58 self.parser.add_argument("-i", "--stdin", action='append_const', const=(OPT_STDIN, None), dest='arguments', help=_(u"standard input"))
59 self.parser.add_argument("-s", "--short", type=self.opt(OPT_SHORT), action='append', dest='arguments', help=_(u"short option"))
60 self.parser.add_argument("-l", "--long", type=self.opt(OPT_LONG), action='append', dest='arguments', help=_(u"long option"))
61 self.parser.add_argument("-p", "--positional", type=self.opt(OPT_POS), action='append', dest='arguments', help=_(u"positional argument"))
62 self.parser.add_argument("-x", "--ignore", action='append_const', const=(OPT_IGNORE, None), dest='arguments', help=_(u"ignore value"))
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"))
66 self.parser.add_argument("command", nargs=argparse.REMAINDER)
67
68 def opt(self, type_):
69 return lambda s: (type_, s)
70
71 def addValue(self, value):
72 """add a parsed value according to arguments sequence"""
73 self._values_ori.append(value)
74 arguments = self.args.arguments
75 try:
76 arg_type, arg_name = arguments[self.args_idx]
77 except IndexError:
78 self.disp(_(u"arguments in input data and in arguments sequence don't match"), error=True)
79 self.host.quit(C.EXIT_DATA_ERROR)
80 self.args_idx += 1
81 while self.args_idx < len(arguments):
82 next_arg = arguments[self.args_idx]
83 if next_arg[0] not in OPT_TYPES:
84 # value will not be used if False or None, so we skip filter
85 if value not in (False, None):
86 # we have a filter
87 filter_type, filter_arg = arguments[self.args_idx]
88 value = self.filter(filter_type, filter_arg, value)
89 else:
90 break
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
105
106 if not isinstance(value, list):
107 value = [value]
108
109 for v in value:
110 if arg_type == OPT_STDIN:
111 self._stdin.append(v.encode('utf-8'))
112 elif arg_type == OPT_SHORT:
113 self._opts.append('-{}'.format(arg_name))
114 self._opts.append(v.encode('utf-8'))
115 elif arg_type == OPT_LONG:
116 self._opts.append('--{}'.format(arg_name))
117 self._opts.append(v.encode('utf-8'))
118 elif arg_type == OPT_POS:
119 self._pos.append(v.encode('utf-8'))
120 elif arg_type == OPT_IGNORE:
121 pass
122 else:
123 self.parser.error(_(u"Invalid argument, an option type is expected, got {type_}:{name}").format(
124 type_=arg_type, name=arg_name))
125
126 def runCommand(self):
127 """run requested command with parsed arguments"""
128 if self.args_idx != len(self.args.arguments):
129 self.disp(_(u"arguments in input data and in arguments sequence don't match"), error=True)
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)
132 stdin = ''.join(self._stdin)
133 if self.args.debug:
134 self.disp(A.color(C.A_SUBHEADER, _(u'values: '), A.RESET, u', '.join(self._values_ori)), 2)
135
136 if stdin:
137 self.disp(A.color(C.A_SUBHEADER, u'--- STDIN ---'))
138 self.disp(stdin.decode('utf-8'))
139 self.disp(A.color(C.A_SUBHEADER, u'-------------'))
140 self.disp(u'{indent}{prog} {static} {options} {positionals}'.format(
141 indent = 4*u' ',
142 prog=sys.argv[0],
143 static = ' '.join(self.args.command).decode('utf-8'),
144 options = u' '.join([o.decode('utf-8') for o in self._opts]),
145 positionals = u' '.join([p.decode('utf-8') for p in self._pos])
146 ))
147 self.disp(u'\n')
148 else:
149 self.disp(u' (' + u', '.join(self._values_ori) + u')', 2, no_lf=True)
150 args = [sys.argv[0]] + self.args.command + self._opts + self._pos
151 p = subprocess.Popen(args,
152 stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
153 (stdout, stderr) = p.communicate(stdin)
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()
162 if ret == 0:
163 self.disp(A.color(C.A_SUCCESS, _(u'OK')))
164 else:
165 self.disp(A.color(C.A_FAILURE, _(u'FAILED')))
166
167 self.reset()
168 self.idx += 1
169
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 """
180 raise NotImplementedError
181
182
183 class Csv(InputCommon):
184
185 def __init__(self, host):
186 super(Csv, self).__init__(host, 'csv', _(u'comma-separated values'))
187
188 def add_parser_options(self):
189 InputCommon.add_parser_options(self)
190 self.parser.add_argument("-r", "--row", type=int, default=0, help=_(u"starting row (previous ones will be ignored)"))
191 self.parser.add_argument("-S", "--split", action='append_const', const=('split', None), dest='arguments', help=_(u"split value in several options"))
192 self.parser.add_argument("-E", "--empty", action='append', type=self.opt('empty'), dest='arguments',
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):
196 if filter_type == '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)))
205
206 super(Csv, self).filter(filter_type, filter_arg, value)
207
208 def start(self):
209 import csv
210 reader = csv.reader(sys.stdin)
211 for idx, row in enumerate(reader):
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
220 continue
221
222
223 class Input(base.CommandBase):
224 subcommands = (Csv,)
225
226 def __init__(self, host):
227 super(Input, self).__init__(host, 'input', use_profile=False, help=_(u'launch command with external input'))