comparison sat_frontends/jp/cmd_shell.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_shell.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 import cmd
23 import sys
24 from sat.core.i18n import _
25 from sat.core import exceptions
26 from sat_frontends.jp.constants import Const as C
27 from sat_frontends.jp import arg_tools
28 from sat.tools.common.ansi import ANSI as A
29 import shlex
30 import subprocess
31
32 __commands__ = ["Shell"]
33 INTRO = _(u"""Welcome to {app_name} shell, the Salut à Toi shell !
34
35 This enrironment helps you using several {app_name} commands with similar parameters.
36
37 To quit, just enter "quit" or press C-d.
38 Enter "help" or "?" to know what to do
39 """).format(app_name = C.APP_NAME)
40
41
42 class Shell(base.CommandBase, cmd.Cmd):
43
44 def __init__(self, host):
45 base.CommandBase.__init__(self, host, 'shell', help=_(u'launch jp in shell (REPL) mode'))
46 cmd.Cmd.__init__(self)
47
48 def parse_args(self, args):
49 """parse line arguments"""
50 return shlex.split(args, posix=True)
51
52 def update_path(self):
53 self._cur_parser = self.host.parser
54 self.help = u''
55 for idx, path_elt in enumerate(self.path):
56 try:
57 self._cur_parser = arg_tools.get_cmd_choices(path_elt, self._cur_parser)
58 except exceptions.NotFound:
59 self.disp(_(u'bad command path'), error=True)
60 self.path=self.path[:idx]
61 break
62 else:
63 self.help = self._cur_parser
64
65 self.prompt = A.color(C.A_PROMPT_PATH, u'/'.join(self.path)) + A.color(C.A_PROMPT_SUF, u'> ')
66 try:
67 self.actions = arg_tools.get_cmd_choices(parser=self._cur_parser).keys()
68 except exceptions.NotFound:
69 self.actions = []
70
71 def add_parser_options(self):
72 pass
73
74 def format_args(self, args):
75 """format argument to be printed with quotes if needed"""
76 for arg in args:
77 if " " in arg:
78 yield arg_tools.escape(arg)
79 else:
80 yield arg
81
82 def run_cmd(self, args, external=False):
83 """run command and retur exit code
84
85 @param args[list[string]]: arguments of the command
86 must not include program name
87 @param external(bool): True if it's an external command (i.e. not jp)
88 @return (int): exit code (0 success, any other int failure)
89 """
90 # FIXME: we have to use subprocess
91 # and relaunch whole python for now
92 # because if host.quit() is called in D-Bus callback
93 # GLib quit the whole app without possibility to stop it
94 # didn't found a nice way to work around it so far
95 # Situation should be better when we'll move away from python-dbus
96 if self.verbose:
97 self.disp(_(u"COMMAND {external}=> {args}").format(
98 external = _(u'(external) ') if external else u'',
99 args=u' '.join(self.format_args(args))))
100 if not external:
101 args = sys.argv[0:1] + args
102 ret_code= subprocess.call(args)
103 # XXX: below is a way to launch the command without creating a new process
104 # may be used when a solution to the aforementioned issue is there
105 # try:
106 # self.host.run(args)
107 # except SystemExit as e:
108 # ret_code = e.code
109 # except Exception as e:
110 # self.disp(A.color(C.A_FAILURE, u'command failed with an exception: {msg}'.format(msg=e)), error=True)
111 # ret_code = 1
112 # else:
113 # ret_code = 0
114
115 if ret_code != 0:
116 self.disp(A.color(C.A_FAILURE, u'command failed with an error code of {err_no}'.format(err_no=ret_code)), error=True)
117 return ret_code
118
119 def default(self, args):
120 """called when no shell command is recognized
121
122 will launch the command with args on the line
123 (i.e. will launch do [args])
124 """
125 if args=='EOF':
126 self.do_quit('')
127 self.do_do(args)
128
129 def do_help(self, args):
130 """show help message"""
131 if not args:
132 self.disp(A.color(C.A_HEADER, _(u'Shell commands:')), no_lf=True)
133 super(Shell, self).do_help(args)
134 if not args:
135 self.disp(A.color(C.A_HEADER, _(u'Action commands:')))
136 help_list = self._cur_parser.format_help().split('\n\n')
137 print('\n\n'.join(help_list[1 if self.path else 2:]))
138
139 def do_debug(self, args):
140 """launch internal debugger"""
141 try:
142 import ipdb as pdb
143 except ImportError:
144 import pdb
145 pdb.set_trace()
146
147 def do_verbose(self, args):
148 """show verbose mode, or (de)activate it"""
149 args = self.parse_args(args)
150 if args:
151 self.verbose = C.bool(args[0])
152 self.disp(_(u'verbose mode is {status}').format(
153 status = _(u'ENABLED') if self.verbose else _(u'DISABLED')))
154
155 def do_cmd(self, args):
156 """change command path"""
157 if args == '..':
158 self.path = self.path[:-1]
159 else:
160 if not args or args[0] == '/':
161 self.path = []
162 args = '/'.join(args.split())
163 for path_elt in args.split('/'):
164 path_elt = path_elt.strip()
165 if not path_elt:
166 continue
167 self.path.append(path_elt)
168 self.update_path()
169
170 def do_version(self, args):
171 """show current SàT/jp version"""
172 try:
173 self.host.run(['--version'])
174 except SystemExit:
175 pass
176
177 def do_shell(self, args):
178 """launch an external command (you can use ![command] too)"""
179 args = self.parse_args(args)
180 self.run_cmd(args, external=True)
181
182 def do_do(self, args):
183 """lauch a command"""
184 args = self.parse_args(args)
185 if (self._not_default_profile and
186 not '-p' in args and
187 not '--profile' in args and
188 not 'profile' in self.use):
189 # profile is not specified and we are not using the default profile
190 # so we need to add it in arguments to use current user profile
191 if self.verbose:
192 self.disp(_(u'arg profile={profile} (logged profile)').format(profile=self.profile))
193 use = self.use.copy()
194 use['profile'] = self.profile
195 else:
196 use = self.use
197
198
199 # args may be modified by use_args
200 # to remove subparsers from it
201 parser_args, use_args = arg_tools.get_use_args(self.host,
202 args,
203 use,
204 verbose=self.verbose,
205 parser=self._cur_parser
206 )
207 cmd_args = self.path + parser_args + use_args
208 self.run_cmd(cmd_args)
209
210 def do_use(self, args):
211 """fix an argument"""
212 args = self.parse_args(args)
213 if not args:
214 if not self.use:
215 self.disp(_(u'no argument in USE'))
216 else:
217 self.disp(_(u'arguments in USE:'))
218 for arg, value in self.use.iteritems():
219 self.disp(_(A.color(C.A_SUBHEADER, arg, A.RESET, u' = ', arg_tools.escape(value))))
220 elif len(args) != 2:
221 self.disp(u'bad syntax, please use:\nuse [arg] [value]', error=True)
222 else:
223 self.use[args[0]] = u' '.join(args[1:])
224 if self.verbose:
225 self.disp('set {name} = {value}'.format(
226 name = args[0], value=arg_tools.escape(args[1])))
227
228 def do_use_clear(self, args):
229 """unset one or many argument(s) in USE, or all of them if no arg is specified"""
230 args = self.parse_args(args)
231 if not args:
232 self.use.clear()
233 else:
234 for arg in args:
235 try:
236 del self.use[arg]
237 except KeyError:
238 self.disp(A.color(C.A_FAILURE, _(u'argument {name} not found').format(name=arg)), error=True)
239 else:
240 if self.verbose:
241 self.disp(_(u'argument {name} removed').format(name=arg))
242
243 def do_whoami(self, args):
244 u"""print profile currently used"""
245 self.disp(self.profile)
246
247 def do_quit(self, args):
248 u"""quit the shell"""
249 self.disp(_(u'good bye!'))
250 self.host.quit()
251
252 def do_exit(self, args):
253 u"""alias for quit"""
254 self.do_quit(args)
255
256 def start(self):
257 default_profile = self.host.bridge.profileNameGet(C.PROF_KEY_DEFAULT)
258 self._not_default_profile = self.profile != default_profile
259 self.path = []
260 self._cur_parser = self.host.parser
261 self.use = {}
262 self.verbose = False
263 self.update_path()
264 self.cmdloop(INTRO.encode('utf-8'))