25
25
26
26
# Used to import filters.
27
27
from importlib import import_module
28
+ import importlib .util
28
29
29
30
# Necessary for prince
30
31
import subprocess
@@ -85,6 +86,16 @@ def load_config(config_file=DEFAULT_CONFIG_FILE):
85
86
86
87
config .update (loaded_config )
87
88
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
+
88
99
# Warn if any pages aren't part of a target
89
100
for page in config ["pages" ]:
90
101
if "targets" not in page :
@@ -98,17 +109,38 @@ def load_config(config_file=DEFAULT_CONFIG_FILE):
98
109
page_path = os .path .join (config ["content_path" ], page ["md" ])
99
110
page ["name" ] = guess_title_from_md_file (page_path )
100
111
101
- # Figure out which filters we need and import them
112
+ # Figure out which filters we need
102
113
filternames = set (config ["default_filters" ])
103
114
for target in config ["targets" ]:
104
115
if "filters" in target :
105
116
filternames .update (target ["filters" ])
106
117
for page in config ["pages" ]:
107
118
if "filters" in page :
108
119
filternames .update (page ["filters" ])
109
- for filter_name in filternames :
110
- filters [filter_name ] = import_module ("dactyl.filter_" + filter_name )
111
120
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\n Err:%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 )
112
144
113
145
def default_pdf_name (target ):
114
146
target = get_target (target )
@@ -335,7 +367,8 @@ def get_filters_for_page(page, target=None):
335
367
return ffp
336
368
337
369
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" ):
339
372
"""Take a markdown string and output HTML for that content"""
340
373
target = get_target (target )
341
374
@@ -350,15 +383,31 @@ def parse_markdown(page, target=None, pages=None, bypass_errors=False):
350
383
# We'll apply these filters to the page
351
384
page_filters = get_filters_for_page (page , target )
352
385
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
+ )
355
396
356
397
# Apply markdown-based filters here
357
398
for filter_name in page_filters :
358
399
if "filter_markdown" in dir (filters [filter_name ]):
359
400
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
+ )
362
411
363
412
# Actually parse the markdown
364
413
logger .info ("... parsing markdown..." )
@@ -370,8 +419,16 @@ def parse_markdown(page, target=None, pages=None, bypass_errors=False):
370
419
for filter_name in page_filters :
371
420
if "filter_html" in dir (filters [filter_name ]):
372
421
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
+ )
375
432
376
433
# Some filters would rather operate on a soup than a string.
377
434
# May as well parse once and re-serialize once.
@@ -381,8 +438,16 @@ def parse_markdown(page, target=None, pages=None, bypass_errors=False):
381
438
for filter_name in page_filters :
382
439
if "filter_soup" in dir (filters [filter_name ]):
383
440
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
+ )
386
451
# ^ the soup filters apply to the same object, passed by reference
387
452
388
453
# Replace links and images based on the target
@@ -479,7 +544,8 @@ def get_categories(pages):
479
544
return categories
480
545
481
546
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" ):
483
549
"""Read in a markdown file and pre-process any templating lang in it,
484
550
returning the parsed contents."""
485
551
target = get_target (target )
@@ -492,8 +558,17 @@ def read_markdown_local(filename, pp_env, target=None, bypass_errors=False, curr
492
558
md_out = f .read ()
493
559
else :
494
560
try :
561
+ #TODO: current_time, mode, categories
495
562
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
+ )
497
572
except jinja2 .TemplateError as e :
498
573
traceback .print_tb (e .__traceback__ )
499
574
if bypass_errors :
@@ -516,7 +591,9 @@ def read_markdown_remote(url):
516
591
raise requests .RequestException ("Status code for page was not 200" )
517
592
518
593
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" ):
520
597
"""Read/Fetch and pre-process markdown file"""
521
598
target = get_target (target )
522
599
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
530
607
exit ("Error fetching page %s: %s" % (md_where , e ))
531
608
return mdr
532
609
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 )
534
613
535
614
536
615
def copy_static_files (template_static = True , content_static = True , out_path = None ):
@@ -627,46 +706,55 @@ def toc_from_headers(html_string):
627
706
return str (toc_s )
628
707
629
708
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
+
630
721
def render_pages (target = None , for_pdf = False , bypass_errors = False ):
631
722
"""Parse and render all pages in target, writing files to out_path."""
632
723
target = get_target (target )
633
724
pages = get_pages (target )
634
725
categories = get_categories (pages )
726
+ mode = "pdf" if for_pdf else "html"
727
+ current_time = time .strftime (config ["time_format" ]) # Get time once only
635
728
636
729
# Insert generated HTML into templates using this Jinja environment
637
730
env = setup_html_env ()
638
731
fallback_env = setup_fallback_env ()
639
732
640
733
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 )
651
738
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 )
662
743
663
744
for currentpage in pages :
664
745
if "md" in currentpage :
665
746
# Read and parse the markdown
666
747
667
748
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
+ )
670
758
671
759
except Exception as e :
672
760
if bypass_errors :
@@ -682,36 +770,34 @@ def render_pages(target=None, for_pdf=False, bypass_errors=False):
682
770
else :
683
771
html_content = ""
684
772
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
692
773
693
774
# Prepare some parameters for rendering
694
775
substitute_parameter_links ("doc_page" , currentpage , target )
695
- current_time = time . strftime ( "%B %d, %Y" )
776
+ page_toc = toc_from_headers ( html_content )
696
777
697
778
# Figure out which template to use
698
779
if "template" in currentpage and not for_pdf :
699
780
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 )
701
782
elif "pdf_template" in currentpage and for_pdf :
702
783
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 )
704
785
else :
705
786
use_template = default_template
706
787
707
788
# 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
+ )
715
801
716
802
717
803
if for_pdf :
@@ -807,12 +893,18 @@ def make_pdf(outfile, target=None, bypass_errors=False, remove_tmp=True):
807
893
def githubify (md_file_name , target = None ):
808
894
"""Wrapper - make the markdown resemble GitHub flavor"""
809
895
target = get_target (target )
810
-
811
896
pages = get_pages ()
897
+
898
+ current_time = time .strftime (config ["time_format" ]) # Get time once only
812
899
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
+ )
816
908
817
909
logger .info ("githubifying markdown..." )
818
910
rendered_md = githubify_markdown (md , target = target , pages = pages )
0 commit comments