source: tailor/vcpx/session.py @ 320

Revision 320, 13.4 KB checked in by lele@…, 8 years ago (diff)

Better help with usage for the interactive commands

Line 
1#! /usr/bin/python
2# -*- mode: python; coding: utf-8 -*-
3# :Progetto: vcpx -- Interactive session
4# :Creato:   ven 13 mag 2005 02:00:57 CEST
5# :Autore:   Lele Gaifax <lele@nautilus.homeip.net>
6# :Licenza:  GNU General Public License
7#
8
9"""
10Tailor interactive session.
11
12This module implements an alternative approach at driving the various
13steps tipically performed by tailor, using an interaction with the user
14instead of pushing options madness on him.
15"""
16
17__docformat__ = 'reStructuredText'
18
19from cmd import Cmd
20from os import chdir, getcwd       
21
22
23INTRO = """\
24Welcome to the Tailor interactive session: you can issue several commands
25with the usual `readline` facilities. With "help" you'll get a list of
26available commands.
27"""
28
29
30class Session(Cmd):
31    """Tailor interactive session."""
32   
33    prompt = "tailor $ "
34   
35    def __init__(self, options, args):
36        """
37        Initialize a new interactive session.
38
39        Set the default values, and override them with option settings,
40        then slurp in each command line argument that should contain
41        a list of commands to be executed.
42        """
43       
44        Cmd.__init__(self)
45        self.options = options       
46        self.args = args
47       
48        self.source_repository = options.repository
49        self.source_kind = options.source_kind
50        self.source_module = options.module
51        self.target_repository = None
52        self.target_kind = options.target_kind
53        self.target_module = None
54        self.current_directory = getcwd()
55        self.sub_directory = None
56       
57        self.state_file = 'tailor.state'
58       
59        self.changesets = None
60        self.logfile = None
61        self.logger = None
62       
63        self.__processArgs()
64
65    def __processArgs(self):
66        """
67        Process optional command line arguments.
68
69        Each argument is assumed to contain a list of tailor commands
70        to execute in order.
71        """
72
73        for arg in self.args:
74            self.cmdqueue.extend(file(arg).readlines())
75
76    def __log(self, what):
77        if self.logger:
78            self.logger.info(what)
79           
80        if self.options.verbose:
81            self.stdout.write(what)
82
83    def __err(self, what):
84        if self.logger:
85            self.logger.error(what)
86           
87        self.stdout.write('Error: ')
88        self.stdout.write(what)
89       
90
91    ## Interactive commands
92
93    def emptyline(self):
94        """Override the default impl of reexecuting last command."""
95        pass
96       
97    def do_exit(self, arg):
98        """
99        Usage: exit
100
101        Terminate the interactive session. This is the same thing
102        happening upon EOF (Ctrl-D).
103        """
104
105        self.__log('Exiting...\n')
106        return True
107
108    do_EOF = do_exit
109
110    def do_save(self, arg):
111        """
112        Usage: save filename
113
114        Save the commands history on the specified file.
115        """
116
117        import readline
118
119        if not arg:
120            return
121           
122        readline.write_history_file(arg)
123        self.__log('History saved in: %s\n' % arg)
124       
125    def do_cd(self, arg):
126        """
127        Usage: cd [dirname]
128
129        Print or set current active directory.
130        """
131
132        if arg and self.current_directory <> arg:
133            try:
134                chdir(arg)
135                self.current_directory = getcwd()
136            except:
137                self.__log('Cannot change current directory to %s\n' %
138                           arg)
139        self.__log('Current directory: %s\n' % self.current_directory)
140
141    do_current_directory = do_cd
142
143    def do_sub_directory(self, arg):
144        """
145        Usage: sub_directory dirname
146       
147        Print or set the subdirectory that actually contains the
148        working copy. When not explicitly set, this is desumed from
149        the last component of the upstream module name or repository.
150        """
151       
152        if arg and self.sub_directory <> arg:
153            self.sub_directory = arg
154
155        self.__log('Sub directory: %s\n' % self.sub_directory)
156
157    def do_logfile(self, arg):
158        """
159        Usage: logfile [filename]
160       
161        Print or set the logfile of operations. By default there's no log.
162        """
163
164        import logging
165       
166        if arg:
167            self.logfile = arg
168            self.logger = logging.getLogger('tailor')
169            hdlr = logging.FileHandler(self.logfile)
170            formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')
171            hdlr.setFormatter(formatter)
172            self.logger.addHandler(hdlr) 
173            self.logger.setLevel(logging.INFO)
174           
175        self.__log('Logging to: %s\n' % self.logfile)
176
177    def do_print_executed_commands(self, arg):
178        """
179        Usage: print_executed_commands [0|1]
180
181        Print or set the verbosity on external commands execution.
182        """
183
184        from shwrap import SystemCommand
185       
186        if arg:
187            SystemCommand.VERBOSE = bool(arg)
188
189        self.__log('Print executed commands: %s\n' % SystemCommand.VERBOSE)
190
191    def do_patch_name_format(self, arg):
192        """
193        Usage: patch_name_format [format]
194
195        Print or set the patch name format, ie the prototype that will
196        be used to compute the patch name.
197
198        The prototype may contain %(keyword)s such as 'module',
199        'author', 'date', 'revision', 'firstlogline', 'remaininglog'
200        for normal updates, otherwise 'module', 'authors',
201        'nchangesets', 'mindate' and 'maxdate'.
202        """
203
204        from target import SyncronizableTargetWorkingDir
205
206        if arg:
207            SyncronizableTargetWorkingDir.PATCH_NAME_FORMAT = arg
208
209        self.__log('Patch name format: %s\n' %
210                   SyncronizableTargetWorkingDir.PATCH_NAME_FORMAT)
211       
212    def do_remove_first_log_line(self, arg):
213        """
214        Usage: remove_first_log_line [0|1]
215       
216        Print or set if tailor should drop the first line of the
217        upstream changelog.
218
219        This is intended to go in pair with patch_name_format, when
220        using it's 'firstlogline' variable to build the name of the
221        patch.
222        """
223
224        from target import SyncronizableTargetWorkingDir
225
226        if arg:
227            SyncronizableTargetWorkingDir.REMOVE_FIRST_LOG_LINE = bool(arg)
228
229        self.__log('Remove first log line: %s\n' %
230                   SyncronizableTargetWorkingDir.REMOVE_FIRST_LOG_LINE)
231
232    def do_refill_changelogs(self, arg):
233        """
234        Usage: refill_changelogs [0|1]
235
236        Print or set if tailor should refill the upstream changelogs,
237        as it does by default.
238        """
239
240        from changes import Changeset
241       
242        if arg:
243            Changeset.REFILL_MESSAGE = bool(arg)
244
245        self.__log('Refill changelogs: %s\n' % Changeset.REFILL_MESSAGE)
246       
247    def do_source_kind(self, arg):
248        """
249        Usage: source_kind [svn|darcs|cvs]
250
251        Print or set the source repository kind.
252        """
253
254        if arg and self.source_kind <> arg:
255            self.source_kind = arg
256
257        self.__log('Current source kind: %s\n' % self.source_kind)
258       
259    def do_target_kind(self, arg):
260        """
261        Usage: target_kind [svn|darcs|cvs|monotone|cdv|bzr]
262
263        Print or set the target repository kind.
264        """
265
266        if arg and self.target_kind <> arg:
267            self.target_kind = arg
268
269        self.__log('Current target kind: %s\n' % self.target_kind)
270
271    def do_source_repository(self, arg):
272        """
273        Usage: source_repository [repos]
274
275        Print or set the source repository.
276        """
277
278        from os.path import sep
279       
280        if arg and self.source_repository <> arg:
281            if arg.endswith(sep):
282                arg = arg[:-1]
283            self.source_repository = arg
284
285        self.__log('Current source repository: %s\n' % self.source_repository)
286
287    def do_target_repository(self, arg):
288        """
289        Usage: target_repository [repos]
290
291        Print or set the target repository. This is currently unused.
292        """
293
294        from os.path import sep
295       
296        if arg and self.target_repository <> arg:
297            if arg.endswith(sep):
298                arg = arg[:-1]
299            self.target_repository = arg
300
301        self.__log('Current target repository: %s\n' % self.target_repository)
302
303    def do_source_module(self, arg):
304        """
305        Usage: source_module [module]
306       
307        Print or set the source module.
308        """
309
310        from os.path import sep
311       
312        if arg and self.source_module <> arg:
313            if arg.endswith(sep):
314                arg = arg[:-1]
315            self.source_module = arg
316
317        self.__log('Current target kind: %s\n' % self.source_module)
318
319    def do_target_module(self, arg):
320        """
321        Usage: target_module [module]
322
323        Print or set the target module. This is currently not used.
324        """
325
326        from os.path import sep
327       
328        if arg and self.target_module <> arg:
329            if arg.endswith(sep):
330                arg = arg[:-1]
331            self.target_module = arg
332
333        self.__log('Current target kind: %s\n' % self.target_module)
334
335    def readSourceRevision(self):
336        """Read the source revision from the state file."""
337
338        try:
339            sf = open(self.state_file)
340            revision = sf.read()
341            sf.close()
342        except IOError:
343            revision = None
344
345        return revision
346           
347    def saveSourceRevision(self, revision):
348        """Write current source revision in the state file."""
349
350        sf = open(self.state_file, 'w')
351        sf.write(revision)
352        sf.close()
353       
354    def do_state_file(self, arg):
355        """
356        Usage: state_file [filename]
357       
358        Print or set the current state file, where tailor stores the
359        source revision that has been applied last.
360       
361        The argument must be a file name, possibly with the usual
362        "~user/file" convention.       
363        """
364
365        from os.path import isabs, abspath, expanduser
366
367        if arg:
368            arg = expanduser(arg)
369            if not isabs(arg):
370                arg = abspath(arg)
371       
372        if arg and self.state_file <> arg:
373            self.state_file = arg
374               
375        self.__log('Current state file: %s\n' % self.state_file)
376
377    def do_get_changes(self, arg):
378        """Fetch information on upstream changes."""
379
380        source_revision = self.readSourceRevision()
381        if self.source_kind and \
382           self.source_repository and \
383           self.source_module and \
384           source_revision:
385
386            dwd = DualWorkingDir(self.source_kind, self.target_kind)
387            self.changesets = dwd.getUpstreamChangesets(self.current_directory,
388                                                        self.source_repository,
389                                                        self.source_module,
390                                                        source_revision)
391            self.__log('Collected %d upstream changesets\n' %
392                       len(self.changesets))
393        else:
394            self.__err("needs 'source_kind', 'source_repository' and "
395                       "'source_module' to proceed.\n")
396
397    def do_show_changes(self, arg):
398        """Show the upstream changes not yet applied."""
399
400        if self.changesets:
401            self.__log(`self.changesets`)
402            self.__log('\n')
403        else:
404            self.__err("needs `get_changes` to proceed.\n")
405
406    def do_bootstrap(self, arg):
407        """
408        Usage: bootstrap [revision]
409       
410        Checkout the initial upstream revision, by default HEAD (or
411        specified by argument), then import the subtree into the
412        target repository.
413        """
414       
415        from os.path import join, split, sep
416        from dualwd import DualWorkingDir
417
418        if self.sub_directory:
419            subdir = self.sub_directory
420        else:
421            subdir = split(self.source_module or self.source_repository)[1] or ''
422            self.do_sub_directory(subdir)
423
424        revision = arg or self.options.revision or 'HEAD'
425       
426        dwd = DualWorkingDir(self.source_kind, self.target_kind)
427        self.__log("Getting %s revision '%s' of '%s' from '%s'\n" % (
428            self.source_kind, revision,
429            self.source_module, self.source_repository))
430
431        try:
432            actual = dwd.checkoutUpstreamRevision(self.current_directory,
433                                                  self.source_repository,
434                                                  self.source_module,
435                                                  revision,
436                                                  subdir=subdir,
437                                                  logger=self.logger)
438            self.saveSourceRevision(actual)
439        except Exception, exc:
440            self.__err('Checkout failed: %s, %s' % (exc.__doc__, exc))
441            if self.logger:
442                self.logger.exception('Checkout failed')
443
444        try:
445            dwd.initializeNewWorkingDir(self.current_directory,
446                                        self.target_repository,
447                                        self.target_module,
448                                        self.sub_directory,
449                                        actual)
450        except:
451            self.__err('Working copy initialization failed: %s, %s' % (exc.__doc__, exc))
452            if self.logger:
453                self.logger.exception('Working copy initialization failed')
454
455       
456def interactive(options, args):
457    session = Session(options, args)
458    session.cmdloop(options.verbose and INTRO or "")
Note: See TracBrowser for help on using the repository browser.