mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
[Sync] Sync with trunk. Revision 9330
This commit is contained in:
commit
a62d9f3ab9
@ -19,6 +19,68 @@
|
||||
# new recipes:
|
||||
# - title:
|
||||
|
||||
- version: 0.8.3
|
||||
date: 2011-05-27
|
||||
|
||||
new features:
|
||||
- title: "Allow the coloring of columns in the book list."
|
||||
description: "You can either create a custom column with a fixed set of values and assign a color to each value, or you can use the calibre template language to color any column in arbitrarily powerful ways. For example, you can have the title appear in red if the book has a particular tag."
|
||||
type: major
|
||||
|
||||
- title: "Support for the Nook Simple Reader"
|
||||
|
||||
- title: "Get Books, new stores: Virtualo, lulu.net"
|
||||
|
||||
- title: "A store chooser dialog for Get Books (click the little preferences icon at the bottom of the Get Books screen)."
|
||||
|
||||
- title: "Add a merge_lists, and, or, not template functions to the calibre template language"
|
||||
|
||||
- title: "EPUB Output: Change any white-space:pre declarations in the CSS to pre-wrap to accomodate readers that cannot scroll horizontally."
|
||||
tickets: [786722]
|
||||
|
||||
- title: "Windows installer: Remember and use previous installation folder when upgrading. Note that this will work for future upgrades, after this one."
|
||||
|
||||
bug fixes:
|
||||
- title: "MOBI Output: Fix hidden tags with id attributes also hiding their trailing text"
|
||||
tickets: [788570]
|
||||
|
||||
- title: "Fix switching from one news source to another via a search not saving changes to the scheduling of the first source"
|
||||
tickets: [774849]
|
||||
|
||||
- title: "Dont allow user to use non email usernames when setting up Hotmail or Gmail accounts"
|
||||
|
||||
- title: "Amazon metadata download: Use separate identifiers for country specific downloads so that the links to Amazon in the Book details panel work when downloading metadata from country specific amazon websites."
|
||||
tickets: [786146]
|
||||
|
||||
- title: "Nicer error message when user attempts to set title/author via Edit metadata dialog and one of the files is open in another program."
|
||||
|
||||
- title: "Fix {id} not working in send to device templates"
|
||||
|
||||
- title: "Windows: If creating a bytestring temp dir fails, create a unicode one and hope the rest of calibre can handle it"
|
||||
|
||||
- title: "Get Books: Fix some results from Amazon missing."
|
||||
tickets: [785962]
|
||||
|
||||
improved recipes:
|
||||
- Kathermini
|
||||
- Faz.net
|
||||
- The Washington Post
|
||||
- El Mundo
|
||||
- Marca
|
||||
- The Nation
|
||||
|
||||
new recipes:
|
||||
- title: Various German news sources
|
||||
author: schuster
|
||||
|
||||
- title: "George R. R. Martin's Blog"
|
||||
author: Darko Miletic
|
||||
|
||||
- title: "Focus (DE) and National Geographic"
|
||||
auhtor: Anonymous
|
||||
|
||||
|
||||
|
||||
- version: 0.8.2
|
||||
date: 2011-05-20
|
||||
|
||||
|
42
recipes/aachener_nachrichten.recipe
Normal file
42
recipes/aachener_nachrichten.recipe
Normal file
@ -0,0 +1,42 @@
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
class AdvancedUserRecipe(BasicNewsRecipe):
|
||||
|
||||
title = u'Aachener Nachrichten'
|
||||
__author__ = 'schuster'
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 100
|
||||
use_embedded_content = False
|
||||
language = 'de'
|
||||
remove_javascript = True
|
||||
cover_url = 'http://www.an-online.de/einwaage/images/an_logo.png'
|
||||
masthead_url = 'http://www.an-online.de/einwaage/images/an_logo.png'
|
||||
extra_css = '''
|
||||
.fliesstext_detail:{margin-bottom:10%;}
|
||||
.headline_1:{margin-bottom:25%;}
|
||||
b{font-family:Arial,Helvetica,sans-serif; font-weight:200;font-size:large;}
|
||||
a{font-family:Arial,Helvetica,sans-serif; font-weight:400;font-size:large;}
|
||||
ll{font-family:Arial,Helvetica,sans-serif; font-weight:100;font-size:large;}
|
||||
h4{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;}
|
||||
img {min-width:300px; max-width:600px; min-height:300px; max-height:800px}
|
||||
dd{font-family:Arial,Helvetica,sans-serif;font-size:large;}
|
||||
body{font-family:Helvetica,Arial,sans-serif;font-size:small;}
|
||||
'''
|
||||
|
||||
|
||||
|
||||
keep_only_tags = [
|
||||
dict(name='span', attrs={'class':['fliesstext_detail', 'headline_1', 'autor_detail']}),
|
||||
dict(id=['header-logo'])
|
||||
]
|
||||
|
||||
feeds = [(u'Euregio', u'http://www.an-online.de/an/rss/Euregio.xml'),
|
||||
(u'Aachen', u'http://www.an-online.de/an/rss/Aachen.xml'),
|
||||
(u'Nordkreis', u'http://www.an-online.de/an/rss/Nordkreis.xml'),
|
||||
(u'Düren', u'http://www.an-online.de/an/rss/Dueren.xml'),
|
||||
(u'Eiffel', u'http://www.an-online.de/an/rss/Eifel.xml'),
|
||||
(u'Eschweiler', u'http://www.an-online.de/an/rss/Eschweiler.xml'),
|
||||
(u'Geilenkirchen', u'http://www.an-online.de/an/rss/Geilenkirchen.xml'),
|
||||
(u'Heinsberg', u'http://www.an-online.de/an/rss/Heinsberg.xml'),
|
||||
(u'Jülich', u'http://www.an-online.de/an/rss/Juelich.xml'),
|
||||
(u'Stolberg', u'http://www.an-online.de/an/rss/Stolberg.xml'),
|
||||
(u'Ratgebenr', u'http://www.an-online.de/an/rss/Ratgeber.xml')]
|
@ -1,38 +1,63 @@
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
class AdvancedUserRecipe1303841067(BasicNewsRecipe):
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008-2011, Kovid Goyal <kovid at kovidgoyal.net>, Darko Miletic <darko at gmail.com>'
|
||||
'''
|
||||
Profile to download FAZ.NET
|
||||
'''
|
||||
|
||||
title = u'Faz.net'
|
||||
__author__ = 'schuster'
|
||||
remove_tags = [dict(attrs={'class':['right', 'ArrowLinkRight', 'ModulVerlagsInfo', 'left', 'Head']}),
|
||||
dict(id=['BreadCrumbs', 'tstag', 'FazFooterPrint']),
|
||||
dict(name=['script', 'noscript', 'style'])]
|
||||
oldest_article = 2
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class FazNet(BasicNewsRecipe):
|
||||
title = 'FAZ.NET'
|
||||
__author__ = 'Kovid Goyal, Darko Miletic'
|
||||
description = 'Frankfurter Allgemeine Zeitung'
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
publisher = 'Frankfurter Allgemeine Zeitung GmbH'
|
||||
category = 'news, politics, Germany'
|
||||
use_embedded_content = False
|
||||
language = 'de'
|
||||
|
||||
max_articles_per_feed = 30
|
||||
no_stylesheets = True
|
||||
encoding = 'utf-8'
|
||||
remove_javascript = True
|
||||
cover_url = 'http://www.faz.net/f30/Images/Logos/logo.gif'
|
||||
|
||||
def print_version(self, url):
|
||||
return url.replace('.html', '~Afor~Eprint.html')
|
||||
|
||||
|
||||
|
||||
feeds = [(u'Politik', u'http://www.faz.net/s/RubA24ECD630CAE40E483841DB7D16F4211/Tpl~Epartner~SRss_.xml'),
|
||||
(u'Wirtschaft', u'http://www.faz.net/s/RubC9401175958F4DE28E143E68888825F6/Tpl~Epartner~SRss_.xml'),
|
||||
(u'Feuilleton', u'http://www.faz.net/s/RubCC21B04EE95145B3AC877C874FB1B611/Tpl~Epartner~SRss_.xml'),
|
||||
(u'Sport', u'http://www.faz.net/s/Rub9F27A221597D4C39A82856B0FE79F051/Tpl~Epartner~SRss_.xml'),
|
||||
(u'Gesellschaft', u'http://www.faz.net/s/Rub02DBAA63F9EB43CEB421272A670A685C/Tpl~Epartner~SRss_.xml'),
|
||||
(u'Finanzen', u'http://www.faz.net/s/Rub4B891837ECD14082816D9E088A2D7CB4/Tpl~Epartner~SRss_.xml'),
|
||||
(u'Wissen', u'http://www.faz.net/s/Rub7F4BEE0E0C39429A8565089709B70C44/Tpl~Epartner~SRss_.xml'),
|
||||
(u'Reise', u'http://www.faz.net/s/RubE2FB5CA667054BDEA70FB3BC45F8D91C/Tpl~Epartner~SRss_.xml'),
|
||||
(u'Technik & Motor', u'http://www.faz.net/s/Rub01E4D53776494844A85FDF23F5707AD8/Tpl~Epartner~SRss_.xml'),
|
||||
(u'Beruf & Chance', u'http://www.faz.net/s/RubB1E10A8367E8446897468EDAA6EA0504/Tpl~Epartner~SRss_.xml'),
|
||||
(u'Kunstmarkt', u'http://www.faz.net/s/RubBC09F7BF72A2405A96718ECBFB68FBFE/Tpl~Epartner~SRss_.xml'),
|
||||
(u'Immobilien ', u'http://www.faz.net/s/RubFED172A9E10F46B3A5F01B02098C0C8D/Tpl~Epartner~SRss_.xml'),
|
||||
(u'Rhein-Main Zeitung', u'http://www.faz.net/s/RubABE881A6669742C2A5EBCB5D50D7EBEE/Tpl~Epartner~SRss_.xml'),
|
||||
(u'Atomdebatte ', u'http://www.faz.net/s/Rub469C43057F8C437CACC2DE9ED41B7950/Tpl~Epartner~SRss_.xml')
|
||||
html2lrf_options = [
|
||||
'--comment', description
|
||||
, '--category', category
|
||||
, '--publisher', publisher
|
||||
]
|
||||
|
||||
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
|
||||
|
||||
keep_only_tags = [dict(name='div', attrs={'class':'Article'})]
|
||||
|
||||
remove_tags = [
|
||||
dict(name=['object','link','embed','base'])
|
||||
,dict(name='div',
|
||||
attrs={'class':['LinkBoxModulSmall','ModulVerlagsInfo',
|
||||
'ArtikelServices', 'ModulLesermeinungenFooter',
|
||||
'ModulArtikelServices', 'BoxTool Aufklappen_Grau',
|
||||
'SocialMediaUnten', ]}),
|
||||
dict(id=['KurzLinkMenu', 'ArtikelServicesMenu']),
|
||||
]
|
||||
|
||||
feeds = [
|
||||
('FAZ.NET Aktuell', 'http://www.faz.net/s/RubF3CE08B362D244869BE7984590CB6AC1/Tpl~Epartner~SRss_.xml'),
|
||||
('Politik', 'http://www.faz.net/s/RubA24ECD630CAE40E483841DB7D16F4211/Tpl~Epartner~SRss_.xml'),
|
||||
('Wirtschaft', 'http://www.faz.net/s/RubC9401175958F4DE28E143E68888825F6/Tpl~Epartner~SRss_.xml'),
|
||||
('Feuilleton', 'http://www.faz.net/s/RubCC21B04EE95145B3AC877C874FB1B611/Tpl~Epartner~SRss_.xml'),
|
||||
('Sport', 'http://www.faz.net/s/Rub9F27A221597D4C39A82856B0FE79F051/Tpl~Epartner~SRss_.xml'),
|
||||
('Gesellschaft', 'http://www.faz.net/s/Rub02DBAA63F9EB43CEB421272A670A685C/Tpl~Epartner~SRss_.xml'),
|
||||
('Finanzen', 'http://www.faz.net/s/Rub4B891837ECD14082816D9E088A2D7CB4/Tpl~Epartner~SRss_.xml'),
|
||||
('Wissen', 'http://www.faz.net/s/Rub7F4BEE0E0C39429A8565089709B70C44/Tpl~Epartner~SRss_.xml'),
|
||||
('Reise', 'http://www.faz.net/s/RubE2FB5CA667054BDEA70FB3BC45F8D91C/Tpl~Epartner~SRss_.xml'),
|
||||
('Technik & Motor', 'http://www.faz.net/s/Rub01E4D53776494844A85FDF23F5707AD8/Tpl~Epartner~SRss_.xml'),
|
||||
('Beruf & Chance', 'http://www.faz.net/s/RubB1E10A8367E8446897468EDAA6EA0504/Tpl~Epartner~SRss_.xml')
|
||||
]
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
mtag = '<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>'
|
||||
soup.head.insert(0,mtag)
|
||||
del soup.body['onload']
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
return soup
|
||||
|
35
recipes/frankfurter_rundschau.recipe
Normal file
35
recipes/frankfurter_rundschau.recipe
Normal file
@ -0,0 +1,35 @@
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
class AdvancedUserRecipe(BasicNewsRecipe):
|
||||
|
||||
title = u'Frankfurter Rundschau'
|
||||
__author__ = 'schuster'
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
language = 'de'
|
||||
remove_javascript = True
|
||||
cover_url = 'http://www.fr-online.de/image/view/-/1474018/data/823538/-/logo.png'
|
||||
extra_css = '''
|
||||
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
|
||||
h4{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;}
|
||||
img {min-width:300px; max-width:600px; min-height:300px; max-height:800px}
|
||||
p{font-family:Arial,Helvetica,sans-serif;font-size:small;}
|
||||
body{font-family:Helvetica,Arial,sans-serif;font-size:small;}
|
||||
'''
|
||||
|
||||
feeds = [(u'Startseite', u'http://www.fr-online.de/home/-/1472778/1472778/-/view/asFeed/-/index.xml'),
|
||||
(u'Politik', u'http://www.fr-online.de/politik/-/1472596/1472596/-/view/asFeed/-/index.xml'),
|
||||
(u'Meinungen', u'http://www.fr-online.de/politik/meinung/-/1472602/1472602/-/view/asFeed/-/index.xml'),
|
||||
(u'Wirtschaft', u'http://www.fr-online.de/wirtschaft/-/1472780/1472780/-/view/asFeed/-/index.xml'),
|
||||
(u'Sport', u'http://www.fr-online.de/sport/-/1472784/1472784/-/view/asFeed/-/index.xml'),
|
||||
(u'Kultur', u'http://www.fr-online.de/kultur/-/1472786/1472786/-/view/asFeed/-/index.xml'),
|
||||
(u'Panorama', u'http://www.fr-online.de/panorama/-/1472782/1472782/-/view/asFeed/-/index.xml'),
|
||||
(u'Digital', u'http://www.fr-online.de/digital/-/1472406/1472406/-/view/asFeed/-/index.xml'),
|
||||
(u'Wissenschaft', u'http://www.fr-online.de/wissenschaft/-/1472788/1472788/-/view/asFeed/-/index.xml')
|
||||
]
|
||||
|
||||
|
||||
def print_version(self, url):
|
||||
return url.replace('index.html', 'view/printVersion/-/index.html')
|
||||
|
@ -9,6 +9,8 @@ class Kathimerini(BasicNewsRecipe):
|
||||
publisher = 'Kathimerini'
|
||||
category = 'news, GR'
|
||||
language = 'el'
|
||||
encoding = 'windows-1253'
|
||||
conversion_options = { 'linearize_tables': True}
|
||||
no_stylesheets = True
|
||||
remove_tags_before = dict(name='td',attrs={'class':'news'})
|
||||
remove_tags_after = dict(name='td',attrs={'class':'news'})
|
||||
@ -34,4 +36,3 @@ class Kathimerini(BasicNewsRecipe):
|
||||
def print_version(self, url):
|
||||
return url.replace('http://news.kathimerini.gr/4dcgi/', 'http://news.kathimerini.gr/4dcgi/4dcgi/')
|
||||
|
||||
|
||||
|
55
recipes/rheinische_post.recipe
Normal file
55
recipes/rheinische_post.recipe
Normal file
@ -0,0 +1,55 @@
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
class AdvancedUserRecipe(BasicNewsRecipe):
|
||||
|
||||
title = u'RP-online'
|
||||
__author__ = 'schuster'
|
||||
oldest_article = 2
|
||||
max_articles_per_feed = 100
|
||||
no_stylesheets = True
|
||||
use_embedded_content = False
|
||||
language = 'de'
|
||||
remove_javascript = True
|
||||
masthead_url = 'http://www.die-zeitungen.de/uploads/pics/LOGO_RP_ONLINE_01.jpg'
|
||||
cover_url = 'http://www.manroland.com/com/pressinfo_images/com/RheinischePost_Logo_300dpi.jpg'
|
||||
extra_css = '''
|
||||
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
|
||||
h4{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;}
|
||||
img {min-width:300px; max-width:600px; min-height:300px; max-height:800px}
|
||||
p{font-family:Arial,Helvetica,sans-serif;font-size:small;}
|
||||
body{font-family:Helvetica,Arial,sans-serif;font-size:small;}
|
||||
'''
|
||||
remove_tags_before = dict(id='article_content')
|
||||
remove_tags_after = dict(id='article_content')
|
||||
remove_tags = [dict(attrs={'class':['goodies', 'left', 'right', 'clear-all', 'teaser anzeigenwerbung', 'lesermeinung', 'goodiebox', 'goodiebox 1', 'goodiebox 2', 'goodiebox 3', 'boxframe', 'link']}),
|
||||
dict(id=['click_Fotos_link']),
|
||||
dict(name=['script', 'noscript', 'style', '_top', 'click_Fotos_link'])]
|
||||
|
||||
feeds = [ (u'Top-News', u'http://www.ngz-online.de/app/feed/rss/topnews'),
|
||||
(u'Politik', u'http://www.ngz-online.de/app/feed/rss/politik'),
|
||||
(u'Wirtschaft', u'http://www.ngz-online.de/app/feed/rss/wirtschaft'),
|
||||
(u'Panorama', u'http://www.ngz-online.de/app/feed/rss/panorama'),
|
||||
(u'Sport', u'http://www.ngz-online.de/app/feed/rss/sport'),
|
||||
(u'Tour de France', u'http://www.ngz-online.de/app/feed/rss/tourdefrance'),
|
||||
(u'Fußball', u'http://www.ngz-online.de/app/feed/rss/fussball'),
|
||||
(u'Fußball BuLi', u'http://www.ngz-online.de/app/feed/rss/bundesliga'),
|
||||
(u'Formel 1', u'http://www.ngz-online.de/app/feed/rss/formel1'),
|
||||
(u'US-Sport', u'http://www.ngz-online.de/app/feed/rss/us-sports'),
|
||||
(u'Boxen', u'http://www.ngz-online.de/app/feed/rss/boxen'),
|
||||
(u'Eishockey', u'http://www.ngz-online.de/app/feed/rss/eishockey'),
|
||||
(u'Basketball', u'http://www.ngz-online.de/app/feed/rss/basketball'),
|
||||
(u'Handball', u'http://www.ngz-online.de/app/feed/rss/handball'),
|
||||
(u'Motorsport', u'http://www.ngz-online.de/app/feed/rss/motorsport'),
|
||||
(u'Tennis', u'http://www.ngz-online.de/app/feed/rss/tennis'),
|
||||
(u'Radsport', u'http://www.ngz-online.de/app/feed/rss/radsport'),
|
||||
(u'Kultur', u'http://www.ngz-online.de/app/feed/rss/kultur'),
|
||||
(u'Gesellschaft', u'http://www.ngz-online.de/app/feed/rss/gesellschaft'),
|
||||
(u'Wissenschaft', u'http://www.ngz-online.de/app/feed/rss/wissen'),
|
||||
(u'Gesundheit', u'http://www.ngz-online.de/app/feed/rss/gesundheit'),
|
||||
(u'Digitale Welt', u'http://www.ngz-online.de/app/feed/rss/digitale'),
|
||||
(u'Auto & Mobil', u'http://www.ngz-online.de/app/feed/rss/auto'),
|
||||
(u'Reise & Welt', u'http://www.ngz-online.de/app/feed/rss/reise'),
|
||||
(u'Beruf & Karriere', u'http://www.ngz-online.de/app/feed/rss/beruf'),
|
||||
(u'Herzrasen', u'http://www.ngz-online.de/app/feed/rss/herzrasen'),
|
||||
(u'About a Boy', u'http://www.ngz-online.de/app/feed/rss/about_a_boy'),
|
||||
|
||||
]
|
@ -1,36 +1,40 @@
|
||||
{
|
||||
"and": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n i = 0\n while i < len(args):\n if not args[i]:\n return ''\n i += 1\n return '1'\n",
|
||||
"contains": "def evaluate(self, formatter, kwargs, mi, locals,\n val, test, value_if_present, value_if_not):\n if re.search(test, val):\n return value_if_present\n else:\n return value_if_not\n",
|
||||
"divide": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x / y)\n",
|
||||
"uppercase": "def evaluate(self, formatter, kwargs, mi, locals, val):\n return val.upper()\n",
|
||||
"strcat": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n i = 0\n res = ''\n for i in range(0, len(args)):\n res += args[i]\n return res\n",
|
||||
"in_list": "def evaluate(self, formatter, kwargs, mi, locals, val, sep, pat, fv, nfv):\n l = [v.strip() for v in val.split(sep) if v.strip()]\n for v in l:\n if re.search(pat, v):\n return fv\n return nfv\n",
|
||||
"substr": "def evaluate(self, formatter, kwargs, mi, locals, str_, start_, end_):\n return str_[int(start_): len(str_) if int(end_) == 0 else int(end_)]\n",
|
||||
"multiply": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x * y)\n",
|
||||
"ifempty": "def evaluate(self, formatter, kwargs, mi, locals, val, value_if_empty):\n if val:\n return val\n else:\n return value_if_empty\n",
|
||||
"booksize": "def evaluate(self, formatter, kwargs, mi, locals):\n if mi.book_size is not None:\n try:\n return str(mi.book_size)\n except:\n pass\n return ''\n",
|
||||
"select": "def evaluate(self, formatter, kwargs, mi, locals, val, key):\n if not val:\n return ''\n vals = [v.strip() for v in val.split(',')]\n for v in vals:\n if v.startswith(key+':'):\n return v[len(key)+1:]\n return ''\n",
|
||||
"strcmp": "def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):\n v = strcmp(x, y)\n if v < 0:\n return lt\n if v == 0:\n return eq\n return gt\n",
|
||||
"first_non_empty": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n i = 0\n while i < len(args):\n if args[i]:\n return args[i]\n i += 1\n return ''\n",
|
||||
"field": "def evaluate(self, formatter, kwargs, mi, locals, name):\n return formatter.get_value(name, [], kwargs)\n",
|
||||
"re": "def evaluate(self, formatter, kwargs, mi, locals, val, pattern, replacement):\n return re.sub(pattern, replacement, val)\n",
|
||||
"subtract": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x - y)\n",
|
||||
"list_item": "def evaluate(self, formatter, kwargs, mi, locals, val, index, sep):\n if not val:\n return ''\n index = int(index)\n val = val.split(sep)\n try:\n return val[index]\n except:\n return ''\n",
|
||||
"shorten": "def evaluate(self, formatter, kwargs, mi, locals,\n val, leading, center_string, trailing):\n l = max(0, int(leading))\n t = max(0, int(trailing))\n if len(val) > l + len(center_string) + t:\n return val[0:l] + center_string + ('' if t == 0 else val[-t:])\n else:\n return val\n",
|
||||
"re": "def evaluate(self, formatter, kwargs, mi, locals, val, pattern, replacement):\n return re.sub(pattern, replacement, val)\n",
|
||||
"field": "def evaluate(self, formatter, kwargs, mi, locals, name):\n return formatter.get_value(name, [], kwargs)\n",
|
||||
"add": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x + y)\n",
|
||||
"lookup": "def evaluate(self, formatter, kwargs, mi, locals, val, *args):\n if len(args) == 2: # here for backwards compatibility\n if val:\n return formatter.vformat('{'+args[0].strip()+'}', [], kwargs)\n else:\n return formatter.vformat('{'+args[1].strip()+'}', [], kwargs)\n if (len(args) % 2) != 1:\n raise ValueError(_('lookup requires either 2 or an odd number of arguments'))\n i = 0\n while i < len(args):\n if i + 1 >= len(args):\n return formatter.vformat('{' + args[i].strip() + '}', [], kwargs)\n if re.search(args[i], val):\n return formatter.vformat('{'+args[i+1].strip() + '}', [], kwargs)\n i += 2\n",
|
||||
"template": "def evaluate(self, formatter, kwargs, mi, locals, template):\n template = template.replace('[[', '{').replace(']]', '}')\n return formatter.__class__().safe_format(template, kwargs, 'TEMPLATE', mi)\n",
|
||||
"print": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n print args\n return None\n",
|
||||
"merge_lists": "def evaluate(self, formatter, kwargs, mi, locals, list1, list2, separator):\n l1 = [l.strip() for l in list1.split(separator) if l.strip()]\n l2 = [l.strip() for l in list2.split(separator) if l.strip()]\n lcl1 = set([icu_lower(l) for l in l1])\n res = []\n for i in l1:\n res.append(i)\n for i in l2:\n if icu_lower(i) not in lcl1:\n res.append(i)\n return ', '.join(sorted(res, key=sort_key))\n",
|
||||
"titlecase": "def evaluate(self, formatter, kwargs, mi, locals, val):\n return titlecase(val)\n",
|
||||
"subitems": "def evaluate(self, formatter, kwargs, mi, locals, val, start_index, end_index):\n if not val:\n return ''\n si = int(start_index)\n ei = int(end_index)\n items = [v.strip() for v in val.split(',')]\n rv = set()\n for item in items:\n component = item.split('.')\n try:\n if ei == 0:\n rv.add('.'.join(component[si:]))\n else:\n rv.add('.'.join(component[si:ei]))\n except:\n pass\n return ', '.join(sorted(rv, key=sort_key))\n",
|
||||
"sublist": "def evaluate(self, formatter, kwargs, mi, locals, val, start_index, end_index, sep):\n if not val:\n return ''\n si = int(start_index)\n ei = int(end_index)\n val = val.split(sep)\n try:\n if ei == 0:\n return sep.join(val[si:])\n else:\n return sep.join(val[si:ei])\n except:\n return ''\n",
|
||||
"test": "def evaluate(self, formatter, kwargs, mi, locals, val, value_if_set, value_not_set):\n if val:\n return value_if_set\n else:\n return value_not_set\n",
|
||||
"eval": "def evaluate(self, formatter, kwargs, mi, locals, template):\n from formatter import eval_formatter\n template = template.replace('[[', '{').replace(']]', '}')\n return eval_formatter.safe_format(template, locals, 'EVAL', None)\n",
|
||||
"multiply": "def evaluate(self, formatter, kwargs, mi, locals, x, y):\n x = float(x if x else 0)\n y = float(y if y else 0)\n return unicode(x * y)\n",
|
||||
"not": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n i = 0\n while i < len(args):\n if args[i]:\n return '1'\n i += 1\n return ''\n",
|
||||
"format_date": "def evaluate(self, formatter, kwargs, mi, locals, val, format_string):\n if not val:\n return ''\n try:\n dt = parse_date(val)\n s = format_date(dt, format_string)\n except:\n s = 'BAD DATE'\n return s\n",
|
||||
"capitalize": "def evaluate(self, formatter, kwargs, mi, locals, val):\n return capitalize(val)\n",
|
||||
"count": "def evaluate(self, formatter, kwargs, mi, locals, val, sep):\n return unicode(len(val.split(sep)))\n",
|
||||
"lowercase": "def evaluate(self, formatter, kwargs, mi, locals, val):\n return val.lower()\n",
|
||||
"strcmp": "def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):\n v = strcmp(x, y)\n if v < 0:\n return lt\n if v == 0:\n return eq\n return gt\n",
|
||||
"switch": "def evaluate(self, formatter, kwargs, mi, locals, val, *args):\n if (len(args) % 2) != 1:\n raise ValueError(_('switch requires an odd number of arguments'))\n i = 0\n while i < len(args):\n if i + 1 >= len(args):\n return args[i]\n if re.search(args[i], val):\n return args[i+1]\n i += 2\n",
|
||||
"substr": "def evaluate(self, formatter, kwargs, mi, locals, str_, start_, end_):\n return str_[int(start_): len(str_) if int(end_) == 0 else int(end_)]\n",
|
||||
"assign": "def evaluate(self, formatter, kwargs, mi, locals, target, value):\n locals[target] = value\n return value\n",
|
||||
"switch": "def evaluate(self, formatter, kwargs, mi, locals, val, *args):\n if (len(args) % 2) != 1:\n raise ValueError(_('switch requires an odd number of arguments'))\n i = 0\n while i < len(args):\n if i + 1 >= len(args):\n return args[i]\n if re.search(args[i], val):\n return args[i+1]\n i += 2\n",
|
||||
"or": "def evaluate(self, formatter, kwargs, mi, locals, *args):\n i = 0\n while i < len(args):\n if args[i]:\n return '1'\n i += 1\n return ''\n",
|
||||
"raw_field": "def evaluate(self, formatter, kwargs, mi, locals, name):\n return unicode(getattr(mi, name, None))\n",
|
||||
"cmp": "def evaluate(self, formatter, kwargs, mi, locals, x, y, lt, eq, gt):\n x = float(x if x else 0)\n y = float(y if y else 0)\n if x < y:\n return lt\n if x == y:\n return eq\n return gt\n"
|
||||
}
|
@ -4,7 +4,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__appname__ = u'calibre'
|
||||
numeric_version = (0, 8, 2)
|
||||
numeric_version = (0, 8, 3)
|
||||
__version__ = u'.'.join(map(unicode, numeric_version))
|
||||
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"
|
||||
|
||||
|
@ -594,7 +594,7 @@ from calibre.devices.iliad.driver import ILIAD
|
||||
from calibre.devices.irexdr.driver import IREXDR1000, IREXDR800
|
||||
from calibre.devices.jetbook.driver import JETBOOK, MIBUK, JETBOOK_MINI
|
||||
from calibre.devices.kindle.driver import KINDLE, KINDLE2, KINDLE_DX
|
||||
from calibre.devices.nook.driver import NOOK, NOOK_COLOR
|
||||
from calibre.devices.nook.driver import NOOK, NOOK_COLOR, NOOK_TSR
|
||||
from calibre.devices.prs505.driver import PRS505
|
||||
from calibre.devices.user_defined.driver import USER_DEFINED
|
||||
from calibre.devices.android.driver import ANDROID, S60
|
||||
@ -693,8 +693,7 @@ plugins += [
|
||||
KINDLE,
|
||||
KINDLE2,
|
||||
KINDLE_DX,
|
||||
NOOK,
|
||||
NOOK_COLOR,
|
||||
NOOK, NOOK_COLOR, NOOK_TSR,
|
||||
PRS505,
|
||||
ANDROID,
|
||||
S60,
|
||||
@ -1346,6 +1345,16 @@ class StoreSmashwordsStore(StoreBase):
|
||||
headquarters = 'US'
|
||||
formats = ['EPUB', 'HTML', 'LRF', 'MOBI', 'PDB', 'RTF', 'TXT']
|
||||
|
||||
class StoreVirtualoStore(StoreBase):
|
||||
name = 'Virtualo'
|
||||
author = u'Tomasz Długosz'
|
||||
description = u'Księgarnia internetowa, która oferuje bezpieczny i szeroki dostęp do książek w formie cyfrowej.'
|
||||
actual_plugin = 'calibre.gui2.store.virtualo_plugin:VirtualoStore'
|
||||
|
||||
drm_free_only = False
|
||||
headquarters = 'PL'
|
||||
formats = ['EPUB', 'PDF']
|
||||
|
||||
class StoreWaterstonesUKStore(StoreBase):
|
||||
name = 'Waterstones UK'
|
||||
author = 'Charles Haley'
|
||||
@ -1376,7 +1385,7 @@ class StoreWizardsTowerBooksStore(StoreBase):
|
||||
|
||||
class StoreWoblinkStore(StoreBase):
|
||||
name = 'Woblink'
|
||||
author = 'Tomasz Długosz'
|
||||
author = u'Tomasz Długosz'
|
||||
description = u'Czytanie zdarza się wszędzie!'
|
||||
actual_plugin = 'calibre.gui2.store.woblink_plugin:WoblinkStore'
|
||||
|
||||
@ -1411,7 +1420,8 @@ plugins += [
|
||||
StoreOReillyStore,
|
||||
StorePragmaticBookshelfStore,
|
||||
StoreSmashwordsStore,
|
||||
StoreWaterstonesUKStore,
|
||||
StoreVirtualoStore,
|
||||
#StoreWaterstonesUKStore,
|
||||
StoreWeightlessBooksStore,
|
||||
StoreWizardsTowerBooksStore,
|
||||
StoreWoblinkStore
|
||||
|
@ -107,3 +107,13 @@ class NOOK_COLOR(NOOK):
|
||||
|
||||
return filepath
|
||||
|
||||
class NOOK_TSR(NOOK):
|
||||
gui_name = _('Nook Simple')
|
||||
description = _('Communicate with the Nook TSR eBook reader.')
|
||||
|
||||
PRODUCT_ID = [0x003]
|
||||
BCD = [0x216]
|
||||
|
||||
EBOOK_DIR_MAIN = EBOOK_DIR_CARD_A = 'My Files/Books'
|
||||
|
||||
|
||||
|
@ -297,9 +297,11 @@ class MobiMLizer(object):
|
||||
if id_:
|
||||
# Keep anchors so people can use display:none
|
||||
# to generate hidden TOCs
|
||||
tail = elem.tail
|
||||
elem.clear()
|
||||
elem.text = None
|
||||
elem.set('id', id_)
|
||||
elem.tail = tail
|
||||
else:
|
||||
return
|
||||
tag = barename(elem.tag)
|
||||
@ -309,7 +311,8 @@ class MobiMLizer(object):
|
||||
istates.append(istate)
|
||||
left = 0
|
||||
display = style['display']
|
||||
isblock = not display.startswith('inline')
|
||||
isblock = (not display.startswith('inline') and style['display'] !=
|
||||
'none')
|
||||
isblock = isblock and style['float'] == 'none'
|
||||
isblock = isblock and tag != 'br'
|
||||
if isblock:
|
||||
|
@ -44,7 +44,7 @@ class StoreAction(InterfaceAction):
|
||||
def search(self, query=''):
|
||||
self.show_disclaimer()
|
||||
from calibre.gui2.store.search.search import SearchDialog
|
||||
sd = SearchDialog(self.gui.istores, self.gui, query)
|
||||
sd = SearchDialog(self.gui, self.gui, query)
|
||||
sd.exec_()
|
||||
|
||||
def _get_selected_row(self):
|
||||
|
@ -207,8 +207,9 @@ class SchedulerDialog(QDialog, Ui_Dialog):
|
||||
self.recipe_model.searched.connect(self.search.search_done,
|
||||
type=Qt.QueuedConnection)
|
||||
self.recipe_model.searched.connect(self.search_done)
|
||||
self.search.setFocus(Qt.OtherFocusReason)
|
||||
self.recipes.setFocus(Qt.OtherFocusReason)
|
||||
self.commit_on_change = True
|
||||
self.previous_urn = None
|
||||
|
||||
self.recipes.setModel(self.recipe_model)
|
||||
self.detail_box.setVisible(False)
|
||||
@ -228,6 +229,9 @@ class SchedulerDialog(QDialog, Ui_Dialog):
|
||||
|
||||
self.old_news.setValue(gconf['oldest_news'])
|
||||
|
||||
self.go_button.clicked.connect(self.search.do_search)
|
||||
self.clear_search_button.clicked.connect(self.search.clear_clicked)
|
||||
|
||||
def set_pw_echo_mode(self, state):
|
||||
self.password.setEchoMode(self.password.Normal
|
||||
if state == Qt.Checked else self.password.Password)
|
||||
@ -265,14 +269,9 @@ class SchedulerDialog(QDialog, Ui_Dialog):
|
||||
self.last_downloaded.setVisible(enabled)
|
||||
|
||||
def current_changed(self, current, previous):
|
||||
if self.commit_on_change:
|
||||
if previous.isValid():
|
||||
if not self.commit(urn=getattr(previous.internalPointer(),
|
||||
'urn', None)):
|
||||
self.commit_on_change = False
|
||||
self.recipes.setCurrentIndex(previous)
|
||||
else:
|
||||
self.commit_on_change = True
|
||||
if self.previous_urn is not None:
|
||||
self.commit(urn=self.previous_urn)
|
||||
self.previous_urn = None
|
||||
|
||||
urn = self.current_urn
|
||||
if urn is not None:
|
||||
@ -332,6 +331,7 @@ class SchedulerDialog(QDialog, Ui_Dialog):
|
||||
return True
|
||||
|
||||
def initialize_detail_box(self, urn):
|
||||
self.previous_urn = urn
|
||||
self.detail_box.setVisible(True)
|
||||
self.download_button.setVisible(True)
|
||||
self.detail_box.setCurrentIndex(0)
|
||||
|
@ -17,21 +17,30 @@
|
||||
<iconset resource="../../../../resources/images.qrc">
|
||||
<normaloff>:/images/scheduler.png</normaloff>:/images/scheduler.png</iconset>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout" columnstretch="0,1,2">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="SearchBox2" name="search"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="go_button">
|
||||
<property name="text">
|
||||
<string>&Search:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>search</cstring>
|
||||
<string>Go</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="SearchBox2" name="search"/>
|
||||
<item>
|
||||
<widget class="QToolButton" name="clear_search_button">
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../resources/images.qrc">
|
||||
<normaloff>:/images/clear_left.png</normaloff>:/images/clear_left.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2" rowspan="3">
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="1" rowspan="2">
|
||||
<widget class="QScrollArea" name="scrollArea">
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
@ -44,7 +53,7 @@
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>486</width>
|
||||
<width>524</width>
|
||||
<height>504</height>
|
||||
</rect>
|
||||
</property>
|
||||
@ -320,7 +329,7 @@
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<item row="1" column="0">
|
||||
<widget class="QTreeView" name="recipes">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Minimum" vsizetype="Expanding">
|
||||
@ -345,7 +354,17 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2">
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="count_label">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_7">
|
||||
@ -376,17 +395,7 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="4" column="2">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Save</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<item row="3" column="0">
|
||||
<widget class="QPushButton" name="download_all_button">
|
||||
<property name="toolTip">
|
||||
<string>Download all scheduled news sources at once</string>
|
||||
@ -394,15 +403,19 @@
|
||||
<property name="text">
|
||||
<string>Download &all scheduled</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset resource="../../../../resources/images.qrc">
|
||||
<normaloff>:/images/news.png</normaloff>:/images/news.png</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="QLabel" name="count_label">
|
||||
<property name="text">
|
||||
<string/>
|
||||
<item row="3" column="1">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Save</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -103,7 +103,22 @@ class Matches(QAbstractItemModel):
|
||||
return Qt.Unchecked
|
||||
return Qt.Checked
|
||||
elif role == Qt.ToolTipRole:
|
||||
if col == 0:
|
||||
if is_disabled(result):
|
||||
return QVariant(_('<p>This store is currently diabled and cannot be used in other parts of calibre.</p>'))
|
||||
else:
|
||||
return QVariant(_('<p>This store is currently enabled and can be used in other parts of calibre.</p>'))
|
||||
elif col == 1:
|
||||
return QVariant('<p>%s</p>' % result.description)
|
||||
elif col == 2:
|
||||
if result.drm_free_only:
|
||||
return QVariant(_('<p>This store only distributes ebooks with DRM.</p>'))
|
||||
else:
|
||||
return QVariant(_('<p>This store distributes ebooks with DRM. It may have some titles without DRM, but you will need to check on a per title basis.</p>'))
|
||||
elif col == 3:
|
||||
return QVariant(_('<p>This store is headquartered in %s. This is a good indication of what market the store caters to. However, this does not necessarily mean that the store is limited to that market only.</p>') % result.headquarters)
|
||||
elif col == 4:
|
||||
return QVariant(_('<p>This store distributes ebooks in the following formats: %s</p>') % ', '.join(result.formats))
|
||||
return NONE
|
||||
|
||||
def setData(self, index, data, role):
|
||||
@ -130,7 +145,7 @@ class Matches(QAbstractItemModel):
|
||||
elif col == 1:
|
||||
text = match.name
|
||||
elif col == 2:
|
||||
text = 'b' if getattr(match, 'drm', True) else 'a'
|
||||
text = 'a' if getattr(match, 'drm_free_only', True) else 'b'
|
||||
elif col == 3:
|
||||
text = getattr(match, 'headquarters', '')
|
||||
return text
|
||||
@ -240,5 +255,3 @@ class SearchFilter(SearchQueryParser):
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return matches
|
||||
|
||||
|
||||
|
@ -10,10 +10,11 @@ import re
|
||||
from random import shuffle
|
||||
|
||||
from PyQt4.Qt import (Qt, QDialog, QDialogButtonBox, QTimer, QCheckBox,
|
||||
QVBoxLayout, QIcon, QWidget)
|
||||
QVBoxLayout, QIcon, QWidget, QTabWidget)
|
||||
|
||||
from calibre.gui2 import JSONConfig, info_dialog
|
||||
from calibre.gui2.progress_indicator import ProgressIndicator
|
||||
from calibre.gui2.store.config.chooser.chooser_widget import StoreChooserWidget
|
||||
from calibre.gui2.store.config.search.search_widget import StoreConfigWidget
|
||||
from calibre.gui2.store.search.adv_search_builder import AdvSearchBuilderDialog
|
||||
from calibre.gui2.store.search.download_thread import SearchThreadPool, \
|
||||
@ -22,7 +23,7 @@ from calibre.gui2.store.search.search_ui import Ui_Dialog
|
||||
|
||||
class SearchDialog(QDialog, Ui_Dialog):
|
||||
|
||||
def __init__(self, istores, parent=None, query=''):
|
||||
def __init__(self, gui, parent=None, query=''):
|
||||
QDialog.__init__(self, parent)
|
||||
self.setupUi(self)
|
||||
|
||||
@ -34,8 +35,7 @@ class SearchDialog(QDialog, Ui_Dialog):
|
||||
# the variables it sets up are used later.
|
||||
self.load_settings()
|
||||
|
||||
# We keep a cache of store plugins and reference them by name.
|
||||
self.store_plugins = istores
|
||||
self.gui = gui
|
||||
|
||||
# Setup our worker threads.
|
||||
self.search_pool = SearchThreadPool(self.search_thread_count)
|
||||
@ -49,22 +49,11 @@ class SearchDialog(QDialog, Ui_Dialog):
|
||||
self.hang_check = 0
|
||||
|
||||
# Update store caches silently.
|
||||
for p in self.store_plugins.values():
|
||||
for p in self.gui.istores.values():
|
||||
self.cache_pool.add_task(p, self.timeout)
|
||||
|
||||
# Add check boxes for each store so the user
|
||||
# can disable searching specific stores on a
|
||||
# per search basis.
|
||||
stores_check_widget = QWidget()
|
||||
store_list_layout = QVBoxLayout()
|
||||
stores_check_widget.setLayout(store_list_layout)
|
||||
for x in sorted(self.store_plugins.keys(), key=lambda x: x.lower()):
|
||||
cbox = QCheckBox(x)
|
||||
cbox.setChecked(False)
|
||||
store_list_layout.addWidget(cbox)
|
||||
setattr(self, 'store_check_' + x, cbox)
|
||||
store_list_layout.addStretch()
|
||||
self.store_list.setWidget(stores_check_widget)
|
||||
self.store_checks = {}
|
||||
self.setup_store_checks()
|
||||
|
||||
# Set the search query
|
||||
self.search_edit.setText(query)
|
||||
@ -92,6 +81,27 @@ class SearchDialog(QDialog, Ui_Dialog):
|
||||
|
||||
self.restore_state()
|
||||
|
||||
def setup_store_checks(self):
|
||||
# Add check boxes for each store so the user
|
||||
# can disable searching specific stores on a
|
||||
# per search basis.
|
||||
existing = {}
|
||||
for n in self.store_checks:
|
||||
existing[n] = self.store_checks[n].isChecked()
|
||||
|
||||
self.store_checks = {}
|
||||
|
||||
stores_check_widget = QWidget()
|
||||
store_list_layout = QVBoxLayout()
|
||||
stores_check_widget.setLayout(store_list_layout)
|
||||
for x in sorted(self.gui.istores.keys(), key=lambda x: x.lower()):
|
||||
cbox = QCheckBox(x)
|
||||
cbox.setChecked(existing.get(x, False))
|
||||
store_list_layout.addWidget(cbox)
|
||||
self.store_checks[x] = cbox
|
||||
store_list_layout.addStretch()
|
||||
self.store_list.setWidget(stores_check_widget)
|
||||
|
||||
def build_adv_search(self):
|
||||
adv = AdvSearchBuilderDialog(self)
|
||||
if adv.exec_() == QDialog.Accepted:
|
||||
@ -126,11 +136,12 @@ class SearchDialog(QDialog, Ui_Dialog):
|
||||
# futher filtering.
|
||||
self.results_view.model().set_query(query)
|
||||
|
||||
# Plugins are in alphebetic order. Randomize the
|
||||
# order of plugin names. This way plugins closer
|
||||
# Plugins are in random order that does not change.
|
||||
# Randomize the ord of the plugin names every time
|
||||
# there is a search. This way plugins closer
|
||||
# to a don't have an unfair advantage over
|
||||
# plugins further from a.
|
||||
store_names = self.store_plugins.keys()
|
||||
store_names = self.store_checks.keys()
|
||||
if not store_names:
|
||||
return
|
||||
# Remove all of our internal filtering logic from the query.
|
||||
@ -138,8 +149,8 @@ class SearchDialog(QDialog, Ui_Dialog):
|
||||
shuffle(store_names)
|
||||
# Add plugins that the user has checked to the search pool's work queue.
|
||||
for n in store_names:
|
||||
if getattr(self, 'store_check_' + n).isChecked():
|
||||
self.search_pool.add_task(query, n, self.store_plugins[n], self.max_results, self.timeout)
|
||||
if self.store_checks[n].isChecked():
|
||||
self.search_pool.add_task(query, n, self.gui.istores[n], self.max_results, self.timeout)
|
||||
self.hang_check = 0
|
||||
self.checker.start(100)
|
||||
self.pi.startAnimation()
|
||||
@ -179,8 +190,8 @@ class SearchDialog(QDialog, Ui_Dialog):
|
||||
self.config['open_external'] = self.open_external.isChecked()
|
||||
|
||||
store_check = {}
|
||||
for n in self.store_plugins:
|
||||
store_check[n] = getattr(self, 'store_check_' + n).isChecked()
|
||||
for k, v in self.store_checks.items():
|
||||
store_check[k] = v.isChecked()
|
||||
self.config['store_checked'] = store_check
|
||||
|
||||
def restore_state(self):
|
||||
@ -206,8 +217,8 @@ class SearchDialog(QDialog, Ui_Dialog):
|
||||
store_check = self.config.get('store_checked', None)
|
||||
if store_check:
|
||||
for n in store_check:
|
||||
if hasattr(self, 'store_check_' + n):
|
||||
getattr(self, 'store_check_' + n).setChecked(store_check[n])
|
||||
if n in self.store_checks:
|
||||
self.store_checks[n].setChecked(store_check[n])
|
||||
|
||||
self.results_view.model().sort_col = self.config.get('sort_col', 2)
|
||||
self.results_view.model().sort_order = self.config.get('sort_order', Qt.AscendingOrder)
|
||||
@ -234,20 +245,27 @@ class SearchDialog(QDialog, Ui_Dialog):
|
||||
self.config['open_external'] = self.open_external.isChecked()
|
||||
|
||||
d = QDialog(self)
|
||||
button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
button_box = QDialogButtonBox(QDialogButtonBox.Close)
|
||||
v = QVBoxLayout(d)
|
||||
button_box.accepted.connect(d.accept)
|
||||
button_box.rejected.connect(d.reject)
|
||||
d.setWindowTitle(_('Customize get books search'))
|
||||
config_widget = StoreConfigWidget(self.config)
|
||||
v.addWidget(config_widget)
|
||||
|
||||
tab_widget = QTabWidget(d)
|
||||
v.addWidget(tab_widget)
|
||||
v.addWidget(button_box)
|
||||
|
||||
d.exec_()
|
||||
chooser_config_widget = StoreChooserWidget()
|
||||
search_config_widget = StoreConfigWidget(self.config)
|
||||
|
||||
if d.result() == QDialog.Accepted:
|
||||
config_widget.save_settings()
|
||||
tab_widget.addTab(chooser_config_widget, _('Choose stores'))
|
||||
tab_widget.addTab(search_config_widget, _('Configure search'))
|
||||
|
||||
d.exec_()
|
||||
search_config_widget.save_settings()
|
||||
self.config_changed()
|
||||
self.gui.load_store_plugins()
|
||||
self.setup_store_checks()
|
||||
|
||||
def config_changed(self):
|
||||
self.load_settings()
|
||||
@ -283,7 +301,7 @@ class SearchDialog(QDialog, Ui_Dialog):
|
||||
|
||||
def open_store(self, index):
|
||||
result = self.results_view.model().get_result(index)
|
||||
self.store_plugins[result.store_name].open(self, result.detail_item, self.open_external.isChecked())
|
||||
self.gui.istores[result.store_name].open(self, result.detail_item, self.open_external.isChecked())
|
||||
|
||||
def check_progress(self):
|
||||
if not self.search_pool.threads_running() and not self.results_view.model().cover_pool.threads_running() and not self.results_view.model().details_pool.threads_running():
|
||||
@ -292,27 +310,16 @@ class SearchDialog(QDialog, Ui_Dialog):
|
||||
if not self.pi.isAnimated():
|
||||
self.pi.startAnimation()
|
||||
|
||||
def get_store_checks(self):
|
||||
'''
|
||||
Returns a list of QCheckBox's for each store.
|
||||
'''
|
||||
checks = []
|
||||
for x in self.store_plugins:
|
||||
check = getattr(self, 'store_check_' + x, None)
|
||||
if check:
|
||||
checks.append(check)
|
||||
return checks
|
||||
|
||||
def stores_select_all(self):
|
||||
for check in self.get_store_checks():
|
||||
for check in self.store_checks.values():
|
||||
check.setChecked(True)
|
||||
|
||||
def stores_select_invert(self):
|
||||
for check in self.get_store_checks():
|
||||
for check in self.store_checks.values():
|
||||
check.setChecked(not check.isChecked())
|
||||
|
||||
def stores_select_none(self):
|
||||
for check in self.get_store_checks():
|
||||
for check in self.store_checks.values():
|
||||
check.setChecked(False)
|
||||
|
||||
def dialog_closed(self, result):
|
||||
|
71
src/calibre/gui2/store/virtualo_plugin.py
Normal file
71
src/calibre/gui2/store/virtualo_plugin.py
Normal file
@ -0,0 +1,71 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from __future__ import (unicode_literals, division, absolute_import, print_function)
|
||||
|
||||
__license__ = 'GPL 3'
|
||||
__copyright__ = '2011, Tomasz Długosz <tomek3d@gmail.com>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import re
|
||||
import urllib
|
||||
from contextlib import closing
|
||||
|
||||
from lxml import html
|
||||
|
||||
from PyQt4.Qt import QUrl
|
||||
|
||||
from calibre import browser, url_slash_cleaner
|
||||
from calibre.gui2 import open_url
|
||||
from calibre.gui2.store import StorePlugin
|
||||
from calibre.gui2.store.basic_config import BasicStoreConfig
|
||||
from calibre.gui2.store.search_result import SearchResult
|
||||
from calibre.gui2.store.web_store_dialog import WebStoreDialog
|
||||
|
||||
class VirtualoStore(BasicStoreConfig, StorePlugin):
|
||||
|
||||
def open(self, parent=None, detail_item=None, external=False):
|
||||
url = 'http://virtualo.pl/ebook/c2/'
|
||||
|
||||
if external or self.config.get('open_external', False):
|
||||
open_url(QUrl(url_slash_cleaner(detail_item if detail_item else url)))
|
||||
else:
|
||||
d = WebStoreDialog(self.gui, url, parent, detail_item)
|
||||
d.setWindowTitle(self.name)
|
||||
d.set_tags(self.config.get('tags', ''))
|
||||
d.exec_()
|
||||
|
||||
def search(self, query, max_results=10, timeout=60):
|
||||
url = 'http://virtualo.pl/c2/?q=' + urllib.quote(query.encode('utf-8'))
|
||||
|
||||
br = browser()
|
||||
|
||||
counter = max_results
|
||||
with closing(br.open(url, timeout=timeout)) as f:
|
||||
doc = html.fromstring(f.read())
|
||||
for data in doc.xpath('//div[@id="product_list"]/div/div[@class="column"]'):
|
||||
if counter <= 0:
|
||||
break
|
||||
|
||||
id = ''.join(data.xpath('.//table/tr[2]/td[1]/a/@href'))
|
||||
if not id:
|
||||
continue
|
||||
|
||||
price = ''.join(data.xpath('.//span[@class="price"]/text() | .//span[@class="price abbr"]/text()'))
|
||||
cover_url = ''.join(data.xpath('.//table/tr[2]/td[1]/a/img/@src'))
|
||||
title = ''.join(data.xpath('.//div[@class="title"]/a/text()'))
|
||||
author = ', '.join(data.xpath('.//div[@class="authors"]/a/text()'))
|
||||
formats = ', '.join(data.xpath('.//span[@class="format"]/a/text()'))
|
||||
formats = re.sub(r'(, )?ONLINE(, )?', '', formats)
|
||||
|
||||
counter -= 1
|
||||
|
||||
s = SearchResult()
|
||||
s.cover_url = cover_url
|
||||
s.title = title.strip() + ' ' + formats
|
||||
s.author = author.strip()
|
||||
s.price = price + ' zł'
|
||||
s.detail_item = 'http://virtualo.pl' + id.strip()
|
||||
s.formats = formats.upper().strip()
|
||||
s.drm = SearchResult.DRM_UNKNOWN
|
||||
|
||||
yield s
|
@ -164,7 +164,7 @@ class Sony900(Sony505):
|
||||
|
||||
class Nook(Sony505):
|
||||
id = 'nook'
|
||||
name = 'Nook'
|
||||
name = 'Nook and Nook Simple Reader'
|
||||
manufacturer = 'Barnes & Noble'
|
||||
output_profile = 'nook'
|
||||
|
||||
|
@ -46,6 +46,64 @@ class TestEmail(QDialog, TE_Dialog):
|
||||
finally:
|
||||
self.test_button.setEnabled(True)
|
||||
|
||||
class RelaySetup(QDialog):
|
||||
|
||||
def __init__(self, service, parent):
|
||||
QDialog.__init__(self, parent)
|
||||
|
||||
self.l = l = QGridLayout()
|
||||
self.setLayout(l)
|
||||
self.bb = bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
|
||||
bb.accepted.connect(self.accept)
|
||||
bb.rejected.connect(self.reject)
|
||||
self.tl = QLabel(('<p>'+_('Setup sending email using') +
|
||||
' <b>{name}</b><p>' +
|
||||
_('If you don\'t have an account, you can sign up for a free {name} email '
|
||||
'account at <a href="http://{url}">http://{url}</a>. {extra}')).format(
|
||||
**service))
|
||||
l.addWidget(self.tl, 0, 0, 3, 0)
|
||||
self.tl.setWordWrap(True)
|
||||
self.tl.setOpenExternalLinks(True)
|
||||
for name, label in (
|
||||
['from_', _('Your %s &email address:')],
|
||||
['username', _('Your %s &username:')],
|
||||
['password', _('Your %s &password:')],
|
||||
):
|
||||
la = QLabel(label%service['name'])
|
||||
le = QLineEdit(self)
|
||||
setattr(self, name, le)
|
||||
setattr(self, name+'_label', la)
|
||||
r = l.rowCount()
|
||||
l.addWidget(la, r, 0)
|
||||
l.addWidget(le, r, 1)
|
||||
la.setBuddy(le)
|
||||
if name == 'password':
|
||||
self.ptoggle = QCheckBox(_('&Show password'), self)
|
||||
l.addWidget(self.ptoggle, r, 2)
|
||||
self.ptoggle.stateChanged.connect(
|
||||
lambda s: self.password.setEchoMode(self.password.Normal if s
|
||||
== Qt.Checked else self.password.Password))
|
||||
self.username.setText(service['username'])
|
||||
self.password.setEchoMode(self.password.Password)
|
||||
self.bl = QLabel('<p>' + _(
|
||||
'If you plan to use email to send books to your Kindle, remember to'
|
||||
' add the your %s email address to the allowed email addresses in your '
|
||||
'Amazon.com Kindle management page.')%service['name'])
|
||||
self.bl.setWordWrap(True)
|
||||
l.addWidget(self.bl, l.rowCount(), 0, 3, 0)
|
||||
l.addWidget(bb, l.rowCount(), 0, 3, 0)
|
||||
self.setWindowTitle(_('Setup') + ' ' + service['name'])
|
||||
self.resize(self.sizeHint())
|
||||
self.service = service
|
||||
|
||||
def accept(self):
|
||||
un = unicode(self.username.text())
|
||||
if self.service.get('at_in_username', False) and '@' not in un:
|
||||
return error_dialog(self, _('Incorrect username'),
|
||||
_('%s needs the full email address as your username') %
|
||||
self.service['name'], show=True)
|
||||
QDialog.accept(self)
|
||||
|
||||
|
||||
class SendEmail(QWidget, Ui_Form):
|
||||
|
||||
@ -129,7 +187,8 @@ class SendEmail(QWidget, Ui_Form):
|
||||
'port': 587,
|
||||
'username': '@gmail.com',
|
||||
'url': 'www.gmail.com',
|
||||
'extra': ''
|
||||
'extra': '',
|
||||
'at_in_username': True,
|
||||
},
|
||||
'hotmail': {
|
||||
'name': 'Hotmail',
|
||||
@ -143,53 +202,10 @@ class SendEmail(QWidget, Ui_Form):
|
||||
' will let calibre send email. In this case, I'
|
||||
' strongly suggest you setup a free gmail account'
|
||||
' instead.'),
|
||||
'at_in_username': True,
|
||||
}
|
||||
}[service]
|
||||
d = QDialog(self)
|
||||
l = QGridLayout()
|
||||
d.setLayout(l)
|
||||
bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
|
||||
bb.accepted.connect(d.accept)
|
||||
bb.rejected.connect(d.reject)
|
||||
d.tl = QLabel(('<p>'+_('Setup sending email using') +
|
||||
' <b>{name}</b><p>' +
|
||||
_('If you don\'t have an account, you can sign up for a free {name} email '
|
||||
'account at <a href="http://{url}">http://{url}</a>. {extra}')).format(
|
||||
**service))
|
||||
l.addWidget(d.tl, 0, 0, 3, 0)
|
||||
d.tl.setWordWrap(True)
|
||||
d.tl.setOpenExternalLinks(True)
|
||||
for name, label in (
|
||||
['from_', _('Your %s &email address:')],
|
||||
['username', _('Your %s &username:')],
|
||||
['password', _('Your %s &password:')],
|
||||
):
|
||||
la = QLabel(label%service['name'])
|
||||
le = QLineEdit(d)
|
||||
setattr(d, name, le)
|
||||
setattr(d, name+'_label', la)
|
||||
r = l.rowCount()
|
||||
l.addWidget(la, r, 0)
|
||||
l.addWidget(le, r, 1)
|
||||
la.setBuddy(le)
|
||||
if name == 'password':
|
||||
d.ptoggle = QCheckBox(_('&Show password'), d)
|
||||
l.addWidget(d.ptoggle, r, 2)
|
||||
d.ptoggle.stateChanged.connect(
|
||||
lambda s: d.password.setEchoMode(d.password.Normal if s
|
||||
== Qt.Checked else d.password.Password))
|
||||
d.username.setText(service['username'])
|
||||
d.password.setEchoMode(d.password.Password)
|
||||
d.bl = QLabel('<p>' + _(
|
||||
'If you plan to use email to send books to your Kindle, remember to'
|
||||
' add the your %s email address to the allowed email addresses in your '
|
||||
'Amazon.com Kindle management page.')%service['name'])
|
||||
d.bl.setWordWrap(True)
|
||||
l.addWidget(d.bl, l.rowCount(), 0, 3, 0)
|
||||
l.addWidget(bb, l.rowCount(), 0, 3, 0)
|
||||
d.setWindowTitle(_('Setup') + ' ' + service['name'])
|
||||
d.resize(d.sizeHint())
|
||||
bb.setVisible(True)
|
||||
d = RelaySetup(service, self)
|
||||
if d.exec_() != d.Accepted:
|
||||
return
|
||||
self.relay_username.setText(d.username.text())
|
||||
|
@ -138,7 +138,7 @@ Follow these steps to find the problem:
|
||||
My device is non-standard or unusual. What can I do to connect to it?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
In addition to the :guilabel:`Connect to Folder` function found under the Connect/Share button, |app| provides a ``User Defined`` device plugin that can be used to connect to any USB device that presents that shows up as a disk drive in your operating system. See the device plugin ``Preferences -> Plugins -> Device Plugins -> User Defined`` and ``Preferences -> Miscellaneous -> Get information to setup the user defined device`` for more information.
|
||||
In addition to the :guilabel:`Connect to Folder` function found under the Connect/Share button, |app| provides a ``User Defined`` device plugin that can be used to connect to any USB device that shows up as a disk drive in your operating system. Note: on windows, the device must have a drive letter for calibre to use it. See the device plugin ``Preferences -> Plugins -> Device Plugins -> User Defined`` and ``Preferences -> Miscellaneous -> Get information to setup the user defined device`` for more information.
|
||||
|
||||
How does |app| manage collections on my SONY reader?
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -587,7 +587,7 @@ You can download news and convert it into an ebook with the command::
|
||||
|
||||
/opt/calibre/ebook-convert "Title of news source.recipe" outputfile.epub
|
||||
|
||||
If you want to generate MOBI, use outputfile.mobi instead.
|
||||
If you want to generate MOBI, use outputfile.mobi instead and use ``--output-profile kindle``.
|
||||
|
||||
You can email downloaded news with the command::
|
||||
|
||||
|
@ -229,13 +229,14 @@ For various values of series_index, the program returns:
|
||||
|
||||
The following functions are available in addition to those described in single-function mode. Remember from the example above that the single-function mode functions require an additional first parameter specifying the field to operate on. With the exception of the ``id`` parameter of assign, all parameters can be statements (sequences of expressions):
|
||||
|
||||
* ``and(value, value, ...)`` -- returns the string "1" if all values are not empty, otherwise returns the empty string. This function works well with test or first_non_empty. You can have as many values as you want.
|
||||
* ``add(x, y)`` -- returns x + y. Throws an exception if either x or y are not numbers.
|
||||
* ``assign(id, val)`` -- assigns val to id, then returns val. id must be an identifier, not an expression
|
||||
* ``booksize()`` -- returns the value of the |app| 'size' field. Returns '' if there are no formats.
|
||||
* ``cmp(x, y, lt, eq, gt)`` -- compares x and y after converting both to numbers. Returns ``lt`` if x < y. Returns ``eq`` if x == y. Otherwise returns ``gt``.
|
||||
* ``divide(x, y)`` -- returns x / y. Throws an exception if either x or y are not numbers.
|
||||
* ``field(name)`` -- returns the metadata field named by ``name``.
|
||||
* ``first_non_empty(value, value, ...) -- returns the first value that is not empty. If all values are empty, then the empty value is returned. You can have as many values as you want.
|
||||
* ``first_non_empty(value, value, ...)`` -- returns the first value that is not empty. If all values are empty, then the empty value is returned. You can have as many values as you want.
|
||||
* ``format_date(x, date_format)`` -- format_date(val, format_string) -- format the value, which must be a date field, using the format_string, returning a string. The formatting codes are::
|
||||
|
||||
d : the day as number without a leading zero (1 to 31)
|
||||
@ -251,7 +252,10 @@ The following functions are available in addition to those described in single-f
|
||||
iso : the date with time and timezone. Must be the only format present.
|
||||
|
||||
* ``eval(string)`` -- evaluates the string as a program, passing the local variables (those ``assign`` ed to). This permits using the template processor to construct complex results from local variables.
|
||||
* ``not(value)`` -- returns the string "1" if the value is empty, otherwise returns the empty string. This function works well with test or first_non_empty. You can have as many values as you want.
|
||||
* ``merge_lists(list1, list2, separator)`` -- return a list made by merging the items in list1 and list2, removing duplicate items using a case-insensitive compare. If items differ in case, the one in list1 is used. The items in list1 and list2 are separated by separator, as are the items in the returned list.
|
||||
* ``multiply(x, y)`` -- returns x * y. Throws an exception if either x or y are not numbers.
|
||||
* ``or(value, value, ...)`` -- returns the string "1" if any value is not empty, otherwise returns the empty string. This function works well with test or first_non_empty. You can have as many values as you want.
|
||||
* ``print(a, b, ...)`` -- prints the arguments to standard output. Unless you start calibre from the command line (``calibre-debug -g``), the output will go to a black hole.
|
||||
* ``raw_field(name)`` -- returns the metadata field named by name without applying any formatting.
|
||||
* ``strcat(a, b, ...)`` -- can take any number of arguments. Returns a string formed by concatenating all the arguments.
|
||||
@ -260,6 +264,21 @@ The following functions are available in addition to those described in single-f
|
||||
* ``subtract(x, y)`` -- returns x - y. Throws an exception if either x or y are not numbers.
|
||||
* ``template(x)`` -- evaluates x as a template. The evaluation is done in its own context, meaning that variables are not shared between the caller and the template evaluation. Because the `{` and `}` characters are special, you must use `[[` for the `{` character and `]]` for the '}' character; they are converted automatically. For example, ``template('[[title_sort]]') will evaluate the template ``{title_sort}`` and return its value.
|
||||
|
||||
Function classification summary:
|
||||
|
||||
* Get values from metadata: ``field``. ``raw_field``. In some situations, ``lookup`` can be used in place of ``field``.
|
||||
* Arithmetic: ``add``, ``subtract``, ``multiply``, ``divide``
|
||||
* Boolean: ``and``, ``or``, ``not``. The function ``if_empty`` is similar to ``and`` called with one argument.
|
||||
* If-then-else: ``contains``, ``test``
|
||||
* Iterating over values: ``first_non_empty``, ``lookup``, ``switch``
|
||||
* List lookup: ``in_list``, ``list_item``, ``select``,
|
||||
* List manipulation: ``count``, ``merge_lists``, ``sublist``, ``subitems``
|
||||
* Recursion: ``eval``, ``template``
|
||||
* Relational: ``cmp`` , ``strcmp`` for strings
|
||||
* String case changes: ``lowercase``, ``uppercase``, ``titlecase``, ``capitalize``
|
||||
* String manipulation: ``re``, ``shorten``, ``substr``
|
||||
* Other: ``assign``, ``booksize``, ``print``, ``format_date``,
|
||||
|
||||
.. _general_mode:
|
||||
|
||||
Using general program mode
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
16450
src/calibre/translations/et.po
Normal file
16450
src/calibre/translations/et.po
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -594,7 +594,80 @@ class BuiltinFirstNonEmpty(BuiltinFormatterFunction):
|
||||
i += 1
|
||||
return ''
|
||||
|
||||
class BuiltinAnd(BuiltinFormatterFunction):
|
||||
name = 'and'
|
||||
arg_count = -1
|
||||
doc = _('and(value, value, ...) -- '
|
||||
'returns the string "1" if all values are not empty, otherwise '
|
||||
'returns the empty string. This function works well with test or '
|
||||
'first_non_empty. You can have as many values as you want.')
|
||||
|
||||
def evaluate(self, formatter, kwargs, mi, locals, *args):
|
||||
i = 0
|
||||
while i < len(args):
|
||||
if not args[i]:
|
||||
return ''
|
||||
i += 1
|
||||
return '1'
|
||||
|
||||
class BuiltinOr(BuiltinFormatterFunction):
|
||||
name = 'or'
|
||||
arg_count = -1
|
||||
doc = _('or(value, value, ...) -- '
|
||||
'returns the string "1" if any value is not empty, otherwise '
|
||||
'returns the empty string. This function works well with test or '
|
||||
'first_non_empty. You can have as many values as you want.')
|
||||
|
||||
def evaluate(self, formatter, kwargs, mi, locals, *args):
|
||||
i = 0
|
||||
while i < len(args):
|
||||
if args[i]:
|
||||
return '1'
|
||||
i += 1
|
||||
return ''
|
||||
|
||||
class BuiltinNot(BuiltinFormatterFunction):
|
||||
name = 'not'
|
||||
arg_count = 1
|
||||
doc = _('not(value) -- '
|
||||
'returns the string "1" if the value is empty, otherwise '
|
||||
'returns the empty string. This function works well with test or '
|
||||
'first_non_empty. You can have as many values as you want.')
|
||||
|
||||
def evaluate(self, formatter, kwargs, mi, locals, *args):
|
||||
i = 0
|
||||
while i < len(args):
|
||||
if args[i]:
|
||||
return '1'
|
||||
i += 1
|
||||
return ''
|
||||
|
||||
class BuiltinMergeLists(BuiltinFormatterFunction):
|
||||
name = 'merge_lists'
|
||||
arg_count = 3
|
||||
doc = _('merge_lists(list1, list2, separator) -- '
|
||||
'return a list made by merging the items in list1 and list2, '
|
||||
'removing duplicate items using a case-insensitive compare. If '
|
||||
'items differ in case, the one in list1 is used. '
|
||||
'The items in list1 and list2 are separated by separator, as are '
|
||||
'the items in the returned list.')
|
||||
|
||||
def evaluate(self, formatter, kwargs, mi, locals, list1, list2, separator):
|
||||
l1 = [l.strip() for l in list1.split(separator) if l.strip()]
|
||||
l2 = [l.strip() for l in list2.split(separator) if l.strip()]
|
||||
lcl1 = set([icu_lower(l) for l in l1])
|
||||
|
||||
res = []
|
||||
for i in l1:
|
||||
res.append(i)
|
||||
for i in l2:
|
||||
if icu_lower(i) not in lcl1:
|
||||
res.append(i)
|
||||
return ', '.join(sorted(res, key=sort_key))
|
||||
|
||||
|
||||
builtin_add = BuiltinAdd()
|
||||
builtin_and = BuiltinAnd()
|
||||
builtin_assign = BuiltinAssign()
|
||||
builtin_booksize = BuiltinBooksize()
|
||||
builtin_capitalize = BuiltinCapitalize()
|
||||
@ -611,7 +684,10 @@ builtin_in_list = BuiltinInList()
|
||||
builtin_list_item = BuiltinListitem()
|
||||
builtin_lookup = BuiltinLookup()
|
||||
builtin_lowercase = BuiltinLowercase()
|
||||
builtin_merge_lists = BuiltinMergeLists()
|
||||
builtin_multiply = BuiltinMultiply()
|
||||
builtin_not = BuiltinNot()
|
||||
builtin_or = BuiltinOr()
|
||||
builtin_print = BuiltinPrint()
|
||||
builtin_raw_field = BuiltinRaw_field()
|
||||
builtin_re = BuiltinRe()
|
||||
|
Loading…
x
Reference in New Issue
Block a user