diff --git a/Makefile b/Makefile index d291abe6..5ae969f6 100644 --- a/Makefile +++ b/Makefile @@ -8,6 +8,30 @@ JEKYLL_VERSION=3.8.5 PARSER=bin/markdown_ast.rb DST=_site +# Check Python 3 is installed and determine if it's called via python3 or python +# (https://stackoverflow.com/a/4933395) +PYTHON3_EXE := $(shell which python3 2>/dev/null) +ifneq (, $(PYTHON3_EXE)) + ifeq (,$(findstring Microsoft/WindowsApps/python3,$(subst \,/,$(PYTHON3_EXE)))) + PYTHON := python3 + endif +endif + +ifeq (,$(PYTHON)) + PYTHON_EXE := $(shell which python 2>/dev/null) + ifneq (, $(PYTHON_EXE)) + PYTHON_VERSION_FULL := $(wordlist 2,4,$(subst ., ,$(shell python --version 2>&1))) + PYTHON_VERSION_MAJOR := $(word 1,${PYTHON_VERSION_FULL}) + ifneq (3, ${PYTHON_VERSION_MAJOR}) + $(error "Your system does not appear to have Python 3 installed.") + endif + PYTHON := python + else + $(error "Your system does not appear to have any Python installed.") + endif +endif + + # Controls .PHONY : commands clean files .NOTPARALLEL: @@ -15,7 +39,7 @@ all : commands ## commands : show all commands. commands : - @grep -h -E '^##' ${MAKEFILES} | sed -e 's/## //g' + @grep -h -E '^##' ${MAKEFILES} | sed -e "s/## //g" ## docker-serve : use docker to build the site docker-serve : @@ -54,7 +78,7 @@ clean-rmd : ## workshop-check : check workshop homepage. workshop-check : - @bin/workshop_check.py . + @${PYTHON} bin/workshop_check.py . ## ---------------------------------------- ## Commands specific to lesson websites. @@ -93,15 +117,15 @@ _episodes/%.md: _episodes_rmd/%.Rmd ## lesson-check : validate lesson Markdown. lesson-check : lesson-fixme - @bin/lesson_check.py -s . -p ${PARSER} -r _includes/links.md + @${PYTHON} bin/lesson_check.py -s . -p ${PARSER} -r _includes/links.md ## lesson-check-all : validate lesson Markdown, checking line lengths and trailing whitespace. lesson-check-all : - @bin/lesson_check.py -s . -p ${PARSER} -r _includes/links.md -l -w --permissive + @${PYTHON} bin/lesson_check.py -s . -p ${PARSER} -r _includes/links.md -l -w --permissive ## unittest : run unit tests on checking tools. unittest : - @bin/test_lesson_check.py + @${PYTHON} bin/test_lesson_check.py ## lesson-files : show expected names of generated files for debugging. lesson-files : diff --git a/bin/lesson_check.py b/bin/lesson_check.py old mode 100755 new mode 100644 index 2597da5b..ee84d849 --- a/bin/lesson_check.py +++ b/bin/lesson_check.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - """ Check lesson files and their contents. """ @@ -29,19 +27,19 @@ # specially. This list must include all the Markdown files listed in the # 'bin/initialize' script. REQUIRED_FILES = { - '%/CODE_OF_CONDUCT.md': True, - '%/CONTRIBUTING.md': False, - '%/LICENSE.md': True, - '%/README.md': False, - '%/_extras/discuss.md': True, - '%/_extras/guide.md': True, - '%/index.md': True, - '%/reference.md': True, - '%/setup.md': True, + 'CODE_OF_CONDUCT.md': True, + 'CONTRIBUTING.md': False, + 'LICENSE.md': True, + 'README.md': False, + os.path.join('_extras', 'discuss.md'): True, + os.path.join('_extras', 'guide.md'): True, + 'index.md': True, + 'reference.md': True, + 'setup.md': True, } # Episode filename pattern. -P_EPISODE_FILENAME = re.compile(r'/_episodes/(\d\d)-[-\w]+.md$') +P_EPISODE_FILENAME = re.compile(r'(\d\d)-[-\w]+.md$') # Pattern to match lines ending with whitespace. P_TRAILING_WHITESPACE = re.compile(r'\s+$') @@ -272,7 +270,7 @@ def check_fileset(source_dir, reporter, filenames_present): """Are all required files present? Are extraneous files present?""" # Check files with predictable names. - required = [p.replace('%', source_dir) for p in REQUIRED_FILES] + required = [os.path.join(source_dir, p) for p in REQUIRED_FILES] missing = set(required) - set(filenames_present) for m in missing: reporter.add(None, 'Missing required file {0}', m) @@ -282,7 +280,10 @@ def check_fileset(source_dir, reporter, filenames_present): for filename in filenames_present: if '_episodes' not in filename: continue - m = P_EPISODE_FILENAME.search(filename) + + # split path to check episode name + base_name = os.path.basename(filename) + m = P_EPISODE_FILENAME.search(base_name) if m and m.group(1): seen.append(m.group(1)) else: @@ -556,7 +557,7 @@ def __init__(self, args, filename, metadata, metadata_len, text, lines, doc): (re.compile(r'README\.md'), CheckNonJekyll), (re.compile(r'index\.md'), CheckIndex), (re.compile(r'reference\.md'), CheckReference), - (re.compile(r'_episodes/.*\.md'), CheckEpisode), + (re.compile(os.path.join('_episodes', '*\.md')), CheckEpisode), (re.compile(r'.*\.md'), CheckGeneric) ] diff --git a/bin/lesson_initialize.py b/bin/lesson_initialize.py old mode 100755 new mode 100644 index 9ba81162..2f7b8e67 --- a/bin/lesson_initialize.py +++ b/bin/lesson_initialize.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - """Initialize a newly-created repository.""" @@ -14,11 +12,11 @@ 'CONTRIBUTING.md', 'README.md', '_config.yml', - '_episodes/01-introduction.md', - '_extras/about.md', - '_extras/discuss.md', - '_extras/figures.md', - '_extras/guide.md', + os.path.join('_episodes', '01-introduction.md'), + os.path.join('_extras', 'about.md'), + os.path.join('_extras', 'discuss.md'), + os.path.join('_extras', 'figures.md'), + os.path.join('_extras', 'guide.md'), 'index.md', 'reference.md', 'setup.md', @@ -41,7 +39,7 @@ def main(): # Create. for path in BOILERPLATE: shutil.copyfile( - "bin/boilerplate/{}".format(path), + os.path.join('bin', 'boilerplate', path), path ) diff --git a/bin/repo_check.py b/bin/repo_check.py old mode 100755 new mode 100644 index af4b7823..31651fd2 --- a/bin/repo_check.py +++ b/bin/repo_check.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - """ Check repository settings. """ @@ -104,7 +102,7 @@ def get_repo_url(repo_url): # Guess. cmd = 'git remote -v' p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, - close_fds=True, universal_newlines=True) + close_fds=True, universal_newlines=True, encoding='utf-8') stdout_data, stderr_data = p.communicate() stdout_data = stdout_data.split('\n') matches = [P_GIT_REMOTE.match(line) for line in stdout_data] diff --git a/bin/test_lesson_check.py b/bin/test_lesson_check.py old mode 100755 new mode 100644 index 960059e8..0981720a --- a/bin/test_lesson_check.py +++ b/bin/test_lesson_check.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - import unittest import lesson_check @@ -12,10 +10,8 @@ def setUp(self): def test_file_list_has_expected_entries(self): # For first pass, simply assume that all required files are present - all_filenames = [filename.replace('%', '') - for filename in lesson_check.REQUIRED_FILES] - lesson_check.check_fileset('', self.reporter, all_filenames) + lesson_check.check_fileset('', self.reporter, lesson_check.REQUIRED_FILES) self.assertEqual(len(self.reporter.messages), 0) diff --git a/bin/util.py b/bin/util.py index f9dc12f3..5a46cec0 100644 --- a/bin/util.py +++ b/bin/util.py @@ -117,7 +117,7 @@ def read_markdown(parser, path): # Parse Markdown. cmd = 'ruby {0}'.format(parser) p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, - close_fds=True, universal_newlines=True) + close_fds=True, universal_newlines=True, encoding='utf-8') stdout_data, stderr_data = p.communicate(body) doc = json.loads(stdout_data) diff --git a/bin/workshop_check.py b/bin/workshop_check.py old mode 100755 new mode 100644 index 0523d0c2..2caf5603 --- a/bin/workshop_check.py +++ b/bin/workshop_check.py @@ -1,5 +1,3 @@ -#!/usr/bin/env python3 - '''Check that a workshop's index.html metadata is valid. See the docstrings on the checking functions for a summary of the checks. '''