Skip to content

Commit a60d6a9

Browse files
committed
v0.4.0a1: filter refactor, better examples/docs
1 parent e1c96bb commit a60d6a9

33 files changed

+522
-90
lines changed

dactyl/dactyl_build.py

+151-59
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
# Used to import filters.
2727
from importlib import import_module
28+
import importlib.util
2829

2930
# Necessary for prince
3031
import subprocess
@@ -85,6 +86,16 @@ def load_config(config_file=DEFAULT_CONFIG_FILE):
8586

8687
config.update(loaded_config)
8788

89+
# Migrate legacy config fields
90+
if "pdf_template" in config:
91+
if "default_pdf_template" in config:
92+
logger.warning("Ignoring redundant global config option "+
93+
"pdf_template in favor of default_pdf_template")
94+
else:
95+
config["default_pdf_template"] = config["pdf_template"]
96+
logger.warning("Deprecation warning: Global field pdf_template has "
97+
+"been renamed default_pdf_template")
98+
8899
# Warn if any pages aren't part of a target
89100
for page in config["pages"]:
90101
if "targets" not in page:
@@ -98,17 +109,38 @@ def load_config(config_file=DEFAULT_CONFIG_FILE):
98109
page_path = os.path.join(config["content_path"], page["md"])
99110
page["name"] = guess_title_from_md_file(page_path)
100111

101-
# Figure out which filters we need and import them
112+
# Figure out which filters we need
102113
filternames = set(config["default_filters"])
103114
for target in config["targets"]:
104115
if "filters" in target:
105116
filternames.update(target["filters"])
106117
for page in config["pages"]:
107118
if "filters" in page:
108119
filternames.update(page["filters"])
109-
for filter_name in filternames:
110-
filters[filter_name] = import_module("dactyl.filter_"+filter_name)
111120

121+
load_filters(filternames)
122+
123+
def load_filters(filternames):
124+
global filters
125+
for filter_name in filternames:
126+
filter_loaded = False
127+
if "filter_paths" in config:
128+
for filter_path in config["filter_paths"]:
129+
try:
130+
f_filepath = os.path.join(filter_path, "filter_"+filter_name+".py")
131+
spec = importlib.util.spec_from_file_location(
132+
"dactyl_filters."+filter_name, f_filepath)
133+
filters[filter_name] = importlib.util.module_from_spec(spec)
134+
spec.loader.exec_module(filters[filter_name])
135+
filter_loaded = True
136+
break
137+
except Exception as e:
138+
logger.debug("Filter %s isn't in path %s\nErr:%s" %
139+
(filter_name, filter_path, e))
140+
141+
if not filter_loaded:
142+
# Load from the Dactyl module
143+
filters[filter_name] = import_module("dactyl.filter_"+filter_name)
112144

113145
def default_pdf_name(target):
114146
target = get_target(target)
@@ -335,7 +367,8 @@ def get_filters_for_page(page, target=None):
335367
return ffp
336368

337369

338-
def parse_markdown(page, target=None, pages=None, bypass_errors=False):
370+
def parse_markdown(page, target=None, pages=None, bypass_errors=False,
371+
categories=[], mode="html", current_time="TIME_UNKNOWN"):
339372
"""Take a markdown string and output HTML for that content"""
340373
target = get_target(target)
341374

@@ -350,15 +383,31 @@ def parse_markdown(page, target=None, pages=None, bypass_errors=False):
350383
# We'll apply these filters to the page
351384
page_filters = get_filters_for_page(page, target)
352385

353-
md = get_markdown_for_page(page["md"], pp_env=pp_env, target=target,
354-
bypass_errors=bypass_errors, currentpage=page)
386+
md = get_markdown_for_page(
387+
page["md"],
388+
pp_env=pp_env,
389+
target=target,
390+
bypass_errors=bypass_errors,
391+
currentpage=page,
392+
categories=categories,
393+
mode=mode,
394+
current_time=current_time,
395+
)
355396

