source: tailor/vcpx/config.py @ 1283

Revision 1283, 9.4 KB checked in by lele@…, 7 years ago (diff)

A little easier configuration
It's now possible to specify just 'kind:' to implicitly mean
'kind:projectname' for the source and target repositories.
When there's no need of further information like when using darcs as
target, the relative section may be omitted completely.

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 cStringIO import StringIO
15from ConfigParser import SafeConfigParser, NoSectionError, DEFAULTSECT
16from vcpx import TailorException
17
18
19class ConfigurationError(TailorException):
20    """Configuration error"""
21
22
23LOGGING_SUPER_SECTION = '[[logging]]'
24BASIC_LOGGING_CONFIG = """\
25[formatters]
26keys = console
27
28[formatter_console]
29format =  %(asctime)s [%(levelname).1s] %(message)s
30datefmt = %H:%M:%S
31
32[loggers]
33keys = root
34
35[logger_root]
36level = INFO
37handlers = console
38
39[handlers]
40keys = console
41
42[handler_console]
43class = StreamHandler
44formatter = console
45args = (sys.stdout,)
46level = INFO
47"""
48
49
50class Config(SafeConfigParser):
51    '''
52    Syntactic sugar around standard ConfigParser, for easier access to
53    the configuration. To access any single project use the configuration
54    as a dictionary.
55
56    The file may be a full fledged Python script, starting
57    with the usual ``"#!..."`` notation: in this case, it gets evaluated and
58    its documentation becomes the actual configuration, while the functions
59    it defines may be referenced by the `before-commit` and `after-commit`
60    slots.
61
62    This is where the logging system gets initialized, possibly merging a
63    logging specific configuration section, introduced by a *supersection*
64    ``[[logging]]``.
65    '''
66
67    def __init__(self, fp, defaults):
68        SafeConfigParser.__init__(self)
69        self.namespace = {}
70
71        loggingcfg = None
72        if fp:
73            if fp.read(2) == '#!':
74                fp.seek(0)
75                exec fp.read() in globals(), self.namespace
76                config = self.namespace['__doc__']
77            else:
78                fp.seek(0)
79                config = fp.read()
80
81            # Look for a [[logging]] separator, that introduce a
82            # standard logging  section
83            cfgs = config.split(LOGGING_SUPER_SECTION)
84            if len(cfgs) == 2:
85                tailorcfg, loggingcfg = cfgs
86            else:
87                tailorcfg = cfgs[0]
88
89            self.readfp(StringIO(tailorcfg))
90
91        # Override the defaults with the command line options
92        if defaults:
93            self._defaults.update(defaults)
94
95        self._setupLogging(loggingcfg and loggingcfg or BASIC_LOGGING_CONFIG)
96
97    def _setupLogging(self, config):
98        """
99        Tailor own's approach at file based logging configuration.
100        """
101
102        import logging, logging.handlers
103
104        cp = SafeConfigParser()
105        cp.readfp(StringIO(config), self._defaults)
106
107        #first, do the formatters...
108        flist = cp.get("formatters", "keys")
109        if flist:
110            flist = flist.split(',')
111            formatters = {}
112            for form in flist:
113                sectname = "formatter_%s" % form
114                opts = cp.options(sectname)
115                if "format" in opts:
116                    fs = cp.get(sectname, "format", 1)
117                else:
118                    fs = None
119                if "datefmt" in opts:
120                    dfs = cp.get(sectname, "datefmt", 1)
121                else:
122                    dfs = None
123                f = logging.Formatter(fs, dfs)
124                formatters[form] = f
125        #next, do the handlers...
126        try:
127            hlist = cp.get("handlers", "keys")
128            if hlist:
129                handlers = {}
130                fixups = [] #for inter-handler references
131                for hand in hlist.split(','):
132                    try:
133                        sectname = "handler_%s" % hand
134                        klass = cp.get(sectname, "class")
135                        opts = cp.options(sectname)
136                        if "formatter" in opts:
137                            fmt = cp.get(sectname, "formatter")
138                        else:
139                            fmt = None
140                        klass = eval(klass, vars(logging))
141                        args = cp.get(sectname, "args")
142                        args = eval(args, vars(logging))
143                        h = apply(klass, args)
144                        if "level" in opts:
145                            level = cp.get(sectname, "level")
146                            h.setLevel(logging._levelNames[level])
147                        if fmt:
148                            h.setFormatter(formatters[fmt])
149                        #temporary hack for FileHandler and MemoryHandler.
150                        if klass == logging.handlers.MemoryHandler:
151                            if "target" in opts:
152                                target = cp.get(sectname,"target")
153                            else:
154                                target = ""
155                            if len(target):
156                                # the target handler may not be loaded
157                                # yet, so keep for later...
158                                fixups.append((h, target))
159                        handlers[hand] = h
160                    except:
161                        #if an error occurs when instantiating a
162                        #handler, too bad this could happen
163                        #e.g. because of lack of privileges
164                        raise #pass
165                #now all handlers are loaded, fixup inter-handler references...
166                for h,t in fixups:
167                    h.setTarget(handlers[t])
168            #at last, the loggers...first the root...
169            llist = cp.get("loggers", "keys")
170            if llist:
171                llist = llist.split(',')
172            llist.remove("root")
173            sectname = "logger_root"
174            root = logging.root
175            opts = cp.options(sectname)
176            if "level" in opts:
177                level = cp.get(sectname, "level")
178                root.setLevel(logging._levelNames[level])
179            for h in root.handlers[:]:
180                root.removeHandler(h)
181            hlist = cp.get(sectname, "handlers")
182            if hlist:
183                for h in hlist.split(','):
184                    root.addHandler(handlers[h])
185            #and now the others...
186            for log in llist:
187                sectname = "logger_%s" % log
188                qn = cp.get(sectname, "qualname", log)
189                opts = cp.options(sectname)
190                if "propagate" in opts:
191                    propagate = cp.getint(sectname, "propagate")
192                else:
193                    propagate = 1
194                logger = logging.getLogger(qn)
195                if "level" in opts:
196                    level = cp.get(sectname, "level")
197                    logger.setLevel(logging._levelNames[level])
198                for h in logger.handlers[:]:
199                    logger.removeHandler(h)
200                logger.propagate = propagate
201                logger.disabled = 0
202                hlist = cp.get(sectname, "handlers")
203                if hlist:
204                    for h in hlist.split(','):
205                        logger.addHandler(handlers[h])
206        except:
207            from sys import exc_info, stderr
208            from traceback import print_exception
209            ei = exc_info()
210            print_exception(ei[0], ei[1], ei[2], None, stderr)
211            del ei
212
213    def projects(self):
214        """
215        Return either the default projects or all the projects in the
216        in the configuration.
217        """
218
219        defaultp = self.getTuple('DEFAULT', 'projects')
220        return defaultp or [s for s in self.sections() if not ':' in s]
221
222    def get(self, section, option, default=None, raw=False, vars=None):
223        """Get an option value for a given section or the default value.
224
225        All % interpolations are expanded in the return values, based on the
226        defaults passed into the constructor, unless the optional argument
227        `raw` is true.  Additional substitutions may be provided using the
228        `vars` argument, which must be a dictionary whose contents overrides
229        any pre-existing defaults, but not those in the given section.
230
231        The section DEFAULT is special.
232        """
233
234        # Reimplement parent behaviour, that uses `vars` to override even
235        # the value in the specific section... Overriding the defaults
236        # seems a better idea
237
238        d = self._defaults.copy()
239        # Update with the entry specific variables
240        if vars is not None:
241            d.update(vars)
242        try:
243            d.update(self._sections[section])
244        except KeyError:
245            pass
246        option = self.optionxform(option)
247        try:
248            value = d[option]
249        except KeyError:
250            value = default
251
252        if not raw:
253            value = self._interpolate(section, option, str(value), d)
254
255        if value == 'None':
256            return default
257        elif value == 'True':
258            return True
259        elif value == 'False':
260            return False
261        else:
262            return value
263
264    def getTuple(self, section, option, default=None):
265        """
266        Parse the requested option as a tuple, if its value starts with
267        an open bracket, otherwise consider the value a single item
268        tuple.
269        """
270
271        value = self.get(section, option, default)
272        if value:
273            if value.startswith('('):
274                items = value.strip()[1:-1]
275            else:
276                items = value
277            return [i.strip() for i in items.split(',')]
278        else:
279            return []
Note: See TracBrowser for help on using the repository browser.