~ubuntu-branches/ubuntu/karmic/calibre/karmic

« back to all changes in this revision

Viewing changes to src/calibre/utils/config.py

  • Committer: Bazaar Package Importer
  • Author(s): Martin Pitt
  • Date: 2009-07-30 12:49:41 UTC
  • mfrom: (1.3.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20090730124941-qjdsmri25zt8zocn
Tags: 0.6.3+dfsg-0ubuntu1
* New upstream release. Please see http://calibre.kovidgoyal.net/new_in_6/
  for the list of new features and changes.
* remove_postinstall.patch: Update for new version.
* build_debug.patch: Does not apply any more, disable for now. Might not be
  necessary any more.
* debian/copyright: Fix reference to versionless GPL.
* debian/rules: Drop obsolete dh_desktop call.
* debian/rules: Add workaround for weird Python 2.6 setuptools behaviour of
  putting compiled .so files into src/calibre/plugins/calibre/plugins
  instead of src/calibre/plugins.
* debian/rules: Drop hal fdi moving, new upstream version does not use hal
  any more. Drop hal dependency, too.
* debian/rules: Install udev rules into /lib/udev/rules.d.
* Add debian/calibre.preinst: Remove unmodified
  /etc/udev/rules.d/95-calibre.rules on upgrade.
* debian/control: Bump Python dependencies to 2.6, since upstream needs
  it now.

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
from PyQt4.QtCore import QString
15
15
from calibre.constants import terminal_controller, iswindows, isosx, \
16
16
                              __appname__, __version__, __author__, plugins
17
 
from calibre.utils.lock import LockError, ExclusiveFile 
 
17
from calibre.utils.lock import LockError, ExclusiveFile
18
18
from collections import defaultdict
19
19
 
20
20
if os.environ.has_key('CALIBRE_CONFIG_DIRECTORY'):
38
38
 
39
39
 
40
40
class CustomHelpFormatter(IndentedHelpFormatter):
41
 
    
 
41
 
42
42
    def format_usage(self, usage):
43
43
        return _("%sUsage%s: %s\n") % (terminal_controller.BLUE, terminal_controller.NORMAL, usage)
44
 
    
 
44
 
45
45
    def format_heading(self, heading):
46
 
        return "%*s%s%s%s:\n" % (self.current_indent, terminal_controller.BLUE, 
 
46
        return "%*s%s%s%s:\n" % (self.current_indent, terminal_controller.BLUE,
47
47
                                 "", heading, terminal_controller.NORMAL)
48
 
        
 
48
 
49
49
    def format_option(self, option):
50
50
        result = []
51
51
        opts = self.option_strings[option]
55
55
                                    terminal_controller.GREEN+opts+terminal_controller.NORMAL)
56
56
            indent_first = self.help_position
57
57
        else:                       # start help on same line as opts
58
 
            opts = "%*s%-*s  " % (self.current_indent, "", opt_width + len(terminal_controller.GREEN + terminal_controller.NORMAL), 
 
58
            opts = "%*s%-*s  " % (self.current_indent, "", opt_width + len(terminal_controller.GREEN + terminal_controller.NORMAL),
59
59
                                  terminal_controller.GREEN + opts + terminal_controller.NORMAL)
60
60
            indent_first = 0
61
61
        result.append(opts)
62
62
        if option.help:
63
63
            help_text = self.expand_default(option).split('\n')
64
64
            help_lines = []
65
 
            
 
65
 
66
66
            for line in help_text:
67
67
                help_lines.extend(textwrap.wrap(line, self.help_width))
68
68
            result.append("%*s%s\n" % (indent_first, "", help_lines[0]))
74
74
 
75
75
 
76
76
class OptionParser(_OptionParser):
77
 
    
 
77
 
78
78
    def __init__(self,
79
79
                 usage='%prog [options] filename',
80
80
                 version='%%prog (%s %s)'%(__appname__, __version__),
85
85
        usage = textwrap.dedent(usage)
86
86
        usage += '''\n\nWhenever you pass arguments to %prog that have spaces in them, '''\
87
87
                 '''enclose the arguments in quotation marks.'''
88
 
        _OptionParser.__init__(self, usage=usage, version=version, epilog=epilog, 
89
 
                               formatter=CustomHelpFormatter(), 
 
88
        _OptionParser.__init__(self, usage=usage, version=version, epilog=epilog,
 
89
                               formatter=CustomHelpFormatter(),
90
90
                               conflict_handler=conflict_handler, **kwds)
91
91
        self.gui_mode = gui_mode
92
 
        
 
92
 
93
93
    def error(self, msg):
94
94
        if self.gui_mode:
95
95
            raise Exception(msg)
96
96
        _OptionParser.error(self, msg)
97
 
        
 
97
 
98
98
    def merge(self, parser):
99
99
        '''
100
100
        Add options from parser to self. In case of conflicts, conflicting options from
102
102
        '''
103
103
        opts   = list(parser.option_list)
104
104
        groups = list(parser.option_groups)
105
 
        
 
105
 
106
106
        def merge_options(options, container):
107
107
            for opt in deepcopy(options):
108
108
                if not self.has_option(opt.get_opt_string()):
109
109
                    container.add_option(opt)
110
 
                
 
110
 
111
111
        merge_options(opts, self)
112
 
        
 
112
 
113
113
        for group in groups:
114
114
            g = self.add_option_group(group.title)
115
115
            merge_options(group.option_list, g)
116
 
        
 
116
 
117
117
    def subsume(self, group_name, msg=''):
118
118
        '''
119
119
        Move all existing options into a subgroup named
125
125
        for opt in opts:
126
126
            self.remove_option(opt.get_opt_string())
127
127
            subgroup.add_option(opt)
128
 
        
 
128
 
129
129
    def options_iter(self):
130
130
        for opt in self.option_list:
131
131
            if str(opt).strip():
134
134
            for opt in gr.option_list:
135
135
                if str(opt).strip():
136
136
                    yield opt
137
 
                
 
137
 
138
138
    def option_by_dest(self, dest):
139
139
        for opt in self.options_iter():
140
140
            if opt.dest == dest:
141
141
                return opt
142
 
    
 
142
 
143
143
    def merge_options(self, lower, upper):
144
144
        '''
145
145
        Merge options in lower and upper option lists into upper.
153
153
            if lower.__dict__[dest] != opt.default and \
154
154
               upper.__dict__[dest] == opt.default:
155
155
                upper.__dict__[dest] = lower.__dict__[dest]
156
 
        
 
156
 
157
157
 
158
158
 
159
159
class Option(object):
160
 
    
161
 
    def __init__(self, name, switches=[], help='', type=None, choices=None, 
 
160
 
 
161
    def __init__(self, name, switches=[], help='', type=None, choices=None,
162
162
                 check=None, group=None, default=None, action=None, metavar=None):
163
163
        if choices:
164
164
            type = 'choice'
165
 
        
 
165
 
166
166
        self.name     = name
167
167
        self.switches = switches
168
168
        self.help     = help.replace('%default', repr(default)) if help else None
172
172
                self.type = 'float'
173
173
            elif isinstance(default, int) and not isinstance(default, bool):
174
174
                self.type = 'int'
175
 
            
 
175
 
176
176
        self.choices  = choices
177
177
        self.check    = check
178
178
        self.group    = group
179
179
        self.default  = default
180
180
        self.action   = action
181
181
        self.metavar  = metavar
182
 
        
 
182
 
183
183
    def __eq__(self, other):
184
184
        return self.name == getattr(other, 'name', other)
185
 
    
 
185
 
186
186
    def __repr__(self):
187
187
        return 'Option: '+self.name
188
 
    
 
188
 
189
189
    def __str__(self):
190
190
        return repr(self)
191
 
        
 
191
 
192
192
class OptionValues(object):
193
 
    
 
193
 
194
194
    def copy(self):
195
195
        return deepcopy(self)
196
196
 
197
197
class OptionSet(object):
198
 
    
199
 
    OVERRIDE_PAT = re.compile(r'#{3,100} Override Options #{15}(.*?)#{3,100} End Override #{3,100}', 
 
198
 
 
199
    OVERRIDE_PAT = re.compile(r'#{3,100} Override Options #{15}(.*?)#{3,100} End Override #{3,100}',
200
200
                              re.DOTALL|re.IGNORECASE)
201
 
    
 
201
 
202
202
    def __init__(self, description=''):
203
203
        self.description = description
204
204
        self.preferences = []
205
205
        self.group_list  = []
206
206
        self.groups      = {}
207
207
        self.set_buffer  = {}
208
 
    
 
208
 
209
209
    def has_option(self, name_or_option_object):
210
210
        if name_or_option_object in self.preferences:
211
211
            return True
213
213
            if p.name == name_or_option_object:
214
214
                return True
215
215
        return False
216
 
    
 
216
 
217
217
    def add_group(self, name, description=''):
218
218
        if name in self.group_list:
219
219
            raise ValueError('A group by the name %s already exists in this set'%name)
220
220
        self.groups[name] = description
221
221
        self.group_list.append(name)
222
222
        return partial(self.add_opt, group=name)
223
 
    
 
223
 
224
224
    def update(self, other):
225
225
        for name in other.groups.keys():
226
226
            self.groups[name] = other.groups[name]
230
230
            if pref in self.preferences:
231
231
                self.preferences.remove(pref)
232
232
            self.preferences.append(pref)
233
 
            
 
233
 
234
234
    def smart_update(self, opts1, opts2):
235
235
        '''
236
236
        Updates the preference values in opts1 using only the non-default preference values in opts2.
239
239
            new = getattr(opts2, pref.name, pref.default)
240
240
            if new != pref.default:
241
241
                setattr(opts1, pref.name, new)
242
 
            
 
242
 
243
243
    def remove_opt(self, name):
244
244
        if name in self.preferences:
245
245
            self.preferences.remove(name)
246
 
        
247
 
        
248
 
    def add_opt(self, name, switches=[], help=None, type=None, choices=None, 
 
246
 
 
247
 
 
248
    def add_opt(self, name, switches=[], help=None, type=None, choices=None,
249
249
                 group=None, default=None, action=None, metavar=None):
250
250
        '''
251
251
        Add an option to this section.
252
 
        
 
252
 
253
253
        :param name:       The name of this option. Must be a valid Python identifier.
254
 
                           Must also be unique in this OptionSet and all its subsets. 
255
 
        :param switches:   List of command line switches for this option 
 
254
                           Must also be unique in this OptionSet and all its subsets.
 
255
        :param switches:   List of command line switches for this option
256
256
                           (as supplied to :module:`optparse`). If empty, this
257
257
                           option will not be added to the command line parser.
258
258
        :param help:       Help text.
259
259
        :param type:       Type checking of option values. Supported types are:
260
260
                           `None, 'choice', 'complex', 'float', 'int', 'string'`.
261
261
        :param choices:    List of strings or `None`.
262
 
        :param group:      Group this option belongs to. You must previously 
 
262
        :param group:      Group this option belongs to. You must previously
263
263
                           have created this group with a call to :method:`add_group`.
264
264
        :param default:    The default value for this option.
265
265
        :param action:     The action to pass to optparse. Supported values are:
266
266
                           `None, 'count'`. For choices and boolean options,
267
267
                           action is automatically set correctly.
268
268
        '''
269
 
        pref = Option(name, switches=switches, help=help, type=type, choices=choices, 
 
269
        pref = Option(name, switches=switches, help=help, type=type, choices=choices,
270
270
                 group=group, default=default, action=action, metavar=None)
271
271
        if group is not None and group not in self.groups.keys():
272
272
            raise ValueError('Group %s has not been added to this section'%group)
273
273
        if pref in self.preferences:
274
274
            raise ValueError('An option with the name %s already exists in this set.'%name)
275
275
        self.preferences.append(pref)
276
 
        
 
276
 
277
277
    def option_parser(self, user_defaults=None, usage='', gui_mode=False):
278
278
        parser = OptionParser(usage, gui_mode=gui_mode)
279
279
        groups = defaultdict(lambda : parser)
280
280
        for group, desc in self.groups.items():
281
281
            groups[group] = parser.add_option_group(group.upper(), desc)
282
 
        
 
282
 
283
283
        for pref in self.preferences:
284
284
            if not pref.switches:
285
285
                continue
299
299
                        action=action,
300
300
                        )
301
301
            g.add_option(*pref.switches, **args)
302
 
            
303
 
            
 
302
 
 
303
 
304
304
        return parser
305
 
    
 
305
 
306
306
    def get_override_section(self, src):
307
307
        match = self.OVERRIDE_PAT.search(src)
308
308
        if match:
309
309
            return match.group()
310
310
        return ''
311
 
    
 
311
 
312
312
    def parse_string(self, src):
313
313
        options = {'cPickle':cPickle}
314
314
        if not isinstance(src, unicode):
327
327
            if callable(formatter):
328
328
                val = formatter(val)
329
329
            setattr(opts, pref.name, val)
330
 
            
 
330
 
331
331
        return opts
332
 
    
 
332
 
333
333
    def render_group(self, name, desc, opts):
334
334
        prefs = [pref for pref in self.preferences if pref.group == name]
335
335
        lines = ['### Begin group: %s'%(name if name else 'DEFAULT')]
340
340
            lines.append('# '+pref.name.replace('_', ' '))
341
341
            if pref.help:
342
342
                lines += map(lambda x: '# ' + x, pref.help.split('\n'))
343
 
            lines.append('%s = %s'%(pref.name, 
 
343
            lines.append('%s = %s'%(pref.name,
344
344
                            self.serialize_opt(getattr(opts, pref.name, pref.default))))
345
345
            lines.append(' ')
346
346
        return '\n'.join(lines)
347
 
        
 
347
 
348
348
    def serialize_opt(self, val):
349
349
        if val is val is True or val is False or val is None or \
350
350
           isinstance(val, (int, float, long, basestring)):
353
353
            return repr(unicode(val))
354
354
        pickle = cPickle.dumps(val, -1)
355
355
        return 'cPickle.loads(%s)'%repr(pickle)
356
 
    
 
356
 
357
357
    def serialize(self, opts):
358
358
        src = '# %s\n\n'%(self.description.replace('\n', '\n# '))
359
359
        groups = [self.render_group(name, self.groups.get(name, ''), opts) \
361
361
        return src + '\n\n'.join(groups)
362
362
 
363
363
class ConfigInterface(object):
364
 
    
 
364
 
365
365
    def __init__(self, description):
366
366
        self.option_set       = OptionSet(description=description)
367
367
        self.add_opt          = self.option_set.add_opt
368
368
        self.add_group        = self.option_set.add_group
369
369
        self.remove_opt       = self.remove = self.option_set.remove_opt
370
370
        self.parse_string     = self.option_set.parse_string
371
 
        
 
371
 
372
372
    def update(self, other):
373
373
        self.option_set.update(other.option_set)
374
 
        
 
374
 
375
375
    def option_parser(self, usage='', gui_mode=False):
376
 
        return self.option_set.option_parser(user_defaults=self.parse(), 
 
376
        return self.option_set.option_parser(user_defaults=self.parse(),
377
377
                                             usage=usage, gui_mode=gui_mode)
378
 
    
 
378
 
379
379
    def smart_update(self, opts1, opts2):
380
380
        self.option_set.smart_update(opts1, opts2)
381
 
    
 
381
 
382
382
class Config(ConfigInterface):
383
383
    '''
384
384
    A file based configuration.
385
385
    '''
386
 
    
 
386
 
387
387
    def __init__(self, basename, description=''):
388
388
        ConfigInterface.__init__(self, description)
389
389
        self.config_file_path = os.path.join(config_dir, basename+'.py')
390
 
                
391
 
        
 
390
 
 
391
 
392
392
    def parse(self):
393
393
        src = ''
394
394
        if os.path.exists(self.config_file_path):
398
398
            except LockError:
399
399
                raise IOError('Could not lock config file: %s'%self.config_file_path)
400
400
        return self.option_set.parse_string(src)
401
 
    
 
401
 
402
402
    def as_string(self):
403
403
        if not os.path.exists(self.config_file_path):
404
404
            return ''
407
407
                return f.read().decode('utf-8')
408
408
        except LockError:
409
409
            raise IOError('Could not lock config file: %s'%self.config_file_path)
410
 
    
 
410
 
411
411
    def set(self, name, val):
412
412
        if not self.option_set.has_option(name):
413
413
            raise ValueError('The option %s is not defined.'%name)
427
427
                f.write(src)
428
428
        except LockError:
429
429
            raise IOError('Could not lock config file: %s'%self.config_file_path)
430
 
            
 
430
 
431
431
class StringConfig(ConfigInterface):
432
432
    '''
433
433
    A string based configuration
434
434
    '''
435
 
    
 
435
 
436
436
    def __init__(self, src, description=''):
437
437
        ConfigInterface.__init__(self, description)
438
438
        self.src = src
439
 
        
 
439
 
440
440
    def parse(self):
441
441
        return self.option_set.parse_string(self.src)
442
 
    
 
442
 
443
443
    def set(self, name, val):
444
444
        if not self.option_set.has_option(name):
445
445
            raise ValueError('The option %s is not defined.'%name)
452
452
    '''
453
453
    A Proxy to minimize file reads for widely used config settings
454
454
    '''
455
 
    
 
455
 
456
456
    def __init__(self, config):
457
457
        self.__config = config
458
 
        self.__opts   = None 
459
 
        
 
458
        self.__opts   = None
 
459
 
460
460
    def refresh(self):
461
461
        self.__opts = self.__config.parse()
462
 
    
 
462
 
463
463
    def __getitem__(self, key):
464
464
        return self.get(key)
465
 
    
 
465
 
466
466
    def __setitem__(self, key, val):
467
467
        return self.set(key, val)
468
 
    
 
468
 
469
469
    def get(self, key):
470
470
        if self.__opts is None:
471
471
            self.refresh()
472
472
        return getattr(self.__opts, key)
473
 
        
 
473
 
474
474
    def set(self, key, val):
475
475
        if self.__opts is None:
476
476
            self.refresh()
477
477
        setattr(self.__opts, key, val)
478
 
        return self.__config.set(key, val) 
 
478
        return self.__config.set(key, val)
479
479
 
480
480
class DynamicConfig(dict):
481
481
    '''
489
489
        self.name = name
490
490
        self.file_path = os.path.join(config_dir, name+'.pickle')
491
491
        self.refresh()
492
 
        
 
492
 
493
493
    def refresh(self):
494
494
        d = {}
495
495
        if os.path.exists(self.file_path):
497
497
                raw = f.read()
498
498
                try:
499
499
                    d = cPickle.loads(raw) if raw.strip() else {}
 
500
                except SystemError:
 
501
                    pass
500
502
                except:
501
503
                    import traceback
502
504
                    traceback.print_exc()
503
505
                    d = {}
504
506
        self.clear()
505
507
        self.update(d)
506
 
        
 
508
 
507
509
    def __getitem__(self, key):
508
510
        try:
509
511
            return dict.__getitem__(self, key)
510
512
        except KeyError:
511
513
            return None
512
 
        
 
514
 
513
515
    def __setitem__(self, key, val):
514
516
        dict.__setitem__(self, key, val)
515
517
        self.commit()
516
 
        
 
518
 
517
519
    def set(self, key, val):
518
520
        self.__setitem__(key, val)
519
 
    
 
521
 
520
522
    def commit(self):
521
523
        if hasattr(self, 'file_path') and self.file_path:
522
524
            if not os.path.exists(self.file_path):
526
528
                f.seek(0)
527
529
                f.truncate()
528
530
                f.write(raw)
529
 
            
530
 
dynamic = DynamicConfig()    
 
531
 
 
532
dynamic = DynamicConfig()
531
533
 
532
534
def _prefs():
533
535
    c = Config('global', 'calibre wide preferences')
534
 
    c.add_opt('database_path', 
 
536
    c.add_opt('database_path',
535
537
              default=os.path.expanduser('~/library1.db'),
536
538
              help=_('Path to the database in which books are stored'))
537
539
    c.add_opt('filename_pattern', default=ur'(?P<title>.+) - (?P<author>[^_]+)',
538
540
              help=_('Pattern to guess metadata from filenames'))
539
 
    c.add_opt('isbndb_com_key', default='', 
 
541
    c.add_opt('isbndb_com_key', default='',
540
542
              help=_('Access key for isbndb.com'))
541
543
    c.add_opt('network_timeout', default=5,
542
544
              help=_('Default timeout for network operations (seconds)'))
544
546
              help=_('Path to directory in which your library of books is stored'))
545
547
    c.add_opt('language', default=None,
546
548
              help=_('The language in which to display the user interface'))
547
 
    c.add_opt('output_format', default='EPUB', 
 
549
    c.add_opt('output_format', default='EPUB',
548
550
              help=_('The default output format for ebook conversions.'))
 
551
    c.add_opt('input_format_order', default=['EPUB', 'MOBI', 'LIT', 'PRC',
 
552
        'FB2', 'HTML', 'HTM', 'XHTM', 'SHTML', 'XHTML', 'ODT', 'RTF', 'PDF',
 
553
        'TXT'],
 
554
              help=_('Ordered list of formats to prefer for input.'))
549
555
    c.add_opt('read_file_metadata', default=True,
550
556
              help=_('Read metadata from files'))
551
 
    c.add_opt('worker_process_priority', default='normal', 
 
557
    c.add_opt('worker_process_priority', default='normal',
552
558
              help=_('The priority of worker processes'))
553
 
    
 
559
 
554
560
    c.add_opt('migrated', default=False, help='For Internal use. Don\'t modify.')
555
561
    return c
556
562
 
565
571
 
566
572
    from PyQt4.QtCore import QSettings, QVariant
567
573
    class Settings(QSettings):
568
 
    
 
574
 
569
575
        def __init__(self, name='calibre2'):
570
576
            QSettings.__init__(self, QSettings.IniFormat, QSettings.UserScope,
571
577
                               'kovidgoyal.net', name)
572
 
            
 
578
 
573
579
        def get(self, key, default=None):
574
580
            try:
575
581
                key = str(key)
581
587
                return cPickle.loads(val)
582
588
            except:
583
589
                return default
584
 
        
 
590
 
585
591
    s, migrated = Settings(), set([])
586
592
    all_keys = set(map(unicode, s.allKeys()))
587
593
    from calibre.gui2 import config, dynamic
599
605
            pass
600
606
        finally:
601
607
            migrated.add(key)
602
 
        
603
 
    
 
608
 
 
609
 
604
610
    _migrate('database path',    p=prefs)
605
611
    _migrate('filename pattern', p=prefs)
606
612
    _migrate('network timeout', p=prefs)
607
613
    _migrate('isbndb.com key',   p=prefs)
608
 
    
 
614
 
609
615
    _migrate('frequently used directories')
610
616
    _migrate('send to device by default')
611
617
    _migrate('save to disk single format')
616
622
    _migrate('cover flow queue length')
617
623
    _migrate('LRF conversion defaults')
618
624
    _migrate('LRF ebook viewer options')
619
 
    
 
625
 
620
626
    for key in all_keys - migrated:
621
627
        if key.endswith(': un') or key.endswith(': pw'):
622
628
            _migrate(key, p=dynamic)
623
629
    p.set('migrated', True)
624
 
        
625
 
    
 
630
 
 
631
 
626
632
if __name__ == '__main__':
627
633
    import subprocess
628
634
    from PyQt4.Qt import QByteArray
629
635
    c = Config('test', 'test config')
630
 
    
 
636
 
631
637
    c.add_opt('one', ['-1', '--one'], help="This is option #1")
632
638
    c.set('one', u'345')
633
 
    
 
639
 
634
640
    c.add_opt('two', help="This is option #2")
635
641
    c.set('two', 345)
636
 
    
 
642
 
637
643
    c.add_opt('three', help="This is option #3")
638
644
    c.set('three', QString(u'aflatoon'))
639
 
    
 
645
 
640
646
    c.add_opt('four', help="This is option #4")
641
647
    c.set('four', QByteArray('binary aflatoon'))
642
 
    
 
648
 
643
649
    subprocess.call(['pygmentize', os.path.expanduser('~/.config/calibre/test.py')])
644
 
    
 
650
 
645
651
    opts = c.parse()
646
652
    for i in ('one', 'two', 'three', 'four'):
647
653
        print i, repr(getattr(opts, i))
648
 
        
 
654