commit
46b9dc31af
28 changed files with 819 additions and 52 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -245,3 +245,4 @@ ENV/
|
|||
# End of https://www.gitignore.io/api/node,linux,python,jetbrains,archlinuxpackages
|
||||
|
||||
out/
|
||||
.mypy_cache
|
||||
|
|
|
@ -18,9 +18,12 @@ dependencies:
|
|||
|
||||
|
||||
test:
|
||||
post:
|
||||
override:
|
||||
- npm test
|
||||
- flake8 md_pdf/ --ignore=E128,E501
|
||||
- mypy --ignore-missing-imports md_pdf
|
||||
- safety check
|
||||
- bandit -r md_pdf/
|
||||
- mdp --update-csl
|
||||
- mdp --update-csl -vvv
|
||||
- cd test-files/ && mdp -vvv
|
||||
- scripts/run-tests.sh
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
bandit==1.4.0
|
||||
coverage==4.4.1
|
||||
flake8==3.3.0
|
||||
freezegun==0.3.9
|
||||
mypy==0.511
|
||||
safety==0.5.1
|
||||
|
|
|
@ -2,10 +2,10 @@ import argparse
|
|||
from md_pdf import __version__
|
||||
|
||||
|
||||
def parse_args():
|
||||
def parse_args(args=None):
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("-v", "--verbose", help="Set verbosity level (repeat argument)", action="count", default=0)
|
||||
parser.add_argument("--update-csl", help="Update CSL files", action="store_true")
|
||||
parser.add_argument("--version", action="version", version="%(prog)s {}".format(__version__))
|
||||
parser.add_help = True
|
||||
return parser.parse_args()
|
||||
return parser.parse_args(args=args)
|
||||
|
|
|
@ -11,11 +11,11 @@ import time
|
|||
logger = logging.getLogger(__file__)
|
||||
|
||||
|
||||
def build(config):
|
||||
def build(config: dict):
|
||||
logger.debug("Starting Build...")
|
||||
start_time = time.time()
|
||||
data = read_files(os.path.abspath(config['input']))
|
||||
doc = build_document(data, config.get('bibliography'), config.get('context'))
|
||||
doc = build_document(data, config.get('bibliography'))
|
||||
parsed_template = parse_template(doc, config)
|
||||
if 'html' in config['output_formats']:
|
||||
output_html(parsed_template, os.path.abspath(config['output_dir']))
|
||||
|
|
|
@ -19,7 +19,7 @@ def fix_references_title(content, config):
|
|||
return soup.prettify()
|
||||
|
||||
|
||||
def add_base_tag(doc, config):
|
||||
def make_images_relative(doc, config):
|
||||
logger.debug("Adding Base Tag...")
|
||||
soup = BeautifulSoup(doc, 'html.parser')
|
||||
for img in soup.findAll('img'):
|
||||
|
@ -32,6 +32,8 @@ def add_base_tag(doc, config):
|
|||
def add_body_class(doc, config):
|
||||
logger.debug("Adding Body Class...")
|
||||
soup = BeautifulSoup(doc, 'html.parser')
|
||||
if not soup.body:
|
||||
return doc
|
||||
soup.body['class'] = 'content'
|
||||
return soup.prettify()
|
||||
|
||||
|
@ -46,7 +48,7 @@ def parse_template(doc, config):
|
|||
parsed_doc = doc
|
||||
for parser in [
|
||||
fix_references_title,
|
||||
add_base_tag,
|
||||
make_images_relative,
|
||||
add_body_class,
|
||||
]:
|
||||
parsed_doc = parser(parsed_doc, config)
|
||||
|
|
|
@ -19,11 +19,14 @@ EXTRA_CONTEXT = {
|
|||
}
|
||||
|
||||
|
||||
def get_context(config, content):
|
||||
def get_context(config: dict, content: str) -> dict:
|
||||
config = config.copy()
|
||||
if 'context' in config:
|
||||
context = config['context'].copy()
|
||||
del config['context']
|
||||
context = dict(
|
||||
else:
|
||||
context = {}
|
||||
merged_context = dict(
|
||||
config,
|
||||
**EXTRA_CONTEXT,
|
||||
**context,
|
||||
|
@ -32,11 +35,11 @@ def get_context(config, content):
|
|||
}
|
||||
)
|
||||
if config.get('show_word_count'):
|
||||
context['word_count'] = word_count(get_plain_text(content))
|
||||
merged_context['word_count'] = word_count(get_plain_text(content))
|
||||
if config.get('submission_date'):
|
||||
if type(config['submission_date']) in [datetime.date, datetime.datetime, datetime.time]:
|
||||
submission_date = config['submission_date']
|
||||
else:
|
||||
submission_date = parser.parse(config['submission_date'])
|
||||
context['submission_date'] = submission_date.strftime(DATE_FORMAT)
|
||||
return context
|
||||
merged_context['submission_date'] = submission_date.strftime(DATE_FORMAT)
|
||||
return merged_context
|
||||
|
|
|
@ -10,7 +10,6 @@ def render_content(content, context):
|
|||
'jinja2.ext.with_',
|
||||
'jinja2.ext.loopcontrols'
|
||||
]
|
||||
|
||||
)
|
||||
template = env.from_string(content)
|
||||
return template.render(**context)
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import glob
|
||||
from typing import Generator, List
|
||||
|
||||
|
||||
def get_files_content(filenames):
|
||||
def get_files_content(filenames: List[str]) -> Generator[str, None, None]:
|
||||
for filename in filenames:
|
||||
with open(filename) as f:
|
||||
yield f.read()
|
||||
|
||||
|
||||
def read_files(files_glob):
|
||||
def read_files(files_glob: str) -> str:
|
||||
filenames = sorted(glob.glob(files_glob))
|
||||
return '\n'.join(list(get_files_content(filenames)))
|
||||
|
|
|
@ -6,13 +6,13 @@ import logging
|
|||
logger = logging.getLogger(__file__)
|
||||
|
||||
|
||||
def output_html(html, out_dir):
|
||||
def output_html(html: str, out_dir: str):
|
||||
logger.info("Outputting HTML...")
|
||||
with open(os.path.join(out_dir, 'output.html'), 'w') as f:
|
||||
f.write(html)
|
||||
|
||||
|
||||
def build_document(files_content, bibliography, context):
|
||||
def build_document(files_content: str, bibliography: dict) -> str:
|
||||
args = [
|
||||
'-s',
|
||||
]
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import pdfkit
|
||||
from md_pdf.consts import TEMPLATES_DIR, STATIC_DIR
|
||||
from md_pdf.build.templates import FILE_NAME_FORMAT
|
||||
from md_pdf.exceptions import PDFRenderException
|
||||
import os
|
||||
import logging
|
||||
|
||||
|
@ -12,8 +13,8 @@ DEFAULT_MARGIN_VERTICAL = '1.5cm'
|
|||
DEFAULT_MARGIN_HORIZONTAL = '2.5cm'
|
||||
|
||||
STYLE_FILE = os.path.join(STATIC_DIR, 'style.css')
|
||||
HEADER_FILE = os.path.join(TEMPLATES_DIR, 'header.html')
|
||||
FOOTER_FILE = os.path.join(TEMPLATES_DIR, 'footer.html')
|
||||
HEADER_FILE = FILE_NAME_FORMAT.format('header')
|
||||
FOOTER_FILE = FILE_NAME_FORMAT.format('footer')
|
||||
|
||||
TOC_OPTIONS = {
|
||||
'xsl-style-sheet': os.path.join(TEMPLATES_DIR, 'toc.xsl')
|
||||
|
@ -32,19 +33,21 @@ PDF_OPTIONS = {
|
|||
}
|
||||
|
||||
|
||||
def export_pdf(content, config):
|
||||
def export_pdf(content: str, config: dict) -> dict:
|
||||
if logger.getEffectiveLevel() > logging.DEBUG:
|
||||
PDF_OPTIONS['quiet'] = ""
|
||||
PDF_OPTIONS['title'] = config.get('title', 'Output')
|
||||
PDF_OPTIONS['replace'] = [(key, str(value)) for key, value in config['context'].items()]
|
||||
context = config.get('context', {})
|
||||
|
||||
PDF_OPTIONS['margin-top'] = config['context'].get('margin_vertical', DEFAULT_MARGIN_VERTICAL)
|
||||
PDF_OPTIONS['margin-bottom'] = config['context'].get('margin_vertical', DEFAULT_MARGIN_VERTICAL)
|
||||
PDF_OPTIONS['margin-left'] = config['context'].get('margin_horizontal', DEFAULT_MARGIN_HORIZONTAL)
|
||||
PDF_OPTIONS['margin-right'] = config['context'].get('margin_horizontal', DEFAULT_MARGIN_HORIZONTAL)
|
||||
PDF_OPTIONS['replace'] = [(key, str(value)) for key, value in context.items()]
|
||||
PDF_OPTIONS['margin-top'] = context.get('margin_vertical', DEFAULT_MARGIN_VERTICAL)
|
||||
PDF_OPTIONS['margin-bottom'] = context.get('margin_vertical', DEFAULT_MARGIN_VERTICAL)
|
||||
PDF_OPTIONS['margin-left'] = context.get('margin_horizontal', DEFAULT_MARGIN_HORIZONTAL)
|
||||
PDF_OPTIONS['margin-right'] = context.get('margin_horizontal', DEFAULT_MARGIN_HORIZONTAL)
|
||||
|
||||
logger.info("Rendering PDF...")
|
||||
return pdfkit.from_string(
|
||||
try:
|
||||
pdfkit.from_string(
|
||||
content,
|
||||
os.path.join(os.path.abspath(config['output_dir']), 'output.pdf'),
|
||||
options=PDF_OPTIONS,
|
||||
|
@ -52,3 +55,6 @@ def export_pdf(content, config):
|
|||
toc=TOC_OPTIONS if config.get('toc') else {},
|
||||
cover_first=True
|
||||
)
|
||||
except OSError as e:
|
||||
raise PDFRenderException('Failed to render PDF. ' + str(e))
|
||||
return PDF_OPTIONS # mostly for testing
|
||||
|
|
|
@ -11,7 +11,7 @@ FILE_NAME_FORMAT = os.path.join(TEMPLATES_DIR, "{}.html")
|
|||
TEMPLATE_FORMAT = os.path.join(INTERNAL_TEMPLATES_DIR, "{}-template.html")
|
||||
|
||||
|
||||
def render_page(input_file, output_file, context):
|
||||
def render_page(input_file: str, output_file: str, context: dict) -> str:
|
||||
logger.debug("Rendering {}...".format(os.path.splitext(os.path.basename(output_file))[0].title()))
|
||||
with open(input_file) as f:
|
||||
content = render_content(f.read(), context)
|
||||
|
@ -20,7 +20,7 @@ def render_page(input_file, output_file, context):
|
|||
return content
|
||||
|
||||
|
||||
def render_templates(config, content):
|
||||
def render_templates(config: dict, content: str):
|
||||
context = get_context(config, content)
|
||||
for template in [
|
||||
'cover',
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import yaml
|
||||
import os
|
||||
from md_pdf.consts import CONFIG_FILE
|
||||
from md_pdf.exceptions import ConfigValidationException
|
||||
|
||||
|
||||
def load_config():
|
||||
def load_config(location: str=CONFIG_FILE) -> str:
|
||||
try:
|
||||
with open(os.path.join(CONFIG_FILE)) as f:
|
||||
with open(location) as f:
|
||||
return yaml.safe_load(f)
|
||||
except FileNotFoundError:
|
||||
raise ConfigValidationException("Can't find config file at {}".format(CONFIG_FILE))
|
||||
|
|
|
@ -36,6 +36,9 @@ def test_input(config):
|
|||
abs_input = os.path.abspath(config['input'])
|
||||
if len(glob.glob(abs_input)) == 0:
|
||||
raise ConfigValidationException("No files found at {}".format(abs_input))
|
||||
for file in glob.iglob(abs_input):
|
||||
if not os.path.isfile(file):
|
||||
raise ConfigValidationException("Input must be a glob of files")
|
||||
|
||||
|
||||
def validate_bibliography(config):
|
||||
|
@ -49,9 +52,8 @@ def validate_bibliography(config):
|
|||
abs_bibliography = os.path.abspath(config['bibliography']['references'])
|
||||
if not os.path.isfile(abs_bibliography):
|
||||
raise ConfigValidationException("Invalid bibliography path: '{}'".format(abs_bibliography))
|
||||
if 'csl' in config['bibliography']:
|
||||
if not os.path.isfile(os.path.join(CSL_DIR, "{}.csl".format(config['bibliography']['csl']))):
|
||||
raise ConfigValidationException("Could not find CSL '{}'".format(config.bibliography.csl))
|
||||
raise ConfigValidationException("Could not find CSL '{}' in {}".format(config['bibliography']['csl'], CSL_DIR))
|
||||
|
||||
|
||||
def validate_context(config):
|
||||
|
@ -63,11 +65,11 @@ def validate_context(config):
|
|||
|
||||
non_str_keys = [key for key in config['context'].keys() if type(key) != str]
|
||||
if non_str_keys:
|
||||
raise ConfigValidationException("Context keys must be strings. Non-strings: {}".format(", ".join(non_str_keys)))
|
||||
raise ConfigValidationException("Context keys must be strings. Non-strings: {}".format(non_str_keys))
|
||||
|
||||
invalid_values = [value for value in config['context'].values() if type(value) in [list, dict]]
|
||||
if invalid_values:
|
||||
raise ConfigValidationException("Context keys must be plain. Invalid values: {}".format(", ".join(invalid_values)))
|
||||
raise ConfigValidationException("Context keys must be plain. Invalid values: {}".format(invalid_values))
|
||||
|
||||
|
||||
def validate_toc(config):
|
||||
|
@ -92,7 +94,7 @@ def validate_submission_date(config):
|
|||
try:
|
||||
parser.parse(config['submission_date'])
|
||||
except ValueError:
|
||||
raise ConfigValidationException("Invalid Submission Date format")
|
||||
raise ConfigValidationException("Invalid Submission Date format {}".format(config['submission_date']))
|
||||
|
||||
|
||||
def validate_config(config):
|
||||
|
|
|
@ -39,7 +39,7 @@ def download_csl():
|
|||
with open(download_location, 'rb') as downloaded_file:
|
||||
with zipfile.ZipFile(downloaded_file) as csl_zip:
|
||||
member_list = csl_zip.namelist()
|
||||
logger.info("Extracting CSL...")
|
||||
logger.info("Extracting CSL to {}".format(ASSETS_DIR))
|
||||
bar.start(max_value=len(member_list))
|
||||
|
||||
for i, member in enumerate(member_list):
|
||||
|
|
|
@ -8,3 +8,7 @@ class PrematureExit(BaseException):
|
|||
|
||||
class ConfigValidationException(BaseException):
|
||||
pass
|
||||
|
||||
|
||||
class PDFRenderException(BaseException):
|
||||
pass
|
||||
|
|
|
@ -2,11 +2,13 @@ import shutil
|
|||
import os
|
||||
import logging
|
||||
from bs4 import BeautifulSoup
|
||||
from typing import List
|
||||
|
||||
|
||||
logger = logging.getLogger(__file__)
|
||||
|
||||
|
||||
def remove_dir(dir):
|
||||
def remove_dir(dir: str):
|
||||
logger.debug("Removing directory {}.".format(dir))
|
||||
try:
|
||||
shutil.rmtree(dir)
|
||||
|
@ -15,16 +17,18 @@ def remove_dir(dir):
|
|||
pass
|
||||
|
||||
|
||||
def safe_list_get(l, idx, default):
|
||||
def safe_list_get(l: List, idx: int, default):
|
||||
try:
|
||||
return l[idx]
|
||||
except IndexError:
|
||||
return default
|
||||
|
||||
|
||||
def get_plain_text(content):
|
||||
def get_plain_text(content: str) -> str:
|
||||
soup = BeautifulSoup(content, 'html.parser')
|
||||
body = soup.find('body')
|
||||
if body is None:
|
||||
return content
|
||||
try:
|
||||
body.find('h1', class_='references-title').extract()
|
||||
body.find('div', class_='references').extract()
|
||||
|
|
8
scripts/run-tests.sh
Executable file
8
scripts/run-tests.sh
Executable file
|
@ -0,0 +1,8 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
coverage run --source=md_pdf -m unittest -v $@
|
||||
|
||||
coverage report
|
||||
coverage html
|
2
setup.py
2
setup.py
|
@ -17,7 +17,7 @@ setup(
|
|||
"word-count==0.1.0"
|
||||
],
|
||||
setup_requires=['setuptools_scm'],
|
||||
packages=find_packages(),
|
||||
packages=find_packages(exclude=['tests']),
|
||||
include_package_data=True,
|
||||
zip_safe=False,
|
||||
entry_points="""
|
||||
|
|
64
tests/__init__.py
Normal file
64
tests/__init__.py
Normal file
|
@ -0,0 +1,64 @@
|
|||
import unittest
|
||||
import os
|
||||
from md_pdf.consts import TEMPLATES_DIR, STATIC_DIR
|
||||
from md_pdf.build.templates import FILE_NAME_FORMAT
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
|
||||
class BaseTestCase(unittest.TestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.BASE_VALID_CONFIG = {
|
||||
'title': 'test title',
|
||||
'input': 'test-files/*.md',
|
||||
'output_formats': [
|
||||
'html', 'pdf'
|
||||
],
|
||||
'output_dir': 'out/',
|
||||
|
||||
}
|
||||
|
||||
def parse_html(self, html):
|
||||
return BeautifulSoup(html, 'html.parser')
|
||||
|
||||
def remove_file(self, file):
|
||||
try:
|
||||
os.remove(file)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def touch_file(self, file):
|
||||
open(file, 'w').close()
|
||||
|
||||
def create_fake_templates(self):
|
||||
for template in [
|
||||
'header',
|
||||
'footer',
|
||||
'cover'
|
||||
]:
|
||||
self.touch_file(FILE_NAME_FORMAT.format(template))
|
||||
|
||||
def extend_config(self, *args):
|
||||
base_config = self.BASE_VALID_CONFIG.copy()
|
||||
for arg in args:
|
||||
base_config = dict(base_config, **arg)
|
||||
return base_config
|
||||
|
||||
def delete_templates(self):
|
||||
for template in [
|
||||
'header.html',
|
||||
'footer.html',
|
||||
'cover.html',
|
||||
'toc.xsl',
|
||||
]:
|
||||
self.remove_file(os.path.join(TEMPLATES_DIR, template))
|
||||
|
||||
def tearDown(self):
|
||||
self.delete_templates()
|
||||
self.remove_file(os.path.join(STATIC_DIR, 'style.css'))
|
||||
|
||||
def call_to_args(self, call):
|
||||
args = tuple(call.call_args)[0]
|
||||
kwargs = tuple(call.call_args)[1]
|
||||
return args, kwargs
|
||||
|
22
tests/test_args.py
Normal file
22
tests/test_args.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
from tests import BaseTestCase
|
||||
from md_pdf.args import parse_args
|
||||
|
||||
|
||||
class ArgParserTestCase(BaseTestCase):
|
||||
def test_allows_no_args(self):
|
||||
args = parse_args([])
|
||||
self.assertFalse(args.update_csl)
|
||||
self.assertEqual(args.verbose, 0)
|
||||
|
||||
def test_adds_verbosity(self):
|
||||
args = parse_args(['-v'])
|
||||
self.assertEqual(args.verbose, 1)
|
||||
|
||||
def test_chains_verbosity(self):
|
||||
for i in range(1, 10):
|
||||
args = parse_args(['-' + ('v' * i)])
|
||||
self.assertEqual(args.verbose, i)
|
||||
|
||||
def test_csl_update(self):
|
||||
args = parse_args(['--update-csl'])
|
||||
self.assertTrue(args.update_csl)
|
239
tests/test_config.py
Normal file
239
tests/test_config.py
Normal file
|
@ -0,0 +1,239 @@
|
|||
from tests import BaseTestCase
|
||||
from md_pdf.config import read, validate
|
||||
from md_pdf.exceptions import ConfigValidationException
|
||||
from md_pdf.consts import CSL_DIR
|
||||
from md_pdf.utils import remove_dir
|
||||
import os
|
||||
import datetime
|
||||
from unittest import skipIf
|
||||
|
||||
|
||||
class ReadConfigTestCase(BaseTestCase):
|
||||
def test_reads_config(self):
|
||||
config = read.load_config(os.path.abspath('test-files/mdp.yml'))
|
||||
self.assertIsInstance(config, dict)
|
||||
|
||||
def test_throws_at_missing_config(self):
|
||||
with self.assertRaises(ConfigValidationException):
|
||||
read.load_config(os.path.abspath('non-existant'))
|
||||
|
||||
|
||||
class ConfigValidatorBaseTestCase(BaseTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
validate.validate_config(self.BASE_VALID_CONFIG)
|
||||
|
||||
|
||||
class ValidateSubmissionDateTestCase(ConfigValidatorBaseTestCase):
|
||||
def test_transparent_to_datetime(self):
|
||||
self.BASE_VALID_CONFIG['submission_date'] = datetime.datetime.now()
|
||||
validate.validate_config(self.BASE_VALID_CONFIG)
|
||||
|
||||
def test_transparent_to_date(self):
|
||||
self.BASE_VALID_CONFIG['submission_date'] = datetime.datetime.now().date()
|
||||
validate.validate_config(self.BASE_VALID_CONFIG)
|
||||
|
||||
def test_transparent_to_time(self):
|
||||
self.BASE_VALID_CONFIG['submission_date'] = datetime.datetime.now().time()
|
||||
validate.validate_config(self.BASE_VALID_CONFIG)
|
||||
|
||||
def test_valid_date_format(self):
|
||||
for date in [
|
||||
'2017-01-01',
|
||||
'01-01-2017',
|
||||
'1st jan 2017',
|
||||
'1 january 2017'
|
||||
]:
|
||||
self.BASE_VALID_CONFIG['submission_date'] = date
|
||||
validate.validate_config(self.BASE_VALID_CONFIG)
|
||||
|
||||
def test_invalid_date_format(self):
|
||||
for date in [
|
||||
'nothing',
|
||||
'31-02-2017',
|
||||
'01-2017-01',
|
||||
'1st smarch 2017'
|
||||
]:
|
||||
self.BASE_VALID_CONFIG['submission_date'] = date
|
||||
with self.assertRaises(ConfigValidationException):
|
||||
validate.validate_config(self.BASE_VALID_CONFIG)
|
||||
|
||||
|
||||
class ValidateWordcountTestCase(ConfigValidatorBaseTestCase):
|
||||
def test_boolean_values_only(self):
|
||||
self.BASE_VALID_CONFIG['show_word_count'] = True
|
||||
validate.validate_config(self.BASE_VALID_CONFIG)
|
||||
self.BASE_VALID_CONFIG['show_word_count'] = False
|
||||
validate.validate_config(self.BASE_VALID_CONFIG)
|
||||
|
||||
def test_invalid_values(self):
|
||||
for value in [
|
||||
'True',
|
||||
'False',
|
||||
0,
|
||||
1
|
||||
]:
|
||||
self.BASE_VALID_CONFIG['show_word_count'] = value
|
||||
with self.assertRaises(ConfigValidationException):
|
||||
validate.validate_config(self.BASE_VALID_CONFIG)
|
||||
|
||||
|
||||
|
||||
class ValidateTOCTestCase(ConfigValidatorBaseTestCase):
|
||||
def test_boolean_values_only(self):
|
||||
self.BASE_VALID_CONFIG['toc'] = True
|
||||
validate.validate_config(self.BASE_VALID_CONFIG)
|
||||
self.BASE_VALID_CONFIG['toc'] = False
|
||||
validate.validate_config(self.BASE_VALID_CONFIG)
|
||||
|
||||
def test_invalid_values(self):
|
||||
for value in [
|
||||
'True',
|
||||
'False',
|
||||
0,
|
||||
1
|
||||
]:
|
||||
self.BASE_VALID_CONFIG['toc'] = value
|
||||
with self.assertRaises(ConfigValidationException):
|
||||
validate.validate_config(self.BASE_VALID_CONFIG)
|
||||
|
||||
|
||||
class ValidateContextTestCase(ConfigValidatorBaseTestCase):
|
||||
def test_should_be_dict(self):
|
||||
for value in [
|
||||
[],
|
||||
'dict',
|
||||
1
|
||||
]:
|
||||
self.BASE_VALID_CONFIG['context'] = value
|
||||
with self.assertRaises(ConfigValidationException):
|
||||
validate.validate_config(self.BASE_VALID_CONFIG)
|
||||
self.BASE_VALID_CONFIG['context'] = {}
|
||||
validate.validate_config(self.BASE_VALID_CONFIG)
|
||||
|
||||
def test_non_string_keys(self):
|
||||
self.BASE_VALID_CONFIG['context'] = {
|
||||
1: 'test'
|
||||
}
|
||||
with self.assertRaises(ConfigValidationException):
|
||||
validate.validate_config(self.BASE_VALID_CONFIG)
|
||||
|
||||
def test_string_keys(self):
|
||||
self.BASE_VALID_CONFIG['context'] = {
|
||||
'1': 'test'
|
||||
}
|
||||
validate.validate_config(self.BASE_VALID_CONFIG)
|
||||
|
||||
def test_valid_values(self):
|
||||
for value in [
|
||||
'test',
|
||||
1
|
||||
]:
|
||||
self.BASE_VALID_CONFIG['context'] = {
|
||||
'test': value
|
||||
}
|
||||
validate.validate_config(self.BASE_VALID_CONFIG)
|
||||
|
||||
def test_invalid_values(self):
|
||||
for value in [
|
||||
[],
|
||||
{}
|
||||
]:
|
||||
self.BASE_VALID_CONFIG['context'] = {
|
||||
'test': value
|
||||
}
|
||||
with self.assertRaises(ConfigValidationException):
|
||||
validate.validate_config(self.BASE_VALID_CONFIG)
|
||||
|
||||
|
||||
class ValidateBibliographyTestCase(ConfigValidatorBaseTestCase):
|
||||
def test_contains_all_keys(self):
|
||||
self.BASE_VALID_CONFIG['bibliography'] = {}
|
||||
with self.assertRaises(ConfigValidationException):
|
||||
validate.validate_config(self.BASE_VALID_CONFIG)
|
||||
self.BASE_VALID_CONFIG['bibliography'] = {
|
||||
'references': 'test'
|
||||
}
|
||||
with self.assertRaises(ConfigValidationException):
|
||||
validate.validate_config(self.BASE_VALID_CONFIG)
|
||||
self.BASE_VALID_CONFIG['bibliography'] = {
|
||||
'csl': 'test'
|
||||
}
|
||||
with self.assertRaises(ConfigValidationException):
|
||||
validate.validate_config(self.BASE_VALID_CONFIG)
|
||||
|
||||
def test_valid_references(self):
|
||||
self.BASE_VALID_CONFIG['bibliography'] = {
|
||||
'references': 'non-existant',
|
||||
'csl': 'chicago-author-date'
|
||||
}
|
||||
with self.assertRaises(ConfigValidationException):
|
||||
validate.validate_config(self.BASE_VALID_CONFIG)
|
||||
self.BASE_VALID_CONFIG['bibliography']['references'] = 'test-files/bib.yaml'
|
||||
validate.validate_config(self.BASE_VALID_CONFIG)
|
||||
|
||||
@skipIf(not os.path.isdir(CSL_DIR), 'Missing CSL Files')
|
||||
def test_valid_csl(self):
|
||||
self.BASE_VALID_CONFIG['bibliography'] = {
|
||||
'references': 'test-files/bib.yaml',
|
||||
'csl': 'nothing'
|
||||
}
|
||||
with self.assertRaises(ConfigValidationException):
|
||||
validate.validate_config(self.BASE_VALID_CONFIG)
|
||||
self.BASE_VALID_CONFIG['bibliography']['csl'] = 'chicago-author-date'
|
||||
validate.validate_config(self.BASE_VALID_CONFIG)
|
||||
|
||||
|
||||
class ValidateInputTestCase(ConfigValidatorBaseTestCase):
|
||||
def test_no_matches(self):
|
||||
self.BASE_VALID_CONFIG['input'] = 'test-files/*.mp4'
|
||||
with self.assertRaises(ConfigValidationException):
|
||||
validate.validate_config(self.BASE_VALID_CONFIG)
|
||||
|
||||
def test_invalid_glob(self):
|
||||
self.BASE_VALID_CONFIG['input'] = 'test-files/'
|
||||
with self.assertRaises(ConfigValidationException):
|
||||
validate.validate_config(self.BASE_VALID_CONFIG)
|
||||
|
||||
|
||||
class ValidateOutputTestCase(ConfigValidatorBaseTestCase):
|
||||
def tearDown(self):
|
||||
super().tearDown()
|
||||
remove_dir('test-files/test')
|
||||
|
||||
def test_creates_output_dir(self):
|
||||
self.assertFalse(os.path.isdir('test-files/test'))
|
||||
self.BASE_VALID_CONFIG['output_dir'] = 'test-files/test'
|
||||
validate.validate_config(self.BASE_VALID_CONFIG)
|
||||
self.assertTrue(os.path.isdir('test-files/test'))
|
||||
|
||||
def test_valid_output_formats(self):
|
||||
for format in [
|
||||
'html',
|
||||
'pdf'
|
||||
]:
|
||||
self.BASE_VALID_CONFIG['output_formats'] = [format]
|
||||
validate.validate_config(self.BASE_VALID_CONFIG)
|
||||
|
||||
def test_invalid_output_formats(self):
|
||||
for format in [
|
||||
'text',
|
||||
'foo'
|
||||
]:
|
||||
self.BASE_VALID_CONFIG['output_formats'] = [format]
|
||||
with self.assertRaises(ConfigValidationException):
|
||||
validate.validate_config(self.BASE_VALID_CONFIG)
|
||||
|
||||
def test_part_invalid_format(self):
|
||||
self.BASE_VALID_CONFIG['output_formats'] = ['html', 'foo']
|
||||
with self.assertRaises(ConfigValidationException):
|
||||
validate.validate_config(self.BASE_VALID_CONFIG)
|
||||
|
||||
|
||||
class ValidateRequiredKeysTestCase(ConfigValidatorBaseTestCase):
|
||||
def test_required_keys(self):
|
||||
for key in validate.REQUIRED_KEYS:
|
||||
base_config = self.BASE_VALID_CONFIG.copy()
|
||||
del base_config[key]
|
||||
with self.assertRaises(ConfigValidationException):
|
||||
validate.validate_config(base_config)
|
98
tests/test_consts.py
Normal file
98
tests/test_consts.py
Normal file
|
@ -0,0 +1,98 @@
|
|||
from tests import BaseTestCase
|
||||
from md_pdf import consts
|
||||
from unittest import skipIf
|
||||
import os
|
||||
from urllib.parse import urlparse
|
||||
import datetime
|
||||
from freezegun import freeze_time
|
||||
|
||||
|
||||
class ConstsTestCase(BaseTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.this_dir = os.path.dirname(__file__)
|
||||
self.project_root = os.path.normpath(os.path.join(self.this_dir, '..'))
|
||||
|
||||
def test_project_dir(self):
|
||||
self.assertEqual(
|
||||
consts.PROJECT_DIR,
|
||||
os.path.normpath(os.path.join(self.this_dir, '..', 'md_pdf'))
|
||||
)
|
||||
self.assertIn(consts.WORKING_DIR, consts.PROJECT_DIR)
|
||||
|
||||
def test_working_dir(self):
|
||||
self.assertEqual(consts.WORKING_DIR, self.project_root)
|
||||
|
||||
@skipIf('APPDATA' not in os.environ, 'not on windows')
|
||||
def test_windows_asset_dir(self):
|
||||
self.assertIn(os.environ['APPDATA'], consts.ASSETS_DIR)
|
||||
|
||||
@skipIf('HOME' not in os.environ, 'not on windows')
|
||||
def test_asset_dir(self):
|
||||
self.assertEqual(consts.ASSETS_DIR, os.path.expanduser('~/.mdp'))
|
||||
|
||||
def test_csl_dir(self):
|
||||
self.assertIn(consts.ASSETS_DIR, consts.CSL_DIR)
|
||||
self.assertIn('csl', consts.CSL_DIR)
|
||||
|
||||
def test_templates_dir(self):
|
||||
self.assertIn(consts.ASSETS_DIR, consts.TEMPLATES_DIR)
|
||||
self.assertIn('templates', consts.TEMPLATES_DIR)
|
||||
|
||||
def test_static_dir(self):
|
||||
self.assertIn(consts.ASSETS_DIR, consts.STATIC_DIR)
|
||||
self.assertIn('static', consts.STATIC_DIR)
|
||||
|
||||
def test_internal_asset_dir(self):
|
||||
self.assertIn(consts.PROJECT_DIR, consts.INTERNAL_ASSETS_DIR)
|
||||
self.assertIn('assets', consts.INTERNAL_ASSETS_DIR)
|
||||
|
||||
def test_internal_static_dir(self):
|
||||
self.assertIn(consts.PROJECT_DIR, consts.INTERNAL_STATIC_DIR)
|
||||
self.assertIn('static', consts.INTERNAL_STATIC_DIR)
|
||||
|
||||
def test_internal_templates_dir(self):
|
||||
self.assertIn(consts.PROJECT_DIR, consts.INTERNAL_TEMPLATES_DIR)
|
||||
self.assertIn('templates', consts.INTERNAL_TEMPLATES_DIR)
|
||||
|
||||
def test_config_file(self):
|
||||
self.assertIn(consts.WORKING_DIR, consts.CONFIG_FILE)
|
||||
self.assertIn('mdp.yml', consts.CONFIG_FILE)
|
||||
|
||||
def test_csl_download_link(self):
|
||||
url = urlparse(consts.CSL_DOWNLOAD_LINK)
|
||||
self.assertEqual(url.netloc, 'github.com')
|
||||
self.assertTrue(url.path.endswith('master.zip'))
|
||||
self.assertIn('citation-style-language/styles', url.path)
|
||||
|
||||
@freeze_time('2017-01-01')
|
||||
def test_date_format(self):
|
||||
now = datetime.datetime.now()
|
||||
self.assertEqual(
|
||||
now.strftime(consts.DATE_FORMAT),
|
||||
'01 January 2017'
|
||||
)
|
||||
|
||||
@freeze_time('2017-01-01T12:34')
|
||||
def test_time_format(self):
|
||||
now = datetime.datetime.now()
|
||||
self.assertEqual(
|
||||
now.strftime(consts.TIME_FORMAT),
|
||||
'12:34'
|
||||
)
|
||||
|
||||
@freeze_time('2017-01-01T12:34')
|
||||
def test_time_format(self):
|
||||
now = datetime.datetime.now()
|
||||
self.assertEqual(
|
||||
now.strftime(consts.DATETIME_FORMAT),
|
||||
'01 January 2017 12:34'
|
||||
)
|
||||
|
||||
def test_dirs_exist(self):
|
||||
self.assertTrue(os.path.isdir(consts.ASSETS_DIR))
|
||||
self.assertTrue(os.path.isdir(consts.TEMPLATES_DIR))
|
||||
self.assertTrue(os.path.isdir(consts.STATIC_DIR))
|
||||
|
||||
|
||||
|
77
tests/test_context.py
Normal file
77
tests/test_context.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
from tests import BaseTestCase
|
||||
from md_pdf.build.context import get_context, EXTRA_CONTEXT
|
||||
from md_pdf import consts
|
||||
from datetime import datetime
|
||||
from md_pdf import __version__
|
||||
import os
|
||||
|
||||
|
||||
class ExtraContextTestCase(BaseTestCase):
|
||||
def test_directories(self):
|
||||
self.assertEqual(EXTRA_CONTEXT['templates_dir'], consts.TEMPLATES_DIR)
|
||||
self.assertEqual(EXTRA_CONTEXT['static_dir'], consts.STATIC_DIR)
|
||||
self.assertEqual(EXTRA_CONTEXT['internal_templates_dir'], consts.INTERNAL_TEMPLATES_DIR)
|
||||
self.assertEqual(EXTRA_CONTEXT['internal_static_dir'], consts.INTERNAL_STATIC_DIR)
|
||||
|
||||
def test_datetimes(self):
|
||||
now = datetime.now()
|
||||
self.assertEqual(EXTRA_CONTEXT['date'], now.strftime(consts.DATE_FORMAT))
|
||||
self.assertEqual(EXTRA_CONTEXT['time'], now.strftime(consts.TIME_FORMAT))
|
||||
self.assertEqual(EXTRA_CONTEXT['datetime'], now.strftime(consts.DATETIME_FORMAT))
|
||||
|
||||
def test_version(self):
|
||||
self.assertEqual(EXTRA_CONTEXT['mdp_version'], __version__)
|
||||
|
||||
|
||||
class ContextTestCase(BaseTestCase):
|
||||
def test_context_contains_extra(self):
|
||||
context = get_context(self.BASE_VALID_CONFIG, 'test')
|
||||
for key in EXTRA_CONTEXT.keys():
|
||||
self.assertIn(key, context)
|
||||
|
||||
def test_context_contains_context(self):
|
||||
config = self.extend_config({
|
||||
'context': {
|
||||
'1': '2'
|
||||
}
|
||||
})
|
||||
context = get_context(config, 'test')
|
||||
self.assertEqual(context['1'], '2')
|
||||
self.assertNotIn('context', context)
|
||||
|
||||
def test_context_contains_config(self):
|
||||
context = get_context(self.BASE_VALID_CONFIG, 'test')
|
||||
for key in self.BASE_VALID_CONFIG.keys():
|
||||
self.assertIn(key, context)
|
||||
|
||||
def test_has_output_dir(self):
|
||||
context = get_context(self.BASE_VALID_CONFIG, 'test')
|
||||
self.assertEqual(context['output_dir'], os.path.abspath(self.BASE_VALID_CONFIG['output_dir']))
|
||||
|
||||
def test_word_count(self):
|
||||
config = self.extend_config({
|
||||
'show_word_count': True
|
||||
})
|
||||
context = get_context(config, 'testy test test')
|
||||
self.assertEqual(context['word_count'], 3)
|
||||
|
||||
def test_transparent_datetime_for_submission_date(self):
|
||||
for value in [
|
||||
datetime.now().date(),
|
||||
datetime.now().time(),
|
||||
datetime.now()
|
||||
]:
|
||||
config = self.extend_config({
|
||||
'submission_date': value
|
||||
})
|
||||
context = get_context(config, 'test')
|
||||
self.assertEqual(context['submission_date'], value.strftime(consts.DATE_FORMAT))
|
||||
|
||||
def test_date_format(self):
|
||||
config = self.extend_config({
|
||||
'submission_date': '2017-01-01'
|
||||
})
|
||||
context = get_context(config, 'test')
|
||||
self.assertEqual(context['submission_date'], '01 January 2017')
|
||||
|
||||
|
27
tests/test_jinja.py
Normal file
27
tests/test_jinja.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
from tests import BaseTestCase
|
||||
from md_pdf.build.jinja import render_content
|
||||
|
||||
|
||||
class ContentRendererTestCase(BaseTestCase):
|
||||
def test_renders_template(self):
|
||||
html = 'test {{ test }}'
|
||||
output = render_content(html, self.extend_config({
|
||||
'test': 'content'
|
||||
}))
|
||||
self.assertEqual(output, 'test content')
|
||||
|
||||
def test_changes_nothing(self):
|
||||
html = 'test test'
|
||||
output = render_content(html, self.extend_config({
|
||||
'test': 'content'
|
||||
}))
|
||||
self.assertEqual(output, html)
|
||||
|
||||
def test_with_block(self):
|
||||
html = """
|
||||
{% with test = 'test' %}
|
||||
{{ test }} thing
|
||||
{% endwith %}
|
||||
"""
|
||||
output = render_content(html, self.BASE_VALID_CONFIG)
|
||||
self.assertIn('test thing', output)
|
67
tests/test_pandoc.py
Normal file
67
tests/test_pandoc.py
Normal file
|
@ -0,0 +1,67 @@
|
|||
from tests import BaseTestCase
|
||||
from md_pdf.build.pandoc import output_html, build_document
|
||||
from md_pdf.build.md import read_files
|
||||
from md_pdf.utils import remove_dir
|
||||
import os
|
||||
import glob
|
||||
|
||||
|
||||
class ReadFileTestCase(BaseTestCase):
|
||||
file_glob = 'test-files/*.md'
|
||||
|
||||
def test_reads_files(self):
|
||||
files = read_files(self.file_glob)
|
||||
self.assertNotEqual(files, '')
|
||||
|
||||
def test_contains_all_files(self):
|
||||
files = read_files(self.file_glob)
|
||||
for file in glob.iglob(self.file_glob):
|
||||
with open(file) as f:
|
||||
self.assertIn(f.read(), files)
|
||||
|
||||
|
||||
class OutputHTMLTestCase(BaseTestCase):
|
||||
output_dir = 'test-output'
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
os.makedirs(self.output_dir)
|
||||
|
||||
def tearDown(self):
|
||||
super().tearDown()
|
||||
remove_dir(self.output_dir)
|
||||
|
||||
def test_outputs_file(self):
|
||||
self.assertFalse(os.path.isfile(os.path.join(self.output_dir, 'output.html')))
|
||||
output_html('test', self.output_dir)
|
||||
self.assertTrue(os.path.isfile(os.path.join(self.output_dir, 'output.html')))
|
||||
|
||||
def test_outputs_correct_data(self):
|
||||
output_html('test', self.output_dir)
|
||||
with open(os.path.join(self.output_dir, 'output.html')) as f:
|
||||
self.assertEqual(f.read(), 'test')
|
||||
|
||||
|
||||
class BuildDocumentTestCase(BaseTestCase):
|
||||
def test_parses_markdown(self):
|
||||
doc = build_document('# test', None)
|
||||
self.assertIn('<h1 id="test">test</h1>', doc)
|
||||
|
||||
def converts_nothing_if_plain(self):
|
||||
doc = build_document('test', None)
|
||||
self.assertIn('test', doc)
|
||||
|
||||
def test_bibliography(self):
|
||||
bibliography = {
|
||||
'references': 'test-files/bib.yaml',
|
||||
'csl': 'chicago-author-date'
|
||||
}
|
||||
with open('test-files/2-pandoc.md') as f:
|
||||
test_content = f.read()
|
||||
doc = build_document(test_content, bibliography)
|
||||
self.assertIn(
|
||||
'<span class="citation">Doe (2005, 2006, 30; see also Doe and Roe 2007)</span> says blah.',
|
||||
doc
|
||||
)
|
||||
self.assertIn('Doe, John. 2005.', doc)
|
||||
|
50
tests/test_parser.py
Normal file
50
tests/test_parser.py
Normal file
|
@ -0,0 +1,50 @@
|
|||
from tests import BaseTestCase
|
||||
from md_pdf.build import content
|
||||
import os
|
||||
|
||||
|
||||
class FixReferencesTitleTestCase(BaseTestCase):
|
||||
def test_adds_reference_title(self):
|
||||
html = '<div class="references"></div>'
|
||||
output = content.fix_references_title(html, self.BASE_VALID_CONFIG)
|
||||
self.assertIn('references-title', output)
|
||||
self.assertIn('References', output)
|
||||
|
||||
def test_doesnt_modify_if_no_references(self):
|
||||
html = 'test text'
|
||||
output = content.fix_references_title(html, self.BASE_VALID_CONFIG)
|
||||
self.assertNotIn('references-title', output)
|
||||
self.assertNotIn('References', output)
|
||||
|
||||
|
||||
class RelativeImageTestCase(BaseTestCase):
|
||||
def test_makes_image_relative(self):
|
||||
html = '<img src="test-files/test-image.png" />'
|
||||
output = self.parse_html(content.make_images_relative(html, self.BASE_VALID_CONFIG))
|
||||
self.assertEqual(output.find('img').attrs['src'], os.path.abspath('test-files/test-image.png'))
|
||||
|
||||
def test_leaves_remote_images(self):
|
||||
html = '<img src="http://example.com/image.png" />'
|
||||
output = self.parse_html(content.make_images_relative(html, self.BASE_VALID_CONFIG))
|
||||
self.assertEqual(output.find('img').attrs['src'], 'http://example.com/image.png')
|
||||
|
||||
|
||||
class AddBodyClassTestCase(BaseTestCase):
|
||||
def test_adds_class(self):
|
||||
html = '<body></body>'
|
||||
output = self.parse_html(content.add_body_class(html, self.BASE_VALID_CONFIG))
|
||||
self.assertEqual(output.body.attrs['class'], ['content'])
|
||||
|
||||
def test_doesnt_change(self):
|
||||
html = 'test content'
|
||||
output = content.add_body_class(html, self.BASE_VALID_CONFIG)
|
||||
self.assertEqual(output, html)
|
||||
|
||||
|
||||
class RenderTemplateTestCase(BaseTestCase):
|
||||
def test_renders_template(self):
|
||||
html = 'test {{ test }}'
|
||||
output = content.render_template(html, self.extend_config({
|
||||
'test': 'content'
|
||||
}))
|
||||
self.assertEqual(output, 'test content')
|
88
tests/test_pdf.py
Normal file
88
tests/test_pdf.py
Normal file
|
@ -0,0 +1,88 @@
|
|||
from tests import BaseTestCase
|
||||
import os
|
||||
from md_pdf.build.pdf import export_pdf, TOC_OPTIONS, DEFAULT_MARGIN_VERTICAL, DEFAULT_MARGIN_HORIZONTAL
|
||||
from unittest.mock import patch
|
||||
from md_pdf.build.templates import FILE_NAME_FORMAT
|
||||
from md_pdf.exceptions import PDFRenderException
|
||||
import pdfkit
|
||||
|
||||
|
||||
class PDFRendererTestCase(BaseTestCase):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.content = 'test content'
|
||||
self.output_file_path = os.path.join(self.BASE_VALID_CONFIG['output_dir'], 'output.pdf')
|
||||
self.assertFalse(os.path.isfile(self.output_file_path))
|
||||
self.create_fake_templates()
|
||||
|
||||
def tearDown(self):
|
||||
super().tearDown()
|
||||
self.remove_file(self.output_file_path)
|
||||
|
||||
def test_renders(self):
|
||||
export_pdf(self.content, self.BASE_VALID_CONFIG)
|
||||
self.assertTrue(os.path.isfile(self.output_file_path))
|
||||
|
||||
def test_title(self):
|
||||
context = export_pdf(self.content, self.BASE_VALID_CONFIG)
|
||||
self.assertEqual(context['title'], self.BASE_VALID_CONFIG['title'])
|
||||
|
||||
def test_replace_context(self):
|
||||
self.BASE_VALID_CONFIG['context'] = {
|
||||
'1': 2,
|
||||
'2': '1'
|
||||
}
|
||||
context = export_pdf(self.content, self.BASE_VALID_CONFIG)
|
||||
self.assertEqual(context['replace'], [
|
||||
('1', '2'),
|
||||
('2', '1'),
|
||||
])
|
||||
|
||||
def test_default_margins(self):
|
||||
context = export_pdf(self.content, self.BASE_VALID_CONFIG)
|
||||
self.assertEqual(context['margin-top'], DEFAULT_MARGIN_VERTICAL)
|
||||
self.assertEqual(context['margin-bottom'], DEFAULT_MARGIN_VERTICAL)
|
||||
self.assertEqual(context['margin-left'], DEFAULT_MARGIN_HORIZONTAL)
|
||||
self.assertEqual(context['margin-right'], DEFAULT_MARGIN_HORIZONTAL)
|
||||
|
||||
def test_override_margin(self):
|
||||
self.BASE_VALID_CONFIG['context'] = {
|
||||
'margin_vertical': '1cm',
|
||||
'margin_horizontal': '2cm'
|
||||
}
|
||||
context = export_pdf(self.content, self.BASE_VALID_CONFIG)
|
||||
self.assertEqual(context['margin-top'], '1cm')
|
||||
self.assertEqual(context['margin-bottom'], '1cm')
|
||||
self.assertEqual(context['margin-left'], '2cm')
|
||||
self.assertEqual(context['margin-right'], '2cm')
|
||||
|
||||
@patch.object(pdfkit, 'from_string')
|
||||
def test_kit_call(self, pdf_render):
|
||||
context = export_pdf(self.content, self.BASE_VALID_CONFIG)
|
||||
self.assertTrue(pdf_render.called)
|
||||
args, kwargs = self.call_to_args(pdf_render)
|
||||
self.assertEqual(args[0], self.content)
|
||||
self.assertIn(self.output_file_path, args[1])
|
||||
self.assertEqual(kwargs['options'], context)
|
||||
self.assertTrue(kwargs['cover_first'])
|
||||
self.assertEqual(kwargs['cover'], FILE_NAME_FORMAT.format('cover'))
|
||||
self.assertEqual(kwargs['toc'], {})
|
||||
|
||||
@patch.object(pdfkit, 'from_string')
|
||||
def test_toc(self, pdf_render):
|
||||
self.BASE_VALID_CONFIG['toc'] = True
|
||||
export_pdf(self.content, self.BASE_VALID_CONFIG)
|
||||
args, kwargs = self.call_to_args(pdf_render)
|
||||
self.assertEqual(kwargs['toc'], TOC_OPTIONS)
|
||||
|
||||
def test_fails_if_missing_templates(self):
|
||||
self.remove_file(FILE_NAME_FORMAT.format('cover'))
|
||||
with self.assertRaises(PDFRenderException):
|
||||
export_pdf(self.content, self.BASE_VALID_CONFIG)
|
||||
|
||||
def test_files_exist(self):
|
||||
context = export_pdf(self.content, self.BASE_VALID_CONFIG)
|
||||
self.assertTrue(os.path.isfile(context['header-html']))
|
||||
self.assertTrue(os.path.isfile(context['footer-html']))
|
||||
self.assertEqual(context['header-html'], FILE_NAME_FORMAT.format('header'))
|
||||
self.assertEqual(context['footer-html'], FILE_NAME_FORMAT.format('footer'))
|
Reference in a new issue