Mercurial > libervia-backend
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 |