├── code └── .gitkeep ├── data └── .gitkeep ├── fig ├── .gitkeep ├── completed-page.png ├── using-github-import.png ├── edit-index-file-menu-bar.png └── select-gh-pages-branch.png ├── _episodes └── .gitkeep ├── _extras ├── .gitkeep ├── about.md ├── design.md ├── faq.md └── customization.md ├── files ├── .gitkeep └── etherpad.txt ├── _episodes_rmd ├── .gitkeep └── data │ └── .gitkeep ├── requirements.txt ├── _layouts ├── page.html ├── reference.html ├── lesson.html ├── break.html ├── episode.html ├── base.html └── workshop.html ├── favicon-dc.ico ├── favicon-lc.ico ├── .gitignore ├── favicon-swc.ico ├── _includes ├── github-ribbon.html ├── main_title.html ├── episode_title.html ├── dc │ ├── who.html │ ├── intro.html │ ├── schedule.html │ └── syllabus.html ├── sc │ ├── who.html │ ├── intro.html │ ├── schedule.html │ └── syllabus.html ├── lc │ ├── who.html │ ├── intro.html │ ├── schedule.html │ └── syllabus.html ├── episode_keypoints.html ├── episode_break.html ├── workshop_calendar.html ├── all_keypoints.html ├── workshop_footer.html ├── javascript.html ├── episode_overview.html ├── workshop_ad.html ├── episode_navbar.html ├── lesson_footer.html ├── links.md ├── carpentries.html ├── syllabus.html └── navbar.html ├── assets ├── img │ ├── lc-icon-black.png │ ├── swc-logo-blue.png │ ├── swc-logo-white.png │ ├── swc-icon-blue.svg │ ├── dc-icon-black.svg │ └── lc-icon-black.svg ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 ├── js │ └── lesson.js └── css │ ├── syntax.css │ └── lesson.scss ├── CITATION ├── bin ├── knit_lessons.sh ├── markdown_ast.rb ├── test_lesson_check.py ├── generate_md_episodes.R ├── chunk-options.R ├── repo_check.py ├── util.py ├── workshop_check.py ├── lesson_initialize.py └── lesson_check.py ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── AUTHORS ├── CONDUCT.md ├── .travis.yml ├── setup ├── swc-installation-test-1.py └── index.md ├── .mailmap ├── _config.yml ├── LICENSE.md ├── Makefile ├── CONTRIBUTING.md └── README.md /code/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /data/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /fig/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_episodes/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_extras/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /files/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_episodes_rmd/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /_episodes_rmd/data/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | PyYAML 2 | -------------------------------------------------------------------------------- /_layouts/page.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base 3 | --- 4 | {% include main_title.html %} 5 | {{content}} 6 | -------------------------------------------------------------------------------- /favicon-dc.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MilesMcBain/2018-04-10-dummyswsite/gh-pages/favicon-dc.ico -------------------------------------------------------------------------------- /favicon-lc.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MilesMcBain/2018-04-10-dummyswsite/gh-pages/favicon-lc.ico -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *~ 3 | .DS_Store 4 | .ipynb_checkpoints 5 | .sass-cache 6 | __pycache__ 7 | _site 8 | -------------------------------------------------------------------------------- /favicon-swc.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MilesMcBain/2018-04-10-dummyswsite/gh-pages/favicon-swc.ico -------------------------------------------------------------------------------- /fig/completed-page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MilesMcBain/2018-04-10-dummyswsite/gh-pages/fig/completed-page.png -------------------------------------------------------------------------------- /_includes/github-ribbon.html: -------------------------------------------------------------------------------- 1 | Find us on GitHub 2 | -------------------------------------------------------------------------------- /_extras/about.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: About 4 | permalink: /about/ 5 | --- 6 | {% include carpentries.html %} 7 | -------------------------------------------------------------------------------- /fig/using-github-import.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MilesMcBain/2018-04-10-dummyswsite/gh-pages/fig/using-github-import.png -------------------------------------------------------------------------------- /_layouts/reference.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: "Reference" 4 | --- 5 | {% include all_keypoints.html %} 6 | {{content}} 7 | -------------------------------------------------------------------------------- /assets/img/lc-icon-black.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MilesMcBain/2018-04-10-dummyswsite/gh-pages/assets/img/lc-icon-black.png -------------------------------------------------------------------------------- /assets/img/swc-logo-blue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MilesMcBain/2018-04-10-dummyswsite/gh-pages/assets/img/swc-logo-blue.png -------------------------------------------------------------------------------- /assets/img/swc-logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MilesMcBain/2018-04-10-dummyswsite/gh-pages/assets/img/swc-logo-white.png -------------------------------------------------------------------------------- /_layouts/lesson.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base 3 | --- 4 | {% include main_title.html %} 5 | {{ content }} 6 | {% include syllabus.html %} 7 | -------------------------------------------------------------------------------- /fig/edit-index-file-menu-bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MilesMcBain/2018-04-10-dummyswsite/gh-pages/fig/edit-index-file-menu-bar.png -------------------------------------------------------------------------------- /fig/select-gh-pages-branch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MilesMcBain/2018-04-10-dummyswsite/gh-pages/fig/select-gh-pages-branch.png -------------------------------------------------------------------------------- /assets/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MilesMcBain/2018-04-10-dummyswsite/gh-pages/assets/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /assets/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MilesMcBain/2018-04-10-dummyswsite/gh-pages/assets/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /assets/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MilesMcBain/2018-04-10-dummyswsite/gh-pages/assets/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /assets/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MilesMcBain/2018-04-10-dummyswsite/gh-pages/assets/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /CITATION: -------------------------------------------------------------------------------- 1 | Please cite as: 2 | 3 | Greg Wilson (ed): "Software Carpentry: Workshop Template." Version 4 | 2016.06, June 2016, https://github.com/swcarpentry/workshop-template, 5 | 10.5281/zenodo.58156. 6 | -------------------------------------------------------------------------------- /_includes/main_title.html: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | Main title for lesson pages. 3 | {% endcomment %} 4 |

{{ site.title }}{% if page.title %}: {{ page.title }}{% endif %}

5 | -------------------------------------------------------------------------------- /_includes/episode_title.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |

{{ page.title }}

6 |
7 |
8 |
9 |
10 | -------------------------------------------------------------------------------- /bin/knit_lessons.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Only try running R to translate files if there are some files present. 4 | # The Makefile passes in the names of files. 5 | 6 | if [ $# -ne 0 ] ; then 7 | Rscript -e "source('bin/generate_md_episodes.R')" 8 | fi 9 | -------------------------------------------------------------------------------- /_includes/dc/who.html: -------------------------------------------------------------------------------- 1 |

2 | Who: 3 | The course is aimed at graduate students and other researchers. 4 | 5 | You don't need to have any previous knowledge of the tools 6 | that will be presented at the workshop. 7 | 8 |

9 | -------------------------------------------------------------------------------- /_includes/sc/who.html: -------------------------------------------------------------------------------- 1 |

2 | Who: 3 | The course is aimed at graduate students and other researchers. 4 | 5 | You don't need to have any previous knowledge of the tools 6 | that will be presented at the workshop. 7 | 8 |

9 | -------------------------------------------------------------------------------- /_includes/lc/who.html: -------------------------------------------------------------------------------- 1 |

2 | Who: 3 | The course is for librarians, archivists, and other information workers. 4 | 5 | You don't need to have any previous knowledge of the tools that 6 | will be presented at the workshop. 7 | 8 |

9 | -------------------------------------------------------------------------------- /_layouts/break.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base 3 | --- 4 | {% include episode_navbar.html episode_navbar_title=true %} 5 |
6 | {% include episode_title.html %} 7 | {% include episode_break.html %} 8 | {{content}} 9 |
10 | {% include episode_navbar.html episode_navbar_title=false %} 11 | -------------------------------------------------------------------------------- /bin/markdown_ast.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # Use Kramdown parser to produce AST for Markdown document. 4 | 5 | require "kramdown" 6 | require "json" 7 | 8 | markdown = STDIN.read() 9 | doc = Kramdown::Document.new(markdown) 10 | tree = doc.to_hash_a_s_t 11 | puts JSON.pretty_generate(tree) 12 | -------------------------------------------------------------------------------- /_includes/episode_keypoints.html: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | Display key points for an episode. 3 | {% endcomment %} 4 |
5 |

Key Points

6 | 11 |
12 | -------------------------------------------------------------------------------- /_layouts/episode.html: -------------------------------------------------------------------------------- 1 | --- 2 | layout: base 3 | --- 4 | {% include episode_navbar.html episode_navbar_title=true %} 5 |
6 | {% include episode_title.html %} 7 | {% include episode_overview.html %} 8 | {{content}} 9 | {% include episode_keypoints.html %} 10 |
11 | {% include episode_navbar.html episode_navbar_title=false %} 12 | -------------------------------------------------------------------------------- /_includes/episode_break.html: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | Display a break's timings in a box similar to a learning episode's. 3 | {% endcomment %} 4 |
5 |

Overview

6 | 7 |
8 |
9 | Break: {{ page.break }} min 10 |
11 |
12 |
13 |
14 | 15 |
16 | -------------------------------------------------------------------------------- /_includes/workshop_calendar.html: -------------------------------------------------------------------------------- 1 | Add to your Google Calendar. 2 | -------------------------------------------------------------------------------- /_includes/all_keypoints.html: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | Display key points of all episodes for reference. 3 | {% endcomment %} 4 |

Key Points

5 | 6 | {% for episode in site.episodes %} 7 | {% unless episode.break %} 8 | 9 | 12 | 19 | 20 | {% endunless %} 21 | {% endfor %} 22 |
10 | {{ episode.title }} 11 | 13 |
    14 | {% for keypoint in episode.keypoints %} 15 |
  • {{ keypoint|markdownify }}
  • 16 | {% endfor %} 17 |
18 |
23 | -------------------------------------------------------------------------------- /files/etherpad.txt: -------------------------------------------------------------------------------- 1 | Welcome to Software Carpentry 2 | 3 | We will use this Etherpad to share links and snippets of code, take notes, ask and answer questions, and whatever else comes to mind. 4 | The page displays a screen with three major parts: 5 | 6 | * The left side holds today's notes: please edit these as we go along. 7 | * The top right side shows the names of users who are logged in: please add your name and pick the color that best reflects your mood and personality. 8 | * The bottom right is a real time chat window for asking questions of the instructor and your fellow learners. 9 | 10 | To start, please add yourself to the attendee list below: 11 | 12 | * Instructor (discipline, institution) 13 | -------------------------------------------------------------------------------- /bin/test_lesson_check.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import lesson_check 4 | import util 5 | 6 | class TestFileList(unittest.TestCase): 7 | def setUp(self): 8 | self.reporter = util.Reporter() ## TODO: refactor reporter class. 9 | 10 | def test_file_list_has_expected_entries(self): 11 | # For first pass, simply assume that all required files are present 12 | all_filenames = [filename.replace('%', '') 13 | for filename in lesson_check.REQUIRED_FILES] 14 | 15 | lesson_check.check_fileset('', self.reporter, all_filenames) 16 | self.assertEqual(len(self.reporter.messages), 0) 17 | 18 | if __name__ == "__main__": 19 | unittest.main() 20 | -------------------------------------------------------------------------------- /_includes/workshop_footer.html: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | Footer for a standard workshop. 3 | {% endcomment %} 4 | 25 | -------------------------------------------------------------------------------- /_includes/javascript.html: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | Javascript used in lesson and workshop pages. 3 | {% endcomment %} 4 | 5 | 6 | 7 | 15 | -------------------------------------------------------------------------------- /_includes/dc/intro.html: -------------------------------------------------------------------------------- 1 |

2 | Data Carpentry 3 | aims to help researchers get their work done 4 | in less time and with less pain 5 | by teaching them basic research computing skills. 6 | This hands-on workshop will cover basic concepts and tools, 7 | including program design, version control, data management, 8 | and task automation. 9 | Participants will be encouraged to help one another 10 | and to apply what they have learned to their own research problems. 11 |

12 |

13 | 14 | For more information on what we teach and why, 15 | please see our paper 16 | "Best Practices for Scientific Computing". 17 | 18 |

19 | -------------------------------------------------------------------------------- /_includes/sc/intro.html: -------------------------------------------------------------------------------- 1 |

2 | Software Carpentry 3 | aims to help researchers get their work done 4 | in less time and with less pain 5 | by teaching them basic research computing skills. 6 | This hands-on workshop will cover basic concepts and tools, 7 | including program design, version control, data management, 8 | and task automation. 9 | Participants will be encouraged to help one another 10 | and to apply what they have learned to their own research problems. 11 |

12 |

13 | 14 | For more information on what we teach and why, 15 | please see our paper 16 | "Best Practices for Scientific Computing". 17 | 18 |

19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Please delete the text below before submitting your contribution. 2 | 3 | --- 4 | 5 | Thanks for contributing! If this contribution is for instructor training, please send an email to checkout@carpentries.org with a link to this contribution so we can record your progress. You’ve completed your contribution step for instructor checkout just by submitting this contribution. 6 | 7 | Please keep in mind that lesson maintainers are volunteers and it may be some time before they can respond to your contribution. Although not all contributions can be incorporated into the lesson materials, we appreciate your time and effort to improve the curriculum. If you have any questions about the lesson maintenance process or would like to volunteer your time as a contribution reviewer, please contact Kate Hertweck (k8hertweck@gmail.com). 8 | 9 | --- 10 | -------------------------------------------------------------------------------- /_includes/lc/intro.html: -------------------------------------------------------------------------------- 1 |

2 | Library Carpentry 3 | is made by librarians, for librarians to help you: 4 |

5 | 12 |

13 | 14 | Library Carpentry introduces you to the fundamentals of computing 15 | and provides you with a platform for further self-directed learning. 16 | For more information on what we teach and why, please see our paper 17 | "Library Carpentry: software skills training for library professionals". 18 | 19 |

20 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Please delete the text below before submitting your contribution. 2 | 3 | --- 4 | 5 | Thanks for contributing! If this contribution is for instructor training, please send an email to checkout@carpentries.org with a link to this contribution so we can record your progress. You’ve completed your contribution step for instructor checkout just by submitting this contribution. 6 | 7 | Please keep in mind that lesson maintainers are volunteers and it may be some time before they can respond to your contribution. Although not all contributions can be incorporated into the lesson materials, we appreciate your time and effort to improve the curriculum. If you have any questions about the lesson maintenance process or would like to volunteer your time as a contribution reviewer, please contact Kate Hertweck (k8hertweck@gmail.com). 8 | 9 | --- 10 | -------------------------------------------------------------------------------- /_includes/episode_overview.html: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | Display an episode's timings and learning objectives. 3 | {% endcomment %} 4 |
5 |

Overview

6 | 7 |
8 |
9 | Teaching: {{ page.teaching }} min 10 |
11 | Exercises: {{ page.exercises }} min 12 |
13 |
14 | Questions 15 |
    16 | {% for question in page.questions %} 17 |
  • {{ question|markdownify }}
  • 18 | {% endfor %} 19 |
20 |
21 |
22 | 23 |
24 |
25 |
26 |
27 | Objectives 28 |
    29 | {% for objective in page.objectives %} 30 |
  • {{ objective|markdownify }}
  • 31 | {% endfor %} 32 |
33 |
34 |
35 | 36 |
37 | -------------------------------------------------------------------------------- /_includes/workshop_ad.html: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | Advertising box at the top of a workshop website home page. 3 | {% endcomment %} 4 |
5 |
6 |
7 |

{{page.venue}}

8 |
9 |
10 |

{{page.humandate}}

11 |

{% if page.humantime %}{{page.humantime}}{% endif %}

12 |
13 |
14 |

15 | Instructors: 16 | {% if page.instructor %} 17 | {{page.instructor | join: ', ' %}} 18 | {% else %} 19 | to be announced. 20 | {% endif %} 21 |

22 | {% if page.helper %} 23 |

24 | Helpers: 25 | {{page.helper | join: ', ' %}} 26 |

27 | {% endif %} 28 |
29 |
30 |
31 |
32 |
33 | -------------------------------------------------------------------------------- /_includes/lc/schedule.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Day 1

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
09:00 Data Intro for Librarians
10:30 Coffee
12:00 Lunch break
13:00 Shell Lessons for Libraries
14:30 Coffee
16:00 Wrap-up
16:30 END
13 |
14 |
15 |

Day 2

16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
09:00 Git Intro for Librarians
10:30 Coffee
12:00 Lunch break
13:00 OpenRefine for Librarians
14:30 Coffee
16:00 Wrap-up
16:30 END
25 |
26 |
27 | -------------------------------------------------------------------------------- /assets/js/lesson.js: -------------------------------------------------------------------------------- 1 | // Make all tables striped by default. 2 | $("table").addClass("table table-striped"); 3 | 4 | 5 | // Handle foldable challenges and solutions (on click and at start). 6 | $(".solution").click(function(event) { 7 | var trigger = $(event.target).has(".fold-unfold").size() > 0 8 | || $(event.target).filter(".fold-unfold").size() > 0; 9 | if (trigger) { 10 | $(">*:not(h2)", this).toggle(400); 11 | $(">h2>span.fold-unfold", this).toggleClass("glyphicon-collapse-down glyphicon-collapse-up"); 12 | event.stopPropagation(); 13 | } 14 | }); 15 | $(".solution").each(function() { 16 | $(">*:not(h2)", this).toggle(); 17 | var h2 = $("h2:first", this); 18 | h2.append(""); 19 | }); 20 | 21 | 22 | // Handle searches. 23 | // Relies on document having 'meta' element with name 'search-domain'. 24 | function google_search() { 25 | var query = document.getElementById("google-search").value; 26 | var domain = $("meta[name=search-domain]").attr("value"); 27 | window.open("https://www.google.com/search?q=" + query + "+site:" + domain); 28 | } 29 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Aron Ahmadia 2 | Phillip Alderman 3 | James Allen 4 | Piotr Banaszkiewicz 5 | Pauline Barmby 6 | Radovan Bast 7 | John Blischak 8 | Olga Botvinnik 9 | Andy Boughton 10 | Erik M. Bray 11 | Amy Brown 12 | C. Titus Brown 13 | Greg Caporaso 14 | Sarah Clayton 15 | Matt Davis 16 | Neal Davis 17 | Jonah Duckles 18 | Marianna Foos 19 | Yashasvi Girdhar 20 | Ivan Gonzalez 21 | John Gosset 22 | Alistair Grant 23 | Thomas Guignard 24 | Konrad Hinsen 25 | Xavier Ho 26 | Damien Irving 27 | Mike Jackson 28 | Ben Jolly 29 | Jan T. Kim 30 | W. Trevor King 31 | David LeBauer 32 | Joona Lehtomäki 33 | Kunal Marwaha 34 | Sheldon McKay 35 | Lauren Michael 36 | François Michonneau 37 | Lex Nederbragt 38 | Aleksandra Nenadic 39 | Jeramia Ory 40 | Fernando Perez 41 | Leighton Pritchard 42 | Andrey Prokopenko 43 | Scott Ritchie 44 | Maneesha Sane 45 | Raniere Silva 46 | Arfon Smith 47 | Ashwin Srinath 48 | Sarah Stevens 49 | Julia Stewart Lowndes 50 | Tracy Teal 51 | Stefan Topfstedt 52 | Olav Vahtras 53 | Philipp Von Bieberstein 54 | Andrew Walker 55 | Ben Waugh 56 | Lukas Weber 57 | Ethan White 58 | Jason Williams 59 | Carol Willing 60 | Greg Wilson 61 | Tom Wright 62 | Andrea Zonca 63 | Alex Thompson 64 | Jonathan Cooper 65 | Marcel Stimberg 66 | Mark A. Matienzo 67 | Steve Moss 68 | -------------------------------------------------------------------------------- /_includes/episode_navbar.html: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | Navigation bar for an episode. 3 | {% endcomment %} 4 |
5 |
6 |

7 | {% if page.previous.url %} 8 | previous episode 9 | {% else %} 10 | lesson home 11 | {% endif %} 12 |

13 |
14 |
15 | {% if include.episode_navbar_title %} 16 |

{{ site.title }}

17 | {% endif %} 18 |
19 |
20 |

21 | {% if page.next.url %} 22 | next episode 23 | {% else %} 24 | lesson home 25 | {% endif %} 26 |

27 |
28 |
29 | -------------------------------------------------------------------------------- /_includes/lesson_footer.html: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | Footer for lesson pages. 3 | {% endcomment %} 4 | 39 | -------------------------------------------------------------------------------- /_includes/sc/schedule.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Day 1

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
Before Pre-workshop survey
09:00 Automating tasks with the Unix shell
10:30 Coffee
12:00 Lunch break
13:00 Building programs with Python
14:30 Coffee
16:00 Wrap-up
16:30 END
14 |
15 |
16 |

Day 2

