-
-
- Copyright © 2016–{{ 'now' | date: "%Y" }}
+
-
-
+
{% if page.source %}
{% if page.source == "Rmd" %}
Editar en GitHub
@@ -31,9 +39,13 @@
+
+
+
diff --git a/_includes/links.md b/_includes/links.md
index 273d63e..e18ce63 100644
--- a/_includes/links.md
+++ b/_includes/links.md
@@ -1,12 +1,15 @@
[cc-by-human]: https://creativecommons.org/licenses/by/4.0/
[cc-by-legal]: https://creativecommons.org/licenses/by/4.0/legalcode
+[ci]: http://communityin.org/
+[coc-reporting]: https://docs.carpentries.org/topic_folders/policies/code-of-conduct.html#reporting-guidelines
+[coc]: https://docs.carpentries.org/topic_folders/policies/code-of-conduct.html
[concept-maps]: https://carpentries.github.io/instructor-training/05-memory/
-[email]: mailto:lessons@software-carpentry.org
[contrib-covenant]: https://contributor-covenant.org/
[contributing]: {{ site.github.repository_url }}/blob/gh-pages/CONTRIBUTING.md
-[cran-checkpoint]: https://cran.r-project.org/web/packages/checkpoint/index.html
-[cran-knitr]: https://cran.r-project.org/web/packages/knitr/index.html
-[cran-stringr]: https://cran.r-project.org/web/packages/stringr/index.html
+[cran-checkpoint]: https://cran.r-project.org/package=checkpoint
+[cran-knitr]: https://cran.r-project.org/package=knitr
+[cran-stringr]: https://cran.r-project.org/package=stringr
+[email]: mailto:team@carpentries.org
[github-importer]: https://import.github.com/
[importer]: https://github.com/new/import
[jekyll-collection]: https://jekyllrb.com/docs/collections/
@@ -14,6 +17,7 @@
[jekyll-windows]: http://jekyll-windows.juthilo.com/
[jekyll]: https://jekyllrb.com/
[jupyter]: https://jupyter.org/
+[lesson-example]: https://carpentries.github.io/lesson-example/
[mit-license]: https://opensource.org/licenses/mit-license.html
[morea]: https://morea-framework.github.io/
[numfocus]: https://numfocus.org/
@@ -22,15 +26,12 @@
[paper-now]: https://github.com/PeerJ/paper-now
[python-gapminder]: https://swcarpentry.github.io/python-novice-gapminder/
[pyyaml]: https://pypi.python.org/pypi/PyYAML
-[r-markdown]: http://rmarkdown.rstudio.com/
+[r-markdown]: https://rmarkdown.rstudio.com/
[rstudio]: https://www.rstudio.com/
[ruby-install-guide]: https://www.ruby-lang.org/en/downloads/
[ruby-installer]: https://rubyinstaller.org/
[rubygems]: https://rubygems.org/pages/download/
-[styles]: https://github.com/swcarpentry/styles/
-[training]: https://swcarpentry.github.io/instructor-training/
+[styles]: https://github.com/carpentries/styles/
+[swc-releases]: https://github.com/swcarpentry/swc-releases
[workshop-repo]: {{ site.workshop_repo }}
[yaml]: http://yaml.org/
-[coc]: https://software-carpentry.org/conduct/
-[coc-reporting]: https://software-carpentry.org/CoC-reporting/
-[lesson-example]: https://swcarpentry.github.io/lesson-example/
diff --git a/_includes/navbar.html b/_includes/navbar.html
index e1f8acf..d7de8e9 100644
--- a/_includes/navbar.html
+++ b/_includes/navbar.html
@@ -11,18 +11,23 @@
+
{% comment %} Seleccione qué logotipo mostrar. {% endcomment %}
{% if page.carpentry == "swc" %}
- {% elsif page.carpentry == "dc" %}
+ {% elsif site.carpentry == "dc" %}
- {% elsif page.carpentry == "lc" %}
+ {% elsif site.carpentry == "lc" %}
-
+
+
+ {% elsif site.carpentry == "cp" %}
+
+
{% endif %}
@@ -34,7 +39,7 @@
{% comment %} Mostrar siempre el código de conducta. {% endcomment %}
- Código de conducta
+ Code of Conduct
{% if site.kind == "lesson" %}
{% comment %} Muestra las instrucciones de configuración. {% endcomment %}
diff --git a/_includes/workshop_footer.html b/_includes/workshop_footer.html
index e6610e1..8ba3597 100644
--- a/_includes/workshop_footer.html
+++ b/_includes/workshop_footer.html
@@ -7,11 +7,13 @@
Copyright © 2016–{{ 'now' | date: "%Y" }}
{% if site.carpentry == "swc" %}
- Software Carpentry Foundation
+ Software Carpentry
{% elsif site.carpentry == "dc" %}
Data Carpentry
{% elsif site.carpentry == "lc" %}
Library Carpentry
+ {% elsif site.carpentry == "cp" %}
+ The Carpentries
{% endif %}
diff --git a/_layouts/base.html b/_layouts/base.html
index f1e90d1..99d6567 100644
--- a/_layouts/base.html
+++ b/_layouts/base.html
@@ -13,13 +13,9 @@
- {% if site.carpentry == "swc" %}
-
- {% elsif site.carpentry == "dc" %}
-
- {% elsif site.carpentry == "lc" %}
-
- {% endif %}
+
+ {% include favicons.html %}
+
+
+
image/svg+xml
\ No newline at end of file
diff --git a/bin/boilerplate/.travis.yml b/bin/boilerplate/.travis.yml
new file mode 100644
index 0000000..d30f78a
--- /dev/null
+++ b/bin/boilerplate/.travis.yml
@@ -0,0 +1,23 @@
+# dist: trusty # Ubuntu 14.04
+language: python
+python: 3.6
+branches:
+ only:
+ - gh-pages
+ - /.*/
+before_install:
+ - sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E084DAB9
+ - echo "deb https://cran.rstudio.com/bin/linux/ubuntu trusty/" | sudo tee -a /etc/apt/sources.list
+ - sudo apt-get update -y
+ - sudo apt-get install -y r-base
+ - sudo Rscript -e "install.packages('knitr', repos = 'https://', dependencies = TRUE)"
+ - sudo Rscript -e "install.packages('stringr', repos = 'https://cran.rstudio.com', dependencies = TRUE)"
+ - sudo Rscript -e "install.packages('checkpoint', repos = 'https://cran.rstudio.com', dependencies = TRUE)"
+ - sudo Rscript -e "install.packages('ggplot2', repos = 'https://cran.rstudio.com', dependencies = TRUE)"
+ - rvm default
+ - gem install json kramdown jekyll
+install:
+ - pip install pyyaml
+script:
+ - make lesson-check-all
+ - make --always-make site
diff --git a/bin/boilerplate/AUTHORS b/bin/boilerplate/AUTHORS
new file mode 100644
index 0000000..04e1f5a
--- /dev/null
+++ b/bin/boilerplate/AUTHORS
@@ -0,0 +1 @@
+FIXME: list authors' names and email addresses.
\ No newline at end of file
diff --git a/bin/boilerplate/CITATION b/bin/boilerplate/CITATION
new file mode 100644
index 0000000..56ece3c
--- /dev/null
+++ b/bin/boilerplate/CITATION
@@ -0,0 +1 @@
+FIXME: describe how to cite this lesson.
\ No newline at end of file
diff --git a/bin/boilerplate/CODE_OF_CONDUCT.md b/bin/boilerplate/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..c3b9669
--- /dev/null
+++ b/bin/boilerplate/CODE_OF_CONDUCT.md
@@ -0,0 +1,11 @@
+---
+layout: page
+title: "Contributor Code of Conduct"
+---
+As contributors and maintainers of this project,
+we pledge to follow the [Carpentry Code of Conduct][coc].
+
+Instances of abusive, harassing, or otherwise unacceptable behavior
+may be reported by following our [reporting guidelines][coc-reporting].
+
+{% include links.md %}
diff --git a/bin/boilerplate/CONTRIBUTING.md b/bin/boilerplate/CONTRIBUTING.md
new file mode 100644
index 0000000..63d53e8
--- /dev/null
+++ b/bin/boilerplate/CONTRIBUTING.md
@@ -0,0 +1,148 @@
+# Contributing
+
+[Software Carpentry][swc-site] and [Data Carpentry][dc-site] are open source projects,
+and we welcome contributions of all kinds:
+new lessons,
+fixes to existing material,
+bug reports,
+and reviews of proposed changes are all welcome.
+
+## Contributor Agreement
+
+By contributing,
+you agree that we may redistribute your work under [our license](LICENSE.md).
+In exchange,
+we will address your issues and/or assess your change proposal as promptly as we can,
+and help you become a member of our community.
+Everyone involved in [Software Carpentry][swc-site] and [Data Carpentry][dc-site]
+agrees to abide by our [code of conduct](CODE_OF_CONDUCT.md).
+
+## How to Contribute
+
+The easiest way to get started is to file an issue
+to tell us about a spelling mistake,
+some awkward wording,
+or a factual error.
+This is a good way to introduce yourself
+and to meet some of our community members.
+
+1. If you do not have a [GitHub][github] account,
+ you can [send us comments by email][email].
+ However,
+ we will be able to respond more quickly if you use one of the other methods described below.
+
+2. If you have a [GitHub][github] account,
+ or are willing to [create one][github-join],
+ but do not know how to use Git,
+ you can report problems or suggest improvements by [creating an issue][issues].
+ This allows us to assign the item to someone
+ and to respond to it in a threaded discussion.
+
+3. If you are comfortable with Git,
+ and would like to add or change material,
+ you can submit a pull request (PR).
+ Instructions for doing this are [included below](#using-github).
+
+## Where to Contribute
+
+1. If you wish to change this lesson,
+ please work in
,
+ which can be viewed at .
+
+2. If you wish to change the example lesson,
+ please work in ,
+ which documents the format of our lessons
+ and can be viewed at .
+
+3. If you wish to change the template used for workshop websites,
+ please work in .
+ The home page of that repository explains how to set up workshop websites,
+ while the extra pages in
+ provide more background on our design choices.
+
+4. If you wish to change CSS style files, tools,
+ or HTML boilerplate for lessons or workshops stored in `_includes` or `_layouts`,
+ please work in .
+
+## What to Contribute
+
+There are many ways to contribute,
+from writing new exercises and improving existing ones
+to updating or filling in the documentation
+and submitting [bug reports][issues]
+about things that don't work, aren't clear, or are missing.
+If you are looking for ideas, please see the 'Issues' tab for
+a list of issues associated with this repository,
+or you may also look at the issues for [Data Carpentry][dc-issues]
+and [Software Carpentry][swc-issues] projects.
+
+Comments on issues and reviews of pull requests are just as welcome:
+we are smarter together than we are on our own.
+Reviews from novices and newcomers are particularly valuable:
+it's easy for people who have been using these lessons for a while
+to forget how impenetrable some of this material can be,
+so fresh eyes are always welcome.
+
+## What *Not* to Contribute
+
+Our lessons already contain more material than we can cover in a typical workshop,
+so we are usually *not* looking for more concepts or tools to add to them.
+As a rule,
+if you want to introduce a new idea,
+you must (a) estimate how long it will take to teach
+and (b) explain what you would take out to make room for it.
+The first encourages contributors to be honest about requirements;
+the second, to think hard about priorities.
+
+We are also not looking for exercises or other material that only run on one platform.
+Our workshops typically contain a mixture of Windows, Mac OS X, and Linux users;
+in order to be usable,
+our lessons must run equally well on all three.
+
+## Using GitHub
+
+If you choose to contribute via GitHub, you may want to look at
+[How to Contribute to an Open Source Project on GitHub][how-contribute].
+To manage changes, we follow [GitHub flow][github-flow].
+Each lesson has two maintainers who review issues and pull requests or encourage others to do so.
+The maintainers are community volunteers and have final say over what gets merged into the lesson.
+To use the web interface for contributing to a lesson:
+
+1. Fork the originating repository to your GitHub profile.
+2. Within your version of the forked repository, move to the `gh-pages` branch and
+create a new branch for each significant change being made.
+3. Navigate to the file(s) you wish to change within the new branches and make revisions as required.
+4. Commit all changed files within the appropriate branches.
+5. Create individual pull requests from each of your changed branches
+to the `gh-pages` branch within the originating repository.
+6. If you receive feedback, make changes using your issue-specific branches of the forked
+repository and the pull requests will update automatically.
+7. Repeat as needed until all feedback has been addressed.
+
+When starting work, please make sure your clone of the originating `gh-pages` branch is up-to-date
+before creating your own revision-specific branch(es) from there.
+Additionally, please only work from your newly-created branch(es) and *not*
+your clone of the originating `gh-pages` branch.
+Lastly, published copies of all the lessons are available in the `gh-pages` branch of the originating
+repository for reference while revising.
+
+## Other Resources
+
+General discussion of [Software Carpentry][swc-site] and [Data Carpentry][dc-site]
+happens on the [discussion mailing list][discuss-list],
+which everyone is welcome to join.
+You can also [reach us by email][email].
+
+[email]: mailto:admin@software-carpentry.org
+[dc-issues]: https://github.com/issues?q=user%3Adatacarpentry
+[dc-lessons]: http://datacarpentry.org/lessons/
+[dc-site]: http://datacarpentry.org/
+[discuss-list]: http://lists.software-carpentry.org/listinfo/discuss
+[github]: https://github.com
+[github-flow]: https://guides.github.com/introduction/flow/
+[github-join]: https://github.com/join
+[how-contribute]: https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github
+[issues]: https://guides.github.com/features/issues/
+[swc-issues]: https://github.com/issues?q=user%3Aswcarpentry
+[swc-lessons]: https://software-carpentry.org/lessons/
+[swc-site]: https://software-carpentry.org/
diff --git a/bin/boilerplate/README.md b/bin/boilerplate/README.md
new file mode 100644
index 0000000..43a6b72
--- /dev/null
+++ b/bin/boilerplate/README.md
@@ -0,0 +1,28 @@
+# FIXME Lesson title
+
+[![Create a Slack Account with us](https://img.shields.io/badge/Create_Slack_Account-The_Carpentries-071159.svg)](https://swc-slack-invite.herokuapp.com/)
+
+FIXME
+
+## Contributing
+
+We welcome all contributions to improve the lesson! Maintainers will do their best to help you if you have any
+questions, concerns, or experience any difficulties along the way.
+
+We'd like to ask you to familiarize yourself with our [Contribution Guide](CONTRIBUTING.md) and have a look at
+the [more detailed guidelines][lesson-example] on proper formatting, ways to render the lesson locally, and even
+how to write new episodes.
+
+## Maintainer(s)
+
+* FIXME
+
+## Authors
+
+A list of contributors to the lesson can be found in [AUTHORS](AUTHORS)
+
+## Citation
+
+To cite this lesson, please consult with [CITATION](CITATION)
+
+[lesson-example]: https://carpentries.github.io/lesson-example
diff --git a/bin/boilerplate/_config.yml b/bin/boilerplate/_config.yml
new file mode 100644
index 0000000..a62f808
--- /dev/null
+++ b/bin/boilerplate/_config.yml
@@ -0,0 +1,89 @@
+#------------------------------------------------------------
+# Values for this lesson.
+#------------------------------------------------------------
+
+# Which carpentry is this ("swc", "dc", "lc", or "cp")?
+# swc: Software Carpentry
+# dc: Data Carpentry
+# lc: Library Carpentry
+# cp: Carpentries (to use for instructor traning for instance)
+carpentry: "swc"
+
+# Overall title for pages.
+title: "Lesson Title"
+
+#------------------------------------------------------------
+# Generic settings (should not need to change).
+#------------------------------------------------------------
+
+# What kind of thing is this ("workshop" or "lesson")?
+kind: "lesson"
+
+# Magic to make URLs resolve both locally and on GitHub.
+# See https://help.github.com/articles/repository-metadata-on-github-pages/.
+# Please don't change it: / is correct.
+repository: /
+
+# Email address, no mailto:
+email: "team@carpentries.org"
+
+# Sites.
+amy_site: "https://amy.software-carpentry.org/workshops"
+carpentries_github: "https://github.com/carpentries"
+carpentries_pages: "https://carpentries.github.io"
+carpentries_site: "https://carpentries.org/"
+dc_site: "http://datacarpentry.org"
+example_repo: "https://github.com/carpentries/lesson-example"
+example_site: "https://carpentries.github.io/lesson-example"
+lc_site: "https://librarycarpentry.github.io/"
+swc_github: "https://github.com/swcarpentry"
+swc_pages: "https://swcarpentry.github.io"
+swc_site: "https://software-carpentry.org"
+template_repo: "https://github.com/carpentries/styles"
+training_site: "https://carpentries.github.io/instructor-training"
+workshop_repo: "https://github.com/carpentries/workshop-template"
+workshop_site: "https://carpentries.github.io/workshop-template"
+
+# Surveys.
+pre_survey: "https://www.surveymonkey.com/r/swc_pre_workshop_v1?workshop_id="
+post_survey: "https://www.surveymonkey.com/r/swc_post_workshop_v1?workshop_id="
+training_post_survey: "https://www.surveymonkey.com/r/post-instructor-training"
+
+# Start time in minutes (0 to be clock-independent, 540 to show a start at 09:00 am).
+start_time: 0
+
+# Specify that things in the episodes collection should be output.
+collections:
+ episodes:
+ output: true
+ permalink: /:path/index.html
+ extras:
+ output: true
+ permalink: /:path/index.html
+
+# Set the default layout for things in the episodes collection.
+defaults:
+ - values:
+ root: .
+ layout: page
+ - scope:
+ path: ""
+ type: episodes
+ values:
+ root: ..
+ layout: episode
+ - scope:
+ path: ""
+ type: extras
+ values:
+ root: ..
+ layout: page
+
+# Files and directories that are not to be copied.
+exclude:
+ - Makefile
+ - bin/
+ - .Rproj.user/
+
+# Turn on built-in syntax highlighting.
+highlighter: rouge
diff --git a/bin/boilerplate/_episodes/01-introduction.md b/bin/boilerplate/_episodes/01-introduction.md
new file mode 100644
index 0000000..fcfdda1
--- /dev/null
+++ b/bin/boilerplate/_episodes/01-introduction.md
@@ -0,0 +1,14 @@
+---
+title: "Introduction"
+teaching: 0
+exercises: 0
+questions:
+- "Key question (FIXME)"
+objectives:
+- "First objective. (FIXME)"
+keypoints:
+- "First key point. (FIXME)"
+---
+FIXME
+
+{% include links.md %}
diff --git a/bin/boilerplate/_extras/about.md b/bin/boilerplate/_extras/about.md
new file mode 100644
index 0000000..5f07f65
--- /dev/null
+++ b/bin/boilerplate/_extras/about.md
@@ -0,0 +1,5 @@
+---
+title: About
+---
+{% include carpentries.html %}
+{% include links.md %}
diff --git a/bin/boilerplate/_extras/discuss.md b/bin/boilerplate/_extras/discuss.md
new file mode 100644
index 0000000..bfc33c5
--- /dev/null
+++ b/bin/boilerplate/_extras/discuss.md
@@ -0,0 +1,6 @@
+---
+title: Discussion
+---
+FIXME
+
+{% include links.md %}
diff --git a/bin/boilerplate/_extras/figures.md b/bin/boilerplate/_extras/figures.md
new file mode 100644
index 0000000..c1511e8
--- /dev/null
+++ b/bin/boilerplate/_extras/figures.md
@@ -0,0 +1,40 @@
+---
+title: Figures
+---
+
+{% comment %}
+Create anchor for each one of the episodes.
+{% endcomment %}
+{% for episode in site.episodes %}
+
+{% endfor %}
+
+{% include links.md %}
diff --git a/bin/boilerplate/_extras/guide.md b/bin/boilerplate/_extras/guide.md
new file mode 100644
index 0000000..50f266f
--- /dev/null
+++ b/bin/boilerplate/_extras/guide.md
@@ -0,0 +1,6 @@
+---
+title: "Instructor Notes"
+---
+FIXME
+
+{% include links.md %}
diff --git a/bin/boilerplate/aio.md b/bin/boilerplate/aio.md
new file mode 100644
index 0000000..a418f3b
--- /dev/null
+++ b/bin/boilerplate/aio.md
@@ -0,0 +1,34 @@
+---
+---
+
+{% comment %}
+Create anchor for each one of the episodes.
+{% endcomment %}
+{% for episode in site.episodes %}
+
+{% endfor %}
diff --git a/bin/boilerplate/index.md b/bin/boilerplate/index.md
new file mode 100644
index 0000000..7fb812c
--- /dev/null
+++ b/bin/boilerplate/index.md
@@ -0,0 +1,13 @@
+---
+layout: lesson
+root: . # Is the only page that don't follow the partner /:path/index.html
+permalink: index.html # Is the only page that don't follow the partner /:path/index.html
+---
+FIXME: home page introduction
+
+> ## Prerequisites
+>
+> FIXME
+{: .prereq}
+
+{% include links.md %}
diff --git a/bin/boilerplate/reference.md b/bin/boilerplate/reference.md
new file mode 100644
index 0000000..8c82616
--- /dev/null
+++ b/bin/boilerplate/reference.md
@@ -0,0 +1,9 @@
+---
+layout: reference
+---
+
+## Glossary
+
+FIXME
+
+{% include links.md %}
diff --git a/bin/boilerplate/setup.md b/bin/boilerplate/setup.md
new file mode 100644
index 0000000..b8c5032
--- /dev/null
+++ b/bin/boilerplate/setup.md
@@ -0,0 +1,7 @@
+---
+title: Setup
+---
+FIXME
+
+
+{% include links.md %}
diff --git a/bin/lesson_check.py b/bin/lesson_check.py
index 66f6310..8ed1bf8 100755
--- a/bin/lesson_check.py
+++ b/bin/lesson_check.py
@@ -1,18 +1,17 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
"""
Check lesson files and their contents.
"""
-from __future__ import print_function
-import sys
+
import os
import glob
-import json
import re
-from optparse import OptionParser
+from argparse import ArgumentParser
-from util import Reporter, read_markdown, load_yaml, check_unwanted_files, require, IMAGE_FILE_SUFFIX
+from util import (Reporter, read_markdown, load_yaml, check_unwanted_files,
+ require)
__version__ = '0.3'
@@ -23,10 +22,11 @@
# FIXME: We do not yet validate whether any files have the required
# YAML headers, but should in the future.
# The '%' is replaced with the source directory path for checking.
-# Episodes are handled specially, and extra files in '_extras' are also handled specially.
-# This list must include all the Markdown files listed in the 'bin/initialize' script.
+# Episodes are handled specially, and extra files in '_extras' are also handled
+# specially. This list must include all the Markdown files listed in the
+# 'bin/initialize' script.
REQUIRED_FILES = {
- '%/CONDUCT.md': True,
+ '%/CODE_OF_CONDUCT.md': True,
'%/CONTRIBUTING.md': False,
'%/LICENSE.md': True,
'%/README.md': False,
@@ -101,6 +101,7 @@
# How long are lines allowed to be?
MAX_LINE_LEN = 100
+
def main():
"""Main driver."""
@@ -110,43 +111,50 @@ def main():
args.references = read_references(args.reporter, args.reference_path)
docs = read_all_markdown(args.source_dir, args.parser)
- check_fileset(args.source_dir, args.reporter, docs.keys())
+ check_fileset(args.source_dir, args.reporter, list(docs.keys()))
check_unwanted_files(args.source_dir, args.reporter)
- for filename in docs.keys():
+ for filename in list(docs.keys()):
checker = create_checker(args, filename, docs[filename])
checker.check()
args.reporter.report()
+ if args.reporter.messages and not args.permissive:
+ exit(1)
def parse_args():
"""Parse command-line arguments."""
- parser = OptionParser()
- parser.add_option('-l', '--linelen',
- default=False,
- action="store_true",
- dest='line_lengths',
- help='Check line lengths')
- parser.add_option('-p', '--parser',
- default=None,
- dest='parser',
- help='path to Markdown parser')
- parser.add_option('-r', '--references',
- default=None,
- dest='reference_path',
- help='path to Markdown file of external references')
- parser.add_option('-s', '--source',
- default=os.curdir,
- dest='source_dir',
- help='source directory')
- parser.add_option('-w', '--whitespace',
- default=False,
- action="store_true",
- dest='trailing_whitespace',
- help='Check for trailing whitespace')
-
- args, extras = parser.parse_args()
+ parser = ArgumentParser(description="""Check episode files in a lesson.""")
+ parser.add_argument('-l', '--linelen',
+ default=False,
+ action="store_true",
+ dest='line_lengths',
+ help='Check line lengths')
+ parser.add_argument('-p', '--parser',
+ default=None,
+ dest='parser',
+ help='path to Markdown parser')
+ parser.add_argument('-r', '--references',
+ default=None,
+ dest='reference_path',
+ help='path to Markdown file of external references')
+ parser.add_argument('-s', '--source',
+ default=os.curdir,
+ dest='source_dir',
+ help='source directory')
+ parser.add_argument('-w', '--whitespace',
+ default=False,
+ action="store_true",
+ dest='trailing_whitespace',
+ help='Check for trailing whitespace')
+ parser.add_argument('--permissive',
+ default=False,
+ action="store_true",
+ dest='permissive',
+ help='Do not raise an error even if issues are detected')
+
+ args, extras = parser.parse_known_args()
require(args.parser is not None,
'Path to Markdown parser not provided')
require(not extras,
@@ -160,14 +168,21 @@ def check_config(reporter, source_dir):
config_file = os.path.join(source_dir, '_config.yml')
config = load_yaml(config_file)
- reporter.check_field(config_file, 'configuration', config, 'kind', 'lesson')
- reporter.check_field(config_file, 'configuration', config, 'carpentry', ('swc', 'dc', 'lc'))
+ reporter.check_field(config_file, 'configuration',
+ config, 'kind', 'lesson')
+ reporter.check_field(config_file, 'configuration',
+ config, 'carpentry', ('swc', 'dc', 'lc', 'cp'))
reporter.check_field(config_file, 'configuration', config, 'title')
reporter.check_field(config_file, 'configuration', config, 'email')
- reporter.check({'values': {'root': '..'}} in config.get('defaults', []),
+ for defaults in [
+ {'values': {'root': '.', 'layout': 'page'}},
+ {'values': {'root': '..', 'layout': 'episode'}, 'scope': {'type': 'episodes', 'path': ''}},
+ {'values': {'root': '..', 'layout': 'page'}, 'scope': {'type': 'extras', 'path': ''}}
+ ]:
+ reporter.check(defaults in config.get('defaults', []),
'configuration',
- '"root" not set to ".." in configuration')
+ '"root" not set to "." in configuration')
def read_references(reporter, ref_path):
@@ -235,17 +250,17 @@ def check_fileset(source_dir, reporter, filenames_present):
if m and m.group(1):
seen.append(m.group(1))
else:
- reporter.add(None, 'Episode {0} has badly-formatted filename', filename)
+ reporter.add(
+ None, 'Episode {0} has badly-formatted filename', filename)
# Check for duplicate episode numbers.
reporter.check(len(seen) == len(set(seen)),
- None,
- 'Duplicate episode numbers {0} vs {1}',
- sorted(seen), sorted(set(seen)))
+ None,
+ 'Duplicate episode numbers {0} vs {1}',
+ sorted(seen), sorted(set(seen)))
# Check that numbers are consecutive.
- seen = [int(s) for s in seen]
- seen.sort()
+ seen = sorted([int(s) for s in seen])
clean = True
for i in range(len(seen) - 1):
clean = clean and ((seen[i+1] - seen[i]) == 1)
@@ -261,17 +276,16 @@ def create_checker(args, filename, info):
for (pat, cls) in CHECKERS:
if pat.search(filename):
return cls(args, filename, **info)
+ return NotImplemented
-
-class CheckBase(object):
+class CheckBase:
"""Base class for checking Markdown files."""
def __init__(self, args, filename, metadata, metadata_len, text, lines, doc):
"""Cache arguments for checking."""
- super(CheckBase, self).__init__()
self.args = args
- self.reporter = self.args.reporter # for convenience
+ self.reporter = self.args.reporter # for convenience
self.filename = filename
self.metadata = metadata
self.metadata_len = metadata_len
@@ -281,7 +295,6 @@ def __init__(self, args, filename, metadata, metadata_len, text, lines, doc):
self.layout = None
-
def check(self):
"""Run tests."""
@@ -292,7 +305,6 @@ def check(self):
self.check_codeblock_classes()
self.check_defined_link_references()
-
def check_metadata(self):
"""Check the YAML metadata."""
@@ -301,53 +313,51 @@ def check_metadata(self):
'Missing metadata entirely')
if self.metadata and (self.layout is not None):
- self.reporter.check_field(self.filename, 'metadata', self.metadata, 'layout', self.layout)
-
+ self.reporter.check_field(
+ self.filename, 'metadata', self.metadata, 'layout', self.layout)
def check_line_lengths(self):
"""Check the raw text of the lesson body."""
if self.args.line_lengths:
- over = [i for (i, l, n) in self.lines if (n > MAX_LINE_LEN) and (not l.startswith('!'))]
+ over = [i for (i, l, n) in self.lines if (
+ n > MAX_LINE_LEN) and (not l.startswith('!'))]
self.reporter.check(not over,
self.filename,
'Line(s) are too long: {0}',
', '.join([str(i) for i in over]))
-
def check_trailing_whitespace(self):
"""Check for whitespace at the ends of lines."""
if self.args.trailing_whitespace:
- trailing = [i for (i, l, n) in self.lines if P_TRAILING_WHITESPACE.match(l)]
+ trailing = [
+ i for (i, l, n) in self.lines if P_TRAILING_WHITESPACE.match(l)]
self.reporter.check(not trailing,
self.filename,
'Line(s) end with whitespace: {0}',
', '.join([str(i) for i in trailing]))
-
def check_blockquote_classes(self):
"""Check that all blockquotes have known classes."""
- for node in self.find_all(self.doc, {'type' : 'blockquote'}):
+ for node in self.find_all(self.doc, {'type': 'blockquote'}):
cls = self.get_val(node, 'attr', 'class')
self.reporter.check(cls in KNOWN_BLOCKQUOTES,
(self.filename, self.get_loc(node)),
'Unknown or missing blockquote type {0}',
cls)
-
def check_codeblock_classes(self):
"""Check that all code blocks have known classes."""
- for node in self.find_all(self.doc, {'type' : 'codeblock'}):
+ for node in self.find_all(self.doc, {'type': 'codeblock'}):
cls = self.get_val(node, 'attr', 'class')
self.reporter.check(cls in KNOWN_CODEBLOCKS,
(self.filename, self.get_loc(node)),
'Unknown or missing code block type {0}',
cls)
-
def check_defined_link_references(self):
"""Check that defined links resolve in the file.
@@ -355,7 +365,7 @@ def check_defined_link_references(self):
"""
result = set()
- for node in self.find_all(self.doc, {'type' : 'text'}):
+ for node in self.find_all(self.doc, {'type': 'text'}):
for match in P_INTERNAL_LINK_REF.findall(node['value']):
text = match[0]
link = match[1]
@@ -366,11 +376,10 @@ def check_defined_link_references(self):
'Internally-defined links may be missing definitions: {0}',
', '.join(sorted(result)))
-
def find_all(self, node, pattern, accum=None):
"""Find all matches for a pattern."""
- assert type(pattern) == dict, 'Patterns must be dictionaries'
+ assert isinstance(pattern, dict), 'Patterns must be dictionaries'
if accum is None:
accum = []
if self.match(node, pattern):
@@ -379,7 +388,6 @@ def find_all(self, node, pattern, accum=None):
self.find_all(child, pattern, accum)
return accum
-
def match(self, node, pattern):
"""Does this node match the given pattern?"""
@@ -387,16 +395,16 @@ def match(self, node, pattern):
if key not in node:
return False
val = pattern[key]
- if type(val) == str:
+ if isinstance(val, str):
if node[key] != val:
return False
- elif type(val) == dict:
+ elif isinstance(val, dict):
if not self.match(node[key], val):
return False
return True
-
- def get_val(self, node, *chain):
+ @staticmethod
+ def get_val(node, *chain):
"""Get value one or more levels down."""
curr = node
@@ -406,7 +414,6 @@ def get_val(self, node, *chain):
break
return curr
-
def get_loc(self, node):
"""Convenience method to get node's line number."""
@@ -419,10 +426,6 @@ def get_loc(self, node):
class CheckNonJekyll(CheckBase):
"""Check a file that isn't translated by Jekyll."""
- def __init__(self, args, filename, metadata, metadata_len, text, lines, doc):
- super(CheckNonJekyll, self).__init__(args, filename, metadata, metadata_len, text, lines, doc)
-
-
def check_metadata(self):
self.reporter.check(self.metadata is None,
self.filename,
@@ -433,11 +436,11 @@ class CheckIndex(CheckBase):
"""Check the main index page."""
def __init__(self, args, filename, metadata, metadata_len, text, lines, doc):
- super(CheckIndex, self).__init__(args, filename, metadata, metadata_len, text, lines, doc)
+ super().__init__(args, filename, metadata, metadata_len, text, lines, doc)
self.layout = 'lesson'
def check_metadata(self):
- super(CheckIndex, self).check_metadata()
+ super().check_metadata()
self.reporter.check(self.metadata.get('root', '') == '.',
self.filename,
'Root not set to "."')
@@ -446,19 +449,14 @@ def check_metadata(self):
class CheckEpisode(CheckBase):
"""Check an episode page."""
- def __init__(self, args, filename, metadata, metadata_len, text, lines, doc):
- super(CheckEpisode, self).__init__(args, filename, metadata, metadata_len, text, lines, doc)
-
-
def check(self):
"""Run extra tests."""
- super(CheckEpisode, self).check()
+ super().check()
self.check_reference_inclusion()
-
def check_metadata(self):
- super(CheckEpisode, self).check_metadata()
+ super().check_metadata()
if self.metadata:
if 'layout' in self.metadata:
if self.metadata['layout'] == 'break':
@@ -470,19 +468,18 @@ def check_metadata(self):
else:
self.check_metadata_fields(TEACHING_METADATA_FIELDS)
-
def check_metadata_fields(self, expected):
+ """Check metadata fields."""
for (name, type_) in expected:
if name not in self.metadata:
self.reporter.add(self.filename,
'Missing metadata field {0}',
name)
- elif type(self.metadata[name]) != type_:
+ elif not isinstance(self.metadata[name], type_):
self.reporter.add(self.filename,
'"{0}" has wrong type in metadata ({1} instead of {2})',
name, type(self.metadata[name]), type_)
-
def check_reference_inclusion(self):
"""Check that links file has been included."""
@@ -507,7 +504,7 @@ class CheckReference(CheckBase):
"""Check the reference page."""
def __init__(self, args, filename, metadata, metadata_len, text, lines, doc):
- super(CheckReference, self).__init__(args, filename, metadata, metadata_len, text, lines, doc)
+ super().__init__(args, filename, metadata, metadata_len, text, lines, doc)
self.layout = 'reference'
@@ -515,8 +512,7 @@ class CheckGeneric(CheckBase):
"""Check a generic page."""
def __init__(self, args, filename, metadata, metadata_len, text, lines, doc):
- super(CheckGeneric, self).__init__(args, filename, metadata, metadata_len, text, lines, doc)
- self.layout = 'page'
+ super().__init__(args, filename, metadata, metadata_len, text, lines, doc)
CHECKERS = [
@@ -525,6 +521,7 @@ def __init__(self, args, filename, metadata, metadata_len, text, lines, doc):
(re.compile(r'index\.md'), CheckIndex),
(re.compile(r'reference\.md'), CheckReference),
(re.compile(r'_episodes/.*\.md'), CheckEpisode),
+ (re.compile(r'aio\.md'), CheckNonJekyll),
(re.compile(r'.*\.md'), CheckGeneric)
]
diff --git a/bin/lesson_initialize.py b/bin/lesson_initialize.py
index acb253b..a96ba38 100755
--- a/bin/lesson_initialize.py
+++ b/bin/lesson_initialize.py
@@ -1,415 +1,29 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
"""Initialize a newly-created repository."""
-from __future__ import print_function
import sys
import os
-
-ROOT_AUTHORS = '''\
-FIXME: list authors' names and email addresses.
-'''
-
-ROOT_CITATION = '''\
-FIXME: describe how to cite this lesson.
-'''
-
-ROOT_CONTRIBUTING_MD = '''\
-# Contributing
-
-[Software Carpentry][swc-site] and [Data Carpentry][dc-site] are open source projects,
-and we welcome contributions of all kinds:
-new lessons,
-fixes to existing material,
-bug reports,
-and reviews of proposed changes are all welcome.
-
-## Contributor Agreement
-
-By contributing,
-you agree that we may redistribute your work under [our license](LICENSE.md).
-In exchange,
-we will address your issues and/or assess your change proposal as promptly as we can,
-and help you become a member of our community.
-Everyone involved in [Software Carpentry][swc-site] and [Data Carpentry][dc-site]
-agrees to abide by our [code of conduct](CONDUCT.md).
-
-## How to Contribute
-
-The easiest way to get started is to file an issue
-to tell us about a spelling mistake,
-some awkward wording,
-or a factual error.
-This is a good way to introduce yourself
-and to meet some of our community members.
-
-1. If you do not have a [GitHub][github] account,
- you can [send us comments by email][email].
- However,
- we will be able to respond more quickly if you use one of the other methods described below.
-
-2. If you have a [GitHub][github] account,
- or are willing to [create one][github-join],
- but do not know how to use Git,
- you can report problems or suggest improvements by [creating an issue][issues].
- This allows us to assign the item to someone
- and to respond to it in a threaded discussion.
-
-3. If you are comfortable with Git,
- and would like to add or change material,
- you can submit a pull request (PR).
- Instructions for doing this are [included below](#using-github).
-
-## Where to Contribute
-
-1. If you wish to change this lesson,
- please work in ,
- which can be viewed at .
-
-2. If you wish to change the example lesson,
- please work in ,
- which documents the format of our lessons
- and can be viewed at .
-
-3. If you wish to change the template used for workshop websites,
- please work in .
- The home page of that repository explains how to set up workshop websites,
- while the extra pages in
- provide more background on our design choices.
-
-4. If you wish to change CSS style files, tools,
- or HTML boilerplate for lessons or workshops stored in `_includes` or `_layouts`,
- please work in .
-
-## What to Contribute
-
-There are many ways to contribute,
-from writing new exercises and improving existing ones
-to updating or filling in the documentation
-and and submitting [bug reports][issues]
-about things that don't work, aren't clear, or are missing.
-If you are looking for ideas, please see the 'Issues' tab for
-a list of issues associated with this repository,
-or you may also look at the issues for [Data Carpentry][dc-issues]
-and [Software Carpentry][swc-issues] projects.
-
-Comments on issues and reviews of pull requests are just as welcome:
-we are smarter together than we are on our own.
-Reviews from novices and newcomers are particularly valuable:
-it's easy for people who have been using these lessons for a while
-to forget how impenetrable some of this material can be,
-so fresh eyes are always welcome.
-
-## What *Not* to Contribute
-
-Our lessons already contain more material than we can cover in a typical workshop,
-so we are usually *not* looking for more concepts or tools to add to them.
-As a rule,
-if you want to introduce a new idea,
-you must (a) estimate how long it will take to teach
-and (b) explain what you would take out to make room for it.
-The first encourages contributors to be honest about requirements;
-the second, to think hard about priorities.
-
-We are also not looking for exercises or other material that only run on one platform.
-Our workshops typically contain a mixture of Windows, Mac OS X, and Linux users;
-in order to be usable,
-our lessons must run equally well on all three.
-
-## Using GitHub
-
-If you choose to contribute via GitHub, you may want to look at
-[How to Contribute to an Open Source Project on GitHub][how-contribute].
-To manage changes, we follow [GitHub flow][github-flow].
-Each lesson has two maintainers who review issues and pull requests or encourage others to do so.
-The maintainers are community volunteers and have final say over what gets merged into the lesson.
-To use the web interface for contributing to a lesson:
-
-1. Fork the master repository to your GitHub profile.
-2. Within your version of the forked repository, move to the `gh-pages` branch and
-create a new branch for each significant change being made.
-3. Navigate to the file(s) you wish to change within the new branches and make revisions as required.
-4. Commit all changed files within the appropriate branches.
-5. Create individual pull requests from each of your changed branches
-to the `gh-pages` branch within the master repository.
-6. If you receive feedback, make changes using your issue-specific branches of the forked
-repository and the pull requests will update automatically.
-7. Repeat as needed until all feedback has been addressed.
-
-When starting work, please make sure your clone of the master `gh-pages` branch is up-to-date
-before creating your own revision-specific branch(es) from there.
-Additionally, please only work from your newly-created branch(es) and *not*
-your clone of the master `gh-pages` branch.
-Lastly, published copies of all the lessons are available in the `gh-pages` branch of the master
-repository for reference while revising.
-
-## Other Resources
-
-General discussion of [Software Carpentry][swc-site] and [Data Carpentry][dc-site]
-happens on the [discussion mailing list][discuss-list],
-which everyone is welcome to join.
-You can also [reach us by email][email].
-
-[email]: mailto:admin@software-carpentry.org
-[dc-issues]: https://github.com/issues?q=user%3Adatacarpentry
-[dc-lessons]: http://datacarpentry.org/lessons/
-[dc-site]: http://datacarpentry.org/
-[discuss-list]: http://lists.software-carpentry.org/listinfo/discuss
-[github]: https://github.com
-[github-flow]: https://guides.github.com/introduction/flow/
-[github-join]: https://github.com/join
-[how-contribute]: https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github
-[issues]: https://guides.github.com/features/issues/
-[swc-issues]: https://github.com/issues?q=user%3Aswcarpentry
-[swc-lessons]: https://software-carpentry.org/lessons/
-[swc-site]: https://software-carpentry.org/
-'''
-
-ROOT_CONFIG_YML = '''\
-#------------------------------------------------------------
-# Values for this lesson.
-#------------------------------------------------------------
-
-# Which carpentry is this ("swc", "dc", or "lc")?
-carpentry: "swc"
-
-# Overall title for pages.
-title: "Lesson Title"
-
-# Contact. This *must* include the protocol: if it's an email
-# address, it must look like "mailto:lessons@software-carpentry.org",
-# or if it's a URL, "https://gitter.im/username/ProjectName".
-email: "mailto:lessons@software-carpentry.org"
-
-#------------------------------------------------------------
-# Generic settings (should not need to change).
-#------------------------------------------------------------
-
-# What kind of thing is this ("workshop" or "lesson")?
-kind: "lesson"
-
-# Magic to make URLs resolve both locally and on GitHub.
-# See https://help.github.com/articles/repository-metadata-on-github-pages/.
-repository: /
-
-# Sites.
-amy_site: "https://amy.software-carpentry.org/workshops"
-dc_site: "http://datacarpentry.org"
-swc_github: "https://github.com/swcarpentry"
-swc_site: "https://software-carpentry.org"
-swc_pages: "https://swcarpentry.github.io"
-lc_site: "https://librarycarpentry.github.io/"
-template_repo: "https://github.com/swcarpentry/styles"
-example_repo: "https://github.com/swcarpentry/lesson-example"
-example_site: "https://swcarpentry.github.com/lesson-example"
-workshop_repo: "https://github.com/swcarpentry/workshop-template"
-workshop_site: "https://swcarpentry.github.io/workshop-template"
-training_site: "https://swcarpentry.github.io/instructor-training"
-
-# Surveys.
-pre_survey: "https://www.surveymonkey.com/r/swc_pre_workshop_v1?workshop_id="
-post_survey: "https://www.surveymonkey.com/r/swc_post_workshop_v1?workshop_id="
-training_post_survey: "https://www.surveymonkey.com/r/post-instructor-training"
-
-# Start time in minutes (0 to be clock-independent, 540 to show a start at 09:00 am).
-start_time: 0
-
-# Specify that things in the episodes collection should be output.
-collections:
- episodes:
- output: true
- permalink: /:path/index.html
- extras:
- output: true
- permalink: /:path/index.html
-
-# Set the default layout for things in the episodes collection.
-defaults:
- - values:
- root: ..
- - scope:
- path: ""
- type: episodes
- values:
- layout: episode
-
-# Files and directories that are not to be copied.
-exclude:
- - Makefile
- - bin
-
-# Turn on built-in syntax highlighting.
-highlighter: rouge
-'''
-
-ROOT_INDEX_MD = '''\
----
-layout: lesson
-root: .
-permalink: index.html # Is the only page that don't follow the partner /:path/index.html
----
-FIXME: home page introduction
-
-> ## Prerequisites
->
-> FIXME
-{: .prereq}
-'''
-
-ROOT_REFERENCE_MD = '''\
----
-layout: reference
-root: .
----
-
-## Glossary
-
-FIXME
-'''
-
-ROOT_SETUP_MD = '''\
----
-layout: page
-title: Setup
-root: .
----
-FIXME
-'''
-
-ROOT_AIO_MD = '''\
----
-layout: page
-root: .
----
-
-{% comment %}
-Create anchor for each one of the episodes.
-{% endcomment %}
-{% for episode in site.episodes %}
-
-{% endfor %}
-'''
-
-EPISODES_INTRODUCTION_MD = '''\
----
-title: "Introduction"
-teaching: 0
-exercises: 0
-questions:
-- "Key question"
-objectives:
-- "First objective."
-keypoints:
-- "First key point."
----
-'''
-
-EXTRAS_ABOUT_MD = '''\
----
-layout: page
-title: About
----
-{% include carpentries.html %}
-'''
-
-EXTRAS_DISCUSS_MD = '''\
----
-layout: page
-title: Discussion
----
-FIXME
-'''
-
-EXTRAS_FIGURES_MD = '''\
----
-layout: page
-title: Figures
----
-
-{% comment %}
-Create anchor for each one of the episodes.
-{% endcomment %}
-{% for episode in site.episodes %}
-
-{% endfor %}
-'''
-
-EXTRAS_GUIDE_MD = '''\
----
-layout: page
-title: "Instructor Notes"
----
-FIXME
-'''
+import shutil
BOILERPLATE = (
- ('AUTHORS', ROOT_AUTHORS),
- ('CITATION', ROOT_CITATION),
- ('CONTRIBUTING.md', ROOT_CONTRIBUTING_MD),
- ('_config.yml', ROOT_CONFIG_YML),
- ('index.md', ROOT_INDEX_MD),
- ('reference.md', ROOT_REFERENCE_MD),
- ('setup.md', ROOT_SETUP_MD),
- ('aio.md', ROOT_AIO_MD),
- ('_episodes/01-introduction.md', EPISODES_INTRODUCTION_MD),
- ('_extras/about.md', EXTRAS_ABOUT_MD),
- ('_extras/discuss.md', EXTRAS_DISCUSS_MD),
- ('_extras/figures.md', EXTRAS_FIGURES_MD),
- ('_extras/guide.md', EXTRAS_GUIDE_MD),
+ '.travis.yml',
+ 'AUTHORS',
+ 'CITATION',
+ 'CODE_OF_CONDUCT.md',
+ 'CONTRIBUTING.md',
+ 'README.md',
+ '_config.yml',
+ '_episodes/01-introduction.md',
+ '_extras/about.md',
+ '_extras/discuss.md',
+ '_extras/figures.md',
+ '_extras/guide.md',
+ 'aio.md',
+ 'index.md',
+ 'reference.md',
+ 'setup.md',
)
@@ -418,7 +32,7 @@ def main():
# Check.
errors = False
- for (path, _) in BOILERPLATE:
+ for path in BOILERPLATE:
if os.path.exists(path):
print('Warning: {0} already exists.'.format(path), file=sys.stderr)
errors = True
@@ -427,9 +41,11 @@ def main():
sys.exit(1)
# Create.
- for (path, content) in BOILERPLATE:
- with open(path, 'w') as writer:
- writer.write(content)
+ for path in BOILERPLATE:
+ shutil.copyfile(
+ "bin/boilerplate/{}".format(path),
+ path
+ )
if __name__ == '__main__':
diff --git a/bin/repo_check.py b/bin/repo_check.py
index fd04ce9..af4b782 100755
--- a/bin/repo_check.py
+++ b/bin/repo_check.py
@@ -1,17 +1,17 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
"""
Check repository settings.
"""
-from __future__ import print_function
+
import sys
import os
from subprocess import Popen, PIPE
import re
-from optparse import OptionParser
+from argparse import ArgumentParser
-from util import Reporter, load_yaml, require
+from util import Reporter, require
# Import this way to produce a more useful error message.
try:
@@ -35,15 +35,26 @@
# Expected labels and colors.
EXPECTED = {
- 'bug' : 'bd2c00',
- 'discussion' : 'fc8dc1',
- 'enhancement' : '9cd6dc',
- 'help-wanted' : 'f4fd9c',
- 'instructor-training' : '6e5494',
- 'newcomer-friendly' : 'eec275',
- 'question' : '808040',
- 'template-and-tools' : '2b3990',
- 'work-in-progress' : '7ae78e'
+ 'help wanted': 'dcecc7',
+ 'status:in progress': '9bcc65',
+ 'status:changes requested': '679f38',
+ 'status:wait': 'fff2df',
+ 'status:refer to cac': 'ffdfb2',
+ 'status:need more info': 'ee6c00',
+ 'status:blocked': 'e55100',
+ 'status:out of scope': 'eeeeee',
+ 'status:duplicate': 'bdbdbd',
+ 'type:typo text': 'f8bad0',
+ 'type:bug': 'eb3f79',
+ 'type:formatting': 'ac1357',
+ 'type:template and tools': '7985cb',
+ 'type:instructor guide': '00887a',
+ 'type:discussion': 'b2e5fc',
+ 'type:enhancement': '7fdeea',
+ 'type:clarification': '00acc0',
+ 'type:teaching example': 'ced8dc',
+ 'good first issue': 'ffeb3a',
+ 'high priority': 'd22e2e'
}
@@ -54,7 +65,7 @@ def main():
args = parse_args()
reporter = Reporter()
- repo_url = get_repo_url(args.source_dir, args.repo_url)
+ repo_url = get_repo_url(args.repo_url)
check_labels(reporter, repo_url)
reporter.report()
@@ -64,24 +75,24 @@ def parse_args():
Parse command-line arguments.
"""
- parser = OptionParser()
- parser.add_option('-r', '--repo',
- default=None,
- dest='repo_url',
- help='repository URL')
- parser.add_option('-s', '--source',
- default=os.curdir,
- dest='source_dir',
- help='source directory')
-
- args, extras = parser.parse_args()
+ parser = ArgumentParser(description="""Check repository settings.""")
+ parser.add_argument('-r', '--repo',
+ default=None,
+ dest='repo_url',
+ help='repository URL')
+ parser.add_argument('-s', '--source',
+ default=os.curdir,
+ dest='source_dir',
+ help='source directory')
+
+ args, extras = parser.parse_known_args()
require(not extras,
'Unexpected trailing command-line arguments "{0}"'.format(extras))
return args
-def get_repo_url(source_dir, repo_url):
+def get_repo_url(repo_url):
"""
Figure out which repository to query.
"""
@@ -92,7 +103,8 @@ def get_repo_url(source_dir, repo_url):
# Guess.
cmd = 'git remote -v'
- p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, close_fds=True, universal_newlines=True)
+ p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE,
+ close_fds=True, universal_newlines=True)
stdout_data, stderr_data = p.communicate()
stdout_data = stdout_data.split('\n')
matches = [P_GIT_REMOTE.match(line) for line in stdout_data]
@@ -101,10 +113,12 @@ def get_repo_url(source_dir, repo_url):
'Unexpected output from git remote command: "{0}"'.format(matches))
username = matches[0].group(1)
- require(username, 'empty username in git remote output {0}'.format(matches[0]))
+ require(
+ username, 'empty username in git remote output {0}'.format(matches[0]))
project_name = matches[0].group(2)
- require(username, 'empty project name in git remote output {0}'.format(matches[0]))
+ require(
+ username, 'empty project name in git remote output {0}'.format(matches[0]))
url = F_REPO_URL.format(username, project_name)
return url
@@ -131,7 +145,7 @@ def check_labels(reporter, repo_url):
overlap = set(EXPECTED.keys()).intersection(set(actual.keys()))
for name in sorted(overlap):
- reporter.check(EXPECTED[name] == actual[name],
+ reporter.check(EXPECTED[name].lower() == actual[name].lower(),
None,
'Color mis-match for label {0} in {1}: expected {2}, found {3}',
name, repo_url, EXPECTED[name], actual[name])
@@ -143,13 +157,15 @@ def get_labels(repo_url):
"""
m = P_REPO_URL.match(repo_url)
- require(m, 'repository URL {0} does not match expected pattern'.format(repo_url))
+ require(
+ m, 'repository URL {0} does not match expected pattern'.format(repo_url))
username = m.group(1)
require(username, 'empty username in repository URL {0}'.format(repo_url))
project_name = m.group(2)
- require(username, 'empty project name in repository URL {0}'.format(repo_url))
+ require(
+ username, 'empty project name in repository URL {0}'.format(repo_url))
url = F_API_URL.format(username, project_name)
r = requests.get(url)
diff --git a/bin/test_lesson_check.py b/bin/test_lesson_check.py
index 743d0cf..960059e 100755
--- a/bin/test_lesson_check.py
+++ b/bin/test_lesson_check.py
@@ -1,11 +1,14 @@
+#!/usr/bin/env python3
+
import unittest
import lesson_check
import util
+
class TestFileList(unittest.TestCase):
def setUp(self):
- self.reporter = util.Reporter() ## TODO: refactor reporter class.
+ self.reporter = util.Reporter() # TODO: refactor reporter class.
def test_file_list_has_expected_entries(self):
# For first pass, simply assume that all required files are present
@@ -15,5 +18,6 @@ def test_file_list_has_expected_entries(self):
lesson_check.check_fileset('', self.reporter, all_filenames)
self.assertEqual(len(self.reporter.messages), 0)
+
if __name__ == "__main__":
unittest.main()
diff --git a/bin/util.py b/bin/util.py
index 0cc8de6..522f5df 100644
--- a/bin/util.py
+++ b/bin/util.py
@@ -1,4 +1,3 @@
-from __future__ import print_function
import sys
import os
import json
@@ -29,16 +28,14 @@
# (Can't use 'None' because that might be a legitimate value.)
REPORTER_NOT_SET = []
-class Reporter(object):
+
+class Reporter:
"""Collect and report errors."""
def __init__(self):
"""Constructor."""
-
- super(Reporter, self).__init__()
self.messages = []
-
def check_field(self, filename, name, values, key, expected=REPORTER_NOT_SET):
"""Check that a dictionary has an expected value."""
@@ -48,10 +45,11 @@ def check_field(self, filename, name, values, key, expected=REPORTER_NOT_SET):
pass
elif type(expected) in (tuple, set, list):
if values[key] not in expected:
- self.add(filename, '{0} {1} value {2} is not in {3}', name, key, values[key], expected)
+ self.add(
+ filename, '{0} {1} value {2} is not in {3}', name, key, values[key], expected)
elif values[key] != expected:
- self.add(filename, '{0} {1} is {2} not {3}', name, key, values[key], expected)
-
+ self.add(filename, '{0} {1} is {2} not {3}',
+ name, key, values[key], expected)
def check(self, condition, location, fmt, *args):
"""Append error if condition not met."""
@@ -59,12 +57,36 @@ def check(self, condition, location, fmt, *args):
if not condition:
self.add(location, fmt, *args)
-
def add(self, location, fmt, *args):
"""Append error unilaterally."""
self.messages.append((location, fmt.format(*args)))
+ @staticmethod
+ def pretty(item):
+ location, message = item
+ if isinstance(location, type(None)):
+ return message
+ elif isinstance(location, str):
+ return location + ': ' + message
+ elif isinstance(location, tuple):
+ return '{0}:{1}: '.format(*location) + message
+
+ print('Unknown item "{0}"'.format(item), file=sys.stderr)
+ return NotImplemented
+
+ @staticmethod
+ def key(item):
+ location, message = item
+ if isinstance(location, type(None)):
+ return ('', -1, message)
+ elif isinstance(location, str):
+ return (location, -1, message)
+ elif isinstance(location, tuple):
+ return (location[0], location[1], message)
+
+ print('Unknown item "{0}"'.format(item), file=sys.stderr)
+ return NotImplemented
def report(self, stream=sys.stdout):
"""Report all messages in order."""
@@ -72,30 +94,8 @@ def report(self, stream=sys.stdout):
if not self.messages:
return
- def pretty(item):
- location, message = item
- if isinstance(location, type(None)):
- return message
- elif isinstance(location, str):
- return location + ': ' + message
- elif isinstance(location, tuple):
- return '{0}:{1}: '.format(*location) + message
- else:
- assert False, 'Unknown item "{0}"'.format(item)
-
- def key(item):
- location, message = item
- if isinstance(location, type(None)):
- return ('', -1, message)
- elif isinstance(location, str):
- return (location, -1, message)
- elif isinstance(location, tuple):
- return (location[0], location[1], message)
- else:
- assert False, 'Unknown item "{0}"'.format(item)
-
- for m in sorted(self.messages, key=key):
- print(pretty(m), file=stream)
+ for m in sorted(self.messages, key=self.key):
+ print(self.pretty(m), file=stream)
def read_markdown(parser, path):
@@ -111,11 +111,13 @@ def read_markdown(parser, path):
# Split into lines.
metadata_len = 0 if metadata_raw is None else metadata_raw.count('\n')
- lines = [(metadata_len+i+1, line, len(line)) for (i, line) in enumerate(body.split('\n'))]
+ lines = [(metadata_len+i+1, line, len(line))
+ for (i, line) in enumerate(body.split('\n'))]
# Parse Markdown.
cmd = 'ruby {0}'.format(parser)
- p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, close_fds=True, universal_newlines=True)
+ p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE,
+ close_fds=True, universal_newlines=True)
stdout_data, stderr_data = p.communicate(body)
doc = json.loads(stdout_data)
@@ -136,7 +138,6 @@ def split_metadata(path, text):
metadata_raw = None
metadata_yaml = None
- metadata_len = None
pieces = text.split('---', 2)
if len(pieces) == 3:
@@ -145,7 +146,8 @@ def split_metadata(path, text):
try:
metadata_yaml = yaml.load(metadata_raw)
except yaml.YAMLError as e:
- print('Unable to parse YAML header in {0}:\n{1}'.format(path, e), file=sys.stderr)
+ print('Unable to parse YAML header in {0}:\n{1}'.format(
+ path, e), file=sys.stderr)
sys.exit(1)
return metadata_raw, metadata_yaml, text
@@ -160,8 +162,9 @@ def load_yaml(filename):
try:
with open(filename, 'r') as reader:
return yaml.load(reader)
- except (yaml.YAMLError, FileNotFoundError) as e:
- print('Unable to load YAML file {0}:\n{1}'.format(filename, e), file=sys.stderr)
+ except (yaml.YAMLError, IOError) as e:
+ print('Unable to load YAML file {0}:\n{1}'.format(
+ filename, e), file=sys.stderr)
sys.exit(1)
diff --git a/bin/workshop_check.py b/bin/workshop_check.py
index f82ce3d..bd15210 100755
--- a/bin/workshop_check.py
+++ b/bin/workshop_check.py
@@ -1,10 +1,10 @@
-#!/usr/bin/env python
+#!/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.
'''
-from __future__ import print_function
+
import sys
import os
import re
@@ -174,8 +174,8 @@ def check_latitude_longitude(latlng):
try:
lat, lng = latlng.split(',')
lat = float(lat)
- long = float(lng)
- return (-90.0 <= lat <= 90.0) and (-180.0 <= long <= 180.0)
+ lng = float(lng)
+ return (-90.0 <= lat <= 90.0) and (-180.0 <= lng <= 180.0)
except ValueError:
return False
@@ -203,9 +203,9 @@ def check_helpers(helpers):
@look_for_fixme
-def check_email(email):
+def check_emails(emails):
"""
- 'email' must be a comma-separated list of valid email addresses.
+ 'emails' must be a comma-separated list of valid email addresses.
The list may be empty. A valid email address consists of characters,
an '@', and more characters. It should not contain the default contact
"""
@@ -217,7 +217,7 @@ def check_email(email):
return False
else:
return False
-
+
return True
@@ -294,9 +294,9 @@ def check_pass(value):
'["First helper", "Second helper",..]'),
'email': (True, check_emails,
- 'contact email list isn\'t a valid list of format ' +
- '["me@example.org", "you@example.org",..] or contains incorrectly formatted email addresses or ' +
- '"{0}".'.format(DEFAULT_CONTACT_EMAIL)),
+ 'contact email list isn\'t a valid list of format ' +
+ '["me@example.org", "you@example.org",..] or contains incorrectly formatted email addresses or ' +
+ '"{0}".'.format(DEFAULT_CONTACT_EMAIL)),
'eventbrite': (False, check_eventbrite, 'Eventbrite key appears invalid'),
@@ -308,10 +308,10 @@ def check_pass(value):
}
# REQUIRED is all required categories.
-REQUIRED = set([k for k in HANDLERS if HANDLERS[k][0]])
+REQUIRED = {k for k in HANDLERS if HANDLERS[k][0]}
# OPTIONAL is all optional categories.
-OPTIONAL = set([k for k in HANDLERS if not HANDLERS[k][0]])
+OPTIONAL = {k for k in HANDLERS if not HANDLERS[k][0]}
def check_blank_lines(reporter, raw):
@@ -319,7 +319,8 @@ def check_blank_lines(reporter, raw):
Blank lines are not allowed in category headers.
"""
- lines = [(i, x) for (i, x) in enumerate(raw.strip().split('\n')) if not x.strip()]
+ lines = [(i, x) for (i, x) in enumerate(
+ raw.strip().split('\n')) if not x.strip()]
reporter.check(not lines,
None,
'Blank line(s) in header: {0}',
diff --git a/favicon-dc.ico b/favicon-dc.ico
deleted file mode 100644
index 4937f2e..0000000
Binary files a/favicon-dc.ico and /dev/null differ
diff --git a/favicon-lc.ico b/favicon-lc.ico
deleted file mode 100644
index f4f3c93..0000000
Binary files a/favicon-lc.ico and /dev/null differ
diff --git a/favicon-swc.ico b/favicon-swc.ico
deleted file mode 100644
index 34f80ad..0000000
Binary files a/favicon-swc.ico and /dev/null differ