comparison sat_frontends/jp/cmd_input.py @ 3040:fee60f17ebac

jp: jp asyncio port: /!\ this commit is huge. Jp is temporarily not working with `dbus` bridge /!\ This patch implements the port of jp to asyncio, so it is now correctly using the bridge asynchronously, and it can be used with bridges like `pb`. This also simplify the code, notably for things which were previously implemented with many callbacks (like pagination with RSM). During the process, some behaviours have been modified/fixed, in jp and backends, check diff for details.
author Goffi <goffi@goffi.org>
date Wed, 25 Sep 2019 08:56:41 +0200
parents ab2696e34d29
children 9d0df638c8b4
comparison
equal deleted inserted replaced
3039:a1bc34f90fa5 3040:fee60f17ebac
16 16
17 # You should have received a copy of the GNU Affero General Public License 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/>. 18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19 19
20 20
21 import subprocess
22 import argparse
23 import sys
24 import shlex
25 import asyncio
21 from . import base 26 from . import base
22 from sat.core.i18n import _ 27 from sat.core.i18n import _
23 from sat.core import exceptions 28 from sat.core import exceptions
24 from sat_frontends.jp.constants import Const as C 29 from sat_frontends.jp.constants import Const as C
25 from sat.tools.common.ansi import ANSI as A 30 from sat.tools.common.ansi import ANSI as A
26 import subprocess
27 import argparse
28 import sys
29 31
30 __commands__ = ["Input"] 32 __commands__ = ["Input"]
31 OPT_STDIN = "stdin" 33 OPT_STDIN = "stdin"
32 OPT_SHORT = "short" 34 OPT_SHORT = "short"
33 OPT_LONG = "long" 35 OPT_LONG = "long"
103 "--debug", 105 "--debug",
104 action="store_true", 106 action="store_true",
105 help=_("don't actually run commands but echo what would be launched"), 107 help=_("don't actually run commands but echo what would be launched"),
106 ) 108 )
107 self.parser.add_argument( 109 self.parser.add_argument(
108 "--log", type=argparse.FileType("wb"), help=_("log stdout to FILE") 110 "--log", type=argparse.FileType("w"), help=_("log stdout to FILE")
109 ) 111 )
110 self.parser.add_argument( 112 self.parser.add_argument(
111 "--log-err", type=argparse.FileType("wb"), help=_("log stderr to FILE") 113 "--log-err", type=argparse.FileType("w"), help=_("log stderr to FILE")
112 ) 114 )
113 self.parser.add_argument("command", nargs=argparse.REMAINDER) 115 self.parser.add_argument("command", nargs=argparse.REMAINDER)
114 116
115 def opt(self, type_): 117 def opt(self, type_):
116 return lambda s: (type_, s) 118 return lambda s: (type_, s)
164 if not isinstance(value, list): 166 if not isinstance(value, list):
165 value = [value] 167 value = [value]
166 168
167 for v in value: 169 for v in value:
168 if arg_type == OPT_STDIN: 170 if arg_type == OPT_STDIN:
169 self._stdin.append(v.encode("utf-8")) 171 self._stdin.append(v)
170 elif arg_type == OPT_SHORT: 172 elif arg_type == OPT_SHORT:
171 self._opts.append("-{}".format(arg_name)) 173 self._opts.append("-{}".format(arg_name))
172 self._opts.append(v.encode("utf-8")) 174 self._opts.append(v)
173 elif arg_type == OPT_LONG: 175 elif arg_type == OPT_LONG:
174 self._opts.append("--{}".format(arg_name)) 176 self._opts.append("--{}".format(arg_name))
175 self._opts.append(v.encode("utf-8")) 177 self._opts.append(v)
176 elif arg_type == OPT_POS: 178 elif arg_type == OPT_POS:
177 self._pos.append(v.encode("utf-8")) 179 self._pos.append(v)
178 elif arg_type == OPT_IGNORE: 180 elif arg_type == OPT_IGNORE:
179 pass 181 pass
180 else: 182 else:
181 self.parser.error( 183 self.parser.error(
182 _( 184 _(
183 "Invalid argument, an option type is expected, got {type_}:{name}" 185 "Invalid argument, an option type is expected, got {type_}:{name}"
184 ).format(type_=arg_type, name=arg_name) 186 ).format(type_=arg_type, name=arg_name)
185 ) 187 )
186 188
187 def runCommand(self): 189 async def runCommand(self):
188 """run requested command with parsed arguments""" 190 """run requested command with parsed arguments"""
189 if self.args_idx != len(self.args.arguments): 191 if self.args_idx != len(self.args.arguments):
190 self.disp( 192 self.disp(
191 _("arguments in input data and in arguments sequence don't match"), 193 _("arguments in input data and in arguments sequence don't match"),
192 error=True, 194 error=True,
198 ) 200 )
199 stdin = "".join(self._stdin) 201 stdin = "".join(self._stdin)
200 if self.args.debug: 202 if self.args.debug:
201 self.disp( 203 self.disp(
202 A.color( 204 A.color(
203 C.A_SUBHEADER, _("values: "), A.RESET, ", ".join(self._values_ori) 205 C.A_SUBHEADER,
206 _("values: "),
207 A.RESET,
208 ", ".join([shlex.quote(a) for a in self._values_ori])
204 ), 209 ),
205 2, 210 2,
206 ) 211 )
207 212
208 if stdin: 213 if stdin:
209 self.disp(A.color(C.A_SUBHEADER, "--- STDIN ---")) 214 self.disp(A.color(C.A_SUBHEADER, "--- STDIN ---"))
210 self.disp(stdin) 215 self.disp(stdin)
211 self.disp(A.color(C.A_SUBHEADER, "-------------")) 216 self.disp(A.color(C.A_SUBHEADER, "-------------"))
217
212 self.disp( 218 self.disp(
213 "{indent}{prog} {static} {options} {positionals}".format( 219 "{indent}{prog} {static} {options} {positionals}".format(
214 indent=4 * " ", 220 indent=4 * " ",
215 prog=sys.argv[0], 221 prog=sys.argv[0],
216 static=" ".join(self.args.command), 222 static=" ".join(self.args.command),
217 options=" ".join([o for o in self._opts]), 223 options=" ".join(shlex.quote(o) for o in self._opts),
218 positionals=" ".join([p for p in self._pos]), 224 positionals=" ".join(shlex.quote(p) for p in self._pos),
219 ) 225 )
220 ) 226 )
221 self.disp("\n") 227 self.disp("\n")
222 else: 228 else:
223 self.disp(" (" + ", ".join(self._values_ori) + ")", 2, no_lf=True) 229 self.disp(" (" + ", ".join(self._values_ori) + ")", 2, no_lf=True)
224 args = [sys.argv[0]] + self.args.command + self._opts + self._pos 230 args = [sys.argv[0]] + self.args.command + self._opts + self._pos
225 p = subprocess.Popen( 231 p = await asyncio.create_subprocess_exec(
226 args, 232 *args,
227 stdin=subprocess.PIPE, 233 stdin=subprocess.PIPE,
228 stdout=subprocess.PIPE, 234 stdout=subprocess.PIPE,
229 stderr=subprocess.PIPE, 235 stderr=subprocess.PIPE,
230 ) 236 )
231 (stdout, stderr) = p.communicate(stdin) 237 stdout, stderr = await p.communicate(stdin.encode('utf-8'))
232 log = self.args.log 238 log = self.args.log
233 log_err = self.args.log_err 239 log_err = self.args.log_err
234 log_tpl = "{command}\n{buff}\n\n" 240 log_tpl = "{command}\n{buff}\n\n"
235 if log: 241 if log:
236 log.write(log_tpl.format(command=" ".join(args), buff=stdout)) 242 log.write(log_tpl.format(
243 command=" ".join(shlex.quote(a) for a in args),
244 buff=stdout.decode('utf-8', 'replace')))
237 if log_err: 245 if log_err:
238 log_err.write(log_tpl.format(command=" ".join(args), buff=stderr)) 246 log_err.write(log_tpl.format(
239 ret = p.wait() 247 command=" ".join(shlex.quote(a) for a in args),
248 buff=stderr.decode('utf-8', 'replace')))
249 ret = p.returncode
240 if ret == 0: 250 if ret == 0:
241 self.disp(A.color(C.A_SUCCESS, _("OK"))) 251 self.disp(A.color(C.A_SUCCESS, _("OK")))
242 else: 252 else:
243 self.disp(A.color(C.A_FAILURE, _("FAILED"))) 253 self.disp(A.color(C.A_FAILURE, _("FAILED")))
244 254
305 ) 315 )
306 ) 316 )
307 317
308 super(Csv, self).filter(filter_type, filter_arg, value) 318 super(Csv, self).filter(filter_type, filter_arg, value)
309 319
310 def start(self): 320 async def start(self):
311 import csv 321 import csv
312 322
323 if self.args.encoding:
324 sys.stdin.reconfigure(encoding=self.args.encoding, errors="replace")
313 reader = csv.reader(sys.stdin) 325 reader = csv.reader(sys.stdin)
314 for idx, row in enumerate(reader): 326 for idx, row in enumerate(reader):
315 try: 327 try:
316 if idx < self.args.row: 328 if idx < self.args.row:
317 continue 329 continue
318 for value in row: 330 for value in row:
319 self.addValue(value.decode(self.args.encoding)) 331 self.addValue(value)
320 self.runCommand() 332 await self.runCommand()
321 except exceptions.CancelError: 333 except exceptions.CancelError:
322 #  this row has been cancelled, we skip it 334 #  this row has been cancelled, we skip it
323 continue 335 continue
336
337 self.host.quit()
324 338
325 339
326 class Input(base.CommandBase): 340 class Input(base.CommandBase):
327 subcommands = (Csv,) 341 subcommands = (Csv,)
328 342