356397
# Apply markdown-based filters here
357398
for filter_name in page_filters:
358399
if "filter_markdown" in dir(filters[filter_name]):
359400
logger.info("... applying markdown filter %s" % filter_name)
360-
md = filters[filter_name].filter_markdown(md, target=target,
361-
page=page, config=config)
401+
md = filters[filter_name].filter_markdown(
402+
md,
403+
currentpage=page,
404+
categories=categories,
405+
pages=pages,
406+
target=target,
407+
current_time=current_time,
408+
mode=mode,
409+
config=config,
410+
)
362411

363412
# Actually parse the markdown
364413
logger.info("... parsing markdown...")
@@ -370,8 +419,16 @@ def parse_markdown(page, target=None, pages=None, bypass_errors=False):
370419
for filter_name in page_filters:
371420
if "filter_html" in dir(filters[filter_name]):
372421
logger.info("... applying HTML filter %s" % filter_name)
373-
html = filters[filter_name].filter_html(html, target=target,
374-
page=page, config=config)
422+
html = filters[filter_name].filter_html(
423+
html,
424+
currentpage=page,
425+
categories=categories,
426+
pages=pages,
427+
target=target,
428+
current_time=current_time,
429+
mode=mode,
430+
config=config,
431+
)
375432

376433
# Some filters would rather operate on a soup than a string.
377434
# May as well parse once and re-serialize once.
@@ -381,8 +438,16 @@ def parse_markdown(page, target=None, pages=None, bypass_errors=False):
381438
for filter_name in page_filters:
382439
if "filter_soup" in dir(filters[filter_name]):
383440
logger.info("... applying soup filter %s" % filter_name)
384-
filters[filter_name].filter_soup(soup, target=target,
385-
page=page, config=config)
441+
filters[filter_name].filter_soup(
442+
soup,
443+
currentpage=page,
444+
categories=categories,
445+
pages=pages,
446+
target=target,
447+
current_time=current_time,
448+
mode=mode,
449+
config=config,
450+
)
386451
# ^ the soup filters apply to the same object, passed by reference
387452

388453
# Replace links and images based on the target
@@ -479,7 +544,8 @@ def get_categories(pages):
479544
return categories
480545

481546

