News download system: Allow use of __future__ in recipes, and do not change line numbers of code in the recipe when compiling it

This commit is contained in:
Kovid Goyal 2012-02-22 12:06:00 +05:30
parent 1962d12290
commit 959afbd350

View File

@ -4,20 +4,14 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
''' '''
Builtin recipes. Builtin recipes.
''' '''
import re, imp, inspect, time, os import re, time, io
from calibre.web.feeds.news import BasicNewsRecipe, CustomIndexRecipe, \ from calibre.web.feeds.news import (BasicNewsRecipe, CustomIndexRecipe,
AutomaticNewsRecipe, CalibrePeriodical AutomaticNewsRecipe, CalibrePeriodical)
from calibre.ebooks.BeautifulSoup import BeautifulSoup from calibre.ebooks.BeautifulSoup import BeautifulSoup
from calibre.ptempfile import PersistentTemporaryDirectory
from calibre import __appname__, english_sort
from calibre.utils.config import JSONConfig from calibre.utils.config import JSONConfig
BeautifulSoup, time, english_sort
basic_recipes = (BasicNewsRecipe, AutomaticNewsRecipe, CustomIndexRecipe, basic_recipes = (BasicNewsRecipe, AutomaticNewsRecipe, CustomIndexRecipe,
CalibrePeriodical) CalibrePeriodical)
_tdir = None
_crep = 0
custom_recipes = JSONConfig('custom_recipes/index.json') custom_recipes = JSONConfig('custom_recipes/index.json')
@ -28,39 +22,33 @@ def custom_recipe_filename(id_, title):
def compile_recipe(src): def compile_recipe(src):
''' '''
Compile the code in src and return the first object that is a recipe or profile. Compile the code in src and return a recipe object, if found.
@param src: Python source code
@type src: string :param src: Python source code as bytestring or unicode object
@return: Recipe class or None, if no such class was found in C{src}
:return: Recipe class or None, if no such class was found in src
''' '''
global _tdir, _crep
if _tdir is None or not os.path.exists(_tdir):
_tdir = PersistentTemporaryDirectory('_recipes')
temp = os.path.join(_tdir, 'recipe%d.py'%_crep)
_crep += 1
if not isinstance(src, unicode): if not isinstance(src, unicode):
match = re.search(r'coding[:=]\s*([-\w.]+)', src[:200]) match = re.search(r'coding[:=]\s*([-\w.]+)', src[:200])
enc = match.group(1) if match else 'utf-8' enc = match.group(1) if match else 'utf-8'
src = src.decode(enc) src = src.decode(enc)
src = re.sub(r'from __future__.*', '', src) # Python complains if there is a coding declaration in a unicode string
f = open(temp, 'wb') src = re.sub(r'^#.*coding\s*[:=]\s*([-\w.]+)', '#', src, flags=re.MULTILINE)
src = 'from %s.web.feeds.news import BasicNewsRecipe, AutomaticNewsRecipe\n'%__appname__ + src # Translate newlines to \n
src = '# coding: utf-8\n' + src src = io.StringIO(src, newline=None).getvalue()
src = 'from __future__ import with_statement\n' + src
src = src.replace('from libprs500', 'from calibre').encode('utf-8') namespace = {
f.write(src) 'BasicNewsRecipe':BasicNewsRecipe,
f.close() 'AutomaticNewsRecipe':AutomaticNewsRecipe,
module = imp.find_module(os.path.splitext(os.path.basename(temp))[0], 'time':time, 're':re,
[os.path.dirname(temp)]) 'BeautifulSoup':BeautifulSoup
module = imp.load_module(os.path.splitext(os.path.basename(temp))[0], *module) }
classes = inspect.getmembers(module, exec src in namespace
lambda x : inspect.isclass(x) and \
issubclass(x, (BasicNewsRecipe,)) and \
x not in basic_recipes)
if not classes:
return None
return classes[0][1] for x in namespace.itervalues():
if (isinstance(x, type) and issubclass(x, BasicNewsRecipe) and x not
in basic_recipes):
return x
return None