This commit is contained in:
Sengian 2011-01-21 19:22:53 +01:00
commit 03c61a48ef
61 changed files with 3278 additions and 1123 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@ -1,6 +1,6 @@
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2008-2010, Darko Miletic <darko.miletic at gmail.com>' __copyright__ = '2008-2011, Darko Miletic <darko.miletic at gmail.com>'
''' '''
blic.rs blic.rs
''' '''
@ -21,21 +21,53 @@ class Blic(BasicNewsRecipe):
masthead_url = 'http://www.blic.rs/resources/images/header/header_back.png' masthead_url = 'http://www.blic.rs/resources/images/header/header_back.png'
language = 'sr' language = 'sr'
publication_type = 'newspaper' publication_type = 'newspaper'
extra_css = '@font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)} @font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)} body{font-family: Georgia, serif1, serif} .article_description{font-family: Arial, sans1, sans-serif} .img_full{float: none} img{margin-bottom: 0.8em} ' extra_css = """
@font-face {font-family: "serif1";src:url(res:///opt/sony/ebook/FONT/tt0011m_.ttf)}
@font-face {font-family: "sans1";src:url(res:///opt/sony/ebook/FONT/tt0003m_.ttf)}
body{font-family: Georgia, serif1, serif}
.articledescription,#nadnaslov,.article_info{font-family: Arial, sans1, sans-serif}
.img_full{float: none}
#nadnaslov{font-size: small}
#article_lead{font-size: 1.5em}
h1{color: red}
.potpis{font-size: x-small; color: gray}
.article_info{font-size: small}
img{margin-bottom: 0.8em; margin-top: 0.8em; display: block}
"""
conversion_options = { conversion_options = {
'comment' : description 'comment' : description
, 'tags' : category , 'tags' : category
, 'publisher': publisher , 'publisher': publisher
, 'language' : language , 'language' : language
, 'linearize_tables' : True
} }
preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')] preprocess_regexps = [(re.compile(u'\u0110'), lambda match: u'\u00D0')]
remove_tags_before = dict(name='div', attrs={'id':'article_info'}) remove_tags_before = dict(name='div', attrs={'id':'article_info'})
remove_tags = [dict(name=['object','link'])] remove_tags = [dict(name=['object','link','meta','base','object','embed'])]
remove_attributes = ['width','height'] remove_attributes = ['width','height','m_id','m_ext','mlg_id','poll_id','v_id']
feeds = [(u'Danasnje Vesti', u'http://www.blic.rs/rss/danasnje-vesti')] feeds = [
(u'Politika' , u'http://www.blic.rs/rss/Vesti/Politika')
,(u'Tema Dana' , u'http://www.blic.rs/rss/Vesti/Tema-Dana')
,(u'Svet' , u'http://www.blic.rs/rss/Vesti/Svet')
,(u'Drustvo' , u'http://www.blic.rs/rss/Vesti/Drustvo')
,(u'Ekonomija' , u'http://www.blic.rs/rss/Vesti/Ekonomija')
,(u'Hronika' , u'http://www.blic.rs/rss/Vesti/Hronika')
,(u'Beograd' , u'http://www.blic.rs/rss/Vesti/Beograd')
,(u'Srbija' , u'http://www.blic.rs/rss/Vesti/Srbija')
,(u'Vojvodina' , u'http://www.blic.rs/rss/Vesti/Vojvodina')
,(u'Republika Srpska' , u'http://www.blic.rs/rss/Vesti/Republika-Srpska')
,(u'Reportaza' , u'http://www.blic.rs/rss/Vesti/Reportaza')
,(u'Dodatak' , u'http://www.blic.rs/rss/Vesti/Dodatak')
,(u'Zabava' , u'http://www.blic.rs/rss/Zabava')
,(u'Kultura' , u'http://www.blic.rs/rss/Kultura')
,(u'Slobodno Vreme' , u'http://www.blic.rs/rss/Slobodno-vreme')
,(u'IT' , u'http://www.blic.rs/rss/IT')
,(u'Komentar' , u'http://www.blic.rs/rss/Komentar')
,(u'Intervju' , u'http://www.blic.rs/rss/Intervju')
]
def print_version(self, url): def print_version(self, url):
@ -44,4 +76,4 @@ class Blic(BasicNewsRecipe):
def preprocess_html(self, soup): def preprocess_html(self, soup):
for item in soup.findAll(style=True): for item in soup.findAll(style=True):
del item['style'] del item['style']
return self.adeify_images(soup) return soup

View File

@ -7,22 +7,29 @@ class DallasNews(BasicNewsRecipe):
max_articles_per_feed = 25 max_articles_per_feed = 25
no_stylesheets = True no_stylesheets = True
remove_tags_before = dict(name='h2', attrs={'class':'vitstoryheadline'}) use_embedded_content = False
remove_tags_after = dict(name='div', attrs={'style':'width: 100%; clear: right'}) remove_tags_before = dict(name='h1')
remove_tags_after = dict(name='div', attrs={'id':'article_tools_bottom'}) keep_only_tags = {'class':lambda x: x and 'article' in x}
remove_tags = [ remove_tags = [
dict(name='iframe'), {'class':['DMNSocialTools', 'article ', 'article first ', 'article premium']},
dict(name='div', attrs={'class':'biblockmore'}),
dict(name='div', attrs={'style':'width: 100%; clear: right'}),
dict(name='div', attrs={'id':'article_tools_bottom'}),
#dict(name='ul', attrs={'class':'articleTools'}),
] ]
feeds = [ feeds = [
('Latest News', 'http://www.dallasnews.com/newskiosk/rss/dallasnewslatestnews.xml'), ('Local News',
('Local News', 'http://www.dallasnews.com/newskiosk/rss/dallasnewslocalnews.xml'), 'http://www.dallasnews.com/news/politics/local-politics/?rss'),
('Nation and World', 'http://www.dallasnews.com/newskiosk/rss/dallasnewsnationworld.xml'), ('National Politics',
('Politics', 'http://www.dallasnews.com/newskiosk/rss/dallasnewsnationalpolitics.xml'), 'http://www.dallasnews.com/news/politics/national-politic/?rss'),
('Science', 'http://www.dallasnews.com/newskiosk/rss/dallasnewsscience.xml'), ('State Politics',
'http://www.dallasnews.com/news/politics/state-politics/?rss'),
('Religion',
'http://www.dallasnews.com/news/religion/?rss'),
('Crime',
'http://www.dallasnews.com/news/crime/headlines/?rss'),
('Celebrity News',
'http://www.dallasnews.com/entertainment/celebrity-news/?rss&listname=TopStories'),
('Nation',
'http://www.dallasnews.com/news/nation-world/nation/?rss'),
('World',
'http://www.dallasnews.com/news/nation-world/world/?rss'),
] ]

View File

@ -0,0 +1,64 @@
__license__ = 'GPL v3'
__copyright__ = '2011, Darko Miletic <darko.miletic at gmail.com>'
'''
gulfnews.com
'''
from calibre.web.feeds.news import BasicNewsRecipe
class GulfNews(BasicNewsRecipe):
title = 'Gulf News'
__author__ = 'Darko Miletic'
description = 'News from United Arab Emirrates, persian gulf and rest of the world'
publisher = 'Al Nisr Publishing LLC'
category = 'news, politics, UAE, world'
oldest_article = 2
max_articles_per_feed = 200
no_stylesheets = True
encoding = 'utf8'
use_embedded_content = False
language = 'en'
remove_empty_feeds = True
publication_type = 'newsportal'
masthead_url = 'http://gulfnews.com/media/img/gulf_news_logo.jpg'
extra_css = """
body{font-family: Arial,Helvetica,sans-serif }
img{margin-bottom: 0.4em; display:block}
h1{font-family: Georgia, 'Times New Roman', Times, serif}
ol,ul{list-style: none}
.synopsis{font-size: small}
.details{font-size: x-small}
.image{font-size: xx-small}
"""
conversion_options = {
'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
}
remove_tags = [
dict(name=['meta','link','object','embed'])
,dict(attrs={'class':['quickLinks','ratings']})
,dict(attrs={'id':'imageSelector'})
]
remove_attributes=['lang']
keep_only_tags=[
dict(name='h1')
,dict(attrs={'class':['synopsis','details','image','article']})
]
feeds = [
(u'UAE News' , u'http://gulfnews.com/cmlink/1.446094')
,(u'Business' , u'http://gulfnews.com/cmlink/1.446098')
,(u'Entertainment' , u'http://gulfnews.com/cmlink/1.446095')
,(u'Sport' , u'http://gulfnews.com/cmlink/1.446096')
,(u'Life' , u'http://gulfnews.com/cmlink/1.446097')
]
def preprocess_html(self, soup):
for item in soup.findAll(style=True):
del item['style']
return soup

View File

@ -5,6 +5,7 @@ class AdvancedUserRecipe1293122276(BasicNewsRecipe):
__author__ = 'Jack Mason' __author__ = 'Jack Mason'
author = 'IBM Global Business Services' author = 'IBM Global Business Services'
publisher = 'IBM' publisher = 'IBM'
language = 'en'
category = 'news, technology, IT, internet of things, analytics' category = 'news, technology, IT, internet of things, analytics'
oldest_article = 7 oldest_article = 7
max_articles_per_feed = 30 max_articles_per_feed = 30

View File

@ -6,6 +6,7 @@ class KANewsRecipe(BasicNewsRecipe):
description = u'Nachrichten aus Karlsruhe, Deutschland und der Welt.' description = u'Nachrichten aus Karlsruhe, Deutschland und der Welt.'
__author__ = 'tfeld' __author__ = 'tfeld'
lang='de' lang='de'
language = 'de'
no_stylesheets = True no_stylesheets = True
oldest_article = 7 oldest_article = 7

View File

@ -4,6 +4,7 @@ class AdvancedUserRecipe1295262156(BasicNewsRecipe):
title = u'kath.net' title = u'kath.net'
__author__ = 'Bobus' __author__ = 'Bobus'
oldest_article = 7 oldest_article = 7
language = 'en'
max_articles_per_feed = 100 max_articles_per_feed = 100
feeds = [(u'kath.net', u'http://www.kath.net/2005/xml/index.xml')] feeds = [(u'kath.net', u'http://www.kath.net/2005/xml/index.xml')]

View File

@ -3,12 +3,17 @@ from calibre.web.feeds.news import BasicNewsRecipe
class AdvancedUserRecipe1274742400(BasicNewsRecipe): class AdvancedUserRecipe1274742400(BasicNewsRecipe):
title = u'Las Vegas Review Journal' title = u'Las Vegas Review Journal'
__author__ = 'Joel' __author__ = 'Kovid Goyal'
language = 'en' language = 'en'
oldest_article = 7 oldest_article = 7
max_articles_per_feed = 100 max_articles_per_feed = 100
keep_only_tags = [dict(id='content-main')]
remove_tags = [dict(id=['right-col-content', 'trending-topics']),
{'class':['ppy-outer']}
]
no_stylesheets = True
feeds = [ feeds = [
(u'News', u'http://www.lvrj.com/news.rss'), (u'News', u'http://www.lvrj.com/news.rss'),

View File

@ -20,8 +20,8 @@ class LaVanguardia(BasicNewsRecipe):
max_articles_per_feed = 100 max_articles_per_feed = 100
no_stylesheets = True no_stylesheets = True
use_embedded_content = False use_embedded_content = False
delay = 1 delay = 5
encoding = 'cp1252' # encoding = 'cp1252'
language = 'es' language = 'es'
direction = 'ltr' direction = 'ltr'
@ -35,8 +35,8 @@ class LaVanguardia(BasicNewsRecipe):
html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"' html2epub_options = 'publisher="' + publisher + '"\ncomments="' + description + '"\ntags="' + category + '"'
feeds = [ feeds = [
(u'Ciudadanos' , u'http://feeds.feedburner.com/lavanguardia/ciudadanos' ) (u'Portada' , u'http://feeds.feedburner.com/lavanguardia/home' )
,(u'Cultura' , u'http://feeds.feedburner.com/lavanguardia/cultura' ) ,(u'Cultura' , u'http://feeds.feedburner.com/lavanguardia/cultura' )
,(u'Deportes' , u'http://feeds.feedburner.com/lavanguardia/deportes' ) ,(u'Deportes' , u'http://feeds.feedburner.com/lavanguardia/deportes' )
,(u'Economia' , u'http://feeds.feedburner.com/lavanguardia/economia' ) ,(u'Economia' , u'http://feeds.feedburner.com/lavanguardia/economia' )
,(u'El lector opina' , u'http://feeds.feedburner.com/lavanguardia/lectoropina' ) ,(u'El lector opina' , u'http://feeds.feedburner.com/lavanguardia/lectoropina' )
@ -45,17 +45,17 @@ class LaVanguardia(BasicNewsRecipe):
,(u'Internet y tecnologia', u'http://feeds.feedburner.com/lavanguardia/internet' ) ,(u'Internet y tecnologia', u'http://feeds.feedburner.com/lavanguardia/internet' )
,(u'Motor' , u'http://feeds.feedburner.com/lavanguardia/motor' ) ,(u'Motor' , u'http://feeds.feedburner.com/lavanguardia/motor' )
,(u'Politica' , u'http://feeds.feedburner.com/lavanguardia/politica' ) ,(u'Politica' , u'http://feeds.feedburner.com/lavanguardia/politica' )
,(u'Sucessos' , u'http://feeds.feedburner.com/lavanguardia/sucesos' ) ,(u'Sucesos' , u'http://feeds.feedburner.com/lavanguardia/sucesos' )
] ]
keep_only_tags = [ keep_only_tags = [
dict(name='div', attrs={'class':'element1_3'}) dict(name='div', attrs={'class':'detalle noticia'})
] ]
remove_tags = [ remove_tags = [
dict(name=['object','link','script']) dict(name=['object','link','script'])
,dict(name='div', attrs={'class':['colC','peu']}) ,dict(name='div', attrs={'class':['colC','peu','jstoolbar']})
] ]
remove_tags_after = [dict(name='div', attrs={'class':'text'})] remove_tags_after = [dict(name='div', attrs={'class':'text'})]
@ -67,4 +67,3 @@ class LaVanguardia(BasicNewsRecipe):
for item in soup.findAll(style=True): for item in soup.findAll(style=True):
del item['style'] del item['style']
return soup return soup

View File

@ -10,6 +10,7 @@ import re
class NationalGeographicNews(BasicNewsRecipe): class NationalGeographicNews(BasicNewsRecipe):
title = u'National Geographic News' title = u'National Geographic News'
oldest_article = 7 oldest_article = 7
language = 'en'
max_articles_per_feed = 100 max_articles_per_feed = 100
remove_javascript = True remove_javascript = True
no_stylesheets = True no_stylesheets = True

View File

@ -241,7 +241,7 @@ def get_parsed_proxy(typ='http', debug=True):
return ans return ans
def browser(honor_time=True, max_time=2, mobile_browser=False): def browser(honor_time=True, max_time=2, mobile_browser=False, user_agent=None):
''' '''
Create a mechanize browser for web scraping. The browser handles cookies, Create a mechanize browser for web scraping. The browser handles cookies,
refresh requests and ignores robots.txt. Also uses proxy if avaialable. refresh requests and ignores robots.txt. Also uses proxy if avaialable.
@ -253,8 +253,10 @@ def browser(honor_time=True, max_time=2, mobile_browser=False):
opener = Browser() opener = Browser()
opener.set_handle_refresh(True, max_time=max_time, honor_time=honor_time) opener.set_handle_refresh(True, max_time=max_time, honor_time=honor_time)
opener.set_handle_robots(False) opener.set_handle_robots(False)
opener.addheaders = [('User-agent', ' Mozilla/5.0 (Windows; U; Windows CE 5.1; rv:1.8.1a3) Gecko/20060610 Minimo/0.016' if mobile_browser else \ if user_agent is None:
'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.13) Gecko/20101210 Gentoo Firefox/3.6.13')] user_agent = ' Mozilla/5.0 (Windows; U; Windows CE 5.1; rv:1.8.1a3) Gecko/20060610 Minimo/0.016' if mobile_browser else \
'Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.2.13) Gecko/20101210 Gentoo Firefox/3.6.13'
opener.addheaders = [('User-agent', user_agent)]
http_proxy = get_proxies().get('http', None) http_proxy = get_proxies().get('http', None)
if http_proxy: if http_proxy:
opener.set_proxies({'http':http_proxy}) opener.set_proxies({'http':http_proxy})

View File

@ -21,7 +21,7 @@ class ANDROID(USBMS):
# HTC # HTC
0x0bb4 : { 0x0c02 : [0x100, 0x0227, 0x0226], 0x0c01 : [0x100, 0x0227], 0x0ff9 0x0bb4 : { 0x0c02 : [0x100, 0x0227, 0x0226], 0x0c01 : [0x100, 0x0227], 0x0ff9
: [0x0100, 0x0227, 0x0226], 0x0c87: [0x0100, 0x0227, 0x0226], : [0x0100, 0x0227, 0x0226], 0x0c87: [0x0100, 0x0227, 0x0226],
0xc92 : [0x100], 0xc97: [0x226]}, 0xc92 : [0x100], 0xc97: [0x226], 0xc99 : [0x0100]},
# Eken # Eken
0x040d : { 0x8510 : [0x0001], 0x0851 : [0x1] }, 0x040d : { 0x8510 : [0x0001], 0x0851 : [0x1] },
@ -54,7 +54,7 @@ class ANDROID(USBMS):
0x1004 : { 0x61cc : [0x100] }, 0x1004 : { 0x61cc : [0x100] },
# Archos # Archos
0x0e79 : { 0x1420 : [0x0216]}, 0x0e79 : { 0x1419: [0x0216], 0x1420 : [0x0216]},
} }
EBOOK_DIR_MAIN = ['eBooks/import', 'wordplayer/calibretransfer', 'Books'] EBOOK_DIR_MAIN = ['eBooks/import', 'wordplayer/calibretransfer', 'Books']
@ -70,10 +70,10 @@ class ANDROID(USBMS):
'__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD', 'SGH-I897', '__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD', 'SGH-I897',
'GT-I9000', 'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-I9000', 'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID',
'SCH-I500_CARD', 'SPH-D700_CARD', 'MB810', 'GT-P1000', 'DESIRE', 'SCH-I500_CARD', 'SPH-D700_CARD', 'MB810', 'GT-P1000', 'DESIRE',
'SGH-T849', '_MB300', 'A70S', 'S_ANDROID'] 'SGH-T849', '_MB300', 'A70S', 'S_ANDROID', 'A101IT']
WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897', WINDOWS_CARD_A_MEM = ['ANDROID_PHONE', 'GT-I9000_CARD', 'SGH-I897',
'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD', 'FILE-STOR_GADGET', 'SGH-T959', 'SAMSUNG_ANDROID', 'GT-P1000_CARD',
'A70S'] 'A70S', 'A101IT']
OSX_MAIN_MEM = 'Android Device Main Memory' OSX_MAIN_MEM = 'Android Device Main Memory'

