Mercurial > libervia-backend
annotate frontends/src/jp/cmd_input.py @ 2307:8fa7edd0da24
plugin Pubsub Hook: first draft:
This new plugin allow to attach an external action to a Pubsub event (i.e. notification).
Hook can be persitent accross restarts, or temporary (will be deleted on profile disconnection).
Only Python files are handled for now.
In the future, it may make sense to move hooks in a generic plugin which could be used by ad-hoc commands, messages, pubsub, etc.
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 05 Jul 2017 15:05:47 +0200 |
parents | cbc989508474 |
children | 8b37a62336c3 |
rev | line source |
---|---|
2278 | 1 #!/usr/bin/env python2 |
2 # -*- coding: utf-8 -*- | |
3 | |
4 # jp: a SàT command line tool | |
5 # Copyright (C) 2009-2016 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 _ | |
2282
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
23 from sat.core import exceptions |
2278 | 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) | |
2282
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
37 OPT_EMPTY_SKIP = 'skip' |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
38 OPT_EMPTY_IGNORE = 'ignore' |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
39 OPT_EMPTY_CHOICES = (OPT_EMPTY_SKIP, OPT_EMPTY_IGNORE) |
2278 | 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 = [] | |
2282
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
54 self._values_ori = [] |
2278 | 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")) | |
2282
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
64 self.parser.add_argument("--log", type=argparse.FileType('wb'), help=_(u"log stdout to FILE")) |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
65 self.parser.add_argument("--log-err", type=argparse.FileType('wb'), help=_(u"log stderr to FILE")) |
2278 | 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""" | |
2282
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
73 self._values_ori.append(value) |
2278 | 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: | |
2282
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
84 # value will not be used if False or None, so we skip filter |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
85 if value not in (False, None): |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
86 # we have a filter |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
87 filter_type, filter_arg = arguments[self.args_idx] |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
88 value = self.filter(filter_type, filter_arg, value) |
2278 | 89 else: |
90 break | |
91 self.args_idx += 1 | |
92 | |
2282
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
93 if value is None: |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
94 # we ignore this argument |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
95 return |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
96 |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
97 if value is False: |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
98 # we skip the whole row |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
99 if self.args.debug: |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
100 self.disp(A.color(C.A_SUBHEADER, _(u'values: '), A.RESET, u', '.join(self._values_ori)), 2) |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
101 self.disp(A.color(A.BOLD, _(u'**SKIPPING**\n'))) |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
102 self.reset() |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
103 self.idx += 1 |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
104 raise exceptions.CancelError |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
105 |
2278 | 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) | |
2282
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
131 self.disp(A.color(C.A_HEADER, _(u'command {idx}').format(idx=self.idx)), no_lf=not self.args.debug) |
2278 | 132 stdin = ''.join(self._stdin) |
133 if self.args.debug: | |
2282
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
134 self.disp(A.color(C.A_SUBHEADER, _(u'values: '), A.RESET, u', '.join(self._values_ori)), 2) |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
135 |
2278 | 136 if stdin: |
2282
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
137 self.disp(A.color(C.A_SUBHEADER, u'--- STDIN ---')) |
2278 | 138 self.disp(stdin.decode('utf-8')) |
2282
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
139 self.disp(A.color(C.A_SUBHEADER, u'-------------')) |
2278 | 140 self.disp(u'{indent}{prog} {static} {options} {positionals}'.format( |
141 indent = 4*u' ', | |
142 prog=sys.argv[0], | |
2293
cbc989508474
jp (input/csv): fixed encoding issue in debug mode
Goffi <goffi@goffi.org>
parents:
2282
diff
changeset
|
143 static = ' '.join(self.args.command).decode('utf-8'), |
cbc989508474
jp (input/csv): fixed encoding issue in debug mode
Goffi <goffi@goffi.org>
parents:
2282
diff
changeset
|
144 options = u' '.join([o.decode('utf-8') for o in self._opts]), |
cbc989508474
jp (input/csv): fixed encoding issue in debug mode
Goffi <goffi@goffi.org>
parents:
2282
diff
changeset
|
145 positionals = u' '.join([p.decode('utf-8') for p in self._pos]) |
2278 | 146 )) |
2282
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
147 self.disp(u'\n') |
2278 | 148 else: |
2282
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
149 self.disp(u' (' + u', '.join(self._values_ori) + u')', 2, no_lf=True) |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
150 args = [sys.argv[0]] + self.args.command + self._opts + self._pos |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
151 p = subprocess.Popen(args, |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
152 stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
153 (stdout, stderr) = p.communicate(stdin) |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
154 log = self.args.log |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
155 log_err = self.args.log_err |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
156 log_tpl = '{command}\n{buff}\n\n' |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
157 if log: |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
158 log.write(log_tpl.format(command=' '.join(args), buff=stdout)) |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
159 if log_err: |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
160 log_err.write(log_tpl.format(command=' '.join(args), buff=stderr)) |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
161 ret = p.wait() |
2278 | 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): | |
2282
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
171 """change input value |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
172 |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
173 @param filter_type(unicode): name of the filter |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
174 @param filter_arg(unicode, None): argument of the filter |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
175 @param value(unicode): value to filter |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
176 @return (unicode, False, None): modified value |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
177 False to skip the whole row |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
178 None to ignore this argument (but continue row with other ones) |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
179 """ |
2278 | 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")) | |
2282
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
192 self.parser.add_argument("-E", "--empty", action='append', type=self.opt('empty'), dest='arguments', |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
193 help=_(u"action to do on empty value ({choices})").format(choices=u', '.join(OPT_EMPTY_CHOICES))) |
2278 | 194 |
2282
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
195 def filter(self, filter_type, filter_arg, value): |
2278 | 196 if filter_type == 'split': |
197 return value.split() | |
2282
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
198 elif filter_type == 'empty': |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
199 if filter_arg == OPT_EMPTY_IGNORE: |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
200 return value if value else None |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
201 elif filter_arg == OPT_EMPTY_SKIP: |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
202 return value if value else False |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
203 else: |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
204 self.parser.error(_(u"--empty value must be one of {choices}").format(choices=u', '.join(OPT_EMPTY_CHOICES))) |
2278 | 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): | |
2282
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
212 try: |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
213 if idx < self.args.row: |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
214 continue |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
215 for value in row: |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
216 self.addValue(value.decode(self.args.encoding)) |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
217 self.runCommand() |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
218 except exceptions.CancelError: |
d8e48c850ad2
jp (input): log improvments + empty filter:
Goffi <goffi@goffi.org>
parents:
2278
diff
changeset
|
219 # this row has been cancelled, we skip it |
2278 | 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')) |