Pull from trunk

This commit is contained in:
Kovid Goyal 2009-04-27 15:43:24 -07:00
commit dbc2d315ed
5 changed files with 68 additions and 52 deletions

View File

@ -5,16 +5,16 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
import sys, array, os, re, codecs, logging import sys, array, os, re, codecs, logging
from calibre import setup_cli_handlers, sanitize_file_name from calibre import setup_cli_handlers, sanitize_file_name
from calibre.utils.config import OptionParser from calibre.utils.config import OptionParser
from calibre.ebooks.lrf.meta import LRFMetaFile from calibre.ebooks.lrf.meta import LRFMetaFile
from calibre.ebooks.lrf.objects import get_object, PageTree, StyleObject, \ from calibre.ebooks.lrf.objects import get_object, PageTree, StyleObject, \
Font, Text, TOCObject, BookAttr, ruby_tags Font, Text, TOCObject, BookAttr, ruby_tags
class LRFDocument(LRFMetaFile): class LRFDocument(LRFMetaFile):
class temp(object): pass class temp(object): pass
def __init__(self, stream): def __init__(self, stream):
LRFMetaFile.__init__(self, stream) LRFMetaFile.__init__(self, stream)
self.scramble_key = self.xor_key self.scramble_key = self.xor_key
@ -23,11 +23,11 @@ class LRFDocument(LRFMetaFile):
self.image_map = {} self.image_map = {}
self.toc = '' self.toc = ''
self.keep_parsing = True self.keep_parsing = True
def parse(self): def parse(self):
self._parse_objects() self._parse_objects()
self.metadata = LRFDocument.temp() self.metadata = LRFDocument.temp()
for a in ('title', 'title_reading', 'author', 'author_reading', 'book_id', for a in ('title', 'title_reading', 'author', 'author_reading', 'book_id',
'classification', 'free_text', 'publisher', 'label', 'category'): 'classification', 'free_text', 'publisher', 'label', 'category'):
setattr(self.metadata, a, getattr(self, a)) setattr(self.metadata, a, getattr(self, a))
self.doc_info = LRFDocument.temp() self.doc_info = LRFDocument.temp()
@ -37,7 +37,7 @@ class LRFDocument(LRFMetaFile):
self.device_info = LRFDocument.temp() self.device_info = LRFDocument.temp()
for a in ('dpi', 'width', 'height'): for a in ('dpi', 'width', 'height'):
setattr(self.device_info, a, getattr(self, a)) setattr(self.device_info, a, getattr(self, a))
def _parse_objects(self): def _parse_objects(self):
self.objects = {} self.objects = {}
self._file.seek(self.object_index_offset) self._file.seek(self.object_index_offset)
@ -68,15 +68,15 @@ class LRFDocument(LRFMetaFile):
attr = h[0] attr = h[0]
if hasattr(obj, attr): if hasattr(obj, attr):
self.ruby_tags[attr] = getattr(obj, attr) self.ruby_tags[attr] = getattr(obj, attr)
def __iter__(self): def __iter__(self):
for pt in self.page_trees: for pt in self.page_trees:
yield pt yield pt
def write_files(self): def write_files(self):
for obj in self.image_map.values() + self.font_map.values(): for obj in self.image_map.values() + self.font_map.values():
open(obj.file, 'wb').write(obj.stream) open(obj.file, 'wb').write(obj.stream)
def to_xml(self, write_files=True): def to_xml(self, write_files=True):
bookinfo = u'<BookInformation>\n<Info version="1.1">\n<BookInfo>\n' bookinfo = u'<BookInformation>\n<Info version="1.1">\n<BookInfo>\n'
bookinfo += u'<Title reading="%s">%s</Title>\n'%(self.metadata.title_reading, self.metadata.title) bookinfo += u'<Title reading="%s">%s</Title>\n'%(self.metadata.title_reading, self.metadata.title)
@ -113,7 +113,7 @@ class LRFDocument(LRFMetaFile):
pages += unicode(page) pages += unicode(page)
pages += close pages += close
traversed_objects = [int(i) for i in re.findall(r'objid="(\w+)"', pages)] + [pt_id] traversed_objects = [int(i) for i in re.findall(r'objid="(\w+)"', pages)] + [pt_id]
objects = u'\n<Objects>\n' objects = u'\n<Objects>\n'
styles = u'\n<Style>\n' styles = u'\n<Style>\n'
for obj in self.objects: for obj in self.objects:
@ -131,16 +131,16 @@ class LRFDocument(LRFMetaFile):
if write_files: if write_files:
self.write_files() self.write_files()
return '<BBeBXylog version="1.0">\n' + bookinfo + pages + styles + objects + '</BBeBXylog>' return '<BBeBXylog version="1.0">\n' + bookinfo + pages + styles + objects + '</BBeBXylog>'
def option_parser(): def option_parser():
parser = OptionParser(usage=_('%prog book.lrf\nConvert an LRF file into an LRS (XML UTF-8 encoded) file')) parser = OptionParser(usage=_('%prog book.lrf\nConvert an LRF file into an LRS (XML UTF-8 encoded) file'))
parser.add_option('--output', '-o', default=None, help=_('Output LRS file'), dest='out') parser.add_option('--output', '-o', default=None, help=_('Output LRS file'), dest='out')
parser.add_option('--dont-output-resources', default=True, action='store_false', parser.add_option('--dont-output-resources', default=True, action='store_false',
help=_('Do not save embedded image and font files to disk'), help=_('Do not save embedded image and font files to disk'),
dest='output_resources') dest='output_resources')
parser.add_option('--verbose', default=False, action='store_true', dest='verbose') parser.add_option('--verbose', default=False, action='store_true', dest='verbose')
return parser return parser
def main(args=sys.argv, logger=None): def main(args=sys.argv, logger=None):
parser = option_parser() parser = option_parser()
opts, args = parser.parse_args(args) opts, args = parser.parse_args(args)

