Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow repeatable options and multiple option values per option #40

Merged
merged 3 commits into from
Sep 6, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ If you wish to further process generated PDF, you can read it to a variable:
# Use False instead of output path to save pdf to a variable
pdf = pdfkit.from_url('http://google.com', False)

You can specify all wkhtmltopdf `options <http://wkhtmltopdf.org/usage/wkhtmltopdf.txt>`_. You can drop '--' in option name. If option without value, use *None, False* or *''* for dict value:
You can specify all wkhtmltopdf `options <http://wkhtmltopdf.org/usage/wkhtmltopdf.txt>`_. You can drop '--' in option name. If option without value, use *None, False* or *''* for dict value:. For repeatable options (incl. allow, cookies, custom-header, post, postfile, run-script, replace) you may use a list or a tuple. With option that need multiple values (e.g. --custom-header Authorization secret) we may use a 2-tuple.

.. code-block:: python

Expand All @@ -78,6 +78,10 @@ You can specify all wkhtmltopdf `options <http://wkhtmltopdf.org/usage/wkhtmltop
'margin-bottom': '0.75in',
'margin-left': '0.75in',
'encoding': "UTF-8",
'cookies': [
('cookie-name1', 'cookie-value1'),
('cookie-name2', 'cookie-value2'),
],
'no-outline': None
}

Expand Down
76 changes: 53 additions & 23 deletions pdfkit/pdfkit.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,49 +42,73 @@ def __init__(self, url_or_file, type_, options=None, toc=None, cover=None,
self.options = dict()
if self.source.isString():
self.options.update(self._find_options_in_meta(url_or_file))

if options is not None: self.options.update(options)
self.options = self._normalize_options(self.options)

toc = {} if toc is None else toc
self.toc = self._normalize_options(toc)
self.toc = {} if toc is None else toc
self.cover = cover
self.css = css
self.stylesheets = []

def command(self, path=None):
def _genargs(self, opts):
"""
Generator of args parts based on options specification.

Note: Empty parts will be filtered out at _command generator
"""
for optkey, optval in self._normalize_options(opts):
yield optkey

if isinstance(optval, (list, tuple)):
assert len(optval) == 2 and optval[0] and optval[1], 'Option value can only be either a string or a (tuple, list) of 2 items'
yield optval[0]
yield optval[1]
else:
yield optval

def _command(self, path=None):
"""
Generator of all command parts
"""
if self.css:
self._prepend_css(self.css)

args = [self.wkhtmltopdf]
yield self.wkhtmltopdf

args += list(chain.from_iterable(list(self.options.items())))
args = [_f for _f in args if _f]
for argpart in self._genargs(self.options):
if argpart:
yield argpart

if self.toc:
args.append('toc')
args += list(chain.from_iterable(list(self.toc.items())))
yield 'toc'
for argpart in self._genargs(self.toc):
if argpart:
yield argpart

if self.cover:
args.append('cover')
args.append(self.cover)
yield 'cover'
yield self.cover

# If the source is a string then we will pipe it into wkhtmltopdf
# If the source is file-like then we will read from it and pipe it in
if self.source.isString() or self.source.isFileObj():
args.append('-')
yield '-'
else:
if isinstance(self.source.source, str):
args.append(self.source.to_s())
yield self.source.to_s()
else:
args += self.source.source
for s in self.source.source:
yield s

# If output_path evaluates to False append '-' to end of args
# and wkhtmltopdf will pass generated PDF to stdout
if path:
args.append(path)
yield path
else:
args.append('-')
yield '-'

return args
def command(self, path=None):
return list(self._command(path))

def to_pdf(self, path=None):
args = self.command(path)
Expand All @@ -108,7 +132,7 @@ def to_pdf(self, path=None):

if 'cannot connect to X server' in stderr.decode('utf-8'):
raise IOError('%s\n'
'You will need to run whktmltopdf within a "virutal" X server.\n'
'You will need to run whktmltopdf within a "virtual" X server.\n'
'Go to the link above for more information\n'
'https://github.com/JazzCore/python-pdfkit/wiki/Using-wkhtmltopdf-without-X-server' % stderr.decode('utf-8'))

Expand Down Expand Up @@ -141,13 +165,15 @@ def to_pdf(self, path=None):
' '.join(args))

def _normalize_options(self, options):
"""Updates a dict of config options to make then usable on command line
""" Generator of 2-tuples (option-key, option-value).
When options spec is a list, generate a 2-tuples per list item.

:param options: dict {option name: value}

returns:
dict: {option name: value} - option names lower cased and prepended with
'--' if necessary. Non-empty values cast to str
iterator (option-key, option-value)
- option names lower cased and prepended with
'--' if necessary. Non-empty values cast to str
"""
normalized_options = {}

Expand All @@ -156,9 +182,13 @@ def _normalize_options(self, options):
normalized_key = '--%s' % self._normalize_arg(key)
else:
normalized_key = self._normalize_arg(key)
normalized_options[normalized_key] = str(value) if value else value

if isinstance(value, (list, tuple)):
for optval in value:
yield (normalized_key, optval)
else:
yield (normalized_key, str(value) if value else value)

return normalized_options

def _normalize_arg(self, arg):
return arg.lower()
Expand Down
35 changes: 33 additions & 2 deletions tests/pdfkit-tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,42 @@ def test_file_source_with_path(self):

def test_options_parsing(self):
r = pdfkit.PDFKit('html', 'string', options={'page-size': 'Letter'})
self.assertTrue(r.options['--page-size'])
test_command = r.command('test')
idx = test_command.index('--page-size') # Raise exception in case of not found
self.assertTrue(test_command[idx+1] == 'Letter')

def test_options_parsing_with_dashes(self):
r = pdfkit.PDFKit('html', 'string', options={'--page-size': 'Letter'})
self.assertTrue(r.options['--page-size'])

test_command = r.command('test')
idx = test_command.index('--page-size') # Raise exception in case of not found
self.assertTrue(test_command[idx+1] == 'Letter')

def test_repeatable_options(self):
roptions={
'--page-size': 'Letter',
'cookies': [
('test_cookie1','cookie_value1'),
('test_cookie2','cookie_value2'),
]
}

r = pdfkit.PDFKit('html', 'string', options=roptions)

test_command = r.command('test')

idx1 = test_command.index('--page-size') # Raise exception in case of not found
self.assertTrue(test_command[idx1 + 1] == 'Letter')

self.assertTrue(test_command.count('--cookies') == 2)

idx2 = test_command.index('--cookies')
self.assertTrue(test_command[idx2 + 1] == 'test_cookie1')
self.assertTrue(test_command[idx2 + 2] == 'cookie_value1')

idx3 = test_command.index('--cookies', idx2 + 2)
self.assertTrue(test_command[idx3 + 1] == 'test_cookie2')
self.assertTrue(test_command[idx3 + 2] == 'cookie_value2')

def test_custom_configuration(self):
conf = pdfkit.configuration()
Expand Down