17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
09:00 Version control with Git
10:30 Coffee
12:00 Lunch break
13:00 Managing data with SQL
14:30 Coffee
16:00 Wrap-up
16:30 Post-workshop Survey
16:40 END
27 |
28 |
29 | -------------------------------------------------------------------------------- /_includes/dc/schedule.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Day 1

4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
Before startingPre-workshop survey
Morning Data organization in spreadsheets
OpenRefine for data cleaning
AfternoonIntroduction to R
Evening END
14 |
15 |
16 |

Day 2

17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
Morning Continuation of R: data analysis & visualization
AfternoonData management with SQL
EveningPost-workshop survey
END
29 |
30 |
31 | -------------------------------------------------------------------------------- /bin/generate_md_episodes.R: -------------------------------------------------------------------------------- 1 | generate_md_episodes <- function() { 2 | 3 | library("methods") 4 | 5 | if (require("knitr") && packageVersion("knitr") < '1.9.19') 6 | stop("knitr must be version 1.9.20 or higher") 7 | 8 | if (!require("stringr")) 9 | stop("The package stringr is required for generating the lessons.") 10 | 11 | if (require("checkpoint") && packageVersion("checkpoint") >= '0.4.0') { 12 | required_pkgs <- 13 | checkpoint:::scanForPackages(project = "_episodes_rmd", 14 | verbose=FALSE, use.knitr = TRUE)$pkgs 15 | } else { 16 | stop("The checkpoint package (>= 0.4.0) is required to build the lessons.") 17 | } 18 | 19 | missing_pkgs <- required_pkgs[!(required_pkgs %in% rownames(installed.packages()))] 20 | 21 | if (length(missing_pkgs)) { 22 | message("Installing missing required packages: ", 23 | paste(missing_pkgs, collapse=", ")) 24 | install.packages(missing_pkgs) 25 | } 26 | 27 | ## find all the Rmd files, and generate the paths for their respective outputs 28 | src_rmd <- list.files(pattern = "??-*.Rmd$", path = "_episodes_rmd", full.names = TRUE) 29 | dest_md <- file.path("_episodes", gsub("Rmd$", "md", basename(src_rmd))) 30 | 31 | ## knit the Rmd into markdown 32 | mapply(function(x, y) { 33 | knitr::knit(x, output = y) 34 | }, src_rmd, dest_md) 35 | 36 | } 37 | 38 | generate_md_episodes() 39 | -------------------------------------------------------------------------------- /CONDUCT.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: "Contributor Code of Conduct" 4 | --- 5 | As contributors and maintainers of this project, 6 | we pledge to respect all people who contribute through reporting issues, 7 | posting feature requests, 8 | updating documentation, 9 | submitting pull requests or patches, 10 | and other activities. 11 | 12 | We are committed to making participation in this project a harassment-free experience for everyone, 13 | regardless of level of experience, 14 | gender, 15 | gender identity and expression, 16 | sexual orientation, 17 | disability, 18 | personal appearance, 19 | body size, 20 | race, 21 | ethnicity, 22 | age, 23 | or religion. 24 | 25 | Examples of unacceptable behavior by participants include the use of sexual language or imagery, 26 | derogatory comments or personal attacks, 27 | trolling, 28 | public or private harassment, 29 | insults, 30 | or other unprofessional conduct. 31 | 32 | Project maintainers have the right and responsibility to remove, edit, or reject 33 | comments, commits, code, wiki edits, issues, and other contributions 34 | that are not aligned to our [Code of Conduct][coc]. 35 | Project maintainers who do not follow the Code of Conduct may be removed from the project team. 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior 38 | may be reported by following our [reporting guidelines][coc-reporting]. 39 | 40 | 41 | - [Software and Data Carpentry Code of Conduct][coc] 42 | - [Code of Conduct Reporting Guide][coc-reporting] 43 | 44 | {% include links.md %} 45 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "3.2" 4 | - "3.3" 5 | - "3.4" 6 | # command to install dependencies 7 | install: 8 | - pip install -r requirements.txt 9 | # command to run tests 10 | before_script: 11 | - "awk -i inplace 'FNR == 4 {print \"carpentry: swc\"} {print}' index.html" 12 | - "awk -i inplace 'FNR == 5 {print \"venue: Foo\"} {print}' index.html" 13 | - "awk -i inplace 'FNR == 6 {print \"address: Room 123\"} {print}' index.html" 14 | - "awk -i inplace 'FNR == 7 {print \"country: us\"} {print}' index.html" 15 | - "awk -i inplace 'FNR == 8 {print \"language: en\"} {print}' index.html" 16 | - "awk -i inplace 'FNR == 9 {print \"latlng: 0,0\"} {print}' index.html" 17 | - "awk -i inplace 'FNR == 10 {print \"humandate: Jan 01-02, 2020\"} {print}' index.html" 18 | - "awk -i inplace 'FNR == 11 {print \"humantime: 9:00 am - 4:30 pm\"} {print}' index.html" 19 | - "awk -i inplace 'FNR == 12 {print \"startdate: 2020-01-01\"} {print}' index.html" 20 | - "awk -i inplace 'FNR == 13 {print \"enddate: 2020-01-01\"} {print}' index.html" 21 | - "awk -i inplace 'FNR == 14 {print \"instructor: [Foo]\"} {print}' index.html" 22 | - "awk -i inplace 'FNR == 15 {print \"helper: [Foo]\"} {print}' index.html" 23 | - "awk -i inplace 'FNR == 16 {print \"contact: [foo@bar.com]\"} {print}' index.html" 24 | - "awk -i inplace 'FNR == 17 {print \"collaborative_notes: http://foo.bar\"} {print}' index.html" 25 | - "awk -i inplace 'FNR == 18 {print \"eventbrite: 1234567890AB\"} {print}' index.html" 26 | script: 27 | - python bin/workshop_check.py . 28 | branches: 29 | only: 30 | - gh-pages 31 | -------------------------------------------------------------------------------- /setup/swc-installation-test-1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Test script to check required Python version. 4 | 5 | Execute this code at the command line by typing: 6 | 7 | python swc-installation-test-1.py 8 | 9 | How to get a command line: 10 | 11 | - On OSX run this with the Terminal application. 12 | 13 | - On Windows, go to the Start menu, select 'Run' and type 'cmd' 14 | (without the quotes) to run the 'cmd.exe' Windows Command Prompt. 15 | 16 | - On Linux, either use your login shell directly, or run one of a 17 | number of graphical terminals (e.g. 'xterm', 'gnome-terminal', ...). 18 | 19 | For some screen shots, see: 20 | 21 | http://software-carpentry.org/setup/terminal.html 22 | 23 | Run the script and follow the instructions it prints at the end. If 24 | you see an error saying that the 'python' command was not found, than 25 | you may not have any version of Python installed. See: 26 | 27 | http://www.python.org/download/releases/2.7.3/#download 28 | 29 | for installation instructions. 30 | 31 | This test is separate to avoid Python syntax errors parsing the more 32 | elaborate `swc-installation-test-2.py`. 33 | """ 34 | 35 | import sys as _sys 36 | 37 | 38 | __version__ = '0.2' 39 | 40 | 41 | def check(): 42 | if _sys.version_info < (3, 3): 43 | print('check for Python version (python):') 44 | print('outdated version of Python: ' + _sys.version) 45 | return False 46 | return True 47 | 48 | 49 | if __name__ == '__main__': 50 | if check(): 51 | print('Passed') 52 | else: 53 | print('Failed') 54 | print('Install a current version of Python 3!') 55 | print('http://continuum.io/downloads') 56 | _sys.exit(1) 57 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Abigail Cabunoc Mayes 2 | Abigail Cabunoc Mayes 3 | Amy Brown 4 | Andrew Walker 5 | Andy Boughton 6 | Ben Jolly 7 | Erin Becker 8 | Evan P. Williamson 9 | François Michonneau 10 | François Michonneau 11 | Greg Wilson 12 | James Allen 13 | Jonah Duckles 14 | Jonathan Cooper 15 | Lauren Michael 16 | Leighton Pritchard 17 | Lex Nederbragt 18 | Maneesha Sane 19 | Marianna Foos 20 | Maxim Belkin 21 | Mike Jackson 22 | Neal Davis 23 | Pauline Barmby 24 | Philipp Von Bieberstein 25 | Raniere Silva 26 | Raniere Silva 27 | Rémi Emonet 28 | Rémi Emonet 29 | Sarah Stevens 30 | Timothée Poisot 31 | Yashasvi Girdhar 32 | -------------------------------------------------------------------------------- /setup/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Testing Setup 4 | root: .. 5 | --- 6 | 7 | This directory contains scripts for testing your machine to make sure 8 | you have the software you'll need for your workshop installed. To use 9 | these scripts: 10 | 11 | 1. Download [swc-installation-test-1.py](swc-installation-test-1.py). 12 | 13 | 2. Run it from the shell: 14 | 15 | ~~~ 16 | $ python swc-installation-test-1.py 17 | Passed 18 | ~~~ 19 | 20 | 3. Download [swc-installation-test-2.py](swc-installation-test-2.py). 21 | 22 | 4. Run it from the shell: 23 | 24 | ~~~ 25 | $ python swc-installation-test-2.py 26 | check virtual-shell... pass 27 | ... 28 | Successes: 29 | 30 | virtual-shell Bourne Again Shell (bash) 4.2.37 31 | ... 32 | ~~~ 33 | 34 | If you see something like: 35 | 36 | ~~~ 37 | $ python swc-installation-test-2.py 38 | check virtual-shell... fail 39 | ... 40 | check for command line shell (virtual-shell) failed: 41 | command line shell (virtual-shell) requires at least one of the following dependencies 42 | For instructions on installing an up-to-date version, see 43 | http://software-carpentry.org/setup/ 44 | causes: 45 | check for Bourne Again Shell (bash) failed: 46 | could not find 'bash' executable for Bourne Again Shell (bash) 47 | For instructions on installing an up-to-date version, see 48 | http://software-carpentry.org/setup/ 49 | ... 50 | ~~~ 51 | 52 | follow the suggestions to try and install any missing software. For 53 | additional troubleshooting information, you can use the `--verbose` 54 | option: 55 | 56 | ~~~ 57 | $ python swc-installation-test-2.py --verbose 58 | check virtual-shell... fail 59 | ... 60 | ================== 61 | System information 62 | ================== 63 | os.name : posix 64 | ... 65 | ~~~ 66 | -------------------------------------------------------------------------------- /_includes/links.md: -------------------------------------------------------------------------------- 1 | [cc-by-human]: https://creativecommons.org/licenses/by/4.0/ 2 | [cc-by-legal]: https://creativecommons.org/licenses/by/4.0/legalcode 3 | [concept-maps]: http://carpentries.github.io/instructor-training/05-memory/ 4 | [email]: mailto:lessons@software-carpentry.org 5 | [contrib-covenant]: http://contributor-covenant.org/ 6 | [contributing]: {{ site.github.repository_url }}/blob/gh-pages/CONTRIBUTING.md 7 | [cran-checkpoint]: https://cran.r-project.org/web/packages/checkpoint/index.html 8 | [cran-knitr]: https://cran.r-project.org/web/packages/knitr/index.html 9 | [cran-stringr]: https://cran.r-project.org/web/packages/stringr/index.html 10 | [github-importer]: https://import.github.com/ 11 | [importer]: https://github.com/new/import 12 | [jekyll-collection]: https://jekyllrb.com/docs/collections/ 13 | [jekyll-install]: https://jekyllrb.com/docs/installation/ 14 | [jekyll-windows]: http://jekyll-windows.juthilo.com/ 15 | [jekyll]: https://jekyllrb.com/ 16 | [jupyter]: https://jupyter.org/ 17 | [mit-license]: http://opensource.org/licenses/mit-license.html 18 | [morea]: https://morea-framework.github.io/ 19 | [numfocus]: http://numfocus.org/ 20 | [osi]: http://opensource.org 21 | [pandoc]: https://pandoc.org/ 22 | [paper-now]: https://github.com/PeerJ/paper-now 23 | [python-gapminder]: https://swcarpentry.github.io/python-novice-gapminder/ 24 | [pyyaml]: https://pypi.python.org/pypi/PyYAML 25 | [r-markdown]: http://rmarkdown.rstudio.com/ 26 | [rstudio]: https://www.rstudio.com/ 27 | [ruby-install-guide]: https://www.ruby-lang.org/en/downloads/ 28 | [ruby-installer]: http://rubyinstaller.org/ 29 | [rubygems]: https://rubygems.org/pages/download/ 30 | [styles]: https://github.com/swcarpentry/styles/ 31 | [training]: http://swcarpentry.github.io/instructor-training/ 32 | [workshop-repo]: {{ site.workshop_repo }} 33 | [yaml]: http://yaml.org/ 34 | [coc]: https://software-carpentry.org/conduct/ 35 | [coc-reporting]: https://software-carpentry.org/CoC-reporting/ 36 | -------------------------------------------------------------------------------- /_layouts/base.html: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {% if site.carpentry == "swc" %} 17 | 18 | {% elsif site.carpentry == "dc" %} 19 | 20 | {% elsif site.carpentry == "lc" %} 21 | 22 | {% endif %} 23 | 24 | 25 | 29 | {{ site.title }}{% if page.title %}: {{ page.title }}{% endif %} 30 | 31 | 32 |
33 | {% include navbar.html %} 34 | {{ content }} 35 | {% if site.kind == "workshop" %} 36 | {% include workshop_footer.html %} 37 | {% else %} 38 | {% include lesson_footer.html %} 39 | {% endif %} 40 |
41 | {% include javascript.html %} 42 | 43 | 44 | -------------------------------------------------------------------------------- /_includes/carpentries.html: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | General description of Software and Data Carpentry. 3 | {% endcomment %} 4 |
5 |
6 | Software Carpentry logo 7 |
8 |
9 | Since 1998, 10 | Software Carpentry 11 | has been teaching researchers in science, engineering, medicine, and related disciplines 12 | the computing skills they need to get more done in less time and with less pain. 13 | Its volunteer instructors have run hundreds of events 14 | for thousands of learners in the past two and a half years. 15 |
16 |
17 |
18 |
19 |
20 | Data Carpentry logo 21 |
22 |
23 | Data Carpentry develops and teaches workshops on the fundamental data skills needed to conduct research. 24 | Its target audience is researchers who have little to no prior computational experience, 25 | and its lessons are domain specific, 26 | building on learners' existing knowledge to enable them to quickly apply skills learned to their own research. 27 |
28 |
29 |
30 |
31 |
32 | Library Carpentry logo 33 |
34 |
35 | Library Carpentry is made by librarians to help librarians 36 | automate repetitive, boring, error-prone tasks; 37 | create, maintain and analyse sustainable and reusable data; 38 | work effectively with IT and systems colleagues; 39 | better understand the use of software in research; 40 | and much more. 41 | Library Carpentry was the winner of the 2016 42 | British Library Labs Teaching and Learning Award. 43 |
44 |
45 | -------------------------------------------------------------------------------- /bin/chunk-options.R: -------------------------------------------------------------------------------- 1 | # These settings control the behavior of all chunks in the novice R materials. 2 | # For example, to generate the lessons with all the output hidden, simply change 3 | # `results` from "markup" to "hide". 4 | # For more information on available chunk options, see 5 | # http://yihui.name/knitr/options#chunk_options 6 | 7 | library("knitr") 8 | 9 | fix_fig_path <- function(pth) file.path("..", pth) 10 | 11 | 12 | ## We set the path for the figures globally below, so if we want to 13 | ## customize it for individual episodes, we can append a prefix to the 14 | ## global path. For instance, if we call knitr_fig_path("01-") in the 15 | ## first episode of the lesson, it will generate the figures in 16 | ## `fig/rmd-01-` 17 | knitr_fig_path <- function(prefix) { 18 | new_path <- paste0(opts_chunk$get("fig.path"), 19 | prefix) 20 | opts_chunk$set(fig.path = new_path) 21 | } 22 | 23 | ## We use the rmd- prefix for the figures generated by the lssons so 24 | ## they can be easily identified and deleted by `make clean-rmd`. The 25 | ## working directory when the lessons are generated is the root so the 26 | ## figures need to be saved in fig/, but when the site is generated, 27 | ## the episodes will be one level down. We fix the path using the 28 | ## `fig.process` option. 29 | 30 | opts_chunk$set(tidy = FALSE, results = "markup", comment = NA, 31 | fig.align = "center", fig.path = "fig/rmd-", 32 | fig.process = fix_fig_path) 33 | 34 | # The hooks below add html tags to the code chunks and their output so that they 35 | # are properly formatted when the site is built. 36 | 37 | hook_in <- function(x, options) { 38 | stringr::str_c("\n\n~~~\n", 39 | paste0(x, collapse="\n"), 40 | "\n~~~\n{: .language-r}\n\n") 41 | } 42 | 43 | hook_out <- function(x, options) { 44 | x <- gsub("\n$", "", x) 45 | stringr::str_c("\n\n~~~\n", 46 | paste0(x, collapse="\n"), 47 | "\n~~~\n{: .output}\n\n") 48 | } 49 | 50 | hook_error <- function(x, options) { 51 | x <- gsub("\n$", "", x) 52 | stringr::str_c("\n\n~~~\n", 53 | paste0(x, collapse="\n"), 54 | "\n~~~\n{: .error}\n\n") 55 | } 56 | 57 | knit_hooks$set(source = hook_in, output = hook_out, warning = hook_error, 58 | error = hook_error, message = hook_out) 59 | -------------------------------------------------------------------------------- /_includes/lc/syllabus.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Data Intro

4 |
    5 |
  • Intro to data
  • 6 |
  • Jargon busting
  • 7 |
  • Keyboard shortcuts
  • 8 |
  • Plain text formats
  • 9 |
  • Naming files
  • 10 |
  • Regular expressions
  • 11 |
  • Reference...
  • 12 |
13 |
14 |
15 |

The Unix Shell

16 |
    17 |
  • Files and directories
  • 18 |
  • History and tab completion
  • 19 |
  • Counting and sorting contents in files
  • 20 |
  • Pipes and redirection
  • 21 |
  • Mining or searching in files
  • 22 |
  • Reference...
  • 23 |
24 |
25 | 26 |
27 | 28 |
29 |
30 |

Version Control with Git

31 |
    32 |
  • Creating a repository
  • 33 |
  • Configuring git
  • 34 |
  • Recording changes to files: add, commit, ...
  • 35 |
  • Viewing state changes with status
  • 36 |
  • Working on the web: clone, pull, push, ...
  • 37 |
  • Where to host work, and why
  • 38 |
  • Reference...
  • 39 |
40 |
41 |
42 |
43 |

Open Refine

44 |
    45 |
  • Introduction to OpenRefine
  • 46 |
  • Importing data
  • 47 |
  • Basic functions
  • 48 |
  • Advanced Functions
  • 49 |
  • Reference...
  • 50 |
51 |
52 |
53 |
54 | 70 | -------------------------------------------------------------------------------- /_layouts/workshop.html: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | {% if page.redirect %} 21 | 22 | {% endif %} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | {% if site.carpentry == "swc" %} 32 | 33 | {% elsif site.carpentry == "dc" %} 34 | 35 | {% elsif site.carpentry == "lc" %} 36 | 37 | {% endif %} 38 | 39 | 40 | 44 | {{ page.venue }}: {{ page.humandate }} 45 | 46 | 47 |
48 | {% include navbar.html %} 49 | {% include workshop_ad.html %} 50 | {{ content }} 51 | {% include workshop_footer.html %} 52 |
53 | {% include javascript.html %} 54 | 55 | 56 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | #------------------------------------------------------------ 2 | # Values for this workshop. 3 | #------------------------------------------------------------ 4 | 5 | # Which carpentry is this ("swc", "dc", or "lc")? 6 | carpentry: "swc" 7 | 8 | # Overall title for pages. 9 | title: "Workshop Title" 10 | 11 | # Contact email address. 12 | email: lessons@software-carpentry.org 13 | 14 | #------------------------------------------------------------ 15 | # Generic settings (should not need to change). 16 | #------------------------------------------------------------ 17 | 18 | # What kind of thing is this ("workshop" or "lesson")? 19 | kind: "workshop" 20 | 21 | # Magic to make URLs resolve both locally and on GitHub. 22 | # See https://help.github.com/articles/repository-metadata-on-github-pages/. 23 | repository: / 24 | 25 | # Sites. 26 | amy_site: "https://amy.software-carpentry.org/workshops" 27 | lc_site: "https://librarycarpentry.github.io/" 28 | dc_site: "http://datacarpentry.org" 29 | swc_github: "https://github.com/swcarpentry" 30 | swc_site: "https://software-carpentry.org" 31 | swc_pages: "https://swcarpentry.github.io" 32 | template_repo: "https://github.com/swcarpentry/styles" 33 | example_repo: "https://github.com/swcarpentry/lesson-example" 34 | example_site: "https://swcarpentry.github.com/lesson-example" 35 | workshop_repo: "https://github.com/swcarpentry/workshop-template" 36 | workshop_site: "https://swcarpentry.github.io/workshop-template" 37 | training_site: "https://swcarpentry.github.io/instructor-training" 38 | swc_installer: "https://github.com/swcarpentry/windows-installer/releases/download/v0.3/SWCarpentryInstaller.exe" 39 | 40 | # Surveys. 41 | swc_pre_survey: "https://www.surveymonkey.com/r/swc_pre_workshop_v1?workshop_id=" 42 | swc_post_survey: "https://www.surveymonkey.com/r/swc_post_workshop_v1?workshop_id=" 43 | dc_pre_survey: "https://www.surveymonkey.com/r/dcpreworkshopassessment?workshop_id=" 44 | dc_post_survey: "https://www.surveymonkey.com/r/dcpostworkshopassessment?workshop_id=" 45 | 46 | # Start time in minutes (0 to be clock-independent, 540 to show a start at 09:00 am) 47 | start_time: 0 48 | 49 | # Specify that things in the episodes collection should be output. 50 | collections: 51 | episodes: 52 | output: true 53 | permalink: /:path/index.html 54 | extras: 55 | output: true 56 | permalink: /:path/index.html 57 | 58 | # Set the default layout for things in the episodes collection. 59 | defaults: 60 | - values: 61 | root: . 62 | - scope: 63 | path: "" 64 | type: episodes 65 | values: 66 | layout: episode 67 | root: .. 68 | 69 | # Files and directories that are not to be copied. 70 | exclude: 71 | - Makefile 72 | - bin 73 | 74 | # Turn off built-in syntax highlighting. 75 | highlighter: false 76 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: "Licenses" 4 | --- 5 | ## Instructional Material 6 | 7 | All Software Carpentry and Data Carpentry instructional material is 8 | made available under the [Creative Commons Attribution 9 | license][cc-by-human]. The following is a human-readable summary of 10 | (and not a substitute for) the [full legal text of the CC BY 4.0 11 | license][cc-by-legal]. 12 | 13 | You are free: 14 | 15 | * to **Share**---copy and redistribute the material in any medium or format 16 | * to **Adapt**---remix, transform, and build upon the material 17 | 18 | for any purpose, even commercially. 19 | 20 | The licensor cannot revoke these freedoms as long as you follow the 21 | license terms. 22 | 23 | Under the following terms: 24 | 25 | * **Attribution**---You must give appropriate credit (mentioning that 26 | your work is derived from work that is Copyright © Software 27 | Carpentry and, where practical, linking to 28 | http://software-carpentry.org/), provide a [link to the 29 | license][cc-by-human], and indicate if changes were made. You may do 30 | so in any reasonable manner, but not in any way that suggests the 31 | licensor endorses you or your use. 32 | 33 | **No additional restrictions**---You may not apply legal terms or 34 | technological measures that legally restrict others from doing 35 | anything the license permits. With the understanding that: 36 | 37 | Notices: 38 | 39 | * You do not have to comply with the license for elements of the 40 | material in the public domain or where your use is permitted by an 41 | applicable exception or limitation. 42 | * No warranties are given. The license may not give you all of the 43 | permissions necessary for your intended use. For example, other 44 | rights such as publicity, privacy, or moral rights may limit how you 45 | use the material. 46 | 47 | ## Software 48 | 49 | Except where otherwise noted, the example programs and other software 50 | provided by Software Carpentry and Data Carpentry are made available under the 51 | [OSI][osi]-approved 52 | [MIT license][mit-license]. 53 | 54 | Permission is hereby granted, free of charge, to any person obtaining 55 | a copy of this software and associated documentation files (the 56 | "Software"), to deal in the Software without restriction, including 57 | without limitation the rights to use, copy, modify, merge, publish, 58 | distribute, sublicense, and/or sell copies of the Software, and to 59 | permit persons to whom the Software is furnished to do so, subject to 60 | the following conditions: 61 | 62 | The above copyright notice and this permission notice shall be 63 | included in all copies or substantial portions of the Software. 64 | 65 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 66 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 67 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 68 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 69 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 70 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 71 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 72 | 73 | ## Trademark 74 | 75 | "Software Carpentry" and "Data Carpentry" and their respective logos 76 | are registered trademarks of [NumFOCUS][numfocus]. 77 | 78 | [cc-by-human]: https://creativecommons.org/licenses/by/4.0/ 79 | [cc-by-legal]: https://creativecommons.org/licenses/by/4.0/legalcode 80 | [mit-license]: http://opensource.org/licenses/mit-license.html 81 | [numfocus]: http://numfocus.org/ 82 | [osi]: http://opensource.org 83 | -------------------------------------------------------------------------------- /_includes/syllabus.html: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | Display syllabus in tabular form. 3 | Days are displayed if at least one episode has 'start = true'. 4 | {% endcomment %} 5 |
6 |

Schedule

7 | 8 | {% assign lesson_number = 0 %} 9 | {% assign day = 0 %} 10 | {% assign multiday = false %} 11 | {% for episode in site.episodes %} 12 | {% if episode.start %}{% assign multiday = true %}{% break %}{% endif %} 13 | {% endfor %} 14 | {% assign current = site.start_time %} 15 | 16 | 17 | 18 | {% if multiday %}{% endif %} 19 | 20 | 21 | 22 | 23 | {% for episode in site.episodes %} 24 | {% if episode.start %} {% comment %} Starting a new day? {% endcomment %} 25 | {% assign day = day | plus: 1 %} 26 | {% if day > 1 %} {% comment %} If about to start day 2 or later, show finishing time for previous day {% endcomment %} 27 | {% assign hours = current | divided_by: 60 %} 28 | {% assign minutes = current | modulo: 60 %} 29 | 30 | {% if multiday %}{% endif %} 31 | 32 | 33 | 34 | 35 | {% endif %} 36 | {% assign current = site.start_time %} {% comment %}Re-set start time of this episode to general daily start time {% endcomment %} 37 | {% endif %} 38 | {% assign hours = current | divided_by: 60 %} 39 | {% assign minutes = current | modulo: 60 %} 40 | 41 | {% if multiday %}{% endif %} 42 | 43 | 47 | 61 | 62 | {% assign current = current | plus: episode.teaching | plus: episode.exercises | plus: episode.break %} 63 | {% endfor %} 64 | {% assign hours = current | divided_by: 60 %} 65 | {% assign minutes = current | modulo: 60 %} 66 | 67 | {% if multiday %}{% endif %} 68 | 69 | 70 | 71 | 72 |
SetupDownload files required for the lesson
{% if hours < 10 %}0{% endif %}{{ hours }}:{% if minutes < 10 %}0{% endif %}{{ minutes }}Finish
{% if episode.start %}Day {{ day }}{% endif %}{% if hours < 10 %}0{% endif %}{{ hours }}:{% if minutes < 10 %}0{% endif %}{{ minutes }} 44 | {% assign lesson_number = lesson_number | plus: 1 %} 45 | {{ lesson_number }}. {{ episode.title }} 46 | 48 | {% if episode.break %} 49 | Break 50 | {% else %} 51 | {% if episode.questions %} 52 | {% for question in episode.questions %} 53 | {{question|markdownify|strip_html}} 54 | {% unless forloop.last %} 55 |
56 | {% endunless %} 57 | {% endfor %} 58 | {% endif %} 59 | {% endif %} 60 |
{% if hours < 10 %}0{% endif %}{{ hours }}:{% if minutes < 10 %}0{% endif %}{{ minutes }}Finish
73 | 74 |

75 | The actual schedule may vary slightly depending on the topics and exercises chosen by the instructor. 76 |

77 | 78 |
79 | -------------------------------------------------------------------------------- /_includes/sc/syllabus.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

The Unix Shell

4 |
    5 |
  • Files and directories
  • 6 |
  • History and tab completion
  • 7 |
  • Pipes and redirection
  • 8 |
  • Looping over files
  • 9 |
  • Creating and running shell scripts
  • 10 |
  • Finding things
  • 11 |
  • Reference...
  • 12 |
13 |
14 |
15 |

Programming in Python

16 |
    17 |
  • Using libraries
  • 18 |
  • Working with arrays
  • 19 |
  • Reading and plotting data
  • 20 |
  • Creating and using functions
  • 21 |
  • Loops and conditionals
  • 22 |
  • Defensive programming
  • 23 |
  • Using Python from the command line
  • 24 |
  • Reference...
  • 25 |
26 |
27 | 40 | 53 |
54 | 55 |
56 |
57 |

Version Control with Git

58 |
    59 |
  • Creating a repository
  • 60 |
  • Recording changes to files: add, commit, ...
  • 61 |
  • Viewing changes: status, diff, ...
  • 62 |
  • Ignoring files
  • 63 |
  • Working on the web: clone, pull, push, ...
  • 64 |
  • Resolving conflicts
  • 65 |
  • Open licenses
  • 66 |
  • Where to host work, and why
  • 67 |
  • Reference...
  • 68 |
69 |
70 |
71 |

Managing Data with SQL

72 |
    73 |
  • Reading and sorting data
  • 74 |
  • Filtering with where
  • 75 |
  • Calculating new values on the fly
  • 76 |
  • Handling missing values
  • 77 |
  • Combining values using aggregation
  • 78 |
  • Combining information from multiple tables using join
  • 79 |
  • Creating, modifying, and deleting data
  • 80 |
  • Programming with databases
  • 81 |
  • Reference...
  • 82 |
83 |
84 | 95 |
96 | -------------------------------------------------------------------------------- /_includes/dc/syllabus.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

The Unix Shell

4 |
    5 |
  • Files and directories
  • 6 |
  • History and tab completion
  • 7 |
  • Pipes and redirection
  • 8 |
  • Looping over files
  • 9 |
  • Creating and running shell scripts
  • 10 |
  • Finding things
  • 11 |
  • Reference...
  • 12 |
13 |
14 |
15 |

Programming in Python

16 |
    17 |
  • Using libraries
  • 18 |
  • Working with arrays
  • 19 |
  • Reading and plotting data
  • 20 |
  • Creating and using functions
  • 21 |
  • Loops and conditionals
  • 22 |
  • Defensive programming
  • 23 |
  • Using Python from the command line
  • 24 |
  • Reference...
  • 25 |
26 |
27 | 40 | 53 |
54 | 55 |
56 |
57 |

Version Control with Git

58 |
    59 |
  • Creating a repository
  • 60 |
  • Recording changes to files: add, commit, ...
  • 61 |
  • Viewing changes: status, diff, ...
  • 62 |
  • Ignoring files
  • 63 |
  • Working on the web: clone, pull, push, ...
  • 64 |
  • Resolving conflicts
  • 65 |
  • Open licenses
  • 66 |
  • Where to host work, and why
  • 67 |
  • Reference...
  • 68 |
69 |
70 |
71 |

Managing Data with SQL

72 |
    73 |
  • Reading and sorting data
  • 74 |
  • Filtering with where
  • 75 |
  • Calculating new values on the fly
  • 76 |
  • Handling missing values
  • 77 |
  • Combining values using aggregation
  • 78 |
  • Combining information from multiple tables using join
  • 79 |
  • Creating, modifying, and deleting data
  • 80 |
  • Programming with databases
  • 81 |
  • Reference...
  • 82 |
83 |
84 | 96 |
97 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ## ======================================== 2 | ## Commands for both workshop and lesson websites. 3 | 4 | # Settings 5 | MAKEFILES=Makefile $(wildcard *.mk) 6 | JEKYLL=jekyll 7 | PARSER=bin/markdown_ast.rb 8 | DST=_site 9 | 10 | # Controls 11 | .PHONY : commands clean files 12 | .NOTPARALLEL: 13 | all : commands 14 | 15 | ## commands : show all commands. 16 | commands : 17 | @grep -h -E '^##' ${MAKEFILES} | sed -e 's/## //g' 18 | 19 | ## serve : run a local server. 20 | serve : lesson-md 21 | ${JEKYLL} serve 22 | 23 | ## site : build files but do not run a server. 24 | site : lesson-md 25 | ${JEKYLL} build 26 | 27 | # repo-check : check repository settings. 28 | repo-check : 29 | @bin/repo_check.py -s . 30 | 31 | ## clean : clean up junk files. 32 | clean : 33 | @rm -rf ${DST} 34 | @rm -rf .sass-cache 35 | @rm -rf bin/__pycache__ 36 | @find . -name .DS_Store -exec rm {} \; 37 | @find . -name '*~' -exec rm {} \; 38 | @find . -name '*.pyc' -exec rm {} \; 39 | 40 | ## clean-rmd : clean intermediate R files (that need to be committed to the repo). 41 | clear-rmd : 42 | @rm -rf ${RMD_DST} 43 | @rm -rf fig/rmd-* 44 | 45 | ## ---------------------------------------- 46 | ## Commands specific to workshop websites. 47 | 48 | .PHONY : workshop-check 49 | 50 | ## workshop-check : check workshop homepage. 51 | workshop-check : 52 | @bin/workshop_check.py . 53 | 54 | ## ---------------------------------------- 55 | ## Commands specific to lesson websites. 56 | 57 | .PHONY : lesson-check lesson-md lesson-files lesson-fixme 58 | 59 | # RMarkdown files 60 | RMD_SRC = $(wildcard _episodes_rmd/??-*.Rmd) 61 | RMD_DST = $(patsubst _episodes_rmd/%.Rmd,_episodes/%.md,$(RMD_SRC)) 62 | 63 | # Lesson source files in the order they appear in the navigation menu. 64 | MARKDOWN_SRC = \ 65 | index.md \ 66 | CONDUCT.md \ 67 | setup.md \ 68 | $(sort $(wildcard _episodes/*.md)) \ 69 | reference.md \ 70 | $(sort $(wildcard _extras/*.md)) \ 71 | LICENSE.md 72 | 73 | # Generated lesson files in the order they appear in the navigation menu. 74 | HTML_DST = \ 75 | ${DST}/index.html \ 76 | ${DST}/conduct/index.html \ 77 | ${DST}/setup/index.html \ 78 | $(patsubst _episodes/%.md,${DST}/%/index.html,$(sort $(wildcard _episodes/*.md))) \ 79 | ${DST}/reference/index.html \ 80 | $(patsubst _extras/%.md,${DST}/%/index.html,$(sort $(wildcard _extras/*.md))) \ 81 | ${DST}/license/index.html 82 | 83 | ## lesson-md : convert Rmarkdown files to markdown 84 | lesson-md : ${RMD_DST} 85 | 86 | # Use of .NOTPARALLEL makes rule execute only once 87 | ${RMD_DST} : ${RMD_SRC} 88 | @bin/knit_lessons.sh ${RMD_SRC} 89 | 90 | ## lesson-check : validate lesson Markdown. 91 | lesson-check : 92 | @bin/lesson_check.py -s . -p ${PARSER} -r _includes/links.md 93 | 94 | ## lesson-check-all : validate lesson Markdown, checking line lengths and trailing whitespace. 95 | lesson-check-all : 96 | @bin/lesson_check.py -s . -p ${PARSER} -l -w 97 | 98 | ## unittest : run unit tests on checking tools. 99 | unittest : 100 | python bin/test_lesson_check.py 101 | 102 | ## lesson-files : show expected names of generated files for debugging. 103 | lesson-files : 104 | @echo 'RMD_SRC:' ${RMD_SRC} 105 | @echo 'RMD_DST:' ${RMD_DST} 106 | @echo 'MARKDOWN_SRC:' ${MARKDOWN_SRC} 107 | @echo 'HTML_DST:' ${HTML_DST} 108 | 109 | ## lesson-fixme : show FIXME markers embedded in source files. 110 | lesson-fixme : 111 | @fgrep -i -n FIXME ${MARKDOWN_SRC} || true 112 | 113 | #------------------------------------------------------------------------------- 114 | # Include extra commands if available. 115 | #------------------------------------------------------------------------------- 116 | 117 | -include commands.mk 118 | -------------------------------------------------------------------------------- /_includes/navbar.html: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | Lesson navigation bar. 3 | {% endcomment %} 4 | 87 | -------------------------------------------------------------------------------- /assets/css/syntax.css: -------------------------------------------------------------------------------- 1 | .highlight .hll { background-color: #ffffcc } 2 | .highlight { background: #f8f8f8; } 3 | .highlight .c { color: #408080; font-style: italic } /* Comment */ 4 | .highlight .err { border: 1px solid #FF0000 } /* Error */ 5 | .highlight .k { color: #008000; font-weight: bold } /* Keyword */ 6 | .highlight .o { color: #666666 } /* Operator */ 7 | .highlight .ch { color: #408080; font-style: italic } /* Comment.Hashbang */ 8 | .highlight .cm { color: #408080; font-style: italic } /* Comment.Multiline */ 9 | .highlight .cp { color: #BC7A00 } /* Comment.Preproc */ 10 | .highlight .cpf { color: #408080; font-style: italic } /* Comment.PreprocFile */ 11 | .highlight .c1 { color: #408080; font-style: italic } /* Comment.Single */ 12 | .highlight .cs { color: #408080; font-style: italic } /* Comment.Special */ 13 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 14 | .highlight .ge { font-style: italic } /* Generic.Emph */ 15 | .highlight .gr { color: #FF0000 } /* Generic.Error */ 16 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 17 | .highlight .gi { color: #00A000 } /* Generic.Inserted */ 18 | .highlight .go { color: #888888 } /* Generic.Output */ 19 | .highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ 20 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 21 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 22 | .highlight .gt { color: #0044DD } /* Generic.Traceback */ 23 | .highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ 24 | .highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ 25 | .highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ 26 | .highlight .kp { color: #008000 } /* Keyword.Pseudo */ 27 | .highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ 28 | .highlight .kt { color: #B00040 } /* Keyword.Type */ 29 | .highlight .m { color: #666666 } /* Literal.Number */ 30 | .highlight .s { color: #BA2121 } /* Literal.String */ 31 | .highlight .na { color: #7D9029 } /* Name.Attribute */ 32 | .highlight .nb { color: #008000 } /* Name.Builtin */ 33 | .highlight .nc { color: #0000FF; font-weight: bold } /* Name.Class */ 34 | .highlight .no { color: #880000 } /* Name.Constant */ 35 | .highlight .nd { color: #AA22FF } /* Name.Decorator */ 36 | .highlight .ni { color: #999999; font-weight: bold } /* Name.Entity */ 37 | .highlight .ne { color: #D2413A; font-weight: bold } /* Name.Exception */ 38 | .highlight .nf { color: #0000FF } /* Name.Function */ 39 | .highlight .nl { color: #A0A000 } /* Name.Label */ 40 | .highlight .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */ 41 | .highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ 42 | .highlight .nv { color: #19177C } /* Name.Variable */ 43 | .highlight .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */ 44 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 45 | .highlight .mb { color: #666666 } /* Literal.Number.Bin */ 46 | .highlight .mf { color: #666666 } /* Literal.Number.Float */ 47 | .highlight .mh { color: #666666 } /* Literal.Number.Hex */ 48 | .highlight .mi { color: #666666 } /* Literal.Number.Integer */ 49 | .highlight .mo { color: #666666 } /* Literal.Number.Oct */ 50 | .highlight .sa { color: #BA2121 } /* Literal.String.Affix */ 51 | .highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ 52 | .highlight .sc { color: #BA2121 } /* Literal.String.Char */ 53 | .highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ 54 | .highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ 55 | .highlight .s2 { color: #BA2121 } /* Literal.String.Double */ 56 | .highlight .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */ 57 | .highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ 58 | .highlight .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */ 59 | .highlight .sx { color: #008000 } /* Literal.String.Other */ 60 | .highlight .sr { color: #BB6688 } /* Literal.String.Regex */ 61 | .highlight .s1 { color: #BA2121 } /* Literal.String.Single */ 62 | .highlight .ss { color: #19177C } /* Literal.String.Symbol */ 63 | .highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ 64 | .highlight .fm { color: #0000FF } /* Name.Function.Magic */ 65 | .highlight .vc { color: #19177C } /* Name.Variable.Class */ 66 | .highlight .vg { color: #19177C } /* Name.Variable.Global */ 67 | .highlight .vi { color: #19177C } /* Name.Variable.Instance */ 68 | .highlight .vm { color: #19177C } /* Name.Variable.Magic */ 69 | .highlight .il { color: #666666 } /* Literal.Number.Integer.Long */ 70 | -------------------------------------------------------------------------------- /assets/css/lesson.scss: -------------------------------------------------------------------------------- 1 | --- 2 | --- 3 | 4 | //---------------------------------------- 5 | // Colors. 6 | //---------------------------------------- 7 | 8 | // branding 9 | $color-brand: #2b3990 !default; 10 | 11 | // code boxes 12 | $color-error: #bd2c00 !default; 13 | $color-output: #303030 !default; 14 | $color-source: #6e5494 !default; 15 | 16 | // blockquotes 17 | $color-callout: #f4fd9c !default; 18 | $color-challenge: #eec275 !default; 19 | $color-checklist: #dfd2a0 !default; 20 | $color-discussion: #eec275 !default; 21 | $color-keypoints: #7ae78e !default; 22 | $color-objectives: #daee84 !default; 23 | $color-prereq: #9cd6dc !default; 24 | $color-solution: #ded4b9 !default; 25 | $color-testimonial: #fc8dc1 !default; 26 | 27 | //---------------------------------------- 28 | // Specialized code blocks. 29 | //---------------------------------------- 30 | 31 | @mixin cdSetup($color) { 32 | color: $color; 33 | border-left: solid 5px $color; 34 | margin-bottom: 0px; 35 | border-radius: 4px 0 0 4px; 36 | } 37 | 38 | .error { @include cdSetup($color-error); } 39 | .output { @include cdSetup($color-output); } 40 | .source { @include cdSetup($color-source); } 41 | 42 | .bash { @include cdSetup($color-source); } 43 | .make { @include cdSetup($color-source); } 44 | .matlab { @include cdSetup($color-source); } 45 | .python { @include cdSetup($color-source); } 46 | .r { @include cdSetup($color-source); } 47 | .sql { @include cdSetup($color-source); } 48 | 49 | .error pre, 50 | .output pre, 51 | .source pre, 52 | .bash pre, 53 | .make pre, 54 | .matlab pre, 55 | .python pre, 56 | .r pre, 57 | .sql pre { 58 | border-radius: 0 4px 4px 0; 59 | } 60 | 61 | //---------------------------------------- 62 | // Specialized blockquote environments for learning objectives, callouts, etc. 63 | //---------------------------------------- 64 | 65 | $codeblock-padding: 5px !default; 66 | 67 | @mixin bkSetup($color, $glyph) { 68 | 69 | $gradientcolor1: $color; 70 | $gradientcolor2: scale-color($color, $lightness: 10%); 71 | 72 | padding-left: $codeblock-padding; 73 | padding-top: 0; 74 | padding-bottom: 0; 75 | padding-right: 0; 76 | border: 1px solid; 77 | border-color: $color; 78 | padding-bottom: $codeblock-padding; 79 | 80 | h2 { 81 | padding-top: $codeblock-padding; 82 | padding-bottom: $codeblock-padding; 83 | font-size: 20px; 84 | background: linear-gradient(to bottom, $gradientcolor1, $gradientcolor2); 85 | border-color: $color; 86 | margin-top: 0px; 87 | margin-left: -$codeblock-padding; // to move back to the left margin of the enclosing blockquote 88 | } 89 | h2:before { 90 | font-family: 'Glyphicons Halflings'; 91 | content: $glyph; 92 | float: left; 93 | padding-left: $codeblock-padding; 94 | padding-right: $codeblock-padding; 95 | display: inline-block; 96 | -webkit-font-smoothing: antialiased; 97 | } 98 | 99 | } 100 | 101 | .callout{ @include bkSetup($color-callout, "\e146"); } 102 | .challenge{ @include bkSetup($color-challenge, "\270f"); } 103 | .checklist{ @include bkSetup($color-checklist, "\e067"); } 104 | .discussion{ @include bkSetup($color-discussion, "\e123"); } 105 | .keypoints{ @include bkSetup($color-keypoints, "\e101"); } 106 | .objectives{ @include bkSetup($color-objectives, "\e085"); } 107 | .prereq{ @include bkSetup($color-prereq, "\e124"); } 108 | .solution{ @include bkSetup($color-solution, "\e105"); } 109 | .testimonial{ @include bkSetup($color-testimonial, "\e143"); } 110 | 111 | //---------------------------------------- 112 | // Override Bootstrap settings. 113 | //---------------------------------------- 114 | 115 | code { 116 | padding: 2px 5px; 117 | color: #3d90d9; 118 | background-color: #e7e7e7; 119 | } 120 | 121 | img { 122 | max-width: 100%; 123 | } 124 | 125 | //---------------------------------------- 126 | // Miscellaneous. 127 | //---------------------------------------- 128 | 129 | .maintitle { 130 | text-align: center; 131 | } 132 | 133 | .footertext { 134 | text-align: center; 135 | } 136 | 137 | img.navbar-logo { 138 | height: 40px; // synchronize with height of navbar 139 | padding-top: 5px; 140 | padding-right: 10px; 141 | } 142 | 143 | div.branding { 144 | color: $color-brand; 145 | } 146 | 147 | ul, 148 | ol { 149 | padding-left: 2em; 150 | } 151 | 152 | span.fold-unfold { 153 | margin-left: 1em; 154 | opacity: 0.5; 155 | } 156 | 157 | 158 | //---------------------------------------- 159 | // keyboard key style, from StackExchange. 160 | //---------------------------------------- 161 | 162 | kbd { 163 | display: inline-block; 164 | margin: 0 .1em; 165 | padding: .1em .6em; 166 | font-family: Arial,"Helvetica Neue",Helvetica,sans-serif; 167 | font-size: 11px; 168 | line-height: 1.4; 169 | color: #242729; 170 | text-shadow: 0 1px 0 #FFF; 171 | background-color: #e1e3e5; 172 | border: 1px solid #adb3b9; 173 | border-radius: 3px; 174 | box-shadow: 0 1px 0 rgba(12,13,14,0.2), 0 0 0 2px #FFF inset; 175 | white-space: nowrap; 176 | font-style: normal; 177 | } 178 | -------------------------------------------------------------------------------- /bin/repo_check.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Check repository settings. 5 | """ 6 | 7 | from __future__ import print_function 8 | import sys 9 | import os 10 | from subprocess import Popen, PIPE 11 | import re 12 | from optparse import OptionParser 13 | 14 | from util import Reporter, load_yaml, require 15 | 16 | # Import this way to produce a more useful error message. 17 | try: 18 | import requests 19 | except ImportError: 20 | print('Unable to import requests module: please install requests', file=sys.stderr) 21 | sys.exit(1) 22 | 23 | 24 | # Pattern to match Git command-line output for remotes => (user name, project name). 25 | P_GIT_REMOTE = re.compile(r'upstream\s+[^:]+:([^/]+)/([^.]+)\.git\s+\(fetch\)') 26 | 27 | # Repository URL format string. 28 | F_REPO_URL = 'https://github.com/{0}/{1}/' 29 | 30 | # Pattern to match repository URLs => (user name, project name) 31 | P_REPO_URL = re.compile(r'https?://github\.com/([^.]+)/([^/]+)/?') 32 | 33 | # API URL format string. 34 | F_API_URL = 'https://api.github.com/repos/{0}/{1}/labels' 35 | 36 | # Expected labels and colors. 37 | EXPECTED = { 38 | 'bug' : 'bd2c00', 39 | 'discussion' : 'fc8dc1', 40 | 'enhancement' : '9cd6dc', 41 | 'help-wanted' : 'f4fd9c', 42 | 'instructor-training' : '6e5494', 43 | 'newcomer-friendly' : 'eec275', 44 | 'question' : '808040', 45 | 'template-and-tools' : '2b3990', 46 | 'work-in-progress' : '7ae78e' 47 | } 48 | 49 | 50 | def main(): 51 | """ 52 | Main driver. 53 | """ 54 | 55 | args = parse_args() 56 | reporter = Reporter() 57 | repo_url = get_repo_url(args.source_dir, args.repo_url) 58 | check_labels(reporter, repo_url) 59 | reporter.report() 60 | 61 | 62 | def parse_args(): 63 | """ 64 | Parse command-line arguments. 65 | """ 66 | 67 | parser = OptionParser() 68 | parser.add_option('-r', '--repo', 69 | default=None, 70 | dest='repo_url', 71 | help='repository URL') 72 | parser.add_option('-s', '--source', 73 | default=os.curdir, 74 | dest='source_dir', 75 | help='source directory') 76 | 77 | args, extras = parser.parse_args() 78 | require(not extras, 79 | 'Unexpected trailing command-line arguments "{0}"'.format(extras)) 80 | 81 | return args 82 | 83 | 84 | def get_repo_url(source_dir, repo_url): 85 | """ 86 | Figure out which repository to query. 87 | """ 88 | 89 | # Explicitly specified. 90 | if repo_url is not None: 91 | return repo_url 92 | 93 | # Guess. 94 | cmd = 'git remote -v' 95 | p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, close_fds=True, universal_newlines=True) 96 | stdout_data, stderr_data = p.communicate() 97 | stdout_data = stdout_data.split('\n') 98 | matches = [P_GIT_REMOTE.match(line) for line in stdout_data] 99 | matches = [m for m in matches if m is not None] 100 | require(len(matches) == 1, 101 | 'Unexpected output from git remote command: "{0}"'.format(matches)) 102 | 103 | username = matches[0].group(1) 104 | require(username, 'empty username in git remote output {0}'.format(matches[0])) 105 | 106 | project_name = matches[0].group(2) 107 | require(username, 'empty project name in git remote output {0}'.format(matches[0])) 108 | 109 | url = F_REPO_URL.format(username, project_name) 110 | return url 111 | 112 | 113 | def check_labels(reporter, repo_url): 114 | """ 115 | Check labels in repository. 116 | """ 117 | 118 | actual = get_labels(repo_url) 119 | extra = set(actual.keys()) - set(EXPECTED.keys()) 120 | 121 | reporter.check(not extra, 122 | None, 123 | 'Extra label(s) in repository {0}: {1}', 124 | repo_url, ', '.join(sorted(extra))) 125 | 126 | missing = set(EXPECTED.keys()) - set(actual.keys()) 127 | reporter.check(not missing, 128 | None, 129 | 'Missing label(s) in repository {0}: {1}', 130 | repo_url, ', '.join(sorted(missing))) 131 | 132 | overlap = set(EXPECTED.keys()).intersection(set(actual.keys())) 133 | for name in sorted(overlap): 134 | reporter.check(EXPECTED[name] == actual[name], 135 | None, 136 | 'Color mis-match for label {0} in {1}: expected {2}, found {3}', 137 | name, repo_url, EXPECTED[name], actual[name]) 138 | 139 | 140 | def get_labels(repo_url): 141 | """ 142 | Get actual labels from repository. 143 | """ 144 | 145 | m = P_REPO_URL.match(repo_url) 146 | require(m, 'repository URL {0} does not match expected pattern'.format(repo_url)) 147 | 148 | username = m.group(1) 149 | require(username, 'empty username in repository URL {0}'.format(repo_url)) 150 | 151 | project_name = m.group(2) 152 | require(username, 'empty project name in repository URL {0}'.format(repo_url)) 153 | 154 | url = F_API_URL.format(username, project_name) 155 | r = requests.get(url) 156 | require(r.status_code == 200, 157 | 'Request for {0} failed with {1}'.format(url, r.status_code)) 158 | 159 | result = {} 160 | for entry in r.json(): 161 | result[entry['name']] = entry['color'] 162 | return result 163 | 164 | 165 | if __name__ == '__main__': 166 | main() 167 | -------------------------------------------------------------------------------- /assets/img/swc-icon-blue.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /_extras/design.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Background and Design 4 | permalink: /design/ 5 | --- 6 | There are a few things you need to know in order to understand why we 7 | do things the way we do. Some of them are specific to GitHub, rather 8 | than Git itself. 9 | 10 | 1. Git uses the term "clone" to mean "a copy of a repository". 11 | GitHub uses the term "fork" to mean, "a copy of a GitHub-hosted 12 | repo that is also hosted on GitHub", and the term "clone" to mean 13 | "a copy of a GitHub-hosted repo that's located on someone else's 14 | machine". In both cases, the duplicate has a remote called 15 | `origin` that points to the original repo; other remotes can be 16 | added manually. 17 | 18 | 2. A user on GitHub can only have one fork of a particular repo. 19 | This is a problem for us because an instructor may be involved in 20 | several workshops, each of which has its own website repo. Those 21 | website repositories ought to be forks of this one, but since 22 | GitHub doesn't allow that, we have to use `import.github.com`. 23 | 24 | 3. If a repository has a file called `README.md` in its root 25 | directory, GitHub displays the contents of that file on the 26 | repository's home page. 27 | 28 | 4. If a repository has a branch called `gh-pages` (which stands for 29 | "GitHub pages"), then GitHub uses the HTML and Markdown files in 30 | that branch to create a website for the repository. If the 31 | repository's URL is `http://github.com/darwin/finches`, the URL 32 | for the website is `http://darwin.github.io/finches`. 33 | 34 | 5. If an HTML or Markdown file has a header consisting of three 35 | dashes, some data about the page, and three more dashes: 36 | 37 | ~~~ 38 | --- 39 | key: value 40 | other_key: other_value 41 | --- 42 | stuff in the page 43 | ~~~ 44 | 45 | then GitHub doesn't just copy the file over verbatim. Instead, it 46 | runs the file through a translator called [Jekyll][jekyll] that 47 | looks for specially-formatted commands embedded in the file and 48 | uses them to fill in the page. 49 | 50 | 6. Commands can be embedded in the body of a page. One is 51 | `{% raw %}{% include something.html %}{% endraw %}`, which tells 52 | Jekyll to copy the contents of `something.html` into the file 53 | being translated; this is used to create standard headers and 54 | footers for pages. Another is `{{variable}}`: when Jekyll sees 55 | this, it replaces it with the value of `variable`. This is used 56 | to insert things like a contact email address and the URL for our 57 | Twitter account. 58 | 59 | 7. Jekyll gets variables from two places: a file called `_config.yml` 60 | located in the repo's root directory, and the header of each 61 | individual page. Variables from `_config.yml` are put in an 62 | object called `site`, and referred to as `site.variable`, so that 63 | (for example) `{{site.swc_site}}` in a page is replaced by the URL 64 | of the main Software Carpentry web site. Variables from the 65 | page's header are put in an object called `page`, and referred to 66 | as `page.variable`, so if a page's header defines a variable 67 | called `venue`, `{{page.venue}}` is replaced by "Euphoric State 68 | University" (or whatever value the variable has). 69 | 70 | 8. If a page uses `{% raw %}{% include something.html %}{% endraw %}` 71 | to include a snippet of HTML, Jekyll looks in a directory called 72 | `_includes` to find `something.html`. It always looks there, and 73 | nowhere else, so anything we want people to be able to include in 74 | their pages has to be stored in `_includes`. 75 | 76 | 9. A repository can have another special directory called `_layouts`. 77 | If a page like `index.html` has a variable called `layout`, and 78 | that variable's value is `standard.html`, Jekyll loads the file 79 | `_layouts/standard.html` and copies the content of `index.html` 80 | into it, then expands the result. This is used to give the pages 81 | in a site a uniform appearance. 82 | We have created two layouts for workshop pages: 83 | 84 | * `workshop.html` is used for workshops' home pages, and is the 85 | layout for the `index.html` page in your repo's root directory. 86 | That `index.html` page's header must define several variables as 87 | specified in the the customization instructions in order for 88 | your workshop to be included in our main website. 89 | 90 | * `page.html` is used for any other pages you want to create. 91 | **Note:** if you create extra pages, you *must* edit the values 92 | in the top section of `_config.yml` as described in 93 | [the lesson template documentation]({{ site.example_site }}). 94 | 95 | ## Extra Directories 96 | 97 | This workshop template shares resources with the template used for 98 | Carpentry lessons. As a result, your workshop website's repository 99 | contains directories that most workshops don't need, but which can be 100 | used to store extra material when necessary: 101 | 102 | * `_extras/`: extra pages (like this one). 103 | * `_episodes/`: lesson episodes (which workshops usually don't have). 104 | * `_episodes_rmd/`: R Markdown lesson episodes (if any). 105 | * `code/`: for code samples. 106 | * `data/`: for data files. 107 | * `fig/`: for figures and other images. 108 | * `files/`: for miscellaneous files. 109 | 110 | For more information on these, please see [the documentation for the 111 | lesson template]({{ site.example_site }}). 112 | 113 | [jekyll]: https://jekyllrb.com/ 114 | -------------------------------------------------------------------------------- /assets/img/dc-icon-black.svg: -------------------------------------------------------------------------------- 1 | 2 | 16 | 18 | 19 | 21 | image/svg+xml 22 | 24 | 25 | 26 | 27 | 28 | 30 | 54 | 59 | 64 | 69 | 74 | 75 | -------------------------------------------------------------------------------- /_extras/faq.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: FAQ 4 | permalink: /faq/ 5 | --- 6 | 7 | ## General 8 | 9 | *Where can I get help?* 10 | : Mail us at [{{site.email}}](mailto:{{site.email}}), 11 | or join our [discussion list]({{site.swc_site}}/join/) 12 | and ask for help there. 13 | 14 | *What if I can't wait?* 15 | : Run `make workshop-check` to run the workshop homepage checking program on `index.html`. 16 | 17 | *Where can I report problems or suggest improvements?* 18 | : Please file an issue against [{{site.workshop_repo}}](this repository) 19 | or [mail us](mailto:{{site.email}}). 20 | 21 | *Why does the workshop repository have to be created by importing rather than forking?* 22 | : Because any particular user can only have one fork of a repository, 23 | but instructors frequently need to work on several workshops at once. 24 | 25 | *Why do I have to be logged in before I start the import?* 26 | : It's a known issue with GitHub's importer. 27 | 28 | *Why does the workshop repository name have to follow the `YYYY-MM-DD-site` pattern?* 29 | : This makes it easy for coordinators to track workshops. 30 | There are plans to move that coordination into [AMY][amy], 31 | but until that happens this pattern makes it easy to sort workshops 32 | by date without requiring an additional start-date column. 33 | **Note: `YYYY-MM-DD` should be the start date of the workshop.** 34 | 35 | *Why use the `gh-pages` branch instead of `master`?* 36 | : Because [GitHub automatically publishes `gh-pages`][github-pages] as a website. 37 | 38 | *Why use Jekyll? Why not some other markup language and some other converter?* 39 | : Because it's the only tool supported by GitHub Pages. 40 | 41 | *Where should pages go if multiple workshops are running at a site simultaneously?* 42 | : Use subdirectories like `2015-07-01-esu/beginners`, 43 | so that repository names always follow our four-part convention. 44 | 45 | *What if I want to add more values to `index.html`, like `address1` and `address2` for different rooms on different days?* 46 | : Go ahead, 47 | but you *must* have the variables described in the customization instructions. 48 | 49 | *What is the "Windows installer"?* 50 | : We have built a small installation helper for Windows 51 | that installs nano and SQLite, adds R to the path, and so on. 52 | It is maintained in 53 | 54 | which also has an up-to-date description of what it actually does. 55 | The latest version is always available at 56 | , 57 | and contributions are always welcome. 58 | 59 | ## Debugging 60 | 61 | *Help, my website is not updating!* 62 | : Ensure that strings in the header of `index.html` are enclosed in quotations `"`. 63 | Special characters such as `"&"` may render correctly on your local machine 64 | but cause rendering to fail silently on GitHub. 65 | 66 | *Eventbrite registration isn't showing up on the workshop's home page.* 67 | : First check that you have something like: 68 | 69 | ~~~ 70 | eventbrite: 1234567890AB 71 | ~~~ 72 | 73 | in the YAML header of `index.html`. 74 | If the YAML header is set properly you may be accessing 75 | `file:///home/to/workshop/directory/_site/index.html` directly. 76 | Instead, 77 | please run 78 | 79 | ~~~ 80 | $ make serve 81 | ~~~ 82 | 83 | and look at `http://localhost:4000` in your browser 84 | (or push your changes to GitHub and view your page there). 85 | 86 | *What do I do if I see a `invalid byte sequence in ...` error when I run `tools/check`?* 87 | : Your computer is telling you that it doesn't understand some of the characters you're using. 88 | Declare your locale to be `en_US.UTF-8` in your shell: 89 | 90 | ~~~ 91 | $ export LC_ALL=en_US.UTF-8 92 | $ export LANG=en_US.UTF-8 93 | ~~~ 94 | 95 | *What do I do if I get a "can't convert nil into String" error?* 96 | : On some Linux distributions (e.g, Ubuntu 14.04), you may get this error: 97 | 98 | ~~~ 99 | $ ./tools/preview 100 | /usr/lib/ruby/1.9.1/rubygems/custom_require.rb:36:in `require': iconv will be deprecated in the future, use String#encode instead. 101 | /usr/lib/ruby/1.9.1/time.rb:265:in `_parse': can't convert nil into String (TypeError) 102 | from /usr/lib/ruby/1.9.1/time.rb:265:in `parse' 103 | from /usr/bin/jekyll:95:in `block (2 levels) in
' 104 | from /usr/lib/ruby/1.9.1/optparse.rb:1391:in `call' 105 | from /usr/lib/ruby/1.9.1/optparse.rb:1391:in `block in parse_in_order' 106 | from /usr/lib/ruby/1.9.1/optparse.rb:1347:in `catch' 107 | from /usr/lib/ruby/1.9.1/optparse.rb:1347:in `parse_in_order' 108 | from /usr/lib/ruby/1.9.1/optparse.rb:1341:in `order!' 109 | from /usr/lib/ruby/1.9.1/optparse.rb:1432:in `permute!' 110 | from /usr/lib/ruby/1.9.1/optparse.rb:1453:in `parse!' 111 | from /usr/bin/jekyll:137:in `
' 112 | ~~~ 113 | 114 | This occurs because you are using an old version of Jekyll located in `/usr/bin`. 115 | Make sure that you have installed Jekyll using: 116 | 117 | ~~~ 118 | $ gem install jekyll 119 | ~~~ 120 | 121 | This installs Jekyll in `/usr/local/bin`, 122 | so make sure this directory comes before `/usr/bin` in your `PATH` environment variable. 123 | When your path is set correctly, 124 | you should see: 125 | 126 | ~~~ 127 | $ which jekyll 128 | /usr/local/bin/jekyll 129 | ~~~ 130 | 131 | You may also have to install the `nodejs` package to disable references to JavaScript, 132 | which you can do using: 133 | 134 | ~~~ 135 | $ sudo apt-get install nodejs 136 | ~~~ 137 | 138 | For more information, 139 | see . 140 | 141 | [amy]: https://github.com/swcarpentry/amy 142 | [github-pages]: https://help.github.com/articles/creating-project-pages-manually/ 143 | -------------------------------------------------------------------------------- /bin/util.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import sys 3 | import os 4 | import json 5 | from subprocess import Popen, PIPE 6 | 7 | # Import this way to produce a more useful error message. 8 | try: 9 | import yaml 10 | except ImportError: 11 | print('Unable to import YAML module: please install PyYAML', file=sys.stderr) 12 | sys.exit(1) 13 | 14 | 15 | # Things an image file's name can end with. 16 | IMAGE_FILE_SUFFIX = { 17 | '.gif', 18 | '.jpg', 19 | '.png', 20 | '.svg' 21 | } 22 | 23 | # Files that shouldn't be present. 24 | UNWANTED_FILES = [ 25 | '.nojekyll' 26 | ] 27 | 28 | # Marker to show that an expected value hasn't been provided. 29 | # (Can't use 'None' because that might be a legitimate value.) 30 | REPORTER_NOT_SET = [] 31 | 32 | class Reporter(object): 33 | """Collect and report errors.""" 34 | 35 | def __init__(self): 36 | """Constructor.""" 37 | 38 | super(Reporter, self).__init__() 39 | self.messages = [] 40 | 41 | 42 | def check_field(self, filename, name, values, key, expected=REPORTER_NOT_SET): 43 | """Check that a dictionary has an expected value.""" 44 | 45 | if key not in values: 46 | self.add(filename, '{0} does not contain {1}', name, key) 47 | elif expected is REPORTER_NOT_SET: 48 | pass 49 | elif type(expected) in (tuple, set, list): 50 | if values[key] not in expected: 51 | self.add(filename, '{0} {1} value {2} is not in {3}', name, key, values[key], expected) 52 | elif values[key] != expected: 53 | self.add(filename, '{0} {1} is {2} not {3}', name, key, values[key], expected) 54 | 55 | 56 | def check(self, condition, location, fmt, *args): 57 | """Append error if condition not met.""" 58 | 59 | if not condition: 60 | self.add(location, fmt, *args) 61 | 62 | 63 | def add(self, location, fmt, *args): 64 | """Append error unilaterally.""" 65 | 66 | self.messages.append((location, fmt.format(*args))) 67 | 68 | 69 | def report(self, stream=sys.stdout): 70 | """Report all messages in order.""" 71 | 72 | if not self.messages: 73 | return 74 | 75 | def pretty(item): 76 | location, message = item 77 | if isinstance(location, type(None)): 78 | return message 79 | elif isinstance(location, str): 80 | return location + ': ' + message 81 | elif isinstance(location, tuple): 82 | return '{0}:{1}: '.format(*location) + message 83 | else: 84 | assert False, 'Unknown item "{0}"'.format(item) 85 | 86 | def key(item): 87 | location, message = item 88 | if isinstance(location, type(None)): 89 | return ('', -1, message) 90 | elif isinstance(location, str): 91 | return (location, -1, message) 92 | elif isinstance(location, tuple): 93 | return (location[0], location[1], message) 94 | else: 95 | assert False, 'Unknown item "{0}"'.format(item) 96 | 97 | for m in sorted(self.messages, key=key): 98 | print(pretty(m), file=stream) 99 | 100 | 101 | def read_markdown(parser, path): 102 | """ 103 | Get YAML and AST for Markdown file, returning 104 | {'metadata':yaml, 'metadata_len':N, 'text':text, 'lines':[(i, line, len)], 'doc':doc}. 105 | """ 106 | 107 | # Split and extract YAML (if present). 108 | with open(path, 'r') as reader: 109 | body = reader.read() 110 | metadata_raw, metadata_yaml, body = split_metadata(path, body) 111 | 112 | # Split into lines. 113 | metadata_len = 0 if metadata_raw is None else metadata_raw.count('\n') 114 | lines = [(metadata_len+i+1, line, len(line)) for (i, line) in enumerate(body.split('\n'))] 115 | 116 | # Parse Markdown. 117 | cmd = 'ruby {0}'.format(parser) 118 | p = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, close_fds=True, universal_newlines=True) 119 | stdout_data, stderr_data = p.communicate(body) 120 | doc = json.loads(stdout_data) 121 | 122 | return { 123 | 'metadata': metadata_yaml, 124 | 'metadata_len': metadata_len, 125 | 'text': body, 126 | 'lines': lines, 127 | 'doc': doc 128 | } 129 | 130 | 131 | def split_metadata(path, text): 132 | """ 133 | Get raw (text) metadata, metadata as YAML, and rest of body. 134 | If no metadata, return (None, None, body). 135 | """ 136 | 137 | metadata_raw = None 138 | metadata_yaml = None 139 | metadata_len = None 140 | 141 | pieces = text.split('---', 2) 142 | if len(pieces) == 3: 143 | metadata_raw = pieces[1] 144 | text = pieces[2] 145 | try: 146 | metadata_yaml = yaml.load(metadata_raw) 147 | except yaml.YAMLError as e: 148 | print('Unable to parse YAML header in {0}:\n{1}'.format(path, e), file=sys.stderr) 149 | sys.exit(1) 150 | 151 | return metadata_raw, metadata_yaml, text 152 | 153 | 154 | def load_yaml(filename): 155 | """ 156 | Wrapper around YAML loading so that 'import yaml' is only needed 157 | in one file. 158 | """ 159 | 160 | try: 161 | with open(filename, 'r') as reader: 162 | return yaml.load(reader) 163 | except (yaml.YAMLError, FileNotFoundError) as e: 164 | print('Unable to load YAML file {0}:\n{1}'.format(filename, e), file=sys.stderr) 165 | sys.exit(1) 166 | 167 | 168 | def check_unwanted_files(dir_path, reporter): 169 | """ 170 | Check that unwanted files are not present. 171 | """ 172 | 173 | for filename in UNWANTED_FILES: 174 | path = os.path.join(dir_path, filename) 175 | reporter.check(not os.path.exists(path), 176 | path, 177 | "Unwanted file found") 178 | 179 | 180 | def require(condition, message): 181 | """Fail if condition not met.""" 182 | 183 | if not condition: 184 | print(message, file=sys.stderr) 185 | sys.exit(1) 186 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | [Software Carpentry][swc-site] and [Data Carpentry][dc-site] are open source projects, 4 | and we welcome contributions of all kinds: 5 | new lessons, 6 | fixes to existing material, 7 | bug reports, 8 | and reviews of proposed changes are all welcome. 9 | 10 | ## Contributor Agreement 11 | 12 | By contributing, 13 | you agree that we may redistribute your work under [our license](LICENSE.md). 14 | In exchange, 15 | we will address your issues and/or assess your change proposal as promptly as we can, 16 | and help you become a member of our community. 17 | Everyone involved in [Software Carpentry][swc-site] and [Data Carpentry][dc-site] 18 | agrees to abide by our [code of conduct](CONDUCT.md). 19 | 20 | ## How to Contribute 21 | 22 | The easiest way to get started is to file an issue 23 | to tell us about a spelling mistake, 24 | some awkward wording, 25 | or a factual error. 26 | This is a good way to introduce yourself 27 | and to meet some of our community members. 28 | 29 | 1. If you do not have a [GitHub][github] account, 30 | you can [send us comments by email][contact]. 31 | However, 32 | we will be able to respond more quickly if you use one of the other methods described below. 33 | 34 | 2. If you have a [GitHub][github] account, 35 | or are willing to [create one][github-join], 36 | but do not know how to use Git, 37 | you can report problems or suggest improvements by [creating an issue][issues]. 38 | This allows us to assign the item to someone 39 | and to respond to it in a threaded discussion. 40 | 41 | 3. If you are comfortable with Git, 42 | and would like to add or change material, 43 | you can submit a pull request (PR). 44 | Instructions for doing this are [included below](#using-github). 45 | 46 | ## Where to Contribute 47 | 48 | 1. If you wish to change the template used for workshop websites, 49 | please work in . 50 | The home page of that repository explains how to set up workshop websites, 51 | while the extra pages in 52 | provide more background on our design choices. 53 | 54 | 2. If you wish to change CSS style files, tools, 55 | or HTML boilerplate for lessons or workshops stored in `_includes` or `_layouts`, 56 | please work in . 57 | 58 | ## What to Contribute 59 | 60 | There are many ways to contribute, 61 | from writing new exercises and improving existing ones 62 | to updating or filling in the documentation 63 | and and submitting [bug reports][issues] 64 | about things that don't work, aren't clear, or are missing. 65 | If you are looking for ideas, 66 | please see [the list of issues for this repository][issues], 67 | or the issues for [Data Carpentry][dc-issues] 68 | and [Software Carpentry][swc-issues] projects. 69 | 70 | Comments on issues and reviews of pull requests are just as welcome: 71 | we are smarter together than we are on our own. 72 | Reviews from novices and newcomers are particularly valuable: 73 | it's easy for people who have been using these lessons for a while 74 | to forget how impenetrable some of this material can be, 75 | so fresh eyes are always welcome. 76 | 77 | ## What *Not* to Contribute 78 | 79 | Our lessons already contain more material than we can cover in a typical workshop, 80 | so we are usually *not* looking for more concepts or tools to add to them. 81 | As a rule, 82 | if you want to introduce a new idea, 83 | you must (a) estimate how long it will take to teach 84 | and (b) explain what you would take out to make room for it. 85 | The first encourages contributors to be honest about requirements; 86 | the second, to think hard about priorities. 87 | 88 | We are also not looking for exercises or other material that only run on one platform. 89 | Our workshops typically contain a mixture of Windows, macOS, and Linux users; 90 | in order to be usable, 91 | our lessons must run equally well on all three. 92 | 93 | ## Using GitHub 94 | 95 | If you choose to contribute via GitHub, 96 | you may want to look at 97 | [How to Contribute to an Open Source Project on GitHub][how-contribute]. 98 | In brief: 99 | 100 | 1. The published copy of the lesson is in the `gh-pages` branch of the repository 101 | (so that GitHub will regenerate it automatically). 102 | Please create all branches from that, 103 | and merge the [master repository][repo]'s `gh-pages` branch into your `gh-pages` branch 104 | before starting work. 105 | Please do *not* work directly in your `gh-pages` branch, 106 | since that will make it difficult for you to work on other contributions. 107 | 108 | 2. We use [GitHub flow][github-flow] to manage changes: 109 | 1. Create a new branch in your desktop copy of this repository for each significant change. 110 | 2. Commit the change in that branch. 111 | 3. Push that branch to your fork of this repository on GitHub. 112 | 4. Submit a pull request from that branch to the [master repository][repo]. 113 | 5. If you receive feedback, 114 | make changes on your desktop and push to your branch on GitHub: 115 | the pull request will update automatically. 116 | 117 | Each lesson has two maintainers who review issues and pull requests 118 | or encourage others to do so. 119 | The maintainers are community volunteers, 120 | and have final say over what gets merged into the lesson. 121 | 122 | ## Other Resources 123 | 124 | General discussion of [Software Carpentry][swc-site] and [Data Carpentry][dc-site] 125 | happens on the [discussion mailing list][discuss-list], 126 | which everyone is welcome to join. 127 | You can also [reach us by email][contact]. 128 | 129 | [contact]: mailto:admin@software-carpentry.org 130 | [dc-issues]: https://github.com/issues?q=user%3Adatacarpentry 131 | [dc-lessons]: http://datacarpentry.org/lessons/ 132 | [dc-site]: http://datacarpentry.org/ 133 | [discuss-list]: http://lists.software-carpentry.org/listinfo/discuss 134 | [github]: http://github.com 135 | [github-flow]: https://guides.github.com/introduction/flow/ 136 | [github-join]: https://github.com/join 137 | [how-contribute]: https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github 138 | [issues]: https://github.com/swcarpentry/workshop-template/issues/ 139 | [repo]: https://github.com/swcarpentry/workshop-template/ 140 | [swc-issues]: https://github.com/issues?q=user%3Aswcarpentry 141 | [swc-lessons]: http://software-carpentry.org/lessons/ 142 | [swc-site]: http://software-carpentry.org/ 143 | -------------------------------------------------------------------------------- /_extras/customization.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: page 3 | title: Customizing Your Workshop's Website 4 | permalink: /customization/ 5 | --- 6 | ## Configuration File `_config.yml` 7 | 8 | You should edit the `_config.yml` configuration file in the root directory of your workshop to configure some site-wide variables and make the site function correctly: 9 | 10 | * `carpentry` - to tell us which carpentry workshop this is, possible values are ("swc", "dc" or "lc") 11 | * `title` - overall title for all pages 12 | * `repository` - as \/\ (e.g. `gvwilson/2015-07-01-miskatonic`), so that URLs resolve correctly both locally and on GitHub - see https://help.github.com/articles/repository-metadata-on-github-pages 13 | * `workshop_repo` - the URL of your workshop repository on GitHub 14 | * `workshop_site` - the repository's GitHub Pages URL 15 | 16 | For example, if the URL for the repository is `https://github.com/gvwilson/2015-07-01-miskatonic`, 17 | the URL for the website will be `http://gvwilson.github.io/2015-07-01-miskatonic`. 18 | 19 | You should not need to modify any of the other values in `_config.yml`. 20 | 21 | ## Home Page: Data 22 | 23 | Your workshop's home page lives in `index.html`, 24 | which must define the following values in its header: 25 | 26 | * `layout` must be `workshop`. 27 | 28 | * `carpentry` must be either "dc" (for Data Carpentry) or "swc" (for 29 | Software Carpentry). 30 | 31 | * `venue` is the short name of the institution or group hosting the 32 | workshop, like "Euphoric State University". It should *not* 33 | include the address or other details, since this value is 34 | displayed in a table on the main 35 | [Software Carpentry](http://software-carpentry.org) website. 36 | 37 | * `address` is the workshop's address (including details like the 38 | room number). The address should be all on one line. 39 | 40 | * `country` must be a two-letter ISO-3166 code for the country in 41 | which the workshop is going to take place, such as "fr" (for 42 | France) or "nz" (for New Zealand) - see [Wikipedia](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2#Officially_assigned_code_elements) 43 | for a complete list. 44 | 45 | * `language` is the language that will be used in the workshop. 46 | It must be an [ISO 639-1 code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes). 47 | Note that two-letter codes mean different things for countries 48 | and languages: "ar" is Arabic when used for a language, but 49 | Argentina when used for a country. 50 | 51 | * `latlng` is the latitude and longitude of the workshop site (so we 52 | can put a pin on our map). You can use 53 | [this site](http://itouchmap.com/latlong.html) to find these 54 | values. You can *not* put spaces around the comma separating the 55 | latitude from the longitude. 56 | 57 | * `humandate` is the human-friendly start and end date for the 58 | workshop. Please use three-letter month names and abbreviations 59 | (e.g., `Jul` instead of `July`), since these values are displayed 60 | in a table on our websites. (Strictly speaking this information 61 | is redundant, since we require a machine-readable `startdate` and 62 | `enddate`, but reliably translating those into human-readable 63 | dates is an interesting challenge...) 64 | 65 | * `humantime` is the human-friendly start and end time for each day of 66 | the workshop, e.g., "09:00 am - 4:00 pm" or "09:00-16:00". (We 67 | recognize that we ought to allow different start or end times on 68 | different days, but going down that path leads eventually to 69 | embedding iCal date/time specifications in our headers, which in 70 | turn leads to madness...) 71 | 72 | * `startdate` is the workshop's starting date in YYYY-MM-DD format, 73 | such as `2015-07-01`. You must use four digits for the year and 74 | two each for the month and day. 75 | 76 | * `enddate` is the workshop's ending date in the same format. If your 77 | workshop is only one day long, the `enddate` field should be deleted. 78 | If your workshop has a more complicated schedule (e.g., a half day a 79 | week for four weeks), please delete the `enddate` field and only tell 80 | us its start date. 81 | 82 | * `instructor` is a comma-separated list of instructor names. The 83 | list must be enclosed in square brackets, and each name must be in 84 | double quotes, as in `["Alan Turing","Grace Hopper"]`. Do not 85 | include other information (such as the word "instructor") in these 86 | values. 87 | 88 | * `helper` is a comma-separated list of helper names formatted in the 89 | same way as the instructor names. If there are no helpers, use an 90 | empty list `[]`. 91 | 92 | * `contact` is the contact email address to use for your workshop. 93 | If you do not provide a contact email address, your website will 94 | display the address for the workshop coordinators (who probably 95 | won't be able to answer questions about the specific details of 96 | your workshop). 97 | 98 | The header may optionally define the following: 99 | 100 | * `etherpad` is the URL for the Etherpad for your workshop. If you are 101 | not using an Etherpad, you can delete this line. 102 | 103 | * `eventbrite` is the multi-digit Eventbrite registration key. If you 104 | are using Eventbrite, the Software Carpentry administrators will 105 | give this to you. If you are using something else, you may delete 106 | this line. Note: this value must be given as a string in double 107 | quotes, rather than as a number. 108 | 109 | ## Home Page: Schedule and Syllabus 110 | 111 | You should edit the sections titled `Schedule` and `Syllabus` 112 | so that they show what you're actually planning to teach and when. 113 | 114 | ## Home Page: Setup 115 | 116 | You should delete the pieces of the `Setup` section 117 | related to software you will not be using in your workshop, 118 | so that learners don't spend time installing software they don't need. 119 | 120 | ## Setup: Installation tests 121 | 122 | If you intend to use the installation-test scripts, 123 | uncomment the paragraph linking to `setup/index.html` in `index.html` 124 | and edit `setup/swc-installation-test-2.py` as described below. 125 | 126 | `swc-installation-test-1.py` is pretty simple, and just checks that 127 | the students have a recent enough version of Python installed that 128 | they can run `swc-installation-test-2.py`. 129 | 130 | `swc-installation-test-2.py` 131 | checks for a list of dependencies and prints error messages if a 132 | package is not installed, or if the installed version is not current 133 | enough. By default, the script checks for pretty much anything that 134 | has ever been used at a Software Carpentry workshop, which is probably 135 | not what you want for your particular workshop. 136 | 137 | Go through `swc-installation-test-2.py` and 138 | comment any dependencies you don't need out of the `CHECKS` list. You 139 | might also want to skim through the minimum version numbers listed 140 | where particular dependencies are defined (e.g. `('git', 'Git', (1, 7, 141 | 0), None)`). For the most part, fairly conservative values have been 142 | selected, so students with modern machines should be fine. If your 143 | workshop has stricter version requirements, feel free to bump them 144 | accordingly. 145 | 146 | Similarly, the virtual dependencies can be satisfied by any of several 147 | packages. If you don't want to support a particular package (e.g. if 148 | you have no Emacs experience and don't want to be responsible for 149 | students who show up with Emacs as their only editor), you can comment 150 | out that particular `or_dependency`. 151 | 152 | ## Updating the repository 153 | 154 | If for some reason, 155 | such as the installation instructions having become disconnected 156 | with the current lesson material, 157 | you need to get changes from this repository 158 | into the clone of it with your workshop page, 159 | please follow the steps bellow: 160 | 161 | 1. Add the workshop-template repository as upstream: 162 | 163 | $ git remote add upstream https://github.com/swcarpentry/workshop-template.git 164 | 165 | 2. Fetch the data from upstream repository (also know as the workshop-template 166 | repository): 167 | 168 | $ git fetch upstream 169 | 170 | 3. Merge the new changes: 171 | 172 | $ git merge upstream/gh-pages 173 | 174 | 4. When get conflicts, solve it and 175 | 176 | $ git commit -a 177 | 178 | 5. Push the changes to GitHub: 179 | 180 | $ git push origin gh-pages 181 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # workshop-template 2 | 3 | This repository is [Software Carpentry][swc-site] and [Data Carpentry][dc-site]'s 4 | template for creating websites for workshops. 5 | 6 | 1. Please *do not fork this repository directly on GitHub.* 7 | Instead, please use GitHub's importer following [the instructions below](#creating-a-repository) 8 | to copy this `workshop-template` repository and customize it for your workshop. 9 | 10 | 2. Please *do your work in your repository's `gh-pages` branch*, 11 | since that is what is 12 | [automatically published as a website by GitHub][github-project-pages]. 13 | 14 | 3. Once you are done, please also [let us know][email] the workshop URL. If this is a self-organised workshop, you should also [fill in the self-organized workshop form][self-organized-workshop-form] (if you have not already done so), so we can keep track of all workshops. We build the list of workshops on our websites from the data included in your `index.md` page. We can only do that if you [customize][customization] that page correctly *and* let us know the workshop URL. 15 | 16 | If you run into problems, 17 | or have ideas about how to make this process simpler, 18 | please [get in touch](#getting-and-giving-help). 19 | The pages on [customizing your website][customization], 20 | the [FAQ][faq], 21 | and the [design notes][design] have more detail on what we do and why. 22 | And please note: 23 | if you are teaching Git, 24 | please [create a separate repository](#setting-up-a-separate-repository-for-learners) 25 | for your learners to practice in. 26 | 27 | ## Creating a Repository 28 | 29 | 1. Log in to GitHub. 30 | (If you do not have an account, you can quickly create one for free.) 31 | You must be logged in for the remaining steps to work. 32 | 33 | 2. Go to GitHub's importer. 34 | 35 | 3. Paste the url of this repo as the old repository to clone: 36 | . 37 | 38 | 4. Select the owner for your new repository. 39 | (This will probably be you, but may instead be an organization you belong to.) 40 | 41 | 5. Choose a name for your workshop website repository. 42 | This name should have the form `YYYY-MM-DD-site`, 43 | e.g., `2016-12-01-miskatonic`, 44 | where `YYYY-MM-DD` is the start date of the workshop. 45 | 46 | 6. Make sure the repository is public. 47 | 48 | 7. At this point, you should have a page like this: 49 | 50 | ![](fig/using-github-import.png?raw=true) 51 | 52 | You can now click "Begin Import". 53 | When the process is done, 54 | you will receive a message like 55 | "Importing complete! Your new repository gvwilson/2016-12-01-miskatonic is ready." 56 | and you can go to the new repository by clicking on the name. 57 | 58 | **Note:** 59 | some people have had intermittent errors during the import process, 60 | possibly because of the network timing out. 61 | If you experience a problem, please re-try; 62 | if the problem persists, 63 | please [get in touch](#getting-and-giving-help). 64 | 65 | ## Customizing Your Website 66 | 67 | 1. Go into your newly-created repository, 68 | which will be at `https://github.com/your_username/YYYY-MM-DD-site`. 69 | For example, 70 | if your username is `gvwilson`, 71 | the repository's URL will be `https://github.com/gvwilson/2016-12-01-miskatonic`. 72 | 73 | 3. Ensure you are on the gh-pages branch by clicking on the branch under the drop 74 | down in the menu bar (see the note below): 75 | 76 | ![](fig/select-gh-pages-branch.png?raw=true) 77 | 78 | 3. Edit the header of `index.md` to customize the list of instructors, 79 | workshop venue, etc. 80 | You can do this in the browser by clicking on it in the file view on GitHub 81 | and then selecting the pencil icon in the menu bar: 82 | 83 | ![](fig/edit-index-file-menu-bar.png?raw=true) 84 | 85 | Editing hints are embedded in `index.md`, 86 | and full instructions are in [the customization instructions][customization]. 87 | 88 | 4. Edit `_config.yml` to customize certain site-wide variables, such as: `carpentry` (to tell us which carpentry workshop this is), `title` (overall title for all pages), `repository` (so that URLs resolve correctly both locally and on GitHub), `workshop_repo` (the URL of the workshop repository on GitHub) and `workshop_site` (the repository's GitHub Pages URL). 89 | 90 | Editing hints are embedded in `_config.yml`, 91 | and full instructions are in [the customization instructions][customization]. 92 | 93 | 5. Edit the `schedule.html` file to edit the schedule for your upcoming workshop. This file is located in the `_includes` directory, make sure to choose the one from the appropriate `dc` (Data Carpentry workshop), `lc` (Library Carpentry), or `sc` (Software Carpentry) subdirectory. 94 | 95 | 6. Alternatively, 96 | if you are already familiar with Git, 97 | you can clone the repository to your desktop, 98 | edit `index.md`, `_config.yml`, and `schedule.html` there, 99 | and push your changes back to the repository. 100 | 101 | ~~~ 102 | git clone -b gh-pages https://github.com/your_username/YYYY-MM-DD-site 103 | ~~~ 104 | 105 | You should specify `-b gh-pages` to checkout the gh-pages branch because the imported 106 | repository doesn't have a `master` branch. 107 | 108 | In order to view your changes once you are done editing, 109 | you must push to your GitHub repository: 110 | 111 | ~~~ 112 | git push origin gh-pages 113 | ~~~ 114 | 115 | 7. When you are done editing, 116 | go to the GitHub Pages URL for your workshop and preview your changes. 117 | In the example above, this is `https://gvwilson.github.io/2016-12-01-miskatonic`. 118 | The finished page should look [something like this](fig/completed-page.png?raw=true). 119 | 120 | 8. Optional: you can now change the README.md file in your website's repository, which contains these instructions, so that it contains a short description of your workshop and a link to the workshop website. 121 | 122 | **Note:** 123 | please do all of your work in your repository's `gh-pages` branch, 124 | since [GitHub automatically publishes that as a website][github-project-pages]. 125 | 126 | **Note:** 127 | this template includes some files and directories that most workshops do not need, 128 | but which provide a standard place to put extra content if desired. 129 | See the [design notes][design] for more information about these. 130 | 131 | Further instructions are available in [the customization instructions][customization]. 132 | This [FAQ][faq] includes a few extra tips (additions are always welcome) 133 | and these notes on [the background and design][design] of this template may help as well. 134 | 135 | ## Checking Your Changes 136 | 137 | If you want to preview your changes on your own machine before publishing them on GitHub, 138 | you can do so as described below. 139 | 140 | 1. Install the software [described below](#installing-software). 141 | This may require some work, 142 | so feel free to preview by pushing to the website. 143 | 144 | 2. Run the command 145 | 146 | ~~~ 147 | make serve 148 | ~~~ 149 | 150 | and go to to preview your site. 151 | You can also run this command by typing `make serve` 152 | (assuming you have Make installed). 153 | 154 | 3. Run the command 155 | 156 | ~~~ 157 | make workshop-check 158 | ~~~ 159 | 160 | to check for a few common errors in your workshop's home page. 161 | (You must have Python 3 installed to do this.) 162 | 163 | ## (Optional) Linking to Your Page 164 | 165 | At the top of your repository on GitHub you'll see 166 | 167 | ~~~ 168 | No description, website, or topics provided. — Edit 169 | ~~~ 170 | 171 | Click 'Edit' and add: 172 | 173 | 1. A very brief description of your workshop in the "Description" box (e.g., "Miskatonic University workshop, Dec. 2016") 174 | 175 | 2. The URL for your workshop in the "Website" box (e.g., `https://gvwilson.github.io/2016-12-01-miskatonic`) 176 | 177 | This will help people find your website if they come to your repository's home page. 178 | 179 | ## Creating Extra Pages 180 | 181 | In rare cases, 182 | you may want to add extra pages to your workshop website. 183 | You can do this by putting either Markdown or HTML pages in the website's root directory 184 | and styling them according to the instructions give in 185 | [the lesson template][lesson-example]. 186 | If you do this, 187 | you *must* also edit `_config.yml` to set these three values: 188 | 189 | 1. `carpentry` is either "dc" (for Data Carpentry), "swc" (for Software Carpentry), 190 | or "lc" (for Library Carpentry). This determines which logos are loaded. 191 | 192 | 2. `title` is the title of your workshop (typically the venue and date). 193 | 194 | 3. `email` is the contact email address for your workshop, 195 | e.g., `gvwilson@miskatonic.edu`. 196 | 197 | Note: `carpentry` and `email` duplicate information that's in `index.md`, 198 | but there is no way to avoid this 199 | without requiring people to edit both files in the usual case 200 | where no extra pages are created. 201 | 202 | ## Installing Software 203 | 204 | If you want to set up Jekyll 205 | so that you can preview changes on your own machine before pushing them to GitHub, 206 | you must install the software described below. 207 | (Note: Julian Thilo has written instructions for 208 | [installing Jekyll on Windows][jekyll-windows].) 209 | 210 | 1. **Ruby**. 211 | This is included with Linux and macOS; 212 | the simplest option on Windows is to use [RubyInstaller][ruby-installer]. 213 | You can test your installation by running `ruby --version`. 214 | For more information, 215 | see [the Ruby installation guidelines][ruby-install-guide]. 216 | 217 | 2. **[RubyGems][rubygems]** 218 | (the package manager for Ruby). 219 | You can test your installation by running `gem --version`. 220 | 221 | 3. **[Jekyll][jekyll]**. 222 | You can install this by running `gem install jekyll`. 223 | 224 | ## Setting Up a Separate Repository for Learners 225 | 226 | If you are teaching Git, 227 | you should create a separate repository for learners to use in that lesson. 228 | You should not have them use the workshop website repository because: 229 | 230 | * your workshop website repository contains many files 231 | that most learners don't need to see during the lesson, 232 | and 233 | 234 | * you probably don't want to accidentally merge 235 | a damaging pull request from a novice Git user 236 | into your workshop's website while you are using it to teach. 237 | 238 | You can call this repository whatever you like, 239 | and add whatever content you need to it. 240 | 241 | ## Getting and Giving Help 242 | 243 | We are committed to offering a pleasant setup experience for our learners and organizers. 244 | If you find bugs in our instructions, 245 | or would like to suggest improvements, 246 | please [file an issue][issues] 247 | or [mail us][email]. 248 | 249 | [email]: mailto:admin@software-carpentry.org 250 | [customization]: https://swcarpentry.github.io/workshop-template/customization/ 251 | [dc-site]: http://datacarpentry.org 252 | [design]: https://swcarpentry.github.io/workshop-template/design/ 253 | [faq]: https://swcarpentry.github.io/workshop-template/faq/ 254 | [github-project-pages]: https://help.github.com/articles/creating-project-pages-manually/ 255 | [importer]: https://github.com/new/import 256 | [issues]: https://github.com/swcarpentry/workshop-template/issues 257 | [jekyll-windows]: http://jekyll-windows.juthilo.com/ 258 | [jekyll]: https://jekyllrb.com/ 259 | [lesson-example]: https://swcarpentry.github.io/lesson-example/ 260 | [pyyaml]: https://pypi.python.org/pypi/PyYAML 261 | [ruby-install-guide]: https://www.ruby-lang.org/en/downloads/ 262 | [ruby-installer]: http://rubyinstaller.org/ 263 | [rubygems]: https://rubygems.org/pages/download/ 264 | [self-organized-workshop-form]: https://amy.software-carpentry.org/workshops/submit/ 265 | [swc-site]: http://software-carpentry.org 266 | -------------------------------------------------------------------------------- /bin/workshop_check.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | '''Check that a workshop's index.html metadata is valid. See the 4 | docstrings on the checking functions for a summary of the checks. 5 | ''' 6 | 7 | from __future__ import print_function 8 | import sys 9 | import os 10 | import re 11 | from datetime import date 12 | from util import Reporter, split_metadata, load_yaml, check_unwanted_files 13 | 14 | # Metadata field patterns. 15 | EMAIL_PATTERN = r'[^@]+@[^@]+\.[^@]+' 16 | HUMANTIME_PATTERN = r'((0?[1-9]|1[0-2]):[0-5]\d(am|pm)(-|to)(0?[1-9]|1[0-2]):[0-5]\d(am|pm))|((0?\d|1\d|2[0-3]):[0-5]\d(-|to)(0?\d|1\d|2[0-3]):[0-5]\d)' 17 | EVENTBRITE_PATTERN = r'\d{9,10}' 18 | URL_PATTERN = r'https?://.+' 19 | 20 | # Defaults. 21 | CARPENTRIES = ("dc", "swc") 22 | DEFAULT_CONTACT_EMAIL = 'admin@software-carpentry.org' 23 | 24 | USAGE = 'Usage: "workshop_check.py path/to/root/directory"' 25 | 26 | # Country and language codes. Note that codes mean different things: 'ar' 27 | # is 'Arabic' as a language but 'Argentina' as a country. 28 | 29 | ISO_COUNTRY = [ 30 | 'ad', 'ae', 'af', 'ag', 'ai', 'al', 'am', 'an', 'ao', 'aq', 'ar', 'as', 31 | 'at', 'au', 'aw', 'ax', 'az', 'ba', 'bb', 'bd', 'be', 'bf', 'bg', 'bh', 32 | 'bi', 'bj', 'bm', 'bn', 'bo', 'br', 'bs', 'bt', 'bv', 'bw', 'by', 'bz', 33 | 'ca', 'cc', 'cd', 'cf', 'cg', 'ch', 'ci', 'ck', 'cl', 'cm', 'cn', 'co', 34 | 'cr', 'cu', 'cv', 'cx', 'cy', 'cz', 'de', 'dj', 'dk', 'dm', 'do', 'dz', 35 | 'ec', 'ee', 'eg', 'eh', 'er', 'es', 'et', 'eu', 'fi', 'fj', 'fk', 'fm', 36 | 'fo', 'fr', 'ga', 'gb', 'gd', 'ge', 'gf', 'gg', 'gh', 'gi', 'gl', 'gm', 37 | 'gn', 'gp', 'gq', 'gr', 'gs', 'gt', 'gu', 'gw', 'gy', 'hk', 'hm', 'hn', 38 | 'hr', 'ht', 'hu', 'id', 'ie', 'il', 'im', 'in', 'io', 'iq', 'ir', 'is', 39 | 'it', 'je', 'jm', 'jo', 'jp', 'ke', 'kg', 'kh', 'ki', 'km', 'kn', 'kp', 40 | 'kr', 'kw', 'ky', 'kz', 'la', 'lb', 'lc', 'li', 'lk', 'lr', 'ls', 'lt', 41 | 'lu', 'lv', 'ly', 'ma', 'mc', 'md', 'me', 'mg', 'mh', 'mk', 'ml', 'mm', 42 | 'mn', 'mo', 'mp', 'mq', 'mr', 'ms', 'mt', 'mu', 'mv', 'mw', 'mx', 'my', 43 | 'mz', 'na', 'nc', 'ne', 'nf', 'ng', 'ni', 'nl', 'no', 'np', 'nr', 'nu', 44 | 'nz', 'om', 'pa', 'pe', 'pf', 'pg', 'ph', 'pk', 'pl', 'pm', 'pn', 'pr', 45 | 'ps', 'pt', 'pw', 'py', 'qa', 're', 'ro', 'rs', 'ru', 'rw', 'sa', 'sb', 46 | 'sc', 'sd', 'se', 'sg', 'sh', 'si', 'sj', 'sk', 'sl', 'sm', 'sn', 'so', 47 | 'sr', 'st', 'sv', 'sy', 'sz', 'tc', 'td', 'tf', 'tg', 'th', 'tj', 'tk', 48 | 'tl', 'tm', 'tn', 'to', 'tr', 'tt', 'tv', 'tw', 'tz', 'ua', 'ug', 'um', 49 | 'us', 'uy', 'uz', 'va', 'vc', 've', 'vg', 'vi', 'vn', 'vu', 'wf', 'ws', 50 | 'ye', 'yt', 'za', 'zm', 'zw' 51 | ] 52 | 53 | ISO_LANGUAGE = [ 54 | 'aa', 'ab', 'ae', 'af', 'ak', 'am', 'an', 'ar', 'as', 'av', 'ay', 'az', 55 | 'ba', 'be', 'bg', 'bh', 'bi', 'bm', 'bn', 'bo', 'br', 'bs', 'ca', 'ce', 56 | 'ch', 'co', 'cr', 'cs', 'cu', 'cv', 'cy', 'da', 'de', 'dv', 'dz', 'ee', 57 | 'el', 'en', 'eo', 'es', 'et', 'eu', 'fa', 'ff', 'fi', 'fj', 'fo', 'fr', 58 | 'fy', 'ga', 'gd', 'gl', 'gn', 'gu', 'gv', 'ha', 'he', 'hi', 'ho', 'hr', 59 | 'ht', 'hu', 'hy', 'hz', 'ia', 'id', 'ie', 'ig', 'ii', 'ik', 'io', 'is', 60 | 'it', 'iu', 'ja', 'jv', 'ka', 'kg', 'ki', 'kj', 'kk', 'kl', 'km', 'kn', 61 | 'ko', 'kr', 'ks', 'ku', 'kv', 'kw', 'ky', 'la', 'lb', 'lg', 'li', 'ln', 62 | 'lo', 'lt', 'lu', 'lv', 'mg', 'mh', 'mi', 'mk', 'ml', 'mn', 'mr', 'ms', 63 | 'mt', 'my', 'na', 'nb', 'nd', 'ne', 'ng', 'nl', 'nn', 'no', 'nr', 'nv', 64 | 'ny', 'oc', 'oj', 'om', 'or', 'os', 'pa', 'pi', 'pl', 'ps', 'pt', 'qu', 65 | 'rm', 'rn', 'ro', 'ru', 'rw', 'sa', 'sc', 'sd', 'se', 'sg', 'si', 'sk', 66 | 'sl', 'sm', 'sn', 'so', 'sq', 'sr', 'ss', 'st', 'su', 'sv', 'sw', 'ta', 67 | 'te', 'tg', 'th', 'ti', 'tk', 'tl', 'tn', 'to', 'tr', 'ts', 'tt', 'tw', 68 | 'ty', 'ug', 'uk', 'ur', 'uz', 've', 'vi', 'vo', 'wa', 'wo', 'xh', 'yi', 69 | 'yo', 'za', 'zh', 'zu' 70 | ] 71 | 72 | 73 | def look_for_fixme(func): 74 | """Decorator to fail test if text argument starts with "FIXME".""" 75 | 76 | def inner(arg): 77 | if (arg is not None) and \ 78 | isinstance(arg, str) and \ 79 | arg.lstrip().startswith('FIXME'): 80 | return False 81 | return func(arg) 82 | return inner 83 | 84 | 85 | @look_for_fixme 86 | def check_layout(layout): 87 | '''"layout" in YAML header must be "workshop".''' 88 | 89 | return layout == 'workshop' 90 | 91 | 92 | @look_for_fixme 93 | def check_carpentry(layout): 94 | '''"carpentry" in YAML header must be "dc" or "swc".''' 95 | 96 | return layout in CARPENTRIES 97 | 98 | 99 | @look_for_fixme 100 | def check_country(country): 101 | '''"country" must be a lowercase ISO-3166 two-letter code.''' 102 | 103 | return country in ISO_COUNTRY 104 | 105 | 106 | @look_for_fixme 107 | def check_language(language): 108 | '''"language" must be a lowercase ISO-639 two-letter code.''' 109 | 110 | return language in ISO_LANGUAGE 111 | 112 | 113 | @look_for_fixme 114 | def check_humandate(date): 115 | """ 116 | 'humandate' must be a human-readable date with a 3-letter month 117 | and 4-digit year. Examples include 'Feb 18-20, 2025' and 'Feb 18 118 | and 20, 2025'. It may be in languages other than English, but the 119 | month name should be kept short to aid formatting of the main 120 | Software Carpentry web site. 121 | """ 122 | 123 | if ',' not in date: 124 | return False 125 | 126 | month_dates, year = date.split(',') 127 | 128 | # The first three characters of month_dates are not empty 129 | month = month_dates[:3] 130 | if any(char == ' ' for char in month): 131 | return False 132 | 133 | # But the fourth character is empty ("February" is illegal) 134 | if month_dates[3] != ' ': 135 | return False 136 | 137 | # year contains *only* numbers 138 | try: 139 | int(year) 140 | except: 141 | return False 142 | 143 | return True 144 | 145 | 146 | @look_for_fixme 147 | def check_humantime(time): 148 | """ 149 | 'humantime' is a human-readable start and end time for the 150 | workshop, such as '09:00 - 16:00'. 151 | """ 152 | 153 | return bool(re.match(HUMANTIME_PATTERN, time.replace(' ', ''))) 154 | 155 | 156 | def check_date(this_date): 157 | """ 158 | 'startdate' and 'enddate' are machine-readable start and end dates 159 | for the workshop, and must be in YYYY-MM-DD format, e.g., 160 | '2015-07-01'. 161 | """ 162 | 163 | # YAML automatically loads valid dates as datetime.date. 164 | return isinstance(this_date, date) 165 | 166 | 167 | @look_for_fixme 168 | def check_latitude_longitude(latlng): 169 | """ 170 | 'latlng' must be a valid latitude and longitude represented as two 171 | floating-point numbers separated by a comma. 172 | """ 173 | 174 | try: 175 | lat, lng = latlng.split(',') 176 | lat = float(lat) 177 | long = float(lng) 178 | return (-90.0 <= lat <= 90.0) and (-180.0 <= long <= 180.0) 179 | except ValueError: 180 | return False 181 | 182 | 183 | def check_instructors(instructors): 184 | """ 185 | 'instructor' must be a non-empty comma-separated list of quoted 186 | names, e.g. ['First name', 'Second name', ...']. Do not use 'TBD' 187 | or other placeholders. 188 | """ 189 | 190 | # YAML automatically loads list-like strings as lists. 191 | return isinstance(instructors, list) and len(instructors) > 0 192 | 193 | 194 | def check_helpers(helpers): 195 | """ 196 | 'helper' must be a comma-separated list of quoted names, 197 | e.g. ['First name', 'Second name', ...']. The list may be empty. 198 | Do not use 'TBD' or other placeholders. 199 | """ 200 | 201 | # YAML automatically loads list-like strings as lists. 202 | return isinstance(helpers, list) and len(helpers) >= 0 203 | 204 | 205 | @look_for_fixme 206 | def check_emails(emails): 207 | """ 208 | 'email' must be a comma-separated list of valid email addresses. 209 | The list may be empty. A valid email address consists of characters, 210 | an '@', and more characters. It should not contain the default contact 211 | """ 212 | 213 | # YAML automatically loads list-like strings as lists. 214 | if (isinstance(emails, list) and len(emails) >= 0): 215 | for email in emails: 216 | if ((not bool(re.match(EMAIL_PATTERN, email))) or (email == DEFAULT_CONTACT_EMAIL)): 217 | return False 218 | else: 219 | return False 220 | 221 | return True 222 | 223 | 224 | def check_eventbrite(eventbrite): 225 | """ 226 | 'eventbrite' (the Eventbrite registration key) must be 9 or more 227 | digits. It may appear as an integer or as a string. 228 | """ 229 | 230 | if isinstance(eventbrite, int): 231 | return True 232 | else: 233 | return bool(re.match(EVENTBRITE_PATTERN, eventbrite)) 234 | 235 | 236 | @look_for_fixme 237 | def check_collaborative_notes(collaborative_notes): 238 | """ 239 | 'collaborative_notes' must be a valid URL. 240 | """ 241 | 242 | return bool(re.match(URL_PATTERN, collaborative_notes)) 243 | 244 | 245 | @look_for_fixme 246 | def check_pass(value): 247 | """ 248 | This test always passes (it is used for 'checking' things like the 249 | workshop address, for which no sensible validation is feasible). 250 | """ 251 | 252 | return True 253 | 254 | 255 | HANDLERS = { 256 | 'layout': (True, check_layout, 'layout isn\'t "workshop"'), 257 | 258 | 'carpentry': (True, check_carpentry, 'carpentry isn\'t in ' + 259 | ', '.join(CARPENTRIES)), 260 | 261 | 'country': (True, check_country, 262 | 'country invalid: must use lowercase two-letter ISO code ' + 263 | 'from ' + ', '.join(ISO_COUNTRY)), 264 | 265 | 'language': (False, check_language, 266 | 'language invalid: must use lowercase two-letter ISO code' + 267 | ' from ' + ', '.join(ISO_LANGUAGE)), 268 | 269 | 'humandate': (True, check_humandate, 270 | 'humandate invalid. Please use three-letter months like ' + 271 | '"Jan" and four-letter years like "2025"'), 272 | 273 | 'humantime': (True, check_humantime, 274 | 'humantime doesn\'t include numbers'), 275 | 276 | 'startdate': (True, check_date, 277 | 'startdate invalid. Must be of format year-month-day, ' + 278 | 'i.e., 2014-01-31'), 279 | 280 | 'enddate': (False, check_date, 281 | 'enddate invalid. Must be of format year-month-day, i.e.,' + 282 | ' 2014-01-31'), 283 | 284 | 'latlng': (True, check_latitude_longitude, 285 | 'latlng invalid. Check that it is two floating point ' + 286 | 'numbers, separated by a comma'), 287 | 288 | 'instructor': (True, check_instructors, 289 | 'instructor list isn\'t a valid list of format ' + 290 | '["First instructor", "Second instructor",..]'), 291 | 292 | 'helper': (True, check_helpers, 293 | 'helper list isn\'t a valid list of format ' + 294 | '["First helper", "Second helper",..]'), 295 | 296 | 'email': (True, check_emails, 297 | 'contact email list isn\'t a valid list of format ' + 298 | '["me@example.org", "you@example.org",..] or contains incorrectly formatted email addresses or ' + 299 | '"{0}".'.format(DEFAULT_CONTACT_EMAIL)), 300 | 301 | 'eventbrite': (False, check_eventbrite, 'Eventbrite key appears invalid'), 302 | 303 | 'collaborative_notes': (False, check_collaborative_notes, 'Collaborative Notes URL appears invalid'), 304 | 305 | 'venue': (False, check_pass, 'venue name not specified'), 306 | 307 | 'address': (False, check_pass, 'address not specified') 308 | } 309 | 310 | # REQUIRED is all required categories. 311 | REQUIRED = set([k for k in HANDLERS if HANDLERS[k][0]]) 312 | 313 | # OPTIONAL is all optional categories. 314 | OPTIONAL = set([k for k in HANDLERS if not HANDLERS[k][0]]) 315 | 316 | 317 | def check_blank_lines(reporter, raw): 318 | """ 319 | Blank lines are not allowed in category headers. 320 | """ 321 | 322 | lines = [(i, x) for (i, x) in enumerate(raw.strip().split('\n')) if not x.strip()] 323 | reporter.check(not lines, 324 | None, 325 | 'Blank line(s) in header: {0}', 326 | ', '.join(["{0}: {1}".format(i, x.rstrip()) for (i, x) in lines])) 327 | 328 | 329 | def check_categories(reporter, left, right, msg): 330 | """ 331 | Report differences (if any) between two sets of categories. 332 | """ 333 | 334 | diff = left - right 335 | reporter.check(len(diff) == 0, 336 | None, 337 | '{0}: offending entries {1}', 338 | msg, sorted(list(diff))) 339 | 340 | 341 | def check_file(reporter, path, data): 342 | """ 343 | Get header from file, call all other functions, and check file for 344 | validity. 345 | """ 346 | 347 | # Get metadata as text and as YAML. 348 | raw, header, body = split_metadata(path, data) 349 | 350 | # Do we have any blank lines in the header? 351 | check_blank_lines(reporter, raw) 352 | 353 | # Look through all header entries. If the category is in the input 354 | # file and is either required or we have actual data (as opposed to 355 | # a commented-out entry), we check it. If it *isn't* in the header 356 | # but is required, report an error. 357 | for category in HANDLERS: 358 | required, handler, message = HANDLERS[category] 359 | if category in header: 360 | if required or header[category]: 361 | reporter.check(handler(header[category]), 362 | None, 363 | '{0}\n actual value "{1}"', 364 | message, header[category]) 365 | elif required: 366 | reporter.add(None, 367 | 'Missing mandatory key "{0}"', 368 | category) 369 | 370 | # Check whether we have missing or too many categories 371 | seen_categories = set(header.keys()) 372 | check_categories(reporter, REQUIRED, seen_categories, 373 | 'Missing categories') 374 | check_categories(reporter, seen_categories, REQUIRED.union(OPTIONAL), 375 | 'Superfluous categories') 376 | 377 | 378 | def check_config(reporter, filename): 379 | """ 380 | Check YAML configuration file. 381 | """ 382 | 383 | config = load_yaml(filename) 384 | 385 | kind = config.get('kind', None) 386 | reporter.check(kind == 'workshop', 387 | filename, 388 | 'Missing or unknown kind of event: {0}', 389 | kind) 390 | 391 | carpentry = config.get('carpentry', None) 392 | reporter.check(carpentry in ('swc', 'dc'), 393 | filename, 394 | 'Missing or unknown carpentry: {0}', 395 | carpentry) 396 | 397 | 398 | def main(): 399 | '''Run as the main program.''' 400 | 401 | if len(sys.argv) != 2: 402 | print(USAGE, file=sys.stderr) 403 | sys.exit(1) 404 | 405 | root_dir = sys.argv[1] 406 | index_file = os.path.join(root_dir, 'index.md') 407 | config_file = os.path.join(root_dir, '_config.yml') 408 | 409 | reporter = Reporter() 410 | check_config(reporter, config_file) 411 | check_unwanted_files(root_dir, reporter) 412 | with open(index_file) as reader: 413 | data = reader.read() 414 | check_file(reporter, index_file, data) 415 | reporter.report() 416 | 417 | 418 | if __name__ == '__main__': 419 | main() 420 | -------------------------------------------------------------------------------- /bin/lesson_initialize.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """Initialize a newly-created repository.""" 4 | 5 | 6 | from __future__ import print_function 7 | import sys 8 | import os 9 | 10 | ROOT_AUTHORS = '''\ 11 | FIXME: list authors' names and email addresses. 12 | ''' 13 | 14 | ROOT_CITATION = '''\ 15 | FIXME: describe how to cite this lesson. 16 | ''' 17 | 18 | ROOT_CONTRIBUTING_MD = '''\ 19 | # Contributing 20 | 21 | [Software Carpentry][swc-site] and [Data Carpentry][dc-site] are open source projects, 22 | and we welcome contributions of all kinds: 23 | new lessons, 24 | fixes to existing material, 25 | bug reports, 26 | and reviews of proposed changes are all welcome. 27 | 28 | ## Contributor Agreement 29 | 30 | By contributing, 31 | you agree that we may redistribute your work under [our license](LICENSE.md). 32 | In exchange, 33 | we will address your issues and/or assess your change proposal as promptly as we can, 34 | and help you become a member of our community. 35 | Everyone involved in [Software Carpentry][swc-site] and [Data Carpentry][dc-site] 36 | agrees to abide by our [code of conduct](CONDUCT.md). 37 | 38 | ## How to Contribute 39 | 40 | The easiest way to get started is to file an issue 41 | to tell us about a spelling mistake, 42 | some awkward wording, 43 | or a factual error. 44 | This is a good way to introduce yourself 45 | and to meet some of our community members. 46 | 47 | 1. If you do not have a [GitHub][github] account, 48 | you can [send us comments by email][email]. 49 | However, 50 | we will be able to respond more quickly if you use one of the other methods described below. 51 | 52 | 2. If you have a [GitHub][github] account, 53 | or are willing to [create one][github-join], 54 | but do not know how to use Git, 55 | you can report problems or suggest improvements by [creating an issue][issues]. 56 | This allows us to assign the item to someone 57 | and to respond to it in a threaded discussion. 58 | 59 | 3. If you are comfortable with Git, 60 | and would like to add or change material, 61 | you can submit a pull request (PR). 62 | Instructions for doing this are [included below](#using-github). 63 | 64 | ## Where to Contribute 65 | 66 | 1. If you wish to change this lesson, 67 | please work in , 68 | which can be viewed at . 69 | 70 | 2. If you wish to change the example lesson, 71 | please work in , 72 | which documents the format of our lessons 73 | and can be viewed at . 74 | 75 | 3. If you wish to change the template used for workshop websites, 76 | please work in . 77 | The home page of that repository explains how to set up workshop websites, 78 | while the extra pages in 79 | provide more background on our design choices. 80 | 81 | 4. If you wish to change CSS style files, tools, 82 | or HTML boilerplate for lessons or workshops stored in `_includes` or `_layouts`, 83 | please work in . 84 | 85 | ## What to Contribute 86 | 87 | There are many ways to contribute, 88 | from writing new exercises and improving existing ones 89 | to updating or filling in the documentation 90 | and and submitting [bug reports][issues] 91 | about things that don't work, aren't clear, or are missing. 92 | If you are looking for ideas, 93 | please see [the list of issues for this repository][issues], 94 | or the issues for [Data Carpentry][dc-issues] 95 | and [Software Carpentry][swc-issues] projects. 96 | 97 | Comments on issues and reviews of pull requests are just as welcome: 98 | we are smarter together than we are on our own. 99 | Reviews from novices and newcomers are particularly valuable: 100 | it's easy for people who have been using these lessons for a while 101 | to forget how impenetrable some of this material can be, 102 | so fresh eyes are always welcome. 103 | 104 | ## What *Not* to Contribute 105 | 106 | Our lessons already contain more material than we can cover in a typical workshop, 107 | so we are usually *not* looking for more concepts or tools to add to them. 108 | As a rule, 109 | if you want to introduce a new idea, 110 | you must (a) estimate how long it will take to teach 111 | and (b) explain what you would take out to make room for it. 112 | The first encourages contributors to be honest about requirements; 113 | the second, to think hard about priorities. 114 | 115 | We are also not looking for exercises or other material that only run on one platform. 116 | Our workshops typically contain a mixture of Windows, macOS, and Linux users; 117 | in order to be usable, 118 | our lessons must run equally well on all three. 119 | 120 | ## Using GitHub 121 | 122 | If you choose to contribute via GitHub, 123 | you may want to look at 124 | [How to Contribute to an Open Source Project on GitHub][how-contribute]. 125 | In brief: 126 | 127 | 1. The published copy of the lesson is in the `gh-pages` branch of the repository 128 | (so that GitHub will regenerate it automatically). 129 | Please create all branches from that, 130 | and merge the [master repository][repo]'s `gh-pages` branch into your `gh-pages` branch 131 | before starting work. 132 | Please do *not* work directly in your `gh-pages` branch, 133 | since that will make it difficult for you to work on other contributions. 134 | 135 | 2. We use [GitHub flow][github-flow] to manage changes: 136 | 1. Create a new branch in your desktop copy of this repository for each significant change. 137 | 2. Commit the change in that branch. 138 | 3. Push that branch to your fork of this repository on GitHub. 139 | 4. Submit a pull request from that branch to the [master repository][repo]. 140 | 5. If you receive feedback, 141 | make changes on your desktop and push to your branch on GitHub: 142 | the pull request will update automatically. 143 | 144 | Each lesson has two maintainers who review issues and pull requests 145 | or encourage others to do so. 146 | The maintainers are community volunteers, 147 | and have final say over what gets merged into the lesson. 148 | 149 | ## Other Resources 150 | 151 | General discussion of [Software Carpentry][swc-site] and [Data Carpentry][dc-site] 152 | happens on the [discussion mailing list][discuss-list], 153 | which everyone is welcome to join. 154 | You can also [reach us by email][email]. 155 | 156 | [email]: mailto:admin@software-carpentry.org 157 | [dc-issues]: https://github.com/issues?q=user%3Adatacarpentry 158 | [dc-lessons]: http://datacarpentry.org/lessons/ 159 | [dc-site]: http://datacarpentry.org/ 160 | [discuss-list]: http://lists.software-carpentry.org/listinfo/discuss 161 | [github]: http://github.com 162 | [github-flow]: https://guides.github.com/introduction/flow/ 163 | [github-join]: https://github.com/join 164 | [how-contribute]: https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github 165 | [issues]: https://github.com/swcarpentry/FIXME/issues/ 166 | [repo]: https://github.com/swcarpentry/FIXME/ 167 | [swc-issues]: https://github.com/issues?q=user%3Aswcarpentry 168 | [swc-lessons]: http://software-carpentry.org/lessons/ 169 | [swc-site]: http://software-carpentry.org/ 170 | ''' 171 | 172 | ROOT_CONFIG_YML = '''\ 173 | #------------------------------------------------------------ 174 | # Values for this lesson. 175 | #------------------------------------------------------------ 176 | 177 | # Which carpentry is this ("swc", "dc", or "lc")? 178 | carpentry: "swc" 179 | 180 | # Overall title for pages. 181 | title: "Lesson Title" 182 | 183 | # Contact. This *must* include the protocol: if it's an email 184 | # address, it must look like "mailto:lessons@software-carpentry.org", 185 | # or if it's a URL, "https://gitter.im/username/ProjectName". 186 | email: "mailto:lessons@software-carpentry.org" 187 | 188 | #------------------------------------------------------------ 189 | # Generic settings (should not need to change). 190 | #------------------------------------------------------------ 191 | 192 | # What kind of thing is this ("workshop" or "lesson")? 193 | kind: "lesson" 194 | 195 | # Magic to make URLs resolve both locally and on GitHub. 196 | # See https://help.github.com/articles/repository-metadata-on-github-pages/. 197 | repository: / 198 | 199 | # Sites. 200 | amy_site: "https://amy.software-carpentry.org/workshops" 201 | dc_site: "http://datacarpentry.org" 202 | swc_github: "https://github.com/swcarpentry" 203 | swc_site: "https://software-carpentry.org" 204 | swc_pages: "https://swcarpentry.github.io" 205 | lc_site: "http://librarycarpentry.github.io/" 206 | template_repo: "https://github.com/swcarpentry/styles" 207 | example_repo: "https://github.com/swcarpentry/lesson-example" 208 | example_site: "https://swcarpentry.github.com/lesson-example" 209 | workshop_repo: "https://github.com/swcarpentry/workshop-template" 210 | workshop_site: "https://swcarpentry.github.io/workshop-template" 211 | training_site: "https://swcarpentry.github.io/instructor-training" 212 | 213 | # Surveys. 214 | swc_pre_survey: "https://www.surveymonkey.com/r/swc_pre_workshop_v1?workshop_id=" 215 | swc_post_survey: "https://www.surveymonkey.com/r/swc_post_workshop_v1?workshop_id=" 216 | dc_pre_survey: "https://www.surveymonkey.com/r/dcpostworkshopassessment?workshop_id=" 217 | dc_post_survey: "https://www.surveymonkey.com/r/dcpostworkshopassessment?workshop_id=" 218 | training_post_survey: "https://www.surveymonkey.com/r/post-instructor-training" 219 | 220 | # Start time in minutes (0 to be clock-independent, 540 to show a start at 09:00 am). 221 | start_time: 0 222 | 223 | # Specify that things in the episodes collection should be output. 224 | collections: 225 | episodes: 226 | output: true 227 | permalink: /:path/index.html 228 | extras: 229 | output: true 230 | permalink: /:path/index.html 231 | 232 | # Set the default layout for things in the episodes collection. 233 | defaults: 234 | - values: 235 | root: .. 236 | - scope: 237 | path: "" 238 | type: episodes 239 | values: 240 | layout: episode 241 | 242 | # Files and directories that are not to be copied. 243 | exclude: 244 | - Makefile 245 | - bin 246 | 247 | # Turn on built-in syntax highlighting. 248 | highlighter: rouge 249 | ''' 250 | 251 | ROOT_INDEX_MD = '''\ 252 | --- 253 | layout: lesson 254 | root: . 255 | permalink: index.html # Is the only page that don't follow the partner /:path/index.html 256 | --- 257 | FIXME: home page introduction 258 | 259 | > ## Prerequisites 260 | > 261 | > FIXME 262 | {: .prereq} 263 | ''' 264 | 265 | ROOT_REFERENCE_MD = '''\ 266 | --- 267 | layout: reference 268 | root: . 269 | --- 270 | 271 | ## Glossary 272 | 273 | FIXME 274 | ''' 275 | 276 | ROOT_SETUP_MD = '''\ 277 | --- 278 | layout: page 279 | title: Setup 280 | root: . 281 | --- 282 | FIXME 283 | ''' 284 | 285 | ROOT_AIO_MD = '''\ 286 | --- 287 | layout: page 288 | root: . 289 | --- 290 | 316 | {% comment %} 317 | Create anchor for each one of the episodes. 318 | {% endcomment %} 319 | {% for episode in site.episodes %} 320 |
321 | {% endfor %} 322 | ''' 323 | 324 | EPISODES_INTRODUCTION_MD = '''\ 325 | --- 326 | title: "Introduction" 327 | teaching: 0 328 | exercises: 0 329 | questions: 330 | - "Key question" 331 | objectives: 332 | - "First objective." 333 | keypoints: 334 | - "First key point." 335 | --- 336 | ''' 337 | 338 | EXTRAS_ABOUT_MD = '''\ 339 | --- 340 | layout: page 341 | title: About 342 | --- 343 | {% include carpentries.html %} 344 | ''' 345 | 346 | EXTRAS_DISCUSS_MD = '''\ 347 | --- 348 | layout: page 349 | title: Discussion 350 | --- 351 | FIXME 352 | ''' 353 | 354 | EXTRAS_FIGURES_MD = '''\ 355 | --- 356 | layout: page 357 | title: Figures 358 | --- 359 | 388 | {% comment %} 389 | Create anchor for each one of the episodes. 390 | {% endcomment %} 391 | {% for episode in site.episodes %} 392 |
393 | {% endfor %} 394 | ''' 395 | 396 | EXTRAS_GUIDE_MD = '''\ 397 | --- 398 | layout: page 399 | title: "Instructor Notes" 400 | --- 401 | FIXME 402 | ''' 403 | 404 | BOILERPLATE = ( 405 | ('AUTHORS', ROOT_AUTHORS), 406 | ('CITATION', ROOT_CITATION), 407 | ('CONTRIBUTING.md', ROOT_CONTRIBUTING_MD), 408 | ('_config.yml', ROOT_CONFIG_YML), 409 | ('index.md', ROOT_INDEX_MD), 410 | ('reference.md', ROOT_REFERENCE_MD), 411 | ('setup.md', ROOT_SETUP_MD), 412 | ('aio.md', ROOT_AIO_MD), 413 | ('_episodes/01-introduction.md', EPISODES_INTRODUCTION_MD), 414 | ('_extras/about.md', EXTRAS_ABOUT_MD), 415 | ('_extras/discuss.md', EXTRAS_DISCUSS_MD), 416 | ('_extras/figures.md', EXTRAS_FIGURES_MD), 417 | ('_extras/guide.md', EXTRAS_GUIDE_MD), 418 | ) 419 | 420 | 421 | def main(): 422 | """Check for collisions, then create.""" 423 | 424 | # Check. 425 | errors = False 426 | for (path, _) in BOILERPLATE: 427 | if os.path.exists(path): 428 | print('Warning: {0} already exists.'.format(path), file=sys.stderr) 429 | errors = True 430 | if errors: 431 | print('**Exiting without creating files.**', file=sys.stderr) 432 | sys.exit(1) 433 | 434 | # Create. 435 | for (path, content) in BOILERPLATE: 436 | with open(path, 'w') as writer: 437 | writer.write(content) 438 | 439 | 440 | if __name__ == '__main__': 441 | main() 442 | -------------------------------------------------------------------------------- /assets/img/lc-icon-black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml -------------------------------------------------------------------------------- /bin/lesson_check.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Check lesson files and their contents. 5 | """ 6 | 7 | from __future__ import print_function 8 | import sys 9 | import os 10 | import glob 11 | import json 12 | import re 13 | from optparse import OptionParser 14 | 15 | from util import Reporter, read_markdown, load_yaml, check_unwanted_files, require, IMAGE_FILE_SUFFIX 16 | 17 | __version__ = '0.3' 18 | 19 | # Where to look for source Markdown files. 20 | SOURCE_DIRS = ['', '_episodes', '_extras'] 21 | 22 | # Required files: each entry is ('path': YAML_required). 23 | # FIXME: We do not yet validate whether any files have the required 24 | # YAML headers, but should in the future. 25 | # The '%' is replaced with the source directory path for checking. 26 | # Episodes are handled specially, and extra files in '_extras' are also handled specially. 27 | # This list must include all the Markdown files listed in the 'bin/initialize' script. 28 | REQUIRED_FILES = { 29 | '%/CONDUCT.md': True, 30 | '%/CONTRIBUTING.md': False, 31 | '%/LICENSE.md': True, 32 | '%/README.md': False, 33 | '%/_extras/discuss.md': True, 34 | '%/_extras/guide.md': True, 35 | '%/index.md': True, 36 | '%/reference.md': True, 37 | '%/setup.md': True, 38 | } 39 | 40 | # Episode filename pattern. 41 | P_EPISODE_FILENAME = re.compile(r'/_episodes/(\d\d)-[-\w]+.md$') 42 | 43 | # Pattern to match lines ending with whitespace. 44 | P_TRAILING_WHITESPACE = re.compile(r'\s+$') 45 | 46 | # Pattern to match figure references in HTML. 47 | P_FIGURE_REFS = re.compile(r']+src="([^"]+)"[^>]*>') 48 | 49 | # Pattern to match internally-defined Markdown links. 50 | P_INTERNAL_LINK_REF = re.compile(r'\[([^\]]+)\]\[([^\]]+)\]') 51 | 52 | # Pattern to match reference links (to resolve internally-defined references). 53 | P_INTERNAL_LINK_DEF = re.compile(r'^\[([^\]]+)\]:\s*(.+)') 54 | 55 | # What kinds of blockquotes are allowed? 56 | KNOWN_BLOCKQUOTES = { 57 | 'callout', 58 | 'challenge', 59 | 'checklist', 60 | 'discussion', 61 | 'keypoints', 62 | 'objectives', 63 | 'prereq', 64 | 'quotation', 65 | 'solution', 66 | 'testimonial' 67 | } 68 | 69 | # What kinds of code fragments are allowed? 70 | KNOWN_CODEBLOCKS = { 71 | 'error', 72 | 'output', 73 | 'source', 74 | 'language-bash', 75 | 'html', 76 | 'language-make', 77 | 'language-matlab', 78 | 'language-python', 79 | 'language-r', 80 | 'language-shell', 81 | 'language-sql' 82 | } 83 | 84 | # What fields are required in teaching episode metadata? 85 | TEACHING_METADATA_FIELDS = { 86 | ('title', str), 87 | ('teaching', int), 88 | ('exercises', int), 89 | ('questions', list), 90 | ('objectives', list), 91 | ('keypoints', list) 92 | } 93 | 94 | # What fields are required in break episode metadata? 95 | BREAK_METADATA_FIELDS = { 96 | ('layout', str), 97 | ('title', str), 98 | ('break', int) 99 | } 100 | 101 | # How long are lines allowed to be? 102 | MAX_LINE_LEN = 100 103 | 104 | def main(): 105 | """Main driver.""" 106 | 107 | args = parse_args() 108 | args.reporter = Reporter() 109 | check_config(args.reporter, args.source_dir) 110 | args.references = read_references(args.reporter, args.reference_path) 111 | 112 | docs = read_all_markdown(args.source_dir, args.parser) 113 | check_fileset(args.source_dir, args.reporter, docs.keys()) 114 | check_unwanted_files(args.source_dir, args.reporter) 115 | for filename in docs.keys(): 116 | checker = create_checker(args, filename, docs[filename]) 117 | checker.check() 118 | 119 | args.reporter.report() 120 | 121 | 122 | def parse_args(): 123 | """Parse command-line arguments.""" 124 | 125 | parser = OptionParser() 126 | parser.add_option('-l', '--linelen', 127 | default=False, 128 | action="store_true", 129 | dest='line_lengths', 130 | help='Check line lengths') 131 | parser.add_option('-p', '--parser', 132 | default=None, 133 | dest='parser', 134 | help='path to Markdown parser') 135 | parser.add_option('-r', '--references', 136 | default=None, 137 | dest='reference_path', 138 | help='path to Markdown file of external references') 139 | parser.add_option('-s', '--source', 140 | default=os.curdir, 141 | dest='source_dir', 142 | help='source directory') 143 | parser.add_option('-w', '--whitespace', 144 | default=False, 145 | action="store_true", 146 | dest='trailing_whitespace', 147 | help='Check for trailing whitespace') 148 | 149 | args, extras = parser.parse_args() 150 | require(args.parser is not None, 151 | 'Path to Markdown parser not provided') 152 | require(not extras, 153 | 'Unexpected trailing command-line arguments "{0}"'.format(extras)) 154 | 155 | return args 156 | 157 | 158 | def check_config(reporter, source_dir): 159 | """Check configuration file.""" 160 | 161 | config_file = os.path.join(source_dir, '_config.yml') 162 | config = load_yaml(config_file) 163 | reporter.check_field(config_file, 'configuration', config, 'kind', 'lesson') 164 | reporter.check_field(config_file, 'configuration', config, 'carpentry', ('swc', 'dc', 'lc')) 165 | reporter.check_field(config_file, 'configuration', config, 'title') 166 | reporter.check_field(config_file, 'configuration', config, 'email') 167 | 168 | reporter.check({'values': {'root': '..'}} in config.get('defaults', []), 169 | 'configuration', 170 | '"root" not set to ".." in configuration') 171 | 172 | 173 | def read_references(reporter, ref_path): 174 | """Read shared file of reference links, returning dictionary of valid references 175 | {symbolic_name : URL} 176 | """ 177 | 178 | result = {} 179 | urls_seen = set() 180 | if ref_path: 181 | with open(ref_path, 'r') as reader: 182 | for (num, line) in enumerate(reader): 183 | line_num = num + 1 184 | m = P_INTERNAL_LINK_DEF.search(line) 185 | require(m, 186 | '{0}:{1} not valid reference:\n{2}'.format(ref_path, line_num, line.rstrip())) 187 | name = m.group(1) 188 | url = m.group(2) 189 | require(name, 190 | 'Empty reference at {0}:{1}'.format(ref_path, line_num)) 191 | reporter.check(name not in result, 192 | ref_path, 193 | 'Duplicate reference {0} at line {1}', 194 | name, line_num) 195 | reporter.check(url not in urls_seen, 196 | ref_path, 197 | 'Duplicate definition of URL {0} at line {1}', 198 | url, line_num) 199 | result[name] = url 200 | urls_seen.add(url) 201 | return result 202 | 203 | 204 | def read_all_markdown(source_dir, parser): 205 | """Read source files, returning 206 | {path : {'metadata':yaml, 'metadata_len':N, 'text':text, 'lines':[(i, line, len)], 'doc':doc}} 207 | """ 208 | 209 | all_dirs = [os.path.join(source_dir, d) for d in SOURCE_DIRS] 210 | all_patterns = [os.path.join(d, '*.md') for d in all_dirs] 211 | result = {} 212 | for pat in all_patterns: 213 | for filename in glob.glob(pat): 214 | data = read_markdown(parser, filename) 215 | if data: 216 | result[filename] = data 217 | return result 218 | 219 | 220 | def check_fileset(source_dir, reporter, filenames_present): 221 | """Are all required files present? Are extraneous files present?""" 222 | 223 | # Check files with predictable names. 224 | required = [p.replace('%', source_dir) for p in REQUIRED_FILES] 225 | missing = set(required) - set(filenames_present) 226 | for m in missing: 227 | reporter.add(None, 'Missing required file {0}', m) 228 | 229 | # Check episode files' names. 230 | seen = [] 231 | for filename in filenames_present: 232 | if '_episodes' not in filename: 233 | continue 234 | m = P_EPISODE_FILENAME.search(filename) 235 | if m and m.group(1): 236 | seen.append(m.group(1)) 237 | else: 238 | reporter.add(None, 'Episode {0} has badly-formatted filename', filename) 239 | 240 | # Check for duplicate episode numbers. 241 | reporter.check(len(seen) == len(set(seen)), 242 | None, 243 | 'Duplicate episode numbers {0} vs {1}', 244 | sorted(seen), sorted(set(seen))) 245 | 246 | # Check that numbers are consecutive. 247 | seen = [int(s) for s in seen] 248 | seen.sort() 249 | clean = True 250 | for i in range(len(seen) - 1): 251 | clean = clean and ((seen[i+1] - seen[i]) == 1) 252 | reporter.check(clean, 253 | None, 254 | 'Missing or non-consecutive episode numbers {0}', 255 | seen) 256 | 257 | 258 | def create_checker(args, filename, info): 259 | """Create appropriate checker for file.""" 260 | 261 | for (pat, cls) in CHECKERS: 262 | if pat.search(filename): 263 | return cls(args, filename, **info) 264 | 265 | 266 | class CheckBase(object): 267 | """Base class for checking Markdown files.""" 268 | 269 | def __init__(self, args, filename, metadata, metadata_len, text, lines, doc): 270 | """Cache arguments for checking.""" 271 | 272 | super(CheckBase, self).__init__() 273 | self.args = args 274 | self.reporter = self.args.reporter # for convenience 275 | self.filename = filename 276 | self.metadata = metadata 277 | self.metadata_len = metadata_len 278 | self.text = text 279 | self.lines = lines 280 | self.doc = doc 281 | 282 | self.layout = None 283 | 284 | 285 | def check(self): 286 | """Run tests.""" 287 | 288 | self.check_metadata() 289 | self.check_line_lengths() 290 | self.check_trailing_whitespace() 291 | self.check_blockquote_classes() 292 | self.check_codeblock_classes() 293 | self.check_defined_link_references() 294 | 295 | 296 | def check_metadata(self): 297 | """Check the YAML metadata.""" 298 | 299 | self.reporter.check(self.metadata is not None, 300 | self.filename, 301 | 'Missing metadata entirely') 302 | 303 | if self.metadata and (self.layout is not None): 304 | self.reporter.check_field(self.filename, 'metadata', self.metadata, 'layout', self.layout) 305 | 306 | 307 | def check_line_lengths(self): 308 | """Check the raw text of the lesson body.""" 309 | 310 | if self.args.line_lengths: 311 | over = [i for (i, l, n) in self.lines if (n > MAX_LINE_LEN) and (not l.startswith('!'))] 312 | self.reporter.check(not over, 313 | self.filename, 314 | 'Line(s) are too long: {0}', 315 | ', '.join([str(i) for i in over])) 316 | 317 | 318 | def check_trailing_whitespace(self): 319 | """Check for whitespace at the ends of lines.""" 320 | 321 | if self.args.trailing_whitespace: 322 | trailing = [i for (i, l, n) in self.lines if P_TRAILING_WHITESPACE.match(l)] 323 | self.reporter.check(not trailing, 324 | self.filename, 325 | 'Line(s) end with whitespace: {0}', 326 | ', '.join([str(i) for i in trailing])) 327 | 328 | 329 | def check_blockquote_classes(self): 330 | """Check that all blockquotes have known classes.""" 331 | 332 | for node in self.find_all(self.doc, {'type' : 'blockquote'}): 333 | cls = self.get_val(node, 'attr', 'class') 334 | self.reporter.check(cls in KNOWN_BLOCKQUOTES, 335 | (self.filename, self.get_loc(node)), 336 | 'Unknown or missing blockquote type {0}', 337 | cls) 338 | 339 | 340 | def check_codeblock_classes(self): 341 | """Check that all code blocks have known classes.""" 342 | 343 | for node in self.find_all(self.doc, {'type' : 'codeblock'}): 344 | cls = self.get_val(node, 'attr', 'class') 345 | self.reporter.check(cls in KNOWN_CODEBLOCKS, 346 | (self.filename, self.get_loc(node)), 347 | 'Unknown or missing code block type {0}', 348 | cls) 349 | 350 | 351 | def check_defined_link_references(self): 352 | """Check that defined links resolve in the file. 353 | 354 | Internally-defined links match the pattern [text][label]. 355 | """ 356 | 357 | result = set() 358 | for node in self.find_all(self.doc, {'type' : 'text'}): 359 | for match in P_INTERNAL_LINK_REF.findall(node['value']): 360 | text = match[0] 361 | link = match[1] 362 | if link not in self.args.references: 363 | result.add('"{0}"=>"{1}"'.format(text, link)) 364 | self.reporter.check(not result, 365 | self.filename, 366 | 'Internally-defined links may be missing definitions: {0}', 367 | ', '.join(sorted(result))) 368 | 369 | 370 | def find_all(self, node, pattern, accum=None): 371 | """Find all matches for a pattern.""" 372 | 373 | assert type(pattern) == dict, 'Patterns must be dictionaries' 374 | if accum is None: 375 | accum = [] 376 | if self.match(node, pattern): 377 | accum.append(node) 378 | for child in node.get('children', []): 379 | self.find_all(child, pattern, accum) 380 | return accum 381 | 382 | 383 | def match(self, node, pattern): 384 | """Does this node match the given pattern?""" 385 | 386 | for key in pattern: 387 | if key not in node: 388 | return False 389 | val = pattern[key] 390 | if type(val) == str: 391 | if node[key] != val: 392 | return False 393 | elif type(val) == dict: 394 | if not self.match(node[key], val): 395 | return False 396 | return True 397 | 398 | 399 | def get_val(self, node, *chain): 400 | """Get value one or more levels down.""" 401 | 402 | curr = node 403 | for selector in chain: 404 | curr = curr.get(selector, None) 405 | if curr is None: 406 | break 407 | return curr 408 | 409 | 410 | def get_loc(self, node): 411 | """Convenience method to get node's line number.""" 412 | 413 | result = self.get_val(node, 'options', 'location') 414 | if self.metadata_len is not None: 415 | result += self.metadata_len 416 | return result 417 | 418 | 419 | class CheckNonJekyll(CheckBase): 420 | """Check a file that isn't translated by Jekyll.""" 421 | 422 | def __init__(self, args, filename, metadata, metadata_len, text, lines, doc): 423 | super(CheckNonJekyll, self).__init__(args, filename, metadata, metadata_len, text, lines, doc) 424 | 425 | 426 | def check_metadata(self): 427 | self.reporter.check(self.metadata is None, 428 | self.filename, 429 | 'Unexpected metadata') 430 | 431 | 432 | class CheckIndex(CheckBase): 433 | """Check the main index page.""" 434 | 435 | def __init__(self, args, filename, metadata, metadata_len, text, lines, doc): 436 | super(CheckIndex, self).__init__(args, filename, metadata, metadata_len, text, lines, doc) 437 | self.layout = 'lesson' 438 | 439 | def check_metadata(self): 440 | super(CheckIndex, self).check_metadata() 441 | self.reporter.check(self.metadata.get('root', '') == '.', 442 | self.filename, 443 | 'Root not set to "."') 444 | 445 | 446 | class CheckEpisode(CheckBase): 447 | """Check an episode page.""" 448 | 449 | def __init__(self, args, filename, metadata, metadata_len, text, lines, doc): 450 | super(CheckEpisode, self).__init__(args, filename, metadata, metadata_len, text, lines, doc) 451 | 452 | 453 | def check(self): 454 | """Run extra tests.""" 455 | 456 | super(CheckEpisode, self).check() 457 | self.check_reference_inclusion() 458 | 459 | 460 | def check_metadata(self): 461 | super(CheckEpisode, self).check_metadata() 462 | if self.metadata: 463 | if 'layout' in self.metadata: 464 | if self.metadata['layout'] == 'break': 465 | self.check_metadata_fields(BREAK_METADATA_FIELDS) 466 | else: 467 | self.reporter.add(self.filename, 468 | 'Unknown episode layout "{0}"', 469 | self.metadata['layout']) 470 | else: 471 | self.check_metadata_fields(TEACHING_METADATA_FIELDS) 472 | 473 | 474 | def check_metadata_fields(self, expected): 475 | for (name, type_) in expected: 476 | if name not in self.metadata: 477 | self.reporter.add(self.filename, 478 | 'Missing metadata field {0}', 479 | name) 480 | elif type(self.metadata[name]) != type_: 481 | self.reporter.add(self.filename, 482 | '"{0}" has wrong type in metadata ({1} instead of {2})', 483 | name, type(self.metadata[name]), type_) 484 | 485 | 486 | def check_reference_inclusion(self): 487 | """Check that links file has been included.""" 488 | 489 | if not self.args.reference_path: 490 | return 491 | 492 | for (i, last_line, line_len) in reversed(self.lines): 493 | if last_line: 494 | break 495 | 496 | require(last_line, 497 | 'No non-empty lines in {0}'.format(self.filename)) 498 | 499 | include_filename = os.path.split(self.args.reference_path)[-1] 500 | if include_filename not in last_line: 501 | self.reporter.add(self.filename, 502 | 'episode does not include "{0}"', 503 | include_filename) 504 | 505 | 506 | class CheckReference(CheckBase): 507 | """Check the reference page.""" 508 | 509 | def __init__(self, args, filename, metadata, metadata_len, text, lines, doc): 510 | super(CheckReference, self).__init__(args, filename, metadata, metadata_len, text, lines, doc) 511 | self.layout = 'reference' 512 | 513 | 514 | class CheckGeneric(CheckBase): 515 | """Check a generic page.""" 516 | 517 | def __init__(self, args, filename, metadata, metadata_len, text, lines, doc): 518 | super(CheckGeneric, self).__init__(args, filename, metadata, metadata_len, text, lines, doc) 519 | self.layout = 'page' 520 | 521 | 522 | CHECKERS = [ 523 | (re.compile(r'CONTRIBUTING\.md'), CheckNonJekyll), 524 | (re.compile(r'README\.md'), CheckNonJekyll), 525 | (re.compile(r'index\.md'), CheckIndex), 526 | (re.compile(r'reference\.md'), CheckReference), 527 | (re.compile(r'_episodes/.*\.md'), CheckEpisode), 528 | (re.compile(r'.*\.md'), CheckGeneric) 529 | ] 530 | 531 | 532 | if __name__ == '__main__': 533 | main() 534 | --------------------------------------------------------------------------------