View File

@ -106,7 +106,7 @@ class PDNOVEL(USBMS):
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = '__UMS_COMPOSITE' WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = '__UMS_COMPOSITE'
THUMBNAIL_HEIGHT = 130 THUMBNAIL_HEIGHT = 130
EBOOK_DIR_MAIN = 'eBooks' EBOOK_DIR_MAIN = EBOOK_DIR_CARD_A = 'eBooks'
SUPPORTS_SUB_DIRS = False SUPPORTS_SUB_DIRS = False
DELETE_EXTS = ['.jpg', '.jpeg', '.png'] DELETE_EXTS = ['.jpg', '.jpeg', '.png']

View File

@ -98,6 +98,9 @@ class PRS505(USBMS):
THUMBNAIL_HEIGHT = 200 THUMBNAIL_HEIGHT = 200
MAX_PATH_LEN = 201 # 250 - (max(len(CACHE_THUMBNAIL), len(MEDIA_THUMBNAIL)) +
# len('main_thumbnail.jpg') + 1)
def windows_filter_pnp_id(self, pnp_id): def windows_filter_pnp_id(self, pnp_id):
return '_LAUNCHER' in pnp_id return '_LAUNCHER' in pnp_id
@ -201,10 +204,13 @@ class PRS505(USBMS):
self._card_b_prefix if idx == 2 \ self._card_b_prefix if idx == 2 \
else self._main_prefix else self._main_prefix
for book in bl: for book in bl:
p = os.path.join(prefix, book.lpath) try:
self._upload_cover(os.path.dirname(p), p = os.path.join(prefix, book.lpath)
os.path.splitext(os.path.basename(p))[0], self._upload_cover(os.path.dirname(p),
book, p) os.path.splitext(os.path.basename(p))[0],
book, p)
except:
debug_print('FAILED to upload cover', p)
else: else:
debug_print('PRS505: NOT uploading covers in sync_booklists') debug_print('PRS505: NOT uploading covers in sync_booklists')
@ -229,7 +235,10 @@ class PRS505(USBMS):
debug_print('PRS505: not uploading cover') debug_print('PRS505: not uploading cover')
return return
debug_print('PRS505: uploading cover') debug_print('PRS505: uploading cover')
self._upload_cover(path, filename, metadata, filepath) try:
self._upload_cover(path, filename, metadata, filepath)
except:
debug_print('FAILED to upload cover', filepath)
def _upload_cover(self, path, filename, metadata, filepath): def _upload_cover(self, path, filename, metadata, filepath):
if metadata.thumbnail and metadata.thumbnail[-1]: if metadata.thumbnail and metadata.thumbnail[-1]:

View File

@ -98,6 +98,9 @@ class Device(DeviceConfig, DevicePlugin):
# copy these back to the library # copy these back to the library
BACKLOADING_ERROR_MESSAGE = None BACKLOADING_ERROR_MESSAGE = None
#: The maximum length of paths created on the device
MAX_PATH_LEN = 250
def reset(self, key='-1', log_packets=False, report_progress=None, def reset(self, key='-1', log_packets=False, report_progress=None,
detected_device=None): detected_device=None):
self._main_prefix = self._card_a_prefix = self._card_b_prefix = None self._main_prefix = self._card_a_prefix = self._card_b_prefix = None
@ -875,7 +878,7 @@ class Device(DeviceConfig, DevicePlugin):
def create_upload_path(self, path, mdata, fname, create_dirs=True): def create_upload_path(self, path, mdata, fname, create_dirs=True):
path = os.path.abspath(path) path = os.path.abspath(path)
extra_components = [] maxlen = self.MAX_PATH_LEN
special_tag = None special_tag = None
if mdata.tags: if mdata.tags:
@ -902,7 +905,7 @@ class Device(DeviceConfig, DevicePlugin):
app_id = str(getattr(mdata, 'application_id', '')) app_id = str(getattr(mdata, 'application_id', ''))
# The db id will be in the created filename # The db id will be in the created filename
extra_components = get_components(template, mdata, fname, extra_components = get_components(template, mdata, fname,
timefmt=opts.send_timefmt, length=250-len(app_id)-1) timefmt=opts.send_timefmt, length=maxlen-len(app_id)-1)
if not extra_components: if not extra_components:
extra_components.append(sanitize(self.filename_callback(fname, extra_components.append(sanitize(self.filename_callback(fname,
mdata))) mdata)))
@ -937,12 +940,11 @@ class Device(DeviceConfig, DevicePlugin):
return ans return ans
extra_components = list(map(remove_trailing_periods, extra_components)) extra_components = list(map(remove_trailing_periods, extra_components))
components = shorten_components_to(250 - len(path), extra_components) components = shorten_components_to(maxlen - len(path), extra_components)
components = self.sanitize_path_components(components) components = self.sanitize_path_components(components)
filepath = os.path.join(path, *components) filepath = os.path.join(path, *components)
filedir = os.path.dirname(filepath) filedir = os.path.dirname(filepath)
if create_dirs and not os.path.exists(filedir): if create_dirs and not os.path.exists(filedir):
os.makedirs(filedir) os.makedirs(filedir)

View File

@ -42,6 +42,12 @@ option.
For full documentation of the conversion system see For full documentation of the conversion system see
''') + 'http://calibre-ebook.com/user_manual/conversion.html' ''') + 'http://calibre-ebook.com/user_manual/conversion.html'
HEURISTIC_OPTIONS = ['markup_chapter_headings',
'italicize_common_cases', 'fix_indents',
'html_unwrap_factor', 'unwrap_lines',
'delete_blank_paragraphs', 'format_scene_breaks',
'dehyphenate', 'renumber_headings']
def print_help(parser, log): def print_help(parser, log):
help = parser.format_help().encode(preferred_encoding, 'replace') help = parser.format_help().encode(preferred_encoding, 'replace')
log(help) log(help)
@ -83,6 +89,8 @@ def option_recommendation_to_cli_option(add_option, rec):
if opt.long_switch == 'verbose': if opt.long_switch == 'verbose':
attrs['action'] = 'count' attrs['action'] = 'count'
attrs.pop('type', '') attrs.pop('type', '')
if opt.name in HEURISTIC_OPTIONS and rec.recommended_value is True:
switches = ['--disable-'+opt.long_switch]
add_option(Option(*switches, **attrs)) add_option(Option(*switches, **attrs))
def add_input_output_options(parser, plumber): def add_input_output_options(parser, plumber):
@ -129,18 +137,15 @@ def add_pipeline_options(parser, plumber):
'asciiize', 'asciiize',
] ]
), ),
'HEURISTIC PROCESSING' : ( 'HEURISTIC PROCESSING' : (
_('Modify the document text and structure using common patterns.'), _('Modify the document text and structure using common'
[ ' patterns. Disabled by default. Use %s to enable. '
'enable_heuristics', 'markup_chapter_headings', ' Individual actions can be disabled with the %s options.')
'italicize_common_cases', 'fix_indents', % ('--enable-heuristics', '--disable-*'),
'html_unwrap_factor', 'unwrap_lines', ['enable_heuristics'] + HEURISTIC_OPTIONS
'delete_blank_paragraphs', 'format_scene_breaks',
'dehyphenate', 'renumber_headings',
]
), ),
'SEARCH AND REPLACE' : ( 'SEARCH AND REPLACE' : (
_('Modify the document text and structure using user defined patterns.'), _('Modify the document text and structure using user defined patterns.'),
[ [

View File

@ -72,7 +72,8 @@ class Plumber(object):
] ]
def __init__(self, input, output, log, report_progress=DummyReporter(), def __init__(self, input, output, log, report_progress=DummyReporter(),
dummy=False, merge_plugin_recs=True, abort_after_input_dump=False): dummy=False, merge_plugin_recs=True, abort_after_input_dump=False,
override_input_metadata=False):
''' '''
:param input: Path to input file. :param input: Path to input file.
:param output: Path to output file/directory :param output: Path to output file/directory
@ -87,6 +88,7 @@ class Plumber(object):
self.log = log self.log = log
self.ui_reporter = report_progress self.ui_reporter = report_progress
self.abort_after_input_dump = abort_after_input_dump self.abort_after_input_dump = abort_after_input_dump
self.override_input_metadata = override_input_metadata
# Pipeline options {{{ # Pipeline options {{{
# Initialize the conversion options that are independent of input and # Initialize the conversion options that are independent of input and
@ -490,19 +492,19 @@ OptionRecommendation(name='enable_heuristics',
'heuristic processing to take place.')), 'heuristic processing to take place.')),
OptionRecommendation(name='markup_chapter_headings', OptionRecommendation(name='markup_chapter_headings',
recommended_value=False, level=OptionRecommendation.LOW, recommended_value=True, level=OptionRecommendation.LOW,
help=_('Detect unformatted chapter headings and sub headings. Change ' help=_('Detect unformatted chapter headings and sub headings. Change '
'them to h2 and h3 tags. This setting will not create a TOC, ' 'them to h2 and h3 tags. This setting will not create a TOC, '
'but can be used in conjunction with structure detection to create ' 'but can be used in conjunction with structure detection to create '
'one.')), 'one.')),
OptionRecommendation(name='italicize_common_cases', OptionRecommendation(name='italicize_common_cases',
recommended_value=False, level=OptionRecommendation.LOW, recommended_value=True, level=OptionRecommendation.LOW,
help=_('Look for common words and patterns that denote ' help=_('Look for common words and patterns that denote '
'italics and italicize them.')), 'italics and italicize them.')),
OptionRecommendation(name='fix_indents', OptionRecommendation(name='fix_indents',
recommended_value=False, level=OptionRecommendation.LOW, recommended_value=True, level=OptionRecommendation.LOW,
help=_('Turn indentation created from multiple non-breaking space entities ' help=_('Turn indentation created from multiple non-breaking space entities '
'into CSS indents.')), 'into CSS indents.')),
@ -515,28 +517,28 @@ OptionRecommendation(name='html_unwrap_factor',
'be reduced')), 'be reduced')),
OptionRecommendation(name='unwrap_lines', OptionRecommendation(name='unwrap_lines',
recommended_value=False, level=OptionRecommendation.LOW, recommended_value=True, level=OptionRecommendation.LOW,
help=_('Unwrap lines using punctuation and other formatting clues.')), help=_('Unwrap lines using punctuation and other formatting clues.')),
OptionRecommendation(name='delete_blank_paragraphs', OptionRecommendation(name='delete_blank_paragraphs',
recommended_value=False, level=OptionRecommendation.LOW, recommended_value=True, level=OptionRecommendation.LOW,
help=_('Remove empty paragraphs from the document when they exist between ' help=_('Remove empty paragraphs from the document when they exist between '
'every other paragraph')), 'every other paragraph')),
OptionRecommendation(name='format_scene_breaks', OptionRecommendation(name='format_scene_breaks',
recommended_value=False, level=OptionRecommendation.LOW, recommended_value=True, level=OptionRecommendation.LOW,
help=_('Left aligned scene break markers are center aligned. ' help=_('Left aligned scene break markers are center aligned. '
'Replace soft scene breaks that use multiple blank lines with' 'Replace soft scene breaks that use multiple blank lines with'
'horizontal rules.')), 'horizontal rules.')),
OptionRecommendation(name='dehyphenate', OptionRecommendation(name='dehyphenate',
recommended_value=False, level=OptionRecommendation.LOW, recommended_value=True, level=OptionRecommendation.LOW,
help=_('Analyze hyphenated words throughout the document. The ' help=_('Analyze hyphenated words throughout the document. The '
'document itself is used as a dictionary to determine whether hyphens ' 'document itself is used as a dictionary to determine whether hyphens '
'should be retained or removed.')), 'should be retained or removed.')),
OptionRecommendation(name='renumber_headings', OptionRecommendation(name='renumber_headings',
recommended_value=False, level=OptionRecommendation.LOW, recommended_value=True, level=OptionRecommendation.LOW,
help=_('Looks for occurrences of sequential <h1> or <h2> tags. ' help=_('Looks for occurrences of sequential <h1> or <h2> tags. '
'The tags are renumbered to prevent splitting in the middle ' 'The tags are renumbered to prevent splitting in the middle '
'of chapter headings.')), 'of chapter headings.')),
@ -924,7 +926,8 @@ OptionRecommendation(name='sr3_replace',
self.opts.dest = self.opts.output_profile self.opts.dest = self.opts.output_profile
from calibre.ebooks.oeb.transforms.metadata import MergeMetadata from calibre.ebooks.oeb.transforms.metadata import MergeMetadata
MergeMetadata()(self.oeb, self.user_metadata, self.opts) MergeMetadata()(self.oeb, self.user_metadata, self.opts,
override_input_metadata=self.override_input_metadata)
pr(0.2) pr(0.2)
self.flush() self.flush()

View File

@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en'
import functools, re import functools, re
from calibre import entity_to_unicode from calibre import entity_to_unicode, as_unicode
XMLDECL_RE = re.compile(r'^\s*<[?]xml.*?[?]>') XMLDECL_RE = re.compile(r'^\s*<[?]xml.*?[?]>')
SVG_NS = 'http://www.w3.org/2000/svg' SVG_NS = 'http://www.w3.org/2000/svg'
@ -201,7 +201,7 @@ class Dehyphenator(object):
lookupword = self.removesuffixes.sub('', dehyphenated) lookupword = self.removesuffixes.sub('', dehyphenated)
else: else:
lookupword = dehyphenated lookupword = dehyphenated
if len(firsthalf) > 3 and self.prefixes.match(firsthalf) is None: if len(firsthalf) > 4 and self.prefixes.match(firsthalf) is None:
lookupword = self.removeprefix.sub('', lookupword) lookupword = self.removeprefix.sub('', lookupword)
if self.verbose > 2: if self.verbose > 2:
self.log("lookup word is: "+str(lookupword)+", orig is: " + str(hyphenated)) self.log("lookup word is: "+str(lookupword)+", orig is: " + str(hyphenated))
@ -224,6 +224,10 @@ class Dehyphenator(object):
return firsthalf+u'\u2014'+wraptags+secondhalf return firsthalf+u'\u2014'+wraptags+secondhalf
else: else:
if self.format == 'individual_words' and len(firsthalf) + len(secondhalf) <= 6:
if self.verbose > 2:
self.log("too short, returned hyphenated word: " + str(hyphenated))
return hyphenated
if len(firsthalf) <= 2 and len(secondhalf) <= 2: if len(firsthalf) <= 2 and len(secondhalf) <= 2:
if self.verbose > 2: if self.verbose > 2:
self.log("too short, returned hyphenated word: " + str(hyphenated)) self.log("too short, returned hyphenated word: " + str(hyphenated))
@ -459,11 +463,12 @@ class HTMLPreProcessor(object):
try: try:
search_re = re.compile(search_pattern) search_re = re.compile(search_pattern)
replace_txt = getattr(self.extra_opts, replace, '') replace_txt = getattr(self.extra_opts, replace, '')
if replace_txt == None: if not replace_txt:
replace_txt = '' replace_txt = ''
rules.insert(0, (search_re, replace_txt)) rules.insert(0, (search_re, replace_txt))
except Exception as e: except Exception as e:
self.log.error('Failed to parse %s regexp because %s' % (search, e)) self.log.error('Failed to parse %r regexp because %s' %
(search, as_unicode(e)))
end_rules = [] end_rules = []
# delete soft hyphens - moved here so it's executed after header/footer removal # delete soft hyphens - moved here so it's executed after header/footer removal

View File

@ -4,7 +4,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
Fetch cover from LibraryThing.com based on ISBN number. Fetch cover from LibraryThing.com based on ISBN number.
''' '''
import sys, socket, os, re import sys, socket, os, re, random
from lxml import html from lxml import html
import mechanize import mechanize
@ -16,13 +16,26 @@ from calibre.ebooks.chardet import strip_encoding_declarations
OPENLIBRARY = 'http://covers.openlibrary.org/b/isbn/%s-L.jpg?default=false' OPENLIBRARY = 'http://covers.openlibrary.org/b/isbn/%s-L.jpg?default=false'
def get_ua():
choices = [
'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.11) Gecko/20101012 Firefox/3.6.11'
'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)'
'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)'
'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1)'
'Mozilla/5.0 (iPhone; U; CPU iPhone OS 3_0 like Mac OS X; en-us) AppleWebKit/528.18 (KHTML, like Gecko) Version/4.0 Mobile/7A341 Safari/528.16'
'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.19 (KHTML, like Gecko) Chrome/0.2.153.1 Safari/525.19'
'Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.2.11) Gecko/20101012 Firefox/3.6.11'
]
return choices[random.randint(0, len(choices)-1)]
class HeadRequest(mechanize.Request): class HeadRequest(mechanize.Request):
def get_method(self): def get_method(self):
return 'HEAD' return 'HEAD'
def check_for_cover(isbn, timeout=5.): def check_for_cover(isbn, timeout=5.):
br = browser() br = browser(user_agent=get_ua())
br.set_handle_redirect(False) br.set_handle_redirect(False)
try: try:
br.open_novisit(HeadRequest(OPENLIBRARY%isbn), timeout=timeout) br.open_novisit(HeadRequest(OPENLIBRARY%isbn), timeout=timeout)
@ -51,7 +64,7 @@ def login(br, username, password, force=True):
def cover_from_isbn(isbn, timeout=5., username=None, password=None): def cover_from_isbn(isbn, timeout=5., username=None, password=None):
src = None src = None
br = browser() br = browser(user_agent=get_ua())
try: try:
return br.open(OPENLIBRARY%isbn, timeout=timeout).read(), 'jpg' return br.open(OPENLIBRARY%isbn, timeout=timeout).read(), 'jpg'
except: except:
@ -100,7 +113,7 @@ def get_social_metadata(title, authors, publisher, isbn, username=None,
from calibre.ebooks.metadata import MetaInformation from calibre.ebooks.metadata import MetaInformation
mi = MetaInformation(title, authors) mi = MetaInformation(title, authors)
if isbn: if isbn:
br = browser() br = browser(user_agent=get_ua())
if username and password: if username and password:
try: try:
login(br, username, password, force=False) login(br, username, password, force=False)

View File

@ -10,7 +10,7 @@ import os
from calibre.utils.date import isoformat, now from calibre.utils.date import isoformat, now
from calibre import guess_type from calibre import guess_type
def meta_info_to_oeb_metadata(mi, m, log): def meta_info_to_oeb_metadata(mi, m, log, override_input_metadata=False):
from calibre.ebooks.oeb.base import OPF from calibre.ebooks.oeb.base import OPF
if not mi.is_null('title'): if not mi.is_null('title'):
m.clear('title') m.clear('title')
@ -29,15 +29,23 @@ def meta_info_to_oeb_metadata(mi, m, log):
if not mi.is_null('book_producer'): if not mi.is_null('book_producer'):
m.filter('contributor', lambda x : x.role.lower() == 'bkp') m.filter('contributor', lambda x : x.role.lower() == 'bkp')
m.add('contributor', mi.book_producer, role='bkp') m.add('contributor', mi.book_producer, role='bkp')
elif override_input_metadata:
m.filter('contributor', lambda x : x.role.lower() == 'bkp')
if not mi.is_null('comments'): if not mi.is_null('comments'):
m.clear('description') m.clear('description')
m.add('description', mi.comments) m.add('description', mi.comments)
elif override_input_metadata:
m.clear('description')
if not mi.is_null('publisher'): if not mi.is_null('publisher'):
m.clear('publisher') m.clear('publisher')
m.add('publisher', mi.publisher) m.add('publisher', mi.publisher)
elif override_input_metadata:
m.clear('publisher')
if not mi.is_null('series'): if not mi.is_null('series'):
m.clear('series') m.clear('series')
m.add('series', mi.series) m.add('series', mi.series)
elif override_input_metadata:
m.clear('series')
if not mi.is_null('isbn'): if not mi.is_null('isbn'):
has = False has = False
for x in m.identifier: for x in m.identifier:
@ -46,19 +54,27 @@ def meta_info_to_oeb_metadata(mi, m, log):
has = True has = True
if not has: if not has:
m.add('identifier', mi.isbn, scheme='ISBN') m.add('identifier', mi.isbn, scheme='ISBN')
elif override_input_metadata:
m.filter('identifier', lambda x: x.scheme.lower() == 'isbn')
if not mi.is_null('language'): if not mi.is_null('language'):
m.clear('language') m.clear('language')
m.add('language', mi.language) m.add('language', mi.language)
if not mi.is_null('series_index'): if not mi.is_null('series_index'):
m.clear('series_index') m.clear('series_index')
m.add('series_index', mi.format_series_index()) m.add('series_index', mi.format_series_index())
elif override_input_metadata:
m.clear('series_index')
if not mi.is_null('rating'): if not mi.is_null('rating'):
m.clear('rating') m.clear('rating')
m.add('rating', '%.2f'%mi.rating) m.add('rating', '%.2f'%mi.rating)
elif override_input_metadata:
m.clear('rating')
if not mi.is_null('tags'): if not mi.is_null('tags'):
m.clear('subject') m.clear('subject')
for t in mi.tags: for t in mi.tags:
m.add('subject', t) m.add('subject', t)
elif override_input_metadata:
m.clear('subject')
if not mi.is_null('pubdate'): if not mi.is_null('pubdate'):
m.clear('date') m.clear('date')
m.add('date', isoformat(mi.pubdate)) m.add('date', isoformat(mi.pubdate))
@ -68,9 +84,14 @@ def meta_info_to_oeb_metadata(mi, m, log):
if not mi.is_null('rights'): if not mi.is_null('rights'):
m.clear('rights') m.clear('rights')
m.add('rights', mi.rights) m.add('rights', mi.rights)
elif override_input_metadata:
m.clear('rights')
if not mi.is_null('publication_type'): if not mi.is_null('publication_type'):
m.clear('publication_type') m.clear('publication_type')
m.add('publication_type', mi.publication_type) m.add('publication_type', mi.publication_type)
elif override_input_metadata:
m.clear('publication_type')
if not m.timestamp: if not m.timestamp:
m.add('timestamp', isoformat(now())) m.add('timestamp', isoformat(now()))
@ -78,11 +99,12 @@ def meta_info_to_oeb_metadata(mi, m, log):
class MergeMetadata(object): class MergeMetadata(object):
'Merge in user metadata, including cover' 'Merge in user metadata, including cover'
def __call__(self, oeb, mi, opts): def __call__(self, oeb, mi, opts, override_input_metadata=False):
self.oeb, self.log = oeb, oeb.log self.oeb, self.log = oeb, oeb.log
m = self.oeb.metadata m = self.oeb.metadata
self.log('Merging user specified metadata...') self.log('Merging user specified metadata...')
meta_info_to_oeb_metadata(mi, m, oeb.log) meta_info_to_oeb_metadata(mi, m, oeb.log,
override_input_metadata=override_input_metadata)
cover_id = self.set_cover(mi, opts.prefer_metadata_cover) cover_id = self.set_cover(mi, opts.prefer_metadata_cover)
m.clear('cover') m.clear('cover')
if cover_id is not None: if cover_id is not None:

View File

@ -262,7 +262,7 @@ class RTFMLizer(object):
if hasattr(elem, 'tail') and elem.tail != None and elem.tail.strip() != '': if hasattr(elem, 'tail') and elem.tail != None and elem.tail.strip() != '':
if 'block' in tag_stack: if 'block' in tag_stack:
text += '%s ' % txt2rtf(elem.tail) text += '%s' % txt2rtf(elem.tail)
else: else:
text += '{\\par \\pard \\hyphpar %s}' % txt2rtf(elem.tail) text += '{\\par \\pard \\hyphpar %s}' % txt2rtf(elem.tail)

View File

@ -71,21 +71,41 @@ class TXTInput(InputFormatPlugin):
txt = txt.decode(ienc, 'replace') txt = txt.decode(ienc, 'replace')
txt = _ent_pat.sub(xml_entity_to_unicode, txt) txt = _ent_pat.sub(xml_entity_to_unicode, txt)
# Normalize line endings
txt = normalize_line_endings(txt)
if options.formatting_type == 'auto':
options.formatting_type = detect_formatting_type(txt)
if options.formatting_type == 'heuristic':
setattr(options, 'enable_heuristics', True)
setattr(options, 'markup_chapter_headings', True)
setattr(options, 'italicize_common_cases', True)
setattr(options, 'fix_indents', True)
setattr(options, 'preserve_spaces', True)
setattr(options, 'delete_blank_paragraphs', True)
setattr(options, 'format_scene_breaks', True)
setattr(options, 'dehyphenate', True)
# Determine the paragraph type of the document.
if options.paragraph_type == 'auto':
options.paragraph_type = detect_paragraph_type(txt)
if options.paragraph_type == 'unknown':
log.debug('Could not reliably determine paragraph type using block')
options.paragraph_type = 'block'
else:
log.debug('Auto detected paragraph type as %s' % options.paragraph_type)
# Preserve spaces will replace multiple spaces to a space # Preserve spaces will replace multiple spaces to a space
# followed by the &nbsp; entity. # followed by the &nbsp; entity.
if options.preserve_spaces: if options.preserve_spaces:
txt = preserve_spaces(txt) txt = preserve_spaces(txt)
# Normalize line endings
txt = normalize_line_endings(txt)
# Get length for hyphen removal and punctuation unwrap # Get length for hyphen removal and punctuation unwrap
docanalysis = DocAnalysis('txt', txt) docanalysis = DocAnalysis('txt', txt)
length = docanalysis.line_length(.5) length = docanalysis.line_length(.5)
if options.formatting_type == 'auto':
options.formatting_type = detect_formatting_type(txt)
if options.formatting_type == 'markdown': if options.formatting_type == 'markdown':
log.debug('Running text though markdown conversion...') log.debug('Running text though markdown conversion...')
try: try:
@ -96,16 +116,8 @@ class TXTInput(InputFormatPlugin):
elif options.formatting_type == 'textile': elif options.formatting_type == 'textile':
log.debug('Running text though textile conversion...') log.debug('Running text though textile conversion...')
html = convert_textile(txt) html = convert_textile(txt)
else:
# Determine the paragraph type of the document.
if options.paragraph_type == 'auto':
options.paragraph_type = detect_paragraph_type(txt)
if options.paragraph_type == 'unknown':
log.debug('Could not reliably determine paragraph type using block')
options.paragraph_type = 'block'
else:
log.debug('Auto detected paragraph type as %s' % options.paragraph_type)
else:
# Dehyphenate # Dehyphenate
dehyphenator = Dehyphenator(options.verbose, log=self.log) dehyphenator = Dehyphenator(options.verbose, log=self.log)
txt = dehyphenator(txt,'txt', length) txt = dehyphenator(txt,'txt', length)
@ -129,15 +141,6 @@ class TXTInput(InputFormatPlugin):
flow_size = getattr(options, 'flow_size', 0) flow_size = getattr(options, 'flow_size', 0)
html = convert_basic(txt, epub_split_size_kb=flow_size) html = convert_basic(txt, epub_split_size_kb=flow_size)
if options.formatting_type == 'heuristic':
setattr(options, 'enable_heuristics', True)
setattr(options, 'markup_chapter_headings', True)
setattr(options, 'italicize_common_cases', True)
setattr(options, 'fix_indents', True)
setattr(options, 'delete_blank_paragraphs', True)
setattr(options, 'format_scene_breaks', True)
setattr(options, 'dehyphenate', True)
from calibre.customize.ui import plugin_for_input_format from calibre.customize.ui import plugin_for_input_format
html_input = plugin_for_input_format('html') html_input = plugin_for_input_format('html')
for opt in html_input.options: for opt in html_input.options:

View File

@ -51,12 +51,12 @@ class TXTOutput(OutputFormatPlugin):
recommended_value=False, level=OptionRecommendation.LOW, recommended_value=False, level=OptionRecommendation.LOW,
help=_('Do not remove links within the document. This is only ' \ help=_('Do not remove links within the document. This is only ' \
'useful when paired with the markdown-format option because' \ 'useful when paired with the markdown-format option because' \
'links are always removed with plain text output.')), ' links are always removed with plain text output.')),
OptionRecommendation(name='keep_image_references', OptionRecommendation(name='keep_image_references',
recommended_value=False, level=OptionRecommendation.LOW, recommended_value=False, level=OptionRecommendation.LOW,
help=_('Do not remove image references within the document. This is only ' \ help=_('Do not remove image references within the document. This is only ' \
'useful when paired with the markdown-format option because' \ 'useful when paired with the markdown-format option because' \
'image references are always removed with plain text output.')), ' image references are always removed with plain text output.')),
]) ])
def convert(self, oeb_book, output_path, input_plugin, opts, log): def convert(self, oeb_book, output_path, input_plugin, opts, log):

