From be10ae02fda678a1ec0e2da16ae001d2b667996e Mon Sep 17 00:00:00 2001
From: Li Fanxi
Date: Sat, 28 May 2011 02:09:41 +0800
Subject: [PATCH 1/8] [Bug] Missing API_KEY when getting book details.
---
src/calibre/ebooks/metadata/sources/douban.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/calibre/ebooks/metadata/sources/douban.py b/src/calibre/ebooks/metadata/sources/douban.py
index 70bf01a00e..8f8f5b80c4 100644
--- a/src/calibre/ebooks/metadata/sources/douban.py
+++ b/src/calibre/ebooks/metadata/sources/douban.py
@@ -46,6 +46,8 @@ cover_url = XPath("descendant::atom:link[@rel='image']/attribute::href")
def get_details(browser, url, timeout): # {{{
try:
+ if Douban.DOUBAN_API_KEY and Douban.DOUBAN_API_KEY != '':
+ url = url + "?apikey=" + Douban.DOUBAN_API_KEY
raw = browser.open_novisit(url, timeout=timeout).read()
except Exception as e:
gc = getattr(e, 'getcode', lambda : -1)
From 32bcac2147ffa344fb58bcc97859b9452ee99ff8 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Mon, 30 May 2011 22:49:13 -0600
Subject: [PATCH 2/8] When deleting all formats except ..., do not delete if it
leaves a book with no formats
---
src/calibre/gui2/actions/delete.py | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/src/calibre/gui2/actions/delete.py b/src/calibre/gui2/actions/delete.py
index 718f0737b3..619a8a1031 100644
--- a/src/calibre/gui2/actions/delete.py
+++ b/src/calibre/gui2/actions/delete.py
@@ -161,9 +161,12 @@ class DeleteAction(InterfaceAction):
continue
bfmts = set([x.lower() for x in bfmts.split(',')])
rfmts = bfmts - set(fmts)
- for fmt in rfmts:
- self.gui.library_view.model().db.remove_format(id, fmt,
- index_is_id=True, notify=False)
+ if bfmts - rfmts:
+ # Do not delete if it will leave the book with no
+ # formats
+ for fmt in rfmts:
+ self.gui.library_view.model().db.remove_format(id, fmt,
+ index_is_id=True, notify=False)
self.gui.library_view.model().refresh_ids(ids)
self.gui.library_view.model().current_changed(self.gui.library_view.currentIndex(),
self.gui.library_view.currentIndex())
From 775c63bd39497eb7c5933b9ddaf91758e4ce20e8 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Mon, 30 May 2011 22:53:15 -0600
Subject: [PATCH 3/8] ...
---
src/calibre/gui2/actions/delete.py | 3 ++-
src/calibre/gui2/dialogs/select_formats.py | 2 +-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/calibre/gui2/actions/delete.py b/src/calibre/gui2/actions/delete.py
index 619a8a1031..43465512e0 100644
--- a/src/calibre/gui2/actions/delete.py
+++ b/src/calibre/gui2/actions/delete.py
@@ -152,7 +152,8 @@ class DeleteAction(InterfaceAction):
if not ids:
return
fmts = self._get_selected_formats(
- ''+_('Choose formats not to be deleted'), ids)
+ '
'+_('Choose formats not to be deleted.
Note that '
+ 'this will never remove all formats from a book.'), ids)
if fmts is None:
return
for id in ids:
diff --git a/src/calibre/gui2/dialogs/select_formats.py b/src/calibre/gui2/dialogs/select_formats.py
index 5934c8c0f9..aea56ad196 100644
--- a/src/calibre/gui2/dialogs/select_formats.py
+++ b/src/calibre/gui2/dialogs/select_formats.py
@@ -44,7 +44,7 @@ class SelectFormats(QDialog):
self.setLayout(self._l)
self.setWindowTitle(_('Choose formats'))
self._m = QLabel(msg)
- self._m.setWordWrap = True
+ self._m.setWordWrap(True)
self._l.addWidget(self._m)
self.formats = Formats(fmt_list)
self.fview = QListView(self)
From cdeb8ade3d36fc14bf2f84fbd774c617b4e277ae Mon Sep 17 00:00:00 2001
From: Charles Haley <>
Date: Tue, 31 May 2011 12:13:08 +0100
Subject: [PATCH 4/8] Yet another improvement on the color wizard. Make 'None'
compare to zero in the formatter function 'cmp'.
---
.../gui2/dialogs/template_line_editor.py | 327 ++++++++++--------
src/calibre/utils/formatter_functions.py | 4 +-
2 files changed, 187 insertions(+), 144 deletions(-)
diff --git a/src/calibre/gui2/dialogs/template_line_editor.py b/src/calibre/gui2/dialogs/template_line_editor.py
index b24328f0ad..6a0b07200e 100644
--- a/src/calibre/gui2/dialogs/template_line_editor.py
+++ b/src/calibre/gui2/dialogs/template_line_editor.py
@@ -46,8 +46,8 @@ class TemplateLineEditor(QLineEdit):
menu.exec_(event.globalPos())
def clear_field(self):
- self.setText('')
self.txt = None
+ self.setText('')
self.setReadOnly(False)
self.setStyleSheet('TemplateLineEditor { color: black }')
@@ -95,6 +95,39 @@ class TemplateLineEditor(QLineEdit):
class TagWizard(QDialog):
+ text_template = " strcmp(field('{f}'), '{v}', '{fv}', '{tv}', '{fv}')"
+ text_empty_template = " test(field('{f}'), '{fv}', '{tv}')"
+ text_re_template = " contains(field('{f}'), '{v}', '{tv}', '{fv}')"
+
+ templates = {
+ 'text.mult' : " str_in_list(field('{f}'), '{mult}', '{v}', '{tv}', '{fv}')",
+ 'text.mult.re' : " in_list(field('{f}'), '{mult}', '^{v}$', '{tv}', '{fv}')",
+ 'text.mult.empty' : " test(field('{f}'), '{fv}', '{tv}')",
+ 'text' : text_template,
+ 'text.re' : text_re_template,
+ 'text.empty' : text_empty_template,
+ 'rating' : " cmp(field('{f}'), '{v}', '{fv}', '{tv}', '{fv}')",
+ 'rating.empty' : text_empty_template,
+ 'int' : " cmp(field('{f}'), '{v}', '{fv}', '{tv}', '{fv}')",
+ 'int.empty' : text_empty_template,
+ 'float' : " cmp(field('{f}'), '{v}', '{fv}', '{tv}', '{fv}')",
+ 'float.empty' : text_empty_template,
+ 'bool' : " strcmp(field('{f}'), '{v}', '{fv}', '{tv}', '{fv}')",
+ 'bool.empty' : text_empty_template,
+ 'series' : text_template,
+ 'series.re' : text_re_template,
+ 'series.empty' : text_empty_template,
+ 'composite' : text_template,
+ 'composite.re' : text_re_template,
+ 'composite.empty' : text_empty_template,
+ 'enumeration' : text_template,
+ 'enumeration.re' : text_re_template,
+ 'enumeration.empty' : text_empty_template,
+ 'comments' : text_template,
+ 'comments.re' : text_re_template,
+ 'comments.empty' : text_empty_template,
+ }
+
def __init__(self, parent, db, txt, mi):
QDialog.__init__(self, parent)
self.setWindowTitle(_('Coloring Wizard'))
@@ -106,11 +139,19 @@ class TagWizard(QDialog):
self.completion_values = defaultdict(dict)
for k in db.all_field_keys():
m = db.metadata_for_field(k)
- if m['datatype'] in ('text', 'enumeration', 'series') and \
- m['is_category'] and k not in ('identifiers'):
+ if k.endswith('_index') or (
+ m['kind'] == 'field' and m['name'] and
+ k not in ('ondevice', 'path', 'size', 'sort') and
+ m['datatype'] not in ('datetime')):
self.columns.append(k)
+ self.completion_values[k]['dt'] = m['datatype']
if m['is_custom']:
- self.completion_values[k]['v'] = db.all_custom(m['label'])
+ if m['datatype'] in ('int', 'float'):
+ self.completion_values[k]['v'] = []
+ elif m['datatype'] == 'bool':
+ self.completion_values[k]['v'] = [_('Yes'), _('No')]
+ else:
+ self.completion_values[k]['v'] = db.all_custom(m['label'])
elif k == 'tags':
self.completion_values[k]['v'] = db.all_tags()
elif k == 'formats':
@@ -127,12 +168,15 @@ class TagWizard(QDialog):
replace('|', ',') for v in f()]
else:
self.completion_values[k]['v'] = [v[1] for v in f()]
+ else:
+ self.completion_values[k]['v'] = []
if k in self.completion_values:
if k == 'authors':
- self.completion_values[k]['m'] = None
+ mult = '&'
else:
- self.completion_values[k]['m'] = m['is_multiple']
+ mult = ',' if m['is_multiple'] == '|' else m['is_multiple']
+ self.completion_values[k]['m'] = mult
self.columns.sort(key=sort_key)
self.columns.insert(0, '')
@@ -140,12 +184,12 @@ class TagWizard(QDialog):
l = QGridLayout()
self.setLayout(l)
l.setColumnStretch(2, 10)
- l.setColumnMinimumWidth(3, 300)
+ l.setColumnMinimumWidth(5, 300)
h = QLabel(_('And'))
h.setToolTip('
' +
_('Set this box to indicate that the two conditions must both '
- 'be true to return the "color if value found". For example, you '
+ 'be true to use the color. For example, you '
'can check if two tags are present, if the book has a tag '
'and a #read custom column is checked, or if a book has '
'some tag and has a particular format.'))
@@ -155,107 +199,106 @@ class TagWizard(QDialog):
h.setAlignment(Qt.AlignCenter)
l.addWidget(h, 0, 1, 1, 1)
- h = QLabel(_('Not'))
- h.setToolTip('
' +
- _('Set this box to indicate that the value must not match '
- 'to return the "color if value found". For example, you '
- 'can check if a tag does not exist by entering that tag '
- 'and checking this box. You can check if tags are empty by '
- 'checking this box, entering .* (period asterisk) for the text, '
- 'then checking the RE box. The .* regular expression matches '
- 'anything, so if this box is checked, it matches nothing. '
- 'This box is particularly useful when using the AND box.'))
+ h = QLabel(_('is'))
h.setAlignment(Qt.AlignCenter)
l.addWidget(h, 0, 2, 1, 1)
- h = QLabel(_('Values (see the popup help for more information)'))
+ h = QLabel(_('not'))
+ h.setToolTip('
' +
+ _('Check this box to indicate that the value must not match '
+ 'to use the color. For example, you can check if a tag does '
+ 'not exist by entering that tag and checking this box.') + '
')
+ h.setAlignment(Qt.AlignCenter)
+ l.addWidget(h, 0, 3, 1, 1)
+
+ c = QLabel(_('empty'))
+ c.setToolTip('' +
+ _('Check this box to check if the column is empty') + '
')
+ l.addWidget(c, 0, 4, 1, 1)
+
+ h = QLabel(_('Values'))
h.setAlignment(Qt.AlignCenter)
h.setToolTip('' +
_('You can enter more than one value per box, separated by commas. '
- 'The comparison ignores letter case. Special note: you can '
- 'enter at most one author.
'
+ 'The comparison ignores letter case. Special note: authors are '
+ 'separated by ampersands (&).
'
'A value can be a regular expression. Check the box to turn '
'them on. When using regular expressions, note that the wizard '
'puts anchors (^ and $) around the expression, so you '
'must ensure your expression matches from the beginning '
- 'to the end of the column you are checking.
'
+ 'to the end of the column/value you are checking.
'
'Regular expression examples:') + '
' +
- _('.*
matches anything in the column. No '
- 'empty values are checked, so you don\'t need to worry about '
- 'empty strings '
+ _('.*
matches anything in the column. '
'A.*
matches anything beginning with A '
'.*mystery.*
matches anything containing '
'the word "mystery" ') + '
')
- l.addWidget(h , 0, 3, 1, 1)
+ l.addWidget(h , 0, 5, 1, 1)
c = QLabel(_('is RE'))
c.setToolTip('' +
_('Check this box if the values box contains regular expressions') + '
')
- l.addWidget(c, 0, 4, 1, 1)
-
- c = QLabel(_('Color if value found'))
- c.setToolTip('' +
- _('At least one of the two color boxes must have a value. Leave '
- 'one color box empty if you want the template to use the next '
- 'line in this wizard. If both boxes are filled in, the rest of '
- 'the lines in this wizard will be ignored.') + '
')
- l.addWidget(c, 0, 5, 1, 1)
- c = QLabel(_('Color if value not found'))
- c.setToolTip('' +
- _('This box is usually filled in only on the last test. If it is '
- 'filled in before the last test, then the color for value found box '
- 'must be empty or all the rest of the tests will be ignored.') + '
')
l.addWidget(c, 0, 6, 1, 1)
- self.andboxes = []
- self.notboxes = []
- self.tagboxes = []
- self.colorboxes = []
- self.nfcolorboxes = []
- self.reboxes = []
- self.colboxes = []
+ c = QLabel(_('color'))
+ c.setAlignment(Qt.AlignCenter)
+ c.setToolTip('' +
+ _('Use this color if the column matches the tests.') + '
')
+ l.addWidget(c, 0, 7, 1, 1)
+
+ self.andboxes = []
+ self.notboxes = []
+ self.tagboxes = []
+ self.colorboxes = []
+ self.reboxes = []
+ self.colboxes = []
+ self.emptyboxes = []
+
self.colors = [unicode(s) for s in list(QColor.colorNames())]
self.colors.insert(0, '')
+ def create_widget(klass, box, layout, row, col, items,
+ align=Qt.AlignCenter, rowspan=False):
+ w = klass(self)
+ if box is not None:
+ box.append(w)
+ if rowspan:
+ layout.addWidget(w, row, col, 2, 1, alignment=Qt.Alignment(align))
+ else:
+ layout.addWidget(w, row, col, 1, 1, alignment=Qt.Alignment(align))
+ if items:
+ w.addItems(items)
+ return w
+
maxlines = 10
for i in range(1, maxlines+1):
- ab = QCheckBox(self)
- self.andboxes.append(ab)
- if i != maxlines:
- # let the last box float in space
- l.addWidget(ab, i, 0, 2, 1)
- ab.stateChanged.connect(partial(self.and_box_changed, line=i-1))
- else:
- ab.setVisible(False)
+ w = create_widget(QCheckBox, self.andboxes, l, i, 0, None, rowspan=True)
+ w.stateChanged.connect(partial(self.and_box_changed, line=i-1))
+ if i == maxlines:
+ # last box is invisible
+ w.setVisible(False)
- w = QComboBox(self)
- w.addItems(self.columns)
- l.addWidget(w, i, 1, 1, 1)
- self.colboxes.append(w)
+ w = create_widget(QComboBox, self.colboxes, l, i, 1, self.columns)
+ w.currentIndexChanged[str].connect(partial(self.column_changed, line=i-1))
- nb = QCheckBox(self)
- self.notboxes.append(nb)
- l.addWidget(nb, i, 2, 1, 1)
+ w = QLabel(self)
+ w.setText(_('is'))
+ l.addWidget(w, i, 2, 1, 1)
- tb = MultiCompleteLineEdit(self)
- tb.set_separator(', ')
- self.tagboxes.append(tb)
- l.addWidget(tb, i, 3, 1, 1)
- w.currentIndexChanged[str].connect(partial(self.column_changed, valbox=tb))
+ create_widget(QCheckBox, self.notboxes, l, i, 3, None)
- w = QCheckBox(self)
- self.reboxes.append(w)
- l.addWidget(w, i, 4, 1, 1)
+ w = create_widget(QCheckBox, self.emptyboxes, l, i, 4, None)
+ w.stateChanged.connect(partial(self.empty_box_changed, line=i-1))
- w = QComboBox(self)
- w.addItems(self.colors)
- self.colorboxes.append(w)
- l.addWidget(w, i, 5, 1, 1)
+ create_widget(MultiCompleteLineEdit, self.tagboxes, l, i, 5, None, align=0)
+ create_widget(QCheckBox, self.reboxes, l, i, 6, None)
+ create_widget(QComboBox, self.colorboxes, l, i, 7, self.colors)
- w = QComboBox(self)
- w.addItems(self.colors)
- self.nfcolorboxes.append(w)
- l.addWidget(w, i, 6, 1, 1)
+ w = create_widget(QLabel, None, l, maxlines+1, 5, None)
+ w.setText(_('If none of the tests match, set the color to'))
+ self.elsebox = create_widget(QComboBox, None, l, maxlines+1, 7, self.colors)
+ self.elsebox.setToolTip('' +
+ _('If this box contains a color, it will be used if none '
+ 'of the above rules match.') + '
')
if txt:
lines = txt.split('\n')[3:]
@@ -263,25 +306,27 @@ class TagWizard(QDialog):
for line in lines:
if line.startswith('#'):
vals = line[1:].split(':|:')
+ if len(vals) == 1 and line.startswith('#else:'):
+ try:
+ self.elsebox.setCurrentIndex(self.elsebox.findText(line[6:]))
+ except:
+ pass
+ continue
if len(vals) == 2:
t, c = vals
- nc = ''
- re = False
f = 'tags'
- a = False
- n = False
+ a = n = e = re = False
else:
- t,c,f,nc,re,a,n = vals
+ t,c,f,re,a,n,e = vals
try:
self.colboxes[i].setCurrentIndex(self.colboxes[i].findText(f))
self.colorboxes[i].setCurrentIndex(
self.colorboxes[i].findText(c))
- self.nfcolorboxes[i].setCurrentIndex(
- self.nfcolorboxes[i].findText(nc))
self.tagboxes[i].setText(t)
self.reboxes[i].setChecked(re == '2')
self.andboxes[i].setChecked(a == '2')
self.notboxes[i].setChecked(n == '2')
+ self.emptyboxes[i].setChecked(e == '2')
i += 1
except:
pass
@@ -290,13 +335,17 @@ class TagWizard(QDialog):
l.addWidget(w, 99, 1, 1, 1)
w = self.test_box = QLineEdit(self)
w.setReadOnly(True)
- l.addWidget(w, 99, 3, 1, 1)
+ l.addWidget(w, 99, 2, 1, 5)
w = QPushButton(_('Test'))
- l.addWidget(w, 99, 5, 1, 1)
+ w.setToolTip('' +
+ _('Press this button to see what color this template will '
+ 'produce for the book that was selected when you '
+ 'entered the preferences dialog.'))
+ l.addWidget(w, 99, 7, 1, 1)
w.clicked.connect(self.preview)
bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel, parent=self)
- l.addWidget(bb, 100, 3, 1, 2)
+ l.addWidget(bb, 100, 5, 1, 3)
bb.accepted.connect(self.accepted)
bb.rejected.connect(self.reject)
self.template = ''
@@ -308,14 +357,22 @@ class TagWizard(QDialog):
_('EXCEPTION'), self.mi)
self.test_box.setText(t)
- def column_changed(self, s, valbox=None):
+ def column_changed(self, s, line=None):
k = unicode(s)
if k in self.completion_values:
+ valbox = self.tagboxes[line]
valbox.update_items_cache(self.completion_values[k]['v'])
if self.completion_values[k]['m']:
valbox.set_separator(', ')
else:
valbox.set_separator(None)
+
+ dt = self.completion_values[k]['dt']
+ if dt in ('int', 'float', 'rating', 'bool'):
+ self.reboxes[line].setChecked(0)
+ self.reboxes[line].setEnabled(False)
+ else:
+ self.reboxes[line].setEnabled(True)
else:
valbox.update_items_cache([])
valbox.set_separator(None)
@@ -324,59 +381,44 @@ class TagWizard(QDialog):
res = ("program:\n#tag wizard -- do not directly edit\n"
" first_non_empty(\n")
lines = []
- was_and = False
- had_line = False
+ was_and = had_line = False
line = 0
- for tb, cb, fb, nfcb, reb, ab, nb in zip(
- self.tagboxes, self.colorboxes, self.colboxes,
- self.nfcolorboxes, self.reboxes, self.andboxes, self.notboxes):
+ for tb, cb, fb, reb, ab, nb, eb in zip(
+ self.tagboxes, self.colorboxes, self.colboxes,
+ self.reboxes, self.andboxes, self.notboxes, self.emptyboxes):
f = unicode(fb.currentText())
if not f:
continue
m = self.completion_values[f]['m']
+ dt = self.completion_values[f]['dt']
c = unicode(cb.currentText()).strip()
- nfc = unicode(nfcb.currentText()).strip()
re = reb.checkState()
a = ab.checkState()
n = nb.checkState()
+ e = eb.checkState()
line += 1
- if n == 2:
- tval = ''
- fval = '1'
- else:
- tval = '1'
- fval = ''
+ tval = '' if n == 2 else '1'
+ fval = '1' if n == 2 else ''
if m:
- tags = [t.strip() for t in unicode(tb.text()).split(',') if t.strip()]
+ tags = [t.strip() for t in unicode(tb.text()).split(m) if t.strip()]
if re == 2:
tags = '$|^'.join(tags)
else:
- tags = ','.join(tags)
+ tags = m.join(tags)
+ if m == '&':
+ tags = tags.replace(',', '|')
else:
tags = unicode(tb.text()).strip()
- if f == 'authors':
- tags.replace(',', '|')
- if (tags or f) and not (tags and f and (a == 2 or c)):
+ if (tags or f) and not ((tags or e) and f and (a == 2 or c)):
error_dialog(self, _('Invalid line'),
_('Line number {0} is not valid').format(line),
show=True, show_copy_button=False)
return False
- if c not in self.colors:
- error_dialog(self, _('Invalid color'),
- _('The color {0} is not valid').format(c),
- show=True, show_copy_button=False)
- return False
- if nfc not in self.colors:
- error_dialog(self, _('Invalid color'),
- _('The color {0} is not valid').format(nfc),
- show=True, show_copy_button=False)
- return False
-
if not was_and:
if had_line:
lines[-1] += ','
@@ -385,57 +427,58 @@ class TagWizard(QDialog):
else:
lines[-1] += ','
- if re == 2:
- if m:
- lines.append(" in_list(field('{1}'), ',', '^{0}$', '{2}', '{3}')".\
- format(tags, f, tval, fval))
- else:
- lines.append(" contains(field('{1}'), '{0}', '{2}', '{3}')".\
- format(tags, f, tval, fval))
- else:
- if m:
- lines.append(" str_in_list(field('{1}'), ',', '{0}', '{2}', '{3}')".\
- format(tags, f, tval, fval))
- else:
- lines.append(" strcmp(field('{1}'), '{0}', '{3}', '{2}', '{3}')".\
- format(tags, f, tval, fval))
+ key = dt + ('.mult' if m else '') + ('.empty' if e else '') + ('.re' if re else '')
+ template = self.templates[key]
+ lines.append(template.format(v=tags, f=f, tv=tval, fv=fval, mult=m))
+
if a == 2:
was_and = True
else:
was_and = False
- lines.append(" ), '{0}', '{1}')".format(c, nfc))
+ lines.append(" ), '{0}', '')".format(c))
res += '\n'.join(lines)
+ else_txt = unicode(self.elsebox.currentText())
+ if else_txt:
+ res += ",\n '" + else_txt + "'"
res += ')\n'
self.template = res
res = ''
- for tb, cb, fb, nfcb, reb, ab, nb in zip(
- self.tagboxes, self.colorboxes, self.colboxes,
- self.nfcolorboxes, self.reboxes, self.andboxes, self.notboxes):
+ for tb, cb, fb, reb, ab, nb, eb in zip(
+ self.tagboxes, self.colorboxes, self.colboxes,
+ self.reboxes, self.andboxes, self.notboxes, self.emptyboxes):
t = unicode(tb.text()).strip()
if t.endswith(','):
t = t[:-1]
c = unicode(cb.currentText()).strip()
f = unicode(fb.currentText())
- nfc = unicode(nfcb.currentText()).strip()
re = unicode(reb.checkState())
a = unicode(ab.checkState())
n = unicode(nb.checkState())
- if f and t and (a == '2' or c):
- res += '#' + t + ':|:' + c + ':|:' + f + ':|:' + \
- nfc + ':|:' + re + ':|:' + a + ':|:' + n + '\n'
+ e = unicode(eb.checkState())
+ if f and (t or e) and (a == '2' or c):
+ res += '#' + t + ':|:' + c + ':|:' + f + ':|:' + re + ':|:' + \
+ a + ':|:' + n + ':|:' + e + '\n'
+ res += '#else:' + else_txt + '\n'
self.template += res
return True
+ def empty_box_changed(self, state, line=None):
+ if state == 2:
+ self.tagboxes[line].setText('')
+ self.tagboxes[line].setEnabled(False)
+ self.reboxes[line].setChecked(0)
+ self.reboxes[line].setEnabled(False)
+ else:
+ self.reboxes[line].setEnabled(True)
+ self.tagboxes[line].setEnabled(True)
+
def and_box_changed(self, state, line=None):
if state == 2:
self.colorboxes[line].setCurrentIndex(0)
self.colorboxes[line].setEnabled(False)
- self.nfcolorboxes[line].setCurrentIndex(0)
- self.nfcolorboxes[line].setEnabled(False)
else:
self.colorboxes[line].setEnabled(True)
- self.nfcolorboxes[line].setEnabled(True)
def accepted(self):
if self.generate_program():
diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py
index b66aec2cb9..7b9a2ac51c 100644
--- a/src/calibre/utils/formatter_functions.py
+++ b/src/calibre/utils/formatter_functions.py
@@ -106,8 +106,8 @@ class BuiltinCmp(BuiltinFormatterFunction):
'numbers. Returns lt if x < y. Returns eq if x == y. Otherwise returns gt.')
def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):
- x = float(x if x else 0)
- y = float(y if y else 0)
+ x = float(x if x and x != 'None' else 0)
+ y = float(y if y and y != 'None' else 0)
if x < y:
return lt
if x == y:
From f4affb57e3479d5ba811442a4c0654591966e2a7 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 31 May 2011 09:12:40 -0600
Subject: [PATCH 5/8] Driver for Motorola Defy
---
src/calibre/devices/android/driver.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/calibre/devices/android/driver.py b/src/calibre/devices/android/driver.py
index 1cdf394c24..3c6ea243e2 100644
--- a/src/calibre/devices/android/driver.py
+++ b/src/calibre/devices/android/driver.py
@@ -109,7 +109,8 @@ class ANDROID(USBMS):
'SGH-T849', '_MB300', 'A70S', 'S_ANDROID', 'A101IT', 'A70H',
'IDEOS_TABLET', 'MYTOUCH_4G', 'UMS_COMPOSITE', 'SCH-I800_CARD',
'7', 'A956', 'A955', 'A43', 'ANDROID_PLATFORM', 'TEGRA_2',
- 'MB860', 'MULTI-CARD', 'MID7015A', 'INCREDIBLE', 'A7EB', 'STREAK']
+ 'MB860', 'MULTI-CARD', 'MID7015A', 'INCREDIBLE', 'A7EB', 'STREAK',
+ 'MB525']
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD',
'A70S', 'A101IT', '7', 'INCREDIBLE', 'A7EB', 'SGH-T849_CARD']
From 37ddce40741d4249efcc047ab28089cacee77ec3 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 31 May 2011 10:40:27 -0600
Subject: [PATCH 6/8] Improve documentation of template functions
---
src/calibre/manual/template_lang.rst | 37 +---
src/calibre/manual/template_ref.rst | 266 +++++++++++++++++++++++
src/calibre/utils/formatter_functions.py | 87 ++++----
3 files changed, 320 insertions(+), 70 deletions(-)
create mode 100644 src/calibre/manual/template_ref.rst
diff --git a/src/calibre/manual/template_lang.rst b/src/calibre/manual/template_lang.rst
index ef44b0a5c9..079af59286 100644
--- a/src/calibre/manual/template_lang.rst
+++ b/src/calibre/manual/template_lang.rst
@@ -268,20 +268,14 @@ The following functions are available in addition to those described in single-f
* ``subtract(x, y)`` -- returns x - y. Throws an exception if either x or y are not numbers.
* ``template(x)`` -- evaluates x as a template. The evaluation is done in its own context, meaning that variables are not shared between the caller and the template evaluation. Because the `{` and `}` characters are special, you must use `[[` for the `{` character and `]]` for the '}' character; they are converted automatically. For example, ``template('[[title_sort]]') will evaluate the template ``{title_sort}`` and return its value.
-Function classification summary:
+Function classification
+---------------------------
+
+.. toctree::
+ :maxdepth: 3
+
+ template_ref
- * Get values from metadata: ``field``. ``raw_field``. In some situations, ``lookup`` can be used in place of ``field``.
- * Arithmetic: ``add``, ``subtract``, ``multiply``, ``divide``
- * Boolean: ``and``, ``or``, ``not``. The function ``if_empty`` is similar to ``and`` called with one argument.
- * If-then-else: ``contains``, ``test``
- * Iterating over values: ``first_non_empty``, ``lookup``, ``switch``
- * List lookup: ``in_list``, ``list_item``, ``select``, ``str_in_list``
- * List manipulation: ``count``, ``merge_lists``, ``sublist``, ``subitems``
- * Recursion: ``eval``, ``template``
- * Relational: ``cmp`` (for numbers), ``strcmp`` (for strings)
- * String case changes: ``lowercase``, ``uppercase``, ``titlecase``, ``capitalize``
- * String manipulation: ``re``, ``shorten``, ``substr``
- * Other: ``assign``, ``booksize``, ``format_date``, ``ondevice`` ``print``
.. _general_mode:
@@ -425,20 +419,9 @@ You might find the following tips useful.
* Templates can use other templates by referencing a composite custom column.
* In a plugboard, you can set a field to empty (or whatever is equivalent to empty) by using the special template ``{null}``. This template will always evaluate to an empty string.
* The technique described above to show numbers even if they have a zero value works with the standard field series_index.
-
-API of the Metadata objects
-----------------------------
-.. module:: calibre.ebooks.metadata.book.base
+.. toctree::
+ :hidden:
-.. autoclass:: Metadata
- :members:
- :member-order: bysource
-
-.. data:: STANDARD_METADATA_FIELDS
-
- The set of standard metadata fields.
-
-.. literalinclude:: ../ebooks/metadata/book/__init__.py
- :lines: 7-
+ template_ref
diff --git a/src/calibre/manual/template_ref.rst b/src/calibre/manual/template_ref.rst
new file mode 100644
index 0000000000..670a7ba791
--- /dev/null
+++ b/src/calibre/manual/template_ref.rst
@@ -0,0 +1,266 @@
+.. include:: global.rst
+
+.. _templaterefcalibre:
+
+Reference for all builtin template language functions
+========================================================
+
+Here, we document all the builtin functions available in the |app| template language. Every function is implemented as a class in python and you can click the source links to see the source code, in case the documentation is insufficient. The functions are arranged in logical groups by type.
+
+.. contents::
+ :depth: 2
+ :local:
+
+.. module:: calibre.utils.formatter_functions
+
+Get values from metadata
+--------------------------
+
+field(name)
+^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinField
+
+raw_field(name)
+^^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinRaw_field
+
+booksize()
+^^^^^^^^^^^^
+
+.. autoclass:: BuiltinBooksize
+
+format_date(val, format_string)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinFormat_date
+
+ondevice()
+^^^^^^^^^^^
+
+.. autoclass:: BuiltinOndevice
+
+Arithmetic
+-------------
+
+add(x, y)
+^^^^^^^^^^^^^
+.. autoclass:: BuiltinAdd
+
+subtract(x, y)
+^^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinSubtract
+
+multiply(x, y)
+^^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinMultiply
+
+divide(x, y)
+^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinDivide
+
+Boolean
+------------
+
+and(value1, value2, ...)
+^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinAnd
+
+or(value1, value2, ...)
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinOr
+
+not(value)
+^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinNot
+
+If-then-else
+-----------------
+
+contains(val, pattern, text if match, text if not match)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinContains
+
+test(val, text if not empty, text if empty)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinTest
+
+ifempty(val, text if empty)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinIfempty
+
+Iterating over values
+------------------------
+
+first_non_empty(value, value, ...)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinFirstNonEmpty
+
+lookup(val, pattern, field, pattern, field, ..., else_field)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinLookup
+
+switch(val, pattern, value, pattern, value, ..., else_value)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinSwitch
+
+List Lookup
+---------------
+
+in_list(val, separator, pattern, found_val, not_found_val)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinInList
+
+str_in_list(val, separator, string, found_val, not_found_val)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinStrInList
+
+list_item(val, index, separator)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinListitem
+
+select(val, key)
+^^^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinSelect
+
+
+List Manipulation
+-------------------
+
+count(val, separator)
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinCount
+
+merge_lists(list1, list2, separator)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinMergeLists
+
+sublist(val, start_index, end_index, separator)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinSublist
+
+subitems(val, start_index, end_index)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinSubitems
+
+Recursion
+-------------
+
+eval(template)
+^^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinEval
+
+template(x)
+^^^^^^^^^^^^
+
+.. autoclass:: BuiltinTemplate
+
+Relational
+-----------
+
+cmp(x, y, lt, eq, gt)
+^^^^^^^^^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinCmp
+
+strcmp(x, y, lt, eq, gt)
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinStrcmp
+
+String case changes
+---------------------
+
+lowercase(val)
+^^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinLowercase
+
+uppercase(val)
+^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinUppercase
+
+titlecase(val)
+^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinTitlecase
+
+capitalize(val)
+^^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinCapitalize
+
+String Manipulation
+---------------------
+
+re(val, pattern, replacement)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinRe
+
+shorten(val, left chars, middle text, right chars)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinShorten
+
+substr(str, start, end)
+^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinSubstr
+
+
+Other
+--------
+
+assign(id, val)
+^^^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinAssign
+
+print(a, b, ...)
+^^^^^^^^^^^^^^^^^
+
+.. autoclass:: BuiltinPrint
+
+
+API of the Metadata objects
+----------------------------
+
+The python implementation of the template functions is passed in a Metadata object. Knowing it's API is useful if you want to define your own template functions.
+
+.. module:: calibre.ebooks.metadata.book.base
+
+.. autoclass:: Metadata
+ :members:
+ :member-order: bysource
+
+.. data:: STANDARD_METADATA_FIELDS
+
+ The set of standard metadata fields.
+
+.. literalinclude:: ../ebooks/metadata/book/__init__.py
+ :lines: 7-
+
diff --git a/src/calibre/utils/formatter_functions.py b/src/calibre/utils/formatter_functions.py
index 7b9a2ac51c..62764510e9 100644
--- a/src/calibre/utils/formatter_functions.py
+++ b/src/calibre/utils/formatter_functions.py
@@ -87,7 +87,7 @@ class BuiltinFormatterFunction(FormatterFunction):
class BuiltinStrcmp(BuiltinFormatterFunction):
name = 'strcmp'
arg_count = 5
- doc = _('strcmp(x, y, lt, eq, gt) -- does a case-insensitive comparison of x '
+ __doc__ = doc = _('strcmp(x, y, lt, eq, gt) -- does a case-insensitive comparison of x '
'and y as strings. Returns lt if x < y. Returns eq if x == y. '
'Otherwise returns gt.')
@@ -102,7 +102,7 @@ class BuiltinStrcmp(BuiltinFormatterFunction):
class BuiltinCmp(BuiltinFormatterFunction):
name = 'cmp'
arg_count = 5
- doc = _('cmp(x, y, lt, eq, gt) -- compares x and y after converting both to '
+ __doc__ = doc = _('cmp(x, y, lt, eq, gt) -- compares x and y after converting both to '
'numbers. Returns lt if x < y. Returns eq if x == y. Otherwise returns gt.')
def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):
@@ -117,7 +117,7 @@ class BuiltinCmp(BuiltinFormatterFunction):
class BuiltinStrcat(BuiltinFormatterFunction):
name = 'strcat'
arg_count = -1
- doc = _('strcat(a, b, ...) -- can take any number of arguments. Returns a '
+ __doc__ = doc = _('strcat(a, b, ...) -- can take any number of arguments. Returns a '
'string formed by concatenating all the arguments')
def evaluate(self, formatter, kwargs, mi, locals, *args):
@@ -130,7 +130,7 @@ class BuiltinStrcat(BuiltinFormatterFunction):
class BuiltinAdd(BuiltinFormatterFunction):
name = 'add'
arg_count = 2
- doc = _('add(x, y) -- returns x + y. Throws an exception if either x or y are not numbers.')
+ __doc__ = doc = _('add(x, y) -- returns x + y. Throws an exception if either x or y are not numbers.')
def evaluate(self, formatter, kwargs, mi, locals, x, y):
x = float(x if x else 0)
@@ -140,7 +140,7 @@ class BuiltinAdd(BuiltinFormatterFunction):
class BuiltinSubtract(BuiltinFormatterFunction):
name = 'subtract'
arg_count = 2
- doc = _('subtract(x, y) -- returns x - y. Throws an exception if either x or y are not numbers.')
+ __doc__ = doc = _('subtract(x, y) -- returns x - y. Throws an exception if either x or y are not numbers.')
def evaluate(self, formatter, kwargs, mi, locals, x, y):
x = float(x if x else 0)
@@ -150,7 +150,7 @@ class BuiltinSubtract(BuiltinFormatterFunction):
class BuiltinMultiply(BuiltinFormatterFunction):
name = 'multiply'
arg_count = 2
- doc = _('multiply(x, y) -- returns x * y. Throws an exception if either x or y are not numbers.')
+ __doc__ = doc = _('multiply(x, y) -- returns x * y. Throws an exception if either x or y are not numbers.')
def evaluate(self, formatter, kwargs, mi, locals, x, y):
x = float(x if x else 0)
@@ -160,7 +160,7 @@ class BuiltinMultiply(BuiltinFormatterFunction):
class BuiltinDivide(BuiltinFormatterFunction):
name = 'divide'
arg_count = 2
- doc = _('divide(x, y) -- returns x / y. Throws an exception if either x or y are not numbers.')
+ __doc__ = doc = _('divide(x, y) -- returns x / y. Throws an exception if either x or y are not numbers.')
def evaluate(self, formatter, kwargs, mi, locals, x, y):
x = float(x if x else 0)
@@ -170,7 +170,7 @@ class BuiltinDivide(BuiltinFormatterFunction):
class BuiltinTemplate(BuiltinFormatterFunction):
name = 'template'
arg_count = 1
- doc = _('template(x) -- evaluates x as a template. The evaluation is done '
+ __doc__ = doc = _('template(x) -- evaluates x as a template. The evaluation is done '
'in its own context, meaning that variables are not shared between '
'the caller and the template evaluation. Because the { and } '
'characters are special, you must use [[ for the { character and '
@@ -185,7 +185,7 @@ class BuiltinTemplate(BuiltinFormatterFunction):
class BuiltinEval(BuiltinFormatterFunction):
name = 'eval'
arg_count = 1
- doc = _('eval(template) -- evaluates the template, passing the local '
+ __doc__ = doc = _('eval(template) -- evaluates the template, passing the local '
'variables (those \'assign\'ed to) instead of the book metadata. '
' This permits using the template processor to construct complex '
'results from local variables.')
@@ -198,7 +198,7 @@ class BuiltinEval(BuiltinFormatterFunction):
class BuiltinAssign(BuiltinFormatterFunction):
name = 'assign'
arg_count = 2
- doc = _('assign(id, val) -- assigns val to id, then returns val. '
+ __doc__ = doc = _('assign(id, val) -- assigns val to id, then returns val. '
'id must be an identifier, not an expression')
def evaluate(self, formatter, kwargs, mi, locals, target, value):
@@ -208,7 +208,7 @@ class BuiltinAssign(BuiltinFormatterFunction):
class BuiltinPrint(BuiltinFormatterFunction):
name = 'print'
arg_count = -1
- doc = _('print(a, b, ...) -- prints the arguments to standard output. '
+ __doc__ = doc = _('print(a, b, ...) -- prints the arguments to standard output. '
'Unless you start calibre from the command line (calibre-debug -g), '
'the output will go to a black hole.')
@@ -219,7 +219,7 @@ class BuiltinPrint(BuiltinFormatterFunction):
class BuiltinField(BuiltinFormatterFunction):
name = 'field'
arg_count = 1
- doc = _('field(name) -- returns the metadata field named by name')
+ __doc__ = doc = _('field(name) -- returns the metadata field named by name')
def evaluate(self, formatter, kwargs, mi, locals, name):
return formatter.get_value(name, [], kwargs)
@@ -227,7 +227,7 @@ class BuiltinField(BuiltinFormatterFunction):
class BuiltinRaw_field(BuiltinFormatterFunction):
name = 'raw_field'
arg_count = 1
- doc = _('raw_field(name) -- returns the metadata field named by name '
+ __doc__ = doc = _('raw_field(name) -- returns the metadata field named by name '
'without applying any formatting.')
def evaluate(self, formatter, kwargs, mi, locals, name):
@@ -236,7 +236,7 @@ class BuiltinRaw_field(BuiltinFormatterFunction):
class BuiltinSubstr(BuiltinFormatterFunction):
name = 'substr'
arg_count = 3
- doc = _('substr(str, start, end) -- returns the start\'th through the end\'th '
+ __doc__ = doc = _('substr(str, start, end) -- returns the start\'th through the end\'th '
'characters of str. The first character in str is the zero\'th '
'character. If end is negative, then it indicates that many '
'characters counting from the right. If end is zero, then it '
@@ -249,7 +249,7 @@ class BuiltinSubstr(BuiltinFormatterFunction):
class BuiltinLookup(BuiltinFormatterFunction):
name = 'lookup'
arg_count = -1
- doc = _('lookup(val, pattern, field, pattern, field, ..., else_field) -- '
+ __doc__ = doc = _('lookup(val, pattern, field, pattern, field, ..., else_field) -- '
'like switch, except the arguments are field (metadata) names, not '
'text. The value of the appropriate field will be fetched and used. '
'Note that because composite columns are fields, you can use this '
@@ -276,7 +276,7 @@ class BuiltinLookup(BuiltinFormatterFunction):
class BuiltinTest(BuiltinFormatterFunction):
name = 'test'
arg_count = 3
- doc = _('test(val, text if not empty, text if empty) -- return `text if not '
+ __doc__ = doc = _('test(val, text if not empty, text if empty) -- return `text if not '
'empty` if the field is not empty, otherwise return `text if empty`')
def evaluate(self, formatter, kwargs, mi, locals, val, value_if_set, value_not_set):
@@ -288,7 +288,7 @@ class BuiltinTest(BuiltinFormatterFunction):
class BuiltinContains(BuiltinFormatterFunction):
name = 'contains'
arg_count = 4
- doc = _('contains(val, pattern, text if match, text if not match) -- checks '
+ __doc__ = doc = _('contains(val, pattern, text if match, text if not match) -- checks '
'if field contains matches for the regular expression `pattern`. '
'Returns `text if match` if matches are found, otherwise it returns '
'`text if no match`')
@@ -303,7 +303,7 @@ class BuiltinContains(BuiltinFormatterFunction):
class BuiltinSwitch(BuiltinFormatterFunction):
name = 'switch'
arg_count = -1
- doc = _('switch(val, pattern, value, pattern, value, ..., else_value) -- '
+ __doc__ = doc = _('switch(val, pattern, value, pattern, value, ..., else_value) -- '
'for each `pattern, value` pair, checks if the field matches '
'the regular expression `pattern` and if so, returns that '
'`value`. If no pattern matches, then else_value is returned. '
@@ -323,7 +323,7 @@ class BuiltinSwitch(BuiltinFormatterFunction):
class BuiltinInList(BuiltinFormatterFunction):
name = 'in_list'
arg_count = 5
- doc = _('in_list(val, separator, pattern, found_val, not_found_val) -- '
+ __doc__ = doc = _('in_list(val, separator, pattern, found_val, not_found_val) -- '
'treat val as a list of items separated by separator, '
'comparing the pattern against each value in the list. If the '
'pattern matches a value, return found_val, otherwise return '
@@ -340,7 +340,7 @@ class BuiltinInList(BuiltinFormatterFunction):
class BuiltinStrInList(BuiltinFormatterFunction):
name = 'str_in_list'
arg_count = 5
- doc = _('str_in_list(val, separator, string, found_val, not_found_val) -- '
+ __doc__ = doc = _('str_in_list(val, separator, string, found_val, not_found_val) -- '
'treat val as a list of items separated by separator, '
'comparing the string against each value in the list. If the '
'string matches a value, return found_val, otherwise return '
@@ -360,7 +360,7 @@ class BuiltinStrInList(BuiltinFormatterFunction):
class BuiltinRe(BuiltinFormatterFunction):
name = 're'
arg_count = 3
- doc = _('re(val, pattern, replacement) -- return the field after applying '
+ __doc__ = doc = _('re(val, pattern, replacement) -- return the field after applying '
'the regular expression. All instances of `pattern` are replaced '
'with `replacement`. As in all of calibre, these are '
'python-compatible regular expressions')
@@ -371,7 +371,7 @@ class BuiltinRe(BuiltinFormatterFunction):
class BuiltinIfempty(BuiltinFormatterFunction):
name = 'ifempty'
arg_count = 2
- doc = _('ifempty(val, text if empty) -- return val if val is not empty, '
+ __doc__ = doc = _('ifempty(val, text if empty) -- return val if val is not empty, '
'otherwise return `text if empty`')
def evaluate(self, formatter, kwargs, mi, locals, val, value_if_empty):
@@ -383,7 +383,7 @@ class BuiltinIfempty(BuiltinFormatterFunction):
class BuiltinShorten(BuiltinFormatterFunction):
name = 'shorten'
arg_count = 4
- doc = _('shorten(val, left chars, middle text, right chars) -- Return a '
+ __doc__ = doc = _('shorten(val, left chars, middle text, right chars) -- Return a '
'shortened version of the field, consisting of `left chars` '
'characters from the beginning of the field, followed by '
'`middle text`, followed by `right chars` characters from '
@@ -408,7 +408,7 @@ class BuiltinShorten(BuiltinFormatterFunction):
class BuiltinCount(BuiltinFormatterFunction):
name = 'count'
arg_count = 2
- doc = _('count(val, separator) -- interprets the value as a list of items '
+ __doc__ = doc = _('count(val, separator) -- interprets the value as a list of items '
'separated by `separator`, returning the number of items in the '
'list. Most lists use a comma as the separator, but authors '
'uses an ampersand. Examples: {tags:count(,)}, {authors:count(&)}')
@@ -419,7 +419,7 @@ class BuiltinCount(BuiltinFormatterFunction):
class BuiltinListitem(BuiltinFormatterFunction):
name = 'list_item'
arg_count = 3
- doc = _('list_item(val, index, separator) -- interpret the value as a list of '
+ __doc__ = doc = _('list_item(val, index, separator) -- interpret the value as a list of '
'items separated by `separator`, returning the `index`th item. '
'The first item is number zero. The last item can be returned '
'using `list_item(-1,separator)`. If the item is not in the list, '
@@ -439,7 +439,7 @@ class BuiltinListitem(BuiltinFormatterFunction):
class BuiltinSelect(BuiltinFormatterFunction):
name = 'select'
arg_count = 2
- doc = _('select(val, key) -- interpret the value as a comma-separated list '
+ __doc__ = doc = _('select(val, key) -- interpret the value as a comma-separated list '
'of items, with the items being "id:value". Find the pair with the'
'id equal to key, and return the corresponding value.'
)
@@ -456,9 +456,9 @@ class BuiltinSelect(BuiltinFormatterFunction):
class BuiltinSublist(BuiltinFormatterFunction):
name = 'sublist'
arg_count = 4
- doc = _('sublist(val, start_index, end_index, separator) -- interpret the '
+ __doc__ = doc = _('sublist(val, start_index, end_index, separator) -- interpret the '
'value as a list of items separated by `separator`, returning a '
- 'new list made from the `start_index`th to the `end_index`th item. '
+ 'new list made from the `start_index` to the `end_index` item. '
'The first item is number zero. If an index is negative, then it '
'counts from the end of the list. As a special case, an end_index '
'of zero is assumed to be the length of the list. Examples using '
@@ -466,7 +466,8 @@ class BuiltinSublist(BuiltinFormatterFunction):
'comma-separated) contains "A, B, C": '
'{tags:sublist(0,1,\,)} returns "A". '
'{tags:sublist(-1,0,\,)} returns "C". '
- '{tags:sublist(0,-1,\,)} returns "A, B".')
+ '{tags:sublist(0,-1,\,)} returns "A, B".'
+ )
def evaluate(self, formatter, kwargs, mi, locals, val, start_index, end_index, sep):
if not val:
@@ -485,12 +486,12 @@ class BuiltinSublist(BuiltinFormatterFunction):
class BuiltinSubitems(BuiltinFormatterFunction):
name = 'subitems'
arg_count = 3
- doc = _('subitems(val, start_index, end_index) -- This function is used to '
+ __doc__ = doc = _('subitems(val, start_index, end_index) -- This function is used to '
'break apart lists of items such as genres. It interprets the value '
'as a comma-separated list of items, where each item is a period-'
'separated list. Returns a new list made by first finding all the '
'period-separated items, then for each such item extracting the '
- 'start_index`th to the `end_index`th components, then combining '
+ 'start_index` to the `end_index` components, then combining '
'the results back together. The first component in a period-'
'separated list has an index of zero. If an index is negative, '
'then it counts from the end of the list. As a special case, an '
@@ -522,7 +523,7 @@ class BuiltinSubitems(BuiltinFormatterFunction):
class BuiltinFormat_date(BuiltinFormatterFunction):
name = 'format_date'
arg_count = 2
- doc = _('format_date(val, format_string) -- format the value, which must '
+ __doc__ = doc = _('format_date(val, format_string) -- format the value, which must '
'be a date field, using the format_string, returning a string. '
'The formatting codes are: '
'd : the day as number without a leading zero (1 to 31) '
@@ -550,7 +551,7 @@ class BuiltinFormat_date(BuiltinFormatterFunction):
class BuiltinUppercase(BuiltinFormatterFunction):
name = 'uppercase'
arg_count = 1
- doc = _('uppercase(val) -- return value of the field in upper case')
+ __doc__ = doc = _('uppercase(val) -- return value of the field in upper case')
def evaluate(self, formatter, kwargs, mi, locals, val):
return val.upper()
@@ -558,7 +559,7 @@ class BuiltinUppercase(BuiltinFormatterFunction):
class BuiltinLowercase(BuiltinFormatterFunction):
name = 'lowercase'
arg_count = 1
- doc = _('lowercase(val) -- return value of the field in lower case')
+ __doc__ = doc = _('lowercase(val) -- return value of the field in lower case')
def evaluate(self, formatter, kwargs, mi, locals, val):
return val.lower()
@@ -566,7 +567,7 @@ class BuiltinLowercase(BuiltinFormatterFunction):
class BuiltinTitlecase(BuiltinFormatterFunction):
name = 'titlecase'
arg_count = 1
- doc = _('titlecase(val) -- return value of the field in title case')
+ __doc__ = doc = _('titlecase(val) -- return value of the field in title case')
def evaluate(self, formatter, kwargs, mi, locals, val):
return titlecase(val)
@@ -574,7 +575,7 @@ class BuiltinTitlecase(BuiltinFormatterFunction):
class BuiltinCapitalize(BuiltinFormatterFunction):
name = 'capitalize'
arg_count = 1
- doc = _('capitalize(val) -- return value of the field capitalized')
+ __doc__ = doc = _('capitalize(val) -- return value of the field capitalized')
def evaluate(self, formatter, kwargs, mi, locals, val):
return capitalize(val)
@@ -582,7 +583,7 @@ class BuiltinCapitalize(BuiltinFormatterFunction):
class BuiltinBooksize(BuiltinFormatterFunction):
name = 'booksize'
arg_count = 0
- doc = _('booksize() -- return value of the size field')
+ __doc__ = doc = _('booksize() -- return value of the size field')
def evaluate(self, formatter, kwargs, mi, locals):
if mi.book_size is not None:
@@ -595,7 +596,7 @@ class BuiltinBooksize(BuiltinFormatterFunction):
class BuiltinOndevice(BuiltinFormatterFunction):
name = 'ondevice'
arg_count = 0
- doc = _('ondevice() -- return Yes if ondevice is set, otherwise return '
+ __doc__ = doc = _('ondevice() -- return Yes if ondevice is set, otherwise return '
'the empty string')
def evaluate(self, formatter, kwargs, mi, locals):
@@ -606,7 +607,7 @@ class BuiltinOndevice(BuiltinFormatterFunction):
class BuiltinFirstNonEmpty(BuiltinFormatterFunction):
name = 'first_non_empty'
arg_count = -1
- doc = _('first_non_empty(value, value, ...) -- '
+ __doc__ = doc = _('first_non_empty(value, value, ...) -- '
'returns the first value that is not empty. If all values are '
'empty, then the empty value is returned.'
'You can have as many values as you want.')
@@ -622,7 +623,7 @@ class BuiltinFirstNonEmpty(BuiltinFormatterFunction):
class BuiltinAnd(BuiltinFormatterFunction):
name = 'and'
arg_count = -1
- doc = _('and(value, value, ...) -- '
+ __doc__ = doc = _('and(value, value, ...) -- '
'returns the string "1" if all values are not empty, otherwise '
'returns the empty string. This function works well with test or '
'first_non_empty. You can have as many values as you want.')
@@ -638,7 +639,7 @@ class BuiltinAnd(BuiltinFormatterFunction):
class BuiltinOr(BuiltinFormatterFunction):
name = 'or'
arg_count = -1
- doc = _('or(value, value, ...) -- '
+ __doc__ = doc = _('or(value, value, ...) -- '
'returns the string "1" if any value is not empty, otherwise '
'returns the empty string. This function works well with test or '
'first_non_empty. You can have as many values as you want.')
@@ -654,7 +655,7 @@ class BuiltinOr(BuiltinFormatterFunction):
class BuiltinNot(BuiltinFormatterFunction):
name = 'not'
arg_count = 1
- doc = _('not(value) -- '
+ __doc__ = doc = _('not(value) -- '
'returns the string "1" if the value is empty, otherwise '
'returns the empty string. This function works well with test or '
'first_non_empty. You can have as many values as you want.')
@@ -670,7 +671,7 @@ class BuiltinNot(BuiltinFormatterFunction):
class BuiltinMergeLists(BuiltinFormatterFunction):
name = 'merge_lists'
arg_count = 3
- doc = _('merge_lists(list1, list2, separator) -- '
+ __doc__ = doc = _('merge_lists(list1, list2, separator) -- '
'return a list made by merging the items in list1 and list2, '
'removing duplicate items using a case-insensitive compare. If '
'items differ in case, the one in list1 is used. '
From 5c219da8d3bf393d9095975eb5592c10b58ff868 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 31 May 2011 11:28:37 -0600
Subject: [PATCH 7/8] EPUB Output: Fix crash caused by ids with non-ascii
characters in them
---
src/calibre/ebooks/oeb/transforms/split.py | 14 +++++++++++++-
1 file changed, 13 insertions(+), 1 deletion(-)
diff --git a/src/calibre/ebooks/oeb/transforms/split.py b/src/calibre/ebooks/oeb/transforms/split.py
index 69de740ddc..0c9b492855 100644
--- a/src/calibre/ebooks/oeb/transforms/split.py
+++ b/src/calibre/ebooks/oeb/transforms/split.py
@@ -120,7 +120,19 @@ class Split(object):
for i, x in enumerate(page_breaks):
x.set('id', x.get('id', 'calibre_pb_%d'%i))
id = x.get('id')
- page_breaks_.append((XPath('//*[@id=%r]'%id),
+ try:
+ xp = XPath('//*[@id="%s"]'%id)
+ except:
+ try:
+ xp = XPath("//*[@id='%s']"%id)
+ except:
+ # The id has both a quote and an apostrophe or some other
+ # Just replace it since I doubt its going to work anywhere else
+ # either
+ id = 'calibre_pb_%d'%i
+ x.set('id', id)
+ xp = XPath('//*[@id=%r]'%id)
+ page_breaks_.append((xp,
x.get('pb_before', False)))
page_break_ids.append(id)
From db0c24624001e7c2a417566e6cf7538483a9cf64 Mon Sep 17 00:00:00 2001
From: Kovid Goyal
Date: Tue, 31 May 2011 13:43:38 -0600
Subject: [PATCH 8/8] ...
---
src/calibre/ebooks/epub/fix/container.py | 20 ++++++++++++++++++--
1 file changed, 18 insertions(+), 2 deletions(-)
diff --git a/src/calibre/ebooks/epub/fix/container.py b/src/calibre/ebooks/epub/fix/container.py
index 1669290a7b..691bf7132a 100644
--- a/src/calibre/ebooks/epub/fix/container.py
+++ b/src/calibre/ebooks/epub/fix/container.py
@@ -101,7 +101,7 @@ class Container(object):
return None
return existing[0]
- def add_name_to_manifest(self, name):
+ def add_name_to_manifest(self, name, mt=None):
item = self.manifest_item_for_name(name)
if item is not None:
return
@@ -109,11 +109,27 @@ class Container(object):
item = manifest.makeelement('{%s}item'%OPF_NS, nsmap={'opf':OPF_NS},
href=self.name_to_href(name, posixpath.dirname(self.opf_name)),
id=self.generate_manifest_id())
- mt = guess_type(posixpath.basename(name))[0]
+ if not mt:
+ mt = guess_type(posixpath.basename(name))[0]
if not mt:
mt = 'application/octest-stream'
item.set('media-type', mt)
manifest.append(item)
+ self.fix_tail(item)
+
+ def fix_tail(self, item):
+ '''
+ Designed only to work with self closing elements after item has
+ just been inserted/appended
+ '''
+ parent = item.getparent()
+ idx = parent.index(item)
+ if idx == 0:
+ item.tail = parent.text
+ else:
+ item.tail = parent[idx-1].tail
+ if idx == len(parent)-1:
+ parent[idx-1].tail = parent.text
def generate_manifest_id(self):
items = self.opf.xpath('//opf:manifest/opf:item[@id]',