Sync to trunk.

This commit is contained in:
John Schember 2011-03-26 17:19:10 -04:00
commit 1df045e236
8 changed files with 188 additions and 59 deletions

View File

@ -19,6 +19,13 @@
# new recipes:
# - title:
- version: 0.7.52
date: 2011-03-25
bug fixes:
- title: "Fixes a typo in 0.7.51 that broke the downloading of some news. Apologies."
tickets: [742840]
- version: 0.7.51
date: 2011-03-25

View File

@ -1,7 +1,7 @@
from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1291143841(BasicNewsRecipe):
title = u'Poughkeepsipe Journal'
title = u'Poughkeepsie Journal'
language = 'en'
__author__ = 'weebl'
oldest_article = 7

View File

@ -14,7 +14,7 @@ from setup.build_environment import HOST, PROJECT
BASE_RSYNC = ['rsync', '-avz', '--delete']
EXCLUDES = []
for x in [
'src/calibre/plugins', 'src/calibre/manual', 'src/calibre/trac', 'recipes',
'src/calibre/plugins', 'src/calibre/manual', 'src/calibre/trac',
'.bzr', '.build', '.svn', 'build', 'dist', 'imgsrc', '*.pyc', '*.pyo', '*.swp',
'*.swo', 'format_docs']:
EXCLUDES.extend(['--exclude', x])

View File

@ -2,7 +2,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
__appname__ = 'calibre'
__version__ = '0.7.51'
__version__ = '0.7.52'
__author__ = "Kovid Goyal <kovid@kovidgoyal.net>"
import re, importlib

View File

@ -4,9 +4,22 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import os, sys, zipfile, importlib
from calibre.constants import numeric_version
from calibre.constants import numeric_version, iswindows, isosx
from calibre.ptempfile import PersistentTemporaryFile
platform = 'linux'
if iswindows:
platform = 'windows'
elif isosx:
platform = 'osx'
class PluginNotFound(ValueError):
pass
class InvalidPlugin(ValueError):
pass
class Plugin(object): # {{{
'''

View File

@ -2,17 +2,16 @@ from __future__ import with_statement
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import os, shutil, traceback, functools, sys, re
from contextlib import closing
import os, shutil, traceback, functools, sys
from calibre.customize import Plugin, CatalogPlugin, FileTypePlugin, \
MetadataReaderPlugin, MetadataWriterPlugin, \
InterfaceActionBase as InterfaceAction, \
PreferencesPlugin
from calibre.customize import (CatalogPlugin, FileTypePlugin, PluginNotFound,
MetadataReaderPlugin, MetadataWriterPlugin,
InterfaceActionBase as InterfaceAction,
PreferencesPlugin, platform, InvalidPlugin)
from calibre.customize.conversion import InputFormatPlugin, OutputFormatPlugin
from calibre.customize.zipplugin import loader
from calibre.customize.profiles import InputProfile, OutputProfile
from calibre.customize.builtins import plugins as builtin_plugins
from calibre.constants import numeric_version as version, iswindows, isosx
from calibre.devices.interface import DevicePlugin
from calibre.ebooks.metadata import MetaInformation
from calibre.ebooks.metadata.covers import CoverDownload
@ -22,14 +21,6 @@ from calibre.utils.config import make_config_dir, Config, ConfigProxy, \
from calibre.ebooks.epub.fix import ePubFixer
from calibre.ebooks.metadata.sources.base import Source
platform = 'linux'
if iswindows:
platform = 'windows'
elif isosx:
platform = 'osx'
from zipfile import ZipFile
def _config():
c = Config('customize')
c.add_opt('plugins', default={}, help=_('Installed plugins'))
@ -42,11 +33,6 @@ def _config():
config = _config()
class InvalidPlugin(ValueError):
pass
class PluginNotFound(ValueError):
pass
def find_plugin(name):
for plugin in _initialized_plugins:
@ -60,38 +46,7 @@ def load_plugin(path_to_zip_file): # {{{
:return: A :class:`Plugin` instance.
'''
#print 'Loading plugin from', path_to_zip_file
if not os.access(path_to_zip_file, os.R_OK):
raise PluginNotFound
with closing(ZipFile(path_to_zip_file)) as zf:
for name in zf.namelist():
if name.lower().endswith('plugin.py'):
locals = {}
raw = zf.read(name)
lines, encoding = raw.splitlines(), 'utf-8'
cr = re.compile(r'coding[:=]\s*([-\w.]+)')
raw = []
for l in lines[:2]:
match = cr.search(l)
if match is not None:
encoding = match.group(1)
else:
raw.append(l)
raw += lines[2:]
raw = '\n'.join(raw)
raw = raw.decode(encoding)
raw = re.sub('\r\n', '\n', raw)
exec raw in locals
for x in locals.values():
if isinstance(x, type) and issubclass(x, Plugin) and \
x.name != 'Trivial Plugin':
if x.minimum_calibre_version > version or \
platform not in x.supported_platforms:
continue
return x
raise InvalidPlugin(_('No valid plugin found in ')+path_to_zip_file)
return loader.load(path_to_zip_file)
# }}}

View File