View File

@ -176,9 +176,9 @@ def detect_formatting_type(txt):
# Block quote. # Block quote.
textile_count += len(re.findall(r'(?mu)^bq\.', txt)) textile_count += len(re.findall(r'(?mu)^bq\.', txt))
# Images # Images
textile_count += len(re.findall(r'\![^\s]+(:[^\s]+)*', txt)) textile_count += len(re.findall(r'\![^\s]+(?=.*?/)(:[^\s]+)*', txt))
# Links # Links
textile_count += len(re.findall(r'"(\(.+?\))*[^\(]+?(\(.+?\))*":[^\s]+', txt)) textile_count += len(re.findall(r'"(?=".*?\()(\(.+?\))*[^\(]+?(\(.+?\))*":[^\s]+', txt))
if markdown_count > 5 or textile_count > 5: if markdown_count > 5 or textile_count > 5:
if markdown_count > textile_count: if markdown_count > textile_count:

View File

@ -8,11 +8,12 @@ __docformat__ = 'restructuredtext en'
import os import os
from functools import partial from functools import partial
from PyQt4.Qt import QInputDialog, QPixmap, QMenu from PyQt4.Qt import QPixmap, QMenu
from calibre.gui2 import error_dialog, choose_files, \ from calibre.gui2 import error_dialog, choose_files, \
choose_dir, warning_dialog, info_dialog choose_dir, warning_dialog, info_dialog
from calibre.gui2.dialogs.add_empty_book import AddEmptyBookDialog
from calibre.gui2.widgets import IMAGE_EXTENSIONS from calibre.gui2.widgets import IMAGE_EXTENSIONS
from calibre.ebooks import BOOK_EXTENSIONS from calibre.ebooks import BOOK_EXTENSIONS
from calibre.utils.filenames import ascii_filename from calibre.utils.filenames import ascii_filename
@ -42,7 +43,7 @@ class AddAction(InterfaceAction):
'ebook file is a different book)'), self.add_recursive_multiple) 'ebook file is a different book)'), self.add_recursive_multiple)
self.add_menu.addSeparator() self.add_menu.addSeparator()
self.add_menu.addAction(_('Add Empty book. (Book entry with no ' self.add_menu.addAction(_('Add Empty book. (Book entry with no '
'formats)'), self.add_empty) 'formats)'), self.add_empty, _('Shift+Ctrl+E'))
self.add_menu.addAction(_('Add from ISBN'), self.add_from_isbn) self.add_menu.addAction(_('Add from ISBN'), self.add_from_isbn)
self.qaction.setMenu(self.add_menu) self.qaction.setMenu(self.add_menu)
self.qaction.triggered.connect(self.add_books) self.qaction.triggered.connect(self.add_books)
@ -83,12 +84,21 @@ class AddAction(InterfaceAction):
Add an empty book item to the library. This does not import any formats Add an empty book item to the library. This does not import any formats
from a book file. from a book file.
''' '''
num, ok = QInputDialog.getInt(self.gui, _('How many empty books?'), author = None
_('How many empty books should be added?'), 1, 1, 100) index = self.gui.library_view.currentIndex()
if ok: if index.isValid():
raw = index.model().db.authors(index.row())
if raw:
authors = [a.strip().replace('|', ',') for a in raw.split(',')]
if authors:
author = authors[0]
dlg = AddEmptyBookDialog(self.gui, self.gui.library_view.model().db, author)
if dlg.exec_() == dlg.Accepted:
num = dlg.qty_to_add
from calibre.ebooks.metadata import MetaInformation from calibre.ebooks.metadata import MetaInformation
for x in xrange(num): for x in xrange(num):
self.gui.library_view.model().db.import_book(MetaInformation(None), []) mi = MetaInformation(_('Unknown'), dlg.selected_authors)
self.gui.library_view.model().db.import_book(mi, [])
self.gui.library_view.model().books_added(num) self.gui.library_view.model().books_added(num)
def add_isbns(self, books, add_tags=[]): def add_isbns(self, books, add_tags=[]):

View File

@ -32,7 +32,7 @@ class LibraryUsageStats(object): # {{{
locs = list(self.stats.keys()) locs = list(self.stats.keys())
locs.sort(cmp=lambda x, y: cmp(self.stats[x], self.stats[y]), locs.sort(cmp=lambda x, y: cmp(self.stats[x], self.stats[y]),
reverse=True) reverse=True)
for key in locs[15:]: for key in locs[25:]:
self.stats.pop(key) self.stats.pop(key)
gprefs.set('library_usage_stats', self.stats) gprefs.set('library_usage_stats', self.stats)
@ -384,7 +384,14 @@ class ChooseLibraryAction(InterfaceAction):
return return
prefs['library_path'] = loc prefs['library_path'] = loc
#from calibre.utils.mem import memory
#import weakref, gc
#ref = weakref.ref(self.gui.library_view.model().db)
#before = memory()/1024**2
self.gui.library_moved(loc) self.gui.library_moved(loc)
#print gc.get_referrers(ref)[0]
#for i in xrange(3): gc.collect()
#print 'leaked:', memory()/1024**2 - before
def qs_requested(self, idx, *args): def qs_requested(self, idx, *args):
self.switch_requested(self.qs_locations[idx]) self.switch_requested(self.qs_locations[idx])

View File

@ -144,6 +144,9 @@ class PluginWidget(QWidget,Ui_Form):
# Hook changes to thumb_width # Hook changes to thumb_width
self.thumb_width.valueChanged.connect(self.thumb_width_changed) self.thumb_width.valueChanged.connect(self.thumb_width_changed)
# Hook changes to Description section
self.generate_descriptions.stateChanged.connect(self.generate_descriptions_changed)
def options(self): def options(self):
# Save/return the current options # Save/return the current options
# exclude_genre stores literally # exclude_genre stores literally
@ -265,7 +268,7 @@ class PluginWidget(QWidget,Ui_Form):
custom_fields = {} custom_fields = {}
for custom_field in all_custom_fields: for custom_field in all_custom_fields:
field_md = self.db.metadata_for_field(custom_field) field_md = self.db.metadata_for_field(custom_field)
if field_md['datatype'] in ['text','comments']: if field_md['datatype'] in ['text','comments','composite']:
custom_fields[field_md['name']] = {'field':custom_field, custom_fields[field_md['name']] = {'field':custom_field,
'datatype':field_md['datatype']} 'datatype':field_md['datatype']}
# Blank field first # Blank field first
@ -324,6 +327,28 @@ class PluginWidget(QWidget,Ui_Form):
else: else:
self.exclude_pattern.setEnabled(False) self.exclude_pattern.setEnabled(False)
def generate_descriptions_changed(self,new_state):
'''
Process changes to Descriptions section
0: unchecked
2: checked
'''
return
if new_state == 0:
# unchecked
self.merge_source_field.setEnabled(False)
self.merge_before.setEnabled(False)
self.merge_after.setEnabled(False)
self.include_hr.setEnabled(False)
elif new_state == 2:
# checked
self.merge_source_field.setEnabled(True)
self.merge_before.setEnabled(True)
self.merge_after.setEnabled(True)
self.include_hr.setEnabled(True)
def header_note_source_field_changed(self,new_index): def header_note_source_field_changed(self,new_index):
''' '''
Process changes in the header_note_source_field combo box Process changes in the header_note_source_field combo box

