mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Sycn to pluginize
This commit is contained in:
commit
d69b1ffbdc
@ -59,10 +59,9 @@ class HTMLRenderer(object):
|
|||||||
|
|
||||||
def render_html(path_to_html, width=590, height=750):
|
def render_html(path_to_html, width=590, height=750):
|
||||||
from PyQt4.QtWebKit import QWebPage
|
from PyQt4.QtWebKit import QWebPage
|
||||||
from PyQt4.Qt import QEventLoop, QPalette, Qt, SIGNAL, QUrl, QSize, \
|
from PyQt4.Qt import QEventLoop, QPalette, Qt, SIGNAL, QUrl, QSize
|
||||||
QApplication
|
from calibre.gui2 import is_ok_to_use_qt
|
||||||
if QApplication.instance() is None:
|
if not is_ok_to_use_qt(): return None
|
||||||
QApplication([])
|
|
||||||
path_to_html = os.path.abspath(path_to_html)
|
path_to_html = os.path.abspath(path_to_html)
|
||||||
with CurrentDir(os.path.dirname(path_to_html)):
|
with CurrentDir(os.path.dirname(path_to_html)):
|
||||||
page = QWebPage()
|
page = QWebPage()
|
||||||
|
@ -80,8 +80,10 @@ class EPUBInput(InputFormatPlugin):
|
|||||||
t.set('href', guide_cover)
|
t.set('href', guide_cover)
|
||||||
t.set('title', 'Title Page')
|
t.set('title', 'Title Page')
|
||||||
from calibre.ebooks import render_html
|
from calibre.ebooks import render_html
|
||||||
open('calibre_raster_cover.jpg', 'wb').write(
|
renderer = render_html(guide_cover)
|
||||||
render_html(guide_cover).data)
|
if renderer is not None:
|
||||||
|
open('calibre_raster_cover.jpg', 'wb').write(
|
||||||
|
renderer.data)
|
||||||
|
|
||||||
|
|
||||||
def convert(self, stream, options, file_ext, log, accelerators):
|
def convert(self, stream, options, file_ext, log, accelerators):
|
||||||
|
@ -290,14 +290,6 @@ class MobiReader(object):
|
|||||||
self.replace_page_breaks()
|
self.replace_page_breaks()
|
||||||
self.cleanup_html()
|
self.cleanup_html()
|
||||||
|
|
||||||
if self.processed_html.startswith('<body'):
|
|
||||||
self.processed_html = '<html><head></head>'+self.processed_html+'</html>'
|
|
||||||
self.processed_html = \
|
|
||||||
re.compile('<head>', re.IGNORECASE).sub(
|
|
||||||
'\n<head>\n'
|
|
||||||
'\t<link type="text/css" href="styles.css" />\n',
|
|
||||||
self.processed_html)
|
|
||||||
|
|
||||||
self.log.debug('Parsing HTML...')
|
self.log.debug('Parsing HTML...')
|
||||||
root = html.fromstring(self.processed_html)
|
root = html.fromstring(self.processed_html)
|
||||||
if root.xpath('descendant::p/descendant::p'):
|
if root.xpath('descendant::p/descendant::p'):
|
||||||
@ -305,7 +297,7 @@ class MobiReader(object):
|
|||||||
self.log.warning('Markup contains unclosed <p> tags, parsing using',
|
self.log.warning('Markup contains unclosed <p> tags, parsing using',
|
||||||
'BeatifulSoup')
|
'BeatifulSoup')
|
||||||
root = soupparser.fromstring(self.processed_html)
|
root = soupparser.fromstring(self.processed_html)
|
||||||
if root[0].tag != 'html':
|
if root.tag != 'html':
|
||||||
self.log.warn('File does not have opening <html> tag')
|
self.log.warn('File does not have opening <html> tag')
|
||||||
nroot = html.fromstring('<html><head></head><body></body></html>')
|
nroot = html.fromstring('<html><head></head><body></body></html>')
|
||||||
bod = nroot.find('body')
|
bod = nroot.find('body')
|
||||||
@ -314,6 +306,35 @@ class MobiReader(object):
|
|||||||
bod.append(child)
|
bod.append(child)
|
||||||
root = nroot
|
root = nroot
|
||||||
|
|
||||||
|
htmls = list(root.xpath('//html'))
|
||||||
|
if len(htmls) > 1:
|
||||||
|
self.log.warn('Markup contains multiple <html> tags')
|
||||||
|
# Keep only the largest head and body
|
||||||
|
bodies, heads = root.xpath('//body'), root.xpath('//head')
|
||||||
|
def sz(x): return len(list(x.iter()))
|
||||||
|
def scmp(x, y): return cmp(sz(x), sz(y))
|
||||||
|
body = list(sorted(bodies, cmp=scmp))
|
||||||
|
head = list(sorted(heads, cmp=scmp))
|
||||||
|
for x in root: root.remove(x)
|
||||||
|
if head:
|
||||||
|
root.append(head[-1])
|
||||||
|
if body:
|
||||||
|
root.append(body[-1])
|
||||||
|
for x in root.xpath('//script'):
|
||||||
|
x.getparent().remove(x)
|
||||||
|
|
||||||
|
head = root.xpath('//head')
|
||||||
|
if head:
|
||||||
|
head = head[0]
|
||||||
|
else:
|
||||||
|
head = root.makeelement('head', {})
|
||||||
|
root.insert(0, head)
|
||||||
|
head.text = '\n\t'
|
||||||
|
link = head.makeelement('link', {'type':'text/css',
|
||||||
|
'href':'styles.css'})
|
||||||
|
head.insert(0, link)
|
||||||
|
link.tail = '\n\t'
|
||||||
|
|
||||||
self.upshift_markup(root)
|
self.upshift_markup(root)
|
||||||
guides = root.xpath('//guide')
|
guides = root.xpath('//guide')
|
||||||
guide = guides[0] if guides else None
|
guide = guides[0] if guides else None
|
||||||
|
@ -24,6 +24,19 @@ def asfloat(value, default):
|
|||||||
value = default
|
value = default
|
||||||
return float(value)
|
return float(value)
|
||||||
|
|
||||||
|
def dynamic_rescale_factor(node):
|
||||||
|
classes = node.get('class', '').split(' ')
|
||||||
|
classes = [x.replace('calibre_rescale_', '') for x in classes if
|
||||||
|
x.startswith('calibre_rescale_')]
|
||||||
|
if not classes: return None
|
||||||
|
factor = 1.0
|
||||||
|
for x in classes:
|
||||||
|
try:
|
||||||
|
factor *= float(x)/100.
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
return factor
|
||||||
|
|
||||||
|
|
||||||
class KeyMapper(object):
|
class KeyMapper(object):
|
||||||
def __init__(self, sbase, dbase, dkey):
|
def __init__(self, sbase, dbase, dkey):
|
||||||
@ -202,11 +215,19 @@ class CSSFlattener(object):
|
|||||||
if 'bgcolor' in node.attrib:
|
if 'bgcolor' in node.attrib:
|
||||||
cssdict['background-color'] = node.attrib['bgcolor']
|
cssdict['background-color'] = node.attrib['bgcolor']
|
||||||
del node.attrib['bgcolor']
|
del node.attrib['bgcolor']
|
||||||
if not self.context.disable_font_rescaling and \
|
if not self.context.disable_font_rescaling:
|
||||||
'font-size' in cssdict or tag == 'body':
|
_sbase = self.sbase if self.sbase is not None else \
|
||||||
fsize = self.fmap[style['font-size']]
|
self.context.source.fbase
|
||||||
cssdict['font-size'] = "%0.5fem" % (fsize / psize)
|
dyn_rescale = dynamic_rescale_factor(node)
|
||||||
psize = fsize
|
if dyn_rescale is not None:
|
||||||
|
fsize = self.fmap[_sbase]
|
||||||
|
fsize *= dyn_rescale
|
||||||
|
cssdict['font-size'] = '%0.5fem'%(fsize/psize)
|
||||||
|
psize = fsize
|
||||||
|
elif 'font-size' in cssdict or tag == 'body':
|
||||||
|
fsize = self.fmap[style['font-size']]
|
||||||
|
cssdict['font-size'] = "%0.5fem" % (fsize / psize)
|
||||||
|
psize = fsize
|
||||||
if cssdict:
|
if cssdict:
|
||||||
if self.lineh and self.fbase and tag != 'body':
|
if self.lineh and self.fbase and tag != 'body':
|
||||||
self.clean_edges(cssdict, style, psize)
|
self.clean_edges(cssdict, style, psize)
|
||||||
|
@ -25,14 +25,16 @@ class Jacket(object):
|
|||||||
<title>%(title)s</title>
|
<title>%(title)s</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div style="text-align:center">
|
<div class="calibre_rescale_100">
|
||||||
<h1>%(title)s</h1>
|
<div style="text-align:center">
|
||||||
<h2>%(jacket)s</h2>
|
<h1 class="calibre_rescale_180">%(title)s</h1>
|
||||||
<div>%(series)s</div>
|
<h2 class="calibre_rescale_140">%(jacket)s</h2>
|
||||||
<div>%(tags)s</div>
|
<div class="calibre_rescale_100">%(series)s</div>
|
||||||
</div>
|
<div class="calibre_rescale_100">%(tags)s</div>
|
||||||
<div style="margin-top:2em">
|
</div>
|
||||||
%(comments)s
|
<div style="margin-top:2em" class="calibre_rescale_100">
|
||||||
|
%(comments)s
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -369,13 +369,13 @@ class FlowSplitter(object):
|
|||||||
|
|
||||||
for path in (
|
for path in (
|
||||||
'//*[re:match(name(), "h[1-6]", "i")]',
|
'//*[re:match(name(), "h[1-6]", "i")]',
|
||||||
'/html/body/div',
|
'/h:html/h:body/h:div',
|
||||||
'//pre',
|
'//h:pre',
|
||||||
'//hr',
|
'//h:hr',
|
||||||
'//p',
|
'//h:p',
|
||||||
'//div',
|
'//h:div',
|
||||||
'//br',
|
'//h:br',
|
||||||
'//li',
|
'//h:li',
|
||||||
):
|
):
|
||||||
elems = root.xpath(path, namespaces=NAMESPACES)
|
elems = root.xpath(path, namespaces=NAMESPACES)
|
||||||
elem = pick_elem(elems)
|
elem = pick_elem(elems)
|
||||||
|
@ -27,8 +27,6 @@ def _config():
|
|||||||
help=_('Frequently used directories'))
|
help=_('Frequently used directories'))
|
||||||
c.add_opt('send_to_storage_card_by_default', default=False,
|
c.add_opt('send_to_storage_card_by_default', default=False,
|
||||||
help=_('Send file to storage card instead of main memory by default'))
|
help=_('Send file to storage card instead of main memory by default'))
|
||||||
c.add_opt('save_to_disk_single_format', default='lrf',
|
|
||||||
help=_('The format to use when saving single files to disk'))
|
|
||||||
c.add_opt('confirm_delete', default=False,
|
c.add_opt('confirm_delete', default=False,
|
||||||
help=_('Confirm before deleting'))
|
help=_('Confirm before deleting'))
|
||||||
c.add_opt('toolbar_icon_size', default=QSize(48, 48),
|
c.add_opt('toolbar_icon_size', default=QSize(48, 48),
|
||||||
@ -100,18 +98,23 @@ def available_width():
|
|||||||
def extension(path):
|
def extension(path):
|
||||||
return os.path.splitext(path)[1][1:].lower()
|
return os.path.splitext(path)[1][1:].lower()
|
||||||
|
|
||||||
def warning_dialog(parent, title, msg, det_msg=''):
|
def warning_dialog(parent, title, msg, det_msg='', show=False):
|
||||||
d = QMessageBox(QMessageBox.Warning, 'WARNING: '+title, msg, QMessageBox.Ok,
|
d = QMessageBox(QMessageBox.Warning, 'WARNING: '+title, msg, QMessageBox.Ok,
|
||||||
parent)
|
parent)
|
||||||
d.setDetailedText(det_msg)
|
d.setDetailedText(det_msg)
|
||||||
d.setIconPixmap(QPixmap(':/images/dialog_warning.svg'))
|
d.setIconPixmap(QPixmap(':/images/dialog_warning.svg'))
|
||||||
|
|
||||||
|
if show:
|
||||||
|
return d.exec_()
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def error_dialog(parent, title, msg, det_msg=''):
|
def error_dialog(parent, title, msg, det_msg='', show=False):
|
||||||
d = QMessageBox(QMessageBox.Critical, 'ERROR: '+title, msg, QMessageBox.Ok,
|
d = QMessageBox(QMessageBox.Critical, 'ERROR: '+title, msg, QMessageBox.Ok,
|
||||||
parent)
|
parent)
|
||||||
d.setDetailedText(det_msg)
|
d.setDetailedText(det_msg)
|
||||||
d.setIconPixmap(QPixmap(':/images/dialog_error.svg'))
|
d.setIconPixmap(QPixmap(':/images/dialog_error.svg'))
|
||||||
|
if show:
|
||||||
|
return d.exec_()
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def question_dialog(parent, title, msg, det_msg=''):
|
def question_dialog(parent, title, msg, det_msg=''):
|
||||||
@ -121,13 +124,16 @@ def question_dialog(parent, title, msg, det_msg=''):
|
|||||||
d.setIconPixmap(QPixmap(':/images/dialog_information.svg'))
|
d.setIconPixmap(QPixmap(':/images/dialog_information.svg'))
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def info_dialog(parent, title, msg, det_msg=''):
|
def info_dialog(parent, title, msg, det_msg='', show=False):
|
||||||
d = QMessageBox(QMessageBox.Information, title, msg, QMessageBox.NoButton,
|
d = QMessageBox(QMessageBox.Information, title, msg, QMessageBox.NoButton,
|
||||||
parent)
|
parent)
|
||||||
d.setDetailedText(det_msg)
|
d.setDetailedText(det_msg)
|
||||||
d.setIconPixmap(QPixmap(':/images/dialog_information.svg'))
|
d.setIconPixmap(QPixmap(':/images/dialog_information.svg'))
|
||||||
|
if show:
|
||||||
|
return d.exec_()
|
||||||
return d
|
return d
|
||||||
|
|
||||||
|
|
||||||
def qstring_to_unicode(q):
|
def qstring_to_unicode(q):
|
||||||
return unicode(q)
|
return unicode(q)
|
||||||
|
|
||||||
|
@ -530,8 +530,8 @@ class DeviceGUI(object):
|
|||||||
])
|
])
|
||||||
error_dialog(self, _('Failed to email books'),
|
error_dialog(self, _('Failed to email books'),
|
||||||
_('Failed to email the following books:'),
|
_('Failed to email the following books:'),
|
||||||
'%s'%errors,
|
'%s'%errors
|
||||||
show=True)
|
)
|
||||||
else:
|
else:
|
||||||
self.status_bar.showMessage(_('Sent by email:') + ', '.join(good),
|
self.status_bar.showMessage(_('Sent by email:') + ', '.join(good),
|
||||||
5000)
|
5000)
|
||||||
|
@ -399,12 +399,15 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
|||||||
default_index = self.output_format.findText(prefs['output_format'])
|
default_index = self.output_format.findText(prefs['output_format'])
|
||||||
self.output_format.setCurrentIndex(default_index if default_index != -1 else 0)
|
self.output_format.setCurrentIndex(default_index if default_index != -1 else 0)
|
||||||
|
|
||||||
self.book_exts = sorted(BOOK_EXTENSIONS)
|
output_formats = sorted(available_output_formats())
|
||||||
for ext in self.book_exts:
|
output_formats.remove('oeb')
|
||||||
self.single_format.addItem(ext.upper(), QVariant(ext))
|
for f in output_formats:
|
||||||
|
self.output_format.addItem(f.upper())
|
||||||
|
default_index = \
|
||||||
|
self.output_format.findText(prefs['output_format'].upper())
|
||||||
|
self.output_format.setCurrentIndex(default_index if default_index != -1 else 0)
|
||||||
|
|
||||||
|
|
||||||
single_format = config['save_to_disk_single_format']
|
|
||||||
self.single_format.setCurrentIndex(self.book_exts.index(single_format))
|
|
||||||
self.cover_browse.setValue(config['cover_flow_queue_length'])
|
self.cover_browse.setValue(config['cover_flow_queue_length'])
|
||||||
self.systray_notifications.setChecked(not config['disable_tray_notification'])
|
self.systray_notifications.setChecked(not config['disable_tray_notification'])
|
||||||
from calibre.translations.compiled import translations
|
from calibre.translations.compiled import translations
|
||||||
@ -426,17 +429,17 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
|||||||
|
|
||||||
self.pdf_metadata.setChecked(prefs['read_file_metadata'])
|
self.pdf_metadata.setChecked(prefs['read_file_metadata'])
|
||||||
|
|
||||||
added_html = False
|
exts = set([])
|
||||||
for ext in self.book_exts:
|
for ext in BOOK_EXTENSIONS:
|
||||||
ext = ext.lower()
|
ext = ext.lower()
|
||||||
ext = re.sub(r'(x{0,1})htm(l{0,1})', 'html', ext)
|
ext = re.sub(r'(x{0,1})htm(l{0,1})', 'html', ext)
|
||||||
if ext == 'lrf' or is_supported('book.'+ext):
|
if ext == 'lrf' or is_supported('book.'+ext):
|
||||||
if ext == 'html' and added_html:
|
exts.add(ext)
|
||||||
continue
|
|
||||||
self.viewer.addItem(ext.upper())
|
for ext in sorted(exts):
|
||||||
self.viewer.item(self.viewer.count()-1).setFlags(Qt.ItemIsEnabled|Qt.ItemIsUserCheckable)
|
self.viewer.addItem(ext.upper())
|
||||||
self.viewer.item(self.viewer.count()-1).setCheckState(Qt.Checked if ext.upper() in config['internally_viewed_formats'] else Qt.Unchecked)
|
self.viewer.item(self.viewer.count()-1).setFlags(Qt.ItemIsEnabled|Qt.ItemIsUserCheckable)
|
||||||
added_html = ext == 'html'
|
self.viewer.item(self.viewer.count()-1).setCheckState(Qt.Checked if ext.upper() in config['internally_viewed_formats'] else Qt.Unchecked)
|
||||||
self.viewer.sortItems()
|
self.viewer.sortItems()
|
||||||
self.start.setEnabled(not getattr(self.server, 'is_running', False))
|
self.start.setEnabled(not getattr(self.server, 'is_running', False))
|
||||||
self.test.setEnabled(not self.start.isEnabled())
|
self.test.setEnabled(not self.start.isEnabled())
|
||||||
@ -767,8 +770,7 @@ class ConfigDialog(QDialog, Ui_Dialog):
|
|||||||
p = {0:'normal', 1:'high', 2:'low'}[self.priority.currentIndex()]
|
p = {0:'normal', 1:'high', 2:'low'}[self.priority.currentIndex()]
|
||||||
prefs['worker_process_priority'] = p
|
prefs['worker_process_priority'] = p
|
||||||
prefs['read_file_metadata'] = bool(self.pdf_metadata.isChecked())
|
prefs['read_file_metadata'] = bool(self.pdf_metadata.isChecked())
|
||||||
prefs['output_format'] = self.output_format.currentText()
|
prefs['output_format'] = unicode(self.output_format.currentText()).upper()
|
||||||
config['save_to_disk_single_format'] = self.book_exts[self.single_format.currentIndex()]
|
|
||||||
config['cover_flow_queue_length'] = self.cover_browse.value()
|
config['cover_flow_queue_length'] = self.cover_browse.value()
|
||||||
prefs['language'] = str(self.language.itemData(self.language.currentIndex()).toString())
|
prefs['language'] = str(self.language.itemData(self.language.currentIndex()).toString())
|
||||||
config['systray_icon'] = self.systray_icon.checkState() == Qt.Checked
|
config['systray_icon'] = self.systray_icon.checkState() == Qt.Checked
|
||||||
|
@ -147,19 +147,6 @@
|
|||||||
<item>
|
<item>
|
||||||
<layout class="QGridLayout" name="gridLayout_2">
|
<layout class="QGridLayout" name="gridLayout_2">
|
||||||
<item row="1" column="0">
|
<item row="1" column="0">
|
||||||
<widget class="QLabel" name="label_5">
|
|
||||||
<property name="text">
|
|
||||||
<string>Format for &single file save:</string>
|
|
||||||
</property>
|
|
||||||
<property name="buddy">
|
|
||||||
<cstring>single_format</cstring>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="1" column="1">
|
|
||||||
<widget class="QComboBox" name="single_format"/>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="0">
|
|
||||||
<widget class="QLabel" name="label_2">
|
<widget class="QLabel" name="label_2">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Default network &timeout:</string>
|
<string>Default network &timeout:</string>
|
||||||
@ -169,7 +156,7 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="2" column="1">
|
<item row="1" column="1">
|
||||||
<widget class="QSpinBox" name="timeout">
|
<widget class="QSpinBox" name="timeout">
|
||||||
<property name="toolTip">
|
<property name="toolTip">
|
||||||
<string>Set the default timeout for network fetches (i.e. anytime we go out to the internet to get information)</string>
|
<string>Set the default timeout for network fetches (i.e. anytime we go out to the internet to get information)</string>
|
||||||
@ -188,10 +175,10 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="1">
|
<item row="2" column="1">
|
||||||
<widget class="QComboBox" name="language"/>
|
<widget class="QComboBox" name="language"/>
|
||||||
</item>
|
</item>
|
||||||
<item row="3" column="0">
|
<item row="2" column="0">
|
||||||
<widget class="QLabel" name="label_7">
|
<widget class="QLabel" name="label_7">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Choose &language (requires restart):</string>
|
<string>Choose &language (requires restart):</string>
|
||||||
@ -201,7 +188,7 @@
|
|||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="1">
|
<item row="3" column="1">
|
||||||
<widget class="QComboBox" name="priority">
|
<widget class="QComboBox" name="priority">
|
||||||
<item>
|
<item>
|
||||||
<property name="text">
|
<property name="text">
|
||||||
@ -220,7 +207,7 @@
|
|||||||
</item>
|
</item>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item row="4" column="0">
|
<item row="3" column="0">
|
||||||
<widget class="QLabel" name="priority_label">
|
<widget class="QLabel" name="priority_label">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Job &priority:</string>
|
<string>Job &priority:</string>
|
||||||
|
@ -20,6 +20,7 @@ from calibre.utils.search_query_parser import SearchQueryParser
|
|||||||
from calibre.utils.pyparsing import ParseException
|
from calibre.utils.pyparsing import ParseException
|
||||||
from calibre.gui2 import NONE, error_dialog, config as gconf
|
from calibre.gui2 import NONE, error_dialog, config as gconf
|
||||||
from calibre.utils.config import DynamicConfig
|
from calibre.utils.config import DynamicConfig
|
||||||
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
from calibre.gui2.dialogs.user_profiles import UserProfiles
|
from calibre.gui2.dialogs.user_profiles import UserProfiles
|
||||||
|
|
||||||
config = DynamicConfig('scheduler')
|
config = DynamicConfig('scheduler')
|
||||||
@ -522,8 +523,12 @@ class Scheduler(QObject):
|
|||||||
self.recipes.remove(recipe)
|
self.recipes.remove(recipe)
|
||||||
save_recipes(self.recipes)
|
save_recipes(self.recipes)
|
||||||
return
|
return
|
||||||
|
pt = PersistentTemporaryFile('_builtin.recipe')
|
||||||
|
pt.write(script)
|
||||||
|
pt.close()
|
||||||
|
script = pt.name
|
||||||
except ValueError:
|
except ValueError:
|
||||||
script = recipe.title
|
script = recipe.title + '.recipe'
|
||||||
self.debug('\tQueueing:', recipe)
|
self.debug('\tQueueing:', recipe)
|
||||||
self.main.download_scheduled_recipe(recipe, script, self.recipe_downloaded)
|
self.main.download_scheduled_recipe(recipe, script, self.recipe_downloaded)
|
||||||
self.queue.add(recipe)
|
self.queue.add(recipe)
|
||||||
|
@ -47,6 +47,19 @@ from calibre.library.database2 import LibraryDatabase2, CoverCache
|
|||||||
from calibre.parallel import JobKilled
|
from calibre.parallel import JobKilled
|
||||||
from calibre.gui2.dialogs.confirm_delete import confirm
|
from calibre.gui2.dialogs.confirm_delete import confirm
|
||||||
|
|
||||||
|
class SaveMenu(QMenu):
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
QMenu.__init__(self, _('Save single format to disk...'), parent)
|
||||||
|
for ext in sorted(BOOK_EXTENSIONS):
|
||||||
|
action = self.addAction(ext.upper())
|
||||||
|
setattr(self, 'do_'+ext, partial(self.do, ext))
|
||||||
|
self.connect(action, SIGNAL('triggered(bool)'),
|
||||||
|
getattr(self, 'do_'+ext))
|
||||||
|
|
||||||
|
def do(self, ext, *args):
|
||||||
|
self.emit(SIGNAL('save_fmt(PyQt_PyObject)'), ext)
|
||||||
|
|
||||||
class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
||||||
'The main GUI'
|
'The main GUI'
|
||||||
|
|
||||||
@ -201,8 +214,12 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
self.save_menu = QMenu()
|
self.save_menu = QMenu()
|
||||||
self.save_menu.addAction(_('Save to disk'))
|
self.save_menu.addAction(_('Save to disk'))
|
||||||
self.save_menu.addAction(_('Save to disk in a single directory'))
|
self.save_menu.addAction(_('Save to disk in a single directory'))
|
||||||
self.save_menu.addAction(_('Save only %s format to disk')%\
|
self.save_menu.addAction(_('Save only %s format to disk')%
|
||||||
config.get('save_to_disk_single_format').upper())
|
prefs['output_format'].upper())
|
||||||
|
self.save_sub_menu = SaveMenu(self)
|
||||||
|
self.save_menu.addMenu(self.save_sub_menu)
|
||||||
|
self.connect(self.save_sub_menu, SIGNAL('save_fmt(PyQt_PyObject)'),
|
||||||
|
self.save_specific_format_disk)
|
||||||
|
|
||||||
self.view_menu = QMenu()
|
self.view_menu = QMenu()
|
||||||
self.view_menu.addAction(_('View'))
|
self.view_menu.addAction(_('View'))
|
||||||
@ -856,10 +873,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
_('Failed to download metadata for the following:'),
|
_('Failed to download metadata for the following:'),
|
||||||
details, self).exec_()
|
details, self).exec_()
|
||||||
else:
|
else:
|
||||||
err = _('<b>Failed to download metadata:')+\
|
err = _('Failed to download metadata:')
|
||||||
'</b><br><pre>'+x.tb+'</pre>'
|
error_dialog(self, _('Error'), err, det_msg=x.tb).exec_()
|
||||||
error_dialog(self, _('Error'), err,
|
|
||||||
show=True)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -912,7 +927,10 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
|
|
||||||
############################## Save to disk ################################
|
############################## Save to disk ################################
|
||||||
def save_single_format_to_disk(self, checked):
|
def save_single_format_to_disk(self, checked):
|
||||||
self.save_to_disk(checked, True, config['save_to_disk_single_format'])
|
self.save_to_disk(checked, True, prefs['output_format'])
|
||||||
|
|
||||||
|
def save_specific_format_disk(self, fmt):
|
||||||
|
self.save_to_disk(False, True, fmt)
|
||||||
|
|
||||||
def save_to_single_dir(self, checked):
|
def save_to_single_dir(self, checked):
|
||||||
self.save_to_disk(checked, True)
|
self.save_to_disk(checked, True)
|
||||||
@ -921,10 +939,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
|
|
||||||
rows = self.current_view().selectionModel().selectedRows()
|
rows = self.current_view().selectionModel().selectedRows()
|
||||||
if not rows or len(rows) == 0:
|
if not rows or len(rows) == 0:
|
||||||
d = error_dialog(self, _('Cannot save to disk'),
|
return error_dialog(self, _('Cannot save to disk'),
|
||||||
_('No books selected'))
|
_('No books selected'), show=True)
|
||||||
d.exec_()
|
|
||||||
return
|
|
||||||
|
|
||||||
progress = ProgressDialog(_('Saving to disk...'), min=0, max=len(rows),
|
progress = ProgressDialog(_('Saving to disk...'), min=0, max=len(rows),
|
||||||
parent=self)
|
parent=self)
|
||||||
@ -1266,8 +1282,8 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
config['show_text_in_toolbar'] else \
|
config['show_text_in_toolbar'] else \
|
||||||
Qt.ToolButtonIconOnly)
|
Qt.ToolButtonIconOnly)
|
||||||
self.save_menu.actions()[2].setText(
|
self.save_menu.actions()[2].setText(
|
||||||
_('Save only %s format to disk')%config.get(
|
_('Save only %s format to disk')%
|
||||||
'save_to_disk_single_format').upper())
|
prefs['output_format'].upper())
|
||||||
if self.library_path != d.database_location:
|
if self.library_path != d.database_location:
|
||||||
try:
|
try:
|
||||||
newloc = d.database_location
|
newloc = d.database_location
|
||||||
@ -1414,8 +1430,9 @@ class Main(MainWindow, Ui_MainWindow, DeviceGUI):
|
|||||||
return
|
return
|
||||||
if isinstance(job.exception, JobKilled):
|
if isinstance(job.exception, JobKilled):
|
||||||
return
|
return
|
||||||
error_dialog(self, _('Conversion Error'), job.gui_text(),
|
error_dialog(self, _('Conversion Error'),
|
||||||
show=True)
|
_('Failed to process')+': '+unicode(job.description),
|
||||||
|
det_msg=job.console_text()).exec_()
|
||||||
|
|
||||||
|
|
||||||
def initialize_database(self):
|
def initialize_database(self):
|
||||||
|
@ -42,7 +42,7 @@
|
|||||||
<property name="maximumSize">
|
<property name="maximumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>16777215</width>
|
<width>16777215</width>
|
||||||
<height>100</height>
|
<height>75</height>
|
||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="verticalScrollBarPolicy">
|
<property name="verticalScrollBarPolicy">
|
||||||
|
@ -7,6 +7,7 @@ from PyQt4.Qt import QMainWindow, QString, Qt, QFont, QCoreApplication, SIGNAL,\
|
|||||||
QAction, QMenu, QMenuBar, QIcon
|
QAction, QMenu, QMenuBar, QIcon
|
||||||
from calibre.gui2.dialogs.conversion_error import ConversionErrorDialog
|
from calibre.gui2.dialogs.conversion_error import ConversionErrorDialog
|
||||||
from calibre.utils.config import OptionParser
|
from calibre.utils.config import OptionParser
|
||||||
|
from calibre.gui2 import error_dialog
|
||||||
|
|
||||||
def option_parser(usage='''\
|
def option_parser(usage='''\
|
||||||
Usage: %prog [options]
|
Usage: %prog [options]
|
||||||
@ -19,26 +20,26 @@ Launch the Graphical User Interface
|
|||||||
return parser
|
return parser
|
||||||
|
|
||||||
class DebugWindow(ConversionErrorDialog):
|
class DebugWindow(ConversionErrorDialog):
|
||||||
|
|
||||||
def __init__(self, parent):
|
def __init__(self, parent):
|
||||||
ConversionErrorDialog.__init__(self, parent, 'Console output', '')
|
ConversionErrorDialog.__init__(self, parent, 'Console output', '')
|
||||||
self.setModal(Qt.NonModal)
|
self.setModal(Qt.NonModal)
|
||||||
font = QFont()
|
font = QFont()
|
||||||
font.setStyleHint(QFont.TypeWriter)
|
font.setStyleHint(QFont.TypeWriter)
|
||||||
self.text.setFont(font)
|
self.text.setFont(font)
|
||||||
|
|
||||||
def write(self, msg):
|
def write(self, msg):
|
||||||
self.text.setPlainText(self.text.toPlainText()+QString(msg))
|
self.text.setPlainText(self.text.toPlainText()+QString(msg))
|
||||||
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class MainWindow(QMainWindow):
|
class MainWindow(QMainWindow):
|
||||||
|
|
||||||
___menu_bar = None
|
___menu_bar = None
|
||||||
___menu = None
|
___menu = None
|
||||||
__actions = []
|
__actions = []
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_application_menubar(cls):
|
def create_application_menubar(cls):
|
||||||
mb = QMenuBar(None)
|
mb = QMenuBar(None)
|
||||||
@ -50,8 +51,8 @@ class MainWindow(QMainWindow):
|
|||||||
mb.addMenu(menu)
|
mb.addMenu(menu)
|
||||||
cls.___menu_bar = mb
|
cls.___menu_bar = mb
|
||||||
cls.___menu = menu
|
cls.___menu = menu
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_menubar_actions(cls):
|
def get_menubar_actions(cls):
|
||||||
preferences_action = QAction(QIcon(':/images/config.svg'), _('&Preferences'), None)
|
preferences_action = QAction(QIcon(':/images/config.svg'), _('&Preferences'), None)
|
||||||
@ -59,7 +60,7 @@ class MainWindow(QMainWindow):
|
|||||||
preferences_action.setMenuRole(QAction.PreferencesRole)
|
preferences_action.setMenuRole(QAction.PreferencesRole)
|
||||||
quit_action.setMenuRole(QAction.QuitRole)
|
quit_action.setMenuRole(QAction.QuitRole)
|
||||||
return preferences_action, quit_action
|
return preferences_action, quit_action
|
||||||
|
|
||||||
def __init__(self, opts, parent=None):
|
def __init__(self, opts, parent=None):
|
||||||
QMainWindow.__init__(self, parent)
|
QMainWindow.__init__(self, parent)
|
||||||
app = QCoreApplication.instance()
|
app = QCoreApplication.instance()
|
||||||
@ -69,19 +70,18 @@ class MainWindow(QMainWindow):
|
|||||||
self.__console_redirect = DebugWindow(self)
|
self.__console_redirect = DebugWindow(self)
|
||||||
sys.stdout = sys.stderr = self.__console_redirect
|
sys.stdout = sys.stderr = self.__console_redirect
|
||||||
self.__console_redirect.show()
|
self.__console_redirect.show()
|
||||||
|
|
||||||
def unix_signal(self, signal):
|
def unix_signal(self, signal):
|
||||||
print 'Received signal:', repr(signal)
|
print 'Received signal:', repr(signal)
|
||||||
|
|
||||||
def unhandled_exception(self, type, value, tb):
|
def unhandled_exception(self, type, value, tb):
|
||||||
try:
|
try:
|
||||||
sio = StringIO.StringIO()
|
sio = StringIO.StringIO()
|
||||||
traceback.print_exception(type, value, tb, file=sio)
|
traceback.print_exception(type, value, tb, file=sio)
|
||||||
fe = sio.getvalue()
|
fe = sio.getvalue()
|
||||||
print >>sys.stderr, fe
|
print >>sys.stderr, fe
|
||||||
msg = '<p><b>' + unicode(str(value), 'utf8', 'replace') + '</b></p>'
|
msg = unicode(str(value), 'utf8', 'replace')
|
||||||
msg += '<p>Detailed <b>traceback</b>:<pre>'+fe+'</pre>'
|
error_dialog(self, _('ERROR: Unhandled exception'), msg, det_msg=fe,
|
||||||
d = ConversionErrorDialog(self, _('ERROR: Unhandled exception'), msg)
|
show=True)
|
||||||
d.exec_()
|
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
@ -7,22 +7,22 @@ __docformat__ = 'restructuredtext en'
|
|||||||
Logic for setting up conversion jobs
|
Logic for setting up conversion jobs
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import cPickle, os
|
import cPickle
|
||||||
|
|
||||||
from PyQt4.Qt import QDialog
|
from PyQt4.Qt import QDialog
|
||||||
|
|
||||||
from calibre.ptempfile import PersistentTemporaryFile
|
from calibre.ptempfile import PersistentTemporaryFile
|
||||||
from calibre.gui2 import warning_dialog
|
from calibre.gui2 import warning_dialog
|
||||||
from calibre.gui2.convert import load_specifics
|
|
||||||
from calibre.gui2.convert.single import NoSupportedInputFormats
|
from calibre.gui2.convert.single import NoSupportedInputFormats
|
||||||
from calibre.gui2.convert.single import Config as SingleConfig
|
from calibre.gui2.convert.single import Config as SingleConfig
|
||||||
from calibre.gui2.convert.bulk import BulkConfig
|
from calibre.gui2.convert.bulk import BulkConfig
|
||||||
|
from calibre.utils.config import prefs
|
||||||
|
|
||||||
def convert_single_ebook(parent, db, book_ids, auto_conversion=False, out_format=None):
|
def convert_single_ebook(parent, db, book_ids, auto_conversion=False, out_format=None):
|
||||||
changed = False
|
changed = False
|
||||||
jobs = []
|
jobs = []
|
||||||
bad = []
|
bad = []
|
||||||
|
|
||||||
total = len(book_ids)
|
total = len(book_ids)
|
||||||
if total == 0:
|
if total == 0:
|
||||||
return None, None, None
|
return None, None, None
|
||||||
@ -33,23 +33,23 @@ def convert_single_ebook(parent, db, book_ids, auto_conversion=False, out_format
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
d = SingleConfig(parent, db, book_id, None, out_format)
|
d = SingleConfig(parent, db, book_id, None, out_format)
|
||||||
|
|
||||||
if auto_conversion:
|
if auto_conversion:
|
||||||
d.accept()
|
d.accept()
|
||||||
result = QDialog.Accepted
|
result = QDialog.Accepted
|
||||||
else:
|
else:
|
||||||
result = d.exec_()
|
result = d.exec_()
|
||||||
|
|
||||||
if result == QDialog.Accepted:
|
if result == QDialog.Accepted:
|
||||||
mi = db.get_metadata(book_id, True)
|
mi = db.get_metadata(book_id, True)
|
||||||
in_file = db.format_abspath(book_id, d.input_format, True)
|
in_file = db.format_abspath(book_id, d.input_format, True)
|
||||||
|
|
||||||
out_file = PersistentTemporaryFile('.' + d.output_format)
|
out_file = PersistentTemporaryFile('.' + d.output_format)
|
||||||
out_file.write(d.output_format)
|
out_file.write(d.output_format)
|
||||||
out_file.close()
|
out_file.close()
|
||||||
|
|
||||||
desc = _('Convert book %d of %d (%s)') % (i + 1, total, repr(mi.title))
|
desc = _('Convert book %d of %d (%s)') % (i + 1, total, repr(mi.title))
|
||||||
|
|
||||||
recs = cPickle.loads(d.recommendations)
|
recs = cPickle.loads(d.recommendations)
|
||||||
args = [in_file, out_file.name, recs]
|
args = [in_file, out_file.name, recs]
|
||||||
temp_files = [out_file]
|
temp_files = [out_file]
|
||||||
@ -76,7 +76,7 @@ def convert_bulk_ebook(parent, db, book_ids, out_format=None):
|
|||||||
changed = False
|
changed = False
|
||||||
jobs = []
|
jobs = []
|
||||||
bad = []
|
bad = []
|
||||||
|
|
||||||
total = len(book_ids)
|
total = len(book_ids)
|
||||||
if total == 0:
|
if total == 0:
|
||||||
return None, None, None
|
return None, None, None
|
||||||
@ -95,16 +95,16 @@ def convert_bulk_ebook(parent, db, book_ids, out_format=None):
|
|||||||
try:
|
try:
|
||||||
d = SingleConfig(parent, db, book_id, None, output_format)
|
d = SingleConfig(parent, db, book_id, None, output_format)
|
||||||
d.accept()
|
d.accept()
|
||||||
|
|
||||||
mi = db.get_metadata(book_id, True)
|
mi = db.get_metadata(book_id, True)
|
||||||
in_file = db.format_abspath(book_id, d.input_format, True)
|
in_file = db.format_abspath(book_id, d.input_format, True)
|
||||||
|
|
||||||
out_file = PersistentTemporaryFile('.' + output_format)
|
out_file = PersistentTemporaryFile('.' + output_format)
|
||||||
out_file.write(output_format)
|
out_file.write(output_format)
|
||||||
out_file.close()
|
out_file.close()
|
||||||
|
|
||||||
desc = _('Convert book %d of %d (%s)') % (i + 1, total, repr(mi.title))
|
desc = _('Convert book %d of %d (%s)') % (i + 1, total, repr(mi.title))
|
||||||
|
|
||||||
args = [in_file, out_file.name, recs]
|
args = [in_file, out_file.name, recs]
|
||||||
temp_files = [out_file]
|
temp_files = [out_file]
|
||||||
jobs.append(('gui_convert', args, desc, d.output_format.upper(), book_id, temp_files))
|
jobs.append(('gui_convert', args, desc, d.output_format.upper(), book_id, temp_files))
|
||||||
@ -126,33 +126,18 @@ def convert_bulk_ebook(parent, db, book_ids, out_format=None):
|
|||||||
|
|
||||||
return jobs, changed, bad
|
return jobs, changed, bad
|
||||||
|
|
||||||
def _fetch_news(data, fmt):
|
|
||||||
pt = PersistentTemporaryFile(suffix='_feeds2%s.%s'%(fmt.lower(), fmt.lower()))
|
|
||||||
pt.close()
|
|
||||||
args = ['feeds2%s'%fmt.lower(), '--output', pt.name, '--debug']
|
|
||||||
if data['username']:
|
|
||||||
args.extend(['--username', data['username']])
|
|
||||||
if data['password']:
|
|
||||||
args.extend(['--password', data['password']])
|
|
||||||
args.append(data['script'] if data['script'] else data['title'])
|
|
||||||
return 'fconvert_bulk_ebookseeds2'+fmt.lower(), [args], _('Fetch news from ')+data['title'], fmt.upper(), [pt]
|
|
||||||
|
|
||||||
|
|
||||||
def fetch_scheduled_recipe(recipe, script):
|
def fetch_scheduled_recipe(recipe, script):
|
||||||
from calibre.gui2.dialogs.scheduler import config
|
from calibre.gui2.dialogs.scheduler import config
|
||||||
fmt = prefs['output_format'].lower()
|
fmt = prefs['output_format'].lower()
|
||||||
pt = PersistentTemporaryFile(suffix='_feeds2%s.%s'%(fmt.lower(), fmt.lower()))
|
pt = PersistentTemporaryFile(suffix='_recipe_out.%s'%fmt.lower())
|
||||||
pt.close()
|
pt.close()
|
||||||
args = ['feeds2%s'%fmt.lower(), '--output', pt.name, '--debug']
|
args = ['ebook-convert', script, pt.name, '-vv']
|
||||||
if recipe.needs_subscription:
|
if recipe.needs_subscription:
|
||||||
x = config.get('recipe_account_info_%s'%recipe.id, False)
|
x = config.get('recipe_account_info_%s'%recipe.id, False)
|
||||||
if not x:
|
if not x:
|
||||||
raise ValueError(_('You must set a username and password for %s')%recipe.title)
|
raise ValueError(_('You must set a username and password for %s')%recipe.title)
|
||||||
args.extend(['--username', x[0], '--password', x[1]])
|
args.extend(['--username', x[0], '--password', x[1]])
|
||||||
args.append(script)
|
|
||||||
return 'feeds2'+fmt, [args], _('Fetch news from ')+recipe.title, fmt.upper(), [pt]
|
|
||||||
|
|
||||||
def fetch_news(data):
|
return 'ebook-convert', [args], _('Fetch news from ')+recipe.title, fmt.upper(), [pt]
|
||||||
fmt = prefs['output_format'].lower()
|
|
||||||
return _fetch_news(data, fmt)
|
|
||||||
|
|
||||||
|
@ -19,7 +19,6 @@ from calibre.gui2 import Application, ORG_NAME, APP_UID, choose_files, \
|
|||||||
info_dialog, error_dialog
|
info_dialog, error_dialog
|
||||||
from calibre.ebooks.oeb.iterator import EbookIterator
|
from calibre.ebooks.oeb.iterator import EbookIterator
|
||||||
from calibre.ebooks import DRMError
|
from calibre.ebooks import DRMError
|
||||||
from calibre.gui2.dialogs.conversion_error import ConversionErrorDialog
|
|
||||||
from calibre.constants import islinux
|
from calibre.constants import islinux
|
||||||
from calibre.utils.config import Config, StringConfig
|
from calibre.utils.config import Config, StringConfig
|
||||||
from calibre.gui2.library import SearchBox
|
from calibre.gui2.library import SearchBox
|
||||||
@ -543,8 +542,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
|||||||
if isinstance(worker.exception, DRMError):
|
if isinstance(worker.exception, DRMError):
|
||||||
error_dialog(self, _('DRM Error'), _('<p>This book is protected by <a href="%s">DRM</a>')%'http://wiki.mobileread.com/wiki/DRM').exec_()
|
error_dialog(self, _('DRM Error'), _('<p>This book is protected by <a href="%s">DRM</a>')%'http://wiki.mobileread.com/wiki/DRM').exec_()
|
||||||
else:
|
else:
|
||||||
ConversionErrorDialog(self, _('Could not open ebook'),
|
error_dialog(self, _('Could not open ebook'),
|
||||||
_('<b>%s</b><br/><p>%s</p>')%(worker.exception, worker.traceback.replace('\n', '<br>')), show=True)
|
unicode(worker.exception), det_msg=worker.traceback, show=True)
|
||||||
self.close_progress_indicator()
|
self.close_progress_indicator()
|
||||||
else:
|
else:
|
||||||
self.metadata.show_opf(self.iterator.opf)
|
self.metadata.show_opf(self.iterator.opf)
|
||||||
|
@ -14,7 +14,7 @@ from optparse import IndentedHelpFormatter
|
|||||||
from PyQt4.QtCore import QString
|
from PyQt4.QtCore import QString
|
||||||
from calibre.constants import terminal_controller, iswindows, isosx, \
|
from calibre.constants import terminal_controller, iswindows, isosx, \
|
||||||
__appname__, __version__, __author__, plugins
|
__appname__, __version__, __author__, plugins
|
||||||
from calibre.utils.lock import LockError, ExclusiveFile
|
from calibre.utils.lock import LockError, ExclusiveFile
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
if os.environ.has_key('CALIBRE_CONFIG_DIRECTORY'):
|
if os.environ.has_key('CALIBRE_CONFIG_DIRECTORY'):
|
||||||
@ -38,14 +38,14 @@ def make_config_dir():
|
|||||||
|
|
||||||
|
|
||||||
class CustomHelpFormatter(IndentedHelpFormatter):
|
class CustomHelpFormatter(IndentedHelpFormatter):
|
||||||
|
|
||||||
def format_usage(self, usage):
|
def format_usage(self, usage):
|
||||||
return _("%sUsage%s: %s\n") % (terminal_controller.BLUE, terminal_controller.NORMAL, usage)
|
return _("%sUsage%s: %s\n") % (terminal_controller.BLUE, terminal_controller.NORMAL, usage)
|
||||||
|
|
||||||
def format_heading(self, heading):
|
def format_heading(self, heading):
|
||||||
return "%*s%s%s%s:\n" % (self.current_indent, terminal_controller.BLUE,
|
return "%*s%s%s%s:\n" % (self.current_indent, terminal_controller.BLUE,
|
||||||
"", heading, terminal_controller.NORMAL)
|
"", heading, terminal_controller.NORMAL)
|
||||||
|
|
||||||
def format_option(self, option):
|
def format_option(self, option):
|
||||||
result = []
|
result = []
|
||||||
opts = self.option_strings[option]
|
opts = self.option_strings[option]
|
||||||
@ -55,14 +55,14 @@ class CustomHelpFormatter(IndentedHelpFormatter):
|
|||||||
terminal_controller.GREEN+opts+terminal_controller.NORMAL)
|
terminal_controller.GREEN+opts+terminal_controller.NORMAL)
|
||||||
indent_first = self.help_position
|
indent_first = self.help_position
|
||||||
else: # start help on same line as opts
|
else: # start help on same line as opts
|
||||||
opts = "%*s%-*s " % (self.current_indent, "", opt_width + len(terminal_controller.GREEN + terminal_controller.NORMAL),
|
opts = "%*s%-*s " % (self.current_indent, "", opt_width + len(terminal_controller.GREEN + terminal_controller.NORMAL),
|
||||||
terminal_controller.GREEN + opts + terminal_controller.NORMAL)
|
terminal_controller.GREEN + opts + terminal_controller.NORMAL)
|
||||||
indent_first = 0
|
indent_first = 0
|
||||||
result.append(opts)
|
result.append(opts)
|
||||||
if option.help:
|
if option.help:
|
||||||
help_text = self.expand_default(option).split('\n')
|
help_text = self.expand_default(option).split('\n')
|
||||||
help_lines = []
|
help_lines = []
|
||||||
|
|
||||||
for line in help_text:
|
for line in help_text:
|
||||||
help_lines.extend(textwrap.wrap(line, self.help_width))
|
help_lines.extend(textwrap.wrap(line, self.help_width))
|
||||||
result.append("%*s%s\n" % (indent_first, "", help_lines[0]))
|
result.append("%*s%s\n" % (indent_first, "", help_lines[0]))
|
||||||
@ -74,7 +74,7 @@ class CustomHelpFormatter(IndentedHelpFormatter):
|
|||||||
|
|
||||||
|
|
||||||
class OptionParser(_OptionParser):
|
class OptionParser(_OptionParser):
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
usage='%prog [options] filename',
|
usage='%prog [options] filename',
|
||||||
version='%%prog (%s %s)'%(__appname__, __version__),
|
version='%%prog (%s %s)'%(__appname__, __version__),
|
||||||
@ -85,16 +85,16 @@ class OptionParser(_OptionParser):
|
|||||||
usage = textwrap.dedent(usage)
|
usage = textwrap.dedent(usage)
|
||||||
usage += '''\n\nWhenever you pass arguments to %prog that have spaces in them, '''\
|
usage += '''\n\nWhenever you pass arguments to %prog that have spaces in them, '''\
|
||||||
'''enclose the arguments in quotation marks.'''
|
'''enclose the arguments in quotation marks.'''
|
||||||
_OptionParser.__init__(self, usage=usage, version=version, epilog=epilog,
|
_OptionParser.__init__(self, usage=usage, version=version, epilog=epilog,
|
||||||
formatter=CustomHelpFormatter(),
|
formatter=CustomHelpFormatter(),
|
||||||
conflict_handler=conflict_handler, **kwds)
|
conflict_handler=conflict_handler, **kwds)
|
||||||
self.gui_mode = gui_mode
|
self.gui_mode = gui_mode
|
||||||
|
|
||||||
def error(self, msg):
|
def error(self, msg):
|
||||||
if self.gui_mode:
|
if self.gui_mode:
|
||||||
raise Exception(msg)
|
raise Exception(msg)
|
||||||
_OptionParser.error(self, msg)
|
_OptionParser.error(self, msg)
|
||||||
|
|
||||||
def merge(self, parser):
|
def merge(self, parser):
|
||||||
'''
|
'''
|
||||||
Add options from parser to self. In case of conflicts, conflicting options from
|
Add options from parser to self. In case of conflicts, conflicting options from
|
||||||
@ -102,18 +102,18 @@ class OptionParser(_OptionParser):
|
|||||||
'''
|
'''
|
||||||
opts = list(parser.option_list)
|
opts = list(parser.option_list)
|
||||||
groups = list(parser.option_groups)
|
groups = list(parser.option_groups)
|
||||||
|
|
||||||
def merge_options(options, container):
|
def merge_options(options, container):
|
||||||
for opt in deepcopy(options):
|
for opt in deepcopy(options):
|
||||||
if not self.has_option(opt.get_opt_string()):
|
if not self.has_option(opt.get_opt_string()):
|
||||||
container.add_option(opt)
|
container.add_option(opt)
|
||||||
|
|
||||||
merge_options(opts, self)
|
merge_options(opts, self)
|
||||||
|
|
||||||
for group in groups:
|
for group in groups:
|
||||||
g = self.add_option_group(group.title)
|
g = self.add_option_group(group.title)
|
||||||
merge_options(group.option_list, g)
|
merge_options(group.option_list, g)
|
||||||
|
|
||||||
def subsume(self, group_name, msg=''):
|
def subsume(self, group_name, msg=''):
|
||||||
'''
|
'''
|
||||||
Move all existing options into a subgroup named
|
Move all existing options into a subgroup named
|
||||||
@ -125,7 +125,7 @@ class OptionParser(_OptionParser):
|
|||||||
for opt in opts:
|
for opt in opts:
|
||||||
self.remove_option(opt.get_opt_string())
|
self.remove_option(opt.get_opt_string())
|
||||||
subgroup.add_option(opt)
|
subgroup.add_option(opt)
|
||||||
|
|
||||||
def options_iter(self):
|
def options_iter(self):
|
||||||
for opt in self.option_list:
|
for opt in self.option_list:
|
||||||
if str(opt).strip():
|
if str(opt).strip():
|
||||||
@ -134,12 +134,12 @@ class OptionParser(_OptionParser):
|
|||||||
for opt in gr.option_list:
|
for opt in gr.option_list:
|
||||||
if str(opt).strip():
|
if str(opt).strip():
|
||||||
yield opt
|
yield opt
|
||||||
|
|
||||||
def option_by_dest(self, dest):
|
def option_by_dest(self, dest):
|
||||||
for opt in self.options_iter():
|
for opt in self.options_iter():
|
||||||
if opt.dest == dest:
|
if opt.dest == dest:
|
||||||
return opt
|
return opt
|
||||||
|
|
||||||
def merge_options(self, lower, upper):
|
def merge_options(self, lower, upper):
|
||||||
'''
|
'''
|
||||||
Merge options in lower and upper option lists into upper.
|
Merge options in lower and upper option lists into upper.
|
||||||
@ -153,16 +153,16 @@ class OptionParser(_OptionParser):
|
|||||||
if lower.__dict__[dest] != opt.default and \
|
if lower.__dict__[dest] != opt.default and \
|
||||||
upper.__dict__[dest] == opt.default:
|
upper.__dict__[dest] == opt.default:
|
||||||
upper.__dict__[dest] = lower.__dict__[dest]
|
upper.__dict__[dest] = lower.__dict__[dest]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Option(object):
|
class Option(object):
|
||||||
|
|
||||||
def __init__(self, name, switches=[], help='', type=None, choices=None,
|
def __init__(self, name, switches=[], help='', type=None, choices=None,
|
||||||
check=None, group=None, default=None, action=None, metavar=None):
|
check=None, group=None, default=None, action=None, metavar=None):
|
||||||
if choices:
|
if choices:
|
||||||
type = 'choice'
|
type = 'choice'
|
||||||
|
|
||||||
self.name = name
|
self.name = name
|
||||||
self.switches = switches
|
self.switches = switches
|
||||||
self.help = help.replace('%default', repr(default)) if help else None
|
self.help = help.replace('%default', repr(default)) if help else None
|
||||||
@ -172,40 +172,40 @@ class Option(object):
|
|||||||
self.type = 'float'
|
self.type = 'float'
|
||||||
elif isinstance(default, int) and not isinstance(default, bool):
|
elif isinstance(default, int) and not isinstance(default, bool):
|
||||||
self.type = 'int'
|
self.type = 'int'
|
||||||
|
|
||||||
self.choices = choices
|
self.choices = choices
|
||||||
self.check = check
|
self.check = check
|
||||||
self.group = group
|
self.group = group
|
||||||
self.default = default
|
self.default = default
|
||||||
self.action = action
|
self.action = action
|
||||||
self.metavar = metavar
|
self.metavar = metavar
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return self.name == getattr(other, 'name', other)
|
return self.name == getattr(other, 'name', other)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return 'Option: '+self.name
|
return 'Option: '+self.name
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return repr(self)
|
return repr(self)
|
||||||
|
|
||||||
class OptionValues(object):
|
class OptionValues(object):
|
||||||
|
|
||||||
def copy(self):
|
def copy(self):
|
||||||
return deepcopy(self)
|
return deepcopy(self)
|
||||||
|
|
||||||
class OptionSet(object):
|
class OptionSet(object):
|
||||||
|
|
||||||
OVERRIDE_PAT = re.compile(r'#{3,100} Override Options #{15}(.*?)#{3,100} End Override #{3,100}',
|
OVERRIDE_PAT = re.compile(r'#{3,100} Override Options #{15}(.*?)#{3,100} End Override #{3,100}',
|
||||||
re.DOTALL|re.IGNORECASE)
|
re.DOTALL|re.IGNORECASE)
|
||||||
|
|
||||||
def __init__(self, description=''):
|
def __init__(self, description=''):
|
||||||
self.description = description
|
self.description = description
|
||||||
self.preferences = []
|
self.preferences = []
|
||||||
self.group_list = []
|
self.group_list = []
|
||||||
self.groups = {}
|
self.groups = {}
|
||||||
self.set_buffer = {}
|
self.set_buffer = {}
|
||||||
|
|
||||||
def has_option(self, name_or_option_object):
|
def has_option(self, name_or_option_object):
|
||||||
if name_or_option_object in self.preferences:
|
if name_or_option_object in self.preferences:
|
||||||
return True
|
return True
|
||||||
@ -213,14 +213,14 @@ class OptionSet(object):
|
|||||||
if p.name == name_or_option_object:
|
if p.name == name_or_option_object:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def add_group(self, name, description=''):
|
def add_group(self, name, description=''):
|
||||||
if name in self.group_list:
|
if name in self.group_list:
|
||||||
raise ValueError('A group by the name %s already exists in this set'%name)
|
raise ValueError('A group by the name %s already exists in this set'%name)
|
||||||
self.groups[name] = description
|
self.groups[name] = description
|
||||||
self.group_list.append(name)
|
self.group_list.append(name)
|
||||||
return partial(self.add_opt, group=name)
|
return partial(self.add_opt, group=name)
|
||||||
|
|
||||||
def update(self, other):
|
def update(self, other):
|
||||||
for name in other.groups.keys():
|
for name in other.groups.keys():
|
||||||
self.groups[name] = other.groups[name]
|
self.groups[name] = other.groups[name]
|
||||||
@ -230,7 +230,7 @@ class OptionSet(object):
|
|||||||
if pref in self.preferences:
|
if pref in self.preferences:
|
||||||
self.preferences.remove(pref)
|
self.preferences.remove(pref)
|
||||||
self.preferences.append(pref)
|
self.preferences.append(pref)
|
||||||
|
|
||||||
def smart_update(self, opts1, opts2):
|
def smart_update(self, opts1, opts2):
|
||||||
'''
|
'''
|
||||||
Updates the preference values in opts1 using only the non-default preference values in opts2.
|
Updates the preference values in opts1 using only the non-default preference values in opts2.
|
||||||
@ -239,47 +239,47 @@ class OptionSet(object):
|
|||||||
new = getattr(opts2, pref.name, pref.default)
|
new = getattr(opts2, pref.name, pref.default)
|
||||||
if new != pref.default:
|
if new != pref.default:
|
||||||
setattr(opts1, pref.name, new)
|
setattr(opts1, pref.name, new)
|
||||||
|
|
||||||
def remove_opt(self, name):
|
def remove_opt(self, name):
|
||||||
if name in self.preferences:
|
if name in self.preferences:
|
||||||
self.preferences.remove(name)
|
self.preferences.remove(name)
|
||||||
|
|
||||||
|
|
||||||
def add_opt(self, name, switches=[], help=None, type=None, choices=None,
|
def add_opt(self, name, switches=[], help=None, type=None, choices=None,
|
||||||
group=None, default=None, action=None, metavar=None):
|
group=None, default=None, action=None, metavar=None):
|
||||||
'''
|
'''
|
||||||
Add an option to this section.
|
Add an option to this section.
|
||||||
|
|
||||||
:param name: The name of this option. Must be a valid Python identifier.
|
:param name: The name of this option. Must be a valid Python identifier.
|
||||||
Must also be unique in this OptionSet and all its subsets.
|
Must also be unique in this OptionSet and all its subsets.
|
||||||
:param switches: List of command line switches for this option
|
:param switches: List of command line switches for this option
|
||||||
(as supplied to :module:`optparse`). If empty, this
|
(as supplied to :module:`optparse`). If empty, this
|
||||||
option will not be added to the command line parser.
|
option will not be added to the command line parser.
|
||||||
:param help: Help text.
|
:param help: Help text.
|
||||||
:param type: Type checking of option values. Supported types are:
|
:param type: Type checking of option values. Supported types are:
|
||||||
`None, 'choice', 'complex', 'float', 'int', 'string'`.
|
`None, 'choice', 'complex', 'float', 'int', 'string'`.
|
||||||
:param choices: List of strings or `None`.
|
:param choices: List of strings or `None`.
|
||||||
:param group: Group this option belongs to. You must previously
|
:param group: Group this option belongs to. You must previously
|
||||||
have created this group with a call to :method:`add_group`.
|
have created this group with a call to :method:`add_group`.
|
||||||
:param default: The default value for this option.
|
:param default: The default value for this option.
|
||||||
:param action: The action to pass to optparse. Supported values are:
|
:param action: The action to pass to optparse. Supported values are:
|
||||||
`None, 'count'`. For choices and boolean options,
|
`None, 'count'`. For choices and boolean options,
|
||||||
action is automatically set correctly.
|
action is automatically set correctly.
|
||||||
'''
|
'''
|
||||||
pref = Option(name, switches=switches, help=help, type=type, choices=choices,
|
pref = Option(name, switches=switches, help=help, type=type, choices=choices,
|
||||||
group=group, default=default, action=action, metavar=None)
|
group=group, default=default, action=action, metavar=None)
|
||||||
if group is not None and group not in self.groups.keys():
|
if group is not None and group not in self.groups.keys():
|
||||||
raise ValueError('Group %s has not been added to this section'%group)
|
raise ValueError('Group %s has not been added to this section'%group)
|
||||||
if pref in self.preferences:
|
if pref in self.preferences:
|
||||||
raise ValueError('An option with the name %s already exists in this set.'%name)
|
raise ValueError('An option with the name %s already exists in this set.'%name)
|
||||||
self.preferences.append(pref)
|
self.preferences.append(pref)
|
||||||
|
|
||||||
def option_parser(self, user_defaults=None, usage='', gui_mode=False):
|
def option_parser(self, user_defaults=None, usage='', gui_mode=False):
|
||||||
parser = OptionParser(usage, gui_mode=gui_mode)
|
parser = OptionParser(usage, gui_mode=gui_mode)
|
||||||
groups = defaultdict(lambda : parser)
|
groups = defaultdict(lambda : parser)
|
||||||
for group, desc in self.groups.items():
|
for group, desc in self.groups.items():
|
||||||
groups[group] = parser.add_option_group(group.upper(), desc)
|
groups[group] = parser.add_option_group(group.upper(), desc)
|
||||||
|
|
||||||
for pref in self.preferences:
|
for pref in self.preferences:
|
||||||
if not pref.switches:
|
if not pref.switches:
|
||||||
continue
|
continue
|
||||||
@ -299,16 +299,16 @@ class OptionSet(object):
|
|||||||
action=action,
|
action=action,
|
||||||
)
|
)
|
||||||
g.add_option(*pref.switches, **args)
|
g.add_option(*pref.switches, **args)
|
||||||
|
|
||||||
|
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
def get_override_section(self, src):
|
def get_override_section(self, src):
|
||||||
match = self.OVERRIDE_PAT.search(src)
|
match = self.OVERRIDE_PAT.search(src)
|
||||||
if match:
|
if match:
|
||||||
return match.group()
|
return match.group()
|
||||||
return ''
|
return ''
|
||||||
|
|
||||||
def parse_string(self, src):
|
def parse_string(self, src):
|
||||||
options = {'cPickle':cPickle}
|
options = {'cPickle':cPickle}
|
||||||
if not isinstance(src, unicode):
|
if not isinstance(src, unicode):
|
||||||
@ -327,9 +327,9 @@ class OptionSet(object):
|
|||||||
if callable(formatter):
|
if callable(formatter):
|
||||||
val = formatter(val)
|
val = formatter(val)
|
||||||
setattr(opts, pref.name, val)
|
setattr(opts, pref.name, val)
|
||||||
|
|
||||||
return opts
|
return opts
|
||||||
|
|
||||||
def render_group(self, name, desc, opts):
|
def render_group(self, name, desc, opts):
|
||||||
prefs = [pref for pref in self.preferences if pref.group == name]
|
prefs = [pref for pref in self.preferences if pref.group == name]
|
||||||
lines = ['### Begin group: %s'%(name if name else 'DEFAULT')]
|
lines = ['### Begin group: %s'%(name if name else 'DEFAULT')]
|
||||||
@ -340,11 +340,11 @@ class OptionSet(object):
|
|||||||
lines.append('# '+pref.name.replace('_', ' '))
|
lines.append('# '+pref.name.replace('_', ' '))
|
||||||
if pref.help:
|
if pref.help:
|
||||||
lines += map(lambda x: '# ' + x, pref.help.split('\n'))
|
lines += map(lambda x: '# ' + x, pref.help.split('\n'))
|
||||||
lines.append('%s = %s'%(pref.name,
|
lines.append('%s = %s'%(pref.name,
|
||||||
self.serialize_opt(getattr(opts, pref.name, pref.default))))
|
self.serialize_opt(getattr(opts, pref.name, pref.default))))
|
||||||
lines.append(' ')
|
lines.append(' ')
|
||||||
return '\n'.join(lines)
|
return '\n'.join(lines)
|
||||||
|
|
||||||
def serialize_opt(self, val):
|
def serialize_opt(self, val):
|
||||||
if val is val is True or val is False or val is None or \
|
if val is val is True or val is False or val is None or \
|
||||||
isinstance(val, (int, float, long, basestring)):
|
isinstance(val, (int, float, long, basestring)):
|
||||||
@ -353,7 +353,7 @@ class OptionSet(object):
|
|||||||
return repr(unicode(val))
|
return repr(unicode(val))
|
||||||
pickle = cPickle.dumps(val, -1)
|
pickle = cPickle.dumps(val, -1)
|
||||||
return 'cPickle.loads(%s)'%repr(pickle)
|
return 'cPickle.loads(%s)'%repr(pickle)
|
||||||
|
|
||||||
def serialize(self, opts):
|
def serialize(self, opts):
|
||||||
src = '# %s\n\n'%(self.description.replace('\n', '\n# '))
|
src = '# %s\n\n'%(self.description.replace('\n', '\n# '))
|
||||||
groups = [self.render_group(name, self.groups.get(name, ''), opts) \
|
groups = [self.render_group(name, self.groups.get(name, ''), opts) \
|
||||||
@ -361,34 +361,34 @@ class OptionSet(object):
|
|||||||
return src + '\n\n'.join(groups)
|
return src + '\n\n'.join(groups)
|
||||||
|
|
||||||
class ConfigInterface(object):
|
class ConfigInterface(object):
|
||||||
|
|
||||||
def __init__(self, description):
|
def __init__(self, description):
|
||||||
self.option_set = OptionSet(description=description)
|
self.option_set = OptionSet(description=description)
|
||||||
self.add_opt = self.option_set.add_opt
|
self.add_opt = self.option_set.add_opt
|
||||||
self.add_group = self.option_set.add_group
|
self.add_group = self.option_set.add_group
|
||||||
self.remove_opt = self.remove = self.option_set.remove_opt
|
self.remove_opt = self.remove = self.option_set.remove_opt
|
||||||
self.parse_string = self.option_set.parse_string
|
self.parse_string = self.option_set.parse_string
|
||||||
|
|
||||||
def update(self, other):
|
def update(self, other):
|
||||||
self.option_set.update(other.option_set)
|
self.option_set.update(other.option_set)
|
||||||
|
|
||||||
def option_parser(self, usage='', gui_mode=False):
|
def option_parser(self, usage='', gui_mode=False):
|
||||||
return self.option_set.option_parser(user_defaults=self.parse(),
|
return self.option_set.option_parser(user_defaults=self.parse(),
|
||||||
usage=usage, gui_mode=gui_mode)
|
usage=usage, gui_mode=gui_mode)
|
||||||
|
|
||||||
def smart_update(self, opts1, opts2):
|
def smart_update(self, opts1, opts2):
|
||||||
self.option_set.smart_update(opts1, opts2)
|
self.option_set.smart_update(opts1, opts2)
|
||||||
|
|
||||||
class Config(ConfigInterface):
|
class Config(ConfigInterface):
|
||||||
'''
|
'''
|
||||||
A file based configuration.
|
A file based configuration.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, basename, description=''):
|
def __init__(self, basename, description=''):
|
||||||
ConfigInterface.__init__(self, description)
|
ConfigInterface.__init__(self, description)
|
||||||
self.config_file_path = os.path.join(config_dir, basename+'.py')
|
self.config_file_path = os.path.join(config_dir, basename+'.py')
|
||||||
|
|
||||||
|
|
||||||
def parse(self):
|
def parse(self):
|
||||||
src = ''
|
src = ''
|
||||||
if os.path.exists(self.config_file_path):
|
if os.path.exists(self.config_file_path):
|
||||||
@ -398,7 +398,7 @@ class Config(ConfigInterface):
|
|||||||
except LockError:
|
except LockError:
|
||||||
raise IOError('Could not lock config file: %s'%self.config_file_path)
|
raise IOError('Could not lock config file: %s'%self.config_file_path)
|
||||||
return self.option_set.parse_string(src)
|
return self.option_set.parse_string(src)
|
||||||
|
|
||||||
def as_string(self):
|
def as_string(self):
|
||||||
if not os.path.exists(self.config_file_path):
|
if not os.path.exists(self.config_file_path):
|
||||||
return ''
|
return ''
|
||||||
@ -407,7 +407,7 @@ class Config(ConfigInterface):
|
|||||||
return f.read().decode('utf-8')
|
return f.read().decode('utf-8')
|
||||||
except LockError:
|
except LockError:
|
||||||
raise IOError('Could not lock config file: %s'%self.config_file_path)
|
raise IOError('Could not lock config file: %s'%self.config_file_path)
|
||||||
|
|
||||||
def set(self, name, val):
|
def set(self, name, val):
|
||||||
if not self.option_set.has_option(name):
|
if not self.option_set.has_option(name):
|
||||||
raise ValueError('The option %s is not defined.'%name)
|
raise ValueError('The option %s is not defined.'%name)
|
||||||
@ -427,19 +427,19 @@ class Config(ConfigInterface):
|
|||||||
f.write(src)
|
f.write(src)
|
||||||
except LockError:
|
except LockError:
|
||||||
raise IOError('Could not lock config file: %s'%self.config_file_path)
|
raise IOError('Could not lock config file: %s'%self.config_file_path)
|
||||||
|
|
||||||
class StringConfig(ConfigInterface):
|
class StringConfig(ConfigInterface):
|
||||||
'''
|
'''
|
||||||
A string based configuration
|
A string based configuration
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, src, description=''):
|
def __init__(self, src, description=''):
|
||||||
ConfigInterface.__init__(self, description)
|
ConfigInterface.__init__(self, description)
|
||||||
self.src = src
|
self.src = src
|
||||||
|
|
||||||
def parse(self):
|
def parse(self):
|
||||||
return self.option_set.parse_string(self.src)
|
return self.option_set.parse_string(self.src)
|
||||||
|
|
||||||
def set(self, name, val):
|
def set(self, name, val):
|
||||||
if not self.option_set.has_option(name):
|
if not self.option_set.has_option(name):
|
||||||
raise ValueError('The option %s is not defined.'%name)
|
raise ValueError('The option %s is not defined.'%name)
|
||||||
@ -452,30 +452,30 @@ class ConfigProxy(object):
|
|||||||
'''
|
'''
|
||||||
A Proxy to minimize file reads for widely used config settings
|
A Proxy to minimize file reads for widely used config settings
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
self.__config = config
|
self.__config = config
|
||||||
self.__opts = None
|
self.__opts = None
|
||||||
|
|
||||||
def refresh(self):
|
def refresh(self):
|
||||||
self.__opts = self.__config.parse()
|
self.__opts = self.__config.parse()
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
return self.get(key)
|
return self.get(key)
|
||||||
|
|
||||||
def __setitem__(self, key, val):
|
def __setitem__(self, key, val):
|
||||||
return self.set(key, val)
|
return self.set(key, val)
|
||||||
|
|
||||||
def get(self, key):
|
def get(self, key):
|
||||||
if self.__opts is None:
|
if self.__opts is None:
|
||||||
self.refresh()
|
self.refresh()
|
||||||
return getattr(self.__opts, key)
|
return getattr(self.__opts, key)
|
||||||
|
|
||||||
def set(self, key, val):
|
def set(self, key, val):
|
||||||
if self.__opts is None:
|
if self.__opts is None:
|
||||||
self.refresh()
|
self.refresh()
|
||||||
setattr(self.__opts, key, val)
|
setattr(self.__opts, key, val)
|
||||||
return self.__config.set(key, val)
|
return self.__config.set(key, val)
|
||||||
|
|
||||||
class DynamicConfig(dict):
|
class DynamicConfig(dict):
|
||||||
'''
|
'''
|
||||||
@ -489,7 +489,7 @@ class DynamicConfig(dict):
|
|||||||
self.name = name
|
self.name = name
|
||||||
self.file_path = os.path.join(config_dir, name+'.pickle')
|
self.file_path = os.path.join(config_dir, name+'.pickle')
|
||||||
self.refresh()
|
self.refresh()
|
||||||
|
|
||||||
def refresh(self):
|
def refresh(self):
|
||||||
d = {}
|
d = {}
|
||||||
if os.path.exists(self.file_path):
|
if os.path.exists(self.file_path):
|
||||||
@ -503,20 +503,20 @@ class DynamicConfig(dict):
|
|||||||
d = {}
|
d = {}
|
||||||
self.clear()
|
self.clear()
|
||||||
self.update(d)
|
self.update(d)
|
||||||
|
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
try:
|
try:
|
||||||
return dict.__getitem__(self, key)
|
return dict.__getitem__(self, key)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def __setitem__(self, key, val):
|
def __setitem__(self, key, val):
|
||||||
dict.__setitem__(self, key, val)
|
dict.__setitem__(self, key, val)
|
||||||
self.commit()
|
self.commit()
|
||||||
|
|
||||||
def set(self, key, val):
|
def set(self, key, val):
|
||||||
self.__setitem__(key, val)
|
self.__setitem__(key, val)
|
||||||
|
|
||||||
def commit(self):
|
def commit(self):
|
||||||
if hasattr(self, 'file_path') and self.file_path:
|
if hasattr(self, 'file_path') and self.file_path:
|
||||||
if not os.path.exists(self.file_path):
|
if not os.path.exists(self.file_path):
|
||||||
@ -526,17 +526,17 @@ class DynamicConfig(dict):
|
|||||||
f.seek(0)
|
f.seek(0)
|
||||||
f.truncate()
|
f.truncate()
|
||||||
f.write(raw)
|
f.write(raw)
|
||||||
|
|
||||||
dynamic = DynamicConfig()
|
dynamic = DynamicConfig()
|
||||||
|
|
||||||
def _prefs():
|
def _prefs():
|
||||||
c = Config('global', 'calibre wide preferences')
|
c = Config('global', 'calibre wide preferences')
|
||||||
c.add_opt('database_path',
|
c.add_opt('database_path',
|
||||||
default=os.path.expanduser('~/library1.db'),
|
default=os.path.expanduser('~/library1.db'),
|
||||||
help=_('Path to the database in which books are stored'))
|
help=_('Path to the database in which books are stored'))
|
||||||
c.add_opt('filename_pattern', default=ur'(?P<title>.+) - (?P<author>[^_]+)',
|
c.add_opt('filename_pattern', default=ur'(?P<title>.+) - (?P<author>[^_]+)',
|
||||||
help=_('Pattern to guess metadata from filenames'))
|
help=_('Pattern to guess metadata from filenames'))
|
||||||
c.add_opt('isbndb_com_key', default='',
|
c.add_opt('isbndb_com_key', default='',
|
||||||
help=_('Access key for isbndb.com'))
|
help=_('Access key for isbndb.com'))
|
||||||
c.add_opt('network_timeout', default=5,
|
c.add_opt('network_timeout', default=5,
|
||||||
help=_('Default timeout for network operations (seconds)'))
|
help=_('Default timeout for network operations (seconds)'))
|
||||||
@ -544,13 +544,13 @@ def _prefs():
|
|||||||
help=_('Path to directory in which your library of books is stored'))
|
help=_('Path to directory in which your library of books is stored'))
|
||||||
c.add_opt('language', default=None,
|
c.add_opt('language', default=None,
|
||||||
help=_('The language in which to display the user interface'))
|
help=_('The language in which to display the user interface'))
|
||||||
c.add_opt('output_format', default='EPUB',
|
c.add_opt('output_format', default='EPUB',
|
||||||
help=_('The default output format for ebook conversions.'))
|
help=_('The default output format for ebook conversions.'))
|
||||||
c.add_opt('read_file_metadata', default=True,
|
c.add_opt('read_file_metadata', default=True,
|
||||||
help=_('Read metadata from files'))
|
help=_('Read metadata from files'))
|
||||||
c.add_opt('worker_process_priority', default='normal',
|
c.add_opt('worker_process_priority', default='normal',
|
||||||
help=_('The priority of worker processes'))
|
help=_('The priority of worker processes'))
|
||||||
|
|
||||||
c.add_opt('migrated', default=False, help='For Internal use. Don\'t modify.')
|
c.add_opt('migrated', default=False, help='For Internal use. Don\'t modify.')
|
||||||
return c
|
return c
|
||||||
|
|
||||||
@ -565,11 +565,11 @@ def migrate():
|
|||||||
|
|
||||||
from PyQt4.QtCore import QSettings, QVariant
|
from PyQt4.QtCore import QSettings, QVariant
|
||||||
class Settings(QSettings):
|
class Settings(QSettings):
|
||||||
|
|
||||||
def __init__(self, name='calibre2'):
|
def __init__(self, name='calibre2'):
|
||||||
QSettings.__init__(self, QSettings.IniFormat, QSettings.UserScope,
|
QSettings.__init__(self, QSettings.IniFormat, QSettings.UserScope,
|
||||||
'kovidgoyal.net', name)
|
'kovidgoyal.net', name)
|
||||||
|
|
||||||
def get(self, key, default=None):
|
def get(self, key, default=None):
|
||||||
try:
|
try:
|
||||||
key = str(key)
|
key = str(key)
|
||||||
@ -581,7 +581,7 @@ def migrate():
|
|||||||
return cPickle.loads(val)
|
return cPickle.loads(val)
|
||||||
except:
|
except:
|
||||||
return default
|
return default
|
||||||
|
|
||||||
s, migrated = Settings(), set([])
|
s, migrated = Settings(), set([])
|
||||||
all_keys = set(map(unicode, s.allKeys()))
|
all_keys = set(map(unicode, s.allKeys()))
|
||||||
from calibre.gui2 import config, dynamic
|
from calibre.gui2 import config, dynamic
|
||||||
@ -599,13 +599,13 @@ def migrate():
|
|||||||
pass
|
pass
|
||||||
finally:
|
finally:
|
||||||
migrated.add(key)
|
migrated.add(key)
|
||||||
|
|
||||||
|
|
||||||
_migrate('database path', p=prefs)
|
_migrate('database path', p=prefs)
|
||||||
_migrate('filename pattern', p=prefs)
|
_migrate('filename pattern', p=prefs)
|
||||||
_migrate('network timeout', p=prefs)
|
_migrate('network timeout', p=prefs)
|
||||||
_migrate('isbndb.com key', p=prefs)
|
_migrate('isbndb.com key', p=prefs)
|
||||||
|
|
||||||
_migrate('frequently used directories')
|
_migrate('frequently used directories')
|
||||||
_migrate('send to device by default')
|
_migrate('send to device by default')
|
||||||
_migrate('save to disk single format')
|
_migrate('save to disk single format')
|
||||||
@ -616,33 +616,33 @@ def migrate():
|
|||||||
_migrate('cover flow queue length')
|
_migrate('cover flow queue length')
|
||||||
_migrate('LRF conversion defaults')
|
_migrate('LRF conversion defaults')
|
||||||
_migrate('LRF ebook viewer options')
|
_migrate('LRF ebook viewer options')
|
||||||
|
|
||||||
for key in all_keys - migrated:
|
for key in all_keys - migrated:
|
||||||
if key.endswith(': un') or key.endswith(': pw'):
|
if key.endswith(': un') or key.endswith(': pw'):
|
||||||
_migrate(key, p=dynamic)
|
_migrate(key, p=dynamic)
|
||||||
p.set('migrated', True)
|
p.set('migrated', True)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import subprocess
|
import subprocess
|
||||||
from PyQt4.Qt import QByteArray
|
from PyQt4.Qt import QByteArray
|
||||||
c = Config('test', 'test config')
|
c = Config('test', 'test config')
|
||||||
|
|
||||||
c.add_opt('one', ['-1', '--one'], help="This is option #1")
|
c.add_opt('one', ['-1', '--one'], help="This is option #1")
|
||||||
c.set('one', u'345')
|
c.set('one', u'345')
|
||||||
|
|
||||||
c.add_opt('two', help="This is option #2")
|
c.add_opt('two', help="This is option #2")
|
||||||
c.set('two', 345)
|
c.set('two', 345)
|
||||||
|
|
||||||
c.add_opt('three', help="This is option #3")
|
c.add_opt('three', help="This is option #3")
|
||||||
c.set('three', QString(u'aflatoon'))
|
c.set('three', QString(u'aflatoon'))
|
||||||
|
|
||||||
c.add_opt('four', help="This is option #4")
|
c.add_opt('four', help="This is option #4")
|
||||||
c.set('four', QByteArray('binary aflatoon'))
|
c.set('four', QByteArray('binary aflatoon'))
|
||||||
|
|
||||||
subprocess.call(['pygmentize', os.path.expanduser('~/.config/calibre/test.py')])
|
subprocess.call(['pygmentize', os.path.expanduser('~/.config/calibre/test.py')])
|
||||||
|
|
||||||
opts = c.parse()
|
opts = c.parse()
|
||||||
for i in ('one', 'two', 'three', 'four'):
|
for i in ('one', 'two', 'three', 'four'):
|
||||||
print i, repr(getattr(opts, i))
|
print i, repr(getattr(opts, i))
|
||||||
|
|
||||||
|
@ -18,9 +18,13 @@ class RecipeInput(InputFormatPlugin):
|
|||||||
file_types = set(['recipe'])
|
file_types = set(['recipe'])
|
||||||
|
|
||||||
recommendations = set([
|
recommendations = set([
|
||||||
('chapter_mark', 'none', OptionRecommendation.HIGH),
|
('chapter', None, OptionRecommendation.HIGH),
|
||||||
('dont_split_on_page_breaks', True, OptionRecommendation.HIGH),
|
('dont_split_on_page_breaks', True, OptionRecommendation.HIGH),
|
||||||
('use_auto_toc', False, OptionRecommendation.HIGH),
|
('use_auto_toc', False, OptionRecommendation.HIGH),
|
||||||
|
('input_encoding', None, OptionRecommendation.HIGH),
|
||||||
|
('input_profile', 'default', OptionRecommendation.HIGH),
|
||||||
|
('page_breaks_before', None, OptionRecommendation.HIGH),
|
||||||
|
('insert_metadata', False, OptionRecommendation.HIGH),
|
||||||
])
|
])
|
||||||
|
|
||||||
options = set([
|
options = set([
|
||||||
|
@ -231,23 +231,23 @@ class BasicNewsRecipe(Recipe):
|
|||||||
#: use :member:`extra_css` in your recipe to customize look and feel.
|
#: use :member:`extra_css` in your recipe to customize look and feel.
|
||||||
template_css = u'''
|
template_css = u'''
|
||||||
.article_date {
|
.article_date {
|
||||||
font-size: x-small; color: gray; font-family: monospace;
|
color: gray; font-family: monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.article_description {
|
.article_description {
|
||||||
font-size: small; font-family: sans; text-indent: 0pt;
|
font-family: sans; text-indent: 0pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.article {
|
a.article {
|
||||||
font-weight: bold; font-size: large;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.feed {
|
a.feed {
|
||||||
font-weight: bold; font-size: large;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar {
|
.navbar {
|
||||||
font-family:monospace; font-size:8pt
|
font-family:monospace;
|
||||||
}
|
}
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@ -579,8 +579,9 @@ class BasicNewsRecipe(Recipe):
|
|||||||
|
|
||||||
def feeds2index(self, feeds):
|
def feeds2index(self, feeds):
|
||||||
templ = templates.IndexTemplate()
|
templ = templates.IndexTemplate()
|
||||||
|
css = self.template_css + '\n\n' +(self.extra_css if self.extra_css else '')
|
||||||
return templ.generate(self.title, self.timefmt, feeds,
|
return templ.generate(self.title, self.timefmt, feeds,
|
||||||
extra_css=self.extra_css).render(doctype='xhtml')
|
extra_css=css).render(doctype='xhtml')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def description_limiter(cls, src):
|
def description_limiter(cls, src):
|
||||||
@ -631,8 +632,9 @@ class BasicNewsRecipe(Recipe):
|
|||||||
|
|
||||||
|
|
||||||
templ = templates.FeedTemplate()
|
templ = templates.FeedTemplate()
|
||||||
|
css = self.template_css + '\n\n' +(self.extra_css if self.extra_css else '')
|
||||||
return templ.generate(feed, self.description_limiter,
|
return templ.generate(feed, self.description_limiter,
|
||||||
extra_css=self.extra_css).render(doctype='xhtml')
|
extra_css=css).render(doctype='xhtml')
|
||||||
|
|
||||||
|
|
||||||
def _fetch_article(self, url, dir, f, a, num_of_feeds):
|
def _fetch_article(self, url, dir, f, a, num_of_feeds):
|
||||||
|
@ -5,12 +5,13 @@ import re
|
|||||||
from calibre.web.feeds.news import BasicNewsRecipe
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
|
|
||||||
class DNAIndia(BasicNewsRecipe):
|
class DNAIndia(BasicNewsRecipe):
|
||||||
|
|
||||||
title = 'DNA India'
|
title = 'DNA India'
|
||||||
description = 'Mumbai news, India news, World news, breaking news'
|
description = 'Mumbai news, India news, World news, breaking news'
|
||||||
__author__ = 'Kovid Goyal'
|
__author__ = 'Kovid Goyal'
|
||||||
language = _('English')
|
language = _('English')
|
||||||
|
encoding = 'cp1252'
|
||||||
|
|
||||||
feeds = [
|
feeds = [
|
||||||
('Top News', 'http://www.dnaindia.com/syndication/rss_topnews.xml'),
|
('Top News', 'http://www.dnaindia.com/syndication/rss_topnews.xml'),
|
||||||
('Popular News', 'http://www.dnaindia.com/syndication/rss_popular.xml'),
|
('Popular News', 'http://www.dnaindia.com/syndication/rss_popular.xml'),
|
||||||
@ -21,21 +22,21 @@ class DNAIndia(BasicNewsRecipe):
|
|||||||
('Money', 'http://www.dnaindia.com/syndication/rss,catid-4.xml'),
|
('Money', 'http://www.dnaindia.com/syndication/rss,catid-4.xml'),
|
||||||
('Sports', 'http://www.dnaindia.com/syndication/rss,catid-6.xml'),
|
('Sports', 'http://www.dnaindia.com/syndication/rss,catid-6.xml'),
|
||||||
('After Hours', 'http://www.dnaindia.com/syndication/rss,catid-7.xml'),
|
('After Hours', 'http://www.dnaindia.com/syndication/rss,catid-7.xml'),
|
||||||
('Digital Life', 'http://www.dnaindia.com/syndication/rss,catid-1089741.xml'),
|
('Digital Life', 'http://www.dnaindia.com/syndication/rss,catid-1089741.xml'),
|
||||||
]
|
]
|
||||||
remove_tags = [{'id':'footer'}, {'class':['bottom', 'categoryHead']}]
|
remove_tags = [{'id':'footer'}, {'class':['bottom', 'categoryHead']}]
|
||||||
|
|
||||||
def print_version(self, url):
|
def print_version(self, url):
|
||||||
match = re.search(r'newsid=(\d+)', url)
|
match = re.search(r'newsid=(\d+)', url)
|
||||||
if not match:
|
if not match:
|
||||||
return url
|
return url
|
||||||
return 'http://www.dnaindia.com/dnaprint.asp?newsid='+match.group(1)
|
return 'http://www.dnaindia.com/dnaprint.asp?newsid='+match.group(1)
|
||||||
|
|
||||||
def postprocess_html(self, soup, first_fetch):
|
def postprocess_html(self, soup, first_fetch):
|
||||||
for t in soup.findAll(['table', 'tr', 'td']):
|
for t in soup.findAll(['table', 'tr', 'td']):
|
||||||
t.name = 'div'
|
t.name = 'div'
|
||||||
|
|
||||||
a = soup.find(href='http://www.3dsyndication.com/')
|
a = soup.find(href='http://www.3dsyndication.com/')
|
||||||
if a is not None:
|
if a is not None:
|
||||||
a.parent.extract()
|
a.parent.extract()
|
||||||
return soup
|
return soup
|
||||||
|
@ -5,7 +5,6 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
|
|||||||
import re, string, time
|
import re, string, time
|
||||||
from calibre import strftime
|
from calibre import strftime
|
||||||
from calibre.web.feeds.news import BasicNewsRecipe
|
from calibre.web.feeds.news import BasicNewsRecipe
|
||||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup
|
|
||||||
|
|
||||||
class Newsweek(BasicNewsRecipe):
|
class Newsweek(BasicNewsRecipe):
|
||||||
|
|
||||||
@ -14,15 +13,20 @@ class Newsweek(BasicNewsRecipe):
|
|||||||
description = 'Weekly news and current affairs in the US'
|
description = 'Weekly news and current affairs in the US'
|
||||||
no_stylesheets = True
|
no_stylesheets = True
|
||||||
language = _('English')
|
language = _('English')
|
||||||
|
|
||||||
extra_css = '#content { font:serif 12pt; }\n.story {font:12pt}\n.HorizontalHeader {font:18pt}\n.deck {font:16pt}'
|
extra_css = '''
|
||||||
|
#content { font-size:normal; font-family: serif }
|
||||||
|
.story { font-size:normal }
|
||||||
|
.HorizontalHeader {font-size:xx-large}
|
||||||
|
.deck {font-size:x-large}
|
||||||
|
'''
|
||||||
keep_only_tags = [dict(name='div', id='content')]
|
keep_only_tags = [dict(name='div', id='content')]
|
||||||
|
|
||||||
remove_tags = [
|
remove_tags = [
|
||||||
dict(name=['script', 'noscript']),
|
dict(name=['script', 'noscript']),
|
||||||
dict(name='div', attrs={'class':['ad', 'SocialLinks', 'SocialLinksDiv',
|
dict(name='div', attrs={'class':['ad', 'SocialLinks', 'SocialLinksDiv',
|
||||||
'channel', 'bot', 'nav', 'top',
|
'channel', 'bot', 'nav', 'top',
|
||||||
'EmailArticleBlock',
|
'EmailArticleBlock',
|
||||||
'comments-and-social-links-wrapper',
|
'comments-and-social-links-wrapper',
|
||||||
'inline-social-links-wrapper',
|
'inline-social-links-wrapper',
|
||||||
'inline-social-links',
|
'inline-social-links',
|
||||||
@ -31,14 +35,14 @@ class Newsweek(BasicNewsRecipe):
|
|||||||
dict(id=['ToolBox', 'EmailMain', 'EmailArticle', 'comment-box',
|
dict(id=['ToolBox', 'EmailMain', 'EmailArticle', 'comment-box',
|
||||||
'nw-comments'])
|
'nw-comments'])
|
||||||
]
|
]
|
||||||
|
|
||||||
recursions = 1
|
recursions = 1
|
||||||
match_regexps = [r'http://www.newsweek.com/id/\S+/page/\d+']
|
match_regexps = [r'http://www.newsweek.com/id/\S+/page/\d+']
|
||||||
|
|
||||||
|
|
||||||
def get_sections(self, soup):
|
def get_sections(self, soup):
|
||||||
sections = []
|
sections = []
|
||||||
|
|
||||||
def process_section(img):
|
def process_section(img):
|
||||||
articles = []
|
articles = []
|
||||||
match = re.search(r'label_([^_.]+)', img['src'])
|
match = re.search(r'label_([^_.]+)', img['src'])
|
||||||
@ -48,25 +52,25 @@ class Newsweek(BasicNewsRecipe):
|
|||||||
if title in ['coverstory', 'more', 'tipsheet']:
|
if title in ['coverstory', 'more', 'tipsheet']:
|
||||||
return
|
return
|
||||||
title = string.capwords(title)
|
title = string.capwords(title)
|
||||||
|
|
||||||
for a in img.parent.findAll('a', href=True):
|
for a in img.parent.findAll('a', href=True):
|
||||||
art, href = a.string, a['href']
|
art, href = a.string, a['href']
|
||||||
if not re.search('\d+$', href) or not art or 'Preview Article' in art:
|
if not re.search('\d+$', href) or not art or 'Preview Article' in art:
|
||||||
continue
|
continue
|
||||||
articles.append({
|
articles.append({
|
||||||
'title':art, 'url':href, 'description':'',
|
'title':art, 'url':href, 'description':'',
|
||||||
'content':'', 'date':''
|
'content':'', 'date':''
|
||||||
})
|
})
|
||||||
sections.append((title, articles))
|
sections.append((title, articles))
|
||||||
|
|
||||||
img.parent.extract()
|
img.parent.extract()
|
||||||
|
|
||||||
for img in soup.findAll(src=re.compile('/label_')):
|
for img in soup.findAll(src=re.compile('/label_')):
|
||||||
process_section(img)
|
process_section(img)
|
||||||
|
|
||||||
return sections
|
return sections
|
||||||
|
|
||||||
|
|
||||||
def parse_index(self):
|
def parse_index(self):
|
||||||
soup = self.get_current_issue()
|
soup = self.get_current_issue()
|
||||||
if not soup:
|
if not soup:
|
||||||
@ -78,10 +82,10 @@ class Newsweek(BasicNewsRecipe):
|
|||||||
if match is not None:
|
if match is not None:
|
||||||
self.timefmt = strftime(' [%d %b, %Y]', time.strptime(match.group(1), '%y%m%d'))
|
self.timefmt = strftime(' [%d %b, %Y]', time.strptime(match.group(1), '%y%m%d'))
|
||||||
self.cover_url = small.replace('coversmall', 'coverlarge')
|
self.cover_url = small.replace('coversmall', 'coverlarge')
|
||||||
|
|
||||||
sections = self.get_sections(soup)
|
sections = self.get_sections(soup)
|
||||||
sections.insert(0, ('Main articles', []))
|
sections.insert(0, ('Main articles', []))
|
||||||
|
|
||||||
for tag in soup.findAll('h5'):
|
for tag in soup.findAll('h5'):
|
||||||
a = tag.find('a', href=True)
|
a = tag.find('a', href=True)
|
||||||
if a is not None:
|
if a is not None:
|
||||||
@ -97,8 +101,8 @@ class Newsweek(BasicNewsRecipe):
|
|||||||
if art['title'] and art['url']:
|
if art['title'] and art['url']:
|
||||||
sections[0][1].append(art)
|
sections[0][1].append(art)
|
||||||
return sections
|
return sections
|
||||||
|
|
||||||
|
|
||||||
def postprocess_html(self, soup, first_fetch):
|
def postprocess_html(self, soup, first_fetch):
|
||||||
divs = list(soup.findAll('div', 'pagination'))
|
divs = list(soup.findAll('div', 'pagination'))
|
||||||
if not divs:
|
if not divs:
|
||||||
@ -106,8 +110,8 @@ class Newsweek(BasicNewsRecipe):
|
|||||||
divs[0].extract()
|
divs[0].extract()
|
||||||
if len(divs) > 1:
|
if len(divs) > 1:
|
||||||
soup.find('body')['style'] = 'page-break-after:avoid'
|
soup.find('body')['style'] = 'page-break-after:avoid'
|
||||||
divs[1].extract()
|
divs[1].extract()
|
||||||
|
|
||||||
h1 = soup.find('h1')
|
h1 = soup.find('h1')
|
||||||
if h1:
|
if h1:
|
||||||
h1.extract()
|
h1.extract()
|
||||||
@ -116,12 +120,12 @@ class Newsweek(BasicNewsRecipe):
|
|||||||
else:
|
else:
|
||||||
soup.find('body')['style'] = 'page-break-before:always; page-break-after:avoid;'
|
soup.find('body')['style'] = 'page-break-before:always; page-break-after:avoid;'
|
||||||
return soup
|
return soup
|
||||||
|
|
||||||
def get_current_issue(self):
|
def get_current_issue(self):
|
||||||
#from urllib2 import urlopen # For some reason mechanize fails
|
#from urllib2 import urlopen # For some reason mechanize fails
|
||||||
#home = urlopen('http://www.newsweek.com').read()
|
#home = urlopen('http://www.newsweek.com').read()
|
||||||
soup = self.index_to_soup('http://www.newsweek.com')#BeautifulSoup(home)
|
soup = self.index_to_soup('http://www.newsweek.com')#BeautifulSoup(home)
|
||||||
img = soup.find('img', alt='Current Magazine')
|
img = soup.find('img', alt='Current Magazine')
|
||||||
if img and img.parent.has_key('href'):
|
if img and img.parent.has_key('href'):
|
||||||
return self.index_to_soup(img.parent['href'])
|
return self.index_to_soup(img.parent['href'])
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ from calibre import preferred_encoding, strftime
|
|||||||
|
|
||||||
|
|
||||||
class Template(MarkupTemplate):
|
class Template(MarkupTemplate):
|
||||||
|
|
||||||
def generate(self, *args, **kwargs):
|
def generate(self, *args, **kwargs):
|
||||||
if not kwargs.has_key('style'):
|
if not kwargs.has_key('style'):
|
||||||
kwargs['style'] = ''
|
kwargs['style'] = ''
|
||||||
@ -17,20 +17,20 @@ class Template(MarkupTemplate):
|
|||||||
for arg in args:
|
for arg in args:
|
||||||
if isinstance(arg, basestring) and not isinstance(arg, unicode):
|
if isinstance(arg, basestring) and not isinstance(arg, unicode):
|
||||||
arg = unicode(arg, 'utf-8', 'replace')
|
arg = unicode(arg, 'utf-8', 'replace')
|
||||||
|
|
||||||
return MarkupTemplate.generate(self, *args, **kwargs)
|
return MarkupTemplate.generate(self, *args, **kwargs)
|
||||||
|
|
||||||
class NavBarTemplate(Template):
|
class NavBarTemplate(Template):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Template.__init__(self, u'''\
|
Template.__init__(self, u'''\
|
||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml"
|
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xml:lang="en"
|
xml:lang="en"
|
||||||
xmlns:xi="http://www.w3.org/2001/XInclude"
|
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||||
xmlns:py="http://genshi.edgewall.org/"
|
xmlns:py="http://genshi.edgewall.org/"
|
||||||
|
|
||||||
>
|
>
|
||||||
<head>
|
<head>
|
||||||
<style py:if="extra_css" type="text/css">
|
<style py:if="extra_css" type="text/css">
|
||||||
@ -38,7 +38,7 @@ class NavBarTemplate(Template):
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="navbar" style="text-align:${'center' if center else 'left'};">
|
<div class="navbar calibre_rescale_70" style="text-align:${'center' if center else 'left'};">
|
||||||
<hr py:if="bottom" />
|
<hr py:if="bottom" />
|
||||||
<p py:if="bottom" style="text-align:left">
|
<p py:if="bottom" style="text-align:left">
|
||||||
This article was downloaded by <b>${__appname__}</b> from <a href="${url}">${url}</a>
|
This article was downloaded by <b>${__appname__}</b> from <a href="${url}">${url}</a>
|
||||||
@ -50,7 +50,7 @@ class NavBarTemplate(Template):
|
|||||||
<py:if test="art == num - 1 and not bottom">
|
<py:if test="art == num - 1 and not bottom">
|
||||||
| <a href="${prefix}../../feed_${str(feed+1)}/index.html">Next</a>
|
| <a href="${prefix}../../feed_${str(feed+1)}/index.html">Next</a>
|
||||||
</py:if>
|
</py:if>
|
||||||
| <a href="${prefix}../index.html#article_${str(art)}">Section menu</a>
|
| <a href="${prefix}../index.html#article_${str(art)}">Section menu</a>
|
||||||
<py:if test="two_levels">
|
<py:if test="two_levels">
|
||||||
| <a href="${prefix}../../index.html#feed_${str(feed)}">Main menu</a>
|
| <a href="${prefix}../../index.html#feed_${str(feed)}">Main menu</a>
|
||||||
</py:if>
|
</py:if>
|
||||||
@ -64,29 +64,29 @@ class NavBarTemplate(Template):
|
|||||||
</html>
|
</html>
|
||||||
''')
|
''')
|
||||||
|
|
||||||
def generate(self, bottom, feed, art, number_of_articles_in_feed,
|
def generate(self, bottom, feed, art, number_of_articles_in_feed,
|
||||||
two_levels, url, __appname__, prefix='', center=True,
|
two_levels, url, __appname__, prefix='', center=True,
|
||||||
extra_css=None):
|
extra_css=None):
|
||||||
if prefix and not prefix.endswith('/'):
|
if prefix and not prefix.endswith('/'):
|
||||||
prefix += '/'
|
prefix += '/'
|
||||||
return Template.generate(self, bottom=bottom, art=art, feed=feed,
|
return Template.generate(self, bottom=bottom, art=art, feed=feed,
|
||||||
num=number_of_articles_in_feed,
|
num=number_of_articles_in_feed,
|
||||||
two_levels=two_levels, url=url,
|
two_levels=two_levels, url=url,
|
||||||
__appname__=__appname__, prefix=prefix,
|
__appname__=__appname__, prefix=prefix,
|
||||||
center=center, extra_css=extra_css)
|
center=center, extra_css=extra_css)
|
||||||
|
|
||||||
|
|
||||||
class IndexTemplate(Template):
|
class IndexTemplate(Template):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Template.__init__(self, u'''\
|
Template.__init__(self, u'''\
|
||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml"
|
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xml:lang="en"
|
xml:lang="en"
|
||||||
xmlns:xi="http://www.w3.org/2001/XInclude"
|
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||||
xmlns:py="http://genshi.edgewall.org/"
|
xmlns:py="http://genshi.edgewall.org/"
|
||||||
|
|
||||||
>
|
>
|
||||||
<head>
|
<head>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||||
@ -99,15 +99,17 @@ class IndexTemplate(Template):
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1 class="calibre_recipe_title">${title}</h1>
|
<div class="calibre_rescale_100">
|
||||||
<p style="text-align:right">${date}</p>
|
<h1 class="calibre_recipe_title calibre_rescale_180">${title}</h1>
|
||||||
<ul class="calibre_feed_list">
|
<p style="text-align:right">${date}</p>
|
||||||
<py:for each="i, feed in enumerate(feeds)">
|
<ul class="calibre_feed_list">
|
||||||
<li py:if="feed" id="feed_${str(i)}">
|
<py:for each="i, feed in enumerate(feeds)">
|
||||||
<a class="feed" href="${'feed_%d/index.html'%i}">${feed.title}</a>
|
<li py:if="feed" id="feed_${str(i)}">
|
||||||
</li>
|
<a class="feed calibre_rescale_120" href="${'feed_%d/index.html'%i}">${feed.title}</a>
|
||||||
</py:for>
|
</li>
|
||||||
</ul>
|
</py:for>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
''')
|
''')
|
||||||
@ -118,19 +120,19 @@ class IndexTemplate(Template):
|
|||||||
date = strftime(datefmt)
|
date = strftime(datefmt)
|
||||||
return Template.generate(self, title=title, date=date, feeds=feeds,
|
return Template.generate(self, title=title, date=date, feeds=feeds,
|
||||||
extra_css=extra_css)
|
extra_css=extra_css)
|
||||||
|
|
||||||
|
|
||||||
class FeedTemplate(Template):
|
class FeedTemplate(Template):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Template.__init__(self, u'''\
|
Template.__init__(self, u'''\
|
||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml"
|
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xml:lang="en"
|
xml:lang="en"
|
||||||
xmlns:xi="http://www.w3.org/2001/XInclude"
|
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||||
xmlns:py="http://genshi.edgewall.org/"
|
xmlns:py="http://genshi.edgewall.org/"
|
||||||
|
|
||||||
>
|
>
|
||||||
<head>
|
<head>
|
||||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||||
@ -143,61 +145,64 @@ class FeedTemplate(Template):
|
|||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body style="page-break-before:always">
|
<body style="page-break-before:always">
|
||||||
<h2 class="calibre_feed_title">${feed.title}</h2>
|
<div class="calibre_rescale_100">
|
||||||
|
<h2 class="calibre_feed_title calibre_rescale_160">${feed.title}</h2>
|
||||||
<py:if test="getattr(feed, 'image', None)">
|
<py:if test="getattr(feed, 'image', None)">
|
||||||
<div class="calibre_feed_image">
|
<div class="calibre_feed_image">
|
||||||
<img alt="${feed.image_alt}" src="${feed.image_url}" />
|
<img alt="${feed.image_alt}" src="${feed.image_url}" />
|
||||||
</div>
|
</div>
|
||||||
</py:if>
|
</py:if>
|
||||||
<div class="calibre_feed_description" py:if="getattr(feed, 'description', None)">
|
<div class="calibre_feed_description calibre_rescale_80" py:if="getattr(feed, 'description', None)">
|
||||||
${feed.description}<br />
|
${feed.description}<br />
|
||||||
</div>
|
</div>
|
||||||
<ul class="calibre_article_list">
|
<ul class="calibre_article_list">
|
||||||
<py:for each="i, article in enumerate(feed.articles)">
|
<py:for each="i, article in enumerate(feed.articles)">
|
||||||
<li id="${'article_%d'%i}" py:if="getattr(article, 'downloaded', False)" style="padding-bottom:0.5em">
|
<li id="${'article_%d'%i}" py:if="getattr(article, 'downloaded',
|
||||||
<a class="article" href="${article.url}">${article.title}</a>
|
False)" style="padding-bottom:0.5em" class="calibre_rescale_100">
|
||||||
|
<a class="article calibre_rescale_120" href="${article.url}">${article.title}</a>
|
||||||
<span class="article_date">${article.localtime.strftime(" [%a, %d %b %H:%M]")}</span>
|
<span class="article_date">${article.localtime.strftime(" [%a, %d %b %H:%M]")}</span>
|
||||||
<div class="article_decription" py:if="article.summary">
|
<div class="article_decription calibre_rescale_70" py:if="article.summary">
|
||||||
${Markup(cutoff(article.text_summary))}
|
${Markup(cutoff(article.text_summary))}
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</py:for>
|
</py:for>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="navbar" style="text-align:center; font-family:monospace; font-size:8pt">
|
<div class="navbar calibre_rescale_70">
|
||||||
| <a href="../index.html">Up one level</a> |
|
| <a href="../index.html">Up one level</a> |
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
''')
|
''')
|
||||||
|
|
||||||
def generate(self, feed, cutoff, extra_css=None):
|
def generate(self, feed, cutoff, extra_css=None):
|
||||||
return Template.generate(self, feed=feed, cutoff=cutoff,
|
return Template.generate(self, feed=feed, cutoff=cutoff,
|
||||||
extra_css=extra_css)
|
extra_css=extra_css)
|
||||||
|
|
||||||
class EmbeddedContent(Template):
|
class EmbeddedContent(Template):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
Template.__init__(self, u'''\
|
Template.__init__(self, u'''\
|
||||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
|
||||||
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||||||
<html xmlns="http://www.w3.org/1999/xhtml"
|
<html xmlns="http://www.w3.org/1999/xhtml"
|
||||||
xml:lang="en"
|
xml:lang="en"
|
||||||
xmlns:xi="http://www.w3.org/2001/XInclude"
|
xmlns:xi="http://www.w3.org/2001/XInclude"
|
||||||
xmlns:py="http://genshi.edgewall.org/"
|
xmlns:py="http://genshi.edgewall.org/"
|
||||||
|
|
||||||
>
|
>
|
||||||
<head>
|
<head>
|
||||||
<title>${article.title}</title>
|
<title>${article.title}</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<h2>${article.title}</h2>
|
<h2>${article.title}</h2>
|
||||||
<div>
|
<div>
|
||||||
${Markup(article.content if len(article.content if article.content else '') > len(article.summary if article.summary else '') else article.summary)}
|
${Markup(article.content if len(article.content if article.content else '') > len(article.summary if article.summary else '') else article.summary)}
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
''')
|
''')
|
||||||
|
|
||||||
def generate(self, article):
|
def generate(self, article):
|
||||||
return Template.generate(self, article=article)
|
return Template.generate(self, article=article)
|
||||||
|
@ -53,9 +53,15 @@ def save_soup(soup, target):
|
|||||||
ns = BeautifulSoup('<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />')
|
ns = BeautifulSoup('<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />')
|
||||||
nm = ns.find('meta')
|
nm = ns.find('meta')
|
||||||
metas = soup.findAll('meta', content=True)
|
metas = soup.findAll('meta', content=True)
|
||||||
|
added = False
|
||||||
for meta in metas:
|
for meta in metas:
|
||||||
if 'charset' in meta.get('content', '').lower():
|
if 'charset' in meta.get('content', '').lower():
|
||||||
meta.replaceWith(nm)
|
meta.replaceWith(nm)
|
||||||
|
added = True
|
||||||
|
if not added:
|
||||||
|
head = soup.find('head')
|
||||||
|
if head is not None:
|
||||||
|
head.insert(0, nm)
|
||||||
|
|
||||||
selfdir = os.path.dirname(target)
|
selfdir = os.path.dirname(target)
|
||||||
|
|
||||||
@ -67,6 +73,7 @@ def save_soup(soup, target):
|
|||||||
|
|
||||||
html = unicode(soup)
|
html = unicode(soup)
|
||||||
with open(target, 'wb') as f:
|
with open(target, 'wb') as f:
|
||||||
|
idx = html.find('hoping')
|
||||||
f.write(html.encode('utf-8'))
|
f.write(html.encode('utf-8'))
|
||||||
|
|
||||||
class response(str):
|
class response(str):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user