Merge lp:~jml/quickly/commands-cleanup into lp:quickly

Proposed by Jonathan Lange
Status: Merged
Merged at revision: not available
Proposed branch: lp:~jml/quickly/commands-cleanup
Merge into: lp:quickly
Diff against target: 493 lines (+185/-106)
2 files modified
quickly/builtincommands.py (+3/-3)
quickly/commands.py (+182/-103)
To merge this branch: bzr merge lp:~jml/quickly/commands-cleanup
Reviewer Review Type Date Requested Status
Didier Roche-Tolomelli Approve
Review via email: mp+15018@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Jonathan Lange (jml) wrote :

This script makes quickly/commands.py follow PEP 8. It also adds a wrapper around get_command_by_criteria to return the command names, since that's the only way it's used.

I've also added some XXX comments for things I don't understand.

lp:~jml/quickly/commands-cleanup updated
363. By Jonathan Lange

Remove lots of XXXs, mostly by adding more verbose comments.

Revision history for this message
Didier Roche-Tolomelli (didrocks) wrote :

Thanks for your patch.

Again, do not hesitate to share your 7 years of python experience with a stupid C/C++/shell programmer but beginner in python :)

I added a replacement for get_command_by_criteria to get_commands_by_criteria to bin/quickly as it's used there to avoid breaking stuff there.
Merged now, thanks.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'quickly/builtincommands.py'
--- quickly/builtincommands.py 2009-11-15 15:18:26 +0000
+++ quickly/builtincommands.py 2009-11-19 00:25:20 +0000
@@ -146,17 +146,17 @@
146 if template is None:146 if template is None:
147 template = "builtins"147 template = "builtins"
148 try:148 try:
149 command = commands_module.get_command_by_criteria(name=command_name, template=template)[0]149 command = commands_module.get_commands_by_criteria(name=command_name, template=template)[0]
150 except IndexError:150 except IndexError:
151 # check if a builtin commands corresponds151 # check if a builtin commands corresponds
152 template = "builtins"152 template = "builtins"
153 try:153 try:
154 command = commands_module.get_command_by_criteria(name=command_name, template=template)[0]154 command = commands_module.get_commands_by_criteria(name=command_name, template=template)[0]
155 except IndexError: 155 except IndexError:
156 # there is really not such command156 # there is really not such command
157 if template == "builtins":157 if template == "builtins":
158 # to help the user, we can search if this command_name corresponds to a command in a template158 # to help the user, we can search if this command_name corresponds to a command in a template
159 list_possible_commands = commands_module.get_command_by_criteria(name=command_name, followed_by_template=True)159 list_possible_commands = commands_module.get_commands_by_criteria(name=command_name, followed_by_template=True)
160 if list_possible_commands:160 if list_possible_commands:
161 print _("ERROR: help command must be followed by a template name for getting help from templates commands like %s." % command_name)161 print _("ERROR: help command must be followed by a template name for getting help from templates commands like %s." % command_name)
162 print _("Candidates template are: %s") % ", ".join([command.template for command in list_possible_commands])162 print _("Candidates template are: %s") % ", ".join([command.template for command in list_possible_commands])
163163
=== modified file 'quickly/commands.py'
--- quickly/commands.py 2009-11-15 23:06:02 +0000
+++ quickly/commands.py 2009-11-19 00:25:20 +0000
@@ -4,16 +4,16 @@
4#4#
5# This file is part of Quickly5# This file is part of Quickly
6#6#
7#This program is free software: you can redistribute it and/or modify it 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 8#under the terms of the GNU General Public License version 3, as published
9#by the Free Software Foundation.9#by the Free Software Foundation.
1010
11#This program is distributed in the hope that it will be useful, but 11#This program is distributed in the hope that it will be useful, but
12#WITHOUT ANY WARRANTY; without even the implied warranties of 12#WITHOUT ANY WARRANTY; without even the implied warranties of
13#MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR 13#MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
14#PURPOSE. See the GNU General Public License for more details.14#PURPOSE. See the GNU General Public License for more details.
1515
16#You should have received a copy of the GNU General Public License along 16#You should have received a copy of the GNU General Public License along
17#with this program. If not, see <http://www.gnu.org/licenses/>.17#with this program. If not, see <http://www.gnu.org/licenses/>.
1818
19import os19import os
@@ -29,16 +29,19 @@
2929
30gettext.textdomain('quickly')30gettext.textdomain('quickly')
3131
32# double depths tabular : template (or "builtin"), name32# A dictionary with keys either 'builtins' or the names of templates. Values
33# are another dictionary mapping command names to their command objects. This
34# is used as a cache of all commands that we've found in templates.
33__commands = {}35__commands = {}
3436
3537
36def get_all_commands():38def get_all_commands():
37 """Load all commands39 """Load all commands
38 40
39 First, load template command and then builtins one. Push right parameters depending41 First, load template command and then builtins one. Push right parameters
40 if hooks are available, or if the command execution is special42 depending if hooks are available, or if the command execution is special
41 You can note that create command is automatically overloaded atm"""43 You can note that create command is automatically overloaded atm.
44 """
4245
43 if len(__commands) > 0:46 if len(__commands) > 0:
44 return __commands47 return __commands
@@ -57,21 +60,32 @@
57 launch_outside_project_command_list = []60 launch_outside_project_command_list = []
58 command_followed_by_command_list = []61 command_followed_by_command_list = []
59 try:62 try:
60 files_command_parameters = file(os.path.join(template_path, "commandsconfig"), 'rb')63 files_command_parameters = file(
61 for line in files_command_parameters: 64 os.path.join(template_path, "commandsconfig"), 'rb')
62 fields = line.split('#')[0] # Suppress commentary after the value in configuration file and in full line65 for line in files_command_parameters:
66 # Suppress commentary after the value in configuration
67 # file and in full line.
68 fields = line.split('#')[0]
63 fields = fields.split('=') # Separate variable from value69 fields = fields.split('=') # Separate variable from value
64 # normally, we have two fields in "fields"70 # normally, we have two fields in "fields"
65 if len(fields) == 2:71 if len(fields) == 2:
66 targeted_property = fields[0].strip()72 targeted_property = fields[0].strip()
67 command_list = [command.strip() for command in fields[1].split(';')]73 command_list = [
68 if targeted_property == 'COMMANDS_LAUNCHED_IN_OR_OUTSIDE_PROJECT':74 command.strip()
69 launch_inside_project_command_list.extend(command_list)75 for command in fields[1].split(';')]
70 launch_outside_project_command_list.extend(command_list)76 if (targeted_property
71 if targeted_property == 'COMMANDS_LAUNCHED_OUTSIDE_PROJECT_ONLY':77 == 'COMMANDS_LAUNCHED_IN_OR_OUTSIDE_PROJECT'):
72 launch_outside_project_command_list.extend(command_list)78 launch_inside_project_command_list.extend(
79 command_list)
80 launch_outside_project_command_list.extend(
81 command_list)
82 if (targeted_property
83 == 'COMMANDS_LAUNCHED_OUTSIDE_PROJECT_ONLY'):
84 launch_outside_project_command_list.extend(
85 command_list)
73 if targeted_property == 'COMMANDS_FOLLOWED_BY_COMMAND':86 if targeted_property == 'COMMANDS_FOLLOWED_BY_COMMAND':
74 command_followed_by_command_list.extend(command_list)87 command_followed_by_command_list.extend(
88 command_list)
75 except (OSError, IOError):89 except (OSError, IOError):
76 pass90 pass
7791
@@ -81,13 +95,20 @@
81 if "." in command_name:95 if "." in command_name:
82 command_name = ".".join(command_name.split('.')[0:-1])96 command_name = ".".join(command_name.split('.')[0:-1])
8397
84 if os.path.isfile(file_path) and os.access(file_path, os.X_OK): # add the command to the list if is executable98 # add the command to the list if is executable
85 hooks = {'pre': None, 'post':None}99 # XXX: It's generally a bad idea to check if you can read to a
100 # file. Instead, you should just read it. The file might
101 # become unreadable between here and when you actually read
102 # it. -- jml, 2009-11-18
103 if os.path.isfile(file_path) and os.access(file_path, os.X_OK):
104 hooks = {'pre': None, 'post': None}
86 for event in ('pre', 'post'):105 for event in ('pre', 'post'):
87 if hasattr(builtincommands, event + '_' + command_name):106 event_hook = getattr(
88 hooks[event] = getattr(builtincommands, event + '_' + command_name)107 builtincommands, event + '_' + command_name, None)
89 108 if event_hook is not None:
90 # define special options for command 109 hooks[event] = event_hook
110
111 # define special options for command
91 launch_inside_project = False112 launch_inside_project = False
92 launch_outside_project = False113 launch_outside_project = False
93 followed_by_template = False114 followed_by_template = False
@@ -99,18 +120,25 @@
99 followed_by_template = True120 followed_by_template = True
100 if command_name in builtincommands.followed_by_command:121 if command_name in builtincommands.followed_by_command:
101 followed_by_command = True122 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 only123 # default for commands: if not inside nor outside, and
103 if not launch_inside_project and not launch_outside_project:124 # it's a template command, make it launch inside a project
125 # only
126 if not (launch_inside_project or launch_outside_project):
104 launch_inside_project = True127 launch_inside_project = True
105 128
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'])129 __commands[template][command_name] = Command(
107130 command_name, file_path, template,
108 131 launch_inside_project, launch_outside_project,
132 followed_by_template, followed_by_command,
133 hooks['pre'], hooks['post'])
134
135
109 # add builtin commands (avoiding gettext and hooks)136 # add builtin commands (avoiding gettext and hooks)
110 __commands['builtins'] = {}137 __commands['builtins'] = {}
111 for elem in dir(builtincommands):138 for elem in dir(builtincommands):
112 command = getattr(builtincommands, elem)139 command = getattr(builtincommands, elem)
113 if callable(command) and not command.__name__.startswith(('pre_', 'post_', 'gettext')):140 if (callable(command)
141 and not command.__name__.startswith(('pre_', 'post_', 'gettext'))):
114 command_name = command.__name__142 command_name = command.__name__
115 # here, special case for some commands143 # here, special case for some commands
116 launch_inside_project = False144 launch_inside_project = False
@@ -127,51 +155,73 @@
127 if command_name in builtincommands.followed_by_command:155 if command_name in builtincommands.followed_by_command:
128 followed_by_command = True156 followed_by_command = True
129157
130 # default for commands: if not inside nor outside only, and it's a builtin command, make it launch wherever158 # default for commands: if not inside nor outside only, and it's a
159 # builtin command, make it launch wherever
131 if not launch_inside_project and not launch_outside_project:160 if not launch_inside_project and not launch_outside_project:
132 launch_inside_project = True161 launch_inside_project = True
133 launch_outside_project = True162 launch_outside_project = True
134163
135 hooks = {'pre': None, 'post':None}164 hooks = {'pre': None, 'post': None}
136 for event in ('pre', 'post'):165 for event in ('pre', 'post'):
137 if hasattr(builtincommands, event + '_' + command_name):166 if hasattr(builtincommands, event + '_' + command_name):
138 hooks[event] = getattr(builtincommands, event + '_' + command_name) 167 hooks[event] = getattr(
139168 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'])169
141 170 __commands['builtins'][command_name] = Command(
171 command_name, command, None, launch_inside_project,
172 launch_outside_project, followed_by_template,
173 followed_by_command, hooks['pre'], hooks['post'])
174
142 return __commands175 return __commands
143 176
144177
145def get_command_by_criteria(**criterias):178def get_commands_by_criteria(**criterias):
146 """Get a list of all commands corresponding to criterias179 """Get a list of all commands corresponding to criterias
147 180
148 Criterias correponds to Command object properties"""181 Criterias correponds to Command object properties.
149182 """
150 # all criterias are None by default, which means, don't care about the value183
184 # all criterias are None by default, which means, don't care about the
185 # value.
151 matched_commands = []186 matched_commands = []
152 all_commands = get_all_commands()187 all_commands = get_all_commands()
153188
154 for template_available in all_commands:189 for template_available in all_commands:
155 if criterias.has_key('template') and criterias['template'] != template_available:190 if ('template' in criterias
191 and criterias['template'] != template_available):
192 # XXX: I'm sure this speeds up the search, but what exactly is it
193 # that we're skipping and why is it ok to skip this? -- jml,
194 # 2009-11-18.
156 continue # to speed up the search195 continue # to speed up the search
157 for candidate_command_name in all_commands[template_available]:196 for candidate_command_name in all_commands[template_available]:
158 candidate_command = all_commands[template_available][candidate_command_name]197 candidate_command = all_commands[
198 template_available][candidate_command_name]
159 command_ok = True199 command_ok = True
160 # check all criterias (template has already been checked)200 # check all criterias (template has already been checked)
161 for elem in criterias:201 for elem in criterias:
162 if elem is not 'template' and getattr(candidate_command, elem) != criterias[elem]:202 if (elem is not 'template'
203 and getattr(candidate_command, elem) != criterias[elem]):
163 command_ok = False204 command_ok = False
164 continue # no need to check other criterias205 continue # no need to check other criterias
165 if command_ok:206 if command_ok:
166 matched_commands.append(candidate_command) 207 matched_commands.append(candidate_command)
167 208
168 return matched_commands209 return matched_commands
169210
170211
212def get_command_names_by_criteria(**criteria):
213 """Get a list of all command names corresponding to criteria.
214
215 'criteria' correponds to Command object properties.
216 """
217 return (command.name for command in get_commands_by_criteria(**criteria))
218
219
171def get_all_templates():220def get_all_templates():
172 """Get a list of all templates"""221 """Get a list of all templates"""
173 222 return [
174 return [template for template in get_all_commands().keys() if template != "builtins"]223 template for template in get_all_commands().keys()
224 if template != "builtins"]
175225
176226
177class Command:227class Command:
@@ -181,7 +231,10 @@
181 print _("Aborting")231 print _("Aborting")
182 sys.exit(return_code)232 sys.exit(return_code)
183233
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):234 def __init__(self, command_name, command, template=None,
235 inside_project=True, outside_project=False,
236 followed_by_template=False, followed_by_command=False,
237 prehook=None, posthook=None):
185 self.command = command238 self.command = command
186 self.template = template239 self.template = template
187 self.prehook = prehook240 self.prehook = prehook
@@ -194,10 +247,12 @@
194247
195 def shell_completion(self, template_in_cli, args):248 def shell_completion(self, template_in_cli, args):
196 """Smart completion of a command249 """Smart completion of a command
197 250
198 This command try to see if the command is followed by a template and present template251 This command try to see if the command is followed by a template and
199 if it's the case. Otherwise, it calls the corresponding command argument"""252 present template if it's the case. Otherwise, it calls the
200 253 corresponding command argument.
254 """
255
201 completion = []256 completion = []
202257
203 if len(args) == 1:258 if len(args) == 1:
@@ -205,101 +260,127 @@
205 if self.followed_by_template: # template completion260 if self.followed_by_template: # template completion
206 if not self.template: # builtins command case261 if not self.template: # builtins command case
207 completion.extend(get_all_templates())262 completion.extend(get_all_templates())
208 else: # complete with current template (which != from template_in_cli: ex create command (multiple templates))263 else:
264 # complete with current template (which != from
265 # template_in_cli: ex create command (multiple
266 # templates))
209 completion.extend([self.template])267 completion.extend([self.template])
210 else: # there is a template, add template commands268 else: # there is a template, add template commands
211 if self.followed_by_command: # template command completion269 if self.followed_by_command: # template command completion
212 completion.extend([command.name for command in get_command_by_criteria(template=template_in_cli)])270 completion.extend(
271 get_command_names_by_criteria(
272 template=template_in_cli))
213 if self.followed_by_command: # builtin command completion273 if self.followed_by_command: # builtin command completion
214 completion.extend([command.name for command in get_command_by_criteria(template="builtins")])274 completion.extend(
275 get_command_names_by_criteria(template="builtins"))
215276
216 elif len(args) == 2:277 elif len(args) == 2:
217 if not template_in_cli and self.followed_by_template:278 if not template_in_cli and self.followed_by_template:
218 template_in_cli = args[0]279 template_in_cli = args[0]
219 if self.followed_by_command: # template command completion and builtins command280 # template command completion and builtins command.
220 completion.extend([command.name for command in get_command_by_criteria(template=template_in_cli)])281 if self.followed_by_command:
221 completion.extend([command.name for command in get_command_by_criteria(template="builtins")])282 completion.extend(
283 get_command_names_by_criteria(
284 template=template_in_cli))
285 completion.extend(
286 get_command_names_by_criteria(template="builtins"))
222287
223 # give to the command the opportunity of giving some shell-completion features 288 # give to the command the opportunity of giving some shell-completion
289 # features
224 if template_in_cli == self.template and len(completion) == 0:290 if template_in_cli == self.template and len(completion) == 0:
225 if callable(self.command): # Internal function291 if callable(self.command): # Internal function
226 completion.extend(self.command(template_in_cli, "", args, True))292 completion.extend(
293 self.command(template_in_cli, "", args, True))
227 else: # External command294 else: # External command
228 instance = subprocess.Popen([self.command, "shell-completion"] + args, stdout=subprocess.PIPE)295 instance = subprocess.Popen(
296 [self.command, "shell-completion"] + args,
297 stdout=subprocess.PIPE)
229 command_return_completion, err = instance.communicate()298 command_return_completion, err = instance.communicate()
230 if instance.returncode != 0:299 if instance.returncode != 0:
231 print err300 print err
232 sys.exit(1) 301 sys.exit(1)
233 completion.extend(command_return_completion.split(','))302 completion.extend(command_return_completion.split(','))
234303
235 return(" ".join(completion))304 return " ".join(completion)
236305
237 def help(self, dest_path,command_args):306 def help(self, dest_path, command_args):
238 """Print help of the current command"""307 """Print help of the current command"""
239308
240 return_code = 0 309 return_code = 0
241 if callable(self.command): # intern function, return __doc__310 if callable(self.command): # intern function, return __doc__
242 print (self.command.__doc__)311 print (self.command.__doc__)
243 else: # launch command with "help" parameter312 else: # launch command with "help" parameter
244 return_code = subprocess.call([self.command, "help"] + command_args, cwd=dest_path)313 return_code = subprocess.call(
314 [self.command, "help"] + command_args, cwd=dest_path)
245315
246 return(return_code)316 return return_code
247317
248 def is_right_context(self, dest_path, verbose=True):318 def is_right_context(self, dest_path, verbose=True):
249 """Check if we are in the right context for launching the command"""319 """Check if we are in the right context for launching the command.
250 320
251 # verbose à false pour l'introspection des commandes dispos321 If you are using this to introspect available commands, then set
252 322 verbose to False.
323 """
253 # check if dest_path check outside or inside only project :)324 # check if dest_path check outside or inside only project :)
254 if self.inside_project and not self.outside_project:325 if self.inside_project and not self.outside_project:
255 try:326 try:
256 project_path = tools.get_root_project_path(dest_path)327 project_path = tools.get_root_project_path(dest_path)
257 except tools.project_path_not_found:328 except tools.project_path_not_found:
258 if verbose:329 if verbose:
259 print _("ERROR: Can't find project in %s.\nEnsure you launch this command from a quickly project directory.") % dest_path330 print (_(
331 "ERROR: Can't find project in %s.\nEnsure you launch "
332 "this command from a quickly project directory.")
333 % dest_path)
260 print _("Aborting")334 print _("Aborting")
261 return False335 return False
262 if self.outside_project and not self.inside_project:336 if self.outside_project and not self.inside_project:
263 try:337 try:
264 project_path = tools.get_root_project_path(dest_path)338 project_path = tools.get_root_project_path(dest_path)
265 if verbose:339 if verbose:
266 print _("ERROR: %s is a project. You can't launch %s command within a project. " \340 print _(
267 "Please choose another path." % (project_path, self.command))341 "ERROR: %s is a project. You can't launch %s command "
342 "within a project. Please choose another path."
343 % (project_path, self.command))
268 print _("Aborting")344 print _("Aborting")
269 return False345 return False
270 except tools.project_path_not_found:346 except tools.project_path_not_found:
271 pass347 pass
272 348
273 return True349 return True
274350
275
276 def launch(self, current_dir, command_args, template=None):351 def launch(self, current_dir, command_args, template=None):
277 """Launch command and hooks for it352 """Launch command and hooks for it
278 353
279 This command will perform the right action (insider function or script execution) after having354 This command will perform the right action (insider function or script
280 checked the context"""355 execution) after having checked the context.
281 356 """
282 # template is current template (it will be useful for builtin commands)357
283358 # template is current template (it will be useful for builtin
284 # if template not specified, take the one for the command359 # commands)
285 # the template argument is useful when builtin commands which behavior take into account the template name360
361 # if template not specified, take the one for the command the template
362 # argument is useful when builtin commands which behavior take into
363 # account the template name
286 if template is None:364 if template is None:
287 template = self.template # (which can be None if it's a builtin command launched outside a project)365 # (which can be None if it's a builtin command launched outside a
366 # project)
367 template = self.template
288368
289 if not self.is_right_context(current_dir): # check in verbose mode369 if not self.is_right_context(current_dir): # check in verbose mode
290 return(1)370 return 1
291371
292 # get root project dir372 # get root project dir
293 try:373 try:
294 project_path = tools.get_root_project_path(current_dir)374 project_path = tools.get_root_project_path(current_dir)
295 except tools.project_path_not_found: 375 except tools.project_path_not_found:
296 # launch in current project376 # launch in current project
297 project_path = current_dir377 project_path = current_dir
298378
299 # transition if needed379 # transition if needed
300 if self.inside_project and self.name != "upgrade":380 if self.inside_project and self.name != "upgrade":
301 try:381 try:
302 get_all_commands()[self.template]['upgrade'].launch(current_dir, [], template)382 get_all_commands()[self.template]['upgrade'].launch(
383 current_dir, [], template)
303 except KeyError: # if KeyError, no upgrade command.384 except KeyError: # if KeyError, no upgrade command.
304 pass385 pass
305386
@@ -307,20 +388,18 @@
307 return_code = self.prehook(template, project_path, command_args)388 return_code = self.prehook(template, project_path, command_args)
308 if return_code != 0:389 if return_code != 0:
309 self._die(self.prehook.__name__, return_code)390 self._die(self.prehook.__name__, return_code)
310 391
311 if callable(self.command): # Internal function392 if callable(self.command): # Internal function
312 return_code = self.command(template, project_path, command_args)393 return_code = self.command(template, project_path, command_args)
313 else: # External command394 else: # External command
314 return_code = subprocess.call([self.command] + command_args, cwd=project_path)395 return_code = subprocess.call(
396 [self.command] + command_args, cwd=project_path)
315 if return_code != 0:397 if return_code != 0:
316 self._die(self.name,return_code)398 self._die(self.name, return_code)
317399
318 if self.posthook:400 if self.posthook:
319 return_code = self.posthook(template, project_path, command_args)401 return_code = self.posthook(template, project_path, command_args)
320 if return_code != 0:402 if return_code != 0:
321 self._die(self.posthook.__name__, return_code)403 self._die(self.posthook.__name__, return_code)
322 404
323 return(0)405 return 0
324
325
326

Subscribers

People subscribed via source and target branches