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
20
20
if os.environ.has_key('CALIBRE_CONFIG_DIRECTORY'):
40
40
class CustomHelpFormatter(IndentedHelpFormatter):
42
42
def format_usage(self, usage):
43
43
return _("%sUsage%s: %s\n") % (terminal_controller.BLUE, terminal_controller.NORMAL, usage)
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)
49
49
def format_option(self, option):
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)
61
61
result.append(opts)
63
63
help_text = self.expand_default(option).split('\n')
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]))
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
93
93
def error(self, msg):
95
95
raise Exception(msg)
96
96
_OptionParser.error(self, msg)
98
98
def merge(self, parser):
100
100
Add options from parser to self. In case of conflicts, conflicting options from
103
103
opts = list(parser.option_list)
104
104
groups = list(parser.option_groups)
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)
111
111
merge_options(opts, self)
113
113
for group in groups:
114
114
g = self.add_option_group(group.title)
115
115
merge_options(group.option_list, g)
117
117
def subsume(self, group_name, msg=''):
119
119
Move all existing options into a subgroup named
153
153
if lower.__dict__[dest] != opt.default and \
154
154
upper.__dict__[dest] == opt.default:
155
155
upper.__dict__[dest] = lower.__dict__[dest]
159
159
class Option(object):
161
def __init__(self, name, switches=[], help='', type=None, choices=None,
161
def __init__(self, name, switches=[], help='', type=None, choices=None,
162
162
check=None, group=None, default=None, action=None, metavar=None):
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'
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
183
183
def __eq__(self, other):
184
184
return self.name == getattr(other, 'name', other)
186
186
def __repr__(self):
187
187
return 'Option: '+self.name
189
189
def __str__(self):
190
190
return repr(self)
192
192
class OptionValues(object):
195
195
return deepcopy(self)
197
197
class OptionSet(object):
199
OVERRIDE_PAT = re.compile(r'#{3,100} Override Options #{15}(.*?)#{3,100} End Override #{3,100}',
199
OVERRIDE_PAT = re.compile(r'#{3,100} Override Options #{15}(.*?)#{3,100} End Override #{3,100}',
200
200
re.DOTALL|re.IGNORECASE)
202
202
def __init__(self, description=''):
203
203
self.description = description
204
204
self.preferences = []
205
205
self.group_list = []
207
207
self.set_buffer = {}
209
209
def has_option(self, name_or_option_object):
210
210
if name_or_option_object in self.preferences:
213
213
if p.name == name_or_option_object:
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)
224
224
def update(self, other):
225
225
for name in other.groups.keys():
226
226
self.groups[name] = other.groups[name]
239
239
new = getattr(opts2, pref.name, pref.default)
240
240
if new != pref.default:
241
241
setattr(opts1, pref.name, new)
243
243
def remove_opt(self, name):
244
244
if name in self.preferences:
245
245
self.preferences.remove(name)
248
def add_opt(self, name, switches=[], help=None, type=None, choices=None,
248
def add_opt(self, name, switches=[], help=None, type=None, choices=None,
249
249
group=None, default=None, action=None, metavar=None):
251
251
Add an option to this section.
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.
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)
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)
283
283
for pref in self.preferences:
284
284
if not pref.switches:
340
340
lines.append('# '+pref.name.replace('_', ' '))
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)
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)):
361
361
return src + '\n\n'.join(groups)
363
363
class ConfigInterface(object):
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
372
372
def update(self, other):
373
373
self.option_set.update(other.option_set)
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)
379
379
def smart_update(self, opts1, opts2):
380
380
self.option_set.smart_update(opts1, opts2)
382
382
class Config(ConfigInterface):
384
384
A file based configuration.
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')
394
394
if os.path.exists(self.config_file_path):
428
428
except LockError:
429
429
raise IOError('Could not lock config file: %s'%self.config_file_path)
431
431
class StringConfig(ConfigInterface):
433
433
A string based configuration
436
436
def __init__(self, src, description=''):
437
437
ConfigInterface.__init__(self, description)
441
441
return self.option_set.parse_string(self.src)
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)
453
453
A Proxy to minimize file reads for widely used config settings
456
456
def __init__(self, config):
457
457
self.__config = config
460
460
def refresh(self):
461
461
self.__opts = self.__config.parse()
463
463
def __getitem__(self, key):
464
464
return self.get(key)
466
466
def __setitem__(self, key, val):
467
467
return self.set(key, val)
469
469
def get(self, key):
470
470
if self.__opts is None:
472
472
return getattr(self.__opts, key)
474
474
def set(self, key, val):
475
475
if self.__opts is None:
477
477
setattr(self.__opts, key, val)
478
return self.__config.set(key, val)
478
return self.__config.set(key, val)
480
480
class DynamicConfig(dict):
499
499
d = cPickle.loads(raw) if raw.strip() else {}
502
504
traceback.print_exc()
507
509
def __getitem__(self, key):
509
511
return dict.__getitem__(self, key)
513
515
def __setitem__(self, key, val):
514
516
dict.__setitem__(self, key, val)
517
519
def set(self, key, val):
518
520
self.__setitem__(key, val)
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):
530
dynamic = DynamicConfig()
532
dynamic = DynamicConfig()
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',
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'))
554
560
c.add_opt('migrated', default=False, help='For Internal use. Don\'t modify.')
616
622
_migrate('cover flow queue length')
617
623
_migrate('LRF conversion defaults')
618
624
_migrate('LRF ebook viewer options')
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)
626
632
if __name__ == '__main__':
627
633
import subprocess
628
634
from PyQt4.Qt import QByteArray
629
635
c = Config('test', 'test config')
631
637
c.add_opt('one', ['-1', '--one'], help="This is option #1")
632
638
c.set('one', u'345')
634
640
c.add_opt('two', help="This is option #2")
635
641
c.set('two', 345)
637
643
c.add_opt('three', help="This is option #3")
638
644
c.set('three', QString(u'aflatoon'))
640
646
c.add_opt('four', help="This is option #4")
641
647
c.set('four', QByteArray('binary aflatoon'))
643
649
subprocess.call(['pygmentize', os.path.expanduser('~/.config/calibre/test.py')])
646
652
for i in ('one', 'two', 'three', 'four'):
647
653
print i, repr(getattr(opts, i))