Sync to trunk.

This commit is contained in:
John Schember 2010-01-13 18:13:38 -05:00
commit 086d08c9b6
16 changed files with 619 additions and 66 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 521 B

View File

@ -65,6 +65,9 @@ class TheAtlantic(BasicNewsRecipe):
date = self.tag_to_string(byline) if byline else ''
description = ''
self.log('\tFound article:', title)
self.log('\t\t', url)
articles.append({
'title':title,
'date':date,

View File

@ -0,0 +1,52 @@
__license__ = 'GPL v3'
__copyright__ = '2010, Darko Miletic <darko.miletic at gmail.com>'
'''
www.eluniversal.com
'''
from calibre import strftime
from calibre.web.feeds.recipes import BasicNewsRecipe
class ElUniversal(BasicNewsRecipe):
title = 'El Universal'
__author__ = 'Darko Miletic'
description = 'Noticias de Venezuela'
oldest_article = 2
max_articles_per_feed = 100
no_stylesheets = True
use_embedded_content = False
encoding = 'cp1252'
publisher = 'El Universal'
category = 'news, Caracas, Venezuela, world'
language = 'es'
cover_url = strftime('http://static.eluniversal.com/%Y/%m/%d/portada.jpg')
conversion_options = {
'comments' : description
,'tags' : category
,'language' : language
,'publisher' : publisher
}
keep_only_tags = [dict(name='div', attrs={'class':'Nota'})]
remove_tags = [
dict(name=['object','link','script','iframe'])
,dict(name='div',attrs={'class':'Herramientas'})
]
feeds = [
(u'Ultimas Noticias', u'http://www.eluniversal.com/rss/avances.xml' )
,(u'Economia' , u'http://www.eluniversal.com/rss/eco_avances.xml')
,(u'Internacionales' , u'http://www.eluniversal.com/rss/int_avances.xml')
,(u'Deportes' , u'http://www.eluniversal.com/rss/dep_avances.xml')
,(u'Cultura' , u'http://www.eluniversal.com/rss/cul_avances.xml')
,(u'Nacional y politica' , u'http://www.eluniversal.com/rss/pol_avances.xml')
,(u'Ciencia y tecnologia', u'http://www.eluniversal.com/rss/cyt_avances.xml')
,(u'Universo empresarial', u'http://www.eluniversal.com/rss/uni_avances.xml')
,(u'Caracas' , u'http://www.eluniversal.com/rss/ccs_avances.xml')
]
def print_version(self, url):
rp,sep,rest = url.rpartition('/')
return rp + sep + 'imp_' + rest

View File

@ -0,0 +1,31 @@
from calibre.web.feeds.news import BasicNewsRecipe
class NewsandObserver(BasicNewsRecipe):
title = u'News and Observer'
description = 'News from Raleigh, North Carolina'
language = 'en'
__author__ = 'Krittika Goyal'
oldest_article = 5 #days
max_articles_per_feed = 25
remove_stylesheets = True
remove_tags_before = dict(name='h1', attrs={'id':'story_headline'})
remove_tags_after = dict(name='div', attrs={'id':'story_text_remaining'})
remove_tags = [
dict(name='iframe'),
dict(name='div', attrs={'id':['right-rail', 'story_tools']}),
dict(name='ul', attrs={'class':'bold_tabs_nav'}),
]
feeds = [
('Cover', 'http://www.newsobserver.com/100/index.rss'),
('News', 'http://www.newsobserver.com/102/index.rss'),
('Politics', 'http://www.newsobserver.com/105/index.rss'),
('Business', 'http://www.newsobserver.com/104/index.rss'),
('Sports', 'http://www.newsobserver.com/103/index.rss'),
('College Sports', 'http://www.newsobserver.com/119/index.rss'),
('Lifestyles', 'http://www.newsobserver.com/106/index.rss'),
('Editorials', 'http://www.newsobserver.com/158/index.rss')]

View File

@ -1,6 +1,5 @@
# -*- coding: utf-8 -*-
from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import BeautifulSoup
class TheForce(BasicNewsRecipe):
title = u'The Force'
@ -21,11 +20,11 @@ class TheForce(BasicNewsRecipe):
#dict(name='div', attrs={'class':['pt-box-title', 'pt-box-content', 'blog-entry-footer', 'item-list', 'article-sub-meta']}),
#dict(name='div', attrs={'id':['block-td_search_160', 'block-cam_search_160']}),
#dict(name='table', attrs={'cellspacing':'0'}),
#dict(name='ul', attrs={'class':'articleTools'}),
#dict(name='ul', attrs={'class':'articleTools'}),
]
feeds = [
('The Force',
('The Force',
'http://www.theforce.net/outnews/tfnrdf.xml'),
]

View File

@ -0,0 +1,12 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1263409732(BasicNewsRecipe):
title = u'Think Progress'
description = u'A compilation of progressive articles on social and economic justice, healthy communities, media accountability, global and domestic security.'
__author__ = u'Xanthan Gum'
language = 'en'
oldest_article = 7
max_articles_per_feed = 100
feeds = [(u'News Articles', u'http://thinkprogress.org/feed/')]

View File

@ -48,7 +48,7 @@ class Plugin(object):
#: the plugins are run in order of decreasing priority
#: i.e. plugins with higher priority will be run first.
#: The highest possible priority is ``sys.maxint``.
#: Default pririty is 1.
#: Default priority is 1.
priority = 1
#: The earliest version of calibre this plugin requires
@ -226,4 +226,75 @@ class MetadataWriterPlugin(Plugin):
'''
pass
class CatalogPlugin(Plugin):
'''
A plugin that implements a catalog generator.
'''
#: Output file type for which this plugin should be run
#: For example: 'epub' or 'xml'
file_types = set([])
type = _('Catalog generator')
#: CLI parser options specific to this plugin, declared as namedtuple Option
#:
#: from collections import namedtuple
#: Option = namedtuple('Option', 'option, default, dest, help')
#: cli_options = [Option('--catalog-title',
#: default = 'My Catalog',
#: dest = 'catalog_title',
#: help = (_('Title of generated catalog. \nDefault:') + " '" +
#: '%default' + "'"))]
cli_options = []
def search_sort_db_as_dict(self, db, opts):
if opts.search_text:
db.search(opts.search_text)
if opts.sort_by:
# 2nd arg = ascending
db.sort(opts.sort_by, True)
return db.get_data_as_dict()
def get_output_fields(self, opts):
# Return a list of requested fields, with opts.sort_by first
all_fields = set(
['author_sort','authors','comments','cover','formats', 'id','isbn','pubdate','publisher','rating',
'series_index','series','size','tags','timestamp',
'title','uuid'])
fields = all_fields
if opts.fields != 'all':
# Make a list from opts.fields
requested_fields = set(opts.fields.split(','))
fields = list(all_fields & requested_fields)
else:
fields = list(all_fields)
fields.sort()
fields.insert(0,fields.pop(int(fields.index(opts.sort_by))))
return fields
def run(self, path_to_output, opts, db):
'''
Run the plugin. Must be implemented in subclasses.
It should generate the catalog in the format specified
in file_types, returning the absolute path to the
generated catalog file. If an error is encountered
it should raise an Exception and return None. The default
implementation simply returns None.
The generated catalog file should be created with the
:meth:`temporary_file` method.
:param path_to_output: Absolute path to the generated catalog file.
:param opts: A dictionary of keyword arguments
:param db: A LibraryDatabase2 object
:return: None
'''
# Default implementation does nothing
raise NotImplementedError('CatalogPlugin.generate_catalog() default '
'method, should be overridden in subclass')

View File

@ -421,7 +421,8 @@ from calibre.devices.binatone.driver import README
from calibre.devices.hanvon.driver import N516
from calibre.ebooks.metadata.fetch import GoogleBooks, ISBNDB, Amazon
plugins = [HTML2ZIP, PML2PMLZ, GoogleBooks, ISBNDB, Amazon]
from calibre.library.catalog import CSV_XML
plugins = [HTML2ZIP, PML2PMLZ, GoogleBooks, ISBNDB, Amazon, CSV_XML]
plugins += [
ComicInput,
EPUBInput,

View File

@ -5,8 +5,8 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import os, shutil, traceback, functools, sys, re
from contextlib import closing
from calibre.customize import Plugin, FileTypePlugin, MetadataReaderPlugin, \
MetadataWriterPlugin
from calibre.customize import Plugin, CatalogPlugin, FileTypePlugin, \
MetadataReaderPlugin, MetadataWriterPlugin
from calibre.customize.conversion import InputFormatPlugin, OutputFormatPlugin
from calibre.customize.profiles import InputProfile, OutputProfile
from calibre.customize.builtins import plugins as builtin_plugins
@ -300,6 +300,7 @@ def find_plugin(name):
if plugin.name == name:
return plugin
def input_format_plugins():
for plugin in _initialized_plugins:
if isinstance(plugin, InputFormatPlugin):
@ -328,6 +329,7 @@ def available_input_formats():
formats.add('zip'), formats.add('rar')
return formats
def output_format_plugins():
for plugin in _initialized_plugins:
if isinstance(plugin, OutputFormatPlugin):
@ -347,6 +349,27 @@ def available_output_formats():
formats.add(plugin.file_type)
return formats
def catalog_plugins():
for plugin in _initialized_plugins:
if isinstance(plugin, CatalogPlugin):
yield plugin
def available_catalog_formats():
formats = set([])
for plugin in catalog_plugins():
if not is_disabled(plugin):
for format in plugin.file_types:
formats.add(format)
return formats
def plugin_for_catalog_format(fmt):
for plugin in catalog_plugins():
if fmt.lower() in plugin.file_types:
return plugin
else:
return None
def device_plugins():
for plugin in _initialized_plugins:
if isinstance(plugin, DevicePlugin):

View File

@ -881,7 +881,10 @@ class Text(LRFStream):
open_containers.append(c)
if len(open_containers) > 0:
raise LRFParseError('Malformed text stream %s'%([i.name for i in open_containers if isinstance(i, Text.TextTag)],))
if len(open_containers) == 1:
s += u'</%s>'%(open_containers[0].name,)
else:
raise LRFParseError('Malformed text stream %s'%([i.name for i in open_containers if isinstance(i, Text.TextTag)],))
return s
def to_html(self):

View File

@ -7,34 +7,47 @@ __docformat__ = 'restructuredtext en'
'''
import textwrap, os
from PyQt4.QtCore import QCoreApplication, SIGNAL, QModelIndex, QUrl
from PyQt4.QtCore import QCoreApplication, SIGNAL, QModelIndex, QUrl, QTimer, Qt
from PyQt4.QtGui import QDialog, QPixmap, QGraphicsScene, QIcon, QDesktopServices
from calibre.gui2.dialogs.book_info_ui import Ui_BookInfo
from calibre.gui2 import dynamic
from calibre import fit_image
class BookInfo(QDialog, Ui_BookInfo):
def __init__(self, parent, view, row):
QDialog.__init__(self, parent)
Ui_BookInfo.__init__(self)
self.setupUi(self)
self.cover_pixmap = None
desktop = QCoreApplication.instance().desktop()
screen_height = desktop.availableGeometry().height() - 100
self.resize(self.size().width(), screen_height)
self.view = view
self.current_row = None
self.fit_cover.setChecked(dynamic.get('book_info_dialog_fit_cover',
False))
self.refresh(row)
self.connect(self.view.selectionModel(), SIGNAL('currentChanged(QModelIndex,QModelIndex)'), self.slave)
self.connect(self.next_button, SIGNAL('clicked()'), self.next)
self.connect(self.previous_button, SIGNAL('clicked()'), self.previous)
self.connect(self.text, SIGNAL('linkActivated(QString)'), self.open_book_path)
self.fit_cover.stateChanged.connect(self.toggle_cover_fit)
self.cover.resizeEvent = self.cover_view_resized
def toggle_cover_fit(self, state):
dynamic.set('book_info_dialog_fit_cover', self.fit_cover.isChecked())
self.resize_cover()
def cover_view_resized(self, event):
QTimer.singleShot(1, self.resize_cover)
def slave(self, current, previous):
row = current.row()
self.refresh(row)
def open_book_path(self, path):
if os.sep in unicode(path):
QDesktopServices.openUrl(QUrl('file:'+path))
@ -43,41 +56,53 @@ class BookInfo(QDialog, Ui_BookInfo):
path = self.view.model().db.format_abspath(self.current_row, format)
if path is not None:
QDesktopServices.openUrl(QUrl('file:'+path))
def next(self):
row = self.view.currentIndex().row()
ni = self.view.model().index(row+1, 0)
if ni.isValid():
self.view.setCurrentIndex(ni)
def previous(self):
row = self.view.currentIndex().row()
ni = self.view.model().index(row-1, 0)
if ni.isValid():
self.view.setCurrentIndex(ni)
def resize_cover(self):
if self.cover_pixmap is None:
return
self.setWindowIcon(QIcon(self.cover_pixmap))
self.scene = QGraphicsScene()
pixmap = self.cover_pixmap
if self.fit_cover.isChecked():
scaled, new_width, new_height = fit_image(pixmap.width(),
pixmap.height(), self.cover.size().width()-10,
self.cover.size().height()-10)
if scaled:
pixmap = pixmap.scaled(new_width, new_height,
Qt.KeepAspectRatio, Qt.SmoothTransformation)
self.scene.addPixmap(pixmap)
self.cover.setScene(self.scene)
def refresh(self, row):
if isinstance(row, QModelIndex):
row = row.row()
if row == self.current_row:
return
self.previous_button.setEnabled(False if row == 0 else True)
self.next_button.setEnabled(False if row == self.view.model().rowCount(QModelIndex())-1 else True)
self.next_button.setEnabled(False if row == self.view.model().rowCount(QModelIndex())-1 else True)
self.current_row = row
info = self.view.model().get_book_info(row)
self.setWindowTitle(info[_('Title')])
self.title.setText('<b>'+info.pop(_('Title')))
self.comments.setText(info.pop(_('Comments'), ''))
cdata = info.pop('cover', '')
pixmap = QPixmap.fromImage(cdata)
self.setWindowIcon(QIcon(pixmap))
self.scene = QGraphicsScene()
self.scene.addPixmap(pixmap)
self.cover.setScene(self.scene)
self.cover_pixmap = QPixmap.fromImage(cdata)
self.resize_cover()
rows = u''
self.text.setText('')
self.data = info
@ -94,4 +119,4 @@ class BookInfo(QDialog, Ui_BookInfo):
txt = info[key]
txt = u'<br />\n'.join(textwrap.wrap(txt, 120))
rows += u'<tr><td><b>%s:</b></td><td>%s</td></tr>'%(key, txt)
self.text.setText(u'<table>'+rows+'</table>')
self.text.setText(u'<table>'+rows+'</table>')

View File

@ -1,7 +1,8 @@
<ui version="4.0" >
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>BookInfo</class>
<widget class="QDialog" name="BookInfo" >
<property name="geometry" >
<widget class="QDialog" name="BookInfo">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
@ -9,70 +10,77 @@
<height>783</height>
</rect>
</property>
<property name="windowTitle" >
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QGridLayout" name="gridLayout" >
<item row="0" column="0" colspan="2" >
<widget class="QLabel" name="title" >
<property name="text" >
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="2">
<widget class="QLabel" name="title">
<property name="text">
<string>TextLabel</string>
</property>
<property name="alignment" >
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="1" column="0" >
<widget class="QGraphicsView" name="cover" />
<item row="1" column="0">
<widget class="QGraphicsView" name="cover"/>
</item>
<item row="1" column="1" >
<layout class="QVBoxLayout" name="verticalLayout" >
<item row="1" column="1">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="text" >
<property name="text" >
<widget class="QLabel" name="text">
<property name="text">
<string>TextLabel</string>
</property>
<property name="alignment" >
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="wordWrap" >
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox" >
<property name="title" >
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Comments</string>
</property>
<layout class="QGridLayout" >
<item row="0" column="0" >
<widget class="QTextBrowser" name="comments" />
<layout class="QGridLayout">
<item row="0" column="0">
<widget class="QTextBrowser" name="comments"/>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout" >
<widget class="QCheckBox" name="fit_cover">
<property name="text">
<string>Fit &amp;cover to view</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="previous_button" >
<property name="text" >
<widget class="QPushButton" name="previous_button">
<property name="text">
<string>&amp;Previous</string>
</property>
<property name="icon" >
<iconset resource="../../../../resources/images.qrc" >
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/previous.svg</normaloff>:/images/previous.svg</iconset>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="next_button" >
<property name="text" >
<widget class="QPushButton" name="next_button">
<property name="text">
<string>&amp;Next</string>
</property>
<property name="icon" >
<iconset resource="../../../../resources/images.qrc" >
<property name="icon">
<iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/next.svg</normaloff>:/images/next.svg</iconset>
</property>
</widget>
@ -84,7 +92,7 @@
</layout>
</widget>
<resources>
<include location="../../../../resources/images.qrc" />
<include location="../../../../resources/images.qrc"/>
</resources>
<connections/>
</ui>

View File

@ -148,7 +148,9 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
bad_perms.append(_file)
continue
_file = run_plugins_on_import(_file)
nfile = run_plugins_on_import(_file)
if nfile is not None:
_file = nfile
size = os.stat(_file).st_size
ext = os.path.splitext(_file)[1].lower().replace('.', '')
for row in range(self.formats.count()):

View File

@ -0,0 +1,209 @@
import os
from calibre.customize import CatalogPlugin
class CSV_XML(CatalogPlugin):
'CSV/XML catalog generator'
from collections import namedtuple
Option = namedtuple('Option', 'option, default, dest, help')
name = 'Catalog_CSV_XML'
description = 'CSV/XML catalog generator'
supported_platforms = ['windows', 'osx', 'linux']
author = 'Greg Riker'
version = (1, 0, 0)
file_types = set(['csv','xml'])
cli_options = [
Option('--fields',
default = 'all',
dest = 'fields',
help = _('The fields to output when cataloging books in the '
'database. Should be a comma-separated list of fields.\n'
'Available fields: all, author_sort, authors, comments, '
'cover, formats, id, isbn, pubdate, publisher, rating, '
'series_index, series, size, tags, timestamp, title, uuid.\n'
"Default: '%default'\n"
"Applies to: CSV, XML output formats")),
Option('--sort-by',
default = 'id',
dest = 'sort_by',
help = _('Output field to sort on.\n'
'Available fields: author_sort, id, rating, size, timestamp, title.\n'
"Default: '%default'\n"
"Applies to: CSV, XML output formats"))]
def run(self, path_to_output, opts, db):
from calibre.utils.logging import Log
log = Log()
self.fmt = path_to_output[path_to_output.rfind('.') + 1:]
if opts.verbose:
log("%s:run" % self.name)
log(" path_to_output: %s" % path_to_output)
log(" Output format: %s" % self.fmt)
# Display opts
opts_dict = vars(opts)
keys = opts_dict.keys()
keys.sort()
log(" opts:")
for key in keys:
log(" %s: %s" % (key, opts_dict[key]))
# Get the sorted, filtered database as a dictionary
data = self.search_sort_db_as_dict(db, opts)
if not len(data):
log.error("\nNo matching database entries for search criteria '%s'" % opts.search_text)
raise SystemExit(1)
# Get the requested output fields as a list
fields = self.get_output_fields(opts)
if self.fmt == 'csv':
outfile = open(path_to_output, 'w')
# Output the field headers
outfile.write('%s\n' % ','.join(fields))
# Output the entry fields
for entry in data:
outstr = ''
for (x, field) in enumerate(fields):
item = entry[field]
if field in ['authors','tags','formats']:
item = ', '.join(item)
if x < len(fields) - 1:
if item is not None:
outstr += '"%s",' % str(item).replace('"','""')
else:
outstr += '"",'
else:
if item is not None:
outstr += '"%s"\n' % str(item).replace('"','""')
else:
outstr += '""\n'
outfile.write(outstr)
outfile.close()
elif self.fmt == 'xml':
from lxml import etree
from calibre.utils.genshi.template import MarkupTemplate
PY_NAMESPACE = "http://genshi.edgewall.org/"
PY = "{%s}" % PY_NAMESPACE
NSMAP = {'py' : PY_NAMESPACE}
root = etree.Element('calibredb', nsmap=NSMAP)
py_for = etree.SubElement(root, PY + 'for', each="record in data")
record = etree.SubElement(py_for, 'record')
if 'id' in fields:
record_child = etree.SubElement(record, 'id')
record_child.set(PY + "if", "record['id']")
record_child.text = "${record['id']}"
if 'uuid' in fields:
record_child = etree.SubElement(record, 'uuid')
record_child.set(PY + "if", "record['uuid']")
record_child.text = "${record['uuid']}"
if 'title' in fields:
record_child = etree.SubElement(record, 'title')
record_child.set(PY + "if", "record['title']")
record_child.text = "${record['title']}"
if 'authors' in fields:
record_child = etree.SubElement(record, 'authors', sort="${record['author_sort']}")
record_subchild = etree.SubElement(record_child, PY + 'for', each="author in record['authors']")
record_subsubchild = etree.SubElement(record_subchild, 'author')
record_subsubchild.text = '$author'
if 'publisher' in fields:
record_child = etree.SubElement(record, 'publisher')
record_child.set(PY + "if", "record['publisher']")
record_child.text = "${record['publisher']}"
if 'rating' in fields:
record_child = etree.SubElement(record, 'rating')
record_child.set(PY + "if", "record['rating']")
record_child.text = "${record['rating']}"
if 'date' in fields:
record_child = etree.SubElement(record, 'date')
record_child.set(PY + "if", "record['date']")
record_child.text = "${record['date']}"
if 'pubdate' in fields:
record_child = etree.SubElement(record, 'pubdate')
record_child.set(PY + "if", "record['pubdate']")
record_child.text = "${record['pubdate']}"
if 'size' in fields:
record_child = etree.SubElement(record, 'size')
record_child.set(PY + "if", "record['size']")
record_child.text = "${record['size']}"
if 'tags' in fields:
# <tags py:if="record['tags']">
# <py:for each="tag in record['tags']">
# <tag>$tag</tag>
# </py:for>
# </tags>
record_child = etree.SubElement(record, 'tags')
record_child.set(PY + "if", "record['tags']")
record_subchild = etree.SubElement(record_child, PY + 'for', each="tag in record['tags']")
record_subsubchild = etree.SubElement(record_subchild, 'tag')
record_subsubchild.text = '$tag'
if 'comments' in fields:
record_child = etree.SubElement(record, 'comments')
record_child.set(PY + "if", "record['comments']")
record_child.text = "${record['comments']}"
if 'series' in fields:
# <series py:if="record['series']" index="${record['series_index']}">
# ${record['series']}
# </series>
record_child = etree.SubElement(record, 'series')
record_child.set(PY + "if", "record['series']")
record_child.set('index', "${record['series_index']}")
record_child.text = "${record['series']}"
if 'isbn' in fields:
record_child = etree.SubElement(record, 'isbn')
record_child.set(PY + "if", "record['isbn']")
record_child.text = "${record['isbn']}"
if 'cover' in fields:
# <cover py:if="record['cover']">
# ${record['cover'].replace(os.sep, '/')}
# </cover>
record_child = etree.SubElement(record, 'cover')
record_child.set(PY + "if", "record['cover']")
record_child.text = "${record['cover']}"
if 'formats' in fields:
# <formats py:if="record['formats']">
# <py:for each="path in record['formats']">
# <format>${path.replace(os.sep, '/')}</format>
# </py:for>
# </formats>
record_child = etree.SubElement(record, 'formats')
record_child.set(PY + "if", "record['formats']")
record_subchild = etree.SubElement(record_child, PY + 'for', each="path in record['formats']")
record_subsubchild = etree.SubElement(record_subchild, 'format')
record_subsubchild.text = "${path.replace(os.sep, '/')}"
outfile = open(path_to_output, 'w')
template = MarkupTemplate(etree.tostring(root, xml_declaration=True,
encoding="UTF-8", pretty_print=True))
outfile.write(template.generate(data=data, os=os).render('xml'))
outfile.close()
return None

View File

@ -583,8 +583,120 @@ def command_export(args, dbpath):
do_export(get_db(dbpath, opts), ids, dir, opts)
return 0
# GR additions
def catalog_option_parser(args):
from calibre.customize.ui import available_catalog_formats, plugin_for_catalog_format
from calibre.utils.logging import Log
def add_plugin_parser_options(fmt, parser, log):
# Fetch the extension-specific CLI options from the plugin
plugin = plugin_for_catalog_format(fmt)
for option in plugin.cli_options:
parser.add_option(option.option,
default=option.default,
dest=option.dest,
help=option.help)
return plugin
def print_help(parser, log):
help = parser.format_help().encode(preferred_encoding, 'replace')
log(help)
def validate_command_line(parser, args, log):
# calibredb catalog path/to/destination.[epub|csv|xml|...] [options]
# Validate form
if not len(args) or args[0].startswith('-'):
print_help(parser, log)
log.error("\n\nYou must specify a catalog output file of the form 'path/to/destination.extension'\n"
"To review options for an output format, type 'calibredb catalog <.extension> --help'\n"
"For example, 'calibredb catalog .xml --help'\n")
raise SystemExit(1)
# Validate plugin exists for specified output format
output = os.path.abspath(args[0])
file_extension = output[output.rfind('.') + 1:].lower()
if not file_extension in available_catalog_formats():
print_help(parser, log)
log.error("No catalog plugin available for extension '%s'.\n" % file_extension +
"Catalog plugins available for %s\n" % ', '.join(available_catalog_formats()) )
raise SystemExit(1)
return output, file_extension
# Entry point
log = Log()
parser = get_parser(_(
'''
%prog catalog /path/to/destination.(csv|epub|mobi|xml ...) [options]
Export a catalog in format specified by path/to/destination extension.
Options control how entries are displayed in the generated catalog ouput.
'''))
# Confirm that a plugin handler exists for specified output file extension
# Will raise SystemExit(1) if no plugin matching file_extension
output, fmt = validate_command_line(parser, args, log)
# Add options common to all catalog plugins
parser.add_option('-s', '--search', default=None, dest='search_text',
help=_("Filter the results by the search query. For the format of the search query, please see the search-related documentation in the User Manual.\n"+
"Default: no filtering"))
parser.add_option('-v','--verbose', default=False, action='store_true',
dest='verbose',
help=_('Show detailed output information. Useful for debugging'))
# Add options specific to fmt plugin
plugin = add_plugin_parser_options(fmt, parser, log)
# Merge options from GUI Preferences
'''
from calibre.library.save_to_disk import config
c = config()
for pref in ['asciiize', 'update_metadata', 'write_opf', 'save_cover']:
opt = c.get_option(pref)
switch = '--dont-'+pref.replace('_', '-')
parser.add_option(switch, default=True, action='store_false',
help=opt.help+' '+_('Specifying this switch will turn '
'this behavior off.'), dest=pref)
for pref in ['timefmt', 'template', 'formats']:
opt = c.get_option(pref)
switch = '--'+pref
parser.add_option(switch, default=opt.default,
help=opt.help, dest=pref)
for pref in ('replace_whitespace', 'to_lowercase'):
opt = c.get_option(pref)
switch = '--'+pref.replace('_', '-')
parser.add_option(switch, default=False, action='store_true',
help=opt.help)
'''
return parser, plugin, log
def command_catalog(args, dbpath):
parser, plugin, log = catalog_option_parser(args)
opts, args = parser.parse_args(sys.argv[1:])
if len(args) < 2:
parser.print_help()
print
print >>sys.stderr, _('Error: You must specify a catalog output file')
return 1
if opts.verbose:
log("library.cli:command_catalog dispatching to plugin %s" % plugin.name)
plugin.run(args[1], opts, get_db(dbpath, opts))
return 0
# end of GR additions
COMMANDS = ('list', 'add', 'remove', 'add_format', 'remove_format',
'show_metadata', 'set_metadata', 'export')
'show_metadata', 'set_metadata', 'export', 'catalog')
def option_parser():

View File

@ -270,11 +270,13 @@ Why does |app| show only some of my fonts on OS X?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There can be several causes for this:
* **Any windows version**: Try running it as Administrator (Right click on the icon ans select "Run as Administrator")
* **Any windows version**: If this happens during an initial run of calibre, try deleting the folder you chose for your ebooks and restarting calibre.
* If you get an error about a Python function terminating unexpectedly after upgrading calibre, first uninstall calibre, then delete the folders (if they exists)
:file:`C:\\Program Files\\Calibre` and :file:`C:\\Program Files\\Calibre2`. Now re-install and you should be fine.
* If you get an error in the welcome wizard on an initial run of calibre, try choosing a folder like :file:`C:\\library` as the calibre library (calibre sometimes
has trouble with library locations if the path contains non-English characters, or only numbers, etc.)
* Try running it as Administrator (Right click on the icon and select "Run as Administrator")
* **Windows Vista**: If the folder :file:`C:\\Users\\Your User Name\\AppData\\Local\\VirtualStore\\Program Files\\calibre` exists, delete it. Uninstall |app|. Reboot. Re-install.
* **Any windows version**: Search your computer for a folder named :file:`_ipython`. Delete it and try again.
* **Any windows version**: Try disabling any antivirus program you have running and see if that fixes it. Also try diabling any firewall software that prevents connections to the local computer.
* **Any windows version**: Try disabling any antivirus program you have running and see if that fixes it. Also try disabling any firewall software that prevents connections to the local computer.
If it still wont launch, start a command prompt (press the windows key and R; then type :command:`cmd.exe` in the Run dialog that appears). At the command prompt type the following command and press Enter::