Commit 829c5008 authored by Raniere Silva's avatar Raniere Silva
Browse files

Merge pull request #86 from abought/header_yaml_logging

Improve logging of YAML header errors
parents 873a6b4d 5c906ad1
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
CommonMark
pandocfilters
PyYAML
 No newline at end of file
+31 −19
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import re
import sys

import CommonMark
import yaml

import validation_helpers as vh

@@ -87,9 +88,8 @@ class MarkdownValidator(object):
                valid = False
        return valid

    def _validate_one_doc_header_row(self, text):
    def _validate_one_doc_header_row(self, label, content):
        """Validate a single row of the document header section"""
        label, content = text.split(":", 1)
        if label not in self.DOC_HEADERS:
            logging.warning(
                "In {0}: "
@@ -102,8 +102,8 @@ class MarkdownValidator(object):
        if not validate_header:
            logging.error(
                "In {0}: "
                "Document header field for label {1} "
                "does not follow expected format".format(self.filename, label))
                "Contents of document header field for label {1} "
                "do not follow expected format".format(self.filename, label))
        return validate_header

    # Methods related to specific validation. Can override specific tests.
@@ -113,21 +113,37 @@ class MarkdownValidator(object):
        Pass only if the header of the document contains the specified
            sections with the expected contents"""

        # Header section should be wrapped in hrs
        # Test: Header section should be wrapped in hrs
        has_hrs = self._validate_hrs()

        # Labeled sections in the actual headers should match expected format
        header_node = self.ast.children[1]
        test_headers = [self._validate_one_doc_header_row(s)
                        for s in header_node.strings]
        header_text = '\n'.join(header_node.strings)

        # Parse headers as YAML. Don't check if parser returns None or str.
        header_yaml = yaml.load(header_text)
        if not isinstance(header_yaml, dict):
            logging.error("In {0}: "
                          "Expected YAML markup with labels "
                          "{1}".format(self.filename, self.DOC_HEADERS.keys()))
            return False

        # Test: Labeled YAML should match expected format
        test_headers = [self._validate_one_doc_header_row(k, v)
                        for k, v in header_yaml.items()]

        # Must have all expected header lines, and no others.
        only_headers = (len(header_node.strings) == len(self.DOC_HEADERS))
        # Test: Must have all expected header lines, and no others.
        only_headers = (len(header_yaml) == len(self.DOC_HEADERS))

        # Headings must appear in the order expected
        valid_order = self._validate_section_heading_order()
        # If expected headings are missing, print an informative message
        missing_headings = [h for h in self.DOC_HEADERS
                            if h not in header_yaml]

        return has_hrs and all(test_headers) and only_headers and valid_order
        for h in missing_headings:
            logging.error("In {0}: "
                          "Header section is missing expected "
                          "row {1}".format(self.filename, h))

        return has_hrs and all(test_headers) and only_headers

    def _validate_section_heading_order(self, ast_node=None, headings=None):
        """Verify that section headings appear, and in the order expected"""
@@ -330,8 +346,8 @@ class IndexPageValidator(MarkdownValidator):
        return super(IndexPageValidator, self)._validate_links(links_to_skip)

    def _run_tests(self):
        tests = [self._validate_intro_section()]
        parent_tests = super(IndexPageValidator, self)._run_tests()
        tests = [self._validate_intro_section()]
        return all(tests) and parent_tests


@@ -383,9 +399,9 @@ class TopicPageValidator(MarkdownValidator):
        return False

    def _run_tests(self):
        parent_tests = super(TopicPageValidator, self)._run_tests()
        tests = [self._validate_has_no_headings(),
                 self._validate_learning_objective()]
        parent_tests = super(TopicPageValidator, self)._run_tests()
        return all(tests) and parent_tests


@@ -647,7 +663,3 @@ def main(parsed_args_obj):
if __name__ == "__main__":
    parsed_args = command_line()
    main(parsed_args)

    #### Sample of how validator is used directly
    # validator = HomePageValidator('../index.md')
    # print validator.validate()
+13 −10
Original line number Diff line number Diff line
@@ -60,7 +60,6 @@ class TestIndexPage(BaseTemplateTest):

layout: lesson
title: Lesson Title
keywords: ["some", "key terms", "in a list"]

Another section that isn't an HR
""")
@@ -71,7 +70,6 @@ Another section that isn't an HR
        """One of the required headers is missing"""
        validator = self._create_validator("""---
layout: lesson
keywords: ["some", "key terms", "in a list"]
---""")
        self.assertFalse(validator._validate_doc_headers())

@@ -80,16 +78,14 @@ keywords: ["some", "key terms", "in a list"]
        validator = self._create_validator("""---
layout: lesson
title: Lesson Title
keywords: ["some", "key terms", "in a list"]
otherline: Nothing
---""")
        self.assertFalse(validator._validate_doc_headers())

    def test_headers_fail_because_invalid_content(self):
    def test_fail_when_headers_not_yaml_dict(self):
        """Fail when the headers can't be parsed to a dict of YAML data"""
        validator = self._create_validator("""---
layout: lesson
title: Lesson Title
keywords: this is not a list
This will parse as a string, not a dictionary
---""")
        self.assertFalse(validator._validate_doc_headers())

@@ -155,7 +151,6 @@ Paragraph of introductory material.
        validator = self._create_validator("""---
layout: lesson
title: Lesson Title
keywords: ["some", "key terms", "in a list"]
---
Paragraph of introductory material.

@@ -182,7 +177,6 @@ Paragraph of introductory material.
        validator = self._create_validator("""---
layout: lesson
title: Lesson Title
keywords: ["some", "key terms", "in a list"]
---
Paragraph of introductory material.

@@ -197,7 +191,6 @@ Paragraph of introductory material.
        validator = self._create_validator("""---
layout: lesson
title: Lesson Title
keywords: ["some", "key terms", "in a list"]
---
Paragraph of introductory material.

@@ -292,6 +285,16 @@ class TestTopicPage(BaseTemplateTest):
    SAMPLE_FILE = os.path.join(MARKDOWN_DIR, "01-one.md")
    VALIDATOR = check.TopicPageValidator

    def test_headers_fail_because_invalid_content(self):
        """The value provided as YAML does not match the expected datatype"""
        validator = self._create_validator("""---
layout: lesson
title: Lesson Title
subtitle: A page
minutes: not a number
---""")
        self.assertFalse(validator._validate_doc_headers())

    def test_sample_file_passes_validation(self):
        sample_validator = self.VALIDATOR(self.SAMPLE_FILE)
        res = sample_validator.validate()