diff --git a/docs/options.md b/docs/options.md index 985c4d0..3d3b3ec 100644 --- a/docs/options.md +++ b/docs/options.md @@ -22,6 +22,11 @@ plugins: include_css: true enabled: true exclude: + include: + print_docs_dir: "" + pages_to_print: + - page_name: 'print_page' + config: ``` `add_to_navigation` @@ -91,4 +96,78 @@ plugins: ``` `exclude` -: Default is empty. Allows to specify a list of page source paths that should not be included in the print page. See [Do Not Print](how-to/do_not_print.md#ignoring-an-entire-page) for more info. \ No newline at end of file +: Default is empty. Allows to specify a list of page source paths that should not be included in the print page. See [Do Not Print](how-to/do_not_print.md#ignoring-an-entire-page) for more info. +`include` +: Default is * to include the entire site. Allows to specify a list of page source paths that is then filtered by the exclude options to create a final list that should be included in the print page. This does not change the root. +`print_docs_dir` +: Default is "*" to use the site docs_dir. This can be set to a sub folder of the site docs_dir to set the new root for the PDF print page. + +`pages_to_print` +: This is to define multiple pdf print areas with their own config sections that allow overriding the global parameters. + +`page_name` +: This is the name of the page to be used to access the pdf print page. e.g. sitename/%page_name% +`config` +: Configurations specific to the page_name that override the global level options. + + +## Multiple PDFs +A site can contain directory structures to support various aspects of a business or product. Help Guides, Setup Guides, Onboarding Guides or Maintenance processes. These areas often consist of multiple markdown pages of content that would need to be aggregated using the mkdocs_print_site_plugin into a single page. The pages_to_print section allows defining the name of the single page and config overrides for that PDF page. + +If the pages_to_print is not present the configuration settings for a single 'print_page' will apply. + +```yml + - print-site: + add_cover_page: true + cover_page_template: "docs/assets/cover_page_policy.tpl" + add_table_of_contents: true + add_print_site_banner: false + pages_to_print: + - page_name: 'print_page' + config: + - add_table_of_contents: true + - add_print_site_banner: false + - print_page_title: "Full Web Site" + - page_name: 'print_policy_page' + config: + - add_table_of_contents: true + - add_print_site_banner: false + - print_page_title: "Policy" + - print_docs_dir: "Reference/Policies" + - page_name: 'print_presales' + config: + - add_table_of_contents: false + - add_print_site_banner: true + - print_page_title: "Pre Sales Guide" + - print_docs_dir: "Reference/Pre Sales Guide" + - exclude: + - InternalProcesses* + - page_name: 'print_summer_catalog' + config: + - add_cover_page: true + - add_table_of_contents: true + - add_print_site_banner: false + - print_page_title: "Summer Catalog" + - cover_page_template: "docs/assets/cover_page_summer_catalog.tpl" + - include: + - jetskis* + - motorbikes* + - exclude: + - data* + - page_name: 'print_winter_catalog' + config: + - add_cover_page: true + - add_table_of_contents: true + - add_print_site_banner: false + - print_page_title: "Winter Catalog" + - include: + - snowmobiles* + - skiis* + - exclude: + - data* + - cover_page_template: "docs/assets/cover_page_winter_catalog.tpl" + +``` + +The example above is contrived, but illustrates generation of pdfs for different topics or areas of a docs site. + diff --git a/mkdocs_print_site_plugin/plugin.py b/mkdocs_print_site_plugin/plugin.py index 51f7f1a..1dfe8b0 100644 --- a/mkdocs_print_site_plugin/plugin.py +++ b/mkdocs_print_site_plugin/plugin.py @@ -11,13 +11,13 @@ from mkdocs.exceptions import PluginError from mkdocs_print_site_plugin.renderer import Renderer -from mkdocs_print_site_plugin.utils import flatten_nav, get_theme_name +from mkdocs_print_site_plugin.utils import flatten_nav, get_theme_name, find_new_root from mkdocs_print_site_plugin.urls import is_external logger = logging.getLogger("mkdocs.plugins") HERE = os.path.dirname(os.path.abspath(__file__)) - +from mkdocs_print_site_plugin.exclude import exclude class PrintSitePlugin(BasePlugin): """ @@ -42,8 +42,11 @@ class PrintSitePlugin(BasePlugin): ("include_css", config_options.Type(bool, default=True)), ("enabled", config_options.Type(bool, default=True)), ("exclude", config_options.Type(list, default=[])), + ("include", config_options.Type(list, default=["*"])), + ("print_docs_dir", config_options.Type(str, default="*")), + ("pages_to_print", config_options.Type(list, default=[])), ) - + page_renderers = {} def on_config(self, config, **kwargs): """ Event trigger on config. @@ -76,110 +79,188 @@ def on_config(self, config, **kwargs): msg += "when using the --dirtyreload option." logger.warning(msg) - # Get abs path to cover_page_template - self.cover_page_template_path = "" - if self.config.get("add_cover_page"): - if self.config.get("cover_page_template") == "": - self.cover_page_template_path = os.path.join( - HERE, "templates", "cover_page.tpl" - ) - else: - self.cover_page_template_path = os.path.join( - os.path.dirname(config.get("config_file_path")), - self.config.get("cover_page_template"), - ) - if not os.path.exists(self.cover_page_template_path): - msg = "[print-site-plugin]: Path specified in 'cover_page_template' not found." - msg += "\nMake sure to use the URL relative to your mkdocs.yml file." - logger.warning(msg) - raise FileNotFoundError( - "File not found: %s" % self.cover_page_template_path - ) + def get_cover_page_template_path(config_data): + cover_page_template_path="" + if config_data["add_cover_page"]: + if config_data["cover_page_template"] == "": + cover_page_template_path = os.path.join( + HERE, "templates", "cover_page.tpl" + ) + else: + cover_page_template_path = os.path.join( + os.path.dirname(config.get("config_file_path")), + config_data["cover_page_template"], + ) + if not os.path.exists(cover_page_template_path): + msg = "[print-site-plugin]: Path specified in 'cover_page_template' not found." + msg += "\nMake sure to use the URL relative to your mkdocs.yml file." + logger.warning(msg) + raise FileNotFoundError( + "File not found: %s" % cover_page_template_path + ) + return cover_page_template_path # Get abs path to print_site_banner_template - self.banner_template_path = "" - if self.config.get("add_print_site_banner"): - if self.config.get("print_site_banner_template") == "": - self.banner_template_path = os.path.join( - HERE, "templates", "print_site_banner.tpl" - ) - else: - self.banner_template_path = os.path.join( - os.path.dirname(config.get("config_file_path")), - self.config.get("print_site_banner_template"), - ) - if not os.path.exists(self.banner_template_path): - msg = "[print-site-plugin]: Path specified in 'print_site_banner_template' not found." - msg += "\nMake sure to use the URL relative to your mkdocs.yml file." - logger.warning(msg) - raise FileNotFoundError( - "File not found: %s" % self.banner_template_path - ) - - # Add pointer to print-site javascript - config["extra_javascript"] = ["js/print-site.js"] + config["extra_javascript"] - - # Add pointer to theme specific css files - if self.config.get("include_css"): - file = "print-site-%s.css" % get_theme_name(config) - if file in os.listdir(os.path.join(HERE, "css")): - config["extra_css"] = ["css/%s" % file] + config["extra_css"] - else: - msg = f"[mkdocs-print-site] Theme '{get_theme_name(config)}' not yet supported\n" - msg += "which means print margins and page breaks might be off. Feel free to open an issue!" - logger.warning(msg) - - # Add pointer to print-site css files - config["extra_css"] = ["css/print-site.css"] + config["extra_css"] - - # Enumeration CSS files - self.enum_css_files = [] - - if self.config.get('enumerate_headings'): - self.enum_css_files += ["css/print-site-enum-headings1.css"] - if self.config.get('enumerate_headings_depth') >= 2: - self.enum_css_files += ["css/print-site-enum-headings2.css"] - if self.config.get('enumerate_headings_depth') >= 3: - self.enum_css_files += ["css/print-site-enum-headings3.css"] - if self.config.get('enumerate_headings_depth') >= 4: - self.enum_css_files += ["css/print-site-enum-headings4.css"] - if self.config.get('enumerate_headings_depth') >= 5: - self.enum_css_files += ["css/print-site-enum-headings5.css"] - if self.config.get('enumerate_headings_depth') >= 6: - self.enum_css_files += ["css/print-site-enum-headings6.css"] - - config["extra_css"] = self.enum_css_files + config["extra_css"] - - - # Create MkDocs Page and File instances - self.print_file = File( - path="print_page.md", - src_dir="", - dest_dir=config["site_dir"], - use_directory_urls=config.get("use_directory_urls"), - ) - self.print_page = Page( - title=self.config.get("print_page_title"), - file=self.print_file, - config=config, - ) - self.print_page.edit_url = None - - # Save instance of the print page renderer - self.renderer = Renderer( - plugin_config=self.config, - mkdocs_config=config, - cover_page_template_path=self.cover_page_template_path, - banner_template_path=self.banner_template_path, - print_page=self.print_page, - ) - - # Tracker - # to see if context has been extracted from - # template context - self.context = {} + def get_banner_template_path(config_data): + banner_template_path = "" + if config_data["add_print_site_banner"]: + if config_data["print_site_banner_template"] == "": + banner_template_path = os.path.join( + HERE, "templates", "print_site_banner.tpl" + ) + else: + banner_template_path = os.path.join( + os.path.dirname(config.get("config_file_path")), + config_data["print_site_banner_template"], + ) + if not os.path.exists(banner_template_path): + msg = "[print-site-plugin]: Path specified in 'print_site_banner_template' not found." + msg += "\nMake sure to use the URL relative to your mkdocs.yml file." + logger.warning(msg) + raise FileNotFoundError( + "File not found: %s" % banner_template_path + ) + return banner_template_path + + def validate_page_entry(entry): + if not isinstance(entry, dict): + raise ValueError("Each entry must be a dictionary.") + if "page_name" not in entry.keys() or "config" not in entry.keys(): + raise ValueError("Each entry must contain 'page' and 'config' keys.") + if not isinstance(entry["config"], list): + raise ValueError("The 'config' key must contain a list.") + + def convert_config_to_dict(config_list): + return {list(detail.keys())[0]: list(detail.values())[0] for detail in config_list} + + def merge_config(default_config, custom_config): + for key, value in default_config.items(): + if key not in custom_config: + custom_config[key] = value + return custom_config + + + # Create the default print_page + global_page = { + "page_name": "print_page", + "config": { + "add_cover_page": self.config.get("add_cover_page"), + "cover_page_template" : self.config.get("cover_page_template"), + "exclude": self.config.get("exclude"), + "include": self.config.get("include"), + "add_to_navigation": self.config.get("add_to_navigation"), + "print_page_title" : self.config.get("print_page_title"), + "add_table_of_contents": self.config.get("add_table_of_contents"), + "toc_title" : self.config.get("toc_title"), + "toc_depth" : self.config.get("toc_depth"), + "add_full_urls": self.config.get("add_full_urls"), + "enumerate_headings": self.config.get("enumerate_headings"), + "enumerate_headings_depth" : self.config.get("enumerate_headings_depth"), + "enumerate_figures": self.config.get("enumerate_figures"), + "add_print_site_banner": self.config.get("add_print_site_banner"), + "print_site_banner_template" : self.config.get("print_site_banner_template"), + "path_to_pdf" : self.config.get("path_to_pdf"), + "include_css" : self.config.get("include_css"), + "enabled": self.config.get("enabled"), + "print_docs_dir" : self.config.get("print_docs_dir") + } + } + + pages_to_print = self.config.get("pages_to_print") + self.print_pages={} + ## Determine if pages_to_print is populated convert to print_pages or use default + if isinstance(pages_to_print, list) and len(pages_to_print)>0: + for new_page in pages_to_print: + #validate the data + validate_page_entry(new_page) + self.print_pages[new_page["page_name"]] =merge_config( + global_page['config'], + convert_config_to_dict(new_page["config"])) + else: + # If not add default single site content + self.print_pages[global_page["page_name"]]=global_page['config'] + + # Loop though the self.print_pages and + # convert the single page self._____ attribute storage + # to a self.print_pages[key]._____ attribute storage + + # We'll address how to handle the returned config later. + for page_name, page_config in self.print_pages.items(): + # Get abs path to cover_page_template + #self.cover_page_template_path = get_cover_page_template_path(global_page['config']) + page_config['cover_page_template_path'] = get_cover_page_template_path(page_config) + + # Get abs path to print_site_banner_template + #self.banner_template_path = get_banner_template_path(global_page['config']) + page_config['banner_template_path'] = get_banner_template_path(page_config) + + # Add pointer to print-site javascript + config["extra_javascript"] = ["js/print-site.js"] + config["extra_javascript"] + + # Add pointer to theme specific css files + # if self.config.get("include_css"): + if page_config["include_css"]: + file = "print-site-%s.css" % get_theme_name(config) + if file in os.listdir(os.path.join(HERE, "css")): + config["extra_css"] = ["css/%s" % file] + config["extra_css"] + else: + msg = f"[mkdocs-print-site] Theme '{get_theme_name(config)}' not yet supported\n" + msg += "which means print margins and page breaks might be off. Feel free to open an issue!" + logger.warning(msg) + + # Add pointer to print-site css files + config["extra_css"] = ["css/print-site.css"] + config["extra_css"] + + # Add pointer to print-site css files + config["extra_css"] = ["css/print-site.css"] + config["extra_css"] + + # Enumeration CSS files + page_config['enum_css_files']=[] + if page_config['enumerate_headings']: + page_config['enum_css_files'] += ["css/print-site-enum-headings1.css"] + if page_config['enumerate_headings_depth'] >= 2: + page_config['enum_css_files'] += ["css/print-site-enum-headings2.css"] + if page_config['enumerate_headings_depth'] >= 3: + page_config['enum_css_files'] += ["css/print-site-enum-headings3.css"] + if page_config['enumerate_headings_depth'] >= 4: + page_config['enum_css_files'] += ["css/print-site-enum-headings4.css"] + if page_config['enumerate_headings_depth'] >= 5: + page_config['enum_css_files'] += ["css/print-site-enum-headings5.css"] + if page_config['enumerate_headings_depth'] >= 6: + page_config['enum_css_files'] += ["css/print-site-enum-headings6.css"] + + if page_config['enum_css_files'] not in config["extra_css"]: + config["extra_css"] = page_config['enum_css_files']+ config["extra_css"] + # Create MkDocs Page and File instances + page_config['print_file'] = File( + path=f'{page_name}.md', + src_dir="", + dest_dir=config["site_dir"], + use_directory_urls=config.get("use_directory_urls"), + ) + page_config['print_page'] = Page( + title=page_config["print_page_title"], + file=page_config['print_file'], + config=config, + ) + page_config['print_page'].edit_url = None + + # Save instance of the print page renderer + self.page_renderers[page_name] = Renderer( + page_config=page_config, + mkdocs_config=config, + cover_page_template_path=page_config['cover_page_template_path'], + banner_template_path=page_config['banner_template_path'], + print_page=page_config['print_page'], + ) + # Tracker + # to see if context has been extracted from + # template context + page_config['context'] = {} return config + + def on_nav(self, nav, config, files, **kwargs): """ @@ -191,14 +272,38 @@ def on_nav(self, nav, config, files, **kwargs): if not self.config.get("enabled"): return nav - # Save the (order of) pages and sections in the navigation before adding the print page - self.renderer.items = nav.items - self.all_pages_in_nav = flatten_nav(nav.items) + for page_name, page_config in self.print_pages.items(): + if not page_config['enabled']: + continue + # Save the (order of) pages and sections in the navigation before adding the print page + print_dir=page_config['print_docs_dir'] + if print_dir != "*": + new_root = find_new_root(nav.items,print_dir ) + # If x == None the print_docs_dir is an invalid path. + if new_root is None: + msg = f'[mkdocs-print-site] Warning the \'print_docs_dir\' path ' + msg += f'{page_config["print_docs_dir"]} is invalid for page_name: {page_name}. ' + msg += "Check the path. If correct the format is Section\\Section\\Section. " + msg += "This corresponds to Directory\\Directory\\Directory, but mkdocs " + msg += "will upper case a Directory from 'project' to 'Project'. " + msg += "Please update the 'plugins:' section in your mkdocs.yml" + logger.warning(msg) + else: + if hasattr(new_root, 'children'): + self.page_renderers[page_name].items = new_root.children + page_config['all_pages_in_nav'] = flatten_nav(new_root.children) + else: + self.page_renderers[page_name].items = new_root + page_config['all_pages_in_nav'] = flatten_nav(new_root) + else: + self.page_renderers[page_name].items = nav.items + page_config['all_pages_in_nav'] = flatten_nav(nav.items) + + # Optionally add the print page to the site navigation + if page_config['add_to_navigation']: + nav.items.append(page_config['print_page']) + nav.pages.append(page_config['print_page']) - # Optionally add the print page to the site navigation - if self.config.get("add_to_navigation"): - nav.items.append(self.print_page) - nav.pages.append(self.print_page) return nav @@ -211,20 +316,27 @@ def on_page_content(self, html, page, config, files, **kwargs): """ if not self.config.get("enabled"): return html - - # Save each page HTML *before* a template is applied inside the page class - if page != self.print_page: - page.html = html - - # Link to the PDF version of the entire site on a page. - if self.config.get("path_to_pdf") != "": - pdf_url = self.config.get("path_to_pdf") - if is_external(pdf_url): - page.url_to_pdf = pdf_url - else: - page.url_to_pdf = get_relative_url( - pdf_url, page.file.url - ) + html_pages = [val['print_page'] for k, val in self.print_pages.items()] + + for page_name, page_config in self.print_pages.items(): + if not page_config['enabled']: + continue + + # Save each page HTML *before* a template is applied inside the page class + # if page != self.print_page: + # page.html = html + if page not in html_pages: + page.html = html + + # Link to the PDF version of the entire site on a page. + if page_config['path_to_pdf'] != "": + pdf_url = page_config['path_to_pdf'] + if is_external(pdf_url): + page_config['url_to_pdf'] = pdf_url + else: + page_config['url_to_pdf'] = get_relative_url( + pdf_url, page.file.url + ) return html @@ -238,9 +350,12 @@ def on_page_context(self, context, page, config, nav, **kwargs): if not self.config.get("enabled"): return - # Save relative link to print page - # This can be used to customize a theme and add a print button to each page - page.url_to_print_page = self.print_file.url_relative_to(page.file) + for page_name, page_config in self.print_pages.items(): + if not page_config['enabled']: + continue + # Save relative link to print page + # This can be used to customize a theme and add a print button to each page + page_config['url_to_print_page'] = page_config['print_file'].url_relative_to(page.file) def on_template_context(self, context, template_name, config, **kwargs): """ @@ -251,26 +366,29 @@ def on_template_context(self, context, template_name, config, **kwargs): """ if not self.config.get("enabled"): return - - # Save the page context - # We'll use the same context of the last rendered page - # And apply it to the print page as well (in on_post_build event) - - # Note a theme can have multiple templates - # Found a bug where in the mkdocs theme, - # the "sitemap.xml" static template - # has incorrect 'extra_css' and 'extra_js' paths - # leading to breaking the print page - # at random (when sitemap.xml was rendered last) - # we're assuming here all templates have a 404.html template - # print(f"\nName: {template_name}\nContext: {context.get('extra_css')}") - if template_name == "404.html": - self.context = context - # Make sure paths are OK - if config.get('extra_css'): - self.context['extra_css'] = [get_relative_url(f, self.print_page.file.url) for f in config.get('extra_css')] - if config.get('extra_javascript'): - self.context['extra_javascript'] = [get_relative_url(str(f), self.print_page.file.url) for f in config.get('extra_javascript')] + + for page_name, page_config in self.print_pages.items(): + if not page_config['enabled']: + continue + # Save the page context + # We'll use the same context of the last rendered page + # And apply it to the print page as well (in on_post_build event) + + # Note a theme can have multiple templates + # Found a bug where in the mkdocs theme, + # the "sitemap.xml" static template + # has incorrect 'extra_css' and 'extra_js' paths + # leading to breaking the print page + # at random (when sitemap.xml was rendered last) + # we're assuming here all templates have a 404.html template + # print(f"\nName: {template_name}\nContext: {context.get('extra_css')}") + if template_name == "404.html": + page_config['context'] = context + # Make sure paths are OK + if config.get('extra_css'): + page_config['context']['extra_css'] = [get_relative_url(f, page_config['print_page'].file.url) for f in config.get('extra_css')] + if config.get('extra_javascript'): + page_config['context']['extra_javascript'] = [get_relative_url(str(f), page_config['print_page'].file.url) for f in config.get('extra_javascript')] def on_post_build(self, config, **kwargs): @@ -282,111 +400,115 @@ def on_post_build(self, config, **kwargs): if not self.config.get("enabled"): return - if len(self.context) == 0: - msg = "Could not find a template context.\n" - msg += "Report an issue at https://github.com/timvink/mkdocs-print-site-plugin\n" - msg += f"And mention the template you're using: {get_theme_name(config)}" - raise PluginError(msg) - - # Add print-site.js - js_output_base_path = os.path.join(config["site_dir"], "js") - js_file_path = os.path.join(js_output_base_path, "print-site.js") - copy_file(os.path.join(os.path.join(HERE, "js"), "print-site.js"), js_file_path) - - if self.config.get("include_css"): - # Add print-site.css - css_output_base_path = os.path.join(config["site_dir"], "css") - css_file_path = os.path.join(css_output_base_path, "print-site.css") - copy_file( - os.path.join(os.path.join(HERE, "css"), "print-site.css"), css_file_path - ) - - # Add enumeration css - for f in self.enum_css_files: - f = f.replace("/", os.sep) - css_file_path = os.path.join(config["site_dir"], f) + for page_name, page_config in self.print_pages.items(): + if not page_config['enabled']: + continue + + if len(page_config['context']) == 0: + msg = "Could not find a template context.\n" + msg += "Report an issue at https://github.com/timvink/mkdocs-print-site-plugin\n" + msg += f"And mention the template you're using: {get_theme_name(config)}" + raise PluginError(msg) + + # Add print-site.js + js_output_base_path = os.path.join(config["site_dir"], "js") + js_file_path = os.path.join(js_output_base_path, "print-site.js") + copy_file(os.path.join(os.path.join(HERE, "js"), "print-site.js"), js_file_path) + + if page_config['include_css']: + # Add print-site.css + css_output_base_path = os.path.join(config["site_dir"], "css") + css_file_path = os.path.join(css_output_base_path, "print-site.css") copy_file( - os.path.join(HERE, f), css_file_path + os.path.join(os.path.join(HERE, "css"), "print-site.css"), css_file_path ) - # Add theme CSS file - css_file = "print-site-%s.css" % get_theme_name(config) - if css_file in os.listdir(os.path.join(HERE, "css")): - css_file_path = os.path.join(css_output_base_path, css_file) - copy_file( - os.path.join(os.path.join(HERE, "css"), css_file), css_file_path + # Add enumeration css + for f in page_config['enum_css_files']: + f = f.replace("/", os.sep) + css_file_path = os.path.join(config["site_dir"], f) + copy_file( + os.path.join(HERE, f), css_file_path + ) + + # Add theme CSS file + css_file = "print-site-%s.css" % get_theme_name(config) + if css_file in os.listdir(os.path.join(HERE, "css")): + css_file_path = os.path.join(css_output_base_path, css_file) + copy_file( + os.path.join(os.path.join(HERE, "css"), css_file), css_file_path + ) + + # Combine the HTML of all pages present in the navigation + page_config['print_page'].content = self.page_renderers[page_name].write_combined() + # Generate a TOC sidebar for HTML version of print page + page_config['print_page'].toc = self.page_renderers[page_name].get_toc_sidebar() + + # Get the info for MkDocs to be able to apply a theme template on our print page + env = config["theme"].get_env() + # env.list_templates() + template = env.get_template("main.html") + page_config['context']["page"] = page_config['print_page'] + # Render the theme template for the print page + html = template.render(page_config['context']) + + # Remove lazy loading attributes from images + # https://regex101.com/r/HVpKPs/1 + html = re.sub(r"(\ + # Details https://github.com/timvink/mkdocs-print-site-plugin/issues/68 + htmls = html.split("") + base_url = "../" if config.get("use_directory_urls") else "" + htmls[0] = htmls[0].replace("href=\"//", f"href=\"{base_url}") + htmls[0] = htmls[0].replace("src=\"//", f"src=\"{base_url}") + html = "".join(htmls) + + # Determine calls to required javascript functions + js_calls = "remove_material_navigation();" + js_calls += "remove_mkdocs_theme_navigation();" + if page_config['add_table_of_contents']: + js_calls += "generate_toc();" + + # Inject JS into print page + print_site_js = ( + """ + + """ + % js_calls ) + html = html.replace("", print_site_js + "") - # Compatibility with https://github.com/g-provost/lightgallery-markdown - # This plugin insert link hrefs with double dashes, f.e. - # - # Details https://github.com/timvink/mkdocs-print-site-plugin/issues/68 - htmls = html.split("") - base_url = "../" if config.get("use_directory_urls") else "" - htmls[0] = htmls[0].replace("href=\"//", f"href=\"{base_url}") - htmls[0] = htmls[0].replace("src=\"//", f"src=\"{base_url}") - html = "".join(htmls) - - # Determine calls to required javascript functions - js_calls = "remove_material_navigation();" - js_calls += "remove_mkdocs_theme_navigation();" - if self.config.get("add_table_of_contents"): - js_calls += "generate_toc();" - - # Inject JS into print page - print_site_js = ( - """ - - """ - % js_calls - ) - html = html.replace("", print_site_js + "") - - # Write the print_page file to the output folder - write_file( - html.encode("utf-8", errors="xmlcharrefreplace"), - self.print_page.file.abs_dest_path, - ) + # Write the print_page file to the output folder + write_file( + html.encode("utf-8", errors="xmlcharrefreplace"), + page_config['print_page'].file.abs_dest_path, + ) diff --git a/mkdocs_print_site_plugin/renderer.py b/mkdocs_print_site_plugin/renderer.py index 8959298..e56ea7c 100644 --- a/mkdocs_print_site_plugin/renderer.py +++ b/mkdocs_print_site_plugin/renderer.py @@ -21,7 +21,7 @@ class Renderer(object): def __init__( self, - plugin_config, + page_config, mkdocs_config=None, cover_page_template_path="", banner_template_path="", @@ -30,7 +30,7 @@ def __init__( """ Inits the class. """ - self.plugin_config = plugin_config + self.page_config = page_config self.mkdocs_config = mkdocs_config or {} self.cover_page_template_path = cover_page_template_path self.banner_template_path = banner_template_path @@ -48,13 +48,13 @@ def write_combined(self): enabled_classes = [] # Enable options via CSS - if self.plugin_config.get("add_full_urls"): + if self.page_config['add_full_urls']: enabled_classes.append("print-site-add-full-url") - if self.plugin_config.get("enumerate_headings"): + if self.page_config['enumerate_headings']: enabled_classes.append("print-site-enumerate-headings") - if self.plugin_config.get("enumerate_figures"): + if self.page_config['enumerate_figures']: enabled_classes.append("print-site-enumerate-figures") # Wrap entire print page in a div @@ -62,17 +62,17 @@ def write_combined(self): html = '" @@ -155,7 +164,7 @@ def _cover_page(self): Inserts the cover page. """ env = jinja2.Environment() - env.globals = {"config": self.mkdocs_config, "page": self.print_page} + env.globals = {"config": self.mkdocs_config, "page": self.print_page, "page_config": self.page_config} with open( self.cover_page_template_path, "r", encoding="utf-8-sig", errors="strict" @@ -178,7 +187,7 @@ def _print_site_banner(self): Inserts the print site banner. """ env = jinja2.Environment() - env.globals = {"config": self.mkdocs_config, "page": self.print_page} + env.globals = {"config": self.mkdocs_config, "page": self.print_page, "page_config": self.page_config} with open( self.banner_template_path, "r", encoding="utf-8-sig", errors="strict" @@ -199,9 +208,9 @@ def _toc(self): """ return f""" @@ -218,20 +227,34 @@ def get_toc_sidebar(self) -> TableOfContents: Reference: https://github.com/mkdocs/mkdocs/blob/master/mkdocs/structure/toc.py """ - toc = [] - if self.plugin_config.get("enumerate_headings"): + if self.page_config["enumerate_headings"]: chapter_number = 0 section_number = 0 + toc = [] + toc = self.get_toc_sidebar_section(items=self._get_items(),included_pages=self.page_config['include'], excluded_pages=self.page_config['exclude']) + - for item in self._get_items(): + return TableOfContents(toc) + + def get_toc_sidebar_section(self, items: list, included_pages: list, excluded_pages: list, level: int = 0, chapter_number:int =0, section_number:int =0 ): + toc=[] + for item in items: if item.is_page: page_key = get_page_key(item.url) + # Do not exclude page in print page if included + if not exclude(item.file.src_path,included_pages): + logging.debug(f"Excluding page '{item.file.src_path}'") + continue + # Do not include page in print page if excluded + if exclude(item.file.src_path, excluded_pages): + logging.debug(f"Excluding page '{item.file.src_path}'") + continue # navigate to top of page if page is homepage if page_key == "index": page_key = "" - if self.plugin_config.get("enumerate_headings"): + if self.page_config['enumerate_headings']: chapter_number += 1 title = f"{chapter_number}. {item.title}" else: @@ -239,8 +262,7 @@ def get_toc_sidebar(self) -> TableOfContents: toc.append(AnchorLink(title=title, id=f"{page_key}", level=0)) if item.is_section: - - if self.plugin_config.get("enumerate_headings"): + if self.page_config['enumerate_headings']: section_number += 1 title = f"{int_to_roman(section_number)}. {item.title}" else: @@ -249,24 +271,19 @@ def get_toc_sidebar(self) -> TableOfContents: section_link = AnchorLink( title=title, id=f"section-{to_snake_case(item.title)}", level=0 ) - - subpages = [p for p in item.children if p.is_page] - for page in subpages: - if self.plugin_config.get("enumerate_headings"): - chapter_number += 1 - title = f"{chapter_number}. {page.title}" - else: - title = page.title - - page_key = get_page_key(page.url) - section_link.children.append( - AnchorLink(title=title, id=f"{page_key}", level=1) - ) - - toc.append(section_link) - - return TableOfContents(toc) - + section_toc = self.get_toc_sidebar_section(items=item.children, + included_pages=included_pages, + excluded_pages=excluded_pages, + level=(level+1), + chapter_number = chapter_number, + section_number=section_number ) + if len(section_toc) > 0: + toc.append(section_link) + for link in section_toc: + toc.append(link) + else: + section_number -= 1 + return toc def int_to_roman(num): @@ -294,4 +311,5 @@ def int_to_roman(num): for (n, roman) in lookup: (d, num) = divmod(num, n) res += roman * d - return res + + return res \ No newline at end of file diff --git a/mkdocs_print_site_plugin/templates/cover_page.tpl b/mkdocs_print_site_plugin/templates/cover_page.tpl index af282cb..68e4e09 100644 --- a/mkdocs_print_site_plugin/templates/cover_page.tpl +++ b/mkdocs_print_site_plugin/templates/cover_page.tpl @@ -5,6 +5,10 @@

{{ config.site_name }}

{% endif %} + {% if page_config.print_page_title %} +

{{ page_config.print_page_title }}

+ {% endif %} + diff --git a/mkdocs_print_site_plugin/utils.py b/mkdocs_print_site_plugin/utils.py index 2441293..28ba287 100644 --- a/mkdocs_print_site_plugin/utils.py +++ b/mkdocs_print_site_plugin/utils.py @@ -25,7 +25,34 @@ def get_theme_name(config) -> str: else: return name - +def find_new_root( root, path): + # Split the path by '/' + path_parts = path.strip('/').split('/') + + # Recursive helper function + def _find_node(current_node, parts): + if not parts: + return current_node + + # Get the next part of the path + next_part = parts[0] + + # Look for the next node among the current node's children + if not hasattr(current_node, 'children'): + return None + for child in current_node.children: + if child.is_section: + if child.title.lower() in next_part.lower(): + return _find_node(child, parts[1:]) + + return None # If the node is not found + for node in root: + if node.is_section: + if node.title == path_parts[0]: + return _find_node(node, path_parts[1:]) + + return None + def flatten_nav(items): """ Create a flat list of pages from a nested navigation. diff --git a/tests/fixtures/projects/basic/docs/Sub1/b.md b/tests/fixtures/projects/basic/docs/Sub1/b.md new file mode 100644 index 0000000..23b890b --- /dev/null +++ b/tests/fixtures/projects/basic/docs/Sub1/b.md @@ -0,0 +1,4 @@ +# B + +This is page B, from `b.md`. + diff --git a/tests/fixtures/projects/basic/docs/Sub1/c.md b/tests/fixtures/projects/basic/docs/Sub1/c.md new file mode 100644 index 0000000..30c59e4 --- /dev/null +++ b/tests/fixtures/projects/basic/docs/Sub1/c.md @@ -0,0 +1,4 @@ +# C + +This is page C, from `c.md`. + diff --git a/tests/fixtures/projects/basic/docs/Sub1/sub1_exclude.md b/tests/fixtures/projects/basic/docs/Sub1/sub1_exclude.md new file mode 100644 index 0000000..57d5904 --- /dev/null +++ b/tests/fixtures/projects/basic/docs/Sub1/sub1_exclude.md @@ -0,0 +1,3 @@ +# Exclude + +This is page Sub 1 Exclude, from `sub1_exclude.md`. diff --git a/tests/fixtures/projects/basic/docs/Sub2/d.md b/tests/fixtures/projects/basic/docs/Sub2/d.md new file mode 100644 index 0000000..5c68cfa --- /dev/null +++ b/tests/fixtures/projects/basic/docs/Sub2/d.md @@ -0,0 +1,3 @@ +# D + +This is page D, from `d.md`. diff --git a/tests/fixtures/projects/basic/docs/Sub2/e.md b/tests/fixtures/projects/basic/docs/Sub2/e.md new file mode 100644 index 0000000..81cb0c1 --- /dev/null +++ b/tests/fixtures/projects/basic/docs/Sub2/e.md @@ -0,0 +1,3 @@ +# E + +This is page E, from `e.md`. diff --git a/tests/fixtures/projects/basic/docs/Sub2/sub2_exclude.md b/tests/fixtures/projects/basic/docs/Sub2/sub2_exclude.md new file mode 100644 index 0000000..119102c --- /dev/null +++ b/tests/fixtures/projects/basic/docs/Sub2/sub2_exclude.md @@ -0,0 +1,3 @@ +# Exclude + +This is page Sub 2 Exclude, from `exclude.md`. diff --git a/tests/fixtures/projects/basic/docs/exclude.md b/tests/fixtures/projects/basic/docs/exclude.md new file mode 100644 index 0000000..26f6609 --- /dev/null +++ b/tests/fixtures/projects/basic/docs/exclude.md @@ -0,0 +1,3 @@ +# Exclude + +This is page Exclude, from `exclude.md`. diff --git a/tests/fixtures/projects/basic/mkdocs_with_include_and_theme.yml b/tests/fixtures/projects/basic/mkdocs_with_include_and_theme.yml new file mode 100644 index 0000000..35fb680 --- /dev/null +++ b/tests/fixtures/projects/basic/mkdocs_with_include_and_theme.yml @@ -0,0 +1,13 @@ +site_name: Test + +theme: + name: material + palette: + scheme: slate + +plugins: + - print-site: + add_to_navigation: true + include: + - z.md + - a.md \ No newline at end of file diff --git a/tests/fixtures/projects/basic/mkdocs_with_multi_pdf_and_theme.yml b/tests/fixtures/projects/basic/mkdocs_with_multi_pdf_and_theme.yml new file mode 100644 index 0000000..7ac06c0 --- /dev/null +++ b/tests/fixtures/projects/basic/mkdocs_with_multi_pdf_and_theme.yml @@ -0,0 +1,31 @@ +site_name: Test + +theme: + name: material + palette: + scheme: slate + +plugins: + - print-site: + add_to_navigation: true + exclude: + - exclude.md + - Sub2/sub2_exclude.md + pages_to_print: + - page_name: 'print_sub1' + config: + - add_table_of_contents: true + - print_page_title: "Sub 1 Title" + - print_docs_dir: "Sub1" + - exclude: + - Sub1/sub1_exclude.md + - page_name: 'print_sub2' + config: + - add_table_of_contents: true + - print_page_title: "Sub 2 Title" + - add_to_navigation: false + - exclude: + - '**exclude**' + - include: + - z.md + - Sub2* \ No newline at end of file diff --git a/tests/test_building.py b/tests/test_building.py index 4c1b7bf..cd06776 100644 --- a/tests/test_building.py +++ b/tests/test_building.py @@ -275,6 +275,68 @@ def test_basic_build7(tmp_path): """ check_build(tmp_path, "bad_headings/mkdocs.yml", exit_code=0) + +def test_basic_build8(tmp_path): + """ + Test include + """ + prj_path = check_build(tmp_path, "basic/mkdocs_with_include_and_theme.yml") + + # Print page should be in the navigation + assert text_in_page( + prj_path, + "index.html", + 'href="print_page\/" class="md-nav__link"', + ) + + # Make sure the 2 pages are combined and present + assert text_in_page(prj_path, "print_page/index.html", '

A

') + assert text_in_page(prj_path, "print_page/index.html", '

Z

') + +def test_basic_build9(tmp_path): + """ + Test include + """ + prj_path = check_build(tmp_path, "basic/mkdocs_with_multi_pdf_and_theme.yml") + + # Sub 1 print page should be in the navigation + assert text_in_page( + prj_path, + "index.html", + 'href="print_sub1\/" class="md-nav__link"', + ) + # Test override + # Sub 2 print page should not be in navigation + assert not text_in_page( + prj_path, + "index.html", + 'href="print_sub2\/" class="md-nav__link"', + ) + # print_sub1 Tests + # Make sure the 2 pages are combined and present in sub1 + assert text_in_page(prj_path, "print_sub1/index.html", 'This is page B') + assert text_in_page(prj_path, "print_sub1/index.html", 'This is page C') + assert text_in_page(prj_path, "print_sub1/index.html", 'Sub1') + # Make sure content not in Sub1 is excluded + assert not text_in_page(prj_path, "print_sub1/index.html", 'This is page Z') + assert not text_in_page(prj_path, "print_sub1/index.html", 'This is page A') + assert not text_in_page(prj_path, "print_sub1/index.html", 'This is page Exclude') + # Check exclude parameter + assert not text_in_page(prj_path, "print_sub1/index.html", 'This is page Sub 1 Exclude') + + # print_sub2 Tests + # make sure sub 2 includes a, z, and sub2 folder + assert text_in_page(prj_path, "print_sub2/index.html", 'This is page Z') + assert text_in_page(prj_path, "print_sub2/index.html", 'This is page D') + assert text_in_page(prj_path, "print_sub2/index.html", 'This is page E') + assert not text_in_page(prj_path, "print_sub2/index.html", 'This is page Sub 1 Exclude') + assert not text_in_page(prj_path, "print_sub2/index.html", 'This is page Sub 2 Exclude') + assert not text_in_page(prj_path, "print_sub2/index.html", 'This is page Exclude') + + + + + def test_build_with_material_tags(tmp_path): """ Test support with tags.