View File

@ -970,7 +970,12 @@ class Canvas(LRFStream):
stream = cStringIO.StringIO(self.stream) stream = cStringIO.StringIO(self.stream)
while stream.tell() < len(self.stream): while stream.tell() < len(self.stream):
tag = Tag(stream) tag = Tag(stream)
self._contents.append(PutObj(self._document.objects, *struct.unpack("<HHI", tag.contents))) try:
self._contents.append(
PutObj(self._document.objects,
*struct.unpack("<HHI", tag.contents)))
except struct.error:
print 'Canvas object has errors, skipping.'
def __unicode__(self): def __unicode__(self):
s = '\n<%s objid="%s" '%(self.__class__.__name__, self.id,) s = '\n<%s objid="%s" '%(self.__class__.__name__, self.id,)

View File

@ -313,8 +313,10 @@ class MobiReader(object):
self.read_embedded_metadata(root, metadata_elems[0], guide) self.read_embedded_metadata(root, metadata_elems[0], guide)
for elem in guides + metadata_elems: for elem in guides + metadata_elems:
elem.getparent().remove(elem) elem.getparent().remove(elem)
fname = self.name.encode('ascii', 'replace')
fname = re.sub(r'[\x08\x15\0]+', '', fname)
htmlfile = os.path.join(output_dir, htmlfile = os.path.join(output_dir,
sanitize_file_name(self.name)+'.html') sanitize_file_name(fname)+'.html')
try: try:
for ref in guide.xpath('descendant::reference'): for ref in guide.xpath('descendant::reference'):
if ref.attrib.has_key('href'): if ref.attrib.has_key('href'):
@ -396,8 +398,8 @@ class MobiReader(object):
'xx-large' : '6', 'xx-large' : '6',
} }
mobi_version = self.book_header.mobi_version mobi_version = self.book_header.mobi_version
style_map = {}
for i, tag in enumerate(root.iter(etree.Element)): for i, tag in enumerate(root.iter(etree.Element)):
tag.attrib.pop('xmlns', '')
if tag.tag in ('country-region', 'place', 'placetype', 'placename', if tag.tag in ('country-region', 'place', 'placetype', 'placename',
'state', 'city', 'street', 'address', 'content'): 'state', 'city', 'street', 'address', 'content'):
tag.tag = 'div' if tag.tag == 'content' else 'span' tag.tag = 'div' if tag.tag == 'content' else 'span'

View File

