source: tailor/vcpx/config.py @ 463

Revision 463, 8.0 KB checked in by lele@…, 8 years ago (diff)

New configuration mechanism

Line 
1# -*- mode: python; coding: utf-8 -*-
2# :Progetto: vcpx -- Configuration bits
3# :Creato:   sab 30 lug 2005 20:51:28 CEST
4# :Autore:   Lele Gaifax <lele@nautilus.homeip.net>
5# :Licenza:  GNU General Public License
6#
7
8"""
9Handle the configuration details.
10"""
11
12__docformat__ = 'reStructuredText'
13
14from ConfigParser import SafeConfigParser
15
16class ConfigurationError(Exception):
17    """
18    Raised on invalid configuration.
19    """
20
21class StateFile(object):
22    """
23    State file that stores current revision and pending changesets.
24    """
25    def __init__(self, fname, config):
26        self.filename = fname
27
28    def __str__(self):
29        return self.filename
30
31    def load(self):
32        """
33        Read the source revision and pending changesets from the state file.
34        """
35
36        from cPickle import load
37
38        try:
39            sf = open(self.filename)
40            revision, changesets = load(sf)
41            sf.close()
42        except IOError:
43            revision = None
44            changesets = None
45
46        return revision, changesets
47
48    def write(self, revision, changesets):
49        """
50        Write current source revision and pending changesets in the state file.
51        """
52
53        from cPickle import dump
54
55        sf = open(self.filename, 'w')
56        dump((revision, changesets), sf)
57        sf.close()
58
59
60class Project(object):
61    """
62    This class collects the information related to a single project, such
63    as its source and target repositories and state file.
64    """
65
66    def __init__(self, name, config):
67        self.config = config
68        self.name = name
69        self._load()
70
71    def __str__(self):
72        return "Project %s at %s:\n\t" % (self.name, self.root) + \
73               "\n\t".join(['%s = %s' % (v, getattr(self, v))
74                            for v in ('source', 'target', 'state_file')])
75
76    def _load(self):
77        """
78        Load relevant information from the configuration.
79        """
80
81        from os import getcwd
82
83        self.root = self.config.get(self.name, 'root', getcwd())
84        self.source = self.__loadRepository('source')
85        self.target = self.__loadRepository('target')
86        self.state_file = StateFile(self.config.get(self.name, 'state-file'),
87                                    self.config)
88        before = self.config.getTuple(self.name, 'before-commit')
89        try:
90            self.before_commit = [self.config.namespace[f] for f in before]
91        except KeyError, e:
92            raise ConfigurationError('Project %s before-commit references '
93                                     'unknown function: '%self.name + str(e))
94        after = self.config.getTuple(self.name, 'after-commit')
95        try:
96            self.after_commit = [self.config.namespace[f] for f in after]
97        except KeyError, e:
98            raise ConfigurationError('Project %s after-commit references '
99                                     'unknown function: '%self.name + str(e))
100
101    def __loadRepository(self, which):
102        """
103        Given a repository named 'somekind:somename', return a Repository
104        (or a subclass of it, if 'SomekindRepository' exists) instance
105        that wraps it.
106        """
107
108        repname = self.config.get(self.name, which)
109        kind = repname[:repname.index(':')]
110        klassname = kind.capitalize() + 'Repository'
111        try:
112            klass = globals()[klassname]
113        except KeyError:
114            klass = Repository
115        return klass(repname, kind, self.config, which)
116
117    def workingDir(self):
118        """
119        Return a DualWorkingDir instance, ready to work.
120        """
121
122        dwd = DualWorkingDir(self.source, self.target)
123        dwd.setStateFile(self.state_file)
124
125
126class Repository(object):
127    """
128    Collector for the configuration of a single repository.
129    """
130
131    def __init__(self, name, kind, config, which):
132        self.name = name
133        self.kind = kind
134        self._load(config, which)
135
136    def __str__(self):
137        return "%s repository at %s" % (self.kind, self.repository)
138
139    def _load(self, config, which):
140        """
141        Load the configuration for this repository.
142
143        The two main and mandatory attributes, ``repository`` and ``module``
144        can be specified either on the specific slot in the config file, or
145        as ``source-repository`` (or ``target-repository``) in its [DEFAULT]
146        section.
147        """
148
149        self.repository = config.get(self.name, 'repository') or \
150                          config.get(self.name, '%s-repository' % which)
151        self.module = config.get(self.name, 'module') or \
152                      config.get(self.name, '%s-module' % which)
153
154    def workingDir(self):
155        """
156        Return an instance of the specific WorkingDir for this kind of
157        repository.
158        """
159
160        wdname = self.kind.capitalize() + 'WorkingDir'
161        modname = 'vcpx.' + self.kind
162        try:
163            wdmod = __import__(modname, globals(), locals(), [wdname])
164            workingdir = getattr(wdmod, wdname)
165        except (AttributeError, ImportError):
166            raise InvocationError("Unhandled source VCS kind: " + self.kind)
167        return workingdir()
168
169class BzrRepository(Repository):
170    METADIR = '.bzr'
171
172class CdvRepository(Repository):
173    METADIR = '.cdv'
174
175class CvsRepository(Repository):
176    METADIR = 'CVS'
177
178class CvspsRepository(CvsRepository):
179    pass
180
181class DarcsRepository(Repository):
182    METADIR = '_darcs'
183
184class HgRepository(Repository):
185    METADIR = '.hg'
186
187class MonotoneRepository(Repository):
188    METADIR = 'MT'
189
190    def _load(self, config, which):
191        Repository._load(self, config, which)
192        self.passphrase = config.get(self.name, 'passphrase')
193
194class SvnRepository(Repository):
195    METADIR = '.svn'
196
197    def _load(self, config, which):
198        Repository._load(self, config, which)
199        self.use_propset = config.get(self.name, 'use-propset', False)
200
201    def workingDir(self):
202        wd = Repository.workingDir(self)
203        wd.USE_PROPSET = self.use_propset
204        return wd
205
206
207class Config(SafeConfigParser):
208    """
209    Syntactic sugar around standard ConfigParser, for easier access to
210    the configuration. To access any single project use the configuration
211    as a dictionary.
212
213    The file may be a full fledged Python script, starting
214    with the usual "#!..." notation: in this case, it gets evaluated and
215    its documentation becomes the actual configuration, while the functions
216    it defines may be referenced by the 'before-commit' and 'after-commit'
217    slots.
218    """
219
220    def __init__(self, fp, defaults):
221        from cStringIO import StringIO
222
223        SafeConfigParser.__init__(self, defaults)
224        self.namespace = {}
225        if fp.read(2) == '#!':
226            fp.seek(0)
227            exec fp.read() in globals(), self.namespace
228            config = StringIO(self.namespace['__doc__'])
229            self.readfp(config)
230        else:
231            fp.seek(0)
232            self.readfp(fp)
233
234    def projects(self):
235        """
236        Return either the default projects or all the projects in the
237        in the configuration.
238        """
239
240        defaultp = self.getTuple('DEFAULT', 'projects')
241        return defaultp or [s for s in self.sections() if not ':' in s]
242
243    def get(self, section, option, default=None):
244        """
245        Return the requested option value if present, otherwise the default.
246        """
247        if self.has_option(section, option):
248            return SafeConfigParser.get(self, section, option)
249        else:
250            return default
251
252    def getTuple(self, section, option, default=None):
253        """
254        Parse the requested option as a tuple, if its value starts with
255        an open bracket, otherwise consider the value a single item
256        tuple.
257        """
258
259        value = self.get(section, option, default)
260        if value:
261            if value.startswith('('):
262                items = value.strip()[1:-1]
263            else:
264                items = value
265            return [i.strip() for i in items.split(',')]
266        else:
267            return []
268
269    def __getitem__(self, project):
270        return Project(project, self)
Note: See TracBrowser for help on using the repository browser.