1
1
# -*- coding: utf-8 -*-
2
# Copyright 2009 Canonical Ltd.
3
# Author 2009 Didier Roche
2
# Copyright 2009 Didier Roche
5
4
# This file is part of Quickly
7
#This program is free software: you can redistribute it and/or modify it
8
#under the terms of the GNU General Public License version 3, as published
6
#This program is free software: you can redistribute it and/or modify it
7
#under the terms of the GNU General Public License version 3, as published
9
8
#by the Free Software Foundation.
11
#This program is distributed in the hope that it will be useful, but
12
#WITHOUT ANY WARRANTY; without even the implied warranties of
13
#MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
10
#This program is distributed in the hope that it will be useful, but
11
#WITHOUT ANY WARRANTY; without even the implied warranties of
12
#MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14
13
#PURPOSE. See the GNU General Public License for more details.
16
#You should have received a copy of the GNU General Public License along
15
#You should have received a copy of the GNU General Public License along
17
16
#with this program. If not, see <http://www.gnu.org/licenses/>.
23
23
import builtincommands
24
import configurationhandler
25
import templatetools, tools
27
28
from gettext import gettext as _
30
31
gettext.textdomain('quickly')
32
# double depths tabular : template (or "builtin"), name
33
# A dictionary with keys either 'builtins' or the names of templates. Values
34
# are another dictionary mapping command names to their command objects. This
35
# is used as a cache of all commands that we've found in templates.
38
def try_to_import_command(commands, importing_template, imported_template,
40
"""Import command in one template from another template
42
Ignore unknown command or template"""
44
# let's override by locally defined command
45
if command_name not in commands[importing_template]:
47
commands[importing_template][command_name] = \
48
commands[imported_template][command_name]
50
# command/template doesn't exist: ignore
36
54
def get_all_commands():
37
55
"""Load all commands
39
First, load template command and then builtins one. Push right parameters depending
40
if hooks are available, or if the command execution is special
41
You can note that create command is automatically overloaded atm"""
57
First, load template command and then builtins one. Push right parameters
58
depending if hooks are available, or if the command execution is special
59
You can note that create command is automatically overloaded atm.
43
63
if len(__commands) > 0:
47
67
template_directories = tools.get_template_directories()
48
except tools.no_template_path_not_found:
68
except tools.template_path_not_found:
49
69
template_directories = []
50
72
for template_dir in template_directories:
51
73
for template in os.listdir(template_dir):
52
74
__commands[template] = {}
75
import_commands[template] = {}
53
76
template_path = os.path.join(template_dir, template)
55
78
# load special attributes declared for every command
56
79
launch_inside_project_command_list = []
57
80
launch_outside_project_command_list = []
58
81
command_followed_by_command_list = []
82
current_template_import = None
60
files_command_parameters = file(os.path.join(template_path, "commandsconfig"), 'rb')
61
for line in files_command_parameters:
62
fields = line.split('#')[0] # Suppress commentary after the value in configuration file and in full line
85
files_command_parameters = file(
86
os.path.join(template_path, "commandsconfig"), 'rb')
87
for line in files_command_parameters:
88
# Suppress commentary after the value in configuration
89
# file and in full line.
90
fields = line.split('#')[0]
63
91
fields = fields.split('=') # Separate variable from value
64
# normally, we have two fields in "fields"
92
# command definitions or import have two fields
65
93
if len(fields) == 2:
66
94
targeted_property = fields[0].strip()
67
command_list = [command.strip() for command in fields[1].split(';')]
68
if targeted_property == 'COMMANDS_LAUNCHED_IN_OR_OUTSIDE_PROJECT':
69
launch_inside_project_command_list.extend(command_list)
70
launch_outside_project_command_list.extend(command_list)
71
if targeted_property == 'COMMANDS_LAUNCHED_OUTSIDE_PROJECT_ONLY':
72
launch_outside_project_command_list.extend(command_list)
73
if targeted_property == 'COMMANDS_FOLLOWED_BY_COMMAND':
74
command_followed_by_command_list.extend(command_list)
97
for command in fields[1].split(';')]
99
== 'COMMANDS_LAUNCHED_IN_OR_OUTSIDE_PROJECT'):
100
launch_inside_project_command_list.extend(
102
launch_outside_project_command_list.extend(
104
if (targeted_property
105
== 'COMMANDS_LAUNCHED_OUTSIDE_PROJECT_ONLY'):
106
launch_outside_project_command_list.extend(
108
if (targeted_property
109
== 'COMMANDS_FOLLOWED_BY_COMMAND'):
110
command_followed_by_command_list.extend(
112
if (targeted_property
113
== 'IMPORT') and current_template_import:
114
import_commands[template][current_template_import] \
117
# try to fetch import command results
118
reg_result = re.search('\[(.*)\]', fields[0].strip())
120
current_template_import = reg_result.group(1)
75
122
except (OSError, IOError):
99
150
followed_by_template = True
100
151
if command_name in builtincommands.followed_by_command:
101
152
followed_by_command = True
102
# default for commands: if not inside nor outside, and it's a template command, make it launch inside a project only
103
if not launch_inside_project and not launch_outside_project:
153
# default for commands: if not inside nor outside, and
154
# it's a template command, make it launch inside a project
156
if not (launch_inside_project or launch_outside_project):
104
157
launch_inside_project = True
106
__commands[template][command_name] = Command(command_name, file_path, template, launch_inside_project, launch_outside_project, followed_by_template, followed_by_command, hooks['pre'], hooks['post'])
159
__commands[template][command_name] = Command(
160
command_name, file_path, template,
161
launch_inside_project, launch_outside_project,
162
followed_by_template, followed_by_command,
163
hooks['pre'], hooks['post'])
165
# now try to import command for existing templates
166
for importing_template in import_commands:
167
for imported_template in import_commands[importing_template]:
168
for command_name in import_commands[importing_template][imported_template]:
169
# if instruction to import all commands, get them first
170
if command_name == 'all':
172
for command_name in __commands[imported_template]:
173
__commands = try_to_import_command(__commands,
174
importing_template, imported_template,
177
# template doesn't exist: ignore
179
break # no need to cycle anymore (all commands imported)
181
__commands = try_to_import_command(__commands,
182
importing_template, imported_template,
109
185
# add builtin commands (avoiding gettext and hooks)
110
186
__commands['builtins'] = {}
111
187
for elem in dir(builtincommands):
112
188
command = getattr(builtincommands, elem)
113
if callable(command) and not command.__name__.startswith(('pre_', 'post_', 'gettext')):
189
if (callable(command)
190
and not command.__name__.startswith(('pre_', 'post_', 'gettext'))):
114
191
command_name = command.__name__
115
192
# here, special case for some commands
116
193
launch_inside_project = False
127
204
if command_name in builtincommands.followed_by_command:
128
205
followed_by_command = True
130
# default for commands: if not inside nor outside only, and it's a builtin command, make it launch wherever
207
# default for commands: if not inside nor outside only, and it's a
208
# builtin command, make it launch wherever
131
209
if not launch_inside_project and not launch_outside_project:
132
210
launch_inside_project = True
133
211
launch_outside_project = True
135
hooks = {'pre': None, 'post':None}
213
hooks = {'pre': None, 'post': None}
136
214
for event in ('pre', 'post'):
137
if hasattr(builtincommands, event + '_' + command_name):
138
hooks[event] = getattr(builtincommands, event + '_' + command_name)
140
__commands['builtins'][command_name] = Command(command_name, command, None, launch_inside_project, launch_outside_project, followed_by_template, followed_by_command, hooks['pre'], hooks['post'])
215
event_hook = getattr(builtincommands,
216
event + '_' + command_name, None)
218
hooks[event] = event_hook
220
__commands['builtins'][command_name] = Command(
221
command_name, command, None, launch_inside_project,
222
launch_outside_project, followed_by_template,
223
followed_by_command, hooks['pre'], hooks['post'])
142
225
return __commands
145
def get_command_by_criteria(**criterias):
228
def get_commands_by_criteria(**criterias):
146
229
"""Get a list of all commands corresponding to criterias
148
Criterias correponds to Command object properties"""
150
# all criterias are None by default, which means, don't care about the value
231
Criterias correponds to Command object properties.
234
# all criterias are None by default, which means, don't care about the
151
236
matched_commands = []
152
237
all_commands = get_all_commands()
154
239
for template_available in all_commands:
155
if criterias.has_key('template') and criterias['template'] != template_available:
156
continue # to speed up the search
240
if ('template' in criterias
241
and criterias['template'] != template_available):
242
continue # go to next round if no template match
157
243
for candidate_command_name in all_commands[template_available]:
158
candidate_command = all_commands[template_available][candidate_command_name]
244
candidate_command = all_commands[
245
template_available][candidate_command_name]
159
246
command_ok = True
160
247
# check all criterias (template has already been checked)
161
248
for elem in criterias:
162
if elem is not 'template' and getattr(candidate_command, elem) != criterias[elem]:
249
if (elem is not 'template'
250
and getattr(candidate_command, elem) != criterias[elem]):
163
251
command_ok = False
164
252
continue # no need to check other criterias
166
matched_commands.append(candidate_command)
254
matched_commands.append(candidate_command)
168
256
return matched_commands
259
def get_command_names_by_criteria(**criteria):
260
"""Get a tuple of all command names corresponding to criteria.
262
'criteria' correponds to Command object properties.
264
return (command.name for command in get_commands_by_criteria(**criteria))
171
267
def get_all_templates():
172
"""Get a list of all templates"""
174
return [template for template in get_all_commands().keys() if template != "builtins"]
268
"""Get a tuple of all templates"""
270
template for template in get_all_commands().keys()
271
if template != "builtins")
179
275
def _die(self, function_name, return_code):
276
'''Quit immediately and print error if return_code != 4'''
180
278
print _("ERROR: %s command failed") % function_name
181
279
print _("Aborting")
182
sys.exit(return_code)
280
sys.exit(return_code)
184
def __init__(self, command_name, command, template=None, inside_project=True, outside_project=False, followed_by_template=False, followed_by_command=False, prehook=None, posthook=None):
282
def __init__(self, command_name, command, template=None,
283
inside_project=True, outside_project=False,
284
followed_by_template=False, followed_by_command=False,
285
prehook=None, posthook=None):
185
286
self.command = command
287
# self.template is the native template where the command is from
288
# if this command is imported into another template, the object
289
# is still the same, only the access byx
290
# get_all_commands()[importing_template]["command_name"]
186
291
self.template = template
187
292
self.prehook = prehook
188
293
self.posthook = posthook
205
312
if self.followed_by_template: # template completion
206
313
if not self.template: # builtins command case
207
314
completion.extend(get_all_templates())
208
else: # complete with current template (which != from template_in_cli: ex create command (multiple templates))
209
completion.extend([self.template])
316
# complete with current template (which != from
317
# template_in_cli: ex create command (multiple
318
# templates)). Fetch as well imported command in
320
for template in get_all_templates():
322
if get_all_commands()[template][self.name] == self:
323
completion.extend([template])
210
326
else: # there is a template, add template commands
211
327
if self.followed_by_command: # template command completion
212
completion.extend([command.name for command in get_command_by_criteria(template=template_in_cli)])
329
get_command_names_by_criteria(
330
template=template_in_cli))
213
331
if self.followed_by_command: # builtin command completion
214
completion.extend([command.name for command in get_command_by_criteria(template="builtins")])
333
get_command_names_by_criteria(template="builtins"))
216
335
elif len(args) == 2:
217
336
if not template_in_cli and self.followed_by_template:
218
337
template_in_cli = args[0]
219
if self.followed_by_command: # template command completion and builtins command
220
completion.extend([command.name for command in get_command_by_criteria(template=template_in_cli)])
221
completion.extend([command.name for command in get_command_by_criteria(template="builtins")])
338
# template command completion and builtins command.
339
if self.followed_by_command:
341
get_command_names_by_criteria(
342
template=template_in_cli))
344
get_command_names_by_criteria(template="builtins"))
223
# give to the command the opportunity of giving some shell-completion features
346
# give to the command the opportunity of giving some shell-completion
224
348
if template_in_cli == self.template and len(completion) == 0:
225
349
if callable(self.command): # Internal function
226
completion.extend(self.command(template_in_cli, "", args, True))
351
self.command(template_in_cli, "", args, True))
227
352
else: # External command
228
instance = subprocess.Popen([self.command, "shell-completion"] + args, stdout=subprocess.PIPE)
353
instance = subprocess.Popen(
354
[self.command, "shell-completion"] + args,
355
stdout=subprocess.PIPE)
229
356
command_return_completion, err = instance.communicate()
230
357
if instance.returncode != 0:
233
360
completion.extend(command_return_completion.split(','))
235
return(" ".join(completion))
362
return " ".join(completion)
237
def help(self, dest_path,command_args):
364
def help(self, dest_path, command_args):
238
365
"""Print help of the current command"""
241
368
if callable(self.command): # intern function, return __doc__
242
369
print (self.command.__doc__)
243
370
else: # launch command with "help" parameter
244
return_code = subprocess.call([self.command, "help"] + command_args, cwd=dest_path)
371
return_code = subprocess.call(
372
[self.command, "help"] + command_args, cwd=dest_path)
248
376
def is_right_context(self, dest_path, verbose=True):
249
"""Check if we are in the right context for launching the command"""
251
# verbose à false pour l'introspection des commandes dispos
377
"""Check if we are in the right context for launching the command.
379
If you are using this to introspect available commands, then set
253
382
# check if dest_path check outside or inside only project :)
254
383
if self.inside_project and not self.outside_project:
256
385
project_path = tools.get_root_project_path(dest_path)
257
386
except tools.project_path_not_found:
259
print _("ERROR: Can't find project in %s.\nEnsure you launch this command from a quickly project directory.") % dest_path
389
"ERROR: Can't find project in %s.\nEnsure you launch "
390
"this command from a quickly project directory.")
260
392
print _("Aborting")
262
394
if self.outside_project and not self.inside_project:
264
396
project_path = tools.get_root_project_path(dest_path)
266
print _("ERROR: %s is a project. You can't launch %s command within a project. " \
267
"Please choose another path." % (project_path, self.command))
399
"ERROR: %s is a project. You can't launch %s command "
400
"within a project. Please choose another path."
401
% (project_path, self.command))
268
402
print _("Aborting")
270
404
except tools.project_path_not_found:
276
def launch(self, current_dir, command_args, template=None):
409
def launch(self, current_dir, command_args, project_template=None):
277
410
"""Launch command and hooks for it
279
This command will perform the right action (insider function or script execution) after having
280
checked the context"""
282
# template is current template (it will be useful for builtin commands)
284
# if template not specified, take the one for the command
285
# the template argument is useful when builtin commands which behavior take into account the template name
287
template = self.template # (which can be None if it's a builtin command launched outside a project)
412
This command will perform the right action (insider function or script
413
execution) after having checked the context.
289
416
if not self.is_right_context(current_dir): # check in verbose mode
292
421
# get root project dir
294
423
project_path = tools.get_root_project_path(current_dir)
295
except tools.project_path_not_found:
424
inside_project = True
425
except tools.project_path_not_found:
296
426
# launch in current project
297
427
project_path = current_dir
428
inside_project = False
430
# transition if we are inside a project and template is not None (builtins)
431
# (call upgrade from native template)
432
if inside_project and self.name != "upgrade" and self.template:
433
(project_version, template_version) = templatetools.get_project_and_template_versions(self.template)
434
if project_version < template_version:
436
return_code = get_all_commands()[self.template]['upgrade'].launch( current_dir, [project_version, template_version], project_template)
438
templatetools.update_version_in_project_file(template_version, self.template)
440
sys.exit(return_code)
441
except KeyError: # if KeyError, no upgrade command for this template
300
return_code = self.prehook(template, project_path, command_args)
445
return_code = self.prehook(self.template, project_template, project_path, command_args)
301
446
if return_code != 0:
302
447
self._die(self.prehook.__name__, return_code)
304
449
if callable(self.command): # Internal function
305
return_code = self.command(template, project_path, command_args)
450
return_code = self.command(project_template, project_path, command_args)
306
451
else: # External command
307
return_code = subprocess.call([self.command] + command_args, cwd=project_path)
452
return_code = subprocess.call(
453
[self.command] + command_args, cwd=project_path)
308
454
if return_code != 0:
309
self._die(self.name,return_code)
455
self._die(self.name, return_code)
311
457
if self.posthook:
312
return_code = self.posthook(template, project_path, command_args)
458
return_code = self.posthook(project_template, project_path, command_args)
313
459
if return_code != 0:
314
460
self._die(self.posthook.__name__, return_code)