diff --git a/FAQ.html b/FAQ.html index c41f7a816905e0295d074904a1a804aea133af4c..9967a948e57b752fb72962bceb9832940a27be61 100644 --- a/FAQ.html +++ b/FAQ.html @@ -64,6 +64,12 @@ document.write('<a h'+'ref'+'="ma'+'ilto'+':'+e+'" clas'+'s="em' + 'ail">'+'mail </ul> <h2 id="debugging">Debugging</h2> <p>Please add notes about problems and solutions below.</p> +<ul> +<li><p><code>pandoc: command not found</code></p> +<p>Probably you didn't install <a href="http://pandoc.org/installing.html">Pandoc</a>.</p></li> +<li><p><code>pandoc: Error running filter tools/filters/blockquote2div.py</code> due <code>ImportError: No module named 'pandocfilters'</code></p> +<p>Probably you didn't install <a href="https://pypi.python.org/pypi/pandocfilters/1.2.3">pandocfilters</a>.</p></li> +</ul> </div> </div> <div class="footer"> diff --git a/FAQ.md b/FAQ.md index bc8fd39d3db705da39ac024e02faf7ee037e4642..9b7483a05ffa78266a29aafbf6f6984972d7d102 100644 --- a/FAQ.md +++ b/FAQ.md @@ -40,3 +40,13 @@ ## Debugging Please add notes about problems and solutions below. + +* `pandoc: command not found` + + Probably you didn't install [Pandoc](http://pandoc.org/installing.html). + +* `pandoc: Error running filter tools/filters/blockquote2div.py` + due `ImportError: No module named 'pandocfilters'` + + Probably you didn't install + [pandocfilters](https://pypi.python.org/pypi/pandocfilters/1.2.3). diff --git a/LAYOUT.html b/LAYOUT.html index b036af18a9480aa92f77c4398863adb7ea455913..67ffbbc1fe042fb031bae5b708a71a3da6e4e431 100644 --- a/LAYOUT.html +++ b/LAYOUT.html @@ -88,8 +88,8 @@ Paragraph(s) of introductory material. ## Other Resources * [Motivation](motivation.html) -* [Reference Guide](reference.html) -* [Next Steps](discussion.html) +* [Reference](reference.html) +* [Discussion](discussion.html) * [Instructor's Guide](instructors.html)</code></pre> <p><strong>Notes:</strong></p> <ol style="list-style-type: decimal"> @@ -154,7 +154,8 @@ and one or more of these: <p>Every lesson must include a short slide deck suitable for a short presentation (3 minutes or less) that the instructor can use to explain to learners how knowing the subject will help them. The slides must be laid out like this:</p> <pre><code>--- layout: slides -title: Why Make? +title: Lesson Title +subtitle: Motivation --- <section class="slide"> ## Why This Topic? diff --git a/Makefile b/Makefile index badebe2a05fdac9b88700c92c481b7b202e2644c..9eb36408864ae93d1eb78ec47bc6d8f493122342 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,11 @@ motivation.html : motivation.md _layouts/slides.html $(INCLUDES) \ -o $@ $< +## unittest : Run unit test (for Python 2 and 3) +unittest: tools/check tools/validation_helpers.py tools/test_check.py + cd tools/ && python2 test_check.py + cd tools/ && python3 test_check.py + ## commands : Display available commands. commands : Makefile @sed -n 's/^##//p' $< diff --git a/tools/check b/tools/check index abd096ffddb509f5b5e6a853ea73410e311f84ae..ecfaaba9724a5971dc5f08007fb0bca1b6f3a644 100755 --- a/tools/check +++ b/tools/check @@ -406,7 +406,8 @@ class TopicPageValidator(MarkdownValidator): class MotivationPageValidator(MarkdownValidator): """Validate motivation.md""" DOC_HEADERS = {"layout": vh.is_str, - "title": vh.is_str} + "title": vh.is_str, + "subtitle": vh.is_str} # TODO: How to validate? May be a mix of reveal.js (HTML) + markdown. diff --git a/tools/filters/blockquote2div.py b/tools/filters/blockquote2div.py index 6797c86472c92a6526d6f572d0d424f9b2a24638..c583e83a49845e5687469455971f782aac380fb1 100755 --- a/tools/filters/blockquote2div.py +++ b/tools/filters/blockquote2div.py @@ -104,12 +104,11 @@ def blockquote2div(key, value, format, meta): id, classes, kvs = attr - ltitle = pf.stringify(inlines).lower() - if ltitle in SPECIAL_TITLES: - classes.append(SPECIAL_TITLES[ltitle]) - return pf.Div(attr, blockquote) + lowercase_title = pf.stringify(inlines).lower() + if lowercase_title in SPECIAL_TITLES: + classes.append(SPECIAL_TITLES[lowercase_title]) - elif len(classes) == 1 and classes[0] in SPECIAL_CLASSES: + if len(classes) == 1 and classes[0] in SPECIAL_CLASSES: remove_attributes(blockquote) # a blockquote is just a list of blocks, so it can be # passed directly to Div, which expects Div(attr, blocks) diff --git a/tools/test_check.py b/tools/test_check.py index 2f0966f68010dabbaa0c37b102acba04ce580cf4..003e26e82ef8086335a4c1e14565ad1c0a58acd7 100644 --- a/tools/test_check.py +++ b/tools/test_check.py @@ -1,6 +1,18 @@ #! /usr/bin/env python -import imp, logging, os, unittest +""" +Unit and functional tests for markdown lesson template validator. + +Some of these tests require looking for example files, which exist only on +the gh-pages branch. Some tests may therefore fail on branch "core". +""" + + +import imp +import logging +import os +import unittest + check = imp.load_source("check", # Import non-.py file os.path.join(os.path.dirname(__file__), "check")) @@ -8,7 +20,7 @@ check = imp.load_source("check", # Import non-.py file check.start_logging(level=logging.DEBUG) MARKDOWN_DIR = os.path.abspath( - os.path.join(os.path.dirname(__file__), os.pardir)) + os.path.join(os.path.dirname(__file__), os.pardir)) class BaseTemplateTest(unittest.TestCase): @@ -16,9 +28,6 @@ class BaseTemplateTest(unittest.TestCase): SAMPLE_FILE = "" # Path to a file that should pass all tests VALIDATOR = check.MarkdownValidator - def setUp(self): - self.sample_validator = self.VALIDATOR(self.SAMPLE_FILE) - def _create_validator(self, markdown): """Create validator object from markdown string; useful for failures""" return self.VALIDATOR(markdown=markdown) @@ -45,7 +54,8 @@ class TestIndexPage(BaseTemplateTest): VALIDATOR = check.IndexPageValidator def test_sample_file_passes_validation(self): - res = self.sample_validator.validate() + sample_validator = self.VALIDATOR(self.SAMPLE_FILE) + res = sample_validator.validate() self.assertTrue(res) def test_headers_missing_hrs(self): @@ -89,11 +99,33 @@ keywords: this is not a list # TESTS INVOLVING SECTION TITLES/HEADINGS def test_index_has_valid_section_headings(self): """The provided index page""" - res = self.sample_validator._validate_section_heading_order() + validator = self._create_validator("""## Topics + +1. [Topic Title One](01-one.html) +2. [Topic Title Two](02-two.html) + +## Other Resources + +* [Motivation](motivation.html) +* [Reference Guide](reference.html) +* [Next Steps](discussion.html) +* [Instructor's Guide](instructors.html)""") + res = validator._validate_section_heading_order() self.assertTrue(res) def test_index_fail_when_section_heading_absent(self): - res = self.sample_validator.ast.has_section_heading("Fake heading") + validator = self._create_validator("""## Topics + +1. [Topic Title One](01-one.html) +2. [Topic Title Two](02-two.html) + +## Other Resources + +* [Motivation](motivation.html) +* [Reference Guide](reference.html) +* [Next Steps](discussion.html) +* [Instructor's Guide](instructors.html)""") + res = validator.ast.has_section_heading("Fake heading") self.assertFalse(res) def test_fail_when_section_heading_is_wrong_level(self): @@ -122,7 +154,6 @@ Paragraph of introductory material. * [Instructor's Guide](instructors.html)""") self.assertFalse(validator._validate_section_heading_order()) - def test_fail_when_section_headings_in_wrong_order(self): validator = self._create_validator("""--- layout: lesson @@ -182,11 +213,15 @@ Paragraph of introductory material. # TESTS INVOLVING LINKS TO OTHER CONTENT def test_file_links_validate(self): - res = self.sample_validator._validate_links() + """Verify that all links in a sample file validate. + Involves checking for example files; may fail on "core" branch""" + sample_validator = self.VALIDATOR(self.SAMPLE_FILE) + res = sample_validator._validate_links() self.assertTrue(res) def test_html_link_to_extant_md_file_passes(self): - """Verify that an HTML link with corresponding MD file will pass""" + """Verify that an HTML link with corresponding MD file will pass + Involves checking for example files; may fail on "core" branch""" validator = self._create_validator("""[Topic Title One](01-one.html)""") self.assertTrue(validator._validate_links()) @@ -195,6 +230,8 @@ Paragraph of introductory material. For now this just tests that the regex handles #anchors. It doesn't validate that the named anchor exists in the md file + + Involves checking for example files; may fail on "core" branch """ validator = self._create_validator("""[Topic Title One](01-one.html#anchor)""") self.assertTrue(validator._validate_links()) @@ -206,7 +243,6 @@ Paragraph of introductory material. SQLite uses the integers 0 and 1 for the former, and represents the latter as discussed [earlier](#a:dates).""") self.assertTrue(validator._validate_links()) - def test_missing_markdown_file_fails_validation(self): """Fail validation when an html file is linked without corresponding markdown file""" @@ -227,7 +263,8 @@ SQLite uses the integers 0 and 1 for the former, and represents the latter as di self.assertFalse(validator._validate_links()) def test_finds_image_asset(self): - """Image asset is found""" + """Image asset is found in the expected file location + Involves checking for example files; may fail on "core" branch""" validator = self._create_validator( """""") self.assertTrue(validator._validate_links()) @@ -239,7 +276,9 @@ SQLite uses the integers 0 and 1 for the former, and represents the latter as di self.assertFalse(validator._validate_links()) def test_non_html_link_finds_csv(self): - """Look for CSV file in appropriate folder""" + """Look for CSV file in appropriate folder + Involves checking for example files; may fail on "core" branch + """ validator = self._create_validator( """Use [this CSV](data/data.csv) for the exercise.""") self.assertTrue(validator._validate_links()) @@ -257,7 +296,8 @@ class TestTopicPage(BaseTemplateTest): VALIDATOR = check.TopicPageValidator def test_sample_file_passes_validation(self): - res = self.sample_validator.validate() + sample_validator = self.VALIDATOR(self.SAMPLE_FILE) + res = sample_validator.validate() self.assertTrue(res) @@ -267,7 +307,8 @@ class TestMotivationPage(BaseTemplateTest): VALIDATOR = check.MotivationPageValidator def test_sample_file_passes_validation(self): - res = self.sample_validator.validate() + sample_validator = self.VALIDATOR(self.SAMPLE_FILE) + res = sample_validator.validate() self.assertTrue(res) @@ -326,7 +367,8 @@ Key Word 2 self.assertTrue(validator._validate_glossary()) def test_sample_file_passes_validation(self): - res = self.sample_validator.validate() + sample_validator = self.VALIDATOR(self.SAMPLE_FILE) + res = sample_validator.validate() self.assertTrue(res) @@ -336,7 +378,8 @@ class TestInstructorPage(BaseTemplateTest): VALIDATOR = check.InstructorPageValidator def test_sample_file_passes_validation(self): - res = self.sample_validator.validate() + sample_validator = self.VALIDATOR(self.SAMPLE_FILE) + res = sample_validator.validate() self.assertTrue(res) @@ -345,7 +388,8 @@ class TestLicensePage(BaseTemplateTest): VALIDATOR = check.LicensePageValidator def test_sample_file_passes_validation(self): - res = self.sample_validator.validate() + sample_validator = self.VALIDATOR(self.SAMPLE_FILE) + res = sample_validator.validate() self.assertTrue(res) def test_modified_file_fails_validation(self): @@ -361,7 +405,8 @@ class TestDiscussionPage(BaseTemplateTest): VALIDATOR = check.DiscussionPageValidator def test_sample_file_passes_validation(self): - res = self.sample_validator.validate() + sample_validator = self.VALIDATOR(self.SAMPLE_FILE) + res = sample_validator.validate() self.assertTrue(res)