source: tailor/vcpx/session.py @ 315

Revision 315, 10.5 KB checked in by lele@…, 8 years ago (diff)

Reworked the interactive session, separating configuration from state

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        """Exit the interactive session."""
99
100        self.__log('Exiting...\n')
101        return True
102
103    def do_EOF(self, arg):
104        """Exit the interactive session."""
105       
106        return self.do_exit(arg)
107
108    def do_save(self, arg):
109        """Save the commands history on the specified file."""
110
111        import readline
112
113        if not arg:
114            return
115           
116        readline.write_history_file(arg)
117        self.__log('History saved in: %s\n' % arg)
118       
119    def do_cd(self, arg):
120        """Print or set current active directory."""
121
122        if arg and self.current_directory <> arg:
123            try:
124                chdir(arg)
125                self.current_directory = getcwd()
126            except:
127                self.__log('Cannot change current directory to %s\n' %
128                           arg)
129        self.__log('Current directory: %s\n' % self.current_directory)
130
131    do_current_directory = do_cd
132
133    def do_sub_directory(self, arg):
134        """
135        Print or set the subdirectory that actually contains the working copy.
136
137        This is desumed automatically to be the last component of
138        the upstream module or repository.
139        """
140       
141        if arg and self.sub_directory <> arg:
142            self.sub_directory = arg
143
144        self.__log('Sub directory: %s\n' % self.sub_directory)
145
146    def do_logfile(self, arg):
147        """Print or set the logfile of operations."""
148
149        import logging
150       
151        if arg:
152            self.logfile = arg
153            self.logger = logging.getLogger('tailor')
154            hdlr = logging.FileHandler(self.logfile)
155            formatter = logging.Formatter('%(asctime)s [%(levelname)s] %(message)s')
156            hdlr.setFormatter(formatter)
157            self.logger.addHandler(hdlr) 
158            self.logger.setLevel(logging.INFO)
159           
160        self.__log('Logging to: %s\n' % self.logfile)
161
162    def do_source_kind(self, arg):
163        """Print or set the source repository kind."""
164
165        if arg and self.source_kind <> arg:
166            self.source_kind = arg
167
168        self.__log('Current source kind: %s\n' % self.source_kind)
169       
170    def do_target_kind(self, arg):
171        """Print or set the target repository kind."""
172
173        if arg and self.target_kind <> arg:
174            self.target_kind = arg
175
176        self.__log('Current target kind: %s\n' % self.target_kind)
177
178    def do_source_repository(self, arg):
179        """Print or set the source repository."""
180
181        from os.path import sep
182       
183        if arg and self.source_repository <> arg:
184            if arg.endswith(sep):
185                arg = arg[:-1]
186            self.source_repository = arg
187
188        self.__log('Current source repository: %s\n' % self.source_repository)
189
190    def do_target_repository(self, arg):
191        """Print or set the target repository."""
192
193        from os.path import sep
194       
195        if arg and self.target_repository <> arg:
196            if arg.endswith(sep):
197                arg = arg[:-1]
198            self.target_repository = arg
199
200        self.__log('Current target repository: %s\n' % self.target_repository)
201
202    def do_source_module(self, arg):
203        """Print or set the source module."""
204
205        from os.path import sep
206       
207        if arg and self.source_module <> arg:
208            if arg.endswith(sep):
209                arg = arg[:-1]
210            self.source_module = arg
211
212        self.__log('Current target kind: %s\n' % self.source_module)
213
214    def do_target_module(self, arg):
215        """Print or set the target module."""
216
217        from os.path import sep
218       
219        if arg and self.target_module <> arg:
220            if arg.endswith(sep):
221                arg = arg[:-1]
222            self.target_module = arg
223
224        self.__log('Current target kind: %s\n' % self.target_module)
225
226    def readSourceRevision(self):
227        """Read the source revision from the state file."""
228
229        try:
230            sf = open(self.state_file)
231            revision = sf.read()
232            sf.close()
233        except IOError:
234            revision = None
235
236        return revision
237           
238    def saveSourceRevision(self, revision):
239        """Write current source revision in the state file."""
240
241        sf = open(self.state_file, 'w')
242        sf.write(revision)
243        sf.close()
244       
245    def do_state_file(self, arg):
246        """
247        Print or set the current state_file.
248
249        The argument must be a file name, possibly with the usual
250        "~user/file" convention.       
251        """
252
253        from os.path import isabs, abspath, expanduser
254
255        if arg:
256            arg = expanduser(arg)
257            if not isabs(arg):
258                arg = abspath(arg)
259       
260        if arg and self.state_file <> arg:
261            self.state_file = arg
262               
263        self.__log('Current state file: %s\n' % self.state_file)
264
265    def do_get_changes(self, arg):
266        """Fetch information on upstream changes."""
267
268        source_revision = self.readSourceRevision()
269        if self.source_kind and \
270           self.source_repository and \
271           self.source_module and \
272           source_revision:
273
274            dwd = DualWorkingDir(self.source_kind, self.target_kind)
275            self.changesets = dwd.getUpstreamChangesets(self.current_directory,
276                                                        self.source_repository,
277                                                        self.source_module,
278                                                        source_revision)
279            self.__log('Collected %d upstream changesets\n' %
280                       len(self.changesets))
281        else:
282            self.__err("needs 'source_kind', 'source_repository' and "
283                       "'source_module' to proceed.\n")
284
285    def do_show_changes(self, arg):
286        """Show the upstream changes not yet applied."""
287
288        if self.changesets:
289            self.__log(`self.changesets`)
290            self.__log('\n')
291        else:
292            self.__err("needs `get_changes` to proceed.\n")
293
294    def do_bootstrap(self, arg):
295        """
296        Checkout the initial upstream revision, by default HEAD (or
297        specified by argument), then import the subtree into the
298        target repository.
299        """
300       
301        from os.path import join, split, sep
302        from dualwd import DualWorkingDir
303
304        if self.sub_directory:
305            subdir = self.sub_directory
306        else:
307            subdir = split(self.source_module or self.source_repository)[1] or ''
308            self.do_sub_directory(subdir)
309
310        revision = arg or self.options.revision or 'HEAD'
311       
312        dwd = DualWorkingDir(self.source_kind, self.target_kind)
313        self.__log("Getting %s revision '%s' of '%s' from '%s'\n" % (
314            self.source_kind, revision,
315            self.source_module, self.source_repository))
316
317        try:
318            actual = dwd.checkoutUpstreamRevision(self.current_directory,
319                                                  self.source_repository,
320                                                  self.source_module,
321                                                  revision,
322                                                  subdir=subdir,
323                                                  logger=self.logger)
324            self.saveSourceRevision(actual)
325        except Exception, exc:
326            self.__err('Checkout failed: %s, %s' % (exc.__doc__, exc))
327            if self.logger:
328                self.logger.exception('Checkout failed')
329
330        try:
331            dwd.initializeNewWorkingDir(self.current_directory,
332                                        self.target_repository,
333                                        self.target_module,
334                                        self.sub_directory,
335                                        actual)
336        except:
337            self.__err('Working copy initialization failed: %s, %s' % (exc.__doc__, exc))
338            if self.logger:
339                self.logger.exception('Working copy initialization failed')
340
341       
342def interactive(options, args):
343    session = Session(options, args)
344    session.cmdloop(options.verbose and INTRO or "")
Note: See TracBrowser for help on using the repository browser.