View File

@ -35,7 +35,7 @@
</size> </size>
</property> </property>
<property name="toolTip"> <property name="toolTip">
<string>Sections to include in catalog. All catalogs include 'Books by Author'.</string> <string>Sections to include in catalog.</string>
</property> </property>
<property name="title"> <property name="title">
<string>Included sections</string> <string>Included sections</string>
@ -79,13 +79,13 @@
<item row="0" column="0"> <item row="0" column="0">
<widget class="QCheckBox" name="generate_authors"> <widget class="QCheckBox" name="generate_authors">
<property name="enabled"> <property name="enabled">
<bool>false</bool> <bool>true</bool>
</property> </property>
<property name="text"> <property name="text">
<string>Books by Author</string> <string>Books by Author</string>
</property> </property>
<property name="checked"> <property name="checked">
<bool>true</bool> <bool>false</bool>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -94,7 +94,7 @@ class BulkConfig(Config):
if not c: break if not c: break
self.stack.removeWidget(c) self.stack.removeWidget(c)
widgets = [lf, hw, sr, ps, sd, toc] widgets = [lf, hw, ps, sd, toc, sr]
if output_widget is not None: if output_widget is not None:
widgets.append(output_widget) widgets.append(output_widget)
for w in widgets: for w in widgets:

View File

@ -12,17 +12,24 @@ from calibre.customize.ui import plugin_for_catalog_format
from calibre.utils.logging import Log from calibre.utils.logging import Log
def gui_convert(input, output, recommendations, notification=DummyReporter(), def gui_convert(input, output, recommendations, notification=DummyReporter(),
abort_after_input_dump=False, log=None): abort_after_input_dump=False, log=None, override_input_metadata=False):
recommendations = list(recommendations) recommendations = list(recommendations)
recommendations.append(('verbose', 2, OptionRecommendation.HIGH)) recommendations.append(('verbose', 2, OptionRecommendation.HIGH))
if log is None: if log is None:
log = Log() log = Log()
plumber = Plumber(input, output, log, report_progress=notification, plumber = Plumber(input, output, log, report_progress=notification,
abort_after_input_dump=abort_after_input_dump) abort_after_input_dump=abort_after_input_dump,
override_input_metadata=override_input_metadata)
plumber.merge_ui_recommendations(recommendations) plumber.merge_ui_recommendations(recommendations)
plumber.run() plumber.run()
def gui_convert_override(input, output, recommendations, notification=DummyReporter(),
abort_after_input_dump=False, log=None):
gui_convert(input, output, recommendations, notification=notification,
abort_after_input_dump=abort_after_input_dump, log=log,
override_input_metadata=True)
def gui_catalog(fmt, title, dbspec, ids, out_file_name, sync, fmt_options, connected_device, def gui_catalog(fmt, title, dbspec, ids, out_file_name, sync, fmt_options, connected_device,
notification=DummyReporter(), log=None): notification=DummyReporter(), log=None):
if log is None: if log is None:

View File

@ -11,9 +11,10 @@ from calibre.gui2.convert import Widget
class HeuristicsWidget(Widget, Ui_Form): class HeuristicsWidget(Widget, Ui_Form):
TITLE = _('Heuristic Processing') TITLE = _('Heuristic\nProcessing')
HELP = _('Modify the document text and structure using common patterns.') HELP = _('Modify the document text and structure using common patterns.')
COMMIT_NAME = 'heuristics' COMMIT_NAME = 'heuristics'
ICON = I('heuristics.png')
def __init__(self, parent, get_option, get_help, db=None, book_id=None): def __init__(self, parent, get_option, get_help, db=None, book_id=None):
Widget.__init__(self, parent, Widget.__init__(self, parent,
@ -46,23 +47,8 @@ class HeuristicsWidget(Widget, Ui_Form):
return True return True
def enable_heuristics(self, state): def enable_heuristics(self, state):
if state == Qt.Checked: state = state == Qt.Checked
state = True self.heuristic_options.setEnabled(state)
else:
state = False
self.opt_markup_chapter_headings.setEnabled(state)
self.opt_italicize_common_cases.setEnabled(state)
self.opt_fix_indents.setEnabled(state)
self.opt_delete_blank_paragraphs.setEnabled(state)
self.opt_format_scene_breaks.setEnabled(state)
self.opt_dehyphenate.setEnabled(state)
self.opt_renumber_headings.setEnabled(state)
self.opt_unwrap_lines.setEnabled(state)
if state and self.opt_unwrap_lines.checkState() == Qt.Checked:
self.opt_html_unwrap_factor.setEnabled(True)
else:
self.opt_html_unwrap_factor.setEnabled(False)
def enable_unwrap(self, state): def enable_unwrap(self, state):
if state == Qt.Checked: if state == Qt.Checked:

View File

@ -6,7 +6,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>938</width> <width>724</width>
<height>470</height> <height>470</height>
</rect> </rect>
</property> </property>
@ -15,114 +15,163 @@
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<widget class="QCheckBox" name="opt_enable_heuristics"> <widget class="QLabel" name="label">
<property name="text"> <property name="text">
<string>&amp;Preprocess input file to possibly improve structure detection</string> <string>&lt;b&gt;Heuristic processing&lt;/b&gt; means that calibre will scan your book for common patterns and fix them. As the name implies, this involves guesswork, which means that it could end up worsening the result of a conversion, if calibre guesses wrong. Therefore, it is disabled by default. Often, if a conversion does not turn out as you expect, turning on heuristics can improve matters. Read more about the various heuristic processing options in the &lt;a href=&quot;http://calibre-ebook.com/user_manual/conversion.html#heuristic-processing&quot;&gt;User Manual&lt;/a&gt;.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property> </property>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QGroupBox" name="groupBox"> <spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>15</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QCheckBox" name="opt_enable_heuristics">
<property name="text">
<string>Enable &amp;heuristic processing</string>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="heuristic_options">
<property name="title"> <property name="title">
<string>Heuristic Processing</string> <string>Heuristic Processing</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QVBoxLayout" name="verticalLayout_2">
<item row="0" column="0" colspan="2"> <item>
<widget class="QCheckBox" name="opt_unwrap_lines"> <widget class="QCheckBox" name="opt_unwrap_lines">
<property name="text"> <property name="text">
<string>Unwrap lines</string> <string>Unwrap lines</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item>
<widget class="QLabel" name="huf_label"> <layout class="QHBoxLayout" name="horizontalLayout">
<property name="text"> <item>
<string>Line &amp;un-wrap factor during preprocess:</string> <spacer name="horizontalSpacer">
</property> <property name="orientation">
<property name="buddy"> <enum>Qt::Horizontal</enum>
<cstring>opt_html_unwrap_factor</cstring> </property>
</property> <property name="sizeType">
</widget> <enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="huf_label">
<property name="text">
<string>Line &amp;un-wrap factor :</string>
</property>
<property name="buddy">
<cstring>opt_html_unwrap_factor</cstring>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="opt_html_unwrap_factor">
<property name="toolTip">
<string/>
</property>
<property name="maximum">
<double>1.000000000000000</double>
</property>
<property name="singleStep">
<double>0.050000000000000</double>
</property>
<property name="value">
<double>0.400000000000000</double>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item> </item>
<item row="1" column="2"> <item>
<widget class="QDoubleSpinBox" name="opt_html_unwrap_factor">
<property name="toolTip">
<string/>
</property>
<property name="maximum">
<double>1.000000000000000</double>
</property>
<property name="singleStep">
<double>0.050000000000000</double>
</property>
<property name="value">
<double>0.400000000000000</double>
</property>
</widget>
</item>
<item row="1" column="3">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="2" column="0" colspan="4">
<widget class="QCheckBox" name="opt_markup_chapter_headings"> <widget class="QCheckBox" name="opt_markup_chapter_headings">
<property name="text"> <property name="text">
<string>Detect and markup unformatted chapter headings and sub headings</string> <string>Detect and markup unformatted chapter headings and sub headings</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0" colspan="4"> <item>
<widget class="QCheckBox" name="opt_renumber_headings"> <widget class="QCheckBox" name="opt_renumber_headings">
<property name="text"> <property name="text">
<string>Renumber sequences of &lt;h1&gt; or &lt;h2&gt; tags to prevent splitting</string> <string>Renumber sequences of &lt;h1&gt; or &lt;h2&gt; tags to prevent splitting</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="4" column="0" colspan="2"> <item>
<widget class="QCheckBox" name="opt_delete_blank_paragraphs"> <widget class="QCheckBox" name="opt_delete_blank_paragraphs">
<property name="text"> <property name="text">
<string>Delete blank lines between paragraphs</string> <string>Delete blank lines between paragraphs</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="5" column="0" colspan="3"> <item>
<widget class="QCheckBox" name="opt_format_scene_breaks"> <widget class="QCheckBox" name="opt_format_scene_breaks">
<property name="text"> <property name="text">
<string>Ensure scene breaks are consistently formatted</string> <string>Ensure scene breaks are consistently formatted</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="6" column="0" colspan="2"> <item>
<widget class="QCheckBox" name="opt_dehyphenate"> <widget class="QCheckBox" name="opt_dehyphenate">
<property name="text"> <property name="text">
<string>Remove unnecessary hyphens</string> <string>Remove unnecessary hyphens</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="7" column="0" colspan="2"> <item>
<widget class="QCheckBox" name="opt_italicize_common_cases"> <widget class="QCheckBox" name="opt_italicize_common_cases">
<property name="text"> <property name="text">
<string>Italicize common words and patterns</string> <string>Italicize common words and patterns</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="8" column="0" colspan="2"> <item>
<widget class="QCheckBox" name="opt_fix_indents"> <widget class="QCheckBox" name="opt_fix_indents">
<property name="text"> <property name="text">
<string>Replace entity indents with CSS indents</string> <string>Replace entity indents with CSS indents</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="9" column="0" colspan="2"> <item>
<spacer name="verticalSpacer"> <spacer name="verticalSpacer">
<property name="orientation"> <property name="orientation">
<enum>Qt::Vertical</enum> <enum>Qt::Vertical</enum>

View File

@ -47,6 +47,8 @@ class RegexBuilder(QDialog, Ui_RegexBuilder):
return False return False
else: else:
self.regex.setStyleSheet('QLineEdit { color: black; background-color: white; }') self.regex.setStyleSheet('QLineEdit { color: black; background-color: white; }')
self.preview.setExtraSelections([])
return False
return True return True
def do_test(self): def do_test(self):

View File

@ -12,9 +12,10 @@ from calibre.gui2 import error_dialog
class SearchAndReplaceWidget(Widget, Ui_Form): class SearchAndReplaceWidget(Widget, Ui_Form):
TITLE = _('Search &\nReplace') TITLE = _('Search\n&\nReplace')
HELP = _('Modify the document text and structure using user defined patterns.') HELP = _('Modify the document text and structure using user defined patterns.')
COMMIT_NAME = 'search_and_replace' COMMIT_NAME = 'search_and_replace'
ICON = I('search.png')
def __init__(self, parent, get_option, get_help, db=None, book_id=None): def __init__(self, parent, get_option, get_help, db=None, book_id=None):
Widget.__init__(self, parent, Widget.__init__(self, parent,
@ -24,19 +25,19 @@ class SearchAndReplaceWidget(Widget, Ui_Form):
) )
self.db, self.book_id = db, book_id self.db, self.book_id = db, book_id
self.initialize_options(get_option, get_help, db, book_id) self.initialize_options(get_option, get_help, db, book_id)
self.opt_sr1_search.set_msg(_('Search Regular Expression')) self.opt_sr1_search.set_msg(_('&Search Regular Expression'))
self.opt_sr1_search.set_book_id(book_id) self.opt_sr1_search.set_book_id(book_id)
self.opt_sr1_search.set_db(db) self.opt_sr1_search.set_db(db)
self.opt_sr2_search.set_msg(_('Search Regular Expression')) self.opt_sr2_search.set_msg(_('&Search Regular Expression'))
self.opt_sr2_search.set_book_id(book_id) self.opt_sr2_search.set_book_id(book_id)
self.opt_sr2_search.set_db(db) self.opt_sr2_search.set_db(db)
self.opt_sr3_search.set_msg(_('Search Regular Expression')) self.opt_sr3_search.set_msg(_('&Search Regular Expression'))
self.opt_sr3_search.set_book_id(book_id) self.opt_sr3_search.set_book_id(book_id)
self.opt_sr3_search.set_db(db) self.opt_sr3_search.set_db(db)
def break_cycles(self): def break_cycles(self):
Widget.break_cycles(self) Widget.break_cycles(self)
self.opt_sr1_search.break_cycles() self.opt_sr1_search.break_cycles()
self.opt_sr2_search.break_cycles() self.opt_sr2_search.break_cycles()
self.opt_sr3_search.break_cycles() self.opt_sr3_search.break_cycles()
@ -49,6 +50,6 @@ class SearchAndReplaceWidget(Widget, Ui_Form):
re.compile(pat) re.compile(pat)
except Exception, err: except Exception, err:
error_dialog(self, _('Invalid regular expression'), error_dialog(self, _('Invalid regular expression'),
_('Invalid regular expression: %s')%err).exec_() _('Invalid regular expression: %s')%err, show=True)
return False return False
return True return True

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>198</width> <width>468</width>
<height>350</height> <height>451</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
@ -23,7 +23,7 @@
<property name="sizeConstraint"> <property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum> <enum>QLayout::SetDefaultConstraint</enum>
</property> </property>
<item row="0" column="0"> <item row="1" column="0">
<widget class="QGroupBox" name="groupBox"> <widget class="QGroupBox" name="groupBox">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred"> <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
@ -32,7 +32,7 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="title"> <property name="title">
<string>1.</string> <string>First expression</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_2"> <layout class="QGridLayout" name="gridLayout_2">
<property name="sizeConstraint"> <property name="sizeConstraint">
@ -57,7 +57,10 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="text"> <property name="text">
<string>Replacement Text</string> <string>&amp;Replacement Text</string>
</property>
<property name="buddy">
<cstring>opt_sr1_replace</cstring>
</property> </property>
</widget> </widget>
</item> </item>
@ -74,7 +77,7 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item row="2" column="0">
<widget class="QGroupBox" name="groupBox_2"> <widget class="QGroupBox" name="groupBox_2">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred"> <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
@ -83,7 +86,7 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="title"> <property name="title">
<string>2.</string> <string>Second Expression</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<property name="sizeConstraint"> <property name="sizeConstraint">
@ -108,7 +111,10 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="text"> <property name="text">
<string>Replacement Text</string> <string>&amp;Replacement Text</string>
</property>
<property name="buddy">
<cstring>opt_sr2_replace</cstring>
</property> </property>
</widget> </widget>
</item> </item>
@ -125,7 +131,7 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="2" column="0"> <item row="3" column="0">
<widget class="QGroupBox" name="groupBox_3"> <widget class="QGroupBox" name="groupBox_3">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred"> <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
@ -134,7 +140,7 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="title"> <property name="title">
<string>3.</string> <string>Third expression</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout_3"> <layout class="QGridLayout" name="gridLayout_3">
<property name="sizeConstraint"> <property name="sizeConstraint">
@ -159,7 +165,10 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="text"> <property name="text">
<string>Replacement Text</string> <string>&amp;Replacement Text</string>
</property>
<property name="buddy">
<cstring>opt_sr3_replace</cstring>
</property> </property>
</widget> </widget>
</item> </item>
@ -176,6 +185,19 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>&lt;p&gt;Search and replace uses &lt;i&gt;regular expressions&lt;/i&gt;. See the &lt;a href=&quot;http://calibre-ebook.com/user_manual/regexp.html&quot;&gt;regular expressions tutorial&lt;/a&gt; to get started with regular expressions. Also clicking the wizard buttons below will allow you to test your regular expression against the current input document.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<customwidgets> <customwidgets>

View File

@ -207,7 +207,7 @@ class Config(ResizableDialog, Ui_Dialog):
if not c: break if not c: break
self.stack.removeWidget(c) self.stack.removeWidget(c)
widgets = [self.mw, lf, hw, sr, ps, sd, toc] widgets = [self.mw, lf, hw, ps, sd, toc, sr]
if input_widget is not None: if input_widget is not None:
widgets.append(input_widget) widgets.append(input_widget)
if output_widget is not None: if output_widget is not None:

View File

@ -100,7 +100,7 @@
</size> </size>
</property> </property>
<property name="spacing"> <property name="spacing">
<number>20</number> <number>10</number>
</property> </property>
<property name="wordWrap"> <property name="wordWrap">
<bool>true</bool> <bool>true</bool>
@ -129,8 +129,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>805</width> <width>810</width>
<height>484</height> <height>494</height>
</rect> </rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_3"> <layout class="QVBoxLayout" name="verticalLayout_3">

View File

@ -31,7 +31,7 @@ class StructureDetectionWidget(Widget, Ui_Form):
self.opt_chapter.set_msg(_('Detect chapters at (XPath expression):')) self.opt_chapter.set_msg(_('Detect chapters at (XPath expression):'))
self.opt_page_breaks_before.set_msg(_('Insert page breaks before ' self.opt_page_breaks_before.set_msg(_('Insert page breaks before '
'(XPath expression):')) '(XPath expression):'))
def break_cycles(self): def break_cycles(self):
Widget.break_cycles(self) Widget.break_cycles(self)

View File

@ -23,9 +23,9 @@ class PluginWidget(Widget, Ui_Form):
['newline', 'max_line_length', 'force_max_line_length', ['newline', 'max_line_length', 'force_max_line_length',
'inline_toc', 'markdown_format', 'keep_links', 'keep_image_references', 'inline_toc', 'markdown_format', 'keep_links', 'keep_image_references',
'txt_output_encoding']) 'txt_output_encoding'])
self.db, self.book_id = db, book_id self.db, self.book_id = db, book_id
for x in get_option('newline').option.choices: for x in get_option('newline').option.choices:
self.opt_newline.addItem(x) self.opt_newline.addItem(x)
self.initialize_options(get_option, get_help, db, book_id) self.initialize_options(get_option, get_help, db, book_id)
self.opt_markdown_format.stateChanged.connect(self.enable_markdown_format) self.opt_markdown_format.stateChanged.connect(self.enable_markdown_format)
@ -33,17 +33,14 @@ class PluginWidget(Widget, Ui_Form):
def break_cycles(self): def break_cycles(self):
Widget.break_cycles(self) Widget.break_cycles(self)
try: try:
self.opt_markdown_format.stateChanged.disconnect() self.opt_markdown_format.stateChanged.disconnect()
except: except:
pass pass
def enable_markdown_format(self, state): def enable_markdown_format(self, state):
if state == Qt.Checked: state = state == Qt.Checked
state = True
else:
state = False
self.opt_keep_links.setEnabled(state) self.opt_keep_links.setEnabled(state)
self.opt_keep_image_references.setEnabled(state) self.opt_keep_image_references.setEnabled(state)

View File

@ -6,7 +6,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>434</width> <width>430</width>
<height>74</height> <height>74</height>
</rect> </rect>
</property> </property>
@ -59,7 +59,7 @@
<string>...</string> <string>...</string>
</property> </property>
<property name="icon"> <property name="icon">
<iconset> <iconset resource="../../../../resources/images.qrc">
<normaloff>:/images/wizard.png</normaloff>:/images/wizard.png</iconset> <normaloff>:/images/wizard.png</normaloff>:/images/wizard.png</iconset>
</property> </property>
<property name="iconSize"> <property name="iconSize">

View File

@ -379,7 +379,8 @@ def populate_metadata_page(layout, db, book_id, bulk=False, two_column=False, pa
w = bulk_widgets[type](db, col, parent) w = bulk_widgets[type](db, col, parent)
else: else:
w = widgets[type](db, col, parent) w = widgets[type](db, col, parent)
w.initialize(book_id) if book_id is not None:
w.initialize(book_id)
return w return w
x = db.custom_column_num_map x = db.custom_column_num_map
cols = list(x) cols = list(x)
@ -599,7 +600,7 @@ class BulkEnumeration(BulkBase, Enumeration):
value = None value = None
ret_value = None ret_value = None
dialog_shown = False dialog_shown = False
for book_id in book_ids: for i,book_id in enumerate(book_ids):
val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True) val = self.db.get_custom(book_id, num=self.col_id, index_is_id=True)
if val and val not in self.col_metadata['display']['enum_values']: if val and val not in self.col_metadata['display']['enum_values']:
if not dialog_shown: if not dialog_shown:
@ -610,7 +611,7 @@ class BulkEnumeration(BulkBase, Enumeration):
show=True, show_copy_button=False) show=True, show_copy_button=False)
dialog_shown = True dialog_shown = True
ret_value = ' nochange ' ret_value = ' nochange '
elif value is not None and value != val: elif (value is not None and value != val) or (val and i != 0):
ret_value = ' nochange ' ret_value = ' nochange '
value = val value = val
if ret_value is None: if ret_value is None:

View File

@ -0,0 +1,85 @@
#!/usr/bin/env python
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
__license__ = 'GPL v3'
from PyQt4.Qt import QDialog, QGridLayout, QLabel, QDialogButtonBox, \
QApplication, QSpinBox, QToolButton, QIcon
from calibre.ebooks.metadata import authors_to_string, string_to_authors
from calibre.gui2.widgets import CompleteComboBox
from calibre.utils.icu import sort_key
class AddEmptyBookDialog(QDialog):
def __init__(self, parent, db, author):
QDialog.__init__(self, parent)
self.db = db
self.setWindowTitle(_('How many empty books?'))
self._layout = QGridLayout(self)
self.setLayout(self._layout)
self.qty_label = QLabel(_('How many empty books should be added?'))
self._layout.addWidget(self.qty_label, 0, 0, 1, 2)
self.qty_spinbox = QSpinBox(self)
self.qty_spinbox.setRange(1, 10000)
self.qty_spinbox.setValue(1)
self._layout.addWidget(self.qty_spinbox, 1, 0, 1, 2)
self.author_label = QLabel(_('Set the author of the new books to:'))
self._layout.addWidget(self.author_label, 2, 0, 1, 2)
self.authors_combo = CompleteComboBox(self)
self.authors_combo.setSizeAdjustPolicy(
self.authors_combo.AdjustToMinimumContentsLengthWithIcon)
self.authors_combo.setEditable(True)
self._layout.addWidget(self.authors_combo, 3, 0, 1, 1)
self.initialize_authors(db, author)
self.clear_button = QToolButton(self)
self.clear_button.setIcon(QIcon(I('trash.png')))
self.clear_button.setToolTip(_('Reset author to Unknown'))
self.clear_button.clicked.connect(self.reset_author)
self._layout.addWidget(self.clear_button, 3, 1, 1, 1)
button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
button_box.accepted.connect(self.accept)
button_box.rejected.connect(self.reject)
self._layout.addWidget(button_box)
self.resize(self.sizeHint())
def reset_author(self, *args):
self.authors_combo.setEditText(_('Unknown'))
def initialize_authors(self, db, author):
all_authors = db.all_authors()
all_authors.sort(key=lambda x : sort_key(x[1]))
for i in all_authors:
id, name = i
name = [name.strip().replace('|', ',') for n in name.split(',')]
self.authors_combo.addItem(authors_to_string(name))
au = author
if not au:
au = _('Unknown')
self.authors_combo.setEditText(au.replace('|', ','))
self.authors_combo.set_separator('&')
self.authors_combo.set_space_before_sep(True)
self.authors_combo.update_items_cache(db.all_author_names())
@property
def qty_to_add(self):
return self.qty_spinbox.value()
@property
def selected_authors(self):
return string_to_authors(unicode(self.authors_combo.text()))
if __name__ == '__main__':
app = QApplication([])
d = AddEmptyBookDialog()
d.exec_()

View File

@ -724,7 +724,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
au = _('Unknown') au = _('Unknown')
au = ' & '.join([a.strip().replace('|', ',') for a in au.split(',')]) au = ' & '.join([a.strip().replace('|', ',') for a in au.split(',')])
self.authors.setEditText(au) self.authors.setEditText(au)
self.authors.set_separator('&') self.authors.set_separator('&')
self.authors.set_space_before_sep(True) self.authors.set_space_before_sep(True)
self.authors.update_items_cache(self.db.all_author_names()) self.authors.update_items_cache(self.db.all_author_names())
@ -775,7 +775,7 @@ class MetadataSingleDialog(ResizableDialog, Ui_MetadataSingleDialog):
self.original_tags = unicode(self.tags.text()) self.original_tags = unicode(self.tags.text())
else: else:
self.tags.setText(self.original_tags) self.tags.setText(self.original_tags)
d = TagEditor(self, self.db, self.row) d = TagEditor(self, self.db, self.id)
d.exec_() d.exec_()
if d.result() == QDialog.Accepted: if d.result() == QDialog.Accepted:
tag_string = ', '.join(d.tags) tag_string = ', '.join(d.tags)

View File

@ -10,13 +10,13 @@ from calibre.utils.icu import sort_key
class TagEditor(QDialog, Ui_TagEditor): class TagEditor(QDialog, Ui_TagEditor):
def __init__(self, window, db, index=None): def __init__(self, window, db, id_=None):
QDialog.__init__(self, window) QDialog.__init__(self, window)
Ui_TagEditor.__init__(self) Ui_TagEditor.__init__(self)
self.setupUi(self) self.setupUi(self)
self.db = db self.db = db
self.index = index self.index = db.row(id_)
if self.index is not None: if self.index is not None:
tags = self.db.tags(self.index) tags = self.db.tags(self.index)
else: else:
@ -79,6 +79,8 @@ class TagEditor(QDialog, Ui_TagEditor):
def apply_tags(self, item=None): def apply_tags(self, item=None):
items = self.available_tags.selectedItems() if item is None else [item] items = self.available_tags.selectedItems() if item is None else [item]
rows = [self.available_tags.row(i) for i in items]
row = max(rows)
for item in items: for item in items:
tag = unicode(item.text()) tag = unicode(item.text())
self.tags.append(tag) self.tags.append(tag)
@ -89,6 +91,12 @@ class TagEditor(QDialog, Ui_TagEditor):
for tag in self.tags: for tag in self.tags:
self.applied_tags.addItem(tag) self.applied_tags.addItem(tag)
if row >= self.available_tags.count():
row = self.available_tags.count() - 1
if row > 2:
item = self.available_tags.item(row)
self.available_tags.scrollToItem(item)
def unapply_tags(self, item=None): def unapply_tags(self, item=None):

View File

@ -356,6 +356,13 @@ class %(classname)s(%(base_class)s):
self.populate_options(AutomaticNewsRecipe) self.populate_options(AutomaticNewsRecipe)
self.source_code.setText('') self.source_code.setText('')
def reject(self):
if question_dialog(self, _('Are you sure?'),
_('You will lose any unsaved changes. To save your'
' changes, click the Add/Update recipe button.'
' Continue?'), show_copy_button=False):
ResizableDialog.reject(self)
if __name__ == '__main__': if __name__ == '__main__':
from calibre.gui2 import is_ok_to_use_qt from calibre.gui2 import is_ok_to_use_qt
is_ok_to_use_qt() is_ok_to_use_qt()

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,474 @@
#!/usr/bin/env python
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai
__license__ = 'GPL v3'
__copyright__ = '2011, Kovid Goyal <kovid@kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
import os
from functools import partial
from PyQt4.Qt import Qt, QVBoxLayout, QHBoxLayout, QWidget, QPushButton, \
QGridLayout, pyqtSignal, QDialogButtonBox, QScrollArea, QFont, \
QTabWidget, QIcon, QToolButton, QSplitter, QGroupBox, QSpacerItem, \
QSizePolicy
from calibre.ebooks.metadata import authors_to_string, string_to_authors
from calibre.gui2 import ResizableDialog, error_dialog, gprefs
from calibre.gui2.metadata.basic_widgets import TitleEdit, AuthorsEdit, \
AuthorSortEdit, TitleSortEdit, SeriesEdit, SeriesIndexEdit, ISBNEdit, \
RatingEdit, PublisherEdit, TagsEdit, FormatsManager, Cover, CommentsEdit, \
BuddyLabel, DateEdit, PubdateEdit
from calibre.gui2.custom_column_widgets import populate_metadata_page
from calibre.utils.config import tweaks
class MetadataSingleDialog(ResizableDialog):
view_format = pyqtSignal(object)
def __init__(self, db, parent=None):
self.db = db
self.changed = set([])
ResizableDialog.__init__(self, parent)
def setupUi(self, *args): # {{{
self.resize(990, 650)
self.button_box = QDialogButtonBox(
QDialogButtonBox.Ok|QDialogButtonBox.Cancel, Qt.Horizontal,
self)
self.button_box.accepted.connect(self.accept)
self.button_box.rejected.connect(self.reject)
self.next_button = QPushButton(QIcon(I('forward.png')), _('Next'),
self)
self.next_button.clicked.connect(partial(self.do_one, delta=1))
self.prev_button = QPushButton(QIcon(I('back.png')), _('Previous'),
self)
self.button_box.addButton(self.prev_button, self.button_box.ActionRole)
self.button_box.addButton(self.next_button, self.button_box.ActionRole)
self.prev_button.clicked.connect(partial(self.do_one, delta=-1))
self.scroll_area = QScrollArea(self)
self.scroll_area.setFrameShape(QScrollArea.NoFrame)
self.scroll_area.setWidgetResizable(True)
self.central_widget = QTabWidget(self)
self.scroll_area.setWidget(self.central_widget)
self.l = QVBoxLayout(self)
self.setLayout(self.l)
self.l.setMargin(0)
self.l.addWidget(self.scroll_area)
self.l.addWidget(self.button_box)
self.setWindowIcon(QIcon(I('edit_input.png')))
self.setWindowTitle(_('Edit Meta Information'))
self.create_basic_metadata_widgets()
if len(self.db.custom_column_label_map) == 0:
self.central_widget.tabBar().setVisible(False)
else:
self.create_custom_metadata_widgets()
self.do_layout()
geom = gprefs.get('metasingle_window_geometry3', None)
if geom is not None:
self.restoreGeometry(bytes(geom))
# }}}
def create_basic_metadata_widgets(self): # {{{
self.basic_metadata_widgets = []
self.title = TitleEdit(self)
self.title.textChanged.connect(self.update_window_title)
self.deduce_title_sort_button = QToolButton(self)
self.deduce_title_sort_button.setToolTip(
_('Automatically create the title sort entry based on the current '
'title entry.\nUsing this button to create title sort will '
'change title sort from red to green.'))
self.deduce_title_sort_button.setWhatsThis(
self.deduce_title_sort_button.toolTip())
self.title_sort = TitleSortEdit(self, self.title,
self.deduce_title_sort_button)
self.basic_metadata_widgets.extend([self.title, self.title_sort])
self.authors = AuthorsEdit(self)
self.deduce_author_sort_button = QToolButton(self)
self.deduce_author_sort_button.setToolTip(_(
'Automatically create the author sort entry based on the current'
' author entry.\n'
'Using this button to create author sort will change author sort from'
' red to green.'))
self.author_sort = AuthorSortEdit(self, self.authors,
self.deduce_author_sort_button, db)
self.basic_metadata_widgets.extend([self.authors, self.author_sort])
self.swap_title_author_button = QToolButton(self)
self.swap_title_author_button.setIcon(QIcon(I('swap.png')))
self.swap_title_author_button.setToolTip(_(
'Swap the author and title'))
self.swap_title_author_button.clicked.connect(self.swap_title_author)
self.series = SeriesEdit(self)
self.remove_unused_series_button = QToolButton(self)
self.remove_unused_series_button.setToolTip(
_('Remove unused series (Series that have no books)') )
self.remove_unused_series_button.clicked.connect(self.remove_unused_series)
self.series_index = SeriesIndexEdit(self, self.series)
self.basic_metadata_widgets.extend([self.series, self.series_index])
self.formats_manager = FormatsManager(self)
self.basic_metadata_widgets.append(self.formats_manager)
self.formats_manager.metadata_from_format_button.clicked.connect(
self.metadata_from_format)
self.formats_manager.cover_from_format_button.clicked.connect(
self.cover_from_format)
self.cover = Cover(self)
self.basic_metadata_widgets.append(self.cover)
self.comments = CommentsEdit(self)
self.basic_metadata_widgets.append(self.comments)
self.rating = RatingEdit(self)
self.basic_metadata_widgets.append(self.rating)
self.tags = TagsEdit(self)
self.tags_editor_button = QToolButton(self)
self.tags_editor_button.setToolTip(_('Open Tag Editor'))
self.tags_editor_button.setIcon(QIcon(I('chapters.png')))
self.tags_editor_button.clicked.connect(self.tags_editor)
self.basic_metadata_widgets.append(self.tags)
self.isbn = ISBNEdit(self)
self.basic_metadata_widgets.append(self.isbn)
self.publisher = PublisherEdit(self)
self.basic_metadata_widgets.append(self.publisher)
self.timestamp = DateEdit(self)
self.pubdate = PubdateEdit(self)
self.basic_metadata_widgets.extend([self.timestamp, self.pubdate])
self.fetch_metadata_button = QPushButton(
_('&Fetch metadata from server'), self)
self.fetch_metadata_button.clicked.connect(self.fetch_metadata)
font = self.fmb_font = QFont()
font.setBold(True)
self.fetch_metadata_button.setFont(font)
# }}}
def create_custom_metadata_widgets(self): # {{{
self.custom_metadata_widgets_parent = w = QWidget(self)
layout = QGridLayout()
w.setLayout(layout)
self.custom_metadata_widgets, self.__cc_spacers = \
populate_metadata_page(layout, self.db, None, parent=w, bulk=False,
two_column=tweaks['metadata_single_use_2_cols_for_custom_fields'])
self.__custom_col_layouts = [layout]
ans = self.custom_metadata_widgets
for i in range(len(ans)-1):
if len(ans[i+1].widgets) == 2:
w.setTabOrder(ans[i].widgets[-1], ans[i+1].widgets[1])
else:
w.setTabOrder(ans[i].widgets[-1], ans[i+1].widgets[0])
for c in range(2, len(ans[i].widgets), 2):
w.setTabOrder(ans[i].widgets[c-1], ans[i].widgets[c+1])
# }}}
def do_layout(self): # {{{
self.central_widget.clear()
self.tabs = []
self.labels = []
self.tabs.append(QWidget(self))
self.central_widget.addTab(self.tabs[0], _("&Basic metadata"))
self.tabs[0].l = l = QVBoxLayout()
self.tabs[0].tl = tl = QGridLayout()
self.tabs[0].setLayout(l)
w = getattr(self, 'custom_metadata_widgets_parent', None)
if w is not None:
self.tabs.append(w)
self.central_widget.addTab(w, _('&Custom metadata'))
l.addLayout(tl)
l.addItem(QSpacerItem(10, 15, QSizePolicy.Expanding,
QSizePolicy.Fixed))
sto = QWidget.setTabOrder
sto(self.button_box, self.fetch_metadata_button)
sto(self.fetch_metadata_button, self.title)
def create_row(row, one, two, three, col=1, icon='forward.png'):
ql = BuddyLabel(one)
tl.addWidget(ql, row, col+0, 1, 1)
self.labels.append(ql)
tl.addWidget(one, row, col+1, 1, 1)
if two is not None:
tl.addWidget(two, row, col+2, 1, 1)
two.setIcon(QIcon(I(icon)))
ql = BuddyLabel(three)
tl.addWidget(ql, row, col+3, 1, 1)
self.labels.append(ql)
tl.addWidget(three, row, col+4, 1, 1)
sto(one, two)
sto(two, three)
tl.addWidget(self.swap_title_author_button, 0, 0, 2, 1)
create_row(0, self.title, self.deduce_title_sort_button, self.title_sort)
sto(self.title_sort, self.authors)
create_row(1, self.authors, self.deduce_author_sort_button, self.author_sort)
sto(self.author_sort, self.series)
create_row(2, self.series, self.remove_unused_series_button,
self.series_index, icon='trash.png')
sto(self.series_index, self.swap_title_author_button)
tl.addWidget(self.formats_manager, 0, 6, 3, 1)
self.splitter = QSplitter(Qt.Horizontal, self)
self.splitter.addWidget(self.cover)
l.addWidget(self.splitter)
self.tabs[0].gb = gb = QGroupBox(_('Change cover'), self)
gb.l = l = QGridLayout()
gb.setLayout(l)
sto(self.swap_title_author_button, self.cover.buttons[0])
for i, b in enumerate(self.cover.buttons[:3]):
l.addWidget(b, 0, i, 1, 1)
sto(b, self.cover.buttons[i+1])
gb.hl = QHBoxLayout()
for b in self.cover.buttons[3:]:
gb.hl.addWidget(b)
sto(self.cover.buttons[-2], self.cover.buttons[-1])
l.addLayout(gb.hl, 1, 0, 1, 3)
self.tabs[0].middle = w = QWidget(self)
w.l = l = QGridLayout()
w.setLayout(w.l)
l.setMargin(0)
self.splitter.addWidget(w)
def create_row2(row, widget, button=None):
row += 1
ql = BuddyLabel(widget)
l.addWidget(ql, row, 0, 1, 1)
l.addWidget(widget, row, 1, 1, 2 if button is None else 1)
if button is not None:
l.addWidget(button, row, 2, 1, 1)
if button is not None:
sto(widget, button)
l.addWidget(gb, 0, 0, 1, 3)
self.tabs[0].spc_one = QSpacerItem(10, 10, QSizePolicy.Expanding,
QSizePolicy.Expanding)
l.addItem(self.tabs[0].spc_one, 1, 0, 1, 3)
sto(self.cover.buttons[-1], self.rating)
create_row2(1, self.rating)
sto(self.rating, self.tags)
create_row2(2, self.tags, self.tags_editor_button)
sto(self.tags_editor_button, self.isbn)
create_row2(3, self.isbn)
sto(self.isbn, self.timestamp)
create_row2(4, self.timestamp, self.timestamp.clear_button)
sto(self.timestamp.clear_button, self.pubdate)
create_row2(5, self.pubdate, self.pubdate.clear_button)
sto(self.pubdate.clear_button, self.publisher)
create_row2(6, self.publisher)
self.tabs[0].spc_two = QSpacerItem(10, 10, QSizePolicy.Expanding,
QSizePolicy.Expanding)
l.addItem(self.tabs[0].spc_two, 8, 0, 1, 3)
l.addWidget(self.fetch_metadata_button, 9, 0, 1, 3)
self.tabs[0].gb2 = gb = QGroupBox(_('Co&mments'), self)
gb.l = l = QVBoxLayout()
gb.setLayout(l)
l.addWidget(self.comments)
self.splitter.addWidget(gb)
# }}}
def __call__(self, id_):
self.book_id = id_
for widget in self.basic_metadata_widgets:
widget.initialize(self.db, id_)
for widget in self.custom_metadata_widgets:
widget.initialize(id_)
# Commented out as it doesn't play nice with Next, Prev buttons
#self.fetch_metadata_button.setFocus(Qt.OtherFocusReason)
def update_window_title(self, *args):
title = self.title.current_val
if len(title) > 50:
title = title[:50] + u'\u2026'
self.setWindowTitle(_('Edit Meta Information') + ' - ' +
title)
def swap_title_author(self, *args):
title = self.title.current_val
self.title.current_val = authors_to_string(self.authors.current_val)
self.authors.current_val = string_to_authors(title)
self.title_sort.auto_generate()
self.author_sort.auto_generate()
def remove_unused_series(self, *args):
self.db.remove_unused_series()
idx = self.series.current_val
self.series.clear()
self.series.initialize(self.db, self.book_id)
if idx:
for i in range(self.series.count()):
if unicode(self.series.itemText(i)) == idx:
self.series.setCurrentIndex(i)
break
def tags_editor(self, *args):
self.tags.edit(self.db, self.book_id)
def metadata_from_format(self, *args):
mi, ext = self.formats_manager.get_selected_format_metadata(self.db,
self.book_id)
if mi is not None:
self.update_from_mi(mi)
def cover_from_format(self, *args):
mi, ext = self.formats_manager.get_selected_format_metadata(self.db,
self.book_id)
if mi is None:
return
cdata = None
if mi.cover and os.access(mi.cover, os.R_OK):
cdata = open(mi.cover).read()
elif mi.cover_data[1] is not None:
cdata = mi.cover_data[1]
if cdata is None:
error_dialog(self, _('Could not read cover'),
_('Could not read cover from %s format')%ext).exec_()
return
orig = self.cover.current_val
self.cover.current_val = cdata
if self.cover.current_val is None:
self.cover.current_val = orig
return error_dialog(self, _('Could not read cover'),
_('The cover in the %s format is invalid')%ext,
show=True)
return
def update_from_mi(self, mi):
if not mi.is_null('title'):
self.title.current_val = mi.title
if not mi.is_null('authors'):
self.authors.current_val = mi.authors
if not mi.is_null('author_sort'):
self.author_sort.current_val = mi.author_sort
if not mi.is_null('rating'):
try:
self.rating.current_val = mi.rating
except:
pass
if not mi.is_null('publisher'):
self.publisher.current_val = mi.publisher
if not mi.is_null('tags'):
self.tags.current_val = mi.tags
if not mi.is_null('isbn'):
self.isbn.current_val = mi.isbn
if not mi.is_null('pubdate'):
self.pubdate.current_val = mi.pubdate
if not mi.is_null('series') and mi.series.strip():
self.series.current_val = mi.series
if mi.series_index is not None:
self.series_index.current_val = float(mi.series_index)
if mi.comments and mi.comments.strip():
self.comments.current_val = mi.comments
def fetch_metadata(self, *args):
pass # TODO: fetch metadata
def apply_changes(self):
self.changed.add(self.book_id)
for widget in self.basic_metadata_widgets:
try:
if not widget.commit(self.db, self.book_id):
return False
except IOError, err:
if err.errno == 13: # Permission denied
import traceback
fname = err.filename if err.filename else 'file'
error_dialog(self, _('Permission denied'),
_('Could not open %s. Is it being used by another'
' program?')%fname, det_msg=traceback.format_exc(),
show=True)
return False
raise
for widget in getattr(self, 'custom_metadata_widgets', []):
widget.commit(self.book_id)
self.db.commit()
return True
def accept(self):
self.save_state()
if not self.apply_changes():
return
ResizableDialog.accept(self)
def reject(self):
self.save_state()
ResizableDialog.reject(self)
def save_state(self):
gprefs['metasingle_window_geometry3'] = bytearray(self.saveGeometry())
def start(self, row_list, current_row, view_slot=None):
self.row_list = row_list
self.current_row = current_row
if view_slot is not None:
self.view_format.connect(view_slot)
self.do_one()
ret = self.exec_()
self.break_cycles()
return ret
def do_one(self, delta=0):
self.current_row += delta
prev = next_ = None
if self.current_row > 0:
prev = self.db.title(self.row_list[self.current_row-1])
if self.current_row < len(self.row_list) - 1:
next_ = self.db.title(self.row_list[self.current_row+1])
if next_ is not None:
tip = _('Save changes and edit the metadata of %s')%next_
self.next_button.setToolTip(tip)
self.next_button.setVisible(next_ is not None)
if prev is not None:
tip = _('Save changes and edit the metadata of %s')%prev
self.prev_button.setToolTip(tip)
self.prev_button.setVisible(prev is not None)
self(self.db.id(self.row_list[self.current_row]))
def break_cycles(self):
# Break any reference cycles that could prevent python
# from garbage collecting this dialog
def disconnect(signal):
try:
signal.disconnect()
except:
pass # Fails if view format was never connected
disconnect(self.view_format)
for b in ('next_button', 'prev_button'):
x = getattr(self, b, None)
if x is not None:
disconnect(x.clicked)
def edit_metadata(db, row_list, current_row, parent=None, view_slot=None):
d = MetadataSingleDialog(db, parent)
d.start(row_list, current_row, view_slot=view_slot)
return d.changed
if __name__ == '__main__':
from PyQt4.Qt import QApplication
app = QApplication([])
from calibre.library import db
db = db()
row_list = list(range(len(db.data)))
edit_metadata(db, row_list, 0)

View File

@ -85,8 +85,8 @@ class CommonOptions(Base):
def load_conversion_widgets(self): def load_conversion_widgets(self):
self.conversion_widgets = [LookAndFeelWidget, HeuristicsWidget, self.conversion_widgets = [LookAndFeelWidget, HeuristicsWidget,
SearchAndReplaceWidget, PageSetupWidget, PageSetupWidget,
StructureDetectionWidget, TOCWidget] StructureDetectionWidget, TOCWidget, SearchAndReplaceWidget,]
class InputOptions(Base): class InputOptions(Base):

View File

@ -75,7 +75,7 @@ def convert_single_ebook(parent, db, book_ids, auto_conversion=False, out_format
temp_files.append(d.cover_file) temp_files.append(d.cover_file)
args = [in_file, out_file.name, recs] args = [in_file, out_file.name, recs]
temp_files.append(out_file) temp_files.append(out_file)
jobs.append(('gui_convert', args, desc, d.output_format.upper(), book_id, temp_files)) jobs.append(('gui_convert_override', args, desc, d.output_format.upper(), book_id, temp_files))
changed = True changed = True
d.break_cycles() d.break_cycles()
@ -185,7 +185,7 @@ class QueueBulk(QProgressDialog):
args = [in_file, out_file.name, lrecs] args = [in_file, out_file.name, lrecs]
temp_files.append(out_file) temp_files.append(out_file)
self.jobs.append(('gui_convert', args, desc, self.output_format.upper(), book_id, temp_files)) self.jobs.append(('gui_convert_override', args, desc, self.output_format.upper(), book_id, temp_files))
self.changed = True self.changed = True
self.setValue(self.i) self.setValue(self.i)

View File

@ -440,6 +440,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
except: except:
import traceback import traceback
traceback.print_exc() traceback.print_exc()
olddb.break_cycles()
if self.device_connected: if self.device_connected:
self.set_books_in_library(self.booklists(), reset=True) self.set_books_in_library(self.booklists(), reset=True)
self.refresh_ondevice() self.refresh_ondevice()
@ -449,7 +450,7 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
def set_window_title(self): def set_window_title(self):
self.setWindowTitle(__appname__ + u' - ||%s||'%self.iactions['Choose Library'].library_name()) self.setWindowTitle(__appname__ + u' - || %s ||'%self.iactions['Choose Library'].library_name())
def location_selected(self, location): def location_selected(self, location):
''' '''

View File

@ -123,6 +123,8 @@ IMAGE_EXTENSIONS = ['jpg', 'jpeg', 'gif', 'png', 'bmp']
class FormatList(QListWidget): class FormatList(QListWidget):
DROPABBLE_EXTENSIONS = BOOK_EXTENSIONS DROPABBLE_EXTENSIONS = BOOK_EXTENSIONS
formats_dropped = pyqtSignal(object, object)
delete_format = pyqtSignal()
@classmethod @classmethod
def paths_from_event(cls, event): def paths_from_event(cls, event):
@ -146,15 +148,14 @@ class FormatList(QListWidget):
def dropEvent(self, event): def dropEvent(self, event):
paths = self.paths_from_event(event) paths = self.paths_from_event(event)
event.setDropAction(Qt.CopyAction) event.setDropAction(Qt.CopyAction)
self.emit(SIGNAL('formats_dropped(PyQt_PyObject,PyQt_PyObject)'), self.formats_dropped.emit(event, paths)
event, paths)
def dragMoveEvent(self, event): def dragMoveEvent(self, event):
event.acceptProposedAction() event.acceptProposedAction()
def keyPressEvent(self, event): def keyPressEvent(self, event):
if event.key() == Qt.Key_Delete: if event.key() == Qt.Key_Delete:
self.emit(SIGNAL('delete_format()')) self.delete_format.emit()
else: else:
return QListWidget.keyPressEvent(self, event) return QListWidget.keyPressEvent(self, event)
@ -162,6 +163,7 @@ class FormatList(QListWidget):
class ImageView(QWidget): class ImageView(QWidget):
BORDER_WIDTH = 1 BORDER_WIDTH = 1
cover_changed = pyqtSignal(object)
def __init__(self, parent=None): def __init__(self, parent=None):
QWidget.__init__(self, parent) QWidget.__init__(self, parent)
@ -201,8 +203,7 @@ class ImageView(QWidget):
if not pmap.isNull(): if not pmap.isNull():
self.setPixmap(pmap) self.setPixmap(pmap)
event.accept() event.accept()
self.emit(SIGNAL('cover_changed(PyQt_PyObject)'), open(path, self.cover_changed.emit(open(path, 'rb').read())
'rb').read())
break break
def dragMoveEvent(self, event): def dragMoveEvent(self, event):
@ -271,7 +272,7 @@ class ImageView(QWidget):
pmap = cb.pixmap(cb.Selection) pmap = cb.pixmap(cb.Selection)
if not pmap.isNull(): if not pmap.isNull():
self.setPixmap(pmap) self.setPixmap(pmap)
self.emit(SIGNAL('cover_changed(PyQt_PyObject)'), self.cover_changed.emit(
pixmap_to_data(pmap)) pixmap_to_data(pmap))
# }}} # }}}

View File

@ -29,7 +29,6 @@ FIELDS = ['all', 'author_sort', 'authors', 'comments',
'series_index', 'series', 'size', 'tags', 'timestamp', 'title', 'series_index', 'series', 'size', 'tags', 'timestamp', 'title',
'uuid'] 'uuid']
#Allowed fields for template #Allowed fields for template
TEMPLATE_ALLOWED_FIELDS = [ 'author_sort', 'authors', 'id', 'isbn', 'pubdate', TEMPLATE_ALLOWED_FIELDS = [ 'author_sort', 'authors', 'id', 'isbn', 'pubdate',
'publisher', 'series_index', 'series', 'tags', 'timestamp', 'title', 'uuid' ] 'publisher', 'series_index', 'series', 'tags', 'timestamp', 'title', 'uuid' ]
@ -581,7 +580,7 @@ class EPUB_MOBI(CatalogPlugin):
"pipeline to the specified " "pipeline to the specified "
"directory. Useful if you are unsure at which stage " "directory. Useful if you are unsure at which stage "
"of the conversion process a bug is occurring.\n" "of the conversion process a bug is occurring.\n"
"Default: '%default'None\n" "Default: '%default'\n"
"Applies to: ePub, MOBI output formats")), "Applies to: ePub, MOBI output formats")),
Option('--exclude-book-marker', Option('--exclude-book-marker',
default=':', default=':',
@ -605,43 +604,42 @@ class EPUB_MOBI(CatalogPlugin):
"Default: '%default'\n" "Default: '%default'\n"
"Applies to: ePub, MOBI output formats")), "Applies to: ePub, MOBI output formats")),
Option('--generate-authors', Option('--generate-authors',
default=True, default=False,
dest='generate_authors', dest='generate_authors',
action = 'store_true', action = 'store_true',
help=_("Include 'Authors' section in catalog." help=_("Include 'Authors' section in catalog.\n"
"This switch is ignored - Books By Author section is always generated."
"Default: '%default'\n" "Default: '%default'\n"
"Applies to: ePub, MOBI output formats")), "Applies to: ePub, MOBI output formats")),
Option('--generate-descriptions', Option('--generate-descriptions',
default=True, default=False,
dest='generate_descriptions', dest='generate_descriptions',
action = 'store_true', action = 'store_true',
help=_("Include book descriptions in catalog.\n" help=_("Include 'Descriptions' section in catalog.\n"
"Default: '%default'\n" "Default: '%default'\n"
"Applies to: ePub, MOBI output formats")), "Applies to: ePub, MOBI output formats")),
Option('--generate-genres', Option('--generate-genres',
default=True, default=False,
dest='generate_genres', dest='generate_genres',
action = 'store_true', action = 'store_true',
help=_("Include 'Genres' section in catalog.\n" help=_("Include 'Genres' section in catalog.\n"
"Default: '%default'\n" "Default: '%default'\n"
"Applies to: ePub, MOBI output formats")), "Applies to: ePub, MOBI output formats")),
Option('--generate-titles', Option('--generate-titles',
default=True, default=False,
dest='generate_titles', dest='generate_titles',
action = 'store_true', action = 'store_true',
help=_("Include 'Titles' section in catalog.\n" help=_("Include 'Titles' section in catalog.\n"
"Default: '%default'\n" "Default: '%default'\n"
"Applies to: ePub, MOBI output formats")), "Applies to: ePub, MOBI output formats")),
Option('--generate-series', Option('--generate-series',
default=True, default=False,
dest='generate_series', dest='generate_series',
action = 'store_true', action = 'store_true',
help=_("Include 'Series' section in catalog.\n" help=_("Include 'Series' section in catalog.\n"
"Default: '%default'\n" "Default: '%default'\n"
"Applies to: ePub, MOBI output formats")), "Applies to: ePub, MOBI output formats")),
Option('--generate-recently-added', Option('--generate-recently-added',
default=True, default=False,
dest='generate_recently_added', dest='generate_recently_added',
action = 'store_true', action = 'store_true',
help=_("Include 'Recently Added' section in catalog.\n" help=_("Include 'Recently Added' section in catalog.\n"
@ -976,7 +974,7 @@ class EPUB_MOBI(CatalogPlugin):
self.__thumbWidth = 0 self.__thumbWidth = 0
self.__thumbHeight = 0 self.__thumbHeight = 0
self.__title = opts.catalog_title self.__title = opts.catalog_title
self.__totalSteps = 8.0 self.__totalSteps = 6.0
self.__useSeriesPrefixInTitlesSection = False self.__useSeriesPrefixInTitlesSection = False
self.__verbose = opts.verbose self.__verbose = opts.verbose
@ -1014,17 +1012,21 @@ class EPUB_MOBI(CatalogPlugin):
(self.__archive_path, float(cached_thumb_width))) (self.__archive_path, float(cached_thumb_width)))
# Tweak build steps based on optional sections: 1 call for HTML, 1 for NCX # Tweak build steps based on optional sections: 1 call for HTML, 1 for NCX
incremental_jobs = 0
if self.opts.generate_authors:
incremental_jobs += 2
if self.opts.generate_titles: if self.opts.generate_titles:
self.__totalSteps += 2 incremental_jobs += 2
if self.opts.generate_recently_added: if self.opts.generate_recently_added:
self.__totalSteps += 2 incremental_jobs += 2
if self.generateRecentlyRead: if self.generateRecentlyRead:
self.__totalSteps += 2 incremental_jobs += 2
if self.opts.generate_series: if self.opts.generate_series:
self.__totalSteps += 2 incremental_jobs += 2
if self.opts.generate_descriptions: if self.opts.generate_descriptions:
# +1 thumbs # +1 thumbs
self.__totalSteps += 3 incremental_jobs += 3
self.__totalSteps += incremental_jobs
# Load section list templates # Load section list templates
templates = [] templates = []
@ -1358,13 +1360,23 @@ class EPUB_MOBI(CatalogPlugin):
if self.opts.generate_descriptions: if self.opts.generate_descriptions:
self.generateThumbnails() self.generateThumbnails()
self.generateHTMLDescriptions() self.generateHTMLDescriptions()
self.generateHTMLByAuthor() if self.opts.generate_authors:
self.generateHTMLByAuthor()
if self.opts.generate_titles: if self.opts.generate_titles:
self.generateHTMLByTitle() self.generateHTMLByTitle()
if self.opts.generate_series: if self.opts.generate_series:
self.generateHTMLBySeries() self.generateHTMLBySeries()
if self.opts.generate_genres: if self.opts.generate_genres:
self.generateHTMLByTags() self.generateHTMLByTags()
# If this is the only Section, and there are no genres, bail
if self.opts.section_list == ['Genres'] and not self.genres:
error_msg = _("No enabled genres found to catalog.\n")
if not self.opts.cli_environment:
error_msg += "Check 'Excluded genres'\nin E-book options.\n"
self.opts.log.error(error_msg)
self.error.append(_('No books available to catalog'))
self.error.append(error_msg)
return False
if self.opts.generate_recently_added: if self.opts.generate_recently_added:
self.generateHTMLByDateAdded() self.generateHTMLByDateAdded()
if self.generateRecentlyRead: if self.generateRecentlyRead:
@ -1372,7 +1384,8 @@ class EPUB_MOBI(CatalogPlugin):
self.generateOPF() self.generateOPF()
self.generateNCXHeader() self.generateNCXHeader()
self.generateNCXByAuthor("Authors") if self.opts.generate_authors:
self.generateNCXByAuthor("Authors")
if self.opts.generate_titles: if self.opts.generate_titles:
self.generateNCXByTitle("Titles") self.generateNCXByTitle("Titles")
if self.opts.generate_series: if self.opts.generate_series:
@ -1508,7 +1521,6 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
for tag in exclude_tags: for tag in exclude_tags:
search_terms.append("tag:=%s" % tag) search_terms.append("tag:=%s" % tag)
search_phrase = "not (%s)" % " or ".join(search_terms) search_phrase = "not (%s)" % " or ".join(search_terms)
# If a list of ids are provided, don't use search_text # If a list of ids are provided, don't use search_text
if self.opts.ids: if self.opts.ids:
self.opts.search_text = search_phrase self.opts.search_text = search_phrase
@ -1879,7 +1891,8 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
# Link to author # Link to author
emTag = Tag(soup, "em") emTag = Tag(soup, "em")
aTag = Tag(soup, "a") aTag = Tag(soup, "a")
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(book['author'])) if self.opts.generate_authors:
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(book['author']))
aTag.insert(0, NavigableString(book['author'])) aTag.insert(0, NavigableString(book['author']))
emTag.insert(0,aTag) emTag.insert(0,aTag)
pBookTag.insert(ptc, emTag) pBookTag.insert(ptc, emTag)
@ -2149,7 +2162,8 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
pAuthorTag = Tag(soup, "p") pAuthorTag = Tag(soup, "p")
pAuthorTag['class'] = "author_index" pAuthorTag['class'] = "author_index"
aTag = Tag(soup, "a") aTag = Tag(soup, "a")
aTag['name'] = "%s" % self.generateAuthorAnchor(current_author) if self.opts.generate_authors:
aTag['name'] = "%s" % self.generateAuthorAnchor(current_author)
aTag.insert(0,NavigableString(current_author)) aTag.insert(0,NavigableString(current_author))
pAuthorTag.insert(0,aTag) pAuthorTag.insert(0,aTag)
divTag.insert(dtc,pAuthorTag) divTag.insert(dtc,pAuthorTag)
@ -2276,7 +2290,8 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
# Link to author # Link to author
emTag = Tag(soup, "em") emTag = Tag(soup, "em")
aTag = Tag(soup, "a") aTag = Tag(soup, "a")
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(new_entry['author'])) if self.opts.generate_authors:
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(new_entry['author']))
aTag.insert(0, NavigableString(new_entry['author'])) aTag.insert(0, NavigableString(new_entry['author']))
emTag.insert(0,aTag) emTag.insert(0,aTag)
pBookTag.insert(ptc, emTag) pBookTag.insert(ptc, emTag)
@ -2425,7 +2440,8 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
# Link to author # Link to author
emTag = Tag(soup, "em") emTag = Tag(soup, "em")
aTag = Tag(soup, "a") aTag = Tag(soup, "a")
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(new_entry['author'])) if self.opts.generate_authors:
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(new_entry['author']))
aTag.insert(0, NavigableString(new_entry['author'])) aTag.insert(0, NavigableString(new_entry['author']))
emTag.insert(0,aTag) emTag.insert(0,aTag)
pBookTag.insert(ptc, emTag) pBookTag.insert(ptc, emTag)
@ -2473,7 +2489,8 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
# Link to author # Link to author
emTag = Tag(soup, "em") emTag = Tag(soup, "em")
aTag = Tag(soup, "a") aTag = Tag(soup, "a")
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(new_entry['author'])) if self.opts.generate_authors:
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(new_entry['author']))
aTag.insert(0, NavigableString(new_entry['author'])) aTag.insert(0, NavigableString(new_entry['author']))
emTag.insert(0,aTag) emTag.insert(0,aTag)
pBookTag.insert(ptc, emTag) pBookTag.insert(ptc, emTag)
@ -2692,7 +2709,8 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
# Link to author # Link to author
aTag = Tag(soup, "a") aTag = Tag(soup, "a")
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", if self.opts.generate_authors:
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor",
self.generateAuthorAnchor(escape(' & '.join(book['authors'])))) self.generateAuthorAnchor(escape(' & '.join(book['authors']))))
aTag.insert(0, NavigableString(' &amp; '.join(book['authors']))) aTag.insert(0, NavigableString(' &amp; '.join(book['authors'])))
pBookTag.insert(ptc, aTag) pBookTag.insert(ptc, aTag)
@ -2776,14 +2794,16 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
genre_list.append(tag_list) genre_list.append(tag_list)
if self.opts.verbose: if self.opts.verbose:
self.opts.log.info(" Genre summary: %d active genre tags used in generating catalog with %d titles" % if len(genre_list):
self.opts.log.info(" Genre summary: %d active genre tags used in generating catalog with %d titles" %
(len(genre_list), len(self.booksByTitle))) (len(genre_list), len(self.booksByTitle)))
for genre in genre_list: for genre in genre_list:
for key in genre: for key in genre:
self.opts.log.info(" %s: %d %s" % (self.getFriendlyGenreTag(key), self.opts.log.info(" %s: %d %s" % (self.getFriendlyGenreTag(key),
len(genre[key]), len(genre[key]),
'titles' if len(genre[key]) > 1 else 'title')) 'titles' if len(genre[key]) > 1 else 'title'))
# Write the results # Write the results
# genre_list = [ {friendly_tag:[{book},{book}]}, {friendly_tag:[{book},{book}]}, ...] # genre_list = [ {friendly_tag:[{book},{book}]}, {friendly_tag:[{book},{book}]}, ...]
@ -3074,10 +3094,36 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
textTag.insert(0, NavigableString(self.title)) textTag.insert(0, NavigableString(self.title))
navLabelTag.insert(0, textTag) navLabelTag.insert(0, textTag)
navPointTag.insert(0, navLabelTag) navPointTag.insert(0, navLabelTag)
contentTag = Tag(soup, 'content')
#contentTag['src'] = "content/book_%d.html" % int(self.booksByTitle[0]['id']) if self.opts.generate_authors:
contentTag['src'] = "content/ByAlphaAuthor.html" contentTag = Tag(soup, 'content')
navPointTag.insert(1, contentTag) contentTag['src'] = "content/ByAlphaAuthor.html"
navPointTag.insert(1, contentTag)
elif self.opts.generate_titles:
contentTag = Tag(soup, 'content')
contentTag['src'] = "content/ByAlphaTitle.html"
navPointTag.insert(1, contentTag)
elif self.opts.generate_series:
contentTag = Tag(soup, 'content')
contentTag['src'] = "content/BySeries.html"
navPointTag.insert(1, contentTag)
elif self.opts.generate_genres:
contentTag = Tag(soup, 'content')
#contentTag['src'] = "content/ByGenres.html"
contentTag['src'] = "%s" % self.genres[0]['file']
navPointTag.insert(1, contentTag)
elif self.opts.generate_recently_added:
contentTag = Tag(soup, 'content')
contentTag['src'] = "content/ByDateAdded.html"
navPointTag.insert(1, contentTag)
else:
# Descriptions only
sort_descriptions_by = self.booksByAuthor if self.opts.sort_descriptions_by_author \
else self.booksByTitle
contentTag = Tag(soup, 'content')
contentTag['src'] = "content/book_%d.html" % int(sort_descriptions_by[0]['id'])
navPointTag.insert(1, contentTag)
cmiTag = Tag(soup, '%s' % 'calibre:meta-img') cmiTag = Tag(soup, '%s' % 'calibre:meta-img')
cmiTag['name'] = "mastheadImage" cmiTag['name'] = "mastheadImage"
cmiTag['src'] = "images/mastheadImage.gif" cmiTag['src'] = "images/mastheadImage.gif"
@ -3085,7 +3131,6 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
navMapTag.insert(0,navPointTag) navMapTag.insert(0,navPointTag)
ncx.insert(0,navMapTag) ncx.insert(0,navMapTag)
self.ncxSoup = soup self.ncxSoup = soup
def generateNCXDescriptions(self, tocTitle): def generateNCXDescriptions(self, tocTitle):
@ -3871,7 +3916,6 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
# Add this section to the body # Add this section to the body
body.insert(btc, navPointTag) body.insert(btc, navPointTag)
btc += 1 btc += 1
self.ncxSoup = ncx_soup self.ncxSoup = ncx_soup
def writeNCX(self): def writeNCX(self):
@ -4015,12 +4059,34 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
# Remove the special marker tags from the database's tag list, # Remove the special marker tags from the database's tag list,
# return sorted list of normalized genre tags # return sorted list of normalized genre tags
def format_tag_list(tags, indent=5, line_break=70, header='Tag list'):
def next_tag(sorted_tags):
for (i, tag) in enumerate(sorted_tags):
if i < len(tags) - 1:
yield tag + ", "
else:
yield tag
ans = '%s%d %s:\n' % (' ' * indent, len(tags), header)
ans += ' ' * (indent + 1)
out_str = ''
sorted_tags = sorted(tags)
for tag in next_tag(sorted_tags):
out_str += tag
if len(out_str) >= line_break:
ans += out_str + '\n'
out_str = ' ' * (indent + 1)
return ans + out_str
normalized_tags = [] normalized_tags = []
friendly_tags = [] friendly_tags = []
excluded_tags = []
for tag in tags: for tag in tags:
if tag[0] in self.markerTags: if tag in self.markerTags:
excluded_tags.append(tag)
continue continue
if re.search(self.opts.exclude_genre, tag): if re.search(self.opts.exclude_genre, tag):
excluded_tags.append(tag)
continue continue
if tag == ' ': if tag == ' ':
continue continue
@ -4039,32 +4105,8 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
if genre_tags_dict[key] == normalized: if genre_tags_dict[key] == normalized:
self.opts.log.warn(" %s" % key) self.opts.log.warn(" %s" % key)
if self.verbose: if self.verbose:
def next_tag(tags): self.opts.log.info('%s' % format_tag_list(genre_tags_dict, header="enabled genre tags in database"))
for (i, tag) in enumerate(tags): self.opts.log.info('%s' % format_tag_list(excluded_tags, header="excluded genre tags"))
if i < len(tags) - 1:
yield tag + ", "
else:
yield tag
self.opts.log.info(u' %d genre tags in database (excluding genres matching %s):' % \
(len(genre_tags_dict), self.opts.exclude_genre))
# Display friendly/normalized genres
# friendly => normalized
if False:
sorted_tags = ['%s => %s' % (key, genre_tags_dict[key]) for key in sorted(genre_tags_dict.keys())]
for tag in next_tag(sorted_tags):
self.opts.log(u' %s' % tag)
else:
sorted_tags = ['%s' % (key) for key in sorted(genre_tags_dict.keys())]
out_str = ''
line_break = 70
for tag in next_tag(sorted_tags):
out_str += tag
if len(out_str) >= line_break:
self.opts.log.info(' %s' % out_str)
out_str = ''
self.opts.log.info(' %s' % out_str)
return genre_tags_dict return genre_tags_dict
@ -4140,7 +4182,8 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
pAuthorTag = Tag(soup, "p") pAuthorTag = Tag(soup, "p")
pAuthorTag['class'] = "author_index" pAuthorTag['class'] = "author_index"
aTag = Tag(soup, "a") aTag = Tag(soup, "a")
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(book['author'])) if self.opts.generate_authors:
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", self.generateAuthorAnchor(book['author']))
aTag.insert(0, book['author']) aTag.insert(0, book['author'])
pAuthorTag.insert(0,aTag) pAuthorTag.insert(0,aTag)
divTag.insert(dtc,pAuthorTag) divTag.insert(dtc,pAuthorTag)
@ -4371,7 +4414,8 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
# Insert the author link (always) # Insert the author link (always)
aTag = body.find('a', attrs={'class':'author'}) aTag = body.find('a', attrs={'class':'author'})
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor", if self.opts.generate_authors:
aTag['href'] = "%s.html#%s" % ("ByAlphaAuthor",
self.generateAuthorAnchor(book['author'])) self.generateAuthorAnchor(book['author']))
if publisher == ' ': if publisher == ' ':
@ -4860,6 +4904,8 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
opts.basename = "Catalog" opts.basename = "Catalog"
opts.cli_environment = not hasattr(opts,'sync') opts.cli_environment = not hasattr(opts,'sync')
# Hard-wired to always sort descriptions by author, with series after non-series
opts.sort_descriptions_by_author = True opts.sort_descriptions_by_author = True
build_log = [] build_log = []
@ -4898,14 +4944,13 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
if opts_dict['ids']: if opts_dict['ids']:
build_log.append(" book count: %d" % len(opts_dict['ids'])) build_log.append(" book count: %d" % len(opts_dict['ids']))
'''
sections_list = [] sections_list = []
if opts.generate_authors: if opts.generate_authors:
sections_list.append('Authors') sections_list.append('Authors')
'''
sections_list = ['Authors']
if opts.generate_titles: if opts.generate_titles:
sections_list.append('Titles') sections_list.append('Titles')
if opts.generate_series:
sections_list.append('Series')
if opts.generate_genres: if opts.generate_genres:
sections_list.append('Genres') sections_list.append('Genres')
if opts.generate_recently_added: if opts.generate_recently_added:
@ -4913,7 +4958,27 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
if opts.generate_descriptions: if opts.generate_descriptions:
sections_list.append('Descriptions') sections_list.append('Descriptions')
build_log.append(u" Sections: %s" % ', '.join(sections_list)) if not sections_list:
if opts.cli_environment:
opts.log.warn('*** No Section switches specified, enabling all Sections ***')
opts.generate_authors = True
opts.generate_titles = True
opts.generate_series = True
opts.generate_genres = True
opts.generate_recently_added = True
opts.generate_descriptions = True
sections_list = ['Authors','Titles','Series','Genres','Recently Added','Descriptions']
else:
opts.log.warn('\n*** No enabled Sections, terminating catalog generation ***')
return ["No Included Sections","No enabled Sections.\nCheck E-book options tab\n'Included sections'\n"]
if opts.fmt == 'mobi' and sections_list == ['Descriptions']:
warning = _("\n*** Adding 'By Authors' Section required for MOBI output ***")
opts.log.warn(warning)
sections_list.insert(0,'Authors')
opts.generate_authors = True
opts.log(u" Sections: %s" % ', '.join(sections_list))
opts.section_list = sections_list
# Limit thumb_width to 1.0" - 2.0" # Limit thumb_width to 1.0" - 2.0"
try: try:
@ -4948,6 +5013,7 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
# Launch the Catalog builder # Launch the Catalog builder
catalog = self.CatalogBuilder(db, opts, self, report_progress=notification) catalog = self.CatalogBuilder(db, opts, self, report_progress=notification)
if opts.verbose: if opts.verbose:
log.info(" Begin catalog source generation") log.info(" Begin catalog source generation")
catalog.createDirectoryStructure() catalog.createDirectoryStructure()
@ -4959,7 +5025,7 @@ then rebuild the catalog.\n''').format(author[0],author[1],current_author[1])
if catalog_source_built: if catalog_source_built:
log.info(" Completed catalog source generation\n") log.info(" Completed catalog source generation\n")
else: else:
log.warn(" *** Errors during catalog generation, check log for details ***") log.error(" *** Terminated catalog generation, check log for details ***")
if catalog_source_built: if catalog_source_built:
recommendations = [] recommendations = []

View File

@ -361,6 +361,8 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.refresh() self.refresh()
self.last_update_check = self.last_modified() self.last_update_check = self.last_modified()
def break_cycles(self):
self.data = self.field_metadata = self.prefs = self.listeners = None
def initialize_database(self): def initialize_database(self):
metadata_sqlite = open(P('metadata_sqlite.sql'), 'rb').read() metadata_sqlite = open(P('metadata_sqlite.sql'), 'rb').read()

View File

@ -260,20 +260,20 @@ The Output profile also controls the screen size. This will cause, for example,
Heuristic Processing Heuristic Processing
--------------------- ---------------------
Heuristic Processing provides a variety of functions which can be used that try to detect and correct Heuristic Processing provides a variety of functions which can be used to try and detect and correct
common problems in poorly formatted input documents. Use these functions if your input document suffers common problems in poorly formatted input documents. Use these functions if your input document suffers
from bad formatting. Because these functions rely on common patterns, be aware that in some cases an from poor formatting. Because these functions rely on common patterns, be aware that in some cases an
option may lead to worse results, so use with care. As an example, several of these options will option may lead to worse results, so use with care. As an example, several of these options will
remove all non-breaking-space entities. remove all non-breaking-space entities, or may include false positive matches relating to the function.
:guilabel:`Preprocess input` :guilabel:`Enable heuristic processing`
This option activates various activates |app|'s Heuristic Processing stage of the conversion pipeline. This option activates |app|'s Heuristic Processing stage of the conversion pipeline.
This must be enabled in order for various sub-functions to be applied This must be enabled in order for various sub-functions to be applied
:guilabel:`Unwrap lines` :guilabel:`Unwrap lines`
Enabling this option will cause |app| to attempt to detect and correct hard line breaks that exist Enabling this option will cause |app| to attempt to detect and correct hard line breaks that exist
within a document using punctuation clues and line length. |app| will first attempt to detect whether within a document using punctuation clues and line length. |app| will first attempt to detect whether
hard line breaks exist, if they do not appear to exist |app| will not attempt to unwrap lines. The hard line breaks exist, if they do not appear to exist |app| will not attempt to unwrap lines. The
line-unwrap factor can be reduced if you want to 'force' |app| to unwrap lines. line-unwrap factor can be reduced if you want to 'force' |app| to unwrap lines.
:guilabel:`Line-unwrap factor` :guilabel:`Line-unwrap factor`
@ -283,22 +283,22 @@ remove all non-breaking-space entities.
correction, then this value should be reduced to somewhere between 0.1 and 0.2. correction, then this value should be reduced to somewhere between 0.1 and 0.2.
:guilabel:`Detect and markup unformatted chapter headings and sub headings` :guilabel:`Detect and markup unformatted chapter headings and sub headings`
If your document does not have Chapter Markers and titles formatted differently from the rest of the text, If your document does not have chapter headings and titles formatted differently from the rest of the text,
|app| can use this option to attempt detection them and surround them with heading tags. &lt;h2&gt; tags are used |app| can use this option to attempt detection them and surround them with heading tags. <h2> tags are used
for chapter headings; &lt;h3&gt; tags are used for any titles that are detected. for chapter headings; <h3> tags are used for any titles that are detected.
This function will not create a TOC, but in many cases it will cause |app|'s default chapter detection settings This function will not create a TOC, but in many cases it will cause |app|'s default chapter detection settings
to correctly detect chapters and build a TOC. Adjust the Xpath under Structure Detection if a TOC is not automatically to correctly detect chapters and build a TOC. Adjust the XPath under Structure Detection if a TOC is not automatically
created. If there are no other headings used in the document then setting "//h:h2" under Structure Detection would created. If there are no other headings used in the document then setting "//h:h2" under Structure Detection would
be the easiest way to create a TOC for the document. be the easiest way to create a TOC for the document.
The inserted headings are not formatted, to apply formatting use the 'extra_css' option under The inserted headings are not formatted, to apply formatting use the :guilabel:`Extra CSS` option under
the Look and Feel conversion settings. For example, to center heading tags, use the following:: the Look and Feel conversion settings. For example, to center heading tags, use the following::
h2, h3 { text-align: center } h2, h3 { text-align: center }
:guilabel:`Renumber sequences of &lt;h1&gt; or &lt;h2&gt; tags` :guilabel:`Renumber sequences of <h1> or <h2> tags`
Some publishers format chapter headings using multiple &lt;h1&gt; or &lt;h2&gt; tags sequentially. Some publishers format chapter headings using multiple <h1> or <h2> tags sequentially.
|app|'s default conversion settings will cause such titles to be split into two pieces. This option |app|'s default conversion settings will cause such titles to be split into two pieces. This option
will re-number the heading tags to prevent splitting. will re-number the heading tags to prevent splitting.
@ -331,21 +331,23 @@ remove all non-breaking-space entities.
Some documents use a convention of defining text indents using non-breaking space entities. When this option is enabled |app| will Some documents use a convention of defining text indents using non-breaking space entities. When this option is enabled |app| will
attempt to detect this sort of formatting and convert them to a 3% text indent using css. attempt to detect this sort of formatting and convert them to a 3% text indent using css.
.. search-replace: .. _search-replace:
Search & Replace Search & Replace
--------------------- ---------------------
These options are useful primarily for conversion of PDF documents. Often, the conversion leaves These options are useful primarily for conversion of PDF documents or OCR conversions, though they can
behind page headers and footers in the text. These options use regular expressions to try and detect also be used to fix many document specific problems. As an example, some conversions can leaves behind page
the headers and footers and remove them. Remember that they operate on the intermediate XHTML produced headers and footers in the text. These options use regular expressions to try and detect headers, footers,
by the conversion pipeline. There is also a wizard to help you customize the regular expressions for or other arbitrary text and remove or replace them. Remember that they operate on the intermediate XHTML produced
your document. These options can also be used for generic search and replace of any content by additionally by the conversion pipeline. There is a wizard to help you customize the regular expressions for
specifying a replacement expression. your document. Click the magic wand beside the expression box, and click the 'Test' button after composing
your search expression. Successful matches will be highlighted in Yellow.
The search works by using a python regular expression. All matched text is simply removed from The search works by using a python regular expression. All matched text is simply removed from
the document or replaced using the replacement pattern. You can learn more about regular expressions and the document or replaced using the replacement pattern. The replacement pattern is optional, if left blank
their syntax at http://docs.python.org/library/re.html. then text matching the search pattern will be deleted from the document. You can learn more about regular expressions
and their syntax at :ref:`regexptutorial`.
.. _structure-detection: .. _structure-detection:

View File

@ -107,10 +107,10 @@ My device is not being detected by |app|?
Follow these steps to find the problem: Follow these steps to find the problem:
* Make sure that you are connecting only a single device to your computer at a time. Do not have another |app| supported device like an iPhone/iPad etc. at the same time. * Make sure that you are connecting only a single device to your computer at a time. Do not have another |app| supported device like an iPhone/iPad etc. at the same time.
* Make sure you are running the latest version of |app|. The latest version can always be downloaded from `http://calibre-ebook.com/download`_. * Make sure you are running the latest version of |app|. The latest version can always be downloaded from `the calibre website <http://calibre-ebook.com/download>`_.
* Ensure your operating system is seeing the device. That is, the device should be mounted as a disk that you can access using Windows explorer or whatever the file management program on your computer is * Ensure your operating system is seeing the device. That is, the device should be mounted as a disk that you can access using Windows explorer or whatever the file management program on your computer is.
* In calibre, go to Preferences->Plugins->Device Interface plugin and make sure the plugin for your device is enabled. * In calibre, go to Preferences->Plugins->Device Interface plugin and make sure the plugin for your device is enabled, the plugin icon next to it should be green when it is enabled.
* If all the above steps fail, go to Preferences->Miscellaneous and click debug device detection with your device attached and post the output as a ticket on `http://bugs.calibre-ebook.com`_. * If all the above steps fail, go to Preferences->Miscellaneous and click debug device detection with your device attached and post the output as a ticket on `the calibre bug tracker <http://bugs.calibre-ebook.com>`_.
How does |app| manage collections on my SONY reader? How does |app| manage collections on my SONY reader?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -441,7 +441,7 @@ menu, choose "Validate fonts".
I downloaded the installer, but it is not working? I downloaded the installer, but it is not working?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Downloading from the internet can sometimes result in a corrupted download. If the |app| installer you downloaded is not opening, try downloading it again. If re-downloading it does not work, download it from `an alternate location <http://sourceforge.net/projects/calibre/files/>`_. If the installer still doesn't work, then something on your computer is preventing it from running. Best place to ask for more help is in the `forums <http://www.mobileread.com/forums/usercp.php>`_. Downloading from the internet can sometimes result in a corrupted download. If the |app| installer you downloaded is not opening, try downloading it again. If re-downloading it does not work, download it from `an alternate location <http://sourceforge.net/projects/calibre/files/>`_. If the installer still doesn't work, then something on your computer is preventing it from running. Try rebooting your computer and running a registry cleaner like `Wise registry cleaner <http://www.wisecleaner.com>`_. Best place to ask for more help is in the `forums <http://www.mobileread.com/forums/usercp.php>`_.
My antivirus program claims |app| is a virus/trojan? My antivirus program claims |app| is a virus/trojan?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -21,7 +21,7 @@ This is, inevitably, going to be somewhat technical- after all, regular expressi
Where in |app| can you use regular expressions? Where in |app| can you use regular expressions?
--------------------------------------------------- ---------------------------------------------------
There are a few places |app| uses regular expressions. There's the header/footer removal in conversion options, metadata detection from filenames in the import settings and, since last version, there's the option to use regular expressions to search and replace in metadata of multiple books. There are a few places |app| uses regular expressions. There's the Search & Replace in conversion options, metadata detection from filenames in the import settings and Search & Replace when editing the metadata of books in bulk.
What on earth *is* a regular expression? What on earth *is* a regular expression?
------------------------------------------------ ------------------------------------------------
@ -94,7 +94,7 @@ I think I'm beginning to understand these regular expressions now... how do I us
Conversions Conversions
^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
Let's begin with the conversion settings, which is really neat. In the structure detection part, you can input a regexp (short for regular expression) that describes the header or footer string that will be removed during the conversion. The neat part is the wizard. Click on the wizard staff and you get a preview of what |app| "sees" during the conversion process. Scroll down to the header or footer you want to remove, select and copy it, paste it into the regexp field on top of the window. If there are variable parts, like page numbers or so, use sets and quantifiers to cover those, and while you're at it, remember to escape special characters, if there are some. Hit the button labeled :guilabel:`Test` and |app| highlights the parts it would remove were you to use the regexp. Once you're satisfied, hit OK and convert. Be careful if your conversion source has tags like this example:: Let's begin with the conversion settings, which is really neat. In the Search and Replace part, you can input a regexp (short for regular expression) that describes the string that will be replaced during the conversion. The neat part is the wizard. Click on the wizard staff and you get a preview of what |app| "sees" during the conversion process. Scroll down to the string you want to remove, select and copy it, paste it into the regexp field on top of the window. If there are variable parts, like page numbers or so, use sets and quantifiers to cover those, and while you're at it, remember to escape special characters, if there are some. Hit the button labeled :guilabel:`Test` and |app| highlights the parts it would replace were you to use the regexp. Once you're satisfied, hit OK and convert. Be careful if your conversion source has tags like this example::
Maybe, but the cops feel like you do, Anita. What's one more dead vampire? Maybe, but the cops feel like you do, Anita. What's one more dead vampire?
New laws don't change that. </p> New laws don't change that. </p>
@ -104,7 +104,7 @@ Let's begin with the conversion settings, which is really neat. In the structure
<p class="calibre4"> It had only been two years since Addison v. Clark. <p class="calibre4"> It had only been two years since Addison v. Clark.
The court case gave us a revised version of what life was The court case gave us a revised version of what life was
(shamelessly ripped out of `this thread <http://www.mobileread.com/forums/showthread.php?t=75594">`_). You'd have to remove some of the tags as well. In this example, I'd recommend beginning with the tag ``<b class="calibre2">``, now you have to end with the corresponding closing tag (opening tags are ``<tag>``, closing tags are ``</tag>``), which is simply the next ``</b>`` in this case. (Refer to a good HTML manual or ask in the forum if you are unclear on this point.) The opening tag can be described using ``<b.*?>``, the closing tag using ``</b>``, thus we could remove everything between those tags using ``<b.*?>.*?</b>``. But using this expression would be a bad idea, because it removes everything enclosed by <b>- tags (which, by the way, render the enclosed text in bold print), and it's a fair bet that we'll remove portions of the book in this way. Instead, include the beginning of the enclosed string as well, making the regular expression ``<b.*?>\s*Generated\s+by\s+ABC\s+Amber\s+LIT.*?</b>`` The ``\s`` with quantifiers are included here instead of explicitly using the spaces as seen in the string to catch any variations of the string that might occur. Remember to check what |app| will remove to make sure you don't remove any portions you want to keep if you test a new expression. If you only check one occurrence, you might miss a mismatch somewhere else in the text. Also note that should you accidentally remove more or fewer tags than you actually wanted to, |app| tries to repair the damaged code after doing the header/footer removal. (shamelessly ripped out of `this thread <http://www.mobileread.com/forums/showthread.php?t=75594">`_). You'd have to remove some of the tags as well. In this example, I'd recommend beginning with the tag ``<b class="calibre2">``, now you have to end with the corresponding closing tag (opening tags are ``<tag>``, closing tags are ``</tag>``), which is simply the next ``</b>`` in this case. (Refer to a good HTML manual or ask in the forum if you are unclear on this point.) The opening tag can be described using ``<b.*?>``, the closing tag using ``</b>``, thus we could remove everything between those tags using ``<b.*?>.*?</b>``. But using this expression would be a bad idea, because it removes everything enclosed by <b>- tags (which, by the way, render the enclosed text in bold print), and it's a fair bet that we'll remove portions of the book in this way. Instead, include the beginning of the enclosed string as well, making the regular expression ``<b.*?>\s*Generated\s+by\s+ABC\s+Amber\s+LIT.*?</b>`` The ``\s`` with quantifiers are included here instead of explicitly using the spaces as seen in the string to catch any variations of the string that might occur. Remember to check what |app| will remove to make sure you don't remove any portions you want to keep if you test a new expression. If you only check one occurrence, you might miss a mismatch somewhere else in the text. Also note that should you accidentally remove more or fewer tags than you actually wanted to, |app| tries to repair the damaged code after doing the removal.
Adding books Adding books
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^

View File

@ -104,12 +104,12 @@ class cmd_commit(_cmd_commit):
def close_bug(self, bug, action, url, config): def close_bug(self, bug, action, url, config):
print 'Closing bug #%s'% bug print 'Closing bug #%s'% bug
nick = config.get_nickname() #nick = config.get_nickname()
suffix = config.get_user_option('bug_close_comment') suffix = config.get_user_option('bug_close_comment')
if suffix is None: if suffix is None:
suffix = 'The fix will be in the next release.' suffix = 'The fix will be in the next release.'
action = action+'ed' action = action+'ed'
msg = '%s in branch %s. %s'%(action, nick, suffix) msg = '%s in branch %s. %s'%(action, 'lp:calibre', suffix)
msg = msg.replace('Fixesed', 'Fixed') msg = msg.replace('Fixesed', 'Fixed')
server = xmlrpclib.ServerProxy(url) server = xmlrpclib.ServerProxy(url)
server.ticket.update(int(bug), msg, server.ticket.update(int(bug), msg,

File diff suppressed because it is too large Load Diff

View File

@ -42,30 +42,44 @@ def supports_long_names(path):
else: else:
return True return True
def shorten_components_to(length, components): def shorten_component(s, by_what):
l = len(s)
if l < by_what:
return s
l = (l - by_what)//2
if l <= 0:
return s
return s[:l] + s[-l:]
def shorten_components_to(length, components, more_to_take=0):
filepath = os.sep.join(components) filepath = os.sep.join(components)
extra = len(filepath) - length extra = len(filepath) - (length - more_to_take)
if extra < 1: if extra < 1:
return components return components
delta = int(ceil(extra/float(len(components)))) deltas = []
ans = []
for x in components: for x in components:
pct = len(x)/float(len(filepath))
deltas.append(int(ceil(pct*extra)))
ans = []
for i, x in enumerate(components):
delta = deltas[i]
if delta > len(x): if delta > len(x):
r = x[0] if x is components[-1] else '' r = x[0] if x is components[-1] else ''
else: else:
if x is components[-1]: if x is components[-1]:
b, e = os.path.splitext(x) b, e = os.path.splitext(x)
if e == '.': e = '' if e == '.': e = ''
r = b[:-delta]+e r = shorten_component(b, delta)+e
if r.startswith('.'): r = x[0]+r if r.startswith('.'): r = x[0]+r
else: else:
r = x[:-delta] r = shorten_component(x, delta)
r = r.strip() r = r.strip()
if not r: if not r:
r = x.strip()[0] if x.strip() else 'x' r = x.strip()[0] if x.strip() else 'x'
ans.append(r) ans.append(r)
if len(os.sep.join(ans)) > length: if len(os.sep.join(ans)) > length:
return shorten_components_to(length, ans) return shorten_components_to(length, components, more_to_take+2)
return ans return ans
def find_executable_in_path(name, path=None): def find_executable_in_path(name, path=None):

View File

@ -75,7 +75,7 @@ class FormatterFunction(object):
exc_type, exc_value, exc_traceback = sys.exc_info() exc_type, exc_value, exc_traceback = sys.exc_info()
info = ': '.join(traceback.format_exception(exc_type, exc_value, info = ': '.join(traceback.format_exception(exc_type, exc_value,
exc_traceback)[-2:]).replace('\n', '') exc_traceback)[-2:]).replace('\n', '')
return _('Exception ' + info) return _('Exception ') + info
all_builtin_functions = [] all_builtin_functions = []
class BuiltinFormatterFunction(FormatterFunction): class BuiltinFormatterFunction(FormatterFunction):

View File

@ -28,6 +28,9 @@ PARALLEL_FUNCS = {
'gui_convert' : 'gui_convert' :
('calibre.gui2.convert.gui_conversion', 'gui_convert', 'notification'), ('calibre.gui2.convert.gui_conversion', 'gui_convert', 'notification'),
'gui_convert_override' :
('calibre.gui2.convert.gui_conversion', 'gui_convert_override', 'notification'),
'gui_catalog' : 'gui_catalog' :
('calibre.gui2.convert.gui_conversion', 'gui_catalog', 'notification'), ('calibre.gui2.convert.gui_conversion', 'gui_catalog', 'notification'),