482-
def read_markdown_local(filename, pp_env, target=None, bypass_errors=False, currentpage={}):
547+
def read_markdown_local(filename, pp_env, target=None, bypass_errors=False,
548+
currentpage={}, categories=[], mode="html", current_time="TIME_UNKNOWN"):
483549
"""Read in a markdown file and pre-process any templating lang in it,
484550
returning the parsed contents."""
485551
target = get_target(target)
@@ -492,8 +558,17 @@ def read_markdown_local(filename, pp_env, target=None, bypass_errors=False, curr
492558
md_out = f.read()
493559
else:
494560
try:
561+
#TODO: current_time, mode, categories
495562
md_raw = pp_env.get_template(filename)
496-
md_out = md_raw.render(target=target, pages=pages, currentpage=currentpage)
563+
md_out = md_raw.render(
564+
currentpage=currentpage,
565+
categories=categories,
566+
pages=pages,
567+
target=target,
568+
current_time=current_time,
569+
mode=mode,
570+
config=config
571+
)
497572
except jinja2.TemplateError as e:
498573
traceback.print_tb(e.__traceback__)
499574
if bypass_errors:
@@ -516,7 +591,9 @@ def read_markdown_remote(url):
516591
raise requests.RequestException("Status code for page was not 200")
517592

518593

519-
def get_markdown_for_page(md_where, pp_env=None, target=None, bypass_errors=False, currentpage={}):
594+
def get_markdown_for_page(md_where, pp_env=None, target=None,
595+
bypass_errors=False, currentpage={}, categories=[], mode="html",
596+
current_time="TIME_UNKNOWN"):
520597
"""Read/Fetch and pre-process markdown file"""
521598
target = get_target(target)
522599
if "http:" in md_where or "https:" in md_where:
@@ -530,7 +607,9 @@ def get_markdown_for_page(md_where, pp_env=None, target=None, bypass_errors=Fals
530607
exit("Error fetching page %s: %s" % (md_where, e))
531608
return mdr
532609
else:
533-
return read_markdown_local(md_where, pp_env, target, bypass_errors, currentpage=currentpage)
610+
return read_markdown_local(md_where, pp_env, target, bypass_errors,
611+
currentpage=currentpage, categories=categories, mode=mode,
612+
current_time=current_time)
534613

535614

536615
def copy_static_files(template_static=True, content_static=True, out_path=None):
@@ -627,46 +706,55 @@ def toc_from_headers(html_string):
627706
return str(toc_s)
628707

629708

709+
def safe_get_template(template_name, env, fallback_env):
710+
"""
711+
Gets the named Jinja template from the specified template path if it exists,
712+
and falls back to the Dactyl built-in templates if it doesn't.
713+
"""
714+
try:
715+
t = env.get_template(template_name)
716+
except jinja2.exceptions.TemplateNotFound:
717+
logger.warning("falling back to Dactyl built-ins for template %s" % template_name)
718+
t = fallback_env.get_template(template_name)
719+
return t
720+
630721
def render_pages(target=None, for_pdf=False, bypass_errors=False):
631722
"""Parse and render all pages in target, writing files to out_path."""
632723
target = get_target(target)
633724
pages = get_pages(target)
634725
categories = get_categories(pages)
726+
mode = "pdf" if for_pdf else "html"
727+
current_time = time.strftime(config["time_format"]) # Get time once only
635728

636729
# Insert generated HTML into templates using this Jinja environment
637730
env = setup_html_env()
638731
fallback_env = setup_fallback_env()
639732

640733
if for_pdf:
641-
try:
642-
if "pdf_template" in target:
643-
logger.debug("reading pdf template %s from target..." % target["pdf_template"])
644-
default_template = env.get_template(target["pdf_template"])
645-
else:
646-
logger.debug("reading default pdf template %s..." % config["pdf_template"])
647-
default_template = env.get_template(config["pdf_template"])
648-
except jinja2.exceptions.TemplateNotFound:
649-
logger.warning("falling back to Dactyl built-in PDF template")
650-
default_template = fallback_env.get_template(config["pdf_template"])
734+
if "pdf_template" in target:
735+
default_template = safe_get_template(target["pdf_template"], env, fallback_env)
736+
else:
737+
default_template = safe_get_template(config["default_pdf_template"], env, fallback_env)
651738
else:
652-
try:
653-
if "template" in target:
654-
logger.debug("reading HTML template %s from target..." % target["template"])
655-
default_template = env.get_template(target["template"])
656-
else:
657-
logger.debug("reading default HTML template %s..." % config["default_template"])
658-
default_template = env.get_template(config["default_template"])
659-
except jinja2.exceptions.TemplateNotFound:
660-
logger.warning("falling back to Dactyl built-in HTML template")
661-
default_template = fallback_env.get_template(config["default_template"])
739+
if "template" in target:
740+
default_template = safe_get_template(target["template"], env, fallback_env)
741+
else:
742+
default_template = safe_get_template(config["default_template"], env, fallback_env)
662743

663744
for currentpage in pages:
664745
if "md" in currentpage:
665746
# Read and parse the markdown
666747

667748
try:
668-
html_content = parse_markdown(currentpage, target=target,
669-
pages=pages, bypass_errors=bypass_errors)
749+
html_content = parse_markdown(
750+
currentpage,
751+
target=target,
752+
pages=pages,
753+
bypass_errors=bypass_errors,
754+
mode=mode,
755+
current_time=current_time,
756+
categories=categories
757+
)
670758

671759
except Exception as e:
672760
if bypass_errors:
@@ -682,36 +770,34 @@ def render_pages(target=None, for_pdf=False, bypass_errors=False):
682770
else:
683771
html_content = ""
684772

685-
# default to a table-of-contents sidebar...
686-
if "sidebar" not in currentpage:
687-
currentpage["sidebar"] = "toc"
688-
if currentpage["sidebar"] == "toc":
689-
sidebar_content = toc_from_headers(html_content)
690-
else:
691-
sidebar_content = None
692773

693774
# Prepare some parameters for rendering
694775
substitute_parameter_links("doc_page", currentpage, target)
695-
current_time = time.strftime("%B %d, %Y")
776+
page_toc = toc_from_headers(html_content)
696777

697778
# Figure out which template to use
698779
if "template" in currentpage and not for_pdf:
699780
logger.debug("using template %s from page" % currentpage["template"])
700-
use_template = env.get_template(currentpage["template"])
781+
use_template = safe_get_template(currentpage["template"], env, fallback_env)
701782
elif "pdf_template" in currentpage and for_pdf:
702783
logger.debug("using pdf_template %s from page" % currentpage["pdf_template"])
703-
use_template = env.get_template(currentpage["pdf_template"])
784+
use_template = safe_get_template(currentpage["pdf_template"], env, fallback_env)
704785
else:
705786
use_template = default_template
706787

707788
# Render the content into the appropriate template
708-
out_html = use_template.render(currentpage=currentpage,
709-
categories=categories,
710-
pages=pages,
711-
content=html_content,
712-
target=target,
713-
current_time=current_time,
714-
sidebar_content=sidebar_content)
789+
out_html = use_template.render(
790+
currentpage=currentpage,
791+
categories=categories,
792+
pages=pages,
793+
content=html_content,
794+
target=target,
795+
current_time=current_time,
796+
sidebar_content=page_toc, # legacy
797+
page_toc=page_toc,
798+
mode=mode,
799+
config=config
800+
)
715801

716802

717803
if for_pdf:
@@ -807,12 +893,18 @@ def make_pdf(outfile, target=None, bypass_errors=False, remove_tmp=True):
807893
def githubify(md_file_name, target=None):
808894
"""Wrapper - make the markdown resemble GitHub flavor"""
809895
target = get_target(target)
810-
811896
pages = get_pages()
897+
898+
current_time = time.strftime(config["time_format"]) # Get time once only
812899
logger.info("getting markdown for page %s" % md_file_name)
813-
md = get_markdown_for_page(md_file_name,
814-
pp_env=setup_pp_env(),
815-
target=target)
900+
md = get_markdown_for_page(
901+
md_file_name,
902+
pp_env=setup_pp_env(),
903+
target=target,
904+
categories=[],
905+
mode="md",
906+
current_time=current_time,
907+
)
816908

817909
logger.info("githubifying markdown...")
818910
rendered_md = githubify_markdown(md, target=target, pages=pages)

dactyl/default-config.yml

+4-2
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,11 @@ prince_executable: prince
3838
## If this is true, parses the files as Markdown without Jinja syntax
3939
skip_preprocessor: false
4040

41-
## Default templates. If you provide a real
41+
## Default templates.
4242
default_template: template-default.html
43-
pdf_template: template-default.html
43+
default_pdf_template: template-default.html
44+
45+
time_format: "%Y-%m-%d" # Used to be "%B %d, %Y"
4446

4547
## Default cover page / index page
4648
cover_page:

dactyl/filter_add_version.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@
99
import re
1010
import logging
1111

12-
def filter_markdown(md, target=None, page=None, config=None):
12+
def filter_markdown(md, currentpage=currentpage, **kwargs):
1313
"""Finds the version number and adds it to the start of the page."""
1414
version_regex = r"https://raw.githubusercontent.com/([A-Za-z0-9_.-]+)/([A-Za-z0-9_.-]+)/([A-Za-z0-9_-]+\.[A-Za-z0-9_.-]+)/.+\.md"
1515

1616
try:
17-
version_match = re.match(version_regex, page["md"])
17+
version_match = re.match(version_regex, currentpage["md"])
1818
except (TypeError, KeyError):
19-
logging.warning("couldn't get MD path from page %s" % page)
19+
logging.warning("couldn't get MD path from page %s" % currentpage)
2020
return md
2121

2222
try:

dactyl/filter_badges.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
BADGE_REGEX = re.compile("BADGE_(BRIGHTGREEN|GREEN|YELLOWGREEN|YELLOW|ORANGE|RED|LIGHTGREY|BLUE|[0-9A-Fa-f]{6})")
1515

16-
def filter_soup(soup, target=None, page=None, config=None):
16+
def filter_soup(soup, **kwargs):
1717
"""replace underscores with dashes in h1,h2,etc. for backwards compatibility"""
1818

1919
badge_links = soup.find_all(name="a", title=BADGE_REGEX)

0 commit comments

Comments
 (0)