@ -0,0 +1,154 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
from __future__ import (unicode_literals, division, absolute_import,
print_function)
__license__ = 'GPL v3'
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import os, zipfile, posixpath, importlib, threading, re
from collections import OrderedDict
from calibre.customize import (Plugin, numeric_version, platform,
InvalidPlugin, PluginNotFound)
# PEP 302 based plugin loading mechanism, works around the bug in zipimport in
# python 2.x that prevents importing from zip files in locations whose paths
# have non ASCII characters
class PluginLoader(object):
'''
The restrictions that a zip file must obey to be a valid calibre plugin
are:
* The .py file that defines the main plugin class must have a name
that:
* Ends in plugin.py
* Is a valid python identifier (contains only English alphabets,
underscores and numbers and starts with an alphabet). This
applies to the file name minus the .py extension, obviously.
* Try to make this name as distinct as possible, as it will be
put into a global namespace of all plugins.
* The zip file must contain a .py file that defines the main plugin
class at the top level. That is, it must not be in a subdirectory.
The filename must follow the restrictions outlined above.
'''
def __init__(self):
self.loaded_plugins = {}
self._lock = threading.RLock()
self._identifier_pat = re.compile(r'[a-zA-Z][_0-9a-zA-Z]*')
def load(self, path_to_zip_file):
if not os.access(path_to_zip_file, os.R_OK):
raise PluginNotFound('Cannot access %r'%path_to_zip_file)
with zipfile.ZipFile(path_to_zip_file) as zf:
plugin_name = self._locate_code(zf, path_to_zip_file)
try:
ans = None
m = importlib.import_module(
'calibre_plugins.%s.__init__'%plugin_name)
for obj in m.__dict__.itervalues():
if isinstance(obj, type) and issubclass(obj, Plugin) and \
obj.name != 'Trivial Plugin':
ans = obj
break
if ans is None:
raise InvalidPlugin('No plugin class found in %r:%r'%(
path_to_zip_file, plugin_name))
if ans.minimum_calibre_version < numeric_version:
raise InvalidPlugin(
'The plugin at %r needs a version of calibre >= %r' %
(path_to_zip_file, '.'.join(ans.minimum_calibre_version)))
if platform not in ans.supported_platforms:
raise InvalidPlugin(
'The plugin at %r cannot be used on %s' %
(path_to_zip_file, platform))
return ans
except:
with self._lock:
del self.loaded_plugins[plugin_name]
raise
def _locate_code(self, zf, path_to_zip_file):
names = [x if isinstance(x, unicode) else x.decode('utf-8') for x in
zf.namelist()]
names = [x[1:] if x[0] == '/' else x for x in names]
plugin_name = None
for name in names:
name, ext = posixpath.splitext(name)
if name.startswith('plugin-import-name-') and ext == '.txt':
plugin_name = name.rpartition('-')[-1]
if plugin_name is None:
c = 0
while True:
c += 1
plugin_name = 'dummy%d'%c
if plugin_name not in self.loaded_plugins:
break
else:
if plugin_name in self.loaded_plugins:
raise InvalidPlugin((
'The plugin in %r uses an import name %r that is already'
' used by another plugin') % (path_to_zip_file, plugin_name))
if self._identifier_pat.match(plugin_name) is None:
raise InvalidPlugin((
'The plugin at %r uses an invalid import name: %r' %
(path_to_zip_file, plugin_name)))
pynames = [x for x in names if x.endswith('.py')]
candidates = [posixpath.dirname(x) for x in pynames if
x.endswith('/__init__.py')]
candidates.sort(key=lambda x: x.count('/'))
valid_packages = set()
for candidate in candidates:
parts = candidate.split('/')
parent = '.'.join(parts[:-1])
if parent and parent not in valid_packages:
continue
valid_packages.add('.'.join(parts))
names = OrderedDict()
for candidate in names:
parts = posixpath.splitext(candidate)[0].split('/')
package = '.'.join(parts[:-1])
if package and package not in valid_packages:
continue
name = '.'.join(parts)
names[name] = zf.getinfo(candidate)
# Legacy plugins
if '__init__' not in names:
for name in list(names.iterkeys()):
if '.' not in name and name.endswith('plugin'):
names['__init__'] = names[name]
break
if '__init__' not in names:
raise InvalidPlugin(('The plugin in %r is invalid. It does not '
'contain a top-level __init__.py file')
% path_to_zip_file)
with self._lock:
self.loaded_plugins[plugin_name] = (path_to_zip_file, names)
return plugin_name
loader = PluginLoader()

View File

@ -4,9 +4,9 @@
#
msgid ""
msgstr ""
"Project-Id-Version: calibre 0.7.51\n"
"POT-Creation-Date: 2011-03-25 12:23+MDT\n"
"PO-Revision-Date: 2011-03-25 12:23+MDT\n"
"Project-Id-Version: calibre 0.7.52\n"
"POT-Creation-Date: 2011-03-25 19:03+MDT\n"
"PO-Revision-Date: 2011-03-25 19:03+MDT\n"
"Last-Translator: Automatically generated\n"
"Language-Team: LANGUAGE\n"
"MIME-Version: 1.0\n"