Source code for cece.parser

"""
.. module cece.parser

The parser reads the files and metadata from the file system, and builds a list
of files to write out to the file system.
"""

from __future__ import print_function
from __future__ import unicode_literals

import cece.util
import fnmatch
import os


[docs]def _get_breadcrumbs(path): """ A utility method for getting the breadcrumb links from a path. They are built by repeatedly taking the dirname of the path. For example, ``"category1/category2/variant1/variant2"`` would produce:: [ "category1", "category1/category2", "category1/category2/variant1", "category1/category2/variant1/variant2" ] """ path = os.path.dirname(path) breadcrumbs = [] while path: breadcrumbs.insert(0, path) path = os.path.dirname(path) return breadcrumbs
[docs]class ParsingException(Exception): """ A custom exception that is thrown when there are errors parsing the files. :param path: The path of the file that raised the exception :type path: string :param message: The error message :type message: string """ def __init__(self, path, message): super(ParsingException, self).__init__("{}: {}".format(path, message))
[docs]class Parser(object): """ The parser object. :param config: The configuration :type config: dict """ def __init__(self, config): self._config = config self._contents = None
[docs] def parse(self): """ Parse the files and cache the parsed result. :returns: The parsed files, as a dictionary """ self._contents = {} # move into the guides source directory cur_dir = os.getcwd() os.chdir("guides") # the empty string is the root folder self._contents[""] = { "type": "folder", "links": [], "name": self._config["site_title"], "short_name": self._config["site_title"], "breadcrumbs": [] } # add entries for all folders in the root directory for f in os.listdir("."): self._load_dirs(f) # sort the child links for each folder using natural sort for folder in filter(lambda x: x["type"] == "folder", self._contents.values()): cece.util.natural_sort(folder["links"], key=lambda x: self._contents[x]["name"]) # change back to the original directory os.chdir(cur_dir) return self._contents
[docs] def _load_folder(self, meta, path): """ Load the contents of a folder into the cache. :param meta: The meta data for the folder :type meta: dict :param path: The path of the folder :type path: string """ # load the children child_links = [] for child_folder in os.listdir(path): child_folder_path = os.path.join(path, child_folder) # if the child directory is a folder, load it if os.path.isdir(child_folder_path): # if the load succeeds, add the child to the list of children links if self._load_dirs(child_folder_path): child_links.append(child_folder_path) # add link to folder to parent self._contents[os.path.dirname(path)]["links"].append(path) # add entry for folder self._contents[path] = { "type": "folder", "links": child_links, "name": meta["name"], "short_name": meta["name"], "description": meta["description"], "breadcrumbs": _get_breadcrumbs(path) }
[docs] def _load_guide(self, meta, path): """ Load the contents of the guide into the cache. :param meta: The meta data for the guide :type meta: dict :param path: The path to the guide folder :type path: string """ # add entry for main guide folder self._contents[path] = { "type": "folder", "links": [], "name": meta["name"], "short_name": meta["name"], "description": meta["description"], "breadcrumbs": _get_breadcrumbs(path) } # loop through all markdown files for variant_src_path in fnmatch.filter(os.listdir(path), "*.md"): variant_full_path = os.path.join(path, variant_src_path) # verify that only one variant of each group is in the file name, # and that all variant groups have a variant variant_tags = os.path.splitext(variant_src_path)[0].split("-") actual_tags = [] # iterate over each expected variant group for expected_variant_group_key in meta["variant_groups"]: expected_variant_group = self._config["variant_groups"][expected_variant_group_key] found_one = False # loop over variants in expected group for variant in expected_variant_group["variants"]: # if the current variant is in the list of tags, do the following: # if it is new, append it to the actual tags # if one was previously found, raise an exception if variant["id"] in variant_tags: if found_one: raise ParsingException( variant_full_path, "Cannot have multiple variants in the variant group \"{}\"." .format(expected_variant_group["name"])) actual_tags.append(variant["id"]) found_one = True # if no variants in the expected group were found, raise an exception if not found_one: raise ParsingException( variant_full_path, "No variants for variant group \"{}\" found" .format(expected_variant_group["name"])) # create entries for variant tags if they don't exist for variant_tags in cece.util.iterate_list_subsets(actual_tags[:-1]): current_tag = variant_tags[-1] parent = os.path.join(path, *variant_tags[:-1]) tag_id = os.path.join(path, *variant_tags) # this tag may have been added already as a parent of another # tag, so check that first if tag_id not in self._contents: # add tag to parent links self._contents[parent]["links"].append(tag_id) # add entry for self self._contents[tag_id] = { "type": "folder", "links": [], "name": "{} ({})".format(meta["name"], ", ".join( self._config["variants"][variant_tag]["name"] for variant_tag in variant_tags)), "short_name": self._config["variants"][current_tag]["name"], "description": meta["description"], "breadcrumbs": _get_breadcrumbs(tag_id) } # create entry for variant current_tag = actual_tags[-1] parent = os.path.join(path, *actual_tags[:-1]) variant_id = os.path.join(path, *actual_tags) # add variant to parent links self._contents[parent]["links"].append(variant_id) self._contents[variant_id] = { "type": "page", "name": "{} ({})".format(meta["name"], ", ".join( self._config["variants"][actual_tag]["name"] for actual_tag in actual_tags)), "short_name": self._config["variants"][current_tag]["name"], "description": meta["description"], "source_path": os.path.join(os.getcwd(), variant_full_path), "breadcrumbs": _get_breadcrumbs(variant_id) }
[docs] def _load_dirs(self, root_dir): """ Load a directory and its children. :param root_dir: The path to the root dir to parse :type root_dir: string """ folder_meta_path = os.path.join(root_dir, "folder_meta.yaml") guide_meta_path = os.path.join(root_dir, "guide_meta.yaml") if os.path.exists(folder_meta_path): meta = cece.util.load_yaml_file(folder_meta_path) self._load_folder(meta, root_dir) elif os.path.exists(guide_meta_path): meta = cece.util.load_yaml_file(guide_meta_path) self._load_guide(meta, root_dir) else: return False return True