@ -81,7 +81,12 @@ def sendmail(msg, from_, to, localhost=None, verbose=0, timeout=30,
for x in to: for x in to:
return sendmail_direct(from_, x, msg, timeout, localhost, verbose) return sendmail_direct(from_, x, msg, timeout, localhost, verbose)
import smtplib import smtplib
cls = smtplib.SMTP if encryption == 'TLS' else smtplib.SMTP_SSL class SMTP_SSL(smtplib.SMTP_SSL): # Workaround for bug in smtplib.py
def _get_socket(self, host, port, timeout):
smtplib.SMTP_SSL._get_socket(self, host, port, timeout)
return self.sock
cls = smtplib.SMTP if encryption == 'TLS' else SMTP_SSL
timeout = None # Non-blocking sockets sometimes don't work timeout = None # Non-blocking sockets sometimes don't work
port = int(port) port = int(port)
s = cls(timeout=timeout, local_hostname=localhost) s = cls(timeout=timeout, local_hostname=localhost)
@ -93,6 +98,8 @@ def sendmail(msg, from_, to, localhost=None, verbose=0, timeout=30,
s.starttls() s.starttls()
s.ehlo() s.ehlo()
if username is not None and password is not None: if username is not None and password is not None:
if encryption == 'SSL':
s.sock = s.file.sslobj
s.login(username, password) s.login(username, password)
s.sendmail(from_, to, msg) s.sendmail(from_, to, msg)
return s.quit() return s.quit()

View File

@ -7,22 +7,22 @@ import sys, re, os
class TerminalController: class TerminalController:
""" """
A class that can be used to portably generate formatted output to A class that can be used to portably generate formatted output to
a terminal. a terminal.
`TerminalController` defines a set of instance variables whose `TerminalController` defines a set of instance variables whose
values are initialized to the control sequence necessary to values are initialized to the control sequence necessary to
perform a given action. These can be simply included in normal perform a given action. These can be simply included in normal
output to the terminal: output to the terminal:
>>> term = TerminalController() >>> term = TerminalController()
>>> print 'This is '+term.GREEN+'green'+term.NORMAL >>> print 'This is '+term.GREEN+'green'+term.NORMAL
Alternatively, the `render()` method can used, which replaces Alternatively, the `render()` method can used, which replaces
'${action}' with the string required to perform 'action': '${action}' with the string required to perform 'action':
>>> term = TerminalController() >>> term = TerminalController()
>>> print term.render('This is ${GREEN}green${NORMAL}') >>> print term.render('This is ${GREEN}green${NORMAL}')
If the terminal doesn't support a given action, then the value of If the terminal doesn't support a given action, then the value of
the corresponding instance variable will be set to ''. As a the corresponding instance variable will be set to ''. As a
result, the above code will still work on terminals that do not result, the above code will still work on terminals that do not
@ -30,11 +30,11 @@ class TerminalController:
Also, this means that you can test whether the terminal supports a Also, this means that you can test whether the terminal supports a
given action by simply testing the truth value of the given action by simply testing the truth value of the
corresponding instance variable: corresponding instance variable:
>>> term = TerminalController() >>> term = TerminalController()
>>> if term.CLEAR_SCREEN: >>> if term.CLEAR_SCREEN:
... print 'This terminal supports clearing the screen.' ... print 'This terminal supports clearing the screen.'
Finally, if the width and height of the terminal are known, then Finally, if the width and height of the terminal are known, then
they will be stored in the `COLS` and `LINES` attributes. they will be stored in the `COLS` and `LINES` attributes.
""" """
@ -44,35 +44,35 @@ class TerminalController:
DOWN = '' #: Move the cursor down one line DOWN = '' #: Move the cursor down one line
LEFT = '' #: Move the cursor left one char LEFT = '' #: Move the cursor left one char
RIGHT = '' #: Move the cursor right one char RIGHT = '' #: Move the cursor right one char
# Deletion: # Deletion:
CLEAR_SCREEN = '' #: Clear the screen and move to home position CLEAR_SCREEN = '' #: Clear the screen and move to home position
CLEAR_EOL = '' #: Clear to the end of the line. CLEAR_EOL = '' #: Clear to the end of the line.
CLEAR_BOL = '' #: Clear to the beginning of the line. CLEAR_BOL = '' #: Clear to the beginning of the line.
CLEAR_EOS = '' #: Clear to the end of the screen CLEAR_EOS = '' #: Clear to the end of the screen
# Output modes: # Output modes:
BOLD = '' #: Turn on bold mode BOLD = '' #: Turn on bold mode
BLINK = '' #: Turn on blink mode BLINK = '' #: Turn on blink mode
DIM = '' #: Turn on half-bright mode DIM = '' #: Turn on half-bright mode
REVERSE = '' #: Turn on reverse-video mode REVERSE = '' #: Turn on reverse-video mode
NORMAL = '' #: Turn off all modes NORMAL = '' #: Turn off all modes
# Cursor display: # Cursor display:
HIDE_CURSOR = '' #: Make the cursor invisible HIDE_CURSOR = '' #: Make the cursor invisible
SHOW_CURSOR = '' #: Make the cursor visible SHOW_CURSOR = '' #: Make the cursor visible
# Terminal size: # Terminal size:
COLS = None #: Width of the terminal (None for unknown) COLS = None #: Width of the terminal (None for unknown)
LINES = None #: Height of the terminal (None for unknown) LINES = None #: Height of the terminal (None for unknown)
# Foreground colors: # Foreground colors:
BLACK = BLUE = GREEN = CYAN = RED = MAGENTA = YELLOW = WHITE = '' BLACK = BLUE = GREEN = CYAN = RED = MAGENTA = YELLOW = WHITE = ''
# Background colors: # Background colors:
BG_BLACK = BG_BLUE = BG_GREEN = BG_CYAN = '' BG_BLACK = BG_BLUE = BG_GREEN = BG_CYAN = ''
BG_RED = BG_MAGENTA = BG_YELLOW = BG_WHITE = '' BG_RED = BG_MAGENTA = BG_YELLOW = BG_WHITE = ''
_STRING_CAPABILITIES = """ _STRING_CAPABILITIES = """
BOL=cr UP=cuu1 DOWN=cud1 LEFT=cub1 RIGHT=cuf1 BOL=cr UP=cuu1 DOWN=cud1 LEFT=cub1 RIGHT=cuf1
CLEAR_SCREEN=clear CLEAR_EOL=el CLEAR_BOL=el1 CLEAR_EOS=ed BOLD=bold CLEAR_SCREEN=clear CLEAR_EOL=el CLEAR_BOL=el1 CLEAR_EOS=ed BOLD=bold
@ -80,7 +80,7 @@ class TerminalController:
HIDE_CURSOR=cinvis SHOW_CURSOR=cnorm""".split() HIDE_CURSOR=cinvis SHOW_CURSOR=cnorm""".split()
_COLORS = """BLACK BLUE GREEN CYAN RED MAGENTA YELLOW WHITE""".split() _COLORS = """BLACK BLUE GREEN CYAN RED MAGENTA YELLOW WHITE""".split()
_ANSICOLORS = "BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE".split() _ANSICOLORS = "BLACK RED GREEN YELLOW BLUE MAGENTA CYAN WHITE".split()
def __init__(self, term_stream=sys.stdout): def __init__(self, term_stream=sys.stdout):
""" """
Create a `TerminalController` and initialize its attributes Create a `TerminalController` and initialize its attributes
@ -92,24 +92,24 @@ class TerminalController:
# Curses isn't available on all platforms # Curses isn't available on all platforms
try: import curses try: import curses
except: return except: return
# If the stream isn't a tty, then assume it has no capabilities. # If the stream isn't a tty, then assume it has no capabilities.
if os.environ.get('CALIBRE_WORKER', None) is not None or not hasattr(term_stream, 'isatty') or not term_stream.isatty(): return if os.environ.get('CALIBRE_WORKER', None) is not None or not hasattr(term_stream, 'isatty') or not term_stream.isatty(): return
# Check the terminal type. If we fail, then assume that the # Check the terminal type. If we fail, then assume that the
# terminal has no capabilities. # terminal has no capabilities.
try: curses.setupterm() try: curses.setupterm()
except: return except: return
# Look up numeric capabilities. # Look up numeric capabilities.
self.COLS = curses.tigetnum('cols') self.COLS = curses.tigetnum('cols')
self.LINES = curses.tigetnum('lines') self.LINES = curses.tigetnum('lines')
# Look up string capabilities. # Look up string capabilities.
for capability in self._STRING_CAPABILITIES: for capability in self._STRING_CAPABILITIES:
(attrib, cap_name) = capability.split('=') (attrib, cap_name) = capability.split('=')
setattr(self, attrib, self._tigetstr(cap_name) or '') setattr(self, attrib, self._tigetstr(cap_name) or '')
# Colors # Colors
set_fg = self._tigetstr('setf') set_fg = self._tigetstr('setf')
if set_fg: if set_fg:
@ -127,7 +127,7 @@ class TerminalController:
if set_bg_ansi: if set_bg_ansi:
for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS): for i,color in zip(range(len(self._ANSICOLORS)), self._ANSICOLORS):
setattr(self, 'BG_'+color, curses.tparm(set_bg_ansi, i) or '') setattr(self, 'BG_'+color, curses.tparm(set_bg_ansi, i) or '')
def _tigetstr(self, cap_name): def _tigetstr(self, cap_name):
# String capabilities can include "delays" of the form "$<2>". # String capabilities can include "delays" of the form "$<2>".
# For any modern terminal, we should be able to just ignore # For any modern terminal, we should be able to just ignore
@ -135,7 +135,7 @@ class TerminalController:
import curses import curses
cap = curses.tigetstr(cap_name) or '' cap = curses.tigetstr(cap_name) or ''
return re.sub(r'\$<\d+>[/*]?', '', cap) return re.sub(r'\$<\d+>[/*]?', '', cap)
def render(self, template): def render(self, template):
""" """
Replace each $-substitutions in the given template string with Replace each $-substitutions in the given template string with
@ -143,7 +143,7 @@ class TerminalController:
'' (if it's not). '' (if it's not).
""" """
return re.sub(r'\$\$|\${\w+}', self._render_sub, template) return re.sub(r'\$\$|\${\w+}', self._render_sub, template)
def _render_sub(self, match): def _render_sub(self, match):
s = match.group() s = match.group()
if s == '$$': return s if s == '$$': return s
@ -156,20 +156,20 @@ class TerminalController:
class ProgressBar: class ProgressBar:
""" """
A 3-line progress bar, which looks like:: A 3-line progress bar, which looks like::
Header Header
20% [===========----------------------------------] 20% [===========----------------------------------]
progress message progress message
The progress bar is colored, if the terminal supports color The progress bar is colored, if the terminal supports color
output; and adjusts to the width of the terminal. output; and adjusts to the width of the terminal.
If the terminal doesn't have the required capabilities, it uses a If the terminal doesn't have the required capabilities, it uses a
simple progress bar. simple progress bar.
""" """
BAR = '%3d%% ${GREEN}[${BOLD}%s%s${NORMAL}${GREEN}]${NORMAL}\n' BAR = '%3d%% ${GREEN}[${BOLD}%s%s${NORMAL}${GREEN}]${NORMAL}\n'
HEADER = '${BOLD}${CYAN}%s${NORMAL}\n\n' HEADER = '${BOLD}${CYAN}%s${NORMAL}\n\n'
def __init__(self, term, header, no_progress_bar = False): def __init__(self, term, header, no_progress_bar = False):
self.term, self.no_progress_bar = term, no_progress_bar self.term, self.no_progress_bar = term, no_progress_bar
self.fancy = self.term.CLEAR_EOL and self.term.UP and self.term.BOL self.fancy = self.term.CLEAR_EOL and self.term.UP and self.term.BOL
@ -177,12 +177,14 @@ class ProgressBar:
self.width = self.term.COLS or 75 self.width = self.term.COLS or 75
self.bar = term.render(self.BAR) self.bar = term.render(self.BAR)
self.header = self.term.render(self.HEADER % header.center(self.width)) self.header = self.term.render(self.HEADER % header.center(self.width))
if isinstance(self.header, unicode):
self.header = self.header.encode('utf-8')
self.cleared = 1 #: true if we haven't drawn the bar yet. self.cleared = 1 #: true if we haven't drawn the bar yet.
def update(self, percent, message=''): def update(self, percent, message=''):
if isinstance(message, unicode): if isinstance(message, unicode):
message = message.encode('utf-8', 'replace') message = message.encode('utf-8', 'replace')
if self.no_progress_bar: if self.no_progress_bar:
if message: if message:
print message print message
@ -203,8 +205,8 @@ class ProgressBar:
else: else:
print '%d%%'%(percent*100), message print '%d%%'%(percent*100), message
sys.stdout.flush() sys.stdout.flush()
def clear(self): def clear(self):
if self.fancy and not self.cleared: if self.fancy and not self.cleared:
sys.stdout.write(self.term.BOL + self.term.CLEAR_EOL + sys.stdout.write(self.term.BOL + self.term.CLEAR_EOL +