#!/usr/bin/python # # Software Carpentry Lesson Validator # # Check for errors at Software Carpentry lessons. # # Usage: # # $ tools/check import os import re import yaml #---------------------------------------- # Error reporting. def report_error(file_path, line_number, line, error_message): ''' FIXME: docstring. ''' ERR_MSG = "Error at line {} of {}:\n\t{}\n{}" print(ERR_MSG.format(line_number, file_path, line, error_message)) def report_missing(present, file_path, missing_element): ''' FIXME: docstring. ''' ERR_MSG = "Error on {}: missing {}" if not present: print(ERR_MSG.format(file_path, missing_element)) def report_missing_metadata(missing_element): ''' FIXME: docstring. ''' ERR_MSG = "Error on YAML header: missing {}" print(ERR_MSG.format(missing_element)) def report_broken_link(file_path, line_number, link): ''' FIXME: docstring. ''' ERR_MSG = "Broken link at line {} of {}:\n\tCan't find {}." print(ERR_MSG.format(line_number, file_path, link)) #---------------------------------------- # Checking. def check_yaml(metadata): """ Check if all metadata are present at YAML header. """ METADATA_REQUIRED = {"layout", "title", "minutes"} for key in METADATA_REQUIRED - set(metadata.keys()): report_missing_metadata(key) def check_lesson(file_path): ''' FIXME: docstring telling people what you want them to write here. ''' pass def check_discussion(file_path): ''' FIXME: docstring telling people what you want them to write here. ''' pass def check_index(file_path): ''' FIXME: docstring. And break this up into pieces -- it's too long. ''' # State variables in_yaml = False yaml_metadata = [] has_prerequisites = False has_topics = False has_other_resources = False # Load file and process it with open(file_path, 'r') as lines: for line_number, line in enumerate(lines): if re.match('---', line): # what if there are multiple YAML blocks?? in_yaml = not in_yaml elif in_yaml: yaml_metadata.append(line) elif re.match('> ## Prerequisites', line): # check this in the Markdown or in the generated HTML? has_prerequisites = True elif re.match('## Topics', line): # as above? has_topics = True elif re.match('## Other Resources', line): # as above has_other_resources = True else: ## Push this check into another function - this one is getting too long. # Check if local links are valid matches = re.search('\[.*\]\((?P<link>.*)\)', line) if matches and not matches.group("link").startswith("http"): link = os.path.join(os.path.dirname(file_path), matches.group("link")) if link.endswith(".html"): link = link.replace("html", "md") # NO: what about '03-html-editing.html' ? if not os.path.exists(link): report_broken_link(file_path, line_number, link) ## Again, this function is too long - break it into sub-functions. # Check YAML yaml_metadata = yaml.load('\n'.join(yaml_metadata)) check_yaml(yaml_metadata, {"minutes"}) # Check sections ## Note the refactoring: replaces three conditionals with one. report_missing(has_prerequisites, file_path, "Prerequisites") report_missing(has_topics, file_path, "Topics") report_missing(has_other_resources, file_path, "Other Resources") def check_intructors(file_path): ''' FIXME: docstring telling people what you want them to write here. ''' pass def check_motivation(file_path): ''' FIXME: docstring telling people what you want them to write here. ''' pass def check_reference(file_path): ''' FIXME: docstring telling people what you want them to write here. ''' pass def check_file(file_path): ''' FIXME: docstring telling people what you want them to write here. ''' ## Functions are objects, and so can be put in tables like the one below. CONTROL = ( ('[0-9]{2}-.*', check_lesson), ('discussion', check_discussion), ('index', check_index), ('instructors', check_intructors), ("motivation", check_motivation), ("reference", check_reference) ) for (pattern, checker) in CONTROL: if re.search(pattern, file_path): checker(file_path) ## main doesn't take sys.argv[1:] or the like? Will help with testing... def main(): ''' FIXME: docstring telling people what you want them to write here. ''' lessons_file = os.listdir("pages") for lesson in lessons_file: if lesson.endswith('.md'): ## Why not os.path.join('pages', lesson) ? check_file('pages/{}'.format(lesson)) if __name__ == "__main__": main()