Mercurial > libervia-backend
comparison frontends/src/jp/cmd_shell.py @ 2309:c7a72b75232b
jp (shell): shell command (REPL mode), first draft:
This command launch jp in REPL mode, allowing do normal jp commands with some facilities.
Command can be selected with "cmd" (e.g. "cmd blog").
An argument can be fixed with "use" (e.g. "use output fancy").
Command is launched with "do", or directly with its name if it doesn't conflict with a shell command.
Arguments completion is still TODO (only shell commands are completed so far).
author | Goffi <goffi@goffi.org> |
---|---|
date | Thu, 06 Jul 2017 20:28:25 +0200 |
parents | |
children | 5996063ecad7 |
comparison
equal
deleted
inserted
replaced
2308:0b21d87c91cf | 2309:c7a72b75232b |
---|---|
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 import cmd | |
23 from sat.core.i18n import _ | |
24 from sat.core import exceptions | |
25 from sat_frontends.jp.constants import Const as C | |
26 from sat.tools.common.ansi import ANSI as A | |
27 import shlex | |
28 | |
29 __commands__ = ["Shell"] | |
30 INTRO = _(u"""Welcome to {app_name} shell, the Salut à Toi shell ! | |
31 | |
32 This enrironment helps you using several {app_name} commands with similar parameters. | |
33 | |
34 To quit, just enter "quit" or press C-c. | |
35 Enter "help" or "?" to know what to do | |
36 """).format(app_name = C.APP_NAME) | |
37 | |
38 | |
39 class Shell(base.CommandBase, cmd.Cmd): | |
40 | |
41 def __init__(self, host): | |
42 base.CommandBase.__init__(self, host, 'shell', help=_(u'launch jp in shell (REPL) mode')) | |
43 cmd.Cmd.__init__(self) | |
44 | |
45 def parse_args(self, args): | |
46 """parse line arguments""" | |
47 return shlex.split(args, posix=True) | |
48 | |
49 def get_cmd_choices(self, cmd=None, parser=None): | |
50 if parser is None: | |
51 parser = self._cur_parser | |
52 try: | |
53 choices = parser._subparsers._group_actions[0].choices | |
54 return choices[cmd] if cmd is not None else choices | |
55 except (KeyError, AttributeError): | |
56 raise exceptions.NotFound | |
57 | |
58 def update_path(self): | |
59 self._cur_parser = self.host.parser | |
60 self.help = u'' | |
61 for idx, path_elt in enumerate(self.path): | |
62 try: | |
63 self._cur_parser = self.get_cmd_choices(path_elt) | |
64 except exceptions.NotFound: | |
65 self.disp(_(u'bad command path'), error=True) | |
66 self.path=self.path[:idx] | |
67 break | |
68 else: | |
69 self.help = self._cur_parser | |
70 | |
71 self.prompt = A.color(C.A_PROMPT_PATH, u'/'.join(self.path)) + A.color(C.A_PROMPT_SUF, u'> ') | |
72 try: | |
73 self.actions = self.get_cmd_choices().keys() | |
74 except exceptions.NotFound: | |
75 self.actions = [] | |
76 | |
77 def add_parser_options(self): | |
78 pass | |
79 | |
80 def escape_arg(self, arg): | |
81 """format arg with quotes""" | |
82 return u'"' + arg.replace(u'"',u'\\"') + u'"' | |
83 | |
84 def format_args(self, args): | |
85 """format argument to be printed with quotes if needed""" | |
86 for arg in args: | |
87 if " " in arg: | |
88 yield self.escape_arg(arg) | |
89 else: | |
90 yield arg | |
91 | |
92 def get_use_args(self, args): | |
93 """format args for current parser according to self.use""" | |
94 parser = self._cur_parser | |
95 | |
96 # we check not optional args to see if there | |
97 # is a corresonding parser | |
98 # else USE args would not work correctly (only for current parser) | |
99 cmd_args = [] | |
100 for arg in args: | |
101 if arg.startswith(u'-'): | |
102 break | |
103 try: | |
104 parser = self.get_cmd_choices(arg, parser) | |
105 except exceptions.NotFound: | |
106 break | |
107 cmd_args.append(arg) | |
108 | |
109 # we remove command args | |
110 # they'll be in returned list (use_args) | |
111 del args[:len(cmd_args)] | |
112 | |
113 opt_args = [] | |
114 pos_args = [] | |
115 actions = {a.dest: a for a in parser._actions} | |
116 for arg, value in self.use.iteritems(): | |
117 try: | |
118 action = actions[arg] | |
119 except KeyError: | |
120 if self.verbose: | |
121 self.disp(_(u'ignoring {name}={value}, not corresponding to any argument (in USE)').format( | |
122 name=arg, | |
123 value=self.escape_arg(value))) | |
124 else: | |
125 if self.verbose: | |
126 self.disp(_(u'arg {name}={value} set (in USE)').format(name=arg, value=self.escape_arg(value))) | |
127 if not action.option_strings: | |
128 pos_args.append(value) | |
129 else: | |
130 opt_args.append(action.option_strings[0]) | |
131 opt_args.append(value) | |
132 return cmd_args + opt_args + pos_args | |
133 | |
134 def default(self, args): | |
135 """called when no shell command is recognized | |
136 | |
137 will launch the command with args on the line | |
138 (i.e. will launch do [args]) | |
139 """ | |
140 self.do_do(args) | |
141 | |
142 def do_help(self, args): | |
143 """show help message""" | |
144 self.disp(A.color(C.A_HEADER, _(u'Shell commands:')), no_lf=True) | |
145 super(Shell, self).do_help(args) | |
146 if not args: | |
147 self.disp(A.color(C.A_HEADER, _(u'Action commands:'))) | |
148 help_list = self._cur_parser.format_help().split('\n\n') | |
149 print('\n\n'.join(help_list[1 if self.path else 2:])) | |
150 | |
151 def do_debug(self, args): | |
152 """launch internal debugger""" | |
153 try: | |
154 import ipdb as pdb | |
155 except ImportError: | |
156 import pdb | |
157 pdb.set_trace() | |
158 | |
159 def do_verbose(self, args): | |
160 """show verbose mode, or (de)activate it""" | |
161 args = self.parse_args(args) | |
162 if args: | |
163 self.verbose = C.bool(args[0]) | |
164 self.disp(_(u'verbose mode is {status}').format( | |
165 status = _(u'ENABLED') if self.verbose else _(u'DISABLED'))) | |
166 | |
167 def do_cmd(self, args): | |
168 """change command path""" | |
169 if args == '..': | |
170 self.path = self.path[:-1] | |
171 else: | |
172 if not args or args[0] == '/': | |
173 self.path = [] | |
174 args = '/'.join(args.split()) | |
175 for path_elt in args.split('/'): | |
176 path_elt = path_elt.strip() | |
177 if not path_elt: | |
178 continue | |
179 self.path.append(path_elt) | |
180 self.update_path() | |
181 | |
182 def do_version(self, args): | |
183 """show current SàT/jp version""" | |
184 try: | |
185 self.host.run(['--version']) | |
186 except SystemExit: | |
187 pass | |
188 | |
189 def do_do(self, args): | |
190 """lauch a command""" | |
191 # we don't want host to really exit | |
192 # we want to stay in the loop | |
193 self.host._no_exit = True | |
194 args = self.parse_args(args) | |
195 # args may be modified by use_args | |
196 # to remove subparsers from it | |
197 use_args = self.get_use_args(args) | |
198 cmd_args = self.path + use_args + args | |
199 if self.verbose: | |
200 self.disp(u"COMMAND => {args}".format(args=u' '.join(self.format_args(cmd_args)))) | |
201 try: | |
202 self.host.run(cmd_args) | |
203 except SystemExit as e: | |
204 if e.code != 0: | |
205 self.disp(A.color(C.A_FAILURE, u'command failed with an error code of {err_no}'.format(err_no=e.code)), error=True) | |
206 except Exception as e: | |
207 self.disp(A.color(C.A_FAILURE, u'command failed with an exception: {msg}'.format(msg=e)), error=True) | |
208 finally: | |
209 self.host._no_exit = False | |
210 | |
211 def do_use(self, args): | |
212 """fix an argument""" | |
213 args = self.parse_args(args) | |
214 if not args: | |
215 if not self.use: | |
216 self.disp(_(u'no argument in USE')) | |
217 else: | |
218 self.disp(_(u'arguments in USE:')) | |
219 for arg, value in self.use.iteritems(): | |
220 self.disp(_(A.color(C.A_SUBHEADER, arg, A.RESET, u' = ', self.escape_arg(value)))) | |
221 elif len(args) != 2: | |
222 self.disp(u'bad syntax, please use:\nuse [arg] [value]', error=True) | |
223 else: | |
224 self.use[args[0]] = u' '.join(args[1:]) | |
225 if self.verbose: | |
226 self.disp('set {name} = {value}'.format( | |
227 name = args[0], value=self.escape_arg(args[1]))) | |
228 | |
229 def do_use_clear(self, args): | |
230 """unset one or many argument(s) in USE, or all of them if no arg is specified""" | |
231 args = self.parse_args(args) | |
232 if not args: | |
233 self.use.clear() | |
234 else: | |
235 for arg in args: | |
236 try: | |
237 del self.use[arg] | |
238 except KeyError: | |
239 self.disp(A.color(C.A_FAILURE, _(u'argument {name} not found').format(name=arg)), error=True) | |
240 else: | |
241 if self.verbose: | |
242 self.disp(_(u'argument {name} removed').format(name=arg)) | |
243 | |
244 def do_quit(self, args): | |
245 u"""quit the shell""" | |
246 self.disp(_(u'good bye!')) | |
247 self.host.quit() | |
248 | |
249 def do_exit(self, args): | |
250 u"""alias for quit""" | |
251 self.do_quit(args) | |
252 | |
253 def start(self): | |
254 self.path = [] | |
255 self._cur_parser = self.host.parser | |
256 self.use = {} | |
257 self.verbose = False | |
258 self.update_path() | |
259 self.cmdloop(INTRO.encode